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, MinerParams,
123 },
124 get_blockchain_info::GetBlockchainInfoBalance,
125 get_mempool_info::GetMempoolInfoResponse,
126 get_mining_info::GetMiningInfoResponse,
127 get_raw_mempool::{self, GetRawMempoolResponse},
128 get_standard_fee::GetStandardFeeResponse,
129 long_poll::LongPollInput,
130 network_info::{GetNetworkInfoResponse, NetworkInfo},
131 peer_info::PeerInfo,
132 submit_block::{SubmitBlockErrorResponse, SubmitBlockParameters, SubmitBlockResponse},
133 subsidy::GetBlockSubsidyResponse,
134 transaction::TransactionObject,
135 unified_address::ZListUnifiedReceiversResponse,
136 validate_address::ValidateAddressResponse,
137 z_validate_address::ZValidateAddressResponse,
138};
139
140include!(concat!(env!("OUT_DIR"), "/rpc_openrpc.rs"));
141
142pub(super) const PARAM_VERBOSE_DESC: &str =
145 "Boolean flag to indicate verbosity, true for a json object, false for hex encoded data.";
146pub(super) const PARAM_POOL_DESC: &str =
147 "The pool from which subtrees should be returned. Either \"sapling\" or \"orchard\".";
148pub(super) const PARAM_START_INDEX_DESC: &str =
149 "The index of the first 2^16-leaf subtree to return.";
150pub(super) const PARAM_LIMIT_DESC: &str = "The maximum number of subtrees to return.";
151pub(super) const PARAM_REQUEST_DESC: &str = "The request object containing the parameters.";
152pub(super) const PARAM_INDEX_DESC: &str = "The index of the subtree to return.";
153pub(super) const PARAM_RAW_TRANSACTION_HEX_DESC: &str = "The hex-encoded raw transaction bytes.";
154#[allow(non_upper_case_globals)]
155pub(super) const PARAM__ALLOW_HIGH_FEES_DESC: &str = "Whether to allow high fees.";
156pub(super) const PARAM_NUM_BLOCKS_DESC: &str = "The number of blocks to return.";
157pub(super) const PARAM_HEIGHT_DESC: &str = "The height of the block to return.";
158pub(super) const PARAM_COMMAND_DESC: &str = "The command to execute.";
159#[allow(non_upper_case_globals)]
160pub(super) const PARAM__PARAMETERS_DESC: &str = "The parameters for the command.";
161pub(super) const PARAM_BLOCK_HASH_DESC: &str = "The hash of the block to return.";
162pub(super) const PARAM_ADDRESS_DESC: &str = "The address to return.";
163pub(super) const PARAM_ADDRESS_STRINGS_DESC: &str = "The addresses to return.";
164pub(super) const PARAM_ADDR_DESC: &str = "The address to return.";
165pub(super) const PARAM_HEX_DATA_DESC: &str = "The hex-encoded data to return.";
166pub(super) const PARAM_TXID_DESC: &str = "The transaction ID to return.";
167pub(super) const PARAM_HASH_OR_HEIGHT_DESC: &str = "The block hash or height to return.";
168pub(super) const PARAM_PARAMETERS_DESC: &str = "The parameters for the command.";
169pub(super) const PARAM_VERBOSITY_DESC: &str = "Whether to include verbose output.";
170pub(super) const PARAM_N_DESC: &str = "The output index in the transaction.";
171pub(super) const PARAM_INCLUDE_MEMPOOL_DESC: &str =
172 "Whether to include mempool transactions in the response.";
173
174#[cfg(test)]
175mod tests;
176
177#[rpc(server)]
178pub trait Rpc {
180 #[method(name = "getinfo")]
195 async fn get_info(&self) -> Result<GetInfoResponse>;
196
197 #[method(name = "getblockchaininfo")]
208 async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse>;
209
210 #[method(name = "getaddressbalance")]
233 async fn get_address_balance(
234 &self,
235 address_strings: GetAddressBalanceRequest,
236 ) -> Result<GetAddressBalanceResponse>;
237
238 #[method(name = "sendrawtransaction")]
255 async fn send_raw_transaction(
256 &self,
257 raw_transaction_hex: String,
258 _allow_high_fees: Option<bool>,
259 ) -> Result<SendRawTransactionResponse>;
260
261 #[method(name = "getblock")]
281 async fn get_block(
282 &self,
283 hash_or_height: String,
284 verbosity: Option<u8>,
285 ) -> Result<GetBlockResponse>;
286
287 #[method(name = "getblockheader")]
305 async fn get_block_header(
306 &self,
307 hash_or_height: String,
308 verbose: Option<bool>,
309 ) -> Result<GetBlockHeaderResponse>;
310
311 #[method(name = "getbestblockhash")]
317 fn get_best_block_hash(&self) -> Result<GetBlockHashResponse>;
318
319 #[method(name = "getbestblockheightandhash")]
325 fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse>;
326
327 #[method(name = "getmempoolinfo")]
331 async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse>;
332
333 #[method(name = "getrawmempool")]
343 async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse>;
344
345 #[method(name = "z_gettreestate")]
362 async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse>;
363
364 #[method(name = "z_getsubtreesbyindex")]
383 async fn z_get_subtrees_by_index(
384 &self,
385 pool: String,
386 start_index: NoteCommitmentSubtreeIndex,
387 limit: Option<NoteCommitmentSubtreeIndex>,
388 ) -> Result<GetSubtreesByIndexResponse>;
389
390 #[method(name = "getrawtransaction")]
402 async fn get_raw_transaction(
403 &self,
404 txid: String,
405 verbose: Option<u8>,
406 block_hash: Option<String>,
407 ) -> Result<GetRawTransactionResponse>;
408
409 #[method(name = "getaddresstxids")]
439 async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>>;
440
441 #[method(name = "getaddressutxos")]
460 async fn get_address_utxos(
461 &self,
462 request: GetAddressUtxosRequest,
463 ) -> Result<GetAddressUtxosResponse>;
464
465 #[method(name = "stop")]
476 fn stop(&self) -> Result<String>;
477
478 #[method(name = "getblockcount")]
485 fn get_block_count(&self) -> Result<u32>;
486
487 #[method(name = "getblockhash")]
503 async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse>;
504
505 #[method(name = "getblocktemplate")]
527 async fn get_block_template(
528 &self,
529 parameters: Option<GetBlockTemplateParameters>,
530 ) -> Result<GetBlockTemplateResponse>;
531
532 #[method(name = "submitblock")]
548 async fn submit_block(
549 &self,
550 hex_data: HexData,
551 _parameters: Option<SubmitBlockParameters>,
552 ) -> Result<SubmitBlockResponse>;
553
554 #[method(name = "getmininginfo")]
560 async fn get_mining_info(&self) -> Result<GetMiningInfoResponse>;
561
562 #[method(name = "getnetworksolps")]
573 async fn get_network_sol_ps(&self, num_blocks: Option<i32>, height: Option<i32>)
574 -> Result<u64>;
575
576 #[method(name = "getnetworkhashps")]
586 async fn get_network_hash_ps(
587 &self,
588 num_blocks: Option<i32>,
589 height: Option<i32>,
590 ) -> Result<u64> {
591 self.get_network_sol_ps(num_blocks, height).await
592 }
593
594 #[method(name = "getnetworkinfo")]
600 async fn get_network_info(&self) -> Result<GetNetworkInfoResponse>;
601
602 #[method(name = "getpeerinfo")]
608 async fn get_peer_info(&self) -> Result<Vec<PeerInfo>>;
609
610 #[method(name = "ping")]
619 async fn ping(&self) -> Result<()>;
620
621 #[method(name = "validateaddress")]
632 async fn validate_address(&self, address: String) -> Result<ValidateAddressResponse>;
633
634 #[method(name = "z_validateaddress")]
649 async fn z_validate_address(&self, address: String) -> Result<ZValidateAddressResponse>;
650
651 #[method(name = "getstandardfee")]
659 async fn get_standard_fee(&self) -> Result<GetStandardFeeResponse>;
660
661 #[method(name = "getblocksubsidy")]
676 async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse>;
677
678 #[method(name = "getdifficulty")]
684 async fn get_difficulty(&self) -> Result<f64>;
685
686 #[method(name = "z_listunifiedreceivers")]
700 async fn z_list_unified_receivers(
701 &self,
702 address: String,
703 ) -> Result<ZListUnifiedReceiversResponse>;
704
705 #[method(name = "invalidateblock")]
713 async fn invalidate_block(&self, block_hash: String) -> Result<()>;
714
715 #[method(name = "reconsiderblock")]
721 async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>>;
722
723 #[method(name = "generate")]
724 async fn generate(&self, num_blocks: u32) -> Result<Vec<GetBlockHashResponse>>;
738
739 #[method(name = "generatetoaddress")]
740 async fn generate_to_address(
760 &self,
761 num_blocks: u32,
762 address: String,
763 ) -> Result<Vec<GetBlockHashResponse>> {
764 let _ = (num_blocks, address);
765 Err(ErrorObject::borrowed(
766 ErrorCode::MethodNotFound.code(),
767 "generatetoaddress is not implemented",
768 None,
769 ))
770 }
771
772 #[method(name = "addnode")]
773 async fn add_node(&self, addr: PeerSocketAddr, command: AddNodeCommand) -> Result<()>;
788
789 #[method(name = "rpc.discover")]
791 fn openrpc(&self) -> openrpsee::openrpc::Response;
792 #[method(name = "gettxout")]
804 async fn get_tx_out(
805 &self,
806 txid: String,
807 n: u32,
808 include_mempool: Option<bool>,
809 ) -> Result<GetTxOutResponse>;
810}
811
812#[derive(Clone)]
814pub struct RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
815where
816 Mempool: MempoolService,
817 State: StateService,
818 ReadState: ReadStateService,
819 Tip: ChainTip + Clone + Send + Sync + 'static,
820 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
821 BlockVerifierRouter: BlockVerifierService,
822 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
823{
824 build_version: String,
828
829 user_agent: String,
831
832 network: Network,
834
835 debug_force_finished_sync: bool,
838
839 mempool: Mempool,
843
844 state: State,
846
847 read_state: ReadState,
849
850 latest_chain_tip: Tip,
852
853 queue_sender: broadcast::Sender<UnminedTx>,
857
858 address_book: AddressBook,
860
861 last_warn_error_log_rx: LoggedLastEvent,
863
864 gbt: GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>,
866}
867
868pub type LoggedLastEvent = watch::Receiver<Option<(String, tracing::Level, chrono::DateTime<Utc>)>>;
870
871impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> fmt::Debug
872 for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
873where
874 Mempool: MempoolService,
875 State: StateService,
876 ReadState: ReadStateService,
877 Tip: ChainTip + Clone + Send + Sync + 'static,
878 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
879 BlockVerifierRouter: BlockVerifierService,
880 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
881{
882 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
883 f.debug_struct("RpcImpl")
885 .field("build_version", &self.build_version)
886 .field("user_agent", &self.user_agent)
887 .field("network", &self.network)
888 .field("debug_force_finished_sync", &self.debug_force_finished_sync)
889 .field("getblocktemplate", &self.gbt)
890 .finish()
891 }
892}
893
894impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
895 RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
896where
897 Mempool: MempoolService,
898 State: StateService,
899 ReadState: ReadStateService,
900 Tip: ChainTip + Clone + Send + Sync + 'static,
901 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
902 BlockVerifierRouter: BlockVerifierService,
903 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
904{
905 #[allow(clippy::too_many_arguments)]
910 pub fn new<VersionString, UserAgentString>(
911 network: Network,
912 mining_config: config::mining::Config,
913 debug_force_finished_sync: bool,
914 build_version: VersionString,
915 user_agent: UserAgentString,
916 mempool: Mempool,
917 state: State,
918 read_state: ReadState,
919 block_verifier_router: BlockVerifierRouter,
920 sync_status: SyncStatus,
921 latest_chain_tip: Tip,
922 address_book: AddressBook,
923 last_warn_error_log_rx: LoggedLastEvent,
924 mined_block_sender: Option<mpsc::Sender<(block::Hash, block::Height)>>,
925 ) -> (Self, JoinHandle<()>)
926 where
927 VersionString: ToString + Clone + Send + 'static,
928 UserAgentString: ToString + Clone + Send + 'static,
929 {
930 let (runner, queue_sender) = Queue::start();
931
932 let mut build_version = build_version.to_string();
933 let user_agent = user_agent.to_string();
934
935 if !build_version.is_empty() && !build_version.starts_with('v') {
937 build_version.insert(0, 'v');
938 }
939
940 let gbt = GetBlockTemplateHandler::new(
941 &network,
942 mining_config.clone(),
943 block_verifier_router,
944 sync_status,
945 mined_block_sender,
946 );
947
948 let rpc_impl = RpcImpl {
949 build_version,
950 user_agent,
951 network: network.clone(),
952 debug_force_finished_sync,
953 mempool: mempool.clone(),
954 state: state.clone(),
955 read_state: read_state.clone(),
956 latest_chain_tip: latest_chain_tip.clone(),
957 queue_sender,
958 address_book,
959 last_warn_error_log_rx,
960 gbt,
961 };
962
963 let rpc_tx_queue_task_handle = tokio::spawn(
965 runner
966 .run(mempool, read_state, latest_chain_tip, network)
967 .in_current_span(),
968 );
969
970 (rpc_impl, rpc_tx_queue_task_handle)
971 }
972
973 pub fn network(&self) -> &Network {
975 &self.network
976 }
977}
978
979#[async_trait]
980impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> RpcServer
981 for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
982where
983 Mempool: MempoolService,
984 State: StateService,
985 ReadState: ReadStateService,
986 Tip: ChainTip + Clone + Send + Sync + 'static,
987 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
988 BlockVerifierRouter: BlockVerifierService,
989 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
990{
991 async fn get_info(&self) -> Result<GetInfoResponse> {
992 let version = GetInfoResponse::version_from_string(&self.build_version)
993 .expect("invalid version string");
994
995 let connections = self.address_book.recently_live_peers(Utc::now()).len();
996
997 let last_error_recorded = self.last_warn_error_log_rx.borrow().clone();
998 let (last_error_log, _level, last_error_log_time) = last_error_recorded.unwrap_or((
999 GetInfoResponse::default().errors,
1000 tracing::Level::INFO,
1001 Utc::now(),
1002 ));
1003
1004 let tip_height = self
1005 .latest_chain_tip
1006 .best_tip_height()
1007 .unwrap_or(Height::MIN);
1008 let testnet = self.network.is_a_test_network();
1009
1010 let pay_tx_fee = 0.0;
1016
1017 let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
1018 / (zebra_chain::amount::COIN as f64);
1019 let difficulty = chain_tip_difficulty(self.network.clone(), self.read_state.clone(), true)
1020 .await
1021 .expect("should always be Ok when `should_use_default` is true");
1022
1023 let response = GetInfoResponse {
1024 version,
1025 build: self.build_version.clone(),
1026 subversion: self.user_agent.clone(),
1027 protocol_version: zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0,
1028 blocks: tip_height.0,
1029 connections,
1030 proxy: None,
1031 difficulty,
1032 testnet,
1033 pay_tx_fee,
1034 relay_fee,
1035 errors: last_error_log,
1036 errors_timestamp: last_error_log_time.timestamp(),
1037 };
1038
1039 Ok(response)
1040 }
1041
1042 #[allow(clippy::unwrap_in_result)]
1043 async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse> {
1044 let debug_force_finished_sync = self.debug_force_finished_sync;
1045 let network = &self.network;
1046
1047 let (usage_info_rsp, tip_pool_values_rsp, chain_tip_difficulty) = {
1048 use zebra_state::ReadRequest::*;
1049 let state_call = |request| self.read_state.clone().oneshot(request);
1050 tokio::join!(
1051 state_call(UsageInfo),
1052 state_call(TipPoolValues),
1053 chain_tip_difficulty(network.clone(), self.read_state.clone(), true)
1054 )
1055 };
1056
1057 let (size_on_disk, (tip_height, tip_hash), value_balance, difficulty) = {
1058 use zebra_state::ReadResponse::*;
1059
1060 let UsageInfo(size_on_disk) = usage_info_rsp.map_misc_error()? else {
1061 unreachable!("unmatched response to a TipPoolValues request")
1062 };
1063
1064 let (tip, value_balance) = match tip_pool_values_rsp {
1065 Ok(TipPoolValues {
1066 tip_height,
1067 tip_hash,
1068 value_balance,
1069 }) => ((tip_height, tip_hash), value_balance),
1070 Ok(_) => unreachable!("unmatched response to a TipPoolValues request"),
1071 Err(_) => ((Height::MIN, network.genesis_hash()), Default::default()),
1072 };
1073
1074 let difficulty = chain_tip_difficulty
1075 .expect("should always be Ok when `should_use_default` is true");
1076
1077 (size_on_disk, tip, value_balance, difficulty)
1078 };
1079
1080 let now = Utc::now();
1081 let (estimated_height, verification_progress) = self
1082 .latest_chain_tip
1083 .best_tip_height_and_block_time()
1084 .map(|(tip_height, tip_block_time)| {
1085 let height =
1086 NetworkChainTipHeightEstimator::new(tip_block_time, tip_height, network)
1087 .estimate_height_at(now);
1088
1089 let height =
1093 if tip_block_time > now || height < tip_height || debug_force_finished_sync {
1094 tip_height
1095 } else {
1096 height
1097 };
1098
1099 (height, f64::from(tip_height.0) / f64::from(height.0))
1100 })
1101 .unwrap_or((Height::MIN, 0.0));
1103
1104 let verification_progress = if network.is_regtest() {
1105 1.0
1106 } else {
1107 verification_progress
1108 };
1109
1110 let mut upgrades = IndexMap::new();
1114 for (activation_height, network_upgrade) in network.full_activation_list() {
1115 if let Some(branch_id) = network_upgrade.branch_id() {
1120 let status = if tip_height >= activation_height {
1122 NetworkUpgradeStatus::Active
1123 } else {
1124 NetworkUpgradeStatus::Pending
1125 };
1126
1127 let upgrade = NetworkUpgradeInfo {
1128 name: network_upgrade,
1129 activation_height,
1130 status,
1131 };
1132 upgrades.insert(ConsensusBranchIdHex(branch_id), upgrade);
1133 }
1134 }
1135
1136 let next_block_height =
1138 (tip_height + 1).expect("valid chain tips are a lot less than Height::MAX");
1139 let consensus = TipConsensusBranch {
1140 chain_tip: ConsensusBranchIdHex(
1141 NetworkUpgrade::current(network, tip_height)
1142 .branch_id()
1143 .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1144 ),
1145 next_block: ConsensusBranchIdHex(
1146 NetworkUpgrade::current(network, next_block_height)
1147 .branch_id()
1148 .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1149 ),
1150 };
1151
1152 let response = GetBlockchainInfoResponse {
1153 chain: network.bip70_network_name(),
1154 blocks: tip_height,
1155 best_block_hash: tip_hash,
1156 estimated_height,
1157 chain_supply: GetBlockchainInfoBalance::chain_supply(value_balance),
1158 value_pools: GetBlockchainInfoBalance::value_pools(value_balance, None),
1159 upgrades,
1160 consensus,
1161 headers: tip_height,
1162 difficulty,
1163 verification_progress,
1164 chain_work: 0,
1166 pruned: false,
1167 size_on_disk,
1168 commitments: 0,
1170 };
1171
1172 Ok(response)
1173 }
1174
1175 async fn get_address_balance(
1176 &self,
1177 address_strings: GetAddressBalanceRequest,
1178 ) -> Result<GetAddressBalanceResponse> {
1179 let valid_addresses = address_strings.valid_addresses()?;
1180
1181 let request = zebra_state::ReadRequest::AddressBalance(valid_addresses);
1182 let response = self
1183 .read_state
1184 .clone()
1185 .oneshot(request)
1186 .await
1187 .map_misc_error()?;
1188
1189 match response {
1190 zebra_state::ReadResponse::AddressBalance { balance, received } => {
1191 Ok(GetAddressBalanceResponse {
1192 balance: u64::from(balance),
1193 received,
1194 })
1195 }
1196 _ => unreachable!("Unexpected response from state service: {response:?}"),
1197 }
1198 }
1199
1200 async fn send_raw_transaction(
1202 &self,
1203 raw_transaction_hex: String,
1204 _allow_high_fees: Option<bool>,
1205 ) -> Result<SendRawTransactionResponse> {
1206 let mempool = self.mempool.clone();
1207 let queue_sender = self.queue_sender.clone();
1208
1209 let raw_transaction_bytes = Vec::from_hex(raw_transaction_hex)
1212 .map_error(server::error::LegacyCode::Deserialization)?;
1213 let raw_transaction = Transaction::zcash_deserialize(&*raw_transaction_bytes)
1214 .map_error(server::error::LegacyCode::Deserialization)?;
1215
1216 let transaction_hash = raw_transaction.hash();
1217
1218 let unmined_transaction = UnminedTx::from(raw_transaction.clone());
1220 let _ = queue_sender.send(unmined_transaction);
1221
1222 let transaction_parameter = mempool::Gossip::Tx(raw_transaction.into());
1223 let request = mempool::Request::Queue(vec![transaction_parameter]);
1224
1225 let response = mempool.oneshot(request).await.map_misc_error()?;
1226
1227 let mut queue_results = match response {
1228 mempool::Response::Queued(results) => results,
1229 _ => unreachable!("incorrect response variant from mempool service"),
1230 };
1231
1232 assert_eq!(
1233 queue_results.len(),
1234 1,
1235 "mempool service returned more results than expected"
1236 );
1237
1238 let queue_result = queue_results
1239 .pop()
1240 .expect("there should be exactly one item in Vec")
1241 .inspect_err(|err| tracing::debug!("sent transaction to mempool: {:?}", &err))
1242 .map_misc_error()?
1243 .await
1244 .map_misc_error()?;
1245
1246 tracing::debug!("sent transaction to mempool: {:?}", &queue_result);
1247
1248 queue_result
1249 .map(|_| SendRawTransactionResponse(transaction_hash))
1250 .map_error(server::error::LegacyCode::Verify)
1257 }
1258
1259 async fn get_block(
1264 &self,
1265 hash_or_height: String,
1266 verbosity: Option<u8>,
1267 ) -> Result<GetBlockResponse> {
1268 let verbosity = verbosity.unwrap_or(1);
1269 let network = self.network.clone();
1270 let original_hash_or_height = hash_or_height.clone();
1271
1272 let get_block_header_future = if matches!(verbosity, 1 | 2) {
1274 Some(self.get_block_header(original_hash_or_height.clone(), Some(true)))
1275 } else {
1276 None
1277 };
1278
1279 let hash_or_height =
1280 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1281 .map_error(server::error::LegacyCode::InvalidParameter)?;
1284
1285 if verbosity == 0 {
1286 let request = zebra_state::ReadRequest::Block(hash_or_height);
1287 let response = self
1288 .read_state
1289 .clone()
1290 .oneshot(request)
1291 .await
1292 .map_misc_error()?;
1293
1294 match response {
1295 zebra_state::ReadResponse::Block(Some(block)) => {
1296 Ok(GetBlockResponse::Raw(block.into()))
1297 }
1298 zebra_state::ReadResponse::Block(None) => {
1299 Err("Block not found").map_error(server::error::LegacyCode::InvalidParameter)
1300 }
1301 _ => unreachable!("unmatched response to a block request"),
1302 }
1303 } else if let Some(get_block_header_future) = get_block_header_future {
1304 let get_block_header_result: Result<GetBlockHeaderResponse> =
1305 get_block_header_future.await;
1306
1307 let GetBlockHeaderResponse::Object(block_header) = get_block_header_result? else {
1308 panic!("must return Object")
1309 };
1310
1311 let BlockHeaderObject {
1312 hash,
1313 confirmations,
1314 height,
1315 version,
1316 merkle_root,
1317 block_commitments,
1318 final_sapling_root,
1319 sapling_tree_size,
1320 time,
1321 nonce,
1322 solution,
1323 bits,
1324 difficulty,
1325 previous_block_hash,
1326 next_block_hash,
1327 } = *block_header;
1328
1329 let transactions_request = match verbosity {
1330 1 => zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height),
1331 2 => zebra_state::ReadRequest::BlockAndSize(hash_or_height),
1332 _other => panic!("get_block_header_fut should be none"),
1333 };
1334
1335 let hash_or_height = hash.into();
1340 let requests = vec![
1341 transactions_request,
1349 zebra_state::ReadRequest::OrchardTree(hash_or_height),
1351 zebra_state::ReadRequest::IronwoodTree(hash_or_height),
1353 zebra_state::ReadRequest::BlockInfo(previous_block_hash.into()),
1355 zebra_state::ReadRequest::BlockInfo(hash_or_height),
1356 ];
1357
1358 let mut futs = FuturesOrdered::new();
1359
1360 for request in requests {
1361 futs.push_back(self.read_state.clone().oneshot(request));
1362 }
1363
1364 let tx_ids_response = futs.next().await.expect("`futs` should not be empty");
1365 let (tx, size): (Vec<_>, Option<usize>) = match tx_ids_response.map_misc_error()? {
1366 zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => (
1367 tx_ids
1368 .ok_or_misc_error("block not found")?
1369 .iter()
1370 .map(|tx_id| GetBlockTransaction::Hash(*tx_id))
1371 .collect(),
1372 None,
1373 ),
1374 zebra_state::ReadResponse::BlockAndSize(block_and_size) => {
1375 let (block, size) = block_and_size.ok_or_misc_error("Block not found")?;
1376 let block_time = block.header.time;
1377 let transactions = block
1378 .transactions
1379 .iter()
1380 .map(|tx| {
1381 GetBlockTransaction::Object(Box::new(
1382 TransactionObject::from_transaction(
1383 tx.clone(),
1384 Some(height),
1385 Some(confirmations),
1386 &network,
1387 Some(block_time),
1388 Some(hash),
1389 Some(true),
1390 tx.hash(),
1391 ),
1392 ))
1393 })
1394 .collect();
1395 (transactions, Some(size))
1396 }
1397 _ => unreachable!("unmatched response to a transaction_ids_for_block request"),
1398 };
1399
1400 let orchard_tree_response = futs.next().await.expect("`futs` should not be empty");
1401 let zebra_state::ReadResponse::OrchardTree(orchard_tree) =
1402 orchard_tree_response.map_misc_error()?
1403 else {
1404 unreachable!("unmatched response to a OrchardTree request");
1405 };
1406
1407 let nu5_activation = NetworkUpgrade::Nu5.activation_height(&network);
1408
1409 let orchard_tree = orchard_tree.ok_or_misc_error("missing Orchard tree")?;
1411
1412 let final_orchard_root = match nu5_activation {
1413 Some(activation_height) if height >= activation_height => {
1414 Some(orchard_tree.root().into())
1415 }
1416 _other => None,
1417 };
1418
1419 let sapling = SaplingTrees {
1420 size: sapling_tree_size,
1421 };
1422
1423 let orchard_tree_size = orchard_tree.count();
1424 let orchard = OrchardTrees {
1425 size: orchard_tree_size,
1426 };
1427
1428 let ironwood_tree_response = futs.next().await.expect("`futs` should not be empty");
1429 let zebra_state::ReadResponse::IronwoodTree(ironwood_tree) =
1430 ironwood_tree_response.map_misc_error()?
1431 else {
1432 unreachable!("unmatched response to an IronwoodTree request");
1433 };
1434
1435 let ironwood_tree = ironwood_tree.ok_or_misc_error("missing Ironwood tree")?;
1438 let ironwood = IronwoodTrees {
1439 size: ironwood_tree.count(),
1440 };
1441
1442 let trees = GetBlockTrees {
1443 sapling,
1444 orchard,
1445 ironwood,
1446 };
1447
1448 let block_info_response = futs.next().await.expect("`futs` should not be empty");
1449 let zebra_state::ReadResponse::BlockInfo(prev_block_info) =
1450 block_info_response.map_misc_error()?
1451 else {
1452 unreachable!("unmatched response to a BlockInfo request");
1453 };
1454 let block_info_response = futs.next().await.expect("`futs` should not be empty");
1455 let zebra_state::ReadResponse::BlockInfo(block_info) =
1456 block_info_response.map_misc_error()?
1457 else {
1458 unreachable!("unmatched response to a BlockInfo request");
1459 };
1460
1461 let delta = block_info.as_ref().and_then(|d| {
1462 let value_pools = d.value_pools().constrain::<NegativeAllowed>().ok()?;
1463 let prev_value_pools = prev_block_info
1464 .map(|d| d.value_pools().constrain::<NegativeAllowed>())
1465 .unwrap_or(Ok(ValueBalance::<NegativeAllowed>::zero()))
1466 .ok()?;
1467 (value_pools - prev_value_pools).ok()
1468 });
1469 let size = size.or(block_info.as_ref().map(|d| d.size() as usize));
1470
1471 Ok(GetBlockResponse::Object(Box::new(BlockObject {
1472 hash,
1473 confirmations,
1474 height: Some(height),
1475 version: Some(version),
1476 merkle_root: Some(merkle_root),
1477 time: Some(time),
1478 nonce: Some(nonce),
1479 solution: Some(solution),
1480 bits: Some(bits),
1481 difficulty: Some(difficulty),
1482 n_tx: tx.len(),
1483 tx,
1484 trees,
1485 chain_supply: block_info
1486 .as_ref()
1487 .map(|d| GetBlockchainInfoBalance::chain_supply(*d.value_pools())),
1488 value_pools: block_info
1489 .map(|d| GetBlockchainInfoBalance::value_pools(*d.value_pools(), delta)),
1490 size: size.map(|size| size as i64),
1491 block_commitments: Some(block_commitments),
1492 final_sapling_root: Some(final_sapling_root),
1493 final_orchard_root,
1494 previous_block_hash: Some(previous_block_hash),
1495 next_block_hash,
1496 })))
1497 } else {
1498 Err("invalid verbosity value").map_error(server::error::LegacyCode::InvalidParameter)
1499 }
1500 }
1501
1502 async fn get_block_header(
1503 &self,
1504 hash_or_height: String,
1505 verbose: Option<bool>,
1506 ) -> Result<GetBlockHeaderResponse> {
1507 let verbose = verbose.unwrap_or(true);
1508 let network = self.network.clone();
1509
1510 let hash_or_height =
1511 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1512 .map_error(server::error::LegacyCode::InvalidParameter)?;
1515 let zebra_state::ReadResponse::BlockHeader {
1516 header,
1517 hash,
1518 height,
1519 next_block_hash,
1520 } = self
1521 .read_state
1522 .clone()
1523 .oneshot(zebra_state::ReadRequest::BlockHeader(hash_or_height))
1524 .await
1525 .map_err(|_| "block height not in best chain")
1526 .map_error(
1527 if hash_or_height.hash().is_some() {
1532 server::error::LegacyCode::InvalidAddressOrKey
1533 } else {
1534 server::error::LegacyCode::InvalidParameter
1535 },
1536 )?
1537 else {
1538 panic!("unexpected response to BlockHeader request")
1539 };
1540
1541 let response = if !verbose {
1542 GetBlockHeaderResponse::Raw(HexData(header.zcash_serialize_to_vec().map_misc_error()?))
1543 } else {
1544 let zebra_state::ReadResponse::SaplingTree(sapling_tree) = self
1545 .read_state
1546 .clone()
1547 .oneshot(zebra_state::ReadRequest::SaplingTree(hash_or_height))
1548 .await
1549 .map_misc_error()?
1550 else {
1551 panic!("unexpected response to SaplingTree request")
1552 };
1553
1554 let sapling_tree = sapling_tree.ok_or_misc_error("missing Sapling tree")?;
1556
1557 let zebra_state::ReadResponse::Depth(depth) = self
1558 .read_state
1559 .clone()
1560 .oneshot(zebra_state::ReadRequest::Depth(hash))
1561 .await
1562 .map_misc_error()?
1563 else {
1564 panic!("unexpected response to SaplingTree request")
1565 };
1566
1567 const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;
1570
1571 let confirmations = depth
1574 .map(|depth| i64::from(depth) + 1)
1575 .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS);
1576
1577 let mut nonce = *header.nonce;
1578 nonce.reverse();
1579
1580 let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network);
1581 let sapling_tree_size = sapling_tree.count();
1582 let final_sapling_root: [u8; 32] =
1583 if sapling_activation.is_some() && height >= sapling_activation.unwrap() {
1584 let mut root: [u8; 32] = sapling_tree.root().into();
1585 root.reverse();
1586 root
1587 } else {
1588 [0; 32]
1589 };
1590
1591 let difficulty = header.difficulty_threshold.relative_to_network(&network);
1592
1593 let block_commitments = match header.commitment(&network, height).expect(
1594 "Unexpected failure while parsing the blockcommitments field in get_block_header",
1595 ) {
1596 Commitment::PreSaplingReserved(bytes) => bytes,
1597 Commitment::FinalSaplingRoot(_) => final_sapling_root,
1598 Commitment::ChainHistoryActivationReserved => [0; 32],
1599 Commitment::ChainHistoryRoot(root) => root.bytes_in_display_order(),
1600 Commitment::ChainHistoryBlockTxAuthCommitment(hash) => {
1601 hash.bytes_in_display_order()
1602 }
1603 };
1604
1605 let block_header = BlockHeaderObject {
1606 hash,
1607 confirmations,
1608 height,
1609 version: header.version,
1610 merkle_root: header.merkle_root,
1611 block_commitments,
1612 final_sapling_root,
1613 sapling_tree_size,
1614 time: header.time.timestamp(),
1615 nonce,
1616 solution: header.solution,
1617 bits: header.difficulty_threshold,
1618 difficulty,
1619 previous_block_hash: header.previous_block_hash,
1620 next_block_hash,
1621 };
1622
1623 GetBlockHeaderResponse::Object(Box::new(block_header))
1624 };
1625
1626 Ok(response)
1627 }
1628
1629 fn get_best_block_hash(&self) -> Result<GetBlockHashResponse> {
1630 self.latest_chain_tip
1631 .best_tip_hash()
1632 .map(GetBlockHashResponse)
1633 .ok_or_misc_error("No blocks in state")
1634 }
1635
1636 fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse> {
1637 self.latest_chain_tip
1638 .best_tip_height_and_hash()
1639 .map(|(height, hash)| GetBlockHeightAndHashResponse { height, hash })
1640 .ok_or_misc_error("No blocks in state")
1641 }
1642
1643 async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse> {
1644 let mut mempool = self.mempool.clone();
1645
1646 let response = mempool
1647 .ready()
1648 .and_then(|service| service.call(mempool::Request::QueueStats))
1649 .await
1650 .map_misc_error()?;
1651
1652 if let mempool::Response::QueueStats {
1653 size,
1654 bytes,
1655 usage,
1656 fully_notified,
1657 } = response
1658 {
1659 Ok(GetMempoolInfoResponse {
1660 size,
1661 bytes,
1662 usage,
1663 fully_notified,
1664 })
1665 } else {
1666 unreachable!("unexpected response to QueueStats request")
1667 }
1668 }
1669
1670 async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse> {
1671 #[allow(unused)]
1672 let verbose = verbose.unwrap_or(false);
1673
1674 use zebra_chain::block::MAX_BLOCK_BYTES;
1675
1676 let mut mempool = self.mempool.clone();
1677
1678 let request = if verbose {
1679 mempool::Request::FullTransactions
1680 } else {
1681 mempool::Request::TransactionIds
1682 };
1683
1684 let response = mempool
1686 .ready()
1687 .and_then(|service| service.call(request))
1688 .await
1689 .map_misc_error()?;
1690
1691 match response {
1692 mempool::Response::FullTransactions {
1693 mut transactions,
1694 transaction_dependencies,
1695 last_seen_tip_hash: _,
1696 } => {
1697 if verbose {
1698 let transactions_by_id = transactions
1699 .iter()
1700 .map(|unmined_tx| (unmined_tx.transaction.id.mined_id(), unmined_tx))
1701 .collect::<HashMap<_, _>>();
1702 let map = transactions
1703 .iter()
1704 .map(|unmined_tx| {
1705 (
1706 unmined_tx.transaction.id.mined_id().encode_hex(),
1707 get_raw_mempool::MempoolObject::from_verified_unmined_tx(
1708 unmined_tx,
1709 &transactions_by_id,
1710 &transaction_dependencies,
1711 ),
1712 )
1713 })
1714 .collect::<HashMap<_, _>>();
1715 Ok(GetRawMempoolResponse::Verbose(map))
1716 } else {
1717 transactions.sort_by_cached_key(|tx| {
1722 cmp::Reverse((
1725 i64::from(tx.miner_fee) as u128 * MAX_BLOCK_BYTES as u128
1726 / tx.transaction.size as u128,
1727 tx.transaction.id.mined_id(),
1729 ))
1730 });
1731 let tx_ids: Vec<String> = transactions
1732 .iter()
1733 .map(|unmined_tx| unmined_tx.transaction.id.mined_id().encode_hex())
1734 .collect();
1735
1736 Ok(GetRawMempoolResponse::TxIds(tx_ids))
1737 }
1738 }
1739
1740 mempool::Response::TransactionIds(unmined_transaction_ids) => {
1741 let mut tx_ids: Vec<String> = unmined_transaction_ids
1742 .iter()
1743 .map(|id| id.mined_id().encode_hex())
1744 .collect();
1745
1746 tx_ids.sort();
1748
1749 Ok(GetRawMempoolResponse::TxIds(tx_ids))
1750 }
1751
1752 _ => unreachable!("unmatched response to a transactionids request"),
1753 }
1754 }
1755
1756 async fn get_raw_transaction(
1757 &self,
1758 txid: String,
1759 verbose: Option<u8>,
1760 block_hash: Option<String>,
1761 ) -> Result<GetRawTransactionResponse> {
1762 let mut mempool = self.mempool.clone();
1763 let verbose = verbose.unwrap_or(0) != 0;
1764
1765 let txid = transaction::Hash::from_hex(txid)
1768 .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
1769
1770 if block_hash.is_none() {
1772 match mempool
1773 .ready()
1774 .and_then(|service| {
1775 service.call(mempool::Request::TransactionsByMinedId([txid].into()))
1776 })
1777 .await
1778 .map_misc_error()?
1779 {
1780 mempool::Response::Transactions(txns) => {
1781 if let Some(tx) = txns.first() {
1782 return Ok(if verbose {
1783 GetRawTransactionResponse::Object(Box::new(
1784 TransactionObject::from_transaction(
1785 tx.transaction.clone(),
1786 None,
1787 None,
1788 &self.network,
1789 None,
1790 None,
1791 Some(false),
1792 txid,
1793 ),
1794 ))
1795 } else {
1796 let hex = tx.transaction.clone().into();
1797 GetRawTransactionResponse::Raw(hex)
1798 });
1799 }
1800 }
1801
1802 _ => unreachable!("unmatched response to a `TransactionsByMinedId` request"),
1803 };
1804 }
1805
1806 let caller_block_context = if let Some(block_hash) = block_hash {
1807 let block_hash = block::Hash::from_hex(block_hash)
1808 .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
1809 match self
1810 .read_state
1811 .clone()
1812 .oneshot(zebra_state::ReadRequest::AnyChainTransactionIdsForBlock(
1813 block_hash.into(),
1814 ))
1815 .await
1816 .map_misc_error()?
1817 {
1818 zebra_state::ReadResponse::AnyChainTransactionIdsForBlock(tx_ids) => {
1819 let (ids, in_best_chain) = tx_ids.ok_or_error(
1820 server::error::LegacyCode::InvalidAddressOrKey,
1821 "block not found",
1822 )?;
1823
1824 ids.iter().find(|id| **id == txid).ok_or_error(
1825 server::error::LegacyCode::InvalidAddressOrKey,
1826 "txid not found",
1827 )?;
1828
1829 Some((block_hash, in_best_chain))
1830 }
1831 _ => {
1832 unreachable!("unmatched response to a `AnyChainTransactionIdsForBlock` request")
1833 }
1834 }
1835 } else {
1836 None
1837 };
1838
1839 match self
1841 .read_state
1842 .clone()
1843 .oneshot(zebra_state::ReadRequest::AnyChainTransaction(txid))
1844 .await
1845 .map_misc_error()?
1846 {
1847 zebra_state::ReadResponse::AnyChainTransaction(Some(tx)) => Ok(if verbose {
1848 if let Some((caller_block_hash, in_best_chain)) = caller_block_context {
1849 let (raw_tx, height, confirmations, block_time) = match &tx {
1852 AnyTx::Mined(mined) if in_best_chain => (
1853 mined.tx.clone(),
1854 Some(mined.height),
1855 Some(mined.confirmations.into()),
1856 Some(mined.block_time),
1857 ),
1858 _ => {
1859 let raw_tx: Arc<Transaction> = tx.into();
1860 (raw_tx, None, None, None)
1861 }
1862 };
1863
1864 GetRawTransactionResponse::Object(Box::new(
1865 TransactionObject::from_transaction(
1866 raw_tx,
1867 height,
1868 confirmations,
1869 &self.network,
1870 block_time,
1871 Some(caller_block_hash),
1872 Some(in_best_chain),
1873 txid,
1874 ),
1875 ))
1876 } else {
1877 match tx {
1878 AnyTx::Mined(tx) => {
1879 let block_hash = match self
1880 .read_state
1881 .clone()
1882 .oneshot(zebra_state::ReadRequest::BestChainBlockHash(tx.height))
1883 .await
1884 .map_misc_error()?
1885 {
1886 zebra_state::ReadResponse::BlockHash(block_hash) => block_hash,
1887 _ => {
1888 unreachable!(
1889 "unmatched response to a `BestChainBlockHash` request"
1890 )
1891 }
1892 };
1893
1894 GetRawTransactionResponse::Object(Box::new(
1895 TransactionObject::from_transaction(
1896 tx.tx.clone(),
1897 Some(tx.height),
1898 Some(tx.confirmations.into()),
1899 &self.network,
1900 Some(tx.block_time),
1903 block_hash,
1904 Some(true),
1905 txid,
1906 ),
1907 ))
1908 }
1909 AnyTx::Side((tx, block_hash)) => GetRawTransactionResponse::Object(
1910 Box::new(TransactionObject::from_transaction(
1911 tx.clone(),
1912 None,
1913 None,
1914 &self.network,
1915 None,
1916 Some(block_hash),
1917 Some(false),
1918 txid,
1919 )),
1920 ),
1921 }
1922 }
1923 } else {
1924 let tx: Arc<Transaction> = tx.into();
1925 let hex = tx.into();
1926 GetRawTransactionResponse::Raw(hex)
1927 }),
1928
1929 zebra_state::ReadResponse::AnyChainTransaction(None) => {
1930 Err("No such mempool or main chain transaction")
1931 .map_error(server::error::LegacyCode::InvalidAddressOrKey)
1932 }
1933
1934 _ => unreachable!("unmatched response to a `Transaction` read request"),
1935 }
1936 }
1937
1938 async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse> {
1939 let mut read_state = self.read_state.clone();
1940 let network = self.network.clone();
1941
1942 let hash_or_height =
1943 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1944 .map_error(server::error::LegacyCode::InvalidParameter)?;
1947
1948 let block = match read_state
1957 .ready()
1958 .and_then(|service| service.call(zebra_state::ReadRequest::Block(hash_or_height)))
1959 .await
1960 .map_misc_error()?
1961 {
1962 zebra_state::ReadResponse::Block(Some(block)) => block,
1963 zebra_state::ReadResponse::Block(None) => {
1964 return Err("the requested block is not in the main chain")
1967 .map_error(server::error::LegacyCode::InvalidParameter);
1968 }
1969 _ => unreachable!("unmatched response to a block request"),
1970 };
1971
1972 let hash = hash_or_height
1973 .hash_or_else(|_| Some(block.hash()))
1974 .expect("block hash");
1975
1976 let height = hash_or_height
1977 .height_or_else(|_| block.coinbase_height())
1978 .expect("verified blocks have a coinbase height");
1979
1980 let time = u32::try_from(block.header.time.timestamp())
1981 .expect("Timestamps of valid blocks always fit into u32.");
1982
1983 let sapling = if network.is_nu_active(consensus::NetworkUpgrade::Sapling, height.into()) {
1984 match read_state
1985 .ready()
1986 .and_then(|service| {
1987 service.call(zebra_state::ReadRequest::SaplingTree(hash.into()))
1988 })
1989 .await
1990 .map_misc_error()?
1991 {
1992 zebra_state::ReadResponse::SaplingTree(tree) => {
1993 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
1994 }
1995 _ => unreachable!("unmatched response to a Sapling tree request"),
1996 }
1997 } else {
1998 None
1999 };
2000 let (sapling_tree, sapling_root) =
2001 sapling.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
2002
2003 let orchard = if network.is_nu_active(consensus::NetworkUpgrade::Nu5, height.into()) {
2004 match read_state
2005 .ready()
2006 .and_then(|service| {
2007 service.call(zebra_state::ReadRequest::OrchardTree(hash.into()))
2008 })
2009 .await
2010 .map_misc_error()?
2011 {
2012 zebra_state::ReadResponse::OrchardTree(tree) => {
2013 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
2014 }
2015 _ => unreachable!("unmatched response to an Orchard tree request"),
2016 }
2017 } else {
2018 None
2019 };
2020 let (orchard_tree, orchard_root) =
2021 orchard.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
2022
2023 let ironwood = if network.is_nu_active(consensus::NetworkUpgrade::Nu6_3, height.into()) {
2024 match read_state
2025 .ready()
2026 .and_then(|service| {
2027 service.call(zebra_state::ReadRequest::IronwoodTree(hash.into()))
2028 })
2029 .await
2030 .map_misc_error()?
2031 {
2032 zebra_state::ReadResponse::IronwoodTree(tree) => {
2033 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
2034 }
2035 _ => unreachable!("unmatched response to an Ironwood tree request"),
2036 }
2037 } else {
2038 None
2039 };
2040 let ironwood = ironwood
2042 .map(|(tree, root)| Treestate::new(trees::Commitments::new(Some(root), Some(tree))));
2043
2044 Ok(GetTreestateResponse::new(
2045 hash,
2046 height,
2047 time,
2048 None,
2051 Treestate::new(trees::Commitments::new(sapling_root, sapling_tree)),
2052 Treestate::new(trees::Commitments::new(orchard_root, orchard_tree)),
2053 ironwood,
2054 ))
2055 }
2056
2057 async fn z_get_subtrees_by_index(
2058 &self,
2059 pool: String,
2060 start_index: NoteCommitmentSubtreeIndex,
2061 limit: Option<NoteCommitmentSubtreeIndex>,
2062 ) -> Result<GetSubtreesByIndexResponse> {
2063 let mut read_state = self.read_state.clone();
2064
2065 const POOL_LIST: &[&str] = &["sapling", "orchard", "ironwood"];
2066
2067 if pool == "sapling" {
2068 let request = zebra_state::ReadRequest::SaplingSubtrees { start_index, limit };
2069 let response = read_state
2070 .ready()
2071 .and_then(|service| service.call(request))
2072 .await
2073 .map_misc_error()?;
2074
2075 let subtrees = match response {
2076 zebra_state::ReadResponse::SaplingSubtrees(subtrees) => subtrees,
2077 _ => unreachable!("unmatched response to a subtrees request"),
2078 };
2079
2080 let subtrees = subtrees
2081 .values()
2082 .map(|subtree| SubtreeRpcData {
2083 root: subtree.root.to_bytes().encode_hex(),
2084 end_height: subtree.end_height,
2085 })
2086 .collect();
2087
2088 Ok(GetSubtreesByIndexResponse {
2089 pool,
2090 start_index,
2091 subtrees,
2092 })
2093 } else if pool == "orchard" {
2094 let request = zebra_state::ReadRequest::OrchardSubtrees { start_index, limit };
2095 let response = read_state
2096 .ready()
2097 .and_then(|service| service.call(request))
2098 .await
2099 .map_misc_error()?;
2100
2101 let subtrees = match response {
2102 zebra_state::ReadResponse::OrchardSubtrees(subtrees) => subtrees,
2103 _ => unreachable!("unmatched response to a subtrees request"),
2104 };
2105
2106 let subtrees = subtrees
2107 .values()
2108 .map(|subtree| SubtreeRpcData {
2109 root: subtree.root.encode_hex(),
2110 end_height: subtree.end_height,
2111 })
2112 .collect();
2113
2114 Ok(GetSubtreesByIndexResponse {
2115 pool,
2116 start_index,
2117 subtrees,
2118 })
2119 } else if pool == "ironwood" {
2120 let request = zebra_state::ReadRequest::IronwoodSubtrees { start_index, limit };
2121 let response = read_state
2122 .ready()
2123 .and_then(|service| service.call(request))
2124 .await
2125 .map_misc_error()?;
2126
2127 let subtrees = match response {
2128 zebra_state::ReadResponse::IronwoodSubtrees(subtrees) => subtrees,
2129 _ => unreachable!("unmatched response to a subtrees request"),
2130 };
2131
2132 let subtrees = subtrees
2134 .values()
2135 .map(|subtree| SubtreeRpcData {
2136 root: subtree.root.encode_hex(),
2137 end_height: subtree.end_height,
2138 })
2139 .collect();
2140
2141 Ok(GetSubtreesByIndexResponse {
2142 pool,
2143 start_index,
2144 subtrees,
2145 })
2146 } else {
2147 Err(ErrorObject::owned(
2148 server::error::LegacyCode::Misc.into(),
2149 format!("invalid pool name, must be one of: {POOL_LIST:?}").as_str(),
2150 None::<()>,
2151 ))
2152 }
2153 }
2154
2155 async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>> {
2156 let mut read_state = self.read_state.clone();
2157 let latest_chain_tip = self.latest_chain_tip.clone();
2158
2159 let height_range = build_height_range(
2160 request.start,
2161 request.end,
2162 best_chain_tip_height(&latest_chain_tip)?,
2163 )?;
2164
2165 let valid_addresses = request.valid_addresses()?;
2166
2167 let request = zebra_state::ReadRequest::TransactionIdsByAddresses {
2168 addresses: valid_addresses,
2169 height_range,
2170 };
2171 let response = read_state
2172 .ready()
2173 .and_then(|service| service.call(request))
2174 .await
2175 .map_misc_error()?;
2176
2177 let hashes = match response {
2178 zebra_state::ReadResponse::AddressesTransactionIds(hashes) => {
2179 let mut last_tx_location = TransactionLocation::from_usize(Height(0), 0);
2180
2181 hashes
2182 .iter()
2183 .map(|(tx_loc, tx_id)| {
2184 assert!(
2186 *tx_loc > last_tx_location,
2187 "Transactions were not in chain order:\n\
2188 {tx_loc:?} {tx_id:?} was after:\n\
2189 {last_tx_location:?}",
2190 );
2191
2192 last_tx_location = *tx_loc;
2193
2194 tx_id.to_string()
2195 })
2196 .collect()
2197 }
2198 _ => unreachable!("unmatched response to a TransactionsByAddresses request"),
2199 };
2200
2201 Ok(hashes)
2202 }
2203
2204 async fn get_address_utxos(
2205 &self,
2206 utxos_request: GetAddressUtxosRequest,
2207 ) -> Result<GetAddressUtxosResponse> {
2208 let mut read_state = self.read_state.clone();
2209 let mut response_utxos = vec![];
2210
2211 let valid_addresses = utxos_request.valid_addresses()?;
2212
2213 let request = zebra_state::ReadRequest::UtxosByAddresses(valid_addresses);
2215 let response = read_state
2216 .ready()
2217 .and_then(|service| service.call(request))
2218 .await
2219 .map_misc_error()?;
2220 let utxos = match response {
2221 zebra_state::ReadResponse::AddressUtxos(utxos) => utxos,
2222 _ => unreachable!("unmatched response to a UtxosByAddresses request"),
2223 };
2224
2225 let mut last_output_location = OutputLocation::from_usize(Height(0), 0, 0);
2226
2227 for utxo_data in utxos.utxos() {
2228 let address = utxo_data.0;
2229 let txid = *utxo_data.1;
2230 let height = utxo_data.2.height();
2231 let output_index = utxo_data.2.output_index();
2232 let script = utxo_data.3.lock_script.clone();
2233 let satoshis = u64::from(utxo_data.3.value);
2234
2235 let output_location = *utxo_data.2;
2236 assert!(
2238 output_location > last_output_location,
2239 "UTXOs were not in chain order:\n\
2240 {output_location:?} {address:?} {txid:?} was after:\n\
2241 {last_output_location:?}",
2242 );
2243
2244 let entry = Utxo {
2245 address,
2246 txid,
2247 output_index,
2248 script,
2249 satoshis,
2250 height,
2251 };
2252 response_utxos.push(entry);
2253
2254 last_output_location = output_location;
2255 }
2256
2257 if !utxos_request.chain_info {
2258 Ok(GetAddressUtxosResponse::Utxos(response_utxos))
2259 } else {
2260 let (height, hash) = utxos
2261 .last_height_and_hash()
2262 .ok_or_misc_error("No blocks in state")?;
2263
2264 Ok(GetAddressUtxosResponse::UtxosAndChainInfo(
2265 GetAddressUtxosResponseObject {
2266 utxos: response_utxos,
2267 hash,
2268 height,
2269 },
2270 ))
2271 }
2272 }
2273
2274 fn stop(&self) -> Result<String> {
2275 #[cfg(not(target_os = "windows"))]
2276 if self.network.is_regtest() {
2277 match nix::sys::signal::raise(nix::sys::signal::SIGINT) {
2278 Ok(_) => Ok("Zebra server stopping".to_string()),
2279 Err(error) => Err(ErrorObject::owned(
2280 ErrorCode::InternalError.code(),
2281 format!("Failed to shut down: {error}").as_str(),
2282 None::<()>,
2283 )),
2284 }
2285 } else {
2286 Err(ErrorObject::borrowed(
2287 ErrorCode::MethodNotFound.code(),
2288 "stop is only available on regtest networks",
2289 None,
2290 ))
2291 }
2292 #[cfg(target_os = "windows")]
2293 Err(ErrorObject::borrowed(
2294 ErrorCode::MethodNotFound.code(),
2295 "stop is not available in windows targets",
2296 None,
2297 ))
2298 }
2299
2300 fn get_block_count(&self) -> Result<u32> {
2301 best_chain_tip_height(&self.latest_chain_tip).map(|height| height.0)
2302 }
2303
2304 async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse> {
2305 let mut read_state = self.read_state.clone();
2306 let latest_chain_tip = self.latest_chain_tip.clone();
2307
2308 let tip_height = best_chain_tip_height(&latest_chain_tip)?;
2310
2311 let height = height_from_signed_int(index, tip_height)?;
2312
2313 let request = zebra_state::ReadRequest::BestChainBlockHash(height);
2314 let response = read_state
2315 .ready()
2316 .and_then(|service| service.call(request))
2317 .await
2318 .map_error(server::error::LegacyCode::default())?;
2319
2320 match response {
2321 zebra_state::ReadResponse::BlockHash(Some(hash)) => Ok(GetBlockHashResponse(hash)),
2322 zebra_state::ReadResponse::BlockHash(None) => Err(ErrorObject::borrowed(
2323 server::error::LegacyCode::InvalidParameter.into(),
2324 "Block not found",
2325 None,
2326 )),
2327 _ => unreachable!("unmatched response to a block request"),
2328 }
2329 }
2330
2331 async fn get_block_template(
2332 &self,
2333 parameters: Option<GetBlockTemplateParameters>,
2334 ) -> Result<GetBlockTemplateResponse> {
2335 use types::get_block_template::{
2336 check_parameters, check_synced_to_tip, fetch_chain_info, fetch_mempool_transactions,
2337 validate_block_proposal, zip317::select_mempool_transactions,
2338 };
2339
2340 let mempool = self.mempool.clone();
2342 let mut latest_chain_tip = self.latest_chain_tip.clone();
2343 let sync_status = self.gbt.sync_status();
2344 let read_state = self.read_state.clone();
2345
2346 if let Some(HexData(block_proposal_bytes)) = parameters
2347 .as_ref()
2348 .and_then(GetBlockTemplateParameters::block_proposal_data)
2349 {
2350 return validate_block_proposal(
2351 self.gbt.block_verifier_router(),
2352 block_proposal_bytes,
2353 &self.network,
2354 latest_chain_tip,
2355 sync_status,
2356 )
2357 .await;
2358 }
2359
2360 check_parameters(¶meters)?;
2362
2363 let client_long_poll_id = parameters.as_ref().and_then(|params| params.long_poll_id);
2364
2365 let miner_params = self
2366 .gbt
2367 .miner_params()
2368 .ok_or_error(0, "miner parameters are required for get_block_template")?;
2369
2370 let mut max_time_reached = false;
2374
2375 let (server_long_poll_id, chain_info, mempool_txs, mempool_tx_deps, submit_old) = loop {
2377 check_synced_to_tip(&self.network, latest_chain_tip.clone(), sync_status.clone())?;
2383 latest_chain_tip.mark_best_tip_seen();
2391
2392 let chain_info @ zebra_state::GetBlockTemplateChainInfo {
2399 tip_hash,
2400 tip_height,
2401 max_time,
2402 cur_time,
2403 ..
2404 } = fetch_chain_info(read_state.clone()).await?;
2405
2406 let Some((mempool_txs, mempool_tx_deps)) =
2417 fetch_mempool_transactions(mempool.clone(), tip_hash)
2418 .await?
2419 .or_else(|| client_long_poll_id.is_none().then(Default::default))
2423 else {
2424 continue;
2425 };
2426
2427 let server_long_poll_id = LongPollInput::new(
2429 tip_height,
2430 tip_hash,
2431 max_time,
2432 mempool_txs.iter().map(|tx| tx.transaction.id),
2433 )
2434 .generate_id();
2435
2436 if Some(&server_long_poll_id) != client_long_poll_id.as_ref() || max_time_reached {
2441 let submit_old = if max_time_reached {
2445 Some(false)
2446 } else {
2447 client_long_poll_id
2448 .as_ref()
2449 .map(|old_long_poll_id| server_long_poll_id.submit_old(old_long_poll_id))
2450 };
2451
2452 break (
2453 server_long_poll_id,
2454 chain_info,
2455 mempool_txs,
2456 mempool_tx_deps,
2457 submit_old,
2458 );
2459 }
2460
2461 let wait_for_mempool_request =
2471 tokio::time::sleep(Duration::from_secs(MEMPOOL_LONG_POLL_INTERVAL));
2472
2473 let mut wait_for_new_tip = latest_chain_tip.clone();
2476 let wait_for_new_tip = wait_for_new_tip.best_tip_changed();
2477 let precomputed_height = Height(chain_info.tip_height.0 + 2);
2479 let wait_for_new_tip = async {
2480 let precompute_coinbase = |network, height, params| {
2488 tokio::task::spawn_blocking(move || {
2489 TransactionTemplate::new_coinbase(&network, height, ¶ms, Amount::zero())
2490 .expect("valid coinbase tx")
2491 })
2492 };
2493
2494 let precomputed_coinbase = precompute_coinbase(
2495 self.network.clone(),
2496 precomputed_height,
2497 miner_params.clone(),
2498 )
2499 .await
2500 .expect("valid coinbase tx");
2501
2502 let _ = wait_for_new_tip.await;
2503
2504 precomputed_coinbase
2505 };
2506
2507 let duration_until_max_time = max_time.saturating_duration_since(cur_time);
2519 let wait_for_max_time: OptionFuture<_> = if duration_until_max_time.seconds() > 0 {
2520 Some(tokio::time::sleep(duration_until_max_time.to_std()))
2521 } else {
2522 None
2523 }
2524 .into();
2525
2526 tokio::select! {
2533 biased;
2536
2537 _elapsed = wait_for_mempool_request => {
2539 tracing::debug!(
2540 ?max_time,
2541 ?cur_time,
2542 ?server_long_poll_id,
2543 ?client_long_poll_id,
2544 MEMPOOL_LONG_POLL_INTERVAL,
2545 "checking for a new mempool change after waiting a few seconds"
2546 );
2547 }
2548
2549 precomputed_coinbase = wait_for_new_tip => {
2550 let chain_info = fetch_chain_info(read_state.clone()).await?;
2551
2552 let server_long_poll_id = LongPollInput::new(
2553 chain_info.tip_height,
2554 chain_info.tip_hash,
2555 chain_info.max_time,
2556 vec![]
2557 )
2558 .generate_id();
2559
2560 let submit_old = client_long_poll_id
2561 .as_ref()
2562 .map(|old_long_poll_id| server_long_poll_id.submit_old(old_long_poll_id));
2563
2564 let next_height = chain_info.tip_height.next().map_misc_error()?;
2568 let precomputed_coinbase = (next_height == precomputed_height)
2569 .then_some(precomputed_coinbase);
2570
2571 return Ok(BlockTemplateResponse::new_internal(
2575 &self.network,
2576 precomputed_coinbase,
2577 None,
2578 miner_params,
2579 &chain_info,
2580 server_long_poll_id,
2581 vec![],
2582 submit_old,
2583 )
2584 .into())
2585 }
2586
2587 Some(_elapsed) = wait_for_max_time => {
2590 tracing::info!(
2592 ?max_time,
2593 ?cur_time,
2594 ?server_long_poll_id,
2595 ?client_long_poll_id,
2596 "returning from long poll because max time was reached"
2597 );
2598
2599 max_time_reached = true;
2600 }
2601 }
2602 };
2603
2604 tracing::debug!(
2611 mempool_tx_hashes = ?mempool_txs
2612 .iter()
2613 .map(|tx| tx.transaction.id.mined_id())
2614 .collect::<Vec<_>>(),
2615 "selecting transactions for the template from the mempool"
2616 );
2617
2618 let height = chain_info.tip_height.next().map_misc_error()?;
2619
2620 let coinbase_cache = self.gbt.coinbase_cache();
2622 let mempool_txs = select_mempool_transactions(
2623 &self.network,
2624 height,
2625 miner_params,
2626 mempool_txs,
2627 mempool_tx_deps,
2628 Some(&coinbase_cache),
2629 );
2630
2631 tracing::debug!(
2632 selected_mempool_tx_hashes = ?mempool_txs
2633 .iter()
2634 .map(|#[cfg(not(test))] tx, #[cfg(test)] (_, tx)| tx.transaction.id.mined_id())
2635 .collect::<Vec<_>>(),
2636 "selected transactions for the template from the mempool"
2637 );
2638
2639 Ok(BlockTemplateResponse::new_internal(
2642 &self.network,
2643 None,
2644 Some(self.gbt.coinbase_cache()),
2645 miner_params,
2646 &chain_info,
2647 server_long_poll_id,
2648 mempool_txs,
2649 submit_old,
2650 )
2651 .into())
2652 }
2653
2654 async fn submit_block(
2655 &self,
2656 HexData(block_bytes): HexData,
2657 _parameters: Option<SubmitBlockParameters>,
2658 ) -> Result<SubmitBlockResponse> {
2659 let mut block_verifier_router = self.gbt.block_verifier_router();
2660
2661 let block: Block = match block_bytes.zcash_deserialize_into() {
2662 Ok(block_bytes) => block_bytes,
2663 Err(error) => {
2664 tracing::info!(
2665 ?error,
2666 "submit block failed: block bytes could not be deserialized into a structurally valid block"
2667 );
2668
2669 return Ok(SubmitBlockErrorResponse::Rejected.into());
2670 }
2671 };
2672
2673 let height = block
2674 .coinbase_height()
2675 .ok_or_error(0, "coinbase height not found")?;
2676 let block_hash = block.hash();
2677
2678 let block_verifier_router_response = block_verifier_router
2679 .ready()
2680 .await
2681 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
2682 .call(zebra_consensus::Request::Commit(Arc::new(block)))
2683 .await;
2684
2685 let chain_error = match block_verifier_router_response {
2686 Ok(hash) => {
2693 tracing::info!(?hash, ?height, "submit block accepted");
2694
2695 self.gbt
2696 .advertise_mined_block(hash, height)
2697 .map_error_with_prefix(0, "failed to send mined block to gossip task")?;
2698
2699 return Ok(SubmitBlockResponse::Accepted);
2700 }
2701
2702 Err(box_error) => {
2705 let error = box_error
2706 .downcast::<RouterError>()
2707 .map(|boxed_chain_error| *boxed_chain_error);
2708
2709 tracing::info!(
2710 ?error,
2711 ?block_hash,
2712 ?height,
2713 "submit block failed verification"
2714 );
2715
2716 error
2717 }
2718 };
2719
2720 let response = match chain_error {
2721 Ok(source) if source.is_duplicate_request() => SubmitBlockErrorResponse::Duplicate,
2722
2723 Ok(_verify_chain_error) => SubmitBlockErrorResponse::Rejected,
2739
2740 Err(_unknown_error_type) => SubmitBlockErrorResponse::Rejected,
2743 };
2744
2745 Ok(response.into())
2746 }
2747
2748 async fn get_mining_info(&self) -> Result<GetMiningInfoResponse> {
2749 let network = self.network.clone();
2750 let mut read_state = self.read_state.clone();
2751
2752 let chain_tip = self.latest_chain_tip.clone();
2753 let tip_height = chain_tip.best_tip_height().unwrap_or(Height(0)).0;
2754
2755 let mut current_block_tx = None;
2756 if tip_height > 0 {
2757 let mined_tx_ids = chain_tip.best_tip_mined_transaction_ids();
2758 current_block_tx =
2759 (!mined_tx_ids.is_empty()).then(|| mined_tx_ids.len().saturating_sub(1));
2760 }
2761
2762 let solution_rate_fut = self.get_network_sol_ps(None, None);
2763 let mut current_block_size = None;
2765 if tip_height > 0 {
2766 let request = zebra_state::ReadRequest::TipBlockSize;
2767 let response: zebra_state::ReadResponse = read_state
2768 .ready()
2769 .and_then(|service| service.call(request))
2770 .await
2771 .map_error(server::error::LegacyCode::default())?;
2772 current_block_size = match response {
2773 zebra_state::ReadResponse::TipBlockSize(Some(block_size)) => Some(block_size),
2774 _ => None,
2775 };
2776 }
2777
2778 Ok(GetMiningInfoResponse::new_internal(
2779 tip_height,
2780 current_block_size,
2781 current_block_tx,
2782 network,
2783 solution_rate_fut.await?,
2784 ))
2785 }
2786
2787 async fn get_network_sol_ps(
2788 &self,
2789 num_blocks: Option<i32>,
2790 height: Option<i32>,
2791 ) -> Result<u64> {
2792 let mut num_blocks = num_blocks.unwrap_or(DEFAULT_SOLUTION_RATE_WINDOW_SIZE);
2794 if num_blocks < 1 {
2796 num_blocks = i32::try_from(POW_AVERAGING_WINDOW).expect("fits in i32");
2797 }
2798 let num_blocks =
2799 usize::try_from(num_blocks).expect("just checked for negatives, i32 fits in usize");
2800
2801 let height = height.and_then(|height| height.try_into_height().ok());
2804
2805 let mut read_state = self.read_state.clone();
2806
2807 let request = ReadRequest::SolutionRate { num_blocks, height };
2808
2809 let response = read_state
2810 .ready()
2811 .and_then(|service| service.call(request))
2812 .await
2813 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
2814
2815 let solution_rate = match response {
2816 ReadResponse::SolutionRate(solution_rate) => solution_rate.unwrap_or(0),
2818
2819 _ => unreachable!("unmatched response to a solution rate request"),
2820 };
2821
2822 Ok(solution_rate
2823 .try_into()
2824 .expect("per-second solution rate always fits in u64"))
2825 }
2826
2827 async fn get_network_info(&self) -> Result<GetNetworkInfoResponse> {
2828 let version = GetInfoResponse::version_from_string(&self.build_version)
2829 .expect("invalid version string");
2830
2831 let subversion = self.user_agent.clone();
2832
2833 let protocol_version = zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0;
2834
2835 let local_services = format!("{:016x}", PeerServices::NODE_NETWORK);
2837
2838 let timeoffset = 0;
2840
2841 let connections = self.address_book.recently_live_peers(Utc::now()).len();
2842
2843 let networks = vec![
2845 NetworkInfo::new("ipv4".to_string(), false, true, "".to_string(), false),
2846 NetworkInfo::new("ipv6".to_string(), false, true, "".to_string(), false),
2847 NetworkInfo::new("onion".to_string(), false, false, "".to_string(), false),
2848 ];
2849
2850 let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
2851 / (zebra_chain::amount::COIN as f64);
2852
2853 let local_addresses = vec![];
2855
2856 let warnings = "".to_string();
2858
2859 let response = GetNetworkInfoResponse {
2860 version,
2861 subversion,
2862 protocol_version,
2863 local_services,
2864 timeoffset,
2865 connections,
2866 networks,
2867 relay_fee,
2868 local_addresses,
2869 warnings,
2870 };
2871
2872 Ok(response)
2873 }
2874
2875 async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
2876 let address_book = self.address_book.clone();
2877 Ok(address_book
2878 .recently_live_peers(chrono::Utc::now())
2879 .into_iter()
2880 .map(PeerInfo::from)
2881 .collect())
2882 }
2883
2884 async fn ping(&self) -> Result<()> {
2885 tracing::debug!("Receiving ping request via RPC");
2886
2887 Ok(())
2891 }
2892
2893 async fn validate_address(&self, raw_address: String) -> Result<ValidateAddressResponse> {
2894 let network = self.network.clone();
2895
2896 validate_address(network, raw_address)
2897 }
2898
2899 async fn z_validate_address(&self, raw_address: String) -> Result<ZValidateAddressResponse> {
2900 let network = self.network.clone();
2901
2902 z_validate_address(network, raw_address)
2903 }
2904
2905 async fn get_standard_fee(&self) -> Result<GetStandardFeeResponse> {
2906 use zebra_chain::transaction::zip317::MARGINAL_FEE;
2907
2908 const VERSION: u32 = 0;
2909
2910 Ok(GetStandardFeeResponse::new(MARGINAL_FEE, VERSION))
2911 }
2912
2913 async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse> {
2914 let net = self.network.clone();
2915
2916 let height = match height {
2917 Some(h) => Height(h),
2918 None => best_chain_tip_height(&self.latest_chain_tip)?,
2919 };
2920
2921 let subsidy = block_subsidy(height, &net).map_misc_error()?;
2922
2923 let (lockbox_streams, mut funding_streams): (Vec<_>, Vec<_>) =
2924 funding_stream_values(height, &net, subsidy)
2925 .map_misc_error()?
2926 .into_iter()
2927 .partition(|(receiver, _)| matches!(receiver, FundingStreamReceiver::Deferred));
2929
2930 let [lockbox_total, funding_streams_total] =
2931 [&lockbox_streams, &funding_streams].map(|streams| {
2932 streams
2933 .iter()
2934 .map(|&(_, amount)| amount)
2935 .sum::<std::result::Result<Amount<_>, _>>()
2936 .map(Zec::from)
2937 .map_misc_error()
2938 });
2939
2940 funding_streams.sort_by_key(|(receiver, _funding_stream)| {
2942 ZCASHD_FUNDING_STREAM_ORDER
2943 .iter()
2944 .position(|zcashd_receiver| zcashd_receiver == receiver)
2945 });
2946
2947 let is_nu6 = NetworkUpgrade::current(&net, height) == NetworkUpgrade::Nu6;
2948
2949 let [funding_streams, lockbox_streams] =
2951 [funding_streams, lockbox_streams].map(|streams| {
2952 streams
2953 .into_iter()
2954 .map(|(receiver, value)| {
2955 let address = funding_stream_address(height, &net, receiver);
2956 types::subsidy::FundingStream::new_internal(
2957 is_nu6, receiver, value, address,
2958 )
2959 })
2960 .collect()
2961 });
2962
2963 Ok(GetBlockSubsidyResponse {
2964 miner: miner_subsidy(height, &net, subsidy)
2965 .map_misc_error()?
2966 .into(),
2967 founders: founders_reward(&net, height).into(),
2968 funding_streams,
2969 lockbox_streams,
2970 funding_streams_total: funding_streams_total?,
2971 lockbox_total: lockbox_total?,
2972 total_block_subsidy: subsidy.into(),
2973 })
2974 }
2975
2976 async fn get_difficulty(&self) -> Result<f64> {
2977 chain_tip_difficulty(self.network.clone(), self.read_state.clone(), false).await
2978 }
2979
2980 async fn z_list_unified_receivers(
2981 &self,
2982 address: String,
2983 ) -> Result<ZListUnifiedReceiversResponse> {
2984 use zcash_address::unified::Container;
2985
2986 let (network, unified_address): (
2987 zcash_protocol::consensus::NetworkType,
2988 zcash_address::unified::Address,
2989 ) = zcash_address::unified::Encoding::decode(address.clone().as_str())
2990 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
2991
2992 let mut p2pkh = None;
2993 let mut p2sh = None;
2994 let mut orchard = None;
2995 let mut sapling = None;
2996
2997 for item in unified_address.items() {
2998 match item {
2999 zcash_address::unified::Receiver::Orchard(_data) => {
3000 let addr = zcash_address::unified::Address::try_from_items(vec![item])
3001 .expect("using data already decoded as valid");
3002 orchard = Some(addr.encode(&network));
3003 }
3004 zcash_address::unified::Receiver::Sapling(data) => {
3005 let addr = zebra_chain::primitives::Address::try_from_sapling(network, data)
3006 .map_error(server::error::LegacyCode::InvalidParameter)?;
3007 sapling = Some(addr.payment_address().unwrap_or_default());
3008 }
3009 zcash_address::unified::Receiver::P2pkh(data) => {
3010 let addr =
3011 zebra_chain::primitives::Address::try_from_transparent_p2pkh(network, data)
3012 .expect("using data already decoded as valid");
3013 p2pkh = Some(addr.payment_address().unwrap_or_default());
3014 }
3015 zcash_address::unified::Receiver::P2sh(data) => {
3016 let addr =
3017 zebra_chain::primitives::Address::try_from_transparent_p2sh(network, data)
3018 .expect("using data already decoded as valid");
3019 p2sh = Some(addr.payment_address().unwrap_or_default());
3020 }
3021 _ => (),
3022 }
3023 }
3024
3025 Ok(ZListUnifiedReceiversResponse::new(
3026 orchard, sapling, p2pkh, p2sh,
3027 ))
3028 }
3029
3030 async fn invalidate_block(&self, block_hash: String) -> Result<()> {
3031 let block_hash = block_hash
3032 .parse()
3033 .map_error(server::error::LegacyCode::InvalidParameter)?;
3034
3035 self.state
3036 .clone()
3037 .oneshot(zebra_state::Request::InvalidateBlock(block_hash))
3038 .await
3039 .map(|rsp| assert_eq!(rsp, zebra_state::Response::Invalidated(block_hash)))
3040 .map_misc_error()
3041 }
3042
3043 async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>> {
3044 let block_hash = block_hash
3045 .parse()
3046 .map_error(server::error::LegacyCode::InvalidParameter)?;
3047
3048 self.state
3049 .clone()
3050 .oneshot(zebra_state::Request::ReconsiderBlock(block_hash))
3051 .await
3052 .map(|rsp| match rsp {
3053 zebra_state::Response::Reconsidered(block_hashes) => block_hashes,
3054 _ => unreachable!("unmatched response to a reconsider block request"),
3055 })
3056 .map_misc_error()
3057 }
3058
3059 async fn generate(&self, num_blocks: u32) -> Result<Vec<Hash>> {
3060 let mut rpc = self.clone();
3061 let network = self.network.clone();
3062
3063 if !network.disable_pow() {
3064 return Err(ErrorObject::borrowed(
3065 0,
3066 "generate is only supported on networks where PoW is disabled",
3067 None,
3068 ));
3069 }
3070
3071 let mut block_hashes = Vec::new();
3072 for _ in 0..num_blocks {
3073 rpc.gbt.randomize_coinbase_data();
3077
3078 let block_template = rpc
3079 .get_block_template(None)
3080 .await
3081 .map_error(server::error::LegacyCode::default())?;
3082
3083 let GetBlockTemplateResponse::TemplateMode(block_template) = block_template else {
3084 return Err(ErrorObject::borrowed(
3085 0,
3086 "error generating block template",
3087 None,
3088 ));
3089 };
3090
3091 let proposal_block = proposal_block_from_template(
3092 &block_template,
3093 BlockTemplateTimeSource::CurTime,
3094 &network,
3095 )
3096 .map_error(server::error::LegacyCode::default())?;
3097
3098 let hex_proposal_block = HexData(
3099 proposal_block
3100 .zcash_serialize_to_vec()
3101 .map_error(server::error::LegacyCode::default())?,
3102 );
3103
3104 let r = rpc
3105 .submit_block(hex_proposal_block, None)
3106 .await
3107 .map_error(server::error::LegacyCode::default())?;
3108 match r {
3109 SubmitBlockResponse::Accepted => { }
3110 SubmitBlockResponse::ErrorResponse(response) => {
3111 return Err(ErrorObject::owned(
3112 server::error::LegacyCode::Misc.into(),
3113 format!("block was rejected: {response:?}"),
3114 None::<()>,
3115 ));
3116 }
3117 }
3118
3119 block_hashes.push(GetBlockHashResponse(proposal_block.hash()));
3120 }
3121
3122 Ok(block_hashes)
3123 }
3124
3125 async fn generate_to_address(
3126 &self,
3127 num_blocks: u32,
3128 address: String,
3129 ) -> Result<Vec<GetBlockHashResponse>> {
3130 if !self.network.disable_pow() {
3131 return Err(ErrorObject::borrowed(
3132 0,
3133 "generatetoaddress is only supported on networks where PoW is disabled",
3134 None,
3135 ));
3136 }
3137
3138 let miner_address = address
3141 .parse()
3142 .map_error(server::error::LegacyCode::default())?;
3143 let miner_params = MinerParams::new(
3144 &self.network,
3145 crate::config::mining::Config {
3146 miner_address: Some(miner_address),
3147 ..Default::default()
3148 },
3149 )
3150 .map_error(server::error::LegacyCode::default())?;
3151
3152 let mut rpc = self.clone();
3156 rpc.gbt.set_miner_params(miner_params);
3157 rpc.generate(num_blocks).await
3158 }
3159
3160 async fn add_node(
3161 &self,
3162 addr: zebra_network::PeerSocketAddr,
3163 command: AddNodeCommand,
3164 ) -> Result<()> {
3165 if self.network.is_regtest() {
3166 match command {
3167 AddNodeCommand::Add => {
3168 tracing::info!(?addr, "adding peer address to the address book");
3169 if self.address_book.clone().add_peer(addr) {
3170 Ok(())
3171 } else {
3172 return Err(ErrorObject::owned(
3173 server::error::LegacyCode::ClientNodeAlreadyAdded.into(),
3174 format!("peer address was already present in the address book: {addr}"),
3175 None::<()>,
3176 ));
3177 }
3178 }
3179 }
3180 } else {
3181 return Err(ErrorObject::owned(
3182 ErrorCode::InvalidParams.code(),
3183 "addnode command is only supported on regtest",
3184 None::<()>,
3185 ));
3186 }
3187 }
3188
3189 fn openrpc(&self) -> openrpsee::openrpc::Response {
3190 let mut generator = openrpsee::openrpc::Generator::new();
3191
3192 let methods = METHODS
3193 .into_iter()
3194 .map(|(name, method)| method.generate(&mut generator, name))
3195 .collect();
3196
3197 Ok(openrpsee::openrpc::OpenRpc {
3198 openrpc: "1.3.2",
3199 info: openrpsee::openrpc::Info {
3200 title: env!("CARGO_PKG_NAME"),
3201 description: env!("CARGO_PKG_DESCRIPTION"),
3202 version: env!("CARGO_PKG_VERSION"),
3203 },
3204 methods,
3205 components: generator.into_components(),
3206 })
3207 }
3208 async fn get_tx_out(
3209 &self,
3210 txid: String,
3211 n: u32,
3212 include_mempool: Option<bool>,
3213 ) -> Result<GetTxOutResponse> {
3214 let txid = transaction::Hash::from_hex(txid)
3215 .map_error(server::error::LegacyCode::InvalidParameter)?;
3216
3217 let outpoint = transparent::OutPoint {
3218 hash: txid,
3219 index: n,
3220 };
3221
3222 if include_mempool.unwrap_or(true) {
3224 let rsp = self
3225 .mempool
3226 .clone()
3227 .oneshot(mempool::Request::UnspentOutput(outpoint))
3228 .await
3229 .map_misc_error()?;
3230
3231 match rsp {
3232 mempool::Response::TransparentOutput(Some(CreatedOrSpent::Created {
3234 output,
3235 tx_version,
3236 last_seen_hash,
3237 })) => {
3238 return Ok(GetTxOutResponse(Some(
3239 types::transaction::OutputObject::from_output(
3240 &output,
3241 last_seen_hash.to_string(),
3242 0,
3243 tx_version,
3244 false,
3245 self.network(),
3246 ),
3247 )))
3248 }
3249 mempool::Response::TransparentOutput(Some(CreatedOrSpent::Spent)) => {
3250 return Ok(GetTxOutResponse(None))
3251 }
3252 mempool::Response::TransparentOutput(None) => {}
3253 _ => unreachable!("unmatched response to an `UnspentOutput` request"),
3254 };
3255 }
3256
3257 let tip_rsp = self
3262 .read_state
3263 .clone()
3264 .oneshot(zebra_state::ReadRequest::Tip)
3265 .await
3266 .map_misc_error()?;
3267
3268 let best_block_hash = match tip_rsp {
3269 zebra_state::ReadResponse::Tip(tip) => tip.ok_or_misc_error("No blocks in state")?.1,
3270 _ => unreachable!("unmatched response to a `Tip` request"),
3271 };
3272
3273 let rsp = self
3275 .read_state
3276 .clone()
3277 .oneshot(zebra_state::ReadRequest::Transaction(txid))
3278 .await
3279 .map_misc_error()?;
3280
3281 match rsp {
3282 zebra_state::ReadResponse::Transaction(Some(tx)) => {
3283 let outputs = tx.tx.outputs();
3284 let index: usize = n.try_into().expect("u32 always fits in usize");
3285 let output = match outputs.get(index) {
3286 Some(output) => output,
3287 None => return Ok(GetTxOutResponse(None)),
3289 };
3290
3291 let is_spent = {
3293 let rsp = self
3294 .read_state
3295 .clone()
3296 .oneshot(zebra_state::ReadRequest::IsTransparentOutputSpent(outpoint))
3297 .await
3298 .map_misc_error()?;
3299
3300 match rsp {
3301 zebra_state::ReadResponse::IsTransparentOutputSpent(spent) => spent,
3302 _ => unreachable!(
3303 "unmatched response to an `IsTransparentOutputSpent` request"
3304 ),
3305 }
3306 };
3307
3308 if is_spent {
3309 return Ok(GetTxOutResponse(None));
3310 }
3311
3312 Ok(GetTxOutResponse(Some(
3313 types::transaction::OutputObject::from_output(
3314 output,
3315 best_block_hash.to_string(),
3316 tx.confirmations,
3317 tx.tx.version(),
3318 tx.tx.is_coinbase(),
3319 self.network(),
3320 ),
3321 )))
3322 }
3323 zebra_state::ReadResponse::Transaction(None) => Ok(GetTxOutResponse(None)),
3324 _ => unreachable!("unmatched response to a `Transaction` request"),
3325 }
3326 }
3327}
3328
3329pub fn best_chain_tip_height<Tip>(latest_chain_tip: &Tip) -> Result<Height>
3334where
3335 Tip: ChainTip + Clone + Send + Sync + 'static,
3336{
3337 latest_chain_tip
3338 .best_tip_height()
3339 .ok_or_misc_error("No blocks in state")
3340}
3341
3342#[allow(clippy::too_many_arguments)]
3346#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3347pub struct GetInfoResponse {
3348 #[getter(rename = "raw_version")]
3350 version: u64,
3351
3352 build: String,
3354
3355 subversion: String,
3357
3358 #[serde(rename = "protocolversion")]
3360 protocol_version: u32,
3361
3362 blocks: u32,
3364
3365 connections: usize,
3367
3368 #[serde(skip_serializing_if = "Option::is_none")]
3370 proxy: Option<String>,
3371
3372 difficulty: f64,
3374
3375 testnet: bool,
3377
3378 #[serde(rename = "paytxfee")]
3380 pay_tx_fee: f64,
3381
3382 #[serde(rename = "relayfee")]
3384 relay_fee: f64,
3385
3386 errors: String,
3388
3389 #[serde(rename = "errorstimestamp")]
3391 errors_timestamp: i64,
3392}
3393
3394#[deprecated(note = "Use `GetInfoResponse` instead")]
3395pub use self::GetInfoResponse as GetInfo;
3396
3397impl Default for GetInfoResponse {
3398 fn default() -> Self {
3399 GetInfoResponse {
3400 version: 0,
3401 build: "some build version".to_string(),
3402 subversion: "some subversion".to_string(),
3403 protocol_version: 0,
3404 blocks: 0,
3405 connections: 0,
3406 proxy: None,
3407 difficulty: 0.0,
3408 testnet: false,
3409 pay_tx_fee: 0.0,
3410 relay_fee: 0.0,
3411 errors: "no errors".to_string(),
3412 errors_timestamp: Utc::now().timestamp(),
3413 }
3414 }
3415}
3416
3417impl GetInfoResponse {
3418 #[allow(clippy::too_many_arguments)]
3420 #[deprecated(note = "Use `GetInfoResponse::new` instead")]
3421 pub fn from_parts(
3422 version: u64,
3423 build: String,
3424 subversion: String,
3425 protocol_version: u32,
3426 blocks: u32,
3427 connections: usize,
3428 proxy: Option<String>,
3429 difficulty: f64,
3430 testnet: bool,
3431 pay_tx_fee: f64,
3432 relay_fee: f64,
3433 errors: String,
3434 errors_timestamp: i64,
3435 ) -> Self {
3436 Self {
3437 version,
3438 build,
3439 subversion,
3440 protocol_version,
3441 blocks,
3442 connections,
3443 proxy,
3444 difficulty,
3445 testnet,
3446 pay_tx_fee,
3447 relay_fee,
3448 errors,
3449 errors_timestamp,
3450 }
3451 }
3452
3453 pub fn into_parts(
3455 self,
3456 ) -> (
3457 u64,
3458 String,
3459 String,
3460 u32,
3461 u32,
3462 usize,
3463 Option<String>,
3464 f64,
3465 bool,
3466 f64,
3467 f64,
3468 String,
3469 i64,
3470 ) {
3471 (
3472 self.version,
3473 self.build,
3474 self.subversion,
3475 self.protocol_version,
3476 self.blocks,
3477 self.connections,
3478 self.proxy,
3479 self.difficulty,
3480 self.testnet,
3481 self.pay_tx_fee,
3482 self.relay_fee,
3483 self.errors,
3484 self.errors_timestamp,
3485 )
3486 }
3487
3488 fn version_from_string(build_string: &str) -> Option<u64> {
3490 let semver_version = semver::Version::parse(build_string.strip_prefix('v')?).ok()?;
3491 let build_number = semver_version
3492 .build
3493 .as_str()
3494 .split('.')
3495 .next()
3496 .and_then(|num_str| num_str.parse::<u64>().ok())
3497 .unwrap_or_default();
3498
3499 let version_number = 1_000_000 * semver_version.major
3501 + 10_000 * semver_version.minor
3502 + 100 * semver_version.patch
3503 + build_number;
3504
3505 Some(version_number)
3506 }
3507}
3508
3509pub type BlockchainValuePoolBalances = [GetBlockchainInfoBalance; 6];
3511
3512#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters)]
3516pub struct GetBlockchainInfoResponse {
3517 chain: String,
3519
3520 #[getter(copy)]
3522 blocks: Height,
3523
3524 #[getter(copy)]
3527 headers: Height,
3528
3529 difficulty: f64,
3531
3532 #[serde(rename = "verificationprogress")]
3534 verification_progress: f64,
3535
3536 #[serde(rename = "chainwork")]
3538 chain_work: u64,
3539
3540 pruned: bool,
3542
3543 size_on_disk: u64,
3545
3546 commitments: u64,
3548
3549 #[serde(rename = "bestblockhash", with = "hex")]
3551 #[getter(copy)]
3552 best_block_hash: block::Hash,
3553
3554 #[serde(rename = "estimatedheight")]
3558 #[getter(copy)]
3559 estimated_height: Height,
3560
3561 #[serde(rename = "chainSupply")]
3563 chain_supply: GetBlockchainInfoBalance,
3564
3565 #[serde(rename = "valuePools")]
3567 value_pools: BlockchainValuePoolBalances,
3568
3569 upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
3571
3572 #[getter(copy)]
3574 consensus: TipConsensusBranch,
3575}
3576
3577impl Default for GetBlockchainInfoResponse {
3578 fn default() -> Self {
3579 Self {
3580 chain: "main".to_string(),
3581 blocks: Height(1),
3582 best_block_hash: block::Hash([0; 32]),
3583 estimated_height: Height(1),
3584 chain_supply: GetBlockchainInfoBalance::chain_supply(Default::default()),
3585 value_pools: GetBlockchainInfoBalance::zero_pools(),
3586 upgrades: IndexMap::new(),
3587 consensus: TipConsensusBranch {
3588 chain_tip: ConsensusBranchIdHex(ConsensusBranchId::default()),
3589 next_block: ConsensusBranchIdHex(ConsensusBranchId::default()),
3590 },
3591 headers: Height(1),
3592 difficulty: 0.0,
3593 verification_progress: 0.0,
3594 chain_work: 0,
3595 pruned: false,
3596 size_on_disk: 0,
3597 commitments: 0,
3598 }
3599 }
3600}
3601
3602impl GetBlockchainInfoResponse {
3603 #[allow(clippy::too_many_arguments)]
3607 pub fn new(
3608 chain: String,
3609 blocks: Height,
3610 best_block_hash: block::Hash,
3611 estimated_height: Height,
3612 chain_supply: GetBlockchainInfoBalance,
3613 value_pools: BlockchainValuePoolBalances,
3614 upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
3615 consensus: TipConsensusBranch,
3616 headers: Height,
3617 difficulty: f64,
3618 verification_progress: f64,
3619 chain_work: u64,
3620 pruned: bool,
3621 size_on_disk: u64,
3622 commitments: u64,
3623 ) -> Self {
3624 Self {
3625 chain,
3626 blocks,
3627 best_block_hash,
3628 estimated_height,
3629 chain_supply,
3630 value_pools,
3631 upgrades,
3632 consensus,
3633 headers,
3634 difficulty,
3635 verification_progress,
3636 chain_work,
3637 pruned,
3638 size_on_disk,
3639 commitments,
3640 }
3641 }
3642}
3643
3644#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, JsonSchema)]
3646#[serde(from = "DGetAddressBalanceRequest")]
3647pub struct GetAddressBalanceRequest {
3648 addresses: Vec<String>,
3650}
3651
3652impl From<DGetAddressBalanceRequest> for GetAddressBalanceRequest {
3653 fn from(address_strings: DGetAddressBalanceRequest) -> Self {
3654 match address_strings {
3655 DGetAddressBalanceRequest::Addresses { addresses } => {
3656 GetAddressBalanceRequest { addresses }
3657 }
3658 DGetAddressBalanceRequest::Address(address) => GetAddressBalanceRequest {
3659 addresses: vec![address],
3660 },
3661 }
3662 }
3663}
3664
3665#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, JsonSchema)]
3667#[serde(untagged)]
3668enum DGetAddressBalanceRequest {
3669 Addresses { addresses: Vec<String> },
3671 Address(String),
3673}
3674
3675#[deprecated(note = "Use `GetAddressBalanceRequest` instead.")]
3677pub type AddressStrings = GetAddressBalanceRequest;
3678
3679pub trait ValidateAddresses {
3681 fn valid_addresses(&self) -> Result<HashSet<Address>> {
3685 let valid_addresses: HashSet<Address> = self
3688 .addresses()
3689 .iter()
3690 .map(|address| {
3691 address
3692 .parse()
3693 .map_error(server::error::LegacyCode::InvalidAddressOrKey)
3694 })
3695 .collect::<Result<_>>()?;
3696
3697 Ok(valid_addresses)
3698 }
3699
3700 fn addresses(&self) -> &[String];
3702}
3703
3704impl ValidateAddresses for GetAddressBalanceRequest {
3705 fn addresses(&self) -> &[String] {
3706 &self.addresses
3707 }
3708}
3709
3710impl GetAddressBalanceRequest {
3711 pub fn new(addresses: Vec<String>) -> GetAddressBalanceRequest {
3713 GetAddressBalanceRequest { addresses }
3714 }
3715
3716 #[deprecated(
3718 note = "Use `AddressStrings::new` instead. Validity will be checked by the server."
3719 )]
3720 pub fn new_valid(addresses: Vec<String>) -> Result<GetAddressBalanceRequest> {
3721 let req = Self { addresses };
3722 req.valid_addresses()?;
3723 Ok(req)
3724 }
3725}
3726
3727#[derive(
3729 Clone,
3730 Copy,
3731 Debug,
3732 Default,
3733 Eq,
3734 PartialEq,
3735 Hash,
3736 serde::Serialize,
3737 serde::Deserialize,
3738 Getters,
3739 new,
3740)]
3741pub struct GetAddressBalanceResponse {
3742 balance: u64,
3744 pub received: u64,
3746}
3747
3748#[deprecated(note = "Use `GetAddressBalanceResponse` instead.")]
3749pub use self::GetAddressBalanceResponse as AddressBalance;
3750
3751#[derive(
3753 Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new, JsonSchema,
3754)]
3755#[serde(from = "DGetAddressUtxosRequest")]
3756pub struct GetAddressUtxosRequest {
3757 addresses: Vec<String>,
3759 #[serde(default)]
3761 #[serde(rename = "chainInfo")]
3762 chain_info: bool,
3763}
3764
3765impl From<DGetAddressUtxosRequest> for GetAddressUtxosRequest {
3766 fn from(request: DGetAddressUtxosRequest) -> Self {
3767 match request {
3768 DGetAddressUtxosRequest::Single(addr) => GetAddressUtxosRequest {
3769 addresses: vec![addr],
3770 chain_info: false,
3771 },
3772 DGetAddressUtxosRequest::Object {
3773 addresses,
3774 chain_info,
3775 } => GetAddressUtxosRequest {
3776 addresses,
3777 chain_info,
3778 },
3779 }
3780 }
3781}
3782
3783#[derive(Debug, serde::Deserialize, JsonSchema)]
3785#[serde(untagged)]
3786enum DGetAddressUtxosRequest {
3787 Single(String),
3789 Object {
3791 addresses: Vec<String>,
3793 #[serde(default)]
3795 #[serde(rename = "chainInfo")]
3796 chain_info: bool,
3797 },
3798}
3799
3800impl ValidateAddresses for GetAddressUtxosRequest {
3801 fn addresses(&self) -> &[String] {
3802 &self.addresses
3803 }
3804}
3805
3806#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
3808pub struct ConsensusBranchIdHex(#[serde(with = "hex")] ConsensusBranchId);
3809
3810impl ConsensusBranchIdHex {
3811 pub fn new(consensus_branch_id: u32) -> Self {
3813 ConsensusBranchIdHex(consensus_branch_id.into())
3814 }
3815
3816 pub fn inner(&self) -> u32 {
3818 self.0.into()
3819 }
3820}
3821
3822#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3824pub struct NetworkUpgradeInfo {
3825 name: NetworkUpgrade,
3829
3830 #[serde(rename = "activationheight")]
3832 activation_height: Height,
3833
3834 status: NetworkUpgradeStatus,
3836}
3837
3838impl NetworkUpgradeInfo {
3839 pub fn from_parts(
3841 name: NetworkUpgrade,
3842 activation_height: Height,
3843 status: NetworkUpgradeStatus,
3844 ) -> Self {
3845 Self {
3846 name,
3847 activation_height,
3848 status,
3849 }
3850 }
3851
3852 pub fn into_parts(self) -> (NetworkUpgrade, Height, NetworkUpgradeStatus) {
3854 (self.name, self.activation_height, self.status)
3855 }
3856}
3857
3858#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3860pub enum NetworkUpgradeStatus {
3861 #[serde(rename = "active")]
3866 Active,
3867
3868 #[serde(rename = "disabled")]
3870 Disabled,
3871
3872 #[serde(rename = "pending")]
3874 Pending,
3875}
3876
3877#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3881pub struct TipConsensusBranch {
3882 #[serde(rename = "chaintip")]
3884 chain_tip: ConsensusBranchIdHex,
3885
3886 #[serde(rename = "nextblock")]
3888 next_block: ConsensusBranchIdHex,
3889}
3890
3891impl TipConsensusBranch {
3892 pub fn from_parts(chain_tip: u32, next_block: u32) -> Self {
3894 Self {
3895 chain_tip: ConsensusBranchIdHex::new(chain_tip),
3896 next_block: ConsensusBranchIdHex::new(next_block),
3897 }
3898 }
3899
3900 pub fn into_parts(self) -> (u32, u32) {
3902 (self.chain_tip.inner(), self.next_block.inner())
3903 }
3904}
3905
3906#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
3912pub struct SendRawTransactionResponse(#[serde(with = "hex")] transaction::Hash);
3913
3914#[deprecated(note = "Use `SendRawTransactionResponse` instead")]
3915pub use self::SendRawTransactionResponse as SentTransactionHash;
3916
3917impl Default for SendRawTransactionResponse {
3918 fn default() -> Self {
3919 Self(transaction::Hash::from([0; 32]))
3920 }
3921}
3922
3923impl SendRawTransactionResponse {
3924 pub fn new(hash: transaction::Hash) -> Self {
3926 SendRawTransactionResponse(hash)
3927 }
3928
3929 #[deprecated(note = "Use `SentTransactionHash::hash` instead")]
3931 pub fn inner(&self) -> transaction::Hash {
3932 self.hash()
3933 }
3934
3935 pub fn hash(&self) -> transaction::Hash {
3937 self.0
3938 }
3939}
3940
3941#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
3945#[serde(untagged)]
3946pub enum GetBlockResponse {
3947 Raw(#[serde(with = "hex")] SerializedBlock),
3949 Object(Box<BlockObject>),
3951}
3952
3953#[deprecated(note = "Use `GetBlockResponse` instead")]
3954pub use self::GetBlockResponse as GetBlock;
3955
3956impl Default for GetBlockResponse {
3957 fn default() -> Self {
3958 GetBlockResponse::Object(Box::new(BlockObject {
3959 hash: block::Hash([0; 32]),
3960 confirmations: 0,
3961 height: None,
3962 time: None,
3963 n_tx: 0,
3964 tx: Vec::new(),
3965 trees: GetBlockTrees::default(),
3966 size: None,
3967 version: None,
3968 merkle_root: None,
3969 block_commitments: None,
3970 final_sapling_root: None,
3971 final_orchard_root: None,
3972 nonce: None,
3973 bits: None,
3974 difficulty: None,
3975 chain_supply: None,
3976 value_pools: None,
3977 previous_block_hash: None,
3978 next_block_hash: None,
3979 solution: None,
3980 }))
3981 }
3982}
3983
3984#[allow(clippy::too_many_arguments)]
3986#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3987pub struct BlockObject {
3988 #[getter(copy)]
3990 #[serde(with = "hex")]
3991 hash: block::Hash,
3992
3993 confirmations: i64,
3996
3997 #[serde(skip_serializing_if = "Option::is_none")]
3999 #[getter(copy)]
4000 size: Option<i64>,
4001
4002 #[serde(skip_serializing_if = "Option::is_none")]
4004 #[getter(copy)]
4005 height: Option<Height>,
4006
4007 #[serde(skip_serializing_if = "Option::is_none")]
4009 #[getter(copy)]
4010 version: Option<u32>,
4011
4012 #[serde(with = "opthex", rename = "merkleroot")]
4014 #[serde(skip_serializing_if = "Option::is_none")]
4015 #[getter(copy)]
4016 merkle_root: Option<block::merkle::Root>,
4017
4018 #[serde(with = "opthex", rename = "blockcommitments")]
4021 #[serde(skip_serializing_if = "Option::is_none")]
4022 #[getter(copy)]
4023 block_commitments: Option<[u8; 32]>,
4024
4025 #[serde(with = "opthex", rename = "finalsaplingroot")]
4029 #[serde(skip_serializing_if = "Option::is_none")]
4030 #[getter(copy)]
4031 final_sapling_root: Option<[u8; 32]>,
4032
4033 #[serde(with = "opthex", rename = "finalorchardroot")]
4035 #[serde(skip_serializing_if = "Option::is_none")]
4036 #[getter(copy)]
4037 final_orchard_root: Option<[u8; 32]>,
4038
4039 #[serde(rename = "nTx")]
4043 n_tx: usize,
4044
4045 tx: Vec<GetBlockTransaction>,
4048
4049 #[serde(skip_serializing_if = "Option::is_none")]
4051 #[getter(copy)]
4052 time: Option<i64>,
4053
4054 #[serde(with = "opthex")]
4056 #[serde(skip_serializing_if = "Option::is_none")]
4057 #[getter(copy)]
4058 nonce: Option<[u8; 32]>,
4059
4060 #[serde(with = "opthex")]
4063 #[serde(skip_serializing_if = "Option::is_none")]
4064 #[getter(copy)]
4065 solution: Option<Solution>,
4066
4067 #[serde(with = "opthex")]
4069 #[serde(skip_serializing_if = "Option::is_none")]
4070 #[getter(copy)]
4071 bits: Option<CompactDifficulty>,
4072
4073 #[serde(skip_serializing_if = "Option::is_none")]
4076 #[getter(copy)]
4077 difficulty: Option<f64>,
4078
4079 #[serde(rename = "chainSupply")]
4084 #[serde(skip_serializing_if = "Option::is_none")]
4085 chain_supply: Option<GetBlockchainInfoBalance>,
4086
4087 #[serde(rename = "valuePools")]
4089 #[serde(skip_serializing_if = "Option::is_none")]
4090 value_pools: Option<BlockchainValuePoolBalances>,
4091
4092 #[getter(copy)]
4094 trees: GetBlockTrees,
4095
4096 #[serde(rename = "previousblockhash", skip_serializing_if = "Option::is_none")]
4098 #[serde(with = "opthex")]
4099 #[getter(copy)]
4100 previous_block_hash: Option<block::Hash>,
4101
4102 #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
4104 #[serde(with = "opthex")]
4105 #[getter(copy)]
4106 next_block_hash: Option<block::Hash>,
4107}
4108
4109#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4110#[serde(untagged)]
4111pub enum GetBlockTransaction {
4114 Hash(#[serde(with = "hex")] transaction::Hash),
4116 Object(Box<TransactionObject>),
4118}
4119
4120#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4124#[serde(untagged)]
4125pub enum GetBlockHeaderResponse {
4126 Raw(hex_data::HexData),
4128
4129 Object(Box<BlockHeaderObject>),
4131}
4132
4133#[deprecated(note = "Use `GetBlockHeaderResponse` instead")]
4134pub use self::GetBlockHeaderResponse as GetBlockHeader;
4135
4136#[allow(clippy::too_many_arguments)]
4137#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4138pub struct BlockHeaderObject {
4142 #[serde(with = "hex")]
4144 #[getter(copy)]
4145 hash: block::Hash,
4146
4147 confirmations: i64,
4150
4151 #[getter(copy)]
4153 height: Height,
4154
4155 version: u32,
4157
4158 #[serde(with = "hex", rename = "merkleroot")]
4160 #[getter(copy)]
4161 merkle_root: block::merkle::Root,
4162
4163 #[serde(with = "hex", rename = "blockcommitments")]
4166 #[getter(copy)]
4167 block_commitments: [u8; 32],
4168
4169 #[serde(with = "hex", rename = "finalsaplingroot")]
4171 #[getter(copy)]
4172 final_sapling_root: [u8; 32],
4173
4174 #[serde(skip)]
4177 sapling_tree_size: u64,
4178
4179 time: i64,
4181
4182 #[serde(with = "hex")]
4184 #[getter(copy)]
4185 nonce: [u8; 32],
4186
4187 #[serde(with = "hex")]
4189 #[getter(copy)]
4190 solution: Solution,
4191
4192 #[serde(with = "hex")]
4194 #[getter(copy)]
4195 bits: CompactDifficulty,
4196
4197 difficulty: f64,
4200
4201 #[serde(rename = "previousblockhash")]
4203 #[serde(with = "hex")]
4204 #[getter(copy)]
4205 previous_block_hash: block::Hash,
4206
4207 #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
4209 #[getter(copy)]
4210 #[serde(with = "opthex")]
4211 next_block_hash: Option<block::Hash>,
4212}
4213
4214#[deprecated(note = "Use `BlockHeaderObject` instead")]
4215pub use BlockHeaderObject as GetBlockHeaderObject;
4216
4217impl Default for GetBlockHeaderResponse {
4218 fn default() -> Self {
4219 GetBlockHeaderResponse::Object(Box::default())
4220 }
4221}
4222
4223impl Default for BlockHeaderObject {
4224 fn default() -> Self {
4225 let difficulty: ExpandedDifficulty = zebra_chain::work::difficulty::U256::one().into();
4226
4227 BlockHeaderObject {
4228 hash: block::Hash([0; 32]),
4229 confirmations: 0,
4230 height: Height::MIN,
4231 version: 4,
4232 merkle_root: block::merkle::Root([0; 32]),
4233 block_commitments: Default::default(),
4234 final_sapling_root: Default::default(),
4235 sapling_tree_size: Default::default(),
4236 time: 0,
4237 nonce: [0; 32],
4238 solution: Solution::for_proposal(),
4239 bits: difficulty.to_compact(),
4240 difficulty: 1.0,
4241 previous_block_hash: block::Hash([0; 32]),
4242 next_block_hash: Some(block::Hash([0; 32])),
4243 }
4244 }
4245}
4246
4247#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4253#[serde(transparent)]
4254pub struct GetBlockHashResponse(#[serde(with = "hex")] pub(crate) block::Hash);
4255
4256impl GetBlockHashResponse {
4257 pub fn new(hash: block::Hash) -> Self {
4259 GetBlockHashResponse(hash)
4260 }
4261
4262 pub fn hash(&self) -> block::Hash {
4264 self.0
4265 }
4266}
4267
4268#[deprecated(note = "Use `GetBlockHashResponse` instead")]
4269pub use self::GetBlockHashResponse as GetBlockHash;
4270
4271pub type Hash = GetBlockHashResponse;
4273
4274#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new)]
4276pub struct GetBlockHeightAndHashResponse {
4277 #[getter(copy)]
4279 height: block::Height,
4280 #[getter(copy)]
4282 hash: block::Hash,
4283}
4284
4285#[deprecated(note = "Use `GetBlockHeightAndHashResponse` instead.")]
4286pub use GetBlockHeightAndHashResponse as GetBestBlockHeightAndHash;
4287
4288impl Default for GetBlockHeightAndHashResponse {
4289 fn default() -> Self {
4290 Self {
4291 height: block::Height::MIN,
4292 hash: block::Hash([0; 32]),
4293 }
4294 }
4295}
4296
4297impl Default for GetBlockHashResponse {
4298 fn default() -> Self {
4299 GetBlockHashResponse(block::Hash([0; 32]))
4300 }
4301}
4302
4303#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4307#[serde(untagged)]
4308pub enum GetRawTransactionResponse {
4309 Raw(#[serde(with = "hex")] SerializedTransaction),
4311 Object(Box<TransactionObject>),
4313}
4314
4315#[deprecated(note = "Use `GetRawTransactionResponse` instead")]
4316pub use self::GetRawTransactionResponse as GetRawTransaction;
4317
4318impl Default for GetRawTransactionResponse {
4319 fn default() -> Self {
4320 Self::Object(Box::default())
4321 }
4322}
4323
4324#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4326#[serde(untagged)]
4327pub enum GetAddressUtxosResponse {
4328 Utxos(Vec<Utxo>),
4330 UtxosAndChainInfo(GetAddressUtxosResponseObject),
4332}
4333
4334#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4336pub struct GetAddressUtxosResponseObject {
4337 utxos: Vec<Utxo>,
4338 #[serde(with = "hex")]
4339 #[getter(copy)]
4340 hash: block::Hash,
4341 #[getter(copy)]
4342 height: block::Height,
4343}
4344
4345#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4349pub struct Utxo {
4350 address: transparent::Address,
4352
4353 #[serde(with = "hex")]
4355 #[getter(copy)]
4356 txid: transaction::Hash,
4357
4358 #[serde(rename = "outputIndex")]
4360 #[getter(copy)]
4361 output_index: OutputIndex,
4362
4363 #[serde(with = "hex")]
4365 script: transparent::Script,
4366
4367 satoshis: u64,
4369
4370 #[getter(copy)]
4374 height: Height,
4375}
4376
4377#[deprecated(note = "Use `Utxo` instead")]
4378pub use self::Utxo as GetAddressUtxos;
4379
4380impl Default for Utxo {
4381 fn default() -> Self {
4382 Self {
4383 address: transparent::Address::from_pub_key_hash(
4384 zebra_chain::parameters::NetworkKind::default(),
4385 [0u8; 20],
4386 ),
4387 txid: transaction::Hash::from([0; 32]),
4388 output_index: OutputIndex::from_u64(0),
4389 script: transparent::Script::new(&[0u8; 10]),
4390 satoshis: u64::default(),
4391 height: Height(0),
4392 }
4393 }
4394}
4395
4396impl Utxo {
4397 #[deprecated(note = "Use `Utxo::new` instead")]
4399 pub fn from_parts(
4400 address: transparent::Address,
4401 txid: transaction::Hash,
4402 output_index: OutputIndex,
4403 script: transparent::Script,
4404 satoshis: u64,
4405 height: Height,
4406 ) -> Self {
4407 Utxo {
4408 address,
4409 txid,
4410 output_index,
4411 script,
4412 satoshis,
4413 height,
4414 }
4415 }
4416
4417 pub fn into_parts(
4419 &self,
4420 ) -> (
4421 transparent::Address,
4422 transaction::Hash,
4423 OutputIndex,
4424 transparent::Script,
4425 u64,
4426 Height,
4427 ) {
4428 (
4429 self.address,
4430 self.txid,
4431 self.output_index,
4432 self.script.clone(),
4433 self.satoshis,
4434 self.height,
4435 )
4436 }
4437}
4438
4439#[derive(
4443 Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new, JsonSchema,
4444)]
4445#[serde(from = "DGetAddressTxIdsRequest")]
4446pub struct GetAddressTxIdsRequest {
4447 addresses: Vec<String>,
4450 start: Option<u32>,
4452 end: Option<u32>,
4454}
4455
4456impl GetAddressTxIdsRequest {
4457 #[deprecated(note = "Use `GetAddressTxIdsRequest::new` instead.")]
4459 pub fn from_parts(addresses: Vec<String>, start: u32, end: u32) -> Self {
4460 GetAddressTxIdsRequest {
4461 addresses,
4462 start: Some(start),
4463 end: Some(end),
4464 }
4465 }
4466
4467 pub fn into_parts(&self) -> (Vec<String>, u32, u32) {
4469 (
4470 self.addresses.clone(),
4471 self.start.unwrap_or(0),
4472 self.end.unwrap_or(0),
4473 )
4474 }
4475}
4476
4477impl From<DGetAddressTxIdsRequest> for GetAddressTxIdsRequest {
4478 fn from(request: DGetAddressTxIdsRequest) -> Self {
4479 match request {
4480 DGetAddressTxIdsRequest::Single(addr) => GetAddressTxIdsRequest {
4481 addresses: vec![addr],
4482 start: None,
4483 end: None,
4484 },
4485 DGetAddressTxIdsRequest::Object {
4486 addresses,
4487 start,
4488 end,
4489 } => GetAddressTxIdsRequest {
4490 addresses,
4491 start,
4492 end,
4493 },
4494 }
4495 }
4496}
4497
4498#[derive(Debug, serde::Deserialize, JsonSchema)]
4500#[serde(untagged)]
4501enum DGetAddressTxIdsRequest {
4502 Single(String),
4504 Object {
4506 addresses: Vec<String>,
4508 start: Option<u32>,
4510 end: Option<u32>,
4512 },
4513}
4514
4515impl ValidateAddresses for GetAddressTxIdsRequest {
4516 fn addresses(&self) -> &[String] {
4517 &self.addresses
4518 }
4519}
4520
4521#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4523pub struct GetBlockTrees {
4524 #[serde(skip_serializing_if = "SaplingTrees::is_empty")]
4525 sapling: SaplingTrees,
4526 #[serde(skip_serializing_if = "OrchardTrees::is_empty")]
4527 orchard: OrchardTrees,
4528 #[serde(default, skip_serializing_if = "IronwoodTrees::is_empty")]
4531 ironwood: IronwoodTrees,
4532}
4533
4534impl Default for GetBlockTrees {
4535 fn default() -> Self {
4536 GetBlockTrees {
4537 sapling: SaplingTrees { size: 0 },
4538 orchard: OrchardTrees { size: 0 },
4539 ironwood: IronwoodTrees { size: 0 },
4540 }
4541 }
4542}
4543
4544impl GetBlockTrees {
4545 pub fn new(sapling: u64, orchard: u64, ironwood: u64) -> Self {
4547 GetBlockTrees {
4548 sapling: SaplingTrees { size: sapling },
4549 orchard: OrchardTrees { size: orchard },
4550 ironwood: IronwoodTrees { size: ironwood },
4551 }
4552 }
4553
4554 pub fn sapling(self) -> u64 {
4556 self.sapling.size
4557 }
4558
4559 pub fn orchard(self) -> u64 {
4561 self.orchard.size
4562 }
4563
4564 pub fn ironwood(self) -> u64 {
4566 self.ironwood.size
4567 }
4568}
4569
4570#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4572pub struct SaplingTrees {
4573 size: u64,
4574}
4575
4576impl SaplingTrees {
4577 fn is_empty(&self) -> bool {
4578 self.size == 0
4579 }
4580}
4581
4582#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4584pub struct OrchardTrees {
4585 size: u64,
4586}
4587
4588impl OrchardTrees {
4589 fn is_empty(&self) -> bool {
4590 self.size == 0
4591 }
4592}
4593
4594#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4596pub struct IronwoodTrees {
4597 size: u64,
4598}
4599
4600impl IronwoodTrees {
4601 fn is_empty(&self) -> bool {
4602 self.size == 0
4603 }
4604}
4605
4606fn build_height_range(
4622 start: Option<u32>,
4623 end: Option<u32>,
4624 chain_height: Height,
4625) -> Result<RangeInclusive<Height>> {
4626 let start = Height(start.unwrap_or(0)).min(chain_height);
4629
4630 let end = match end {
4632 Some(0) | None => chain_height,
4633 Some(val) => Height(val).min(chain_height),
4634 };
4635
4636 if start > end {
4637 return Err(ErrorObject::owned(
4638 ErrorCode::InvalidParams.code(),
4639 format!("start {start:?} must be less than or equal to end {end:?}"),
4640 None::<()>,
4641 ));
4642 }
4643
4644 Ok(start..=end)
4645}
4646
4647pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height> {
4655 if index >= 0 {
4656 let height = index.try_into().map_err(|_| {
4657 ErrorObject::borrowed(
4658 ErrorCode::InvalidParams.code(),
4659 "Index conversion failed",
4660 None,
4661 )
4662 })?;
4663 if height > tip_height.0 {
4664 return Err(ErrorObject::borrowed(
4665 ErrorCode::InvalidParams.code(),
4666 "Provided index is greater than the current tip",
4667 None,
4668 ));
4669 }
4670 Ok(Height(height))
4671 } else {
4672 let height = i32::try_from(tip_height.0)
4674 .map_err(|_| {
4675 ErrorObject::borrowed(
4676 ErrorCode::InvalidParams.code(),
4677 "Tip height conversion failed",
4678 None,
4679 )
4680 })?
4681 .checked_add(index + 1);
4682
4683 let sanitized_height = match height {
4684 None => {
4685 return Err(ErrorObject::borrowed(
4686 ErrorCode::InvalidParams.code(),
4687 "Provided index is not valid",
4688 None,
4689 ));
4690 }
4691 Some(h) => {
4692 if h < 0 {
4693 return Err(ErrorObject::borrowed(
4694 ErrorCode::InvalidParams.code(),
4695 "Provided negative index ends up with a negative height",
4696 None,
4697 ));
4698 }
4699 let h: u32 = h.try_into().map_err(|_| {
4700 ErrorObject::borrowed(
4701 ErrorCode::InvalidParams.code(),
4702 "Height conversion failed",
4703 None,
4704 )
4705 })?;
4706 if h > tip_height.0 {
4707 return Err(ErrorObject::borrowed(
4708 ErrorCode::InvalidParams.code(),
4709 "Provided index is greater than the current tip",
4710 None,
4711 ));
4712 }
4713
4714 h
4715 }
4716 };
4717
4718 Ok(Height(sanitized_height))
4719 }
4720}
4721
4722pub mod opthex {
4724 use hex::{FromHex, ToHex};
4725 use serde::{de, Deserialize, Deserializer, Serializer};
4726
4727 #[allow(missing_docs)]
4728 pub fn serialize<S, T>(data: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
4729 where
4730 S: Serializer,
4731 T: ToHex,
4732 {
4733 match data {
4734 Some(data) => {
4735 let s = data.encode_hex::<String>();
4736 serializer.serialize_str(&s)
4737 }
4738 None => serializer.serialize_none(),
4739 }
4740 }
4741
4742 #[allow(missing_docs)]
4743 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
4744 where
4745 D: Deserializer<'de>,
4746 T: FromHex,
4747 {
4748 let opt = Option::<String>::deserialize(deserializer)?;
4749 match opt {
4750 Some(s) => T::from_hex(&s)
4751 .map(Some)
4752 .map_err(|_e| de::Error::custom("failed to convert hex string")),
4753 None => Ok(None),
4754 }
4755 }
4756}
4757
4758pub mod arrayhex {
4760 use serde::{Deserializer, Serializer};
4761 use std::fmt;
4762
4763 #[allow(missing_docs)]
4764 pub fn serialize<S, const N: usize>(data: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
4765 where
4766 S: Serializer,
4767 {
4768 let hex_string = hex::encode(data);
4769 serializer.serialize_str(&hex_string)
4770 }
4771
4772 #[allow(missing_docs)]
4773 pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
4774 where
4775 D: Deserializer<'de>,
4776 {
4777 struct HexArrayVisitor<const N: usize>;
4778
4779 impl<const N: usize> serde::de::Visitor<'_> for HexArrayVisitor<N> {
4780 type Value = [u8; N];
4781
4782 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
4783 write!(formatter, "a hex string representing exactly {N} bytes")
4784 }
4785
4786 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
4787 where
4788 E: serde::de::Error,
4789 {
4790 let vec = hex::decode(v).map_err(E::custom)?;
4791 vec.clone().try_into().map_err(|_| {
4792 E::invalid_length(vec.len(), &format!("expected {N} bytes").as_str())
4793 })
4794 }
4795 }
4796
4797 deserializer.deserialize_str(HexArrayVisitor::<N>)
4798 }
4799}
4800
4801pub async fn chain_tip_difficulty<State>(
4803 network: Network,
4804 mut state: State,
4805 should_use_default: bool,
4806) -> Result<f64>
4807where
4808 State: ReadStateService,
4809{
4810 let request = ReadRequest::ChainInfo;
4811
4812 let response = state
4818 .ready()
4819 .and_then(|service| service.call(request))
4820 .await;
4821
4822 let response = match (should_use_default, response) {
4823 (_, Ok(res)) => res,
4824 (true, Err(_)) => {
4825 return Ok((U256::from(network.target_difficulty_limit()) >> 128).as_u128() as f64);
4826 }
4827 (false, Err(error)) => return Err(ErrorObject::owned(0, error.to_string(), None::<()>)),
4828 };
4829
4830 let chain_info = match response {
4831 ReadResponse::ChainInfo(info) => info,
4832 _ => unreachable!("unmatched response to a chain info request"),
4833 };
4834
4835 let pow_limit: U256 = network.target_difficulty_limit().into();
4858 let Some(difficulty) = chain_info.expected_difficulty.to_expanded() else {
4859 return Ok(0.0);
4860 };
4861
4862 let pow_limit = pow_limit >> 128;
4864 let difficulty = U256::from(difficulty) >> 128;
4865
4866 let pow_limit = pow_limit.as_u128() as f64;
4869 let difficulty = difficulty.as_u128() as f64;
4870
4871 Ok(pow_limit / difficulty)
4873}
4874
4875#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)]
4877pub enum AddNodeCommand {
4878 #[serde(rename = "add")]
4880 Add,
4881}
4882
4883#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4887#[serde(transparent)]
4888pub struct GetTxOutResponse(Option<types::transaction::OutputObject>);