1use std::{
35 cmp,
36 collections::{HashMap, HashSet},
37 fmt,
38 ops::RangeInclusive,
39 sync::Arc,
40 time::Duration,
41};
42
43use chrono::Utc;
44use derive_getters::Getters;
45use derive_new::new;
46use futures::{future::OptionFuture, stream::FuturesOrdered, StreamExt, TryFutureExt};
47use hex::{FromHex, ToHex};
48use indexmap::IndexMap;
49use jsonrpsee::core::{async_trait, RpcResult as Result};
50use jsonrpsee_proc_macros::rpc;
51use jsonrpsee_types::{ErrorCode, ErrorObject};
52use schemars::JsonSchema;
53use tokio::{
54 sync::{broadcast, mpsc, watch},
55 task::JoinHandle,
56};
57use tower::ServiceExt;
58use tracing::Instrument;
59
60use zcash_address::{unified::Encoding, TryFromAddress};
61use zcash_protocol::consensus::{self, Parameters};
62use zebra_chain::{
63 amount::{Amount, NegativeAllowed},
64 block::{self, Block, Commitment, Height, SerializedBlock, TryIntoHeight},
65 chain_sync_status::ChainSyncStatus,
66 chain_tip::{ChainTip, NetworkChainTipHeightEstimator},
67 parameters::{
68 subsidy::{
69 block_subsidy, founders_reward, funding_stream_values, miner_subsidy,
70 FundingStreamReceiver,
71 },
72 ConsensusBranchId, Network, NetworkUpgrade, POW_AVERAGING_WINDOW,
73 },
74 serialization::{BytesInDisplayOrder, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
75 subtree::NoteCommitmentSubtreeIndex,
76 transaction::{self, SerializedTransaction, Transaction, UnminedTx},
77 transparent::{self, Address, OutputIndex},
78 value_balance::ValueBalance,
79 work::{
80 difficulty::{CompactDifficulty, ExpandedDifficulty, ParameterDifficulty, U256},
81 equihash::Solution,
82 },
83};
84use zebra_consensus::{
85 funding_stream_address, router::service_trait::BlockVerifierService, RouterError,
86};
87use zebra_network::{address_book_peers::AddressBookPeers, types::PeerServices, PeerSocketAddr};
88use zebra_node_services::mempool::{self, CreatedOrSpent, MempoolService};
89use zebra_state::{
90 AnyTx, HashOrHeight, OutputLocation, ReadRequest, ReadResponse, ReadState as ReadStateService,
91 State as StateService, TransactionLocation,
92};
93
94use crate::{
95 client::TransactionTemplate,
96 client::Treestate,
97 config,
98 methods::types::{
99 validate_address::validate_address, z_validate_address::z_validate_address, zec::Zec,
100 },
101 queue::Queue,
102 server::{
103 self,
104 error::{MapError, OkOrError},
105 },
106};
107
108pub(crate) mod hex_data;
109pub(crate) mod trees;
110pub(crate) mod types;
111
112use hex_data::HexData;
113use trees::{GetSubtreesByIndexResponse, GetTreestateResponse, SubtreeRpcData};
114use types::{
115 get_block_template::{
116 constants::{
117 DEFAULT_SOLUTION_RATE_WINDOW_SIZE, MEMPOOL_LONG_POLL_INTERVAL,
118 ZCASHD_FUNDING_STREAM_ORDER,
119 },
120 proposal::proposal_block_from_template,
121 BlockTemplateResponse, BlockTemplateTimeSource, GetBlockTemplateHandler,
122 GetBlockTemplateParameters, GetBlockTemplateResponse,
123 },
124 get_blockchain_info::GetBlockchainInfoBalance,
125 get_mempool_info::GetMempoolInfoResponse,
126 get_mining_info::GetMiningInfoResponse,
127 get_raw_mempool::{self, GetRawMempoolResponse},
128 long_poll::LongPollInput,
129 network_info::{GetNetworkInfoResponse, NetworkInfo},
130 peer_info::PeerInfo,
131 submit_block::{SubmitBlockErrorResponse, SubmitBlockParameters, SubmitBlockResponse},
132 subsidy::GetBlockSubsidyResponse,
133 transaction::TransactionObject,
134 unified_address::ZListUnifiedReceiversResponse,
135 validate_address::ValidateAddressResponse,
136 z_validate_address::ZValidateAddressResponse,
137};
138
139include!(concat!(env!("OUT_DIR"), "/rpc_openrpc.rs"));
140
141pub(super) const PARAM_VERBOSE_DESC: &str =
144 "Boolean flag to indicate verbosity, true for a json object, false for hex encoded data.";
145pub(super) const PARAM_POOL_DESC: &str =
146 "The pool from which subtrees should be returned. Either \"sapling\" or \"orchard\".";
147pub(super) const PARAM_START_INDEX_DESC: &str =
148 "The index of the first 2^16-leaf subtree to return.";
149pub(super) const PARAM_LIMIT_DESC: &str = "The maximum number of subtrees to return.";
150pub(super) const PARAM_REQUEST_DESC: &str = "The request object containing the parameters.";
151pub(super) const PARAM_INDEX_DESC: &str = "The index of the subtree to return.";
152pub(super) const PARAM_RAW_TRANSACTION_HEX_DESC: &str = "The hex-encoded raw transaction bytes.";
153#[allow(non_upper_case_globals)]
154pub(super) const PARAM__ALLOW_HIGH_FEES_DESC: &str = "Whether to allow high fees.";
155pub(super) const PARAM_NUM_BLOCKS_DESC: &str = "The number of blocks to return.";
156pub(super) const PARAM_HEIGHT_DESC: &str = "The height of the block to return.";
157pub(super) const PARAM_COMMAND_DESC: &str = "The command to execute.";
158#[allow(non_upper_case_globals)]
159pub(super) const PARAM__PARAMETERS_DESC: &str = "The parameters for the command.";
160pub(super) const PARAM_BLOCK_HASH_DESC: &str = "The hash of the block to return.";
161pub(super) const PARAM_ADDRESS_DESC: &str = "The address to return.";
162pub(super) const PARAM_ADDRESS_STRINGS_DESC: &str = "The addresses to return.";
163pub(super) const PARAM_ADDR_DESC: &str = "The address to return.";
164pub(super) const PARAM_HEX_DATA_DESC: &str = "The hex-encoded data to return.";
165pub(super) const PARAM_TXID_DESC: &str = "The transaction ID to return.";
166pub(super) const PARAM_HASH_OR_HEIGHT_DESC: &str = "The block hash or height to return.";
167pub(super) const PARAM_PARAMETERS_DESC: &str = "The parameters for the command.";
168pub(super) const PARAM_VERBOSITY_DESC: &str = "Whether to include verbose output.";
169pub(super) const PARAM_N_DESC: &str = "The output index in the transaction.";
170pub(super) const PARAM_INCLUDE_MEMPOOL_DESC: &str =
171 "Whether to include mempool transactions in the response.";
172
173#[cfg(test)]
174mod tests;
175
176#[rpc(server)]
177pub trait Rpc {
179 #[method(name = "getinfo")]
194 async fn get_info(&self) -> Result<GetInfoResponse>;
195
196 #[method(name = "getblockchaininfo")]
207 async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse>;
208
209 #[method(name = "getaddressbalance")]
232 async fn get_address_balance(
233 &self,
234 address_strings: GetAddressBalanceRequest,
235 ) -> Result<GetAddressBalanceResponse>;
236
237 #[method(name = "sendrawtransaction")]
254 async fn send_raw_transaction(
255 &self,
256 raw_transaction_hex: String,
257 _allow_high_fees: Option<bool>,
258 ) -> Result<SendRawTransactionResponse>;
259
260 #[method(name = "getblock")]
280 async fn get_block(
281 &self,
282 hash_or_height: String,
283 verbosity: Option<u8>,
284 ) -> Result<GetBlockResponse>;
285
286 #[method(name = "getblockheader")]
304 async fn get_block_header(
305 &self,
306 hash_or_height: String,
307 verbose: Option<bool>,
308 ) -> Result<GetBlockHeaderResponse>;
309
310 #[method(name = "getbestblockhash")]
316 fn get_best_block_hash(&self) -> Result<GetBlockHashResponse>;
317
318 #[method(name = "getbestblockheightandhash")]
324 fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse>;
325
326 #[method(name = "getmempoolinfo")]
330 async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse>;
331
332 #[method(name = "getrawmempool")]
342 async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse>;
343
344 #[method(name = "z_gettreestate")]
361 async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse>;
362
363 #[method(name = "z_getsubtreesbyindex")]
382 async fn z_get_subtrees_by_index(
383 &self,
384 pool: String,
385 start_index: NoteCommitmentSubtreeIndex,
386 limit: Option<NoteCommitmentSubtreeIndex>,
387 ) -> Result<GetSubtreesByIndexResponse>;
388
389 #[method(name = "getrawtransaction")]
401 async fn get_raw_transaction(
402 &self,
403 txid: String,
404 verbose: Option<u8>,
405 block_hash: Option<String>,
406 ) -> Result<GetRawTransactionResponse>;
407
408 #[method(name = "getaddresstxids")]
438 async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>>;
439
440 #[method(name = "getaddressutxos")]
459 async fn get_address_utxos(
460 &self,
461 request: GetAddressUtxosRequest,
462 ) -> Result<GetAddressUtxosResponse>;
463
464 #[method(name = "stop")]
475 fn stop(&self) -> Result<String>;
476
477 #[method(name = "getblockcount")]
484 fn get_block_count(&self) -> Result<u32>;
485
486 #[method(name = "getblockhash")]
502 async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse>;
503
504 #[method(name = "getblocktemplate")]
526 async fn get_block_template(
527 &self,
528 parameters: Option<GetBlockTemplateParameters>,
529 ) -> Result<GetBlockTemplateResponse>;
530
531 #[method(name = "submitblock")]
547 async fn submit_block(
548 &self,
549 hex_data: HexData,
550 _parameters: Option<SubmitBlockParameters>,
551 ) -> Result<SubmitBlockResponse>;
552
553 #[method(name = "getmininginfo")]
559 async fn get_mining_info(&self) -> Result<GetMiningInfoResponse>;
560
561 #[method(name = "getnetworksolps")]
572 async fn get_network_sol_ps(&self, num_blocks: Option<i32>, height: Option<i32>)
573 -> Result<u64>;
574
575 #[method(name = "getnetworkhashps")]
585 async fn get_network_hash_ps(
586 &self,
587 num_blocks: Option<i32>,
588 height: Option<i32>,
589 ) -> Result<u64> {
590 self.get_network_sol_ps(num_blocks, height).await
591 }
592
593 #[method(name = "getnetworkinfo")]
599 async fn get_network_info(&self) -> Result<GetNetworkInfoResponse>;
600
601 #[method(name = "getpeerinfo")]
607 async fn get_peer_info(&self) -> Result<Vec<PeerInfo>>;
608
609 #[method(name = "ping")]
618 async fn ping(&self) -> Result<()>;
619
620 #[method(name = "validateaddress")]
631 async fn validate_address(&self, address: String) -> Result<ValidateAddressResponse>;
632
633 #[method(name = "z_validateaddress")]
648 async fn z_validate_address(&self, address: String) -> Result<ZValidateAddressResponse>;
649
650 #[method(name = "getblocksubsidy")]
665 async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse>;
666
667 #[method(name = "getdifficulty")]
673 async fn get_difficulty(&self) -> Result<f64>;
674
675 #[method(name = "z_listunifiedreceivers")]
689 async fn z_list_unified_receivers(
690 &self,
691 address: String,
692 ) -> Result<ZListUnifiedReceiversResponse>;
693
694 #[method(name = "invalidateblock")]
702 async fn invalidate_block(&self, block_hash: String) -> Result<()>;
703
704 #[method(name = "reconsiderblock")]
710 async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>>;
711
712 #[method(name = "generate")]
713 async fn generate(&self, num_blocks: u32) -> Result<Vec<GetBlockHashResponse>>;
727
728 #[method(name = "addnode")]
729 async fn add_node(&self, addr: PeerSocketAddr, command: AddNodeCommand) -> Result<()>;
744
745 #[method(name = "rpc.discover")]
747 fn openrpc(&self) -> openrpsee::openrpc::Response;
748 #[method(name = "gettxout")]
760 async fn get_tx_out(
761 &self,
762 txid: String,
763 n: u32,
764 include_mempool: Option<bool>,
765 ) -> Result<GetTxOutResponse>;
766}
767
768#[derive(Clone)]
770pub struct RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
771where
772 Mempool: MempoolService,
773 State: StateService,
774 ReadState: ReadStateService,
775 Tip: ChainTip + Clone + Send + Sync + 'static,
776 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
777 BlockVerifierRouter: BlockVerifierService,
778 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
779{
780 build_version: String,
784
785 user_agent: String,
787
788 network: Network,
790
791 debug_force_finished_sync: bool,
794
795 mempool: Mempool,
799
800 state: State,
802
803 read_state: ReadState,
805
806 latest_chain_tip: Tip,
808
809 queue_sender: broadcast::Sender<UnminedTx>,
813
814 address_book: AddressBook,
816
817 last_warn_error_log_rx: LoggedLastEvent,
819
820 gbt: GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>,
822}
823
824pub type LoggedLastEvent = watch::Receiver<Option<(String, tracing::Level, chrono::DateTime<Utc>)>>;
826
827impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> fmt::Debug
828 for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
829where
830 Mempool: MempoolService,
831 State: StateService,
832 ReadState: ReadStateService,
833 Tip: ChainTip + Clone + Send + Sync + 'static,
834 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
835 BlockVerifierRouter: BlockVerifierService,
836 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
837{
838 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
839 f.debug_struct("RpcImpl")
841 .field("build_version", &self.build_version)
842 .field("user_agent", &self.user_agent)
843 .field("network", &self.network)
844 .field("debug_force_finished_sync", &self.debug_force_finished_sync)
845 .field("getblocktemplate", &self.gbt)
846 .finish()
847 }
848}
849
850impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
851 RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
852where
853 Mempool: MempoolService,
854 State: StateService,
855 ReadState: ReadStateService,
856 Tip: ChainTip + Clone + Send + Sync + 'static,
857 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
858 BlockVerifierRouter: BlockVerifierService,
859 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
860{
861 #[allow(clippy::too_many_arguments)]
866 pub fn new<VersionString, UserAgentString>(
867 network: Network,
868 mining_config: config::mining::Config,
869 debug_force_finished_sync: bool,
870 build_version: VersionString,
871 user_agent: UserAgentString,
872 mempool: Mempool,
873 state: State,
874 read_state: ReadState,
875 block_verifier_router: BlockVerifierRouter,
876 sync_status: SyncStatus,
877 latest_chain_tip: Tip,
878 address_book: AddressBook,
879 last_warn_error_log_rx: LoggedLastEvent,
880 mined_block_sender: Option<mpsc::Sender<(block::Hash, block::Height)>>,
881 ) -> (Self, JoinHandle<()>)
882 where
883 VersionString: ToString + Clone + Send + 'static,
884 UserAgentString: ToString + Clone + Send + 'static,
885 {
886 let (runner, queue_sender) = Queue::start();
887
888 let mut build_version = build_version.to_string();
889 let user_agent = user_agent.to_string();
890
891 if !build_version.is_empty() && !build_version.starts_with('v') {
893 build_version.insert(0, 'v');
894 }
895
896 let gbt = GetBlockTemplateHandler::new(
897 &network,
898 mining_config.clone(),
899 block_verifier_router,
900 sync_status,
901 mined_block_sender,
902 );
903
904 let rpc_impl = RpcImpl {
905 build_version,
906 user_agent,
907 network: network.clone(),
908 debug_force_finished_sync,
909 mempool: mempool.clone(),
910 state: state.clone(),
911 read_state: read_state.clone(),
912 latest_chain_tip: latest_chain_tip.clone(),
913 queue_sender,
914 address_book,
915 last_warn_error_log_rx,
916 gbt,
917 };
918
919 let rpc_tx_queue_task_handle = tokio::spawn(
921 runner
922 .run(mempool, read_state, latest_chain_tip, network)
923 .in_current_span(),
924 );
925
926 (rpc_impl, rpc_tx_queue_task_handle)
927 }
928
929 pub fn network(&self) -> &Network {
931 &self.network
932 }
933}
934
935#[async_trait]
936impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> RpcServer
937 for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
938where
939 Mempool: MempoolService,
940 State: StateService,
941 ReadState: ReadStateService,
942 Tip: ChainTip + Clone + Send + Sync + 'static,
943 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
944 BlockVerifierRouter: BlockVerifierService,
945 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
946{
947 async fn get_info(&self) -> Result<GetInfoResponse> {
948 let version = GetInfoResponse::version_from_string(&self.build_version)
949 .expect("invalid version string");
950
951 let connections = self.address_book.recently_live_peers(Utc::now()).len();
952
953 let last_error_recorded = self.last_warn_error_log_rx.borrow().clone();
954 let (last_error_log, _level, last_error_log_time) = last_error_recorded.unwrap_or((
955 GetInfoResponse::default().errors,
956 tracing::Level::INFO,
957 Utc::now(),
958 ));
959
960 let tip_height = self
961 .latest_chain_tip
962 .best_tip_height()
963 .unwrap_or(Height::MIN);
964 let testnet = self.network.is_a_test_network();
965
966 let pay_tx_fee = 0.0;
972
973 let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
974 / (zebra_chain::amount::COIN as f64);
975 let difficulty = chain_tip_difficulty(self.network.clone(), self.read_state.clone(), true)
976 .await
977 .expect("should always be Ok when `should_use_default` is true");
978
979 let response = GetInfoResponse {
980 version,
981 build: self.build_version.clone(),
982 subversion: self.user_agent.clone(),
983 protocol_version: zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0,
984 blocks: tip_height.0,
985 connections,
986 proxy: None,
987 difficulty,
988 testnet,
989 pay_tx_fee,
990 relay_fee,
991 errors: last_error_log,
992 errors_timestamp: last_error_log_time.timestamp(),
993 };
994
995 Ok(response)
996 }
997
998 #[allow(clippy::unwrap_in_result)]
999 async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse> {
1000 let debug_force_finished_sync = self.debug_force_finished_sync;
1001 let network = &self.network;
1002
1003 let (usage_info_rsp, tip_pool_values_rsp, chain_tip_difficulty) = {
1004 use zebra_state::ReadRequest::*;
1005 let state_call = |request| self.read_state.clone().oneshot(request);
1006 tokio::join!(
1007 state_call(UsageInfo),
1008 state_call(TipPoolValues),
1009 chain_tip_difficulty(network.clone(), self.read_state.clone(), true)
1010 )
1011 };
1012
1013 let (size_on_disk, (tip_height, tip_hash), value_balance, difficulty) = {
1014 use zebra_state::ReadResponse::*;
1015
1016 let UsageInfo(size_on_disk) = usage_info_rsp.map_misc_error()? else {
1017 unreachable!("unmatched response to a TipPoolValues request")
1018 };
1019
1020 let (tip, value_balance) = match tip_pool_values_rsp {
1021 Ok(TipPoolValues {
1022 tip_height,
1023 tip_hash,
1024 value_balance,
1025 }) => ((tip_height, tip_hash), value_balance),
1026 Ok(_) => unreachable!("unmatched response to a TipPoolValues request"),
1027 Err(_) => ((Height::MIN, network.genesis_hash()), Default::default()),
1028 };
1029
1030 let difficulty = chain_tip_difficulty
1031 .expect("should always be Ok when `should_use_default` is true");
1032
1033 (size_on_disk, tip, value_balance, difficulty)
1034 };
1035
1036 let now = Utc::now();
1037 let (estimated_height, verification_progress) = self
1038 .latest_chain_tip
1039 .best_tip_height_and_block_time()
1040 .map(|(tip_height, tip_block_time)| {
1041 let height =
1042 NetworkChainTipHeightEstimator::new(tip_block_time, tip_height, network)
1043 .estimate_height_at(now);
1044
1045 let height =
1049 if tip_block_time > now || height < tip_height || debug_force_finished_sync {
1050 tip_height
1051 } else {
1052 height
1053 };
1054
1055 (height, f64::from(tip_height.0) / f64::from(height.0))
1056 })
1057 .unwrap_or((Height::MIN, 0.0));
1059
1060 let verification_progress = if network.is_regtest() {
1061 1.0
1062 } else {
1063 verification_progress
1064 };
1065
1066 let mut upgrades = IndexMap::new();
1070 for (activation_height, network_upgrade) in network.full_activation_list() {
1071 if let Some(branch_id) = network_upgrade.branch_id() {
1076 let status = if tip_height >= activation_height {
1078 NetworkUpgradeStatus::Active
1079 } else {
1080 NetworkUpgradeStatus::Pending
1081 };
1082
1083 let upgrade = NetworkUpgradeInfo {
1084 name: network_upgrade,
1085 activation_height,
1086 status,
1087 };
1088 upgrades.insert(ConsensusBranchIdHex(branch_id), upgrade);
1089 }
1090 }
1091
1092 let next_block_height =
1094 (tip_height + 1).expect("valid chain tips are a lot less than Height::MAX");
1095 let consensus = TipConsensusBranch {
1096 chain_tip: ConsensusBranchIdHex(
1097 NetworkUpgrade::current(network, tip_height)
1098 .branch_id()
1099 .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1100 ),
1101 next_block: ConsensusBranchIdHex(
1102 NetworkUpgrade::current(network, next_block_height)
1103 .branch_id()
1104 .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1105 ),
1106 };
1107
1108 let response = GetBlockchainInfoResponse {
1109 chain: network.bip70_network_name(),
1110 blocks: tip_height,
1111 best_block_hash: tip_hash,
1112 estimated_height,
1113 chain_supply: GetBlockchainInfoBalance::chain_supply(value_balance),
1114 value_pools: GetBlockchainInfoBalance::value_pools(value_balance, None),
1115 upgrades,
1116 consensus,
1117 headers: tip_height,
1118 difficulty,
1119 verification_progress,
1120 chain_work: 0,
1122 pruned: false,
1123 size_on_disk,
1124 commitments: 0,
1126 };
1127
1128 Ok(response)
1129 }
1130
1131 async fn get_address_balance(
1132 &self,
1133 address_strings: GetAddressBalanceRequest,
1134 ) -> Result<GetAddressBalanceResponse> {
1135 let valid_addresses = address_strings.valid_addresses()?;
1136
1137 let request = zebra_state::ReadRequest::AddressBalance(valid_addresses);
1138 let response = self
1139 .read_state
1140 .clone()
1141 .oneshot(request)
1142 .await
1143 .map_misc_error()?;
1144
1145 match response {
1146 zebra_state::ReadResponse::AddressBalance { balance, received } => {
1147 Ok(GetAddressBalanceResponse {
1148 balance: u64::from(balance),
1149 received,
1150 })
1151 }
1152 _ => unreachable!("Unexpected response from state service: {response:?}"),
1153 }
1154 }
1155
1156 async fn send_raw_transaction(
1158 &self,
1159 raw_transaction_hex: String,
1160 _allow_high_fees: Option<bool>,
1161 ) -> Result<SendRawTransactionResponse> {
1162 let mempool = self.mempool.clone();
1163 let queue_sender = self.queue_sender.clone();
1164
1165 let raw_transaction_bytes = Vec::from_hex(raw_transaction_hex)
1168 .map_error(server::error::LegacyCode::Deserialization)?;
1169 let raw_transaction = Transaction::zcash_deserialize(&*raw_transaction_bytes)
1170 .map_error(server::error::LegacyCode::Deserialization)?;
1171
1172 let transaction_hash = raw_transaction.hash();
1173
1174 let unmined_transaction = UnminedTx::from(raw_transaction.clone());
1176 let _ = queue_sender.send(unmined_transaction);
1177
1178 let transaction_parameter = mempool::Gossip::Tx(raw_transaction.into());
1179 let request = mempool::Request::Queue(vec![transaction_parameter]);
1180
1181 let response = mempool.oneshot(request).await.map_misc_error()?;
1182
1183 let mut queue_results = match response {
1184 mempool::Response::Queued(results) => results,
1185 _ => unreachable!("incorrect response variant from mempool service"),
1186 };
1187
1188 assert_eq!(
1189 queue_results.len(),
1190 1,
1191 "mempool service returned more results than expected"
1192 );
1193
1194 let queue_result = queue_results
1195 .pop()
1196 .expect("there should be exactly one item in Vec")
1197 .inspect_err(|err| tracing::debug!("sent transaction to mempool: {:?}", &err))
1198 .map_misc_error()?
1199 .await
1200 .map_misc_error()?;
1201
1202 tracing::debug!("sent transaction to mempool: {:?}", &queue_result);
1203
1204 queue_result
1205 .map(|_| SendRawTransactionResponse(transaction_hash))
1206 .map_error(server::error::LegacyCode::Verify)
1213 }
1214
1215 async fn get_block(
1220 &self,
1221 hash_or_height: String,
1222 verbosity: Option<u8>,
1223 ) -> Result<GetBlockResponse> {
1224 let verbosity = verbosity.unwrap_or(1);
1225 let network = self.network.clone();
1226 let original_hash_or_height = hash_or_height.clone();
1227
1228 let get_block_header_future = if matches!(verbosity, 1 | 2) {
1230 Some(self.get_block_header(original_hash_or_height.clone(), Some(true)))
1231 } else {
1232 None
1233 };
1234
1235 let hash_or_height =
1236 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1237 .map_error(server::error::LegacyCode::InvalidParameter)?;
1240
1241 if verbosity == 0 {
1242 let request = zebra_state::ReadRequest::Block(hash_or_height);
1243 let response = self
1244 .read_state
1245 .clone()
1246 .oneshot(request)
1247 .await
1248 .map_misc_error()?;
1249
1250 match response {
1251 zebra_state::ReadResponse::Block(Some(block)) => {
1252 Ok(GetBlockResponse::Raw(block.into()))
1253 }
1254 zebra_state::ReadResponse::Block(None) => {
1255 Err("Block not found").map_error(server::error::LegacyCode::InvalidParameter)
1256 }
1257 _ => unreachable!("unmatched response to a block request"),
1258 }
1259 } else if let Some(get_block_header_future) = get_block_header_future {
1260 let get_block_header_result: Result<GetBlockHeaderResponse> =
1261 get_block_header_future.await;
1262
1263 let GetBlockHeaderResponse::Object(block_header) = get_block_header_result? else {
1264 panic!("must return Object")
1265 };
1266
1267 let BlockHeaderObject {
1268 hash,
1269 confirmations,
1270 height,
1271 version,
1272 merkle_root,
1273 block_commitments,
1274 final_sapling_root,
1275 sapling_tree_size,
1276 time,
1277 nonce,
1278 solution,
1279 bits,
1280 difficulty,
1281 previous_block_hash,
1282 next_block_hash,
1283 } = *block_header;
1284
1285 let transactions_request = match verbosity {
1286 1 => zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height),
1287 2 => zebra_state::ReadRequest::BlockAndSize(hash_or_height),
1288 _other => panic!("get_block_header_fut should be none"),
1289 };
1290
1291 let hash_or_height = hash.into();
1296 let requests = vec![
1297 transactions_request,
1305 zebra_state::ReadRequest::OrchardTree(hash_or_height),
1307 zebra_state::ReadRequest::BlockInfo(previous_block_hash.into()),
1309 zebra_state::ReadRequest::BlockInfo(hash_or_height),
1310 ];
1311
1312 let mut futs = FuturesOrdered::new();
1313
1314 for request in requests {
1315 futs.push_back(self.read_state.clone().oneshot(request));
1316 }
1317
1318 let tx_ids_response = futs.next().await.expect("`futs` should not be empty");
1319 let (tx, size): (Vec<_>, Option<usize>) = match tx_ids_response.map_misc_error()? {
1320 zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => (
1321 tx_ids
1322 .ok_or_misc_error("block not found")?
1323 .iter()
1324 .map(|tx_id| GetBlockTransaction::Hash(*tx_id))
1325 .collect(),
1326 None,
1327 ),
1328 zebra_state::ReadResponse::BlockAndSize(block_and_size) => {
1329 let (block, size) = block_and_size.ok_or_misc_error("Block not found")?;
1330 let block_time = block.header.time;
1331 let transactions =
1332 block
1333 .transactions
1334 .iter()
1335 .map(|tx| {
1336 GetBlockTransaction::Object(Box::new(
1337 TransactionObject::from_transaction(
1338 tx.clone(),
1339 Some(height),
1340 Some(confirmations.try_into().expect(
1341 "should be less than max block height, i32::MAX",
1342 )),
1343 &network,
1344 Some(block_time),
1345 Some(hash),
1346 Some(true),
1347 tx.hash(),
1348 ),
1349 ))
1350 })
1351 .collect();
1352 (transactions, Some(size))
1353 }
1354 _ => unreachable!("unmatched response to a transaction_ids_for_block request"),
1355 };
1356
1357 let orchard_tree_response = futs.next().await.expect("`futs` should not be empty");
1358 let zebra_state::ReadResponse::OrchardTree(orchard_tree) =
1359 orchard_tree_response.map_misc_error()?
1360 else {
1361 unreachable!("unmatched response to a OrchardTree request");
1362 };
1363
1364 let nu5_activation = NetworkUpgrade::Nu5.activation_height(&network);
1365
1366 let orchard_tree = orchard_tree.ok_or_misc_error("missing Orchard tree")?;
1368
1369 let final_orchard_root = match nu5_activation {
1370 Some(activation_height) if height >= activation_height => {
1371 Some(orchard_tree.root().into())
1372 }
1373 _other => None,
1374 };
1375
1376 let sapling = SaplingTrees {
1377 size: sapling_tree_size,
1378 };
1379
1380 let orchard_tree_size = orchard_tree.count();
1381 let orchard = OrchardTrees {
1382 size: orchard_tree_size,
1383 };
1384
1385 let trees = GetBlockTrees { sapling, orchard };
1386
1387 let block_info_response = futs.next().await.expect("`futs` should not be empty");
1388 let zebra_state::ReadResponse::BlockInfo(prev_block_info) =
1389 block_info_response.map_misc_error()?
1390 else {
1391 unreachable!("unmatched response to a BlockInfo request");
1392 };
1393 let block_info_response = futs.next().await.expect("`futs` should not be empty");
1394 let zebra_state::ReadResponse::BlockInfo(block_info) =
1395 block_info_response.map_misc_error()?
1396 else {
1397 unreachable!("unmatched response to a BlockInfo request");
1398 };
1399
1400 let delta = block_info.as_ref().and_then(|d| {
1401 let value_pools = d.value_pools().constrain::<NegativeAllowed>().ok()?;
1402 let prev_value_pools = prev_block_info
1403 .map(|d| d.value_pools().constrain::<NegativeAllowed>())
1404 .unwrap_or(Ok(ValueBalance::<NegativeAllowed>::zero()))
1405 .ok()?;
1406 (value_pools - prev_value_pools).ok()
1407 });
1408 let size = size.or(block_info.as_ref().map(|d| d.size() as usize));
1409
1410 Ok(GetBlockResponse::Object(Box::new(BlockObject {
1411 hash,
1412 confirmations,
1413 height: Some(height),
1414 version: Some(version),
1415 merkle_root: Some(merkle_root),
1416 time: Some(time),
1417 nonce: Some(nonce),
1418 solution: Some(solution),
1419 bits: Some(bits),
1420 difficulty: Some(difficulty),
1421 n_tx: tx.len(),
1422 tx,
1423 trees,
1424 chain_supply: block_info
1425 .as_ref()
1426 .map(|d| GetBlockchainInfoBalance::chain_supply(*d.value_pools())),
1427 value_pools: block_info
1428 .map(|d| GetBlockchainInfoBalance::value_pools(*d.value_pools(), delta)),
1429 size: size.map(|size| size as i64),
1430 block_commitments: Some(block_commitments),
1431 final_sapling_root: Some(final_sapling_root),
1432 final_orchard_root,
1433 previous_block_hash: Some(previous_block_hash),
1434 next_block_hash,
1435 })))
1436 } else {
1437 Err("invalid verbosity value").map_error(server::error::LegacyCode::InvalidParameter)
1438 }
1439 }
1440
1441 async fn get_block_header(
1442 &self,
1443 hash_or_height: String,
1444 verbose: Option<bool>,
1445 ) -> Result<GetBlockHeaderResponse> {
1446 let verbose = verbose.unwrap_or(true);
1447 let network = self.network.clone();
1448
1449 let hash_or_height =
1450 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1451 .map_error(server::error::LegacyCode::InvalidParameter)?;
1454 let zebra_state::ReadResponse::BlockHeader {
1455 header,
1456 hash,
1457 height,
1458 next_block_hash,
1459 } = self
1460 .read_state
1461 .clone()
1462 .oneshot(zebra_state::ReadRequest::BlockHeader(hash_or_height))
1463 .await
1464 .map_err(|_| "block height not in best chain")
1465 .map_error(
1466 if hash_or_height.hash().is_some() {
1471 server::error::LegacyCode::InvalidAddressOrKey
1472 } else {
1473 server::error::LegacyCode::InvalidParameter
1474 },
1475 )?
1476 else {
1477 panic!("unexpected response to BlockHeader request")
1478 };
1479
1480 let response = if !verbose {
1481 GetBlockHeaderResponse::Raw(HexData(header.zcash_serialize_to_vec().map_misc_error()?))
1482 } else {
1483 let zebra_state::ReadResponse::SaplingTree(sapling_tree) = self
1484 .read_state
1485 .clone()
1486 .oneshot(zebra_state::ReadRequest::SaplingTree(hash_or_height))
1487 .await
1488 .map_misc_error()?
1489 else {
1490 panic!("unexpected response to SaplingTree request")
1491 };
1492
1493 let sapling_tree = sapling_tree.ok_or_misc_error("missing Sapling tree")?;
1495
1496 let zebra_state::ReadResponse::Depth(depth) = self
1497 .read_state
1498 .clone()
1499 .oneshot(zebra_state::ReadRequest::Depth(hash))
1500 .await
1501 .map_misc_error()?
1502 else {
1503 panic!("unexpected response to SaplingTree request")
1504 };
1505
1506 const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;
1509
1510 let confirmations = depth
1513 .map(|depth| i64::from(depth) + 1)
1514 .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS);
1515
1516 let mut nonce = *header.nonce;
1517 nonce.reverse();
1518
1519 let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network);
1520 let sapling_tree_size = sapling_tree.count();
1521 let final_sapling_root: [u8; 32] =
1522 if sapling_activation.is_some() && height >= sapling_activation.unwrap() {
1523 let mut root: [u8; 32] = sapling_tree.root().into();
1524 root.reverse();
1525 root
1526 } else {
1527 [0; 32]
1528 };
1529
1530 let difficulty = header.difficulty_threshold.relative_to_network(&network);
1531
1532 let block_commitments = match header.commitment(&network, height).expect(
1533 "Unexpected failure while parsing the blockcommitments field in get_block_header",
1534 ) {
1535 Commitment::PreSaplingReserved(bytes) => bytes,
1536 Commitment::FinalSaplingRoot(_) => final_sapling_root,
1537 Commitment::ChainHistoryActivationReserved => [0; 32],
1538 Commitment::ChainHistoryRoot(root) => root.bytes_in_display_order(),
1539 Commitment::ChainHistoryBlockTxAuthCommitment(hash) => {
1540 hash.bytes_in_display_order()
1541 }
1542 };
1543
1544 let block_header = BlockHeaderObject {
1545 hash,
1546 confirmations,
1547 height,
1548 version: header.version,
1549 merkle_root: header.merkle_root,
1550 block_commitments,
1551 final_sapling_root,
1552 sapling_tree_size,
1553 time: header.time.timestamp(),
1554 nonce,
1555 solution: header.solution,
1556 bits: header.difficulty_threshold,
1557 difficulty,
1558 previous_block_hash: header.previous_block_hash,
1559 next_block_hash,
1560 };
1561
1562 GetBlockHeaderResponse::Object(Box::new(block_header))
1563 };
1564
1565 Ok(response)
1566 }
1567
1568 fn get_best_block_hash(&self) -> Result<GetBlockHashResponse> {
1569 self.latest_chain_tip
1570 .best_tip_hash()
1571 .map(GetBlockHashResponse)
1572 .ok_or_misc_error("No blocks in state")
1573 }
1574
1575 fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse> {
1576 self.latest_chain_tip
1577 .best_tip_height_and_hash()
1578 .map(|(height, hash)| GetBlockHeightAndHashResponse { height, hash })
1579 .ok_or_misc_error("No blocks in state")
1580 }
1581
1582 async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse> {
1583 let mut mempool = self.mempool.clone();
1584
1585 let response = mempool
1586 .ready()
1587 .and_then(|service| service.call(mempool::Request::QueueStats))
1588 .await
1589 .map_misc_error()?;
1590
1591 if let mempool::Response::QueueStats {
1592 size,
1593 bytes,
1594 usage,
1595 fully_notified,
1596 } = response
1597 {
1598 Ok(GetMempoolInfoResponse {
1599 size,
1600 bytes,
1601 usage,
1602 fully_notified,
1603 })
1604 } else {
1605 unreachable!("unexpected response to QueueStats request")
1606 }
1607 }
1608
1609 async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse> {
1610 #[allow(unused)]
1611 let verbose = verbose.unwrap_or(false);
1612
1613 use zebra_chain::block::MAX_BLOCK_BYTES;
1614
1615 let mut mempool = self.mempool.clone();
1616
1617 let request = if verbose {
1618 mempool::Request::FullTransactions
1619 } else {
1620 mempool::Request::TransactionIds
1621 };
1622
1623 let response = mempool
1625 .ready()
1626 .and_then(|service| service.call(request))
1627 .await
1628 .map_misc_error()?;
1629
1630 match response {
1631 mempool::Response::FullTransactions {
1632 mut transactions,
1633 transaction_dependencies,
1634 last_seen_tip_hash: _,
1635 } => {
1636 if verbose {
1637 let map = transactions
1638 .iter()
1639 .map(|unmined_tx| {
1640 (
1641 unmined_tx.transaction.id.mined_id().encode_hex(),
1642 get_raw_mempool::MempoolObject::from_verified_unmined_tx(
1643 unmined_tx,
1644 &transactions,
1645 &transaction_dependencies,
1646 ),
1647 )
1648 })
1649 .collect::<HashMap<_, _>>();
1650 Ok(GetRawMempoolResponse::Verbose(map))
1651 } else {
1652 transactions.sort_by_cached_key(|tx| {
1657 cmp::Reverse((
1660 i64::from(tx.miner_fee) as u128 * MAX_BLOCK_BYTES as u128
1661 / tx.transaction.size as u128,
1662 tx.transaction.id.mined_id(),
1664 ))
1665 });
1666 let tx_ids: Vec<String> = transactions
1667 .iter()
1668 .map(|unmined_tx| unmined_tx.transaction.id.mined_id().encode_hex())
1669 .collect();
1670
1671 Ok(GetRawMempoolResponse::TxIds(tx_ids))
1672 }
1673 }
1674
1675 mempool::Response::TransactionIds(unmined_transaction_ids) => {
1676 let mut tx_ids: Vec<String> = unmined_transaction_ids
1677 .iter()
1678 .map(|id| id.mined_id().encode_hex())
1679 .collect();
1680
1681 tx_ids.sort();
1683
1684 Ok(GetRawMempoolResponse::TxIds(tx_ids))
1685 }
1686
1687 _ => unreachable!("unmatched response to a transactionids request"),
1688 }
1689 }
1690
1691 async fn get_raw_transaction(
1692 &self,
1693 txid: String,
1694 verbose: Option<u8>,
1695 block_hash: Option<String>,
1696 ) -> Result<GetRawTransactionResponse> {
1697 let mut mempool = self.mempool.clone();
1698 let verbose = verbose.unwrap_or(0) != 0;
1699
1700 let txid = transaction::Hash::from_hex(txid)
1703 .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
1704
1705 if block_hash.is_none() {
1707 match mempool
1708 .ready()
1709 .and_then(|service| {
1710 service.call(mempool::Request::TransactionsByMinedId([txid].into()))
1711 })
1712 .await
1713 .map_misc_error()?
1714 {
1715 mempool::Response::Transactions(txns) => {
1716 if let Some(tx) = txns.first() {
1717 return Ok(if verbose {
1718 GetRawTransactionResponse::Object(Box::new(
1719 TransactionObject::from_transaction(
1720 tx.transaction.clone(),
1721 None,
1722 None,
1723 &self.network,
1724 None,
1725 None,
1726 Some(false),
1727 txid,
1728 ),
1729 ))
1730 } else {
1731 let hex = tx.transaction.clone().into();
1732 GetRawTransactionResponse::Raw(hex)
1733 });
1734 }
1735 }
1736
1737 _ => unreachable!("unmatched response to a `TransactionsByMinedId` request"),
1738 };
1739 }
1740
1741 let caller_block_context = if let Some(block_hash) = block_hash {
1742 let block_hash = block::Hash::from_hex(block_hash)
1743 .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
1744 match self
1745 .read_state
1746 .clone()
1747 .oneshot(zebra_state::ReadRequest::AnyChainTransactionIdsForBlock(
1748 block_hash.into(),
1749 ))
1750 .await
1751 .map_misc_error()?
1752 {
1753 zebra_state::ReadResponse::AnyChainTransactionIdsForBlock(tx_ids) => {
1754 let (ids, in_best_chain) = tx_ids.ok_or_error(
1755 server::error::LegacyCode::InvalidAddressOrKey,
1756 "block not found",
1757 )?;
1758
1759 ids.iter().find(|id| **id == txid).ok_or_error(
1760 server::error::LegacyCode::InvalidAddressOrKey,
1761 "txid not found",
1762 )?;
1763
1764 Some((block_hash, in_best_chain))
1765 }
1766 _ => {
1767 unreachable!("unmatched response to a `AnyChainTransactionIdsForBlock` request")
1768 }
1769 }
1770 } else {
1771 None
1772 };
1773
1774 match self
1776 .read_state
1777 .clone()
1778 .oneshot(zebra_state::ReadRequest::AnyChainTransaction(txid))
1779 .await
1780 .map_misc_error()?
1781 {
1782 zebra_state::ReadResponse::AnyChainTransaction(Some(tx)) => Ok(if verbose {
1783 if let Some((caller_block_hash, in_best_chain)) = caller_block_context {
1784 let (raw_tx, height, confirmations, block_time) = match &tx {
1787 AnyTx::Mined(mined) if in_best_chain => (
1788 mined.tx.clone(),
1789 Some(mined.height),
1790 Some(mined.confirmations),
1791 Some(mined.block_time),
1792 ),
1793 _ => {
1794 let raw_tx: Arc<Transaction> = tx.into();
1795 (raw_tx, None, None, None)
1796 }
1797 };
1798
1799 GetRawTransactionResponse::Object(Box::new(
1800 TransactionObject::from_transaction(
1801 raw_tx,
1802 height,
1803 confirmations,
1804 &self.network,
1805 block_time,
1806 Some(caller_block_hash),
1807 Some(in_best_chain),
1808 txid,
1809 ),
1810 ))
1811 } else {
1812 match tx {
1813 AnyTx::Mined(tx) => {
1814 let block_hash = match self
1815 .read_state
1816 .clone()
1817 .oneshot(zebra_state::ReadRequest::BestChainBlockHash(tx.height))
1818 .await
1819 .map_misc_error()?
1820 {
1821 zebra_state::ReadResponse::BlockHash(block_hash) => block_hash,
1822 _ => {
1823 unreachable!(
1824 "unmatched response to a `BestChainBlockHash` request"
1825 )
1826 }
1827 };
1828
1829 GetRawTransactionResponse::Object(Box::new(
1830 TransactionObject::from_transaction(
1831 tx.tx.clone(),
1832 Some(tx.height),
1833 Some(tx.confirmations),
1834 &self.network,
1835 Some(tx.block_time),
1838 block_hash,
1839 Some(true),
1840 txid,
1841 ),
1842 ))
1843 }
1844 AnyTx::Side((tx, block_hash)) => GetRawTransactionResponse::Object(
1845 Box::new(TransactionObject::from_transaction(
1846 tx.clone(),
1847 None,
1848 None,
1849 &self.network,
1850 None,
1851 Some(block_hash),
1852 Some(false),
1853 txid,
1854 )),
1855 ),
1856 }
1857 }
1858 } else {
1859 let tx: Arc<Transaction> = tx.into();
1860 let hex = tx.into();
1861 GetRawTransactionResponse::Raw(hex)
1862 }),
1863
1864 zebra_state::ReadResponse::AnyChainTransaction(None) => {
1865 Err("No such mempool or main chain transaction")
1866 .map_error(server::error::LegacyCode::InvalidAddressOrKey)
1867 }
1868
1869 _ => unreachable!("unmatched response to a `Transaction` read request"),
1870 }
1871 }
1872
1873 async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse> {
1874 let mut read_state = self.read_state.clone();
1875 let network = self.network.clone();
1876
1877 let hash_or_height =
1878 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1879 .map_error(server::error::LegacyCode::InvalidParameter)?;
1882
1883 let block = match read_state
1892 .ready()
1893 .and_then(|service| service.call(zebra_state::ReadRequest::Block(hash_or_height)))
1894 .await
1895 .map_misc_error()?
1896 {
1897 zebra_state::ReadResponse::Block(Some(block)) => block,
1898 zebra_state::ReadResponse::Block(None) => {
1899 return Err("the requested block is not in the main chain")
1902 .map_error(server::error::LegacyCode::InvalidParameter);
1903 }
1904 _ => unreachable!("unmatched response to a block request"),
1905 };
1906
1907 let hash = hash_or_height
1908 .hash_or_else(|_| Some(block.hash()))
1909 .expect("block hash");
1910
1911 let height = hash_or_height
1912 .height_or_else(|_| block.coinbase_height())
1913 .expect("verified blocks have a coinbase height");
1914
1915 let time = u32::try_from(block.header.time.timestamp())
1916 .expect("Timestamps of valid blocks always fit into u32.");
1917
1918 let sapling = if network.is_nu_active(consensus::NetworkUpgrade::Sapling, height.into()) {
1919 match read_state
1920 .ready()
1921 .and_then(|service| {
1922 service.call(zebra_state::ReadRequest::SaplingTree(hash.into()))
1923 })
1924 .await
1925 .map_misc_error()?
1926 {
1927 zebra_state::ReadResponse::SaplingTree(tree) => {
1928 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
1929 }
1930 _ => unreachable!("unmatched response to a Sapling tree request"),
1931 }
1932 } else {
1933 None
1934 };
1935 let (sapling_tree, sapling_root) =
1936 sapling.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
1937
1938 let orchard = if network.is_nu_active(consensus::NetworkUpgrade::Nu5, height.into()) {
1939 match read_state
1940 .ready()
1941 .and_then(|service| {
1942 service.call(zebra_state::ReadRequest::OrchardTree(hash.into()))
1943 })
1944 .await
1945 .map_misc_error()?
1946 {
1947 zebra_state::ReadResponse::OrchardTree(tree) => {
1948 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
1949 }
1950 _ => unreachable!("unmatched response to an Orchard tree request"),
1951 }
1952 } else {
1953 None
1954 };
1955 let (orchard_tree, orchard_root) =
1956 orchard.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
1957
1958 Ok(GetTreestateResponse::new(
1959 hash,
1960 height,
1961 time,
1962 None,
1965 Treestate::new(trees::Commitments::new(sapling_root, sapling_tree)),
1966 Treestate::new(trees::Commitments::new(orchard_root, orchard_tree)),
1967 ))
1968 }
1969
1970 async fn z_get_subtrees_by_index(
1971 &self,
1972 pool: String,
1973 start_index: NoteCommitmentSubtreeIndex,
1974 limit: Option<NoteCommitmentSubtreeIndex>,
1975 ) -> Result<GetSubtreesByIndexResponse> {
1976 let mut read_state = self.read_state.clone();
1977
1978 const POOL_LIST: &[&str] = &["sapling", "orchard"];
1979
1980 if pool == "sapling" {
1981 let request = zebra_state::ReadRequest::SaplingSubtrees { start_index, limit };
1982 let response = read_state
1983 .ready()
1984 .and_then(|service| service.call(request))
1985 .await
1986 .map_misc_error()?;
1987
1988 let subtrees = match response {
1989 zebra_state::ReadResponse::SaplingSubtrees(subtrees) => subtrees,
1990 _ => unreachable!("unmatched response to a subtrees request"),
1991 };
1992
1993 let subtrees = subtrees
1994 .values()
1995 .map(|subtree| SubtreeRpcData {
1996 root: subtree.root.to_bytes().encode_hex(),
1997 end_height: subtree.end_height,
1998 })
1999 .collect();
2000
2001 Ok(GetSubtreesByIndexResponse {
2002 pool,
2003 start_index,
2004 subtrees,
2005 })
2006 } else if pool == "orchard" {
2007 let request = zebra_state::ReadRequest::OrchardSubtrees { start_index, limit };
2008 let response = read_state
2009 .ready()
2010 .and_then(|service| service.call(request))
2011 .await
2012 .map_misc_error()?;
2013
2014 let subtrees = match response {
2015 zebra_state::ReadResponse::OrchardSubtrees(subtrees) => subtrees,
2016 _ => unreachable!("unmatched response to a subtrees request"),
2017 };
2018
2019 let subtrees = subtrees
2020 .values()
2021 .map(|subtree| SubtreeRpcData {
2022 root: subtree.root.encode_hex(),
2023 end_height: subtree.end_height,
2024 })
2025 .collect();
2026
2027 Ok(GetSubtreesByIndexResponse {
2028 pool,
2029 start_index,
2030 subtrees,
2031 })
2032 } else {
2033 Err(ErrorObject::owned(
2034 server::error::LegacyCode::Misc.into(),
2035 format!("invalid pool name, must be one of: {POOL_LIST:?}").as_str(),
2036 None::<()>,
2037 ))
2038 }
2039 }
2040
2041 async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>> {
2042 let mut read_state = self.read_state.clone();
2043 let latest_chain_tip = self.latest_chain_tip.clone();
2044
2045 let height_range = build_height_range(
2046 request.start,
2047 request.end,
2048 best_chain_tip_height(&latest_chain_tip)?,
2049 )?;
2050
2051 let valid_addresses = request.valid_addresses()?;
2052
2053 let request = zebra_state::ReadRequest::TransactionIdsByAddresses {
2054 addresses: valid_addresses,
2055 height_range,
2056 };
2057 let response = read_state
2058 .ready()
2059 .and_then(|service| service.call(request))
2060 .await
2061 .map_misc_error()?;
2062
2063 let hashes = match response {
2064 zebra_state::ReadResponse::AddressesTransactionIds(hashes) => {
2065 let mut last_tx_location = TransactionLocation::from_usize(Height(0), 0);
2066
2067 hashes
2068 .iter()
2069 .map(|(tx_loc, tx_id)| {
2070 assert!(
2072 *tx_loc > last_tx_location,
2073 "Transactions were not in chain order:\n\
2074 {tx_loc:?} {tx_id:?} was after:\n\
2075 {last_tx_location:?}",
2076 );
2077
2078 last_tx_location = *tx_loc;
2079
2080 tx_id.to_string()
2081 })
2082 .collect()
2083 }
2084 _ => unreachable!("unmatched response to a TransactionsByAddresses request"),
2085 };
2086
2087 Ok(hashes)
2088 }
2089
2090 async fn get_address_utxos(
2091 &self,
2092 utxos_request: GetAddressUtxosRequest,
2093 ) -> Result<GetAddressUtxosResponse> {
2094 let mut read_state = self.read_state.clone();
2095 let mut response_utxos = vec![];
2096
2097 let valid_addresses = utxos_request.valid_addresses()?;
2098
2099 let request = zebra_state::ReadRequest::UtxosByAddresses(valid_addresses);
2101 let response = read_state
2102 .ready()
2103 .and_then(|service| service.call(request))
2104 .await
2105 .map_misc_error()?;
2106 let utxos = match response {
2107 zebra_state::ReadResponse::AddressUtxos(utxos) => utxos,
2108 _ => unreachable!("unmatched response to a UtxosByAddresses request"),
2109 };
2110
2111 let mut last_output_location = OutputLocation::from_usize(Height(0), 0, 0);
2112
2113 for utxo_data in utxos.utxos() {
2114 let address = utxo_data.0;
2115 let txid = *utxo_data.1;
2116 let height = utxo_data.2.height();
2117 let output_index = utxo_data.2.output_index();
2118 let script = utxo_data.3.lock_script.clone();
2119 let satoshis = u64::from(utxo_data.3.value);
2120
2121 let output_location = *utxo_data.2;
2122 assert!(
2124 output_location > last_output_location,
2125 "UTXOs were not in chain order:\n\
2126 {output_location:?} {address:?} {txid:?} was after:\n\
2127 {last_output_location:?}",
2128 );
2129
2130 let entry = Utxo {
2131 address,
2132 txid,
2133 output_index,
2134 script,
2135 satoshis,
2136 height,
2137 };
2138 response_utxos.push(entry);
2139
2140 last_output_location = output_location;
2141 }
2142
2143 if !utxos_request.chain_info {
2144 Ok(GetAddressUtxosResponse::Utxos(response_utxos))
2145 } else {
2146 let (height, hash) = utxos
2147 .last_height_and_hash()
2148 .ok_or_misc_error("No blocks in state")?;
2149
2150 Ok(GetAddressUtxosResponse::UtxosAndChainInfo(
2151 GetAddressUtxosResponseObject {
2152 utxos: response_utxos,
2153 hash,
2154 height,
2155 },
2156 ))
2157 }
2158 }
2159
2160 fn stop(&self) -> Result<String> {
2161 #[cfg(not(target_os = "windows"))]
2162 if self.network.is_regtest() {
2163 match nix::sys::signal::raise(nix::sys::signal::SIGINT) {
2164 Ok(_) => Ok("Zebra server stopping".to_string()),
2165 Err(error) => Err(ErrorObject::owned(
2166 ErrorCode::InternalError.code(),
2167 format!("Failed to shut down: {error}").as_str(),
2168 None::<()>,
2169 )),
2170 }
2171 } else {
2172 Err(ErrorObject::borrowed(
2173 ErrorCode::MethodNotFound.code(),
2174 "stop is only available on regtest networks",
2175 None,
2176 ))
2177 }
2178 #[cfg(target_os = "windows")]
2179 Err(ErrorObject::borrowed(
2180 ErrorCode::MethodNotFound.code(),
2181 "stop is not available in windows targets",
2182 None,
2183 ))
2184 }
2185
2186 fn get_block_count(&self) -> Result<u32> {
2187 best_chain_tip_height(&self.latest_chain_tip).map(|height| height.0)
2188 }
2189
2190 async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse> {
2191 let mut read_state = self.read_state.clone();
2192 let latest_chain_tip = self.latest_chain_tip.clone();
2193
2194 let tip_height = best_chain_tip_height(&latest_chain_tip)?;
2196
2197 let height = height_from_signed_int(index, tip_height)?;
2198
2199 let request = zebra_state::ReadRequest::BestChainBlockHash(height);
2200 let response = read_state
2201 .ready()
2202 .and_then(|service| service.call(request))
2203 .await
2204 .map_error(server::error::LegacyCode::default())?;
2205
2206 match response {
2207 zebra_state::ReadResponse::BlockHash(Some(hash)) => Ok(GetBlockHashResponse(hash)),
2208 zebra_state::ReadResponse::BlockHash(None) => Err(ErrorObject::borrowed(
2209 server::error::LegacyCode::InvalidParameter.into(),
2210 "Block not found",
2211 None,
2212 )),
2213 _ => unreachable!("unmatched response to a block request"),
2214 }
2215 }
2216
2217 async fn get_block_template(
2218 &self,
2219 parameters: Option<GetBlockTemplateParameters>,
2220 ) -> Result<GetBlockTemplateResponse> {
2221 use types::get_block_template::{
2222 check_parameters, check_synced_to_tip, fetch_chain_info, fetch_mempool_transactions,
2223 validate_block_proposal, zip317::select_mempool_transactions,
2224 };
2225
2226 let mempool = self.mempool.clone();
2228 let mut latest_chain_tip = self.latest_chain_tip.clone();
2229 let sync_status = self.gbt.sync_status();
2230 let read_state = self.read_state.clone();
2231
2232 if let Some(HexData(block_proposal_bytes)) = parameters
2233 .as_ref()
2234 .and_then(GetBlockTemplateParameters::block_proposal_data)
2235 {
2236 return validate_block_proposal(
2237 self.gbt.block_verifier_router(),
2238 block_proposal_bytes,
2239 &self.network,
2240 latest_chain_tip,
2241 sync_status,
2242 )
2243 .await;
2244 }
2245
2246 check_parameters(¶meters)?;
2248
2249 let client_long_poll_id = parameters.as_ref().and_then(|params| params.long_poll_id);
2250
2251 let miner_params = self
2252 .gbt
2253 .miner_params()
2254 .ok_or_error(0, "miner parameters are required for get_block_template")?;
2255
2256 let mut max_time_reached = false;
2260
2261 let (server_long_poll_id, chain_info, mempool_txs, mempool_tx_deps, submit_old) = loop {
2263 check_synced_to_tip(&self.network, latest_chain_tip.clone(), sync_status.clone())?;
2269 latest_chain_tip.mark_best_tip_seen();
2277
2278 let chain_info @ zebra_state::GetBlockTemplateChainInfo {
2285 tip_hash,
2286 tip_height,
2287 max_time,
2288 cur_time,
2289 ..
2290 } = fetch_chain_info(read_state.clone()).await?;
2291
2292 let Some((mempool_txs, mempool_tx_deps)) =
2303 fetch_mempool_transactions(mempool.clone(), tip_hash)
2304 .await?
2305 .or_else(|| client_long_poll_id.is_none().then(Default::default))
2309 else {
2310 continue;
2311 };
2312
2313 let server_long_poll_id = LongPollInput::new(
2315 tip_height,
2316 tip_hash,
2317 max_time,
2318 mempool_txs.iter().map(|tx| tx.transaction.id),
2319 )
2320 .generate_id();
2321
2322 if Some(&server_long_poll_id) != client_long_poll_id.as_ref() || max_time_reached {
2327 let submit_old = if max_time_reached {
2331 Some(false)
2332 } else {
2333 client_long_poll_id
2334 .as_ref()
2335 .map(|old_long_poll_id| server_long_poll_id.submit_old(old_long_poll_id))
2336 };
2337
2338 break (
2339 server_long_poll_id,
2340 chain_info,
2341 mempool_txs,
2342 mempool_tx_deps,
2343 submit_old,
2344 );
2345 }
2346
2347 let wait_for_mempool_request =
2357 tokio::time::sleep(Duration::from_secs(MEMPOOL_LONG_POLL_INTERVAL));
2358
2359 let mut wait_for_new_tip = latest_chain_tip.clone();
2362 let wait_for_new_tip = wait_for_new_tip.best_tip_changed();
2363 let precomputed_height = Height(chain_info.tip_height.0 + 2);
2365 let wait_for_new_tip = async {
2366 let precompute_coinbase = |network, height, params| {
2374 tokio::task::spawn_blocking(move || {
2375 TransactionTemplate::new_coinbase(&network, height, ¶ms, Amount::zero())
2376 .expect("valid coinbase tx")
2377 })
2378 };
2379
2380 let precomputed_coinbase = precompute_coinbase(
2381 self.network.clone(),
2382 precomputed_height,
2383 miner_params.clone(),
2384 )
2385 .await
2386 .expect("valid coinbase tx");
2387
2388 let _ = wait_for_new_tip.await;
2389
2390 precomputed_coinbase
2391 };
2392
2393 let duration_until_max_time = max_time.saturating_duration_since(cur_time);
2405 let wait_for_max_time: OptionFuture<_> = if duration_until_max_time.seconds() > 0 {
2406 Some(tokio::time::sleep(duration_until_max_time.to_std()))
2407 } else {
2408 None
2409 }
2410 .into();
2411
2412 tokio::select! {
2419 biased;
2422
2423 _elapsed = wait_for_mempool_request => {
2425 tracing::debug!(
2426 ?max_time,
2427 ?cur_time,
2428 ?server_long_poll_id,
2429 ?client_long_poll_id,
2430 MEMPOOL_LONG_POLL_INTERVAL,
2431 "checking for a new mempool change after waiting a few seconds"
2432 );
2433 }
2434
2435 precomputed_coinbase = wait_for_new_tip => {
2436 let chain_info = fetch_chain_info(read_state.clone()).await?;
2437
2438 let server_long_poll_id = LongPollInput::new(
2439 chain_info.tip_height,
2440 chain_info.tip_hash,
2441 chain_info.max_time,
2442 vec![]
2443 )
2444 .generate_id();
2445
2446 let submit_old = client_long_poll_id
2447 .as_ref()
2448 .map(|old_long_poll_id| server_long_poll_id.submit_old(old_long_poll_id));
2449
2450 let next_height = chain_info.tip_height.next().map_misc_error()?;
2454 let precomputed_coinbase = (next_height == precomputed_height)
2455 .then_some(precomputed_coinbase);
2456
2457 return Ok(BlockTemplateResponse::new_internal(
2461 &self.network,
2462 precomputed_coinbase,
2463 miner_params,
2464 &chain_info,
2465 server_long_poll_id,
2466 vec![],
2467 submit_old,
2468 )
2469 .into())
2470 }
2471
2472 Some(_elapsed) = wait_for_max_time => {
2475 tracing::info!(
2477 ?max_time,
2478 ?cur_time,
2479 ?server_long_poll_id,
2480 ?client_long_poll_id,
2481 "returning from long poll because max time was reached"
2482 );
2483
2484 max_time_reached = true;
2485 }
2486 }
2487 };
2488
2489 tracing::debug!(
2496 mempool_tx_hashes = ?mempool_txs
2497 .iter()
2498 .map(|tx| tx.transaction.id.mined_id())
2499 .collect::<Vec<_>>(),
2500 "selecting transactions for the template from the mempool"
2501 );
2502
2503 let height = chain_info.tip_height.next().map_misc_error()?;
2504
2505 let mempool_txs = select_mempool_transactions(
2507 &self.network,
2508 height,
2509 miner_params,
2510 mempool_txs,
2511 mempool_tx_deps,
2512 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
2513 None,
2514 );
2515
2516 tracing::debug!(
2517 selected_mempool_tx_hashes = ?mempool_txs
2518 .iter()
2519 .map(|#[cfg(not(test))] tx, #[cfg(test)] (_, tx)| tx.transaction.id.mined_id())
2520 .collect::<Vec<_>>(),
2521 "selected transactions for the template from the mempool"
2522 );
2523
2524 Ok(BlockTemplateResponse::new_internal(
2527 &self.network,
2528 None,
2529 miner_params,
2530 &chain_info,
2531 server_long_poll_id,
2532 mempool_txs,
2533 submit_old,
2534 #[cfg(all(zcash_unstable = "nu7", feature = "tx_v6"))]
2535 None,
2536 )
2537 .into())
2538 }
2539
2540 async fn submit_block(
2541 &self,
2542 HexData(block_bytes): HexData,
2543 _parameters: Option<SubmitBlockParameters>,
2544 ) -> Result<SubmitBlockResponse> {
2545 let mut block_verifier_router = self.gbt.block_verifier_router();
2546
2547 let block: Block = match block_bytes.zcash_deserialize_into() {
2548 Ok(block_bytes) => block_bytes,
2549 Err(error) => {
2550 tracing::info!(
2551 ?error,
2552 "submit block failed: block bytes could not be deserialized into a structurally valid block"
2553 );
2554
2555 return Ok(SubmitBlockErrorResponse::Rejected.into());
2556 }
2557 };
2558
2559 let height = block
2560 .coinbase_height()
2561 .ok_or_error(0, "coinbase height not found")?;
2562 let block_hash = block.hash();
2563
2564 let block_verifier_router_response = block_verifier_router
2565 .ready()
2566 .await
2567 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
2568 .call(zebra_consensus::Request::Commit(Arc::new(block)))
2569 .await;
2570
2571 let chain_error = match block_verifier_router_response {
2572 Ok(hash) => {
2579 tracing::info!(?hash, ?height, "submit block accepted");
2580
2581 self.gbt
2582 .advertise_mined_block(hash, height)
2583 .map_error_with_prefix(0, "failed to send mined block to gossip task")?;
2584
2585 return Ok(SubmitBlockResponse::Accepted);
2586 }
2587
2588 Err(box_error) => {
2591 let error = box_error
2592 .downcast::<RouterError>()
2593 .map(|boxed_chain_error| *boxed_chain_error);
2594
2595 tracing::info!(
2596 ?error,
2597 ?block_hash,
2598 ?height,
2599 "submit block failed verification"
2600 );
2601
2602 error
2603 }
2604 };
2605
2606 let response = match chain_error {
2607 Ok(source) if source.is_duplicate_request() => SubmitBlockErrorResponse::Duplicate,
2608
2609 Ok(_verify_chain_error) => SubmitBlockErrorResponse::Rejected,
2625
2626 Err(_unknown_error_type) => SubmitBlockErrorResponse::Rejected,
2629 };
2630
2631 Ok(response.into())
2632 }
2633
2634 async fn get_mining_info(&self) -> Result<GetMiningInfoResponse> {
2635 let network = self.network.clone();
2636 let mut read_state = self.read_state.clone();
2637
2638 let chain_tip = self.latest_chain_tip.clone();
2639 let tip_height = chain_tip.best_tip_height().unwrap_or(Height(0)).0;
2640
2641 let mut current_block_tx = None;
2642 if tip_height > 0 {
2643 let mined_tx_ids = chain_tip.best_tip_mined_transaction_ids();
2644 current_block_tx =
2645 (!mined_tx_ids.is_empty()).then(|| mined_tx_ids.len().saturating_sub(1));
2646 }
2647
2648 let solution_rate_fut = self.get_network_sol_ps(None, None);
2649 let mut current_block_size = None;
2651 if tip_height > 0 {
2652 let request = zebra_state::ReadRequest::TipBlockSize;
2653 let response: zebra_state::ReadResponse = read_state
2654 .ready()
2655 .and_then(|service| service.call(request))
2656 .await
2657 .map_error(server::error::LegacyCode::default())?;
2658 current_block_size = match response {
2659 zebra_state::ReadResponse::TipBlockSize(Some(block_size)) => Some(block_size),
2660 _ => None,
2661 };
2662 }
2663
2664 Ok(GetMiningInfoResponse::new_internal(
2665 tip_height,
2666 current_block_size,
2667 current_block_tx,
2668 network,
2669 solution_rate_fut.await?,
2670 ))
2671 }
2672
2673 async fn get_network_sol_ps(
2674 &self,
2675 num_blocks: Option<i32>,
2676 height: Option<i32>,
2677 ) -> Result<u64> {
2678 let mut num_blocks = num_blocks.unwrap_or(DEFAULT_SOLUTION_RATE_WINDOW_SIZE);
2680 if num_blocks < 1 {
2682 num_blocks = i32::try_from(POW_AVERAGING_WINDOW).expect("fits in i32");
2683 }
2684 let num_blocks =
2685 usize::try_from(num_blocks).expect("just checked for negatives, i32 fits in usize");
2686
2687 let height = height.and_then(|height| height.try_into_height().ok());
2690
2691 let mut read_state = self.read_state.clone();
2692
2693 let request = ReadRequest::SolutionRate { num_blocks, height };
2694
2695 let response = read_state
2696 .ready()
2697 .and_then(|service| service.call(request))
2698 .await
2699 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
2700
2701 let solution_rate = match response {
2702 ReadResponse::SolutionRate(solution_rate) => solution_rate.unwrap_or(0),
2704
2705 _ => unreachable!("unmatched response to a solution rate request"),
2706 };
2707
2708 Ok(solution_rate
2709 .try_into()
2710 .expect("per-second solution rate always fits in u64"))
2711 }
2712
2713 async fn get_network_info(&self) -> Result<GetNetworkInfoResponse> {
2714 let version = GetInfoResponse::version_from_string(&self.build_version)
2715 .expect("invalid version string");
2716
2717 let subversion = self.user_agent.clone();
2718
2719 let protocol_version = zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0;
2720
2721 let local_services = format!("{:016x}", PeerServices::NODE_NETWORK);
2723
2724 let timeoffset = 0;
2726
2727 let connections = self.address_book.recently_live_peers(Utc::now()).len();
2728
2729 let networks = vec![
2731 NetworkInfo::new("ipv4".to_string(), false, true, "".to_string(), false),
2732 NetworkInfo::new("ipv6".to_string(), false, true, "".to_string(), false),
2733 NetworkInfo::new("onion".to_string(), false, false, "".to_string(), false),
2734 ];
2735
2736 let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
2737 / (zebra_chain::amount::COIN as f64);
2738
2739 let local_addresses = vec![];
2741
2742 let warnings = "".to_string();
2744
2745 let response = GetNetworkInfoResponse {
2746 version,
2747 subversion,
2748 protocol_version,
2749 local_services,
2750 timeoffset,
2751 connections,
2752 networks,
2753 relay_fee,
2754 local_addresses,
2755 warnings,
2756 };
2757
2758 Ok(response)
2759 }
2760
2761 async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
2762 let address_book = self.address_book.clone();
2763 Ok(address_book
2764 .recently_live_peers(chrono::Utc::now())
2765 .into_iter()
2766 .map(PeerInfo::from)
2767 .collect())
2768 }
2769
2770 async fn ping(&self) -> Result<()> {
2771 tracing::debug!("Receiving ping request via RPC");
2772
2773 Ok(())
2777 }
2778
2779 async fn validate_address(&self, raw_address: String) -> Result<ValidateAddressResponse> {
2780 let network = self.network.clone();
2781
2782 validate_address(network, raw_address)
2783 }
2784
2785 async fn z_validate_address(&self, raw_address: String) -> Result<ZValidateAddressResponse> {
2786 let network = self.network.clone();
2787
2788 z_validate_address(network, raw_address)
2789 }
2790
2791 async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse> {
2792 let net = self.network.clone();
2793
2794 let height = match height {
2795 Some(h) => Height(h),
2796 None => best_chain_tip_height(&self.latest_chain_tip)?,
2797 };
2798
2799 let subsidy = block_subsidy(height, &net).map_misc_error()?;
2800
2801 let (lockbox_streams, mut funding_streams): (Vec<_>, Vec<_>) =
2802 funding_stream_values(height, &net, subsidy)
2803 .map_misc_error()?
2804 .into_iter()
2805 .partition(|(receiver, _)| matches!(receiver, FundingStreamReceiver::Deferred));
2807
2808 let [lockbox_total, funding_streams_total] =
2809 [&lockbox_streams, &funding_streams].map(|streams| {
2810 streams
2811 .iter()
2812 .map(|&(_, amount)| amount)
2813 .sum::<std::result::Result<Amount<_>, _>>()
2814 .map(Zec::from)
2815 .map_misc_error()
2816 });
2817
2818 funding_streams.sort_by_key(|(receiver, _funding_stream)| {
2820 ZCASHD_FUNDING_STREAM_ORDER
2821 .iter()
2822 .position(|zcashd_receiver| zcashd_receiver == receiver)
2823 });
2824
2825 let is_nu6 = NetworkUpgrade::current(&net, height) == NetworkUpgrade::Nu6;
2826
2827 let [funding_streams, lockbox_streams] =
2829 [funding_streams, lockbox_streams].map(|streams| {
2830 streams
2831 .into_iter()
2832 .map(|(receiver, value)| {
2833 let address = funding_stream_address(height, &net, receiver);
2834 types::subsidy::FundingStream::new_internal(
2835 is_nu6, receiver, value, address,
2836 )
2837 })
2838 .collect()
2839 });
2840
2841 Ok(GetBlockSubsidyResponse {
2842 miner: miner_subsidy(height, &net, subsidy)
2843 .map_misc_error()?
2844 .into(),
2845 founders: founders_reward(&net, height).into(),
2846 funding_streams,
2847 lockbox_streams,
2848 funding_streams_total: funding_streams_total?,
2849 lockbox_total: lockbox_total?,
2850 total_block_subsidy: subsidy.into(),
2851 })
2852 }
2853
2854 async fn get_difficulty(&self) -> Result<f64> {
2855 chain_tip_difficulty(self.network.clone(), self.read_state.clone(), false).await
2856 }
2857
2858 async fn z_list_unified_receivers(
2859 &self,
2860 address: String,
2861 ) -> Result<ZListUnifiedReceiversResponse> {
2862 use zcash_address::unified::Container;
2863
2864 let (network, unified_address): (
2865 zcash_protocol::consensus::NetworkType,
2866 zcash_address::unified::Address,
2867 ) = zcash_address::unified::Encoding::decode(address.clone().as_str())
2868 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
2869
2870 let mut p2pkh = None;
2871 let mut p2sh = None;
2872 let mut orchard = None;
2873 let mut sapling = None;
2874
2875 for item in unified_address.items() {
2876 match item {
2877 zcash_address::unified::Receiver::Orchard(_data) => {
2878 let addr = zcash_address::unified::Address::try_from_items(vec![item])
2879 .expect("using data already decoded as valid");
2880 orchard = Some(addr.encode(&network));
2881 }
2882 zcash_address::unified::Receiver::Sapling(data) => {
2883 let addr = zebra_chain::primitives::Address::try_from_sapling(network, data)
2884 .map_error(server::error::LegacyCode::InvalidParameter)?;
2885 sapling = Some(addr.payment_address().unwrap_or_default());
2886 }
2887 zcash_address::unified::Receiver::P2pkh(data) => {
2888 let addr =
2889 zebra_chain::primitives::Address::try_from_transparent_p2pkh(network, data)
2890 .expect("using data already decoded as valid");
2891 p2pkh = Some(addr.payment_address().unwrap_or_default());
2892 }
2893 zcash_address::unified::Receiver::P2sh(data) => {
2894 let addr =
2895 zebra_chain::primitives::Address::try_from_transparent_p2sh(network, data)
2896 .expect("using data already decoded as valid");
2897 p2sh = Some(addr.payment_address().unwrap_or_default());
2898 }
2899 _ => (),
2900 }
2901 }
2902
2903 Ok(ZListUnifiedReceiversResponse::new(
2904 orchard, sapling, p2pkh, p2sh,
2905 ))
2906 }
2907
2908 async fn invalidate_block(&self, block_hash: String) -> Result<()> {
2909 let block_hash = block_hash
2910 .parse()
2911 .map_error(server::error::LegacyCode::InvalidParameter)?;
2912
2913 self.state
2914 .clone()
2915 .oneshot(zebra_state::Request::InvalidateBlock(block_hash))
2916 .await
2917 .map(|rsp| assert_eq!(rsp, zebra_state::Response::Invalidated(block_hash)))
2918 .map_misc_error()
2919 }
2920
2921 async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>> {
2922 let block_hash = block_hash
2923 .parse()
2924 .map_error(server::error::LegacyCode::InvalidParameter)?;
2925
2926 self.state
2927 .clone()
2928 .oneshot(zebra_state::Request::ReconsiderBlock(block_hash))
2929 .await
2930 .map(|rsp| match rsp {
2931 zebra_state::Response::Reconsidered(block_hashes) => block_hashes,
2932 _ => unreachable!("unmatched response to a reconsider block request"),
2933 })
2934 .map_misc_error()
2935 }
2936
2937 async fn generate(&self, num_blocks: u32) -> Result<Vec<Hash>> {
2938 let mut rpc = self.clone();
2939 let network = self.network.clone();
2940
2941 if !network.disable_pow() {
2942 return Err(ErrorObject::borrowed(
2943 0,
2944 "generate is only supported on networks where PoW is disabled",
2945 None,
2946 ));
2947 }
2948
2949 let mut block_hashes = Vec::new();
2950 for _ in 0..num_blocks {
2951 rpc.gbt.randomize_coinbase_data();
2955
2956 let block_template = rpc
2957 .get_block_template(None)
2958 .await
2959 .map_error(server::error::LegacyCode::default())?;
2960
2961 let GetBlockTemplateResponse::TemplateMode(block_template) = block_template else {
2962 return Err(ErrorObject::borrowed(
2963 0,
2964 "error generating block template",
2965 None,
2966 ));
2967 };
2968
2969 let proposal_block = proposal_block_from_template(
2970 &block_template,
2971 BlockTemplateTimeSource::CurTime,
2972 &network,
2973 )
2974 .map_error(server::error::LegacyCode::default())?;
2975
2976 let hex_proposal_block = HexData(
2977 proposal_block
2978 .zcash_serialize_to_vec()
2979 .map_error(server::error::LegacyCode::default())?,
2980 );
2981
2982 let r = rpc
2983 .submit_block(hex_proposal_block, None)
2984 .await
2985 .map_error(server::error::LegacyCode::default())?;
2986 match r {
2987 SubmitBlockResponse::Accepted => { }
2988 SubmitBlockResponse::ErrorResponse(response) => {
2989 return Err(ErrorObject::owned(
2990 server::error::LegacyCode::Misc.into(),
2991 format!("block was rejected: {response:?}"),
2992 None::<()>,
2993 ));
2994 }
2995 }
2996
2997 block_hashes.push(GetBlockHashResponse(proposal_block.hash()));
2998 }
2999
3000 Ok(block_hashes)
3001 }
3002
3003 async fn add_node(
3004 &self,
3005 addr: zebra_network::PeerSocketAddr,
3006 command: AddNodeCommand,
3007 ) -> Result<()> {
3008 if self.network.is_regtest() {
3009 match command {
3010 AddNodeCommand::Add => {
3011 tracing::info!(?addr, "adding peer address to the address book");
3012 if self.address_book.clone().add_peer(addr) {
3013 Ok(())
3014 } else {
3015 return Err(ErrorObject::owned(
3016 server::error::LegacyCode::ClientNodeAlreadyAdded.into(),
3017 format!("peer address was already present in the address book: {addr}"),
3018 None::<()>,
3019 ));
3020 }
3021 }
3022 }
3023 } else {
3024 return Err(ErrorObject::owned(
3025 ErrorCode::InvalidParams.code(),
3026 "addnode command is only supported on regtest",
3027 None::<()>,
3028 ));
3029 }
3030 }
3031
3032 fn openrpc(&self) -> openrpsee::openrpc::Response {
3033 let mut generator = openrpsee::openrpc::Generator::new();
3034
3035 let methods = METHODS
3036 .into_iter()
3037 .map(|(name, method)| method.generate(&mut generator, name))
3038 .collect();
3039
3040 Ok(openrpsee::openrpc::OpenRpc {
3041 openrpc: "1.3.2",
3042 info: openrpsee::openrpc::Info {
3043 title: env!("CARGO_PKG_NAME"),
3044 description: env!("CARGO_PKG_DESCRIPTION"),
3045 version: env!("CARGO_PKG_VERSION"),
3046 },
3047 methods,
3048 components: generator.into_components(),
3049 })
3050 }
3051 async fn get_tx_out(
3052 &self,
3053 txid: String,
3054 n: u32,
3055 include_mempool: Option<bool>,
3056 ) -> Result<GetTxOutResponse> {
3057 let txid = transaction::Hash::from_hex(txid)
3058 .map_error(server::error::LegacyCode::InvalidParameter)?;
3059
3060 let outpoint = transparent::OutPoint {
3061 hash: txid,
3062 index: n,
3063 };
3064
3065 if include_mempool.unwrap_or(true) {
3067 let rsp = self
3068 .mempool
3069 .clone()
3070 .oneshot(mempool::Request::UnspentOutput(outpoint))
3071 .await
3072 .map_misc_error()?;
3073
3074 match rsp {
3075 mempool::Response::TransparentOutput(Some(CreatedOrSpent::Created {
3077 output,
3078 tx_version,
3079 last_seen_hash,
3080 })) => {
3081 return Ok(GetTxOutResponse(Some(
3082 types::transaction::OutputObject::from_output(
3083 &output,
3084 last_seen_hash.to_string(),
3085 0,
3086 tx_version,
3087 false,
3088 self.network(),
3089 ),
3090 )))
3091 }
3092 mempool::Response::TransparentOutput(Some(CreatedOrSpent::Spent)) => {
3093 return Ok(GetTxOutResponse(None))
3094 }
3095 mempool::Response::TransparentOutput(None) => {}
3096 _ => unreachable!("unmatched response to an `UnspentOutput` request"),
3097 };
3098 }
3099
3100 let tip_rsp = self
3105 .read_state
3106 .clone()
3107 .oneshot(zebra_state::ReadRequest::Tip)
3108 .await
3109 .map_misc_error()?;
3110
3111 let best_block_hash = match tip_rsp {
3112 zebra_state::ReadResponse::Tip(tip) => tip.ok_or_misc_error("No blocks in state")?.1,
3113 _ => unreachable!("unmatched response to a `Tip` request"),
3114 };
3115
3116 let rsp = self
3118 .read_state
3119 .clone()
3120 .oneshot(zebra_state::ReadRequest::Transaction(txid))
3121 .await
3122 .map_misc_error()?;
3123
3124 match rsp {
3125 zebra_state::ReadResponse::Transaction(Some(tx)) => {
3126 let outputs = tx.tx.outputs();
3127 let index: usize = n.try_into().expect("u32 always fits in usize");
3128 let output = match outputs.get(index) {
3129 Some(output) => output,
3130 None => return Ok(GetTxOutResponse(None)),
3132 };
3133
3134 let is_spent = {
3136 let rsp = self
3137 .read_state
3138 .clone()
3139 .oneshot(zebra_state::ReadRequest::IsTransparentOutputSpent(outpoint))
3140 .await
3141 .map_misc_error()?;
3142
3143 match rsp {
3144 zebra_state::ReadResponse::IsTransparentOutputSpent(spent) => spent,
3145 _ => unreachable!(
3146 "unmatched response to an `IsTransparentOutputSpent` request"
3147 ),
3148 }
3149 };
3150
3151 if is_spent {
3152 return Ok(GetTxOutResponse(None));
3153 }
3154
3155 Ok(GetTxOutResponse(Some(
3156 types::transaction::OutputObject::from_output(
3157 output,
3158 best_block_hash.to_string(),
3159 tx.confirmations,
3160 tx.tx.version(),
3161 tx.tx.is_coinbase(),
3162 self.network(),
3163 ),
3164 )))
3165 }
3166 zebra_state::ReadResponse::Transaction(None) => Ok(GetTxOutResponse(None)),
3167 _ => unreachable!("unmatched response to a `Transaction` request"),
3168 }
3169 }
3170}
3171
3172pub fn best_chain_tip_height<Tip>(latest_chain_tip: &Tip) -> Result<Height>
3177where
3178 Tip: ChainTip + Clone + Send + Sync + 'static,
3179{
3180 latest_chain_tip
3181 .best_tip_height()
3182 .ok_or_misc_error("No blocks in state")
3183}
3184
3185#[allow(clippy::too_many_arguments)]
3189#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3190pub struct GetInfoResponse {
3191 #[getter(rename = "raw_version")]
3193 version: u64,
3194
3195 build: String,
3197
3198 subversion: String,
3200
3201 #[serde(rename = "protocolversion")]
3203 protocol_version: u32,
3204
3205 blocks: u32,
3207
3208 connections: usize,
3210
3211 #[serde(skip_serializing_if = "Option::is_none")]
3213 proxy: Option<String>,
3214
3215 difficulty: f64,
3217
3218 testnet: bool,
3220
3221 #[serde(rename = "paytxfee")]
3223 pay_tx_fee: f64,
3224
3225 #[serde(rename = "relayfee")]
3227 relay_fee: f64,
3228
3229 errors: String,
3231
3232 #[serde(rename = "errorstimestamp")]
3234 errors_timestamp: i64,
3235}
3236
3237#[deprecated(note = "Use `GetInfoResponse` instead")]
3238pub use self::GetInfoResponse as GetInfo;
3239
3240impl Default for GetInfoResponse {
3241 fn default() -> Self {
3242 GetInfoResponse {
3243 version: 0,
3244 build: "some build version".to_string(),
3245 subversion: "some subversion".to_string(),
3246 protocol_version: 0,
3247 blocks: 0,
3248 connections: 0,
3249 proxy: None,
3250 difficulty: 0.0,
3251 testnet: false,
3252 pay_tx_fee: 0.0,
3253 relay_fee: 0.0,
3254 errors: "no errors".to_string(),
3255 errors_timestamp: Utc::now().timestamp(),
3256 }
3257 }
3258}
3259
3260impl GetInfoResponse {
3261 #[allow(clippy::too_many_arguments)]
3263 #[deprecated(note = "Use `GetInfoResponse::new` instead")]
3264 pub fn from_parts(
3265 version: u64,
3266 build: String,
3267 subversion: String,
3268 protocol_version: u32,
3269 blocks: u32,
3270 connections: usize,
3271 proxy: Option<String>,
3272 difficulty: f64,
3273 testnet: bool,
3274 pay_tx_fee: f64,
3275 relay_fee: f64,
3276 errors: String,
3277 errors_timestamp: i64,
3278 ) -> Self {
3279 Self {
3280 version,
3281 build,
3282 subversion,
3283 protocol_version,
3284 blocks,
3285 connections,
3286 proxy,
3287 difficulty,
3288 testnet,
3289 pay_tx_fee,
3290 relay_fee,
3291 errors,
3292 errors_timestamp,
3293 }
3294 }
3295
3296 pub fn into_parts(
3298 self,
3299 ) -> (
3300 u64,
3301 String,
3302 String,
3303 u32,
3304 u32,
3305 usize,
3306 Option<String>,
3307 f64,
3308 bool,
3309 f64,
3310 f64,
3311 String,
3312 i64,
3313 ) {
3314 (
3315 self.version,
3316 self.build,
3317 self.subversion,
3318 self.protocol_version,
3319 self.blocks,
3320 self.connections,
3321 self.proxy,
3322 self.difficulty,
3323 self.testnet,
3324 self.pay_tx_fee,
3325 self.relay_fee,
3326 self.errors,
3327 self.errors_timestamp,
3328 )
3329 }
3330
3331 fn version_from_string(build_string: &str) -> Option<u64> {
3333 let semver_version = semver::Version::parse(build_string.strip_prefix('v')?).ok()?;
3334 let build_number = semver_version
3335 .build
3336 .as_str()
3337 .split('.')
3338 .next()
3339 .and_then(|num_str| num_str.parse::<u64>().ok())
3340 .unwrap_or_default();
3341
3342 let version_number = 1_000_000 * semver_version.major
3344 + 10_000 * semver_version.minor
3345 + 100 * semver_version.patch
3346 + build_number;
3347
3348 Some(version_number)
3349 }
3350}
3351
3352pub type BlockchainValuePoolBalances = [GetBlockchainInfoBalance; 5];
3354
3355#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters)]
3359pub struct GetBlockchainInfoResponse {
3360 chain: String,
3362
3363 #[getter(copy)]
3365 blocks: Height,
3366
3367 #[getter(copy)]
3370 headers: Height,
3371
3372 difficulty: f64,
3374
3375 #[serde(rename = "verificationprogress")]
3377 verification_progress: f64,
3378
3379 #[serde(rename = "chainwork")]
3381 chain_work: u64,
3382
3383 pruned: bool,
3385
3386 size_on_disk: u64,
3388
3389 commitments: u64,
3391
3392 #[serde(rename = "bestblockhash", with = "hex")]
3394 #[getter(copy)]
3395 best_block_hash: block::Hash,
3396
3397 #[serde(rename = "estimatedheight")]
3401 #[getter(copy)]
3402 estimated_height: Height,
3403
3404 #[serde(rename = "chainSupply")]
3406 chain_supply: GetBlockchainInfoBalance,
3407
3408 #[serde(rename = "valuePools")]
3410 value_pools: BlockchainValuePoolBalances,
3411
3412 upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
3414
3415 #[getter(copy)]
3417 consensus: TipConsensusBranch,
3418}
3419
3420impl Default for GetBlockchainInfoResponse {
3421 fn default() -> Self {
3422 Self {
3423 chain: "main".to_string(),
3424 blocks: Height(1),
3425 best_block_hash: block::Hash([0; 32]),
3426 estimated_height: Height(1),
3427 chain_supply: GetBlockchainInfoBalance::chain_supply(Default::default()),
3428 value_pools: GetBlockchainInfoBalance::zero_pools(),
3429 upgrades: IndexMap::new(),
3430 consensus: TipConsensusBranch {
3431 chain_tip: ConsensusBranchIdHex(ConsensusBranchId::default()),
3432 next_block: ConsensusBranchIdHex(ConsensusBranchId::default()),
3433 },
3434 headers: Height(1),
3435 difficulty: 0.0,
3436 verification_progress: 0.0,
3437 chain_work: 0,
3438 pruned: false,
3439 size_on_disk: 0,
3440 commitments: 0,
3441 }
3442 }
3443}
3444
3445impl GetBlockchainInfoResponse {
3446 #[allow(clippy::too_many_arguments)]
3450 pub fn new(
3451 chain: String,
3452 blocks: Height,
3453 best_block_hash: block::Hash,
3454 estimated_height: Height,
3455 chain_supply: GetBlockchainInfoBalance,
3456 value_pools: BlockchainValuePoolBalances,
3457 upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
3458 consensus: TipConsensusBranch,
3459 headers: Height,
3460 difficulty: f64,
3461 verification_progress: f64,
3462 chain_work: u64,
3463 pruned: bool,
3464 size_on_disk: u64,
3465 commitments: u64,
3466 ) -> Self {
3467 Self {
3468 chain,
3469 blocks,
3470 best_block_hash,
3471 estimated_height,
3472 chain_supply,
3473 value_pools,
3474 upgrades,
3475 consensus,
3476 headers,
3477 difficulty,
3478 verification_progress,
3479 chain_work,
3480 pruned,
3481 size_on_disk,
3482 commitments,
3483 }
3484 }
3485}
3486
3487#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, JsonSchema)]
3489#[serde(from = "DGetAddressBalanceRequest")]
3490pub struct GetAddressBalanceRequest {
3491 addresses: Vec<String>,
3493}
3494
3495impl From<DGetAddressBalanceRequest> for GetAddressBalanceRequest {
3496 fn from(address_strings: DGetAddressBalanceRequest) -> Self {
3497 match address_strings {
3498 DGetAddressBalanceRequest::Addresses { addresses } => {
3499 GetAddressBalanceRequest { addresses }
3500 }
3501 DGetAddressBalanceRequest::Address(address) => GetAddressBalanceRequest {
3502 addresses: vec![address],
3503 },
3504 }
3505 }
3506}
3507
3508#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, JsonSchema)]
3510#[serde(untagged)]
3511enum DGetAddressBalanceRequest {
3512 Addresses { addresses: Vec<String> },
3514 Address(String),
3516}
3517
3518#[deprecated(note = "Use `GetAddressBalanceRequest` instead.")]
3520pub type AddressStrings = GetAddressBalanceRequest;
3521
3522pub trait ValidateAddresses {
3524 fn valid_addresses(&self) -> Result<HashSet<Address>> {
3528 let valid_addresses: HashSet<Address> = self
3531 .addresses()
3532 .iter()
3533 .map(|address| {
3534 address
3535 .parse()
3536 .map_error(server::error::LegacyCode::InvalidAddressOrKey)
3537 })
3538 .collect::<Result<_>>()?;
3539
3540 Ok(valid_addresses)
3541 }
3542
3543 fn addresses(&self) -> &[String];
3545}
3546
3547impl ValidateAddresses for GetAddressBalanceRequest {
3548 fn addresses(&self) -> &[String] {
3549 &self.addresses
3550 }
3551}
3552
3553impl GetAddressBalanceRequest {
3554 pub fn new(addresses: Vec<String>) -> GetAddressBalanceRequest {
3556 GetAddressBalanceRequest { addresses }
3557 }
3558
3559 #[deprecated(
3561 note = "Use `AddressStrings::new` instead. Validity will be checked by the server."
3562 )]
3563 pub fn new_valid(addresses: Vec<String>) -> Result<GetAddressBalanceRequest> {
3564 let req = Self { addresses };
3565 req.valid_addresses()?;
3566 Ok(req)
3567 }
3568}
3569
3570#[derive(
3572 Clone,
3573 Copy,
3574 Debug,
3575 Default,
3576 Eq,
3577 PartialEq,
3578 Hash,
3579 serde::Serialize,
3580 serde::Deserialize,
3581 Getters,
3582 new,
3583)]
3584pub struct GetAddressBalanceResponse {
3585 balance: u64,
3587 pub received: u64,
3589}
3590
3591#[deprecated(note = "Use `GetAddressBalanceResponse` instead.")]
3592pub use self::GetAddressBalanceResponse as AddressBalance;
3593
3594#[derive(
3596 Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new, JsonSchema,
3597)]
3598#[serde(from = "DGetAddressUtxosRequest")]
3599pub struct GetAddressUtxosRequest {
3600 addresses: Vec<String>,
3602 #[serde(default)]
3604 #[serde(rename = "chainInfo")]
3605 chain_info: bool,
3606}
3607
3608impl From<DGetAddressUtxosRequest> for GetAddressUtxosRequest {
3609 fn from(request: DGetAddressUtxosRequest) -> Self {
3610 match request {
3611 DGetAddressUtxosRequest::Single(addr) => GetAddressUtxosRequest {
3612 addresses: vec![addr],
3613 chain_info: false,
3614 },
3615 DGetAddressUtxosRequest::Object {
3616 addresses,
3617 chain_info,
3618 } => GetAddressUtxosRequest {
3619 addresses,
3620 chain_info,
3621 },
3622 }
3623 }
3624}
3625
3626#[derive(Debug, serde::Deserialize, JsonSchema)]
3628#[serde(untagged)]
3629enum DGetAddressUtxosRequest {
3630 Single(String),
3632 Object {
3634 addresses: Vec<String>,
3636 #[serde(default)]
3638 #[serde(rename = "chainInfo")]
3639 chain_info: bool,
3640 },
3641}
3642
3643impl ValidateAddresses for GetAddressUtxosRequest {
3644 fn addresses(&self) -> &[String] {
3645 &self.addresses
3646 }
3647}
3648
3649#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
3651pub struct ConsensusBranchIdHex(#[serde(with = "hex")] ConsensusBranchId);
3652
3653impl ConsensusBranchIdHex {
3654 pub fn new(consensus_branch_id: u32) -> Self {
3656 ConsensusBranchIdHex(consensus_branch_id.into())
3657 }
3658
3659 pub fn inner(&self) -> u32 {
3661 self.0.into()
3662 }
3663}
3664
3665#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3667pub struct NetworkUpgradeInfo {
3668 name: NetworkUpgrade,
3672
3673 #[serde(rename = "activationheight")]
3675 activation_height: Height,
3676
3677 status: NetworkUpgradeStatus,
3679}
3680
3681impl NetworkUpgradeInfo {
3682 pub fn from_parts(
3684 name: NetworkUpgrade,
3685 activation_height: Height,
3686 status: NetworkUpgradeStatus,
3687 ) -> Self {
3688 Self {
3689 name,
3690 activation_height,
3691 status,
3692 }
3693 }
3694
3695 pub fn into_parts(self) -> (NetworkUpgrade, Height, NetworkUpgradeStatus) {
3697 (self.name, self.activation_height, self.status)
3698 }
3699}
3700
3701#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3703pub enum NetworkUpgradeStatus {
3704 #[serde(rename = "active")]
3709 Active,
3710
3711 #[serde(rename = "disabled")]
3713 Disabled,
3714
3715 #[serde(rename = "pending")]
3717 Pending,
3718}
3719
3720#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3724pub struct TipConsensusBranch {
3725 #[serde(rename = "chaintip")]
3727 chain_tip: ConsensusBranchIdHex,
3728
3729 #[serde(rename = "nextblock")]
3731 next_block: ConsensusBranchIdHex,
3732}
3733
3734impl TipConsensusBranch {
3735 pub fn from_parts(chain_tip: u32, next_block: u32) -> Self {
3737 Self {
3738 chain_tip: ConsensusBranchIdHex::new(chain_tip),
3739 next_block: ConsensusBranchIdHex::new(next_block),
3740 }
3741 }
3742
3743 pub fn into_parts(self) -> (u32, u32) {
3745 (self.chain_tip.inner(), self.next_block.inner())
3746 }
3747}
3748
3749#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3755pub struct SendRawTransactionResponse(#[serde(with = "hex")] transaction::Hash);
3756
3757#[deprecated(note = "Use `SendRawTransactionResponse` instead")]
3758pub use self::SendRawTransactionResponse as SentTransactionHash;
3759
3760impl Default for SendRawTransactionResponse {
3761 fn default() -> Self {
3762 Self(transaction::Hash::from([0; 32]))
3763 }
3764}
3765
3766impl SendRawTransactionResponse {
3767 pub fn new(hash: transaction::Hash) -> Self {
3769 SendRawTransactionResponse(hash)
3770 }
3771
3772 #[deprecated(note = "Use `SentTransactionHash::hash` instead")]
3774 pub fn inner(&self) -> transaction::Hash {
3775 self.hash()
3776 }
3777
3778 pub fn hash(&self) -> transaction::Hash {
3780 self.0
3781 }
3782}
3783
3784#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
3788#[serde(untagged)]
3789pub enum GetBlockResponse {
3790 Raw(#[serde(with = "hex")] SerializedBlock),
3792 Object(Box<BlockObject>),
3794}
3795
3796#[deprecated(note = "Use `GetBlockResponse` instead")]
3797pub use self::GetBlockResponse as GetBlock;
3798
3799impl Default for GetBlockResponse {
3800 fn default() -> Self {
3801 GetBlockResponse::Object(Box::new(BlockObject {
3802 hash: block::Hash([0; 32]),
3803 confirmations: 0,
3804 height: None,
3805 time: None,
3806 n_tx: 0,
3807 tx: Vec::new(),
3808 trees: GetBlockTrees::default(),
3809 size: None,
3810 version: None,
3811 merkle_root: None,
3812 block_commitments: None,
3813 final_sapling_root: None,
3814 final_orchard_root: None,
3815 nonce: None,
3816 bits: None,
3817 difficulty: None,
3818 chain_supply: None,
3819 value_pools: None,
3820 previous_block_hash: None,
3821 next_block_hash: None,
3822 solution: None,
3823 }))
3824 }
3825}
3826
3827#[allow(clippy::too_many_arguments)]
3829#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3830pub struct BlockObject {
3831 #[getter(copy)]
3833 #[serde(with = "hex")]
3834 hash: block::Hash,
3835
3836 confirmations: i64,
3839
3840 #[serde(skip_serializing_if = "Option::is_none")]
3842 #[getter(copy)]
3843 size: Option<i64>,
3844
3845 #[serde(skip_serializing_if = "Option::is_none")]
3847 #[getter(copy)]
3848 height: Option<Height>,
3849
3850 #[serde(skip_serializing_if = "Option::is_none")]
3852 #[getter(copy)]
3853 version: Option<u32>,
3854
3855 #[serde(with = "opthex", rename = "merkleroot")]
3857 #[serde(skip_serializing_if = "Option::is_none")]
3858 #[getter(copy)]
3859 merkle_root: Option<block::merkle::Root>,
3860
3861 #[serde(with = "opthex", rename = "blockcommitments")]
3864 #[serde(skip_serializing_if = "Option::is_none")]
3865 #[getter(copy)]
3866 block_commitments: Option<[u8; 32]>,
3867
3868 #[serde(with = "opthex", rename = "finalsaplingroot")]
3872 #[serde(skip_serializing_if = "Option::is_none")]
3873 #[getter(copy)]
3874 final_sapling_root: Option<[u8; 32]>,
3875
3876 #[serde(with = "opthex", rename = "finalorchardroot")]
3878 #[serde(skip_serializing_if = "Option::is_none")]
3879 #[getter(copy)]
3880 final_orchard_root: Option<[u8; 32]>,
3881
3882 #[serde(rename = "nTx")]
3886 n_tx: usize,
3887
3888 tx: Vec<GetBlockTransaction>,
3891
3892 #[serde(skip_serializing_if = "Option::is_none")]
3894 #[getter(copy)]
3895 time: Option<i64>,
3896
3897 #[serde(with = "opthex")]
3899 #[serde(skip_serializing_if = "Option::is_none")]
3900 #[getter(copy)]
3901 nonce: Option<[u8; 32]>,
3902
3903 #[serde(with = "opthex")]
3906 #[serde(skip_serializing_if = "Option::is_none")]
3907 #[getter(copy)]
3908 solution: Option<Solution>,
3909
3910 #[serde(with = "opthex")]
3912 #[serde(skip_serializing_if = "Option::is_none")]
3913 #[getter(copy)]
3914 bits: Option<CompactDifficulty>,
3915
3916 #[serde(skip_serializing_if = "Option::is_none")]
3919 #[getter(copy)]
3920 difficulty: Option<f64>,
3921
3922 #[serde(rename = "chainSupply")]
3927 #[serde(skip_serializing_if = "Option::is_none")]
3928 chain_supply: Option<GetBlockchainInfoBalance>,
3929
3930 #[serde(rename = "valuePools")]
3932 #[serde(skip_serializing_if = "Option::is_none")]
3933 value_pools: Option<BlockchainValuePoolBalances>,
3934
3935 #[getter(copy)]
3937 trees: GetBlockTrees,
3938
3939 #[serde(rename = "previousblockhash", skip_serializing_if = "Option::is_none")]
3941 #[serde(with = "opthex")]
3942 #[getter(copy)]
3943 previous_block_hash: Option<block::Hash>,
3944
3945 #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
3947 #[serde(with = "opthex")]
3948 #[getter(copy)]
3949 next_block_hash: Option<block::Hash>,
3950}
3951
3952#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
3953#[serde(untagged)]
3954pub enum GetBlockTransaction {
3957 Hash(#[serde(with = "hex")] transaction::Hash),
3959 Object(Box<TransactionObject>),
3961}
3962
3963#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
3967#[serde(untagged)]
3968pub enum GetBlockHeaderResponse {
3969 Raw(hex_data::HexData),
3971
3972 Object(Box<BlockHeaderObject>),
3974}
3975
3976#[deprecated(note = "Use `GetBlockHeaderResponse` instead")]
3977pub use self::GetBlockHeaderResponse as GetBlockHeader;
3978
3979#[allow(clippy::too_many_arguments)]
3980#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3981pub struct BlockHeaderObject {
3985 #[serde(with = "hex")]
3987 #[getter(copy)]
3988 hash: block::Hash,
3989
3990 confirmations: i64,
3993
3994 #[getter(copy)]
3996 height: Height,
3997
3998 version: u32,
4000
4001 #[serde(with = "hex", rename = "merkleroot")]
4003 #[getter(copy)]
4004 merkle_root: block::merkle::Root,
4005
4006 #[serde(with = "hex", rename = "blockcommitments")]
4009 #[getter(copy)]
4010 block_commitments: [u8; 32],
4011
4012 #[serde(with = "hex", rename = "finalsaplingroot")]
4014 #[getter(copy)]
4015 final_sapling_root: [u8; 32],
4016
4017 #[serde(skip)]
4020 sapling_tree_size: u64,
4021
4022 time: i64,
4024
4025 #[serde(with = "hex")]
4027 #[getter(copy)]
4028 nonce: [u8; 32],
4029
4030 #[serde(with = "hex")]
4032 #[getter(copy)]
4033 solution: Solution,
4034
4035 #[serde(with = "hex")]
4037 #[getter(copy)]
4038 bits: CompactDifficulty,
4039
4040 difficulty: f64,
4043
4044 #[serde(rename = "previousblockhash")]
4046 #[serde(with = "hex")]
4047 #[getter(copy)]
4048 previous_block_hash: block::Hash,
4049
4050 #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
4052 #[getter(copy)]
4053 #[serde(with = "opthex")]
4054 next_block_hash: Option<block::Hash>,
4055}
4056
4057#[deprecated(note = "Use `BlockHeaderObject` instead")]
4058pub use BlockHeaderObject as GetBlockHeaderObject;
4059
4060impl Default for GetBlockHeaderResponse {
4061 fn default() -> Self {
4062 GetBlockHeaderResponse::Object(Box::default())
4063 }
4064}
4065
4066impl Default for BlockHeaderObject {
4067 fn default() -> Self {
4068 let difficulty: ExpandedDifficulty = zebra_chain::work::difficulty::U256::one().into();
4069
4070 BlockHeaderObject {
4071 hash: block::Hash([0; 32]),
4072 confirmations: 0,
4073 height: Height::MIN,
4074 version: 4,
4075 merkle_root: block::merkle::Root([0; 32]),
4076 block_commitments: Default::default(),
4077 final_sapling_root: Default::default(),
4078 sapling_tree_size: Default::default(),
4079 time: 0,
4080 nonce: [0; 32],
4081 solution: Solution::for_proposal(),
4082 bits: difficulty.to_compact(),
4083 difficulty: 1.0,
4084 previous_block_hash: block::Hash([0; 32]),
4085 next_block_hash: Some(block::Hash([0; 32])),
4086 }
4087 }
4088}
4089
4090#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4096#[serde(transparent)]
4097pub struct GetBlockHashResponse(#[serde(with = "hex")] pub(crate) block::Hash);
4098
4099impl GetBlockHashResponse {
4100 pub fn new(hash: block::Hash) -> Self {
4102 GetBlockHashResponse(hash)
4103 }
4104
4105 pub fn hash(&self) -> block::Hash {
4107 self.0
4108 }
4109}
4110
4111#[deprecated(note = "Use `GetBlockHashResponse` instead")]
4112pub use self::GetBlockHashResponse as GetBlockHash;
4113
4114pub type Hash = GetBlockHashResponse;
4116
4117#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new)]
4119pub struct GetBlockHeightAndHashResponse {
4120 #[getter(copy)]
4122 height: block::Height,
4123 #[getter(copy)]
4125 hash: block::Hash,
4126}
4127
4128#[deprecated(note = "Use `GetBlockHeightAndHashResponse` instead.")]
4129pub use GetBlockHeightAndHashResponse as GetBestBlockHeightAndHash;
4130
4131impl Default for GetBlockHeightAndHashResponse {
4132 fn default() -> Self {
4133 Self {
4134 height: block::Height::MIN,
4135 hash: block::Hash([0; 32]),
4136 }
4137 }
4138}
4139
4140impl Default for GetBlockHashResponse {
4141 fn default() -> Self {
4142 GetBlockHashResponse(block::Hash([0; 32]))
4143 }
4144}
4145
4146#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4150#[serde(untagged)]
4151pub enum GetRawTransactionResponse {
4152 Raw(#[serde(with = "hex")] SerializedTransaction),
4154 Object(Box<TransactionObject>),
4156}
4157
4158#[deprecated(note = "Use `GetRawTransactionResponse` instead")]
4159pub use self::GetRawTransactionResponse as GetRawTransaction;
4160
4161impl Default for GetRawTransactionResponse {
4162 fn default() -> Self {
4163 Self::Object(Box::default())
4164 }
4165}
4166
4167#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4169#[serde(untagged)]
4170pub enum GetAddressUtxosResponse {
4171 Utxos(Vec<Utxo>),
4173 UtxosAndChainInfo(GetAddressUtxosResponseObject),
4175}
4176
4177#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4179pub struct GetAddressUtxosResponseObject {
4180 utxos: Vec<Utxo>,
4181 #[serde(with = "hex")]
4182 #[getter(copy)]
4183 hash: block::Hash,
4184 #[getter(copy)]
4185 height: block::Height,
4186}
4187
4188#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4192pub struct Utxo {
4193 address: transparent::Address,
4195
4196 #[serde(with = "hex")]
4198 #[getter(copy)]
4199 txid: transaction::Hash,
4200
4201 #[serde(rename = "outputIndex")]
4203 #[getter(copy)]
4204 output_index: OutputIndex,
4205
4206 #[serde(with = "hex")]
4208 script: transparent::Script,
4209
4210 satoshis: u64,
4212
4213 #[getter(copy)]
4217 height: Height,
4218}
4219
4220#[deprecated(note = "Use `Utxo` instead")]
4221pub use self::Utxo as GetAddressUtxos;
4222
4223impl Default for Utxo {
4224 fn default() -> Self {
4225 Self {
4226 address: transparent::Address::from_pub_key_hash(
4227 zebra_chain::parameters::NetworkKind::default(),
4228 [0u8; 20],
4229 ),
4230 txid: transaction::Hash::from([0; 32]),
4231 output_index: OutputIndex::from_u64(0),
4232 script: transparent::Script::new(&[0u8; 10]),
4233 satoshis: u64::default(),
4234 height: Height(0),
4235 }
4236 }
4237}
4238
4239impl Utxo {
4240 #[deprecated(note = "Use `Utxo::new` instead")]
4242 pub fn from_parts(
4243 address: transparent::Address,
4244 txid: transaction::Hash,
4245 output_index: OutputIndex,
4246 script: transparent::Script,
4247 satoshis: u64,
4248 height: Height,
4249 ) -> Self {
4250 Utxo {
4251 address,
4252 txid,
4253 output_index,
4254 script,
4255 satoshis,
4256 height,
4257 }
4258 }
4259
4260 pub fn into_parts(
4262 &self,
4263 ) -> (
4264 transparent::Address,
4265 transaction::Hash,
4266 OutputIndex,
4267 transparent::Script,
4268 u64,
4269 Height,
4270 ) {
4271 (
4272 self.address,
4273 self.txid,
4274 self.output_index,
4275 self.script.clone(),
4276 self.satoshis,
4277 self.height,
4278 )
4279 }
4280}
4281
4282#[derive(
4286 Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new, JsonSchema,
4287)]
4288#[serde(from = "DGetAddressTxIdsRequest")]
4289pub struct GetAddressTxIdsRequest {
4290 addresses: Vec<String>,
4293 start: Option<u32>,
4295 end: Option<u32>,
4297}
4298
4299impl GetAddressTxIdsRequest {
4300 #[deprecated(note = "Use `GetAddressTxIdsRequest::new` instead.")]
4302 pub fn from_parts(addresses: Vec<String>, start: u32, end: u32) -> Self {
4303 GetAddressTxIdsRequest {
4304 addresses,
4305 start: Some(start),
4306 end: Some(end),
4307 }
4308 }
4309
4310 pub fn into_parts(&self) -> (Vec<String>, u32, u32) {
4312 (
4313 self.addresses.clone(),
4314 self.start.unwrap_or(0),
4315 self.end.unwrap_or(0),
4316 )
4317 }
4318}
4319
4320impl From<DGetAddressTxIdsRequest> for GetAddressTxIdsRequest {
4321 fn from(request: DGetAddressTxIdsRequest) -> Self {
4322 match request {
4323 DGetAddressTxIdsRequest::Single(addr) => GetAddressTxIdsRequest {
4324 addresses: vec![addr],
4325 start: None,
4326 end: None,
4327 },
4328 DGetAddressTxIdsRequest::Object {
4329 addresses,
4330 start,
4331 end,
4332 } => GetAddressTxIdsRequest {
4333 addresses,
4334 start,
4335 end,
4336 },
4337 }
4338 }
4339}
4340
4341#[derive(Debug, serde::Deserialize, JsonSchema)]
4343#[serde(untagged)]
4344enum DGetAddressTxIdsRequest {
4345 Single(String),
4347 Object {
4349 addresses: Vec<String>,
4351 start: Option<u32>,
4353 end: Option<u32>,
4355 },
4356}
4357
4358impl ValidateAddresses for GetAddressTxIdsRequest {
4359 fn addresses(&self) -> &[String] {
4360 &self.addresses
4361 }
4362}
4363
4364#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4366pub struct GetBlockTrees {
4367 #[serde(skip_serializing_if = "SaplingTrees::is_empty")]
4368 sapling: SaplingTrees,
4369 #[serde(skip_serializing_if = "OrchardTrees::is_empty")]
4370 orchard: OrchardTrees,
4371}
4372
4373impl Default for GetBlockTrees {
4374 fn default() -> Self {
4375 GetBlockTrees {
4376 sapling: SaplingTrees { size: 0 },
4377 orchard: OrchardTrees { size: 0 },
4378 }
4379 }
4380}
4381
4382impl GetBlockTrees {
4383 pub fn new(sapling: u64, orchard: u64) -> Self {
4385 GetBlockTrees {
4386 sapling: SaplingTrees { size: sapling },
4387 orchard: OrchardTrees { size: orchard },
4388 }
4389 }
4390
4391 pub fn sapling(self) -> u64 {
4393 self.sapling.size
4394 }
4395
4396 pub fn orchard(self) -> u64 {
4398 self.orchard.size
4399 }
4400}
4401
4402#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4404pub struct SaplingTrees {
4405 size: u64,
4406}
4407
4408impl SaplingTrees {
4409 fn is_empty(&self) -> bool {
4410 self.size == 0
4411 }
4412}
4413
4414#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4416pub struct OrchardTrees {
4417 size: u64,
4418}
4419
4420impl OrchardTrees {
4421 fn is_empty(&self) -> bool {
4422 self.size == 0
4423 }
4424}
4425
4426fn build_height_range(
4442 start: Option<u32>,
4443 end: Option<u32>,
4444 chain_height: Height,
4445) -> Result<RangeInclusive<Height>> {
4446 let start = Height(start.unwrap_or(0)).min(chain_height);
4449
4450 let end = match end {
4452 Some(0) | None => chain_height,
4453 Some(val) => Height(val).min(chain_height),
4454 };
4455
4456 if start > end {
4457 return Err(ErrorObject::owned(
4458 ErrorCode::InvalidParams.code(),
4459 format!("start {start:?} must be less than or equal to end {end:?}"),
4460 None::<()>,
4461 ));
4462 }
4463
4464 Ok(start..=end)
4465}
4466
4467pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height> {
4475 if index >= 0 {
4476 let height = index.try_into().map_err(|_| {
4477 ErrorObject::borrowed(
4478 ErrorCode::InvalidParams.code(),
4479 "Index conversion failed",
4480 None,
4481 )
4482 })?;
4483 if height > tip_height.0 {
4484 return Err(ErrorObject::borrowed(
4485 ErrorCode::InvalidParams.code(),
4486 "Provided index is greater than the current tip",
4487 None,
4488 ));
4489 }
4490 Ok(Height(height))
4491 } else {
4492 let height = i32::try_from(tip_height.0)
4494 .map_err(|_| {
4495 ErrorObject::borrowed(
4496 ErrorCode::InvalidParams.code(),
4497 "Tip height conversion failed",
4498 None,
4499 )
4500 })?
4501 .checked_add(index + 1);
4502
4503 let sanitized_height = match height {
4504 None => {
4505 return Err(ErrorObject::borrowed(
4506 ErrorCode::InvalidParams.code(),
4507 "Provided index is not valid",
4508 None,
4509 ));
4510 }
4511 Some(h) => {
4512 if h < 0 {
4513 return Err(ErrorObject::borrowed(
4514 ErrorCode::InvalidParams.code(),
4515 "Provided negative index ends up with a negative height",
4516 None,
4517 ));
4518 }
4519 let h: u32 = h.try_into().map_err(|_| {
4520 ErrorObject::borrowed(
4521 ErrorCode::InvalidParams.code(),
4522 "Height conversion failed",
4523 None,
4524 )
4525 })?;
4526 if h > tip_height.0 {
4527 return Err(ErrorObject::borrowed(
4528 ErrorCode::InvalidParams.code(),
4529 "Provided index is greater than the current tip",
4530 None,
4531 ));
4532 }
4533
4534 h
4535 }
4536 };
4537
4538 Ok(Height(sanitized_height))
4539 }
4540}
4541
4542pub mod opthex {
4544 use hex::{FromHex, ToHex};
4545 use serde::{de, Deserialize, Deserializer, Serializer};
4546
4547 #[allow(missing_docs)]
4548 pub fn serialize<S, T>(data: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
4549 where
4550 S: Serializer,
4551 T: ToHex,
4552 {
4553 match data {
4554 Some(data) => {
4555 let s = data.encode_hex::<String>();
4556 serializer.serialize_str(&s)
4557 }
4558 None => serializer.serialize_none(),
4559 }
4560 }
4561
4562 #[allow(missing_docs)]
4563 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
4564 where
4565 D: Deserializer<'de>,
4566 T: FromHex,
4567 {
4568 let opt = Option::<String>::deserialize(deserializer)?;
4569 match opt {
4570 Some(s) => T::from_hex(&s)
4571 .map(Some)
4572 .map_err(|_e| de::Error::custom("failed to convert hex string")),
4573 None => Ok(None),
4574 }
4575 }
4576}
4577
4578pub mod arrayhex {
4580 use serde::{Deserializer, Serializer};
4581 use std::fmt;
4582
4583 #[allow(missing_docs)]
4584 pub fn serialize<S, const N: usize>(data: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
4585 where
4586 S: Serializer,
4587 {
4588 let hex_string = hex::encode(data);
4589 serializer.serialize_str(&hex_string)
4590 }
4591
4592 #[allow(missing_docs)]
4593 pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
4594 where
4595 D: Deserializer<'de>,
4596 {
4597 struct HexArrayVisitor<const N: usize>;
4598
4599 impl<const N: usize> serde::de::Visitor<'_> for HexArrayVisitor<N> {
4600 type Value = [u8; N];
4601
4602 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
4603 write!(formatter, "a hex string representing exactly {N} bytes")
4604 }
4605
4606 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
4607 where
4608 E: serde::de::Error,
4609 {
4610 let vec = hex::decode(v).map_err(E::custom)?;
4611 vec.clone().try_into().map_err(|_| {
4612 E::invalid_length(vec.len(), &format!("expected {N} bytes").as_str())
4613 })
4614 }
4615 }
4616
4617 deserializer.deserialize_str(HexArrayVisitor::<N>)
4618 }
4619}
4620
4621pub async fn chain_tip_difficulty<State>(
4623 network: Network,
4624 mut state: State,
4625 should_use_default: bool,
4626) -> Result<f64>
4627where
4628 State: ReadStateService,
4629{
4630 let request = ReadRequest::ChainInfo;
4631
4632 let response = state
4638 .ready()
4639 .and_then(|service| service.call(request))
4640 .await;
4641
4642 let response = match (should_use_default, response) {
4643 (_, Ok(res)) => res,
4644 (true, Err(_)) => {
4645 return Ok((U256::from(network.target_difficulty_limit()) >> 128).as_u128() as f64);
4646 }
4647 (false, Err(error)) => return Err(ErrorObject::owned(0, error.to_string(), None::<()>)),
4648 };
4649
4650 let chain_info = match response {
4651 ReadResponse::ChainInfo(info) => info,
4652 _ => unreachable!("unmatched response to a chain info request"),
4653 };
4654
4655 let pow_limit: U256 = network.target_difficulty_limit().into();
4678 let Some(difficulty) = chain_info.expected_difficulty.to_expanded() else {
4679 return Ok(0.0);
4680 };
4681
4682 let pow_limit = pow_limit >> 128;
4684 let difficulty = U256::from(difficulty) >> 128;
4685
4686 let pow_limit = pow_limit.as_u128() as f64;
4689 let difficulty = difficulty.as_u128() as f64;
4690
4691 Ok(pow_limit / difficulty)
4693}
4694
4695#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)]
4697pub enum AddNodeCommand {
4698 #[serde(rename = "add")]
4700 Add,
4701}
4702
4703#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4707#[serde(transparent)]
4708pub struct GetTxOutResponse(Option<types::transaction::OutputObject>);