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::{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_address, funding_stream_values,
70 miner_subsidy, FundingStreamReceiver,
71 },
72 ConsensusBranchId, Network, NetworkUpgrade,
73 },
74 serialization::{
75 BytesInDisplayOrder, DateTime32, Duration32, ZcashDeserialize, ZcashDeserializeInto,
76 ZcashSerialize,
77 },
78 subtree::NoteCommitmentSubtreeIndex,
79 transaction::{self, SerializedTransaction, Transaction, UnminedTx},
80 transparent::{self, Address, OutputIndex},
81 value_balance::ValueBalance,
82 work::{
83 difficulty::{CompactDifficulty, ExpandedDifficulty, ParameterDifficulty, U256},
84 equihash::Solution,
85 },
86};
87use zebra_consensus::{router::service_trait::BlockVerifierService, RouterError};
88use zebra_network::{address_book_peers::AddressBookPeers, types::PeerServices, PeerSocketAddr};
89use zebra_node_services::mempool::{self, CreatedOrSpent, MempoolService};
90use zebra_state::{
91 AnyTx, HashOrHeight, OutputLocation, ReadRequest, ReadResponse, ReadState as ReadStateService,
92 State as StateService, TransactionLocation,
93};
94
95use crate::{
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 precompute,
121 proposal::proposal_block_from_template,
122 BlockTemplateResponse, BlockTemplateTimeSource, GetBlockTemplateHandler,
123 GetBlockTemplateParameters, GetBlockTemplateResponse, MinerParams,
124 },
125 get_blockchain_info::GetBlockchainInfoBalance,
126 get_mempool_info::GetMempoolInfoResponse,
127 get_mining_info::GetMiningInfoResponse,
128 get_raw_mempool::{self, GetRawMempoolResponse},
129 get_standard_fee::{standard_fee, GetStandardFeeResponse},
130 long_poll::{LongPollId, LongPollInput},
131 network_info::{GetNetworkInfoResponse, NetworkInfo},
132 peer_info::PeerInfo,
133 submit_block::{SubmitBlockErrorResponse, SubmitBlockParameters, SubmitBlockResponse},
134 subsidy::GetBlockSubsidyResponse,
135 transaction::TransactionObject,
136 unified_address::ZListUnifiedReceiversResponse,
137 validate_address::ValidateAddressResponse,
138 z_validate_address::ZValidateAddressResponse,
139};
140
141include!(concat!(env!("OUT_DIR"), "/rpc_openrpc.rs"));
142
143pub(super) const PARAM_VERBOSE_DESC: &str =
146 "Boolean flag to indicate verbosity, true for a json object, false for hex encoded data.";
147pub(super) const PARAM_POOL_DESC: &str =
148 "The pool from which subtrees should be returned. Either \"sapling\" or \"orchard\".";
149pub(super) const PARAM_START_INDEX_DESC: &str =
150 "The index of the first 2^16-leaf subtree to return.";
151pub(super) const PARAM_LIMIT_DESC: &str = "The maximum number of subtrees to return.";
152pub(super) const PARAM_REQUEST_DESC: &str = "The request object containing the parameters.";
153pub(super) const PARAM_INDEX_DESC: &str = "The index of the subtree to return.";
154pub(super) const PARAM_RAW_TRANSACTION_HEX_DESC: &str = "The hex-encoded raw transaction bytes.";
155#[allow(non_upper_case_globals)]
156pub(super) const PARAM__ALLOW_HIGH_FEES_DESC: &str = "Whether to allow high fees.";
157pub(super) const PARAM_NUM_BLOCKS_DESC: &str = "The number of blocks to return.";
158pub(super) const PARAM_HEIGHT_DESC: &str = "The height of the block to return.";
159pub(super) const PARAM_COMMAND_DESC: &str = "The command to execute.";
160#[allow(non_upper_case_globals)]
161pub(super) const PARAM__PARAMETERS_DESC: &str = "The parameters for the command.";
162pub(super) const PARAM_BLOCK_HASH_DESC: &str = "The hash of the block to return.";
163pub(super) const PARAM_ADDRESS_DESC: &str = "The address to return.";
164pub(super) const PARAM_ADDRESS_STRINGS_DESC: &str = "The addresses to return.";
165pub(super) const PARAM_ADDR_DESC: &str = "The address to return.";
166pub(super) const PARAM_HEX_DATA_DESC: &str = "The hex-encoded data to return.";
167pub(super) const PARAM_TXID_DESC: &str = "The transaction ID to return.";
168pub(super) const PARAM_HASH_OR_HEIGHT_DESC: &str = "The block hash or height to return.";
169pub(super) const PARAM_PARAMETERS_DESC: &str = "The parameters for the command.";
170pub(super) const PARAM_VERBOSITY_DESC: &str = "Whether to include verbose output.";
171pub(super) const PARAM_N_DESC: &str = "The output index in the transaction.";
172pub(super) const PARAM_INCLUDE_MEMPOOL_DESC: &str =
173 "Whether to include mempool transactions in the response.";
174
175#[cfg(test)]
176mod tests;
177
178#[rpc(server)]
179pub trait Rpc {
181 #[method(name = "getinfo")]
196 async fn get_info(&self) -> Result<GetInfoResponse>;
197
198 #[method(name = "getdeprecationinfo")]
218 async fn get_deprecation_info(&self) -> Result<GetDeprecationInfoResponse>;
219
220 #[method(name = "getblockchaininfo")]
231 async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse>;
232
233 #[method(name = "getaddressbalance")]
256 async fn get_address_balance(
257 &self,
258 address_strings: GetAddressBalanceRequest,
259 ) -> Result<GetAddressBalanceResponse>;
260
261 #[method(name = "sendrawtransaction")]
278 async fn send_raw_transaction(
279 &self,
280 raw_transaction_hex: String,
281 _allow_high_fees: Option<bool>,
282 ) -> Result<SendRawTransactionResponse>;
283
284 #[method(name = "getblock")]
310 async fn get_block(
311 &self,
312 hash_or_height: String,
313 verbosity: Option<u8>,
314 ) -> Result<GetBlockResponse>;
315
316 #[method(name = "getblockheader")]
334 async fn get_block_header(
335 &self,
336 hash_or_height: String,
337 verbose: Option<bool>,
338 ) -> Result<GetBlockHeaderResponse>;
339
340 #[method(name = "getbestblockhash")]
346 fn get_best_block_hash(&self) -> Result<GetBlockHashResponse>;
347
348 #[method(name = "getbestblockheightandhash")]
354 fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse>;
355
356 #[method(name = "getmempoolinfo")]
360 async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse>;
361
362 #[method(name = "getrawmempool")]
372 async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse>;
373
374 #[method(name = "z_gettreestate")]
391 async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse>;
392
393 #[method(name = "z_getsubtreesbyindex")]
412 async fn z_get_subtrees_by_index(
413 &self,
414 pool: String,
415 start_index: NoteCommitmentSubtreeIndex,
416 limit: Option<NoteCommitmentSubtreeIndex>,
417 ) -> Result<GetSubtreesByIndexResponse>;
418
419 #[method(name = "getrawtransaction")]
431 async fn get_raw_transaction(
432 &self,
433 txid: String,
434 verbose: Option<u8>,
435 block_hash: Option<String>,
436 ) -> Result<GetRawTransactionResponse>;
437
438 #[method(name = "getaddresstxids")]
470 async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>>;
471
472 #[method(name = "getaddressutxos")]
501 async fn get_address_utxos(
502 &self,
503 request: GetAddressUtxosRequest,
504 ) -> Result<GetAddressUtxosResponse>;
505
506 #[method(name = "stop")]
517 fn stop(&self) -> Result<String>;
518
519 #[method(name = "getblockcount")]
526 fn get_block_count(&self) -> Result<u32>;
527
528 #[method(name = "getblockhash")]
544 async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse>;
545
546 #[method(name = "getblocktemplate")]
568 async fn get_block_template(
569 &self,
570 parameters: Option<GetBlockTemplateParameters>,
571 ) -> Result<GetBlockTemplateResponse>;
572
573 #[method(name = "submitblock")]
589 async fn submit_block(
590 &self,
591 hex_data: HexData,
592 _parameters: Option<SubmitBlockParameters>,
593 ) -> Result<SubmitBlockResponse>;
594
595 #[method(name = "getmininginfo")]
601 async fn get_mining_info(&self) -> Result<GetMiningInfoResponse>;
602
603 #[method(name = "getnetworksolps")]
614 async fn get_network_sol_ps(&self, num_blocks: Option<i32>, height: Option<i32>)
615 -> Result<u64>;
616
617 #[method(name = "getnetworkhashps")]
627 async fn get_network_hash_ps(
628 &self,
629 num_blocks: Option<i32>,
630 height: Option<i32>,
631 ) -> Result<u64> {
632 self.get_network_sol_ps(num_blocks, height).await
633 }
634
635 #[method(name = "getnetworkinfo")]
641 async fn get_network_info(&self) -> Result<GetNetworkInfoResponse>;
642
643 #[method(name = "getpeerinfo")]
649 async fn get_peer_info(&self) -> Result<Vec<PeerInfo>>;
650
651 #[method(name = "ping")]
660 async fn ping(&self) -> Result<()>;
661
662 #[method(name = "validateaddress")]
673 async fn validate_address(&self, address: String) -> Result<ValidateAddressResponse>;
674
675 #[method(name = "z_validateaddress")]
690 async fn z_validate_address(&self, address: String) -> Result<ZValidateAddressResponse>;
691
692 #[method(name = "getstandardfee")]
702 async fn get_standard_fee(&self) -> Result<GetStandardFeeResponse>;
703
704 #[method(name = "getblocksubsidy")]
721 async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse>;
722
723 #[method(name = "getdifficulty")]
729 async fn get_difficulty(&self) -> Result<f64>;
730
731 #[method(name = "z_listunifiedreceivers")]
745 async fn z_list_unified_receivers(
746 &self,
747 address: String,
748 ) -> Result<ZListUnifiedReceiversResponse>;
749
750 #[method(name = "invalidateblock")]
758 async fn invalidate_block(&self, block_hash: String) -> Result<()>;
759
760 #[method(name = "reconsiderblock")]
766 async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>>;
767
768 #[method(name = "generate")]
769 async fn generate(&self, num_blocks: u32) -> Result<Vec<GetBlockHashResponse>>;
783
784 #[method(name = "generatetoaddress")]
785 async fn generate_to_address(
805 &self,
806 num_blocks: u32,
807 address: String,
808 ) -> Result<Vec<GetBlockHashResponse>> {
809 let _ = (num_blocks, address);
810 Err(ErrorObject::borrowed(
811 ErrorCode::MethodNotFound.code(),
812 "generatetoaddress is not implemented",
813 None,
814 ))
815 }
816
817 #[method(name = "addnode")]
818 async fn add_node(&self, addr: PeerSocketAddr, command: AddNodeCommand) -> Result<()>;
833
834 #[method(name = "rpc.discover")]
836 fn openrpc(&self) -> openrpsee::openrpc::Response;
837 #[method(name = "gettxout")]
849 async fn get_tx_out(
850 &self,
851 txid: String,
852 n: u32,
853 include_mempool: Option<bool>,
854 ) -> Result<GetTxOutResponse>;
855}
856
857#[derive(Clone)]
859pub struct RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
860where
861 Mempool: MempoolService,
862 State: StateService,
863 ReadState: ReadStateService,
864 Tip: ChainTip + Clone + Send + Sync + 'static,
865 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
866 BlockVerifierRouter: BlockVerifierService,
867 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
868{
869 build_version: String,
873
874 user_agent: String,
876
877 network: Network,
879
880 debug_force_finished_sync: bool,
883
884 end_of_support_height: Option<Height>,
887
888 max_address_query_results: usize,
890
891 mempool: Mempool,
895
896 state: State,
898
899 read_state: ReadState,
901
902 latest_chain_tip: Tip,
904
905 queue_sender: broadcast::Sender<UnminedTx>,
909
910 address_book: AddressBook,
912
913 last_warn_error_log_rx: LoggedLastEvent,
915
916 gbt: GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>,
918
919 #[cfg(test)]
921 template_clock: Option<Arc<std::sync::atomic::AtomicU32>>,
922}
923
924pub type LoggedLastEvent = watch::Receiver<Option<(String, tracing::Level, chrono::DateTime<Utc>)>>;
926
927impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> fmt::Debug
928 for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
929where
930 Mempool: MempoolService,
931 State: StateService,
932 ReadState: ReadStateService,
933 Tip: ChainTip + Clone + Send + Sync + 'static,
934 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
935 BlockVerifierRouter: BlockVerifierService,
936 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
937{
938 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
939 f.debug_struct("RpcImpl")
941 .field("build_version", &self.build_version)
942 .field("user_agent", &self.user_agent)
943 .field("network", &self.network)
944 .field("debug_force_finished_sync", &self.debug_force_finished_sync)
945 .field("getblocktemplate", &self.gbt)
946 .finish()
947 }
948}
949
950impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
951 RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
952where
953 Mempool: MempoolService,
954 State: StateService,
955 ReadState: ReadStateService,
956 Tip: ChainTip + Clone + Send + Sync + 'static,
957 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
958 BlockVerifierRouter: BlockVerifierService,
959 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
960{
961 #[allow(clippy::too_many_arguments)]
966 pub fn new<VersionString, UserAgentString>(
967 network: Network,
968 mining_config: config::mining::Config,
969 debug_force_finished_sync: bool,
970 build_version: VersionString,
971 user_agent: UserAgentString,
972 mempool: Mempool,
973 state: State,
974 read_state: ReadState,
975 block_verifier_router: BlockVerifierRouter,
976 sync_status: SyncStatus,
977 latest_chain_tip: Tip,
978 address_book: AddressBook,
979 last_warn_error_log_rx: LoggedLastEvent,
980 mined_block_sender: Option<mpsc::Sender<(block::Hash, block::Height)>>,
981 ) -> (Self, JoinHandle<()>)
982 where
983 VersionString: ToString + Clone + Send + 'static,
984 UserAgentString: ToString + Clone + Send + 'static,
985 {
986 let (runner, queue_sender) = Queue::start();
987
988 let mut build_version = build_version.to_string();
989 let user_agent = user_agent.to_string();
990
991 if !build_version.is_empty() && !build_version.starts_with('v') {
993 build_version.insert(0, 'v');
994 }
995
996 let gbt = GetBlockTemplateHandler::new(
997 &network,
998 mining_config.clone(),
999 block_verifier_router,
1000 sync_status,
1001 mined_block_sender,
1002 );
1003
1004 let rpc_impl = RpcImpl {
1005 build_version,
1006 user_agent,
1007 network: network.clone(),
1008 debug_force_finished_sync,
1009 end_of_support_height: None,
1010 max_address_query_results: MAX_ADDRESS_QUERY_RESULTS,
1011 mempool: mempool.clone(),
1012 state: state.clone(),
1013 read_state: read_state.clone(),
1014 latest_chain_tip: latest_chain_tip.clone(),
1015 queue_sender,
1016 address_book,
1017 last_warn_error_log_rx,
1018 gbt,
1019 #[cfg(test)]
1020 template_clock: None,
1021 };
1022
1023 let rpc_tx_queue_task_handle = tokio::spawn(
1025 runner
1026 .run(mempool, read_state, latest_chain_tip, network)
1027 .in_current_span(),
1028 );
1029
1030 (rpc_impl, rpc_tx_queue_task_handle)
1031 }
1032
1033 fn template_clock_now(&self) -> DateTime32 {
1035 #[cfg(test)]
1036 if let Some(clock) = &self.template_clock {
1037 return clock.load(std::sync::atomic::Ordering::SeqCst).into();
1038 }
1039
1040 DateTime32::now()
1041 }
1042
1043 pub fn spawn_block_template_updater(&self) -> Option<JoinHandle<()>> {
1049 let miner_params = self.gbt.miner_params()?.clone();
1050 let template_cache = self.gbt.template_cache()?.clone();
1051
1052 Some(tokio::spawn(
1053 precompute::run(
1054 self.network.clone(),
1055 miner_params,
1056 self.gbt.coinbase_cache(),
1057 template_cache,
1058 self.mempool.clone(),
1059 self.read_state.clone(),
1060 self.latest_chain_tip.clone(),
1061 self.gbt.sync_status(),
1062 )
1063 .in_current_span(),
1064 ))
1065 }
1066
1067 async fn precomputed_block_template(
1089 &self,
1090 client_long_poll_id: Option<LongPollId>,
1091 long_poll_started: DateTime32,
1092 ) -> Option<BlockTemplateResponse> {
1093 let cache = self.gbt.template_cache()?;
1094
1095 if cache.is_empty() {
1098 return None;
1099 }
1100
1101 let mut template_changes = cache.subscribe();
1107
1108 let mut tip_change = self.latest_chain_tip.clone();
1112 tip_change.mark_best_tip_seen();
1113
1114 loop {
1115 let template = self.precomputed_template_for_state_tip(cache).await?;
1116
1117 let is_client_template = Some(template.long_poll_id) == client_long_poll_id;
1118 let now = self.template_clock_now();
1119 let max_time_reached =
1120 long_poll_started <= template.max_time && now > template.max_time;
1121
1122 if !is_client_template || max_time_reached {
1123 let mut template = (*template).clone();
1124 template.submit_old = client_long_poll_id.as_ref().map(|old_long_poll_id| {
1125 now <= template.max_time && template.long_poll_id.submit_old(old_long_poll_id)
1126 });
1127
1128 return Some(template);
1129 }
1130
1131 let max_time = template.max_time;
1134
1135 let duration_until_max_time = max_time.saturating_duration_since(now);
1140 let wait_for_max_time =
1141 tokio::time::sleep(duration_until_max_time.to_std() + Duration::from_secs(1));
1142
1143 tokio::select! {
1144 biased;
1145
1146 tip_changed = tip_change.best_tip_changed() => {
1147 tip_changed.ok()?;
1149 }
1150
1151 () = template_changes.changed() => {}
1152
1153 () = wait_for_max_time, if long_poll_started <= max_time => {}
1155 }
1156 }
1157 }
1158
1159 async fn precomputed_template_for_state_tip(
1164 &self,
1165 cache: &precompute::TemplateCache,
1166 ) -> Option<Arc<BlockTemplateResponse>> {
1167 let mut template_changes = cache.subscribe();
1170
1171 let timeout = precompute::new_tip_timeout(self.gbt.miner_params()?);
1174
1175 tokio::time::timeout(timeout, async move {
1176 loop {
1177 let ReadResponse::Tip(Some((_, tip_hash))) = self
1181 .read_state
1182 .clone()
1183 .oneshot(ReadRequest::Tip)
1184 .await
1185 .ok()?
1186 else {
1187 return None;
1188 };
1189
1190 if let Some(template) =
1191 cache.template_for_tip(tip_hash, &self.network, self.template_clock_now())
1192 {
1193 return Some(template);
1194 }
1195
1196 template_changes.changed().await;
1197 }
1198 })
1199 .await
1200 .ok()
1201 .flatten()
1202 }
1203
1204 pub fn network(&self) -> &Network {
1206 &self.network
1207 }
1208
1209 pub fn with_end_of_support_height(mut self, end_of_support_height: Option<Height>) -> Self {
1213 self.end_of_support_height = end_of_support_height;
1214 self
1215 }
1216
1217 #[cfg(test)]
1219 pub(crate) fn with_max_address_query_results(
1220 mut self,
1221 max_address_query_results: usize,
1222 ) -> Self {
1223 self.max_address_query_results = max_address_query_results;
1224 self
1225 }
1226}
1227
1228#[async_trait]
1229impl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus> RpcServer
1230 for RpcImpl<Mempool, State, ReadState, Tip, AddressBook, BlockVerifierRouter, SyncStatus>
1231where
1232 Mempool: MempoolService,
1233 State: StateService,
1234 ReadState: ReadStateService,
1235 Tip: ChainTip + Clone + Send + Sync + 'static,
1236 AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
1237 BlockVerifierRouter: BlockVerifierService,
1238 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
1239{
1240 async fn get_info(&self) -> Result<GetInfoResponse> {
1241 let version = GetInfoResponse::version_from_string(&self.build_version)
1242 .expect("invalid version string");
1243
1244 let connections = self.address_book.recently_live_peers(Utc::now()).len();
1245
1246 let last_error_recorded = self.last_warn_error_log_rx.borrow().clone();
1247 let (last_error_log, _level, last_error_log_time) = last_error_recorded.unwrap_or((
1248 GetInfoResponse::default().errors,
1249 tracing::Level::INFO,
1250 Utc::now(),
1251 ));
1252
1253 let tip_height = self
1254 .latest_chain_tip
1255 .best_tip_height()
1256 .unwrap_or(Height::MIN);
1257 let testnet = self.network.is_a_test_network();
1258
1259 let pay_tx_fee = 0.0;
1265
1266 let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
1267 / (zebra_chain::amount::COIN as f64);
1268 let difficulty = chain_tip_difficulty(self.network.clone(), self.read_state.clone(), true)
1269 .await
1270 .expect("should always be Ok when `should_use_default` is true");
1271
1272 let response = GetInfoResponse {
1273 version,
1274 build: self.build_version.clone(),
1275 subversion: self.user_agent.clone(),
1276 protocol_version: zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0,
1277 blocks: tip_height.0,
1278 connections,
1279 proxy: None,
1280 difficulty,
1281 testnet,
1282 pay_tx_fee,
1283 relay_fee,
1284 errors: last_error_log,
1285 errors_timestamp: last_error_log_time.timestamp(),
1286 };
1287
1288 Ok(response)
1289 }
1290
1291 async fn get_deprecation_info(&self) -> Result<GetDeprecationInfoResponse> {
1292 let end_of_service = self
1293 .end_of_support_height
1294 .filter(|_| self.network == Network::Mainnet)
1297 .map(|end_of_support_height| {
1298 let tip_height = self
1302 .latest_chain_tip
1303 .best_tip_height()
1304 .unwrap_or_else(|| self.network.checkpoint_list().max_height());
1305 let estimated_time = Utc::now()
1306 .timestamp()
1307 .saturating_add(
1308 NetworkUpgrade::duration_between_heights(
1309 &self.network,
1310 tip_height,
1311 end_of_support_height,
1312 )
1313 .num_seconds(),
1314 )
1315 .saturating_sub(END_OF_SERVICE_ESTIMATE_SAFETY_MARGIN)
1316 .max(0);
1317
1318 EndOfService {
1319 block_height: end_of_support_height.0,
1320 estimated_time,
1321 }
1322 });
1323
1324 Ok(GetDeprecationInfoResponse { end_of_service })
1325 }
1326
1327 #[allow(clippy::unwrap_in_result)]
1328 async fn get_blockchain_info(&self) -> Result<GetBlockchainInfoResponse> {
1329 let debug_force_finished_sync = self.debug_force_finished_sync;
1330 let network = &self.network;
1331
1332 let (usage_info_rsp, tip_pool_values_rsp, chain_tip_difficulty) = {
1333 use zebra_state::ReadRequest::*;
1334 let state_call = |request| self.read_state.clone().oneshot(request);
1335 tokio::join!(
1336 state_call(UsageInfo),
1337 state_call(TipPoolValues),
1338 chain_tip_difficulty(network.clone(), self.read_state.clone(), true)
1339 )
1340 };
1341
1342 let (size_on_disk, (tip_height, tip_hash), value_balance, difficulty) = {
1343 use zebra_state::ReadResponse::*;
1344
1345 let UsageInfo(size_on_disk) = usage_info_rsp.map_misc_error()? else {
1346 unreachable!("unmatched response to a TipPoolValues request")
1347 };
1348
1349 let (tip, value_balance) = match tip_pool_values_rsp {
1350 Ok(TipPoolValues {
1351 tip_height,
1352 tip_hash,
1353 value_balance,
1354 }) => ((tip_height, tip_hash), value_balance),
1355 Ok(_) => unreachable!("unmatched response to a TipPoolValues request"),
1356 Err(_) => ((Height::MIN, network.genesis_hash()), Default::default()),
1357 };
1358
1359 let difficulty = chain_tip_difficulty
1360 .expect("should always be Ok when `should_use_default` is true");
1361
1362 (size_on_disk, tip, value_balance, difficulty)
1363 };
1364
1365 let now = Utc::now();
1366 let (estimated_height, verification_progress) = self
1367 .latest_chain_tip
1368 .best_tip_height_and_block_time()
1369 .map(|(tip_height, tip_block_time)| {
1370 let height =
1371 NetworkChainTipHeightEstimator::new(tip_block_time, tip_height, network)
1372 .estimate_height_at(now);
1373
1374 let height =
1378 if tip_block_time > now || height < tip_height || debug_force_finished_sync {
1379 tip_height
1380 } else {
1381 height
1382 };
1383
1384 (height, f64::from(tip_height.0) / f64::from(height.0))
1385 })
1386 .unwrap_or((Height::MIN, 0.0));
1388
1389 let verification_progress = if network.is_regtest() {
1390 1.0
1391 } else {
1392 verification_progress
1393 };
1394
1395 let mut upgrades = IndexMap::new();
1399 for (activation_height, network_upgrade) in network.full_activation_list() {
1400 if let Some(branch_id) = network_upgrade.branch_id() {
1405 let status = if tip_height >= activation_height {
1407 NetworkUpgradeStatus::Active
1408 } else {
1409 NetworkUpgradeStatus::Pending
1410 };
1411
1412 let upgrade = NetworkUpgradeInfo {
1413 name: network_upgrade,
1414 activation_height,
1415 status,
1416 };
1417 upgrades.insert(ConsensusBranchIdHex(branch_id), upgrade);
1418 }
1419 }
1420
1421 let next_block_height =
1423 (tip_height + 1).expect("valid chain tips are a lot less than Height::MAX");
1424 let consensus = TipConsensusBranch {
1425 chain_tip: ConsensusBranchIdHex(
1426 NetworkUpgrade::current(network, tip_height)
1427 .branch_id()
1428 .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1429 ),
1430 next_block: ConsensusBranchIdHex(
1431 NetworkUpgrade::current(network, next_block_height)
1432 .branch_id()
1433 .unwrap_or(ConsensusBranchId::RPC_MISSING_ID),
1434 ),
1435 };
1436
1437 let response = GetBlockchainInfoResponse {
1438 chain: network.bip70_network_name(),
1439 blocks: tip_height,
1440 best_block_hash: tip_hash,
1441 estimated_height,
1442 chain_supply: GetBlockchainInfoBalance::chain_supply(value_balance),
1443 value_pools: GetBlockchainInfoBalance::value_pools(value_balance, None),
1444 upgrades,
1445 consensus,
1446 headers: tip_height,
1447 difficulty,
1448 verification_progress,
1449 chain_work: 0,
1451 pruned: false,
1452 size_on_disk,
1453 commitments: 0,
1455 };
1456
1457 Ok(response)
1458 }
1459
1460 async fn get_address_balance(
1461 &self,
1462 address_strings: GetAddressBalanceRequest,
1463 ) -> Result<GetAddressBalanceResponse> {
1464 let valid_addresses = address_strings.valid_addresses()?;
1465
1466 let request = zebra_state::ReadRequest::AddressBalance(valid_addresses);
1467 let response = self
1468 .read_state
1469 .clone()
1470 .oneshot(request)
1471 .await
1472 .map_misc_error()?;
1473
1474 match response {
1475 zebra_state::ReadResponse::AddressBalance { balance, received } => {
1476 Ok(GetAddressBalanceResponse {
1477 balance: u64::from(balance),
1478 received,
1479 })
1480 }
1481 _ => unreachable!("Unexpected response from state service: {response:?}"),
1482 }
1483 }
1484
1485 async fn send_raw_transaction(
1487 &self,
1488 raw_transaction_hex: String,
1489 _allow_high_fees: Option<bool>,
1490 ) -> Result<SendRawTransactionResponse> {
1491 let mempool = self.mempool.clone();
1492 let queue_sender = self.queue_sender.clone();
1493
1494 let raw_transaction_bytes = Vec::from_hex(raw_transaction_hex)
1497 .map_error(server::error::LegacyCode::Deserialization)?;
1498 let raw_transaction = Transaction::zcash_deserialize(&*raw_transaction_bytes)
1499 .map_error(server::error::LegacyCode::Deserialization)?;
1500
1501 let transaction_hash = raw_transaction.hash();
1502
1503 let raw_transaction = Arc::new(raw_transaction);
1505 let unmined_transaction = UnminedTx::from(raw_transaction.clone());
1506 let _ = queue_sender.send(unmined_transaction);
1507
1508 let transaction_parameter = mempool::Gossip::Tx(raw_transaction.into());
1509 let request = mempool::Request::Queue(vec![transaction_parameter]);
1510
1511 let response = mempool.oneshot(request).await.map_misc_error()?;
1512
1513 let mut queue_results = match response {
1514 mempool::Response::Queued(results) => results,
1515 _ => unreachable!("incorrect response variant from mempool service"),
1516 };
1517
1518 assert_eq!(
1519 queue_results.len(),
1520 1,
1521 "mempool service returned more results than expected"
1522 );
1523
1524 let queue_result = queue_results
1525 .pop()
1526 .expect("there should be exactly one item in Vec")
1527 .inspect_err(|err| tracing::debug!("sent transaction to mempool: {:?}", &err))
1528 .map_misc_error()?
1529 .await
1530 .map_misc_error()?;
1531
1532 tracing::debug!("sent transaction to mempool: {:?}", &queue_result);
1533
1534 queue_result
1535 .map(|_| SendRawTransactionResponse(transaction_hash))
1536 .map_error(server::error::LegacyCode::Verify)
1543 }
1544
1545 async fn get_block(
1550 &self,
1551 hash_or_height: String,
1552 verbosity: Option<u8>,
1553 ) -> Result<GetBlockResponse> {
1554 let verbosity = verbosity.unwrap_or(1);
1555 let network = self.network.clone();
1556
1557 let hash_or_height =
1558 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1559 .map_error(server::error::LegacyCode::InvalidParameter)?;
1562
1563 if verbosity == 0 {
1564 let request = zebra_state::ReadRequest::Block(hash_or_height);
1565 let response = self
1566 .read_state
1567 .clone()
1568 .oneshot(request)
1569 .await
1570 .map_misc_error()?;
1571
1572 match response {
1573 zebra_state::ReadResponse::Block(Some(block)) => {
1574 Ok(GetBlockResponse::Raw(block.into()))
1575 }
1576 zebra_state::ReadResponse::Block(None) => {
1577 Err("Block not found").map_error(server::error::LegacyCode::InvalidParameter)
1578 }
1579 _ => unreachable!("unmatched response to a block request"),
1580 }
1581 } else if matches!(verbosity, 1..=3) {
1582 let get_block_header_result: Result<GetBlockHeaderResponse> = self
1589 .get_block_header(hash_or_height.to_string(), Some(true))
1590 .await;
1591
1592 let GetBlockHeaderResponse::Object(block_header) = get_block_header_result? else {
1593 panic!("must return Object")
1594 };
1595
1596 let BlockHeaderObject {
1597 hash,
1598 confirmations,
1599 height,
1600 version,
1601 merkle_root,
1602 block_commitments,
1603 final_sapling_root,
1604 sapling_tree_size,
1605 time,
1606 nonce,
1607 solution,
1608 bits,
1609 difficulty,
1610 previous_block_hash,
1611 next_block_hash,
1612 } = *block_header;
1613
1614 let hash_or_height = hash.into();
1622 let transactions_request = match verbosity {
1623 1 => zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height),
1624 2 | 3 => zebra_state::ReadRequest::BlockAndSize(hash_or_height),
1625 _other => panic!("get_block_header_fut should be none"),
1626 };
1627
1628 let in_active_chain = confirmations >= 0;
1632
1633 let spent_outputs = if verbosity == 3 {
1640 let response = self
1641 .read_state
1642 .clone()
1643 .oneshot(zebra_state::ReadRequest::SpentOutputs(hash_or_height))
1644 .await
1645 .map_misc_error()?;
1646 let zebra_state::ReadResponse::SpentOutputs(spent_outputs) = response else {
1647 unreachable!("unmatched response to a SpentOutputs request");
1648 };
1649 spent_outputs.unwrap_or_default()
1650 } else {
1651 HashMap::new()
1652 };
1653
1654 let requests = vec![
1655 transactions_request,
1663 zebra_state::ReadRequest::OrchardTree(hash_or_height),
1665 zebra_state::ReadRequest::IronwoodTree(hash_or_height),
1667 zebra_state::ReadRequest::BlockInfo(previous_block_hash.into()),
1669 zebra_state::ReadRequest::BlockInfo(hash_or_height),
1670 ];
1671
1672 let mut futs = FuturesOrdered::new();
1673
1674 for request in requests {
1675 futs.push_back(self.read_state.clone().oneshot(request));
1676 }
1677
1678 let tx_ids_response = futs.next().await.expect("`futs` should not be empty");
1679 let (tx, size): (Vec<_>, Option<usize>) = match tx_ids_response.map_misc_error()? {
1680 zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => (
1681 tx_ids
1682 .ok_or_misc_error("block not found")?
1683 .iter()
1684 .map(|tx_id| GetBlockTransaction::Hash(*tx_id))
1685 .collect(),
1686 None,
1687 ),
1688 zebra_state::ReadResponse::BlockAndSize(block_and_size) => {
1689 let (block, size) = block_and_size.ok_or_misc_error("Block not found")?;
1690 let block_time = block.header.time;
1691
1692 let mut transactions = Vec::with_capacity(block.transactions.len());
1693 for tx in block.transactions.iter() {
1694 let mut object = TransactionObject::from_transaction(
1695 tx.clone(),
1696 Some(height),
1697 Some(confirmations),
1698 &network,
1699 Some(block_time),
1700 Some(hash),
1701 Some(in_active_chain),
1702 tx.hash(),
1703 );
1704
1705 if verbosity == 3 {
1706 object.add_prevouts(tx, &spent_outputs, &network);
1707 }
1708
1709 transactions.push(GetBlockTransaction::Object(Box::new(object)));
1710 }
1711 (transactions, Some(size))
1712 }
1713 _ => unreachable!("unmatched response to a transaction_ids_for_block request"),
1714 };
1715
1716 let orchard_tree_response = futs.next().await.expect("`futs` should not be empty");
1717 let zebra_state::ReadResponse::OrchardTree(orchard_tree) =
1718 orchard_tree_response.map_misc_error()?
1719 else {
1720 unreachable!("unmatched response to a OrchardTree request");
1721 };
1722
1723 let nu5_activation = NetworkUpgrade::Nu5.activation_height(&network);
1724
1725 let orchard_tree = orchard_tree.ok_or_misc_error("missing Orchard tree")?;
1727
1728 let final_orchard_root = match nu5_activation {
1729 Some(activation_height) if height >= activation_height => {
1730 Some(orchard_tree.root().into())
1731 }
1732 _other => None,
1733 };
1734
1735 let sapling = SaplingTrees {
1736 size: sapling_tree_size,
1737 };
1738
1739 let orchard_tree_size = orchard_tree.count();
1740 let orchard = OrchardTrees {
1741 size: orchard_tree_size,
1742 };
1743
1744 let ironwood_tree_response = futs.next().await.expect("`futs` should not be empty");
1745 let zebra_state::ReadResponse::IronwoodTree(ironwood_tree) =
1746 ironwood_tree_response.map_misc_error()?
1747 else {
1748 unreachable!("unmatched response to an IronwoodTree request");
1749 };
1750
1751 let ironwood_tree = ironwood_tree.ok_or_misc_error("missing Ironwood tree")?;
1754 let ironwood = IronwoodTrees {
1755 size: ironwood_tree.count(),
1756 };
1757
1758 let trees = GetBlockTrees {
1759 sapling,
1760 orchard,
1761 ironwood,
1762 };
1763
1764 let block_info_response = futs.next().await.expect("`futs` should not be empty");
1765 let zebra_state::ReadResponse::BlockInfo(prev_block_info) =
1766 block_info_response.map_misc_error()?
1767 else {
1768 unreachable!("unmatched response to a BlockInfo request");
1769 };
1770 let block_info_response = futs.next().await.expect("`futs` should not be empty");
1771 let zebra_state::ReadResponse::BlockInfo(block_info) =
1772 block_info_response.map_misc_error()?
1773 else {
1774 unreachable!("unmatched response to a BlockInfo request");
1775 };
1776
1777 let delta = block_info.as_ref().and_then(|d| {
1778 let value_pools = d.value_pools().constrain::<NegativeAllowed>().ok()?;
1779 let prev_value_pools = prev_block_info
1780 .map(|d| d.value_pools().constrain::<NegativeAllowed>())
1781 .unwrap_or(Ok(ValueBalance::<NegativeAllowed>::zero()))
1782 .ok()?;
1783 (value_pools - prev_value_pools).ok()
1784 });
1785 let size = size.or(block_info.as_ref().map(|d| d.size() as usize));
1786
1787 Ok(GetBlockResponse::Object(Box::new(BlockObject {
1788 hash,
1789 confirmations,
1790 height: Some(height),
1791 version: Some(version),
1792 merkle_root: Some(merkle_root),
1793 time: Some(time),
1794 nonce: Some(nonce),
1795 solution: Some(solution),
1796 bits: Some(bits),
1797 difficulty: Some(difficulty),
1798 n_tx: tx.len(),
1799 tx,
1800 trees,
1801 chain_supply: block_info
1802 .as_ref()
1803 .map(|d| GetBlockchainInfoBalance::chain_supply(*d.value_pools())),
1804 value_pools: block_info
1805 .map(|d| GetBlockchainInfoBalance::value_pools(*d.value_pools(), delta)),
1806 size: size.map(|size| size as i64),
1807 block_commitments: Some(block_commitments),
1808 final_sapling_root: Some(final_sapling_root),
1809 final_orchard_root,
1810 previous_block_hash: Some(previous_block_hash),
1811 next_block_hash,
1812 })))
1813 } else {
1814 Err("invalid verbosity value").map_error(server::error::LegacyCode::InvalidParameter)
1815 }
1816 }
1817
1818 async fn get_block_header(
1819 &self,
1820 hash_or_height: String,
1821 verbose: Option<bool>,
1822 ) -> Result<GetBlockHeaderResponse> {
1823 let verbose = verbose.unwrap_or(true);
1824 let network = self.network.clone();
1825
1826 let hash_or_height =
1827 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
1828 .map_error(server::error::LegacyCode::InvalidParameter)?;
1831 let zebra_state::ReadResponse::BlockHeader {
1832 header,
1833 hash,
1834 height,
1835 next_block_hash,
1836 } = self
1837 .read_state
1838 .clone()
1839 .oneshot(zebra_state::ReadRequest::BlockHeader(hash_or_height))
1840 .await
1841 .map_err(|_| "block height not in best chain")
1842 .map_error(
1843 if hash_or_height.hash().is_some() {
1848 server::error::LegacyCode::InvalidAddressOrKey
1849 } else {
1850 server::error::LegacyCode::InvalidParameter
1851 },
1852 )?
1853 else {
1854 panic!("unexpected response to BlockHeader request")
1855 };
1856
1857 let response = if !verbose {
1858 GetBlockHeaderResponse::Raw(HexData(header.zcash_serialize_to_vec().map_misc_error()?))
1859 } else {
1860 let zebra_state::ReadResponse::SaplingTree(sapling_tree) = self
1866 .read_state
1867 .clone()
1868 .oneshot(zebra_state::ReadRequest::SaplingTree(hash.into()))
1869 .await
1870 .map_misc_error()?
1871 else {
1872 panic!("unexpected response to SaplingTree request")
1873 };
1874
1875 let sapling_tree = sapling_tree.ok_or_misc_error("missing Sapling tree")?;
1877
1878 let zebra_state::ReadResponse::Depth(depth) = self
1879 .read_state
1880 .clone()
1881 .oneshot(zebra_state::ReadRequest::Depth(hash))
1882 .await
1883 .map_misc_error()?
1884 else {
1885 panic!("unexpected response to SaplingTree request")
1886 };
1887
1888 const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;
1891
1892 let confirmations = depth
1895 .map(|depth| i64::from(depth) + 1)
1896 .unwrap_or(NOT_IN_BEST_CHAIN_CONFIRMATIONS);
1897
1898 let mut nonce = *header.nonce;
1899 nonce.reverse();
1900
1901 let sapling_activation = NetworkUpgrade::Sapling.activation_height(&network);
1902 let sapling_tree_size = sapling_tree.count();
1903 let final_sapling_root: [u8; 32] =
1904 if sapling_activation.is_some() && height >= sapling_activation.unwrap() {
1905 let mut root: [u8; 32] = sapling_tree.root().into();
1906 root.reverse();
1907 root
1908 } else {
1909 [0; 32]
1910 };
1911
1912 let difficulty = header.difficulty_threshold.relative_to_network(&network);
1913
1914 let block_commitments = match header.commitment(&network, height).expect(
1915 "Unexpected failure while parsing the blockcommitments field in get_block_header",
1916 ) {
1917 Commitment::PreSaplingReserved(bytes) => bytes,
1918 Commitment::FinalSaplingRoot(_) => final_sapling_root,
1919 Commitment::ChainHistoryActivationReserved => [0; 32],
1920 Commitment::ChainHistoryRoot(root) => root.bytes_in_display_order(),
1921 Commitment::ChainHistoryBlockTxAuthCommitment(hash) => {
1922 hash.bytes_in_display_order()
1923 }
1924 };
1925
1926 let block_header = BlockHeaderObject {
1927 hash,
1928 confirmations,
1929 height,
1930 version: header.version,
1931 merkle_root: header.merkle_root,
1932 block_commitments,
1933 final_sapling_root,
1934 sapling_tree_size,
1935 time: header.time.timestamp(),
1936 nonce,
1937 solution: header.solution,
1938 bits: header.difficulty_threshold,
1939 difficulty,
1940 previous_block_hash: header.previous_block_hash,
1941 next_block_hash,
1942 };
1943
1944 GetBlockHeaderResponse::Object(Box::new(block_header))
1945 };
1946
1947 Ok(response)
1948 }
1949
1950 fn get_best_block_hash(&self) -> Result<GetBlockHashResponse> {
1951 self.latest_chain_tip
1952 .best_tip_hash()
1953 .map(GetBlockHashResponse)
1954 .ok_or_misc_error("No blocks in state")
1955 }
1956
1957 fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHashResponse> {
1958 self.latest_chain_tip
1959 .best_tip_height_and_hash()
1960 .map(|(height, hash)| GetBlockHeightAndHashResponse { height, hash })
1961 .ok_or_misc_error("No blocks in state")
1962 }
1963
1964 async fn get_mempool_info(&self) -> Result<GetMempoolInfoResponse> {
1965 let mut mempool = self.mempool.clone();
1966
1967 let response = mempool
1968 .ready()
1969 .and_then(|service| service.call(mempool::Request::QueueStats))
1970 .await
1971 .map_misc_error()?;
1972
1973 if let mempool::Response::QueueStats {
1974 size,
1975 bytes,
1976 usage,
1977 fully_notified,
1978 } = response
1979 {
1980 Ok(GetMempoolInfoResponse {
1981 size,
1982 bytes,
1983 usage,
1984 fully_notified,
1985 })
1986 } else {
1987 unreachable!("unexpected response to QueueStats request")
1988 }
1989 }
1990
1991 async fn get_raw_mempool(&self, verbose: Option<bool>) -> Result<GetRawMempoolResponse> {
1992 #[allow(unused)]
1993 let verbose = verbose.unwrap_or(false);
1994
1995 use zebra_chain::block::MAX_BLOCK_BYTES;
1996
1997 let mut mempool = self.mempool.clone();
1998
1999 let request = if verbose {
2000 mempool::Request::FullTransactions
2001 } else {
2002 mempool::Request::TransactionIds
2003 };
2004
2005 let response = mempool
2007 .ready()
2008 .and_then(|service| service.call(request))
2009 .await
2010 .map_misc_error()?;
2011
2012 match response {
2013 mempool::Response::FullTransactions {
2014 mut transactions,
2015 transaction_dependencies,
2016 last_seen_tip_hash: _,
2017 } => {
2018 if verbose {
2019 let transactions_by_id = transactions
2020 .iter()
2021 .map(|unmined_tx| (unmined_tx.transaction.id.mined_id(), unmined_tx))
2022 .collect::<HashMap<_, _>>();
2023 let map = transactions
2024 .iter()
2025 .map(|unmined_tx| {
2026 (
2027 unmined_tx.transaction.id.mined_id().encode_hex(),
2028 get_raw_mempool::MempoolObject::from_verified_unmined_tx(
2029 unmined_tx,
2030 &transactions_by_id,
2031 &transaction_dependencies,
2032 ),
2033 )
2034 })
2035 .collect::<HashMap<_, _>>();
2036 Ok(GetRawMempoolResponse::Verbose(map))
2037 } else {
2038 transactions.sort_by_cached_key(|tx| {
2043 cmp::Reverse((
2046 i64::from(tx.miner_fee) as u128 * MAX_BLOCK_BYTES as u128
2047 / tx.transaction.size as u128,
2048 tx.transaction.id.mined_id(),
2050 ))
2051 });
2052 let tx_ids: Vec<String> = transactions
2053 .iter()
2054 .map(|unmined_tx| unmined_tx.transaction.id.mined_id().encode_hex())
2055 .collect();
2056
2057 Ok(GetRawMempoolResponse::TxIds(tx_ids))
2058 }
2059 }
2060
2061 mempool::Response::TransactionIds(unmined_transaction_ids) => {
2062 let mut tx_ids: Vec<String> = unmined_transaction_ids
2063 .iter()
2064 .map(|id| id.mined_id().encode_hex())
2065 .collect();
2066
2067 tx_ids.sort();
2069
2070 Ok(GetRawMempoolResponse::TxIds(tx_ids))
2071 }
2072
2073 _ => unreachable!("unmatched response to a transactionids request"),
2074 }
2075 }
2076
2077 async fn get_raw_transaction(
2078 &self,
2079 txid: String,
2080 verbose: Option<u8>,
2081 block_hash: Option<String>,
2082 ) -> Result<GetRawTransactionResponse> {
2083 let mut mempool = self.mempool.clone();
2084 let verbose = verbose.unwrap_or(0) != 0;
2085
2086 let txid = transaction::Hash::from_hex(txid)
2089 .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
2090
2091 if block_hash.is_none() {
2093 match mempool
2094 .ready()
2095 .and_then(|service| {
2096 service.call(mempool::Request::TransactionsByMinedId([txid].into()))
2097 })
2098 .await
2099 .map_misc_error()?
2100 {
2101 mempool::Response::Transactions(txns) => {
2102 if let Some(tx) = txns.first() {
2103 return Ok(if verbose {
2104 GetRawTransactionResponse::Object(Box::new(
2105 TransactionObject::from_transaction(
2106 tx.transaction.clone(),
2107 None,
2108 None,
2109 &self.network,
2110 None,
2111 None,
2112 Some(false),
2113 txid,
2114 ),
2115 ))
2116 } else {
2117 let hex = tx.transaction.clone().into();
2118 GetRawTransactionResponse::Raw(hex)
2119 });
2120 }
2121 }
2122
2123 _ => unreachable!("unmatched response to a `TransactionsByMinedId` request"),
2124 };
2125 }
2126
2127 let caller_block_context = if let Some(block_hash) = block_hash {
2128 let block_hash = block::Hash::from_hex(block_hash)
2129 .map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
2130 match self
2131 .read_state
2132 .clone()
2133 .oneshot(zebra_state::ReadRequest::AnyChainTransactionIdsForBlock(
2134 block_hash.into(),
2135 ))
2136 .await
2137 .map_misc_error()?
2138 {
2139 zebra_state::ReadResponse::AnyChainTransactionIdsForBlock(tx_ids) => {
2140 let (ids, in_best_chain) = tx_ids.ok_or_error(
2141 server::error::LegacyCode::InvalidAddressOrKey,
2142 "block not found",
2143 )?;
2144
2145 ids.iter().find(|id| **id == txid).ok_or_error(
2146 server::error::LegacyCode::InvalidAddressOrKey,
2147 "txid not found",
2148 )?;
2149
2150 Some((block_hash, in_best_chain))
2151 }
2152 _ => {
2153 unreachable!("unmatched response to a `AnyChainTransactionIdsForBlock` request")
2154 }
2155 }
2156 } else {
2157 None
2158 };
2159
2160 match self
2162 .read_state
2163 .clone()
2164 .oneshot(zebra_state::ReadRequest::AnyChainTransaction(txid))
2165 .await
2166 .map_misc_error()?
2167 {
2168 zebra_state::ReadResponse::AnyChainTransaction(Some(tx)) => Ok(if verbose {
2169 if let Some((caller_block_hash, in_best_chain)) = caller_block_context {
2170 let (raw_tx, height, confirmations, block_time) = match &tx {
2173 AnyTx::Mined(mined) if in_best_chain => (
2174 mined.tx.clone(),
2175 Some(mined.height),
2176 Some(mined.confirmations.into()),
2177 Some(mined.block_time),
2178 ),
2179 _ => {
2180 let raw_tx: Arc<Transaction> = tx.into();
2181 (raw_tx, None, None, None)
2182 }
2183 };
2184
2185 GetRawTransactionResponse::Object(Box::new(
2186 TransactionObject::from_transaction(
2187 raw_tx,
2188 height,
2189 confirmations,
2190 &self.network,
2191 block_time,
2192 Some(caller_block_hash),
2193 Some(in_best_chain),
2194 txid,
2195 ),
2196 ))
2197 } else {
2198 match tx {
2199 AnyTx::Mined(tx) => {
2200 let block_hash = match self
2201 .read_state
2202 .clone()
2203 .oneshot(zebra_state::ReadRequest::BestChainBlockHash(tx.height))
2204 .await
2205 .map_misc_error()?
2206 {
2207 zebra_state::ReadResponse::BlockHash(block_hash) => block_hash,
2208 _ => {
2209 unreachable!(
2210 "unmatched response to a `BestChainBlockHash` request"
2211 )
2212 }
2213 };
2214
2215 GetRawTransactionResponse::Object(Box::new(
2216 TransactionObject::from_transaction(
2217 tx.tx.clone(),
2218 Some(tx.height),
2219 Some(tx.confirmations.into()),
2220 &self.network,
2221 Some(tx.block_time),
2224 block_hash,
2225 Some(true),
2226 txid,
2227 ),
2228 ))
2229 }
2230 AnyTx::Side((tx, block_hash)) => GetRawTransactionResponse::Object(
2231 Box::new(TransactionObject::from_transaction(
2232 tx.clone(),
2233 None,
2234 None,
2235 &self.network,
2236 None,
2237 Some(block_hash),
2238 Some(false),
2239 txid,
2240 )),
2241 ),
2242 }
2243 }
2244 } else {
2245 let tx: Arc<Transaction> = tx.into();
2246 let hex = tx.into();
2247 GetRawTransactionResponse::Raw(hex)
2248 }),
2249
2250 zebra_state::ReadResponse::AnyChainTransaction(None) => {
2251 Err("Transaction not found in mempool or best chain")
2252 .map_error(server::error::LegacyCode::InvalidAddressOrKey)
2253 }
2254
2255 _ => unreachable!("unmatched response to a `Transaction` read request"),
2256 }
2257 }
2258
2259 async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestateResponse> {
2260 let mut read_state = self.read_state.clone();
2261 let network = self.network.clone();
2262
2263 let hash_or_height =
2264 HashOrHeight::new(&hash_or_height, self.latest_chain_tip.best_tip_height())
2265 .map_error(server::error::LegacyCode::InvalidParameter)?;
2268
2269 let block = match read_state
2279 .ready()
2280 .and_then(|service| service.call(zebra_state::ReadRequest::Block(hash_or_height)))
2281 .await
2282 .map_misc_error()?
2283 {
2284 zebra_state::ReadResponse::Block(Some(block)) => block,
2285 zebra_state::ReadResponse::Block(None) => {
2286 return Err("the requested block is not in the main chain")
2289 .map_error(server::error::LegacyCode::InvalidParameter);
2290 }
2291 _ => unreachable!("unmatched response to a block request"),
2292 };
2293
2294 let hash = hash_or_height
2295 .hash_or_else(|_| Some(block.hash()))
2296 .expect("block hash");
2297
2298 let height = hash_or_height
2299 .height_or_else(|_| block.coinbase_height())
2300 .expect("verified blocks have a coinbase height");
2301
2302 let time = u32::try_from(block.header.time.timestamp())
2303 .expect("Timestamps of valid blocks always fit into u32.");
2304
2305 let sapling = if network.is_nu_active(consensus::NetworkUpgrade::Sapling, height.into()) {
2306 match read_state
2307 .ready()
2308 .and_then(|service| {
2309 service.call(zebra_state::ReadRequest::AnyChainSaplingTree(hash))
2310 })
2311 .await
2312 .map_misc_error()?
2313 {
2314 zebra_state::ReadResponse::SaplingTree(tree) => {
2315 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
2316 }
2317 _ => unreachable!("unmatched response to a Sapling tree request"),
2318 }
2319 } else {
2320 None
2321 };
2322 let (sapling_tree, sapling_root) =
2323 sapling.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
2324
2325 let orchard = if network.is_nu_active(consensus::NetworkUpgrade::Nu5, height.into()) {
2326 match read_state
2327 .ready()
2328 .and_then(|service| {
2329 service.call(zebra_state::ReadRequest::AnyChainOrchardTree(hash))
2330 })
2331 .await
2332 .map_misc_error()?
2333 {
2334 zebra_state::ReadResponse::OrchardTree(tree) => {
2335 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
2336 }
2337 _ => unreachable!("unmatched response to an Orchard tree request"),
2338 }
2339 } else {
2340 None
2341 };
2342 let (orchard_tree, orchard_root) =
2343 orchard.map_or((None, None), |(tree, root)| (Some(tree), Some(root)));
2344
2345 let ironwood = if network.is_nu_active(consensus::NetworkUpgrade::Nu6_3, height.into()) {
2346 match read_state
2347 .ready()
2348 .and_then(|service| {
2349 service.call(zebra_state::ReadRequest::AnyChainIronwoodTree(hash))
2350 })
2351 .await
2352 .map_misc_error()?
2353 {
2354 zebra_state::ReadResponse::IronwoodTree(tree) => {
2355 tree.map(|t| (t.to_rpc_bytes(), t.root().bytes_in_display_order().to_vec()))
2356 }
2357 _ => unreachable!("unmatched response to an Ironwood tree request"),
2358 }
2359 } else {
2360 None
2361 };
2362 let ironwood = ironwood
2364 .map(|(tree, root)| Treestate::new(trees::Commitments::new(Some(root), Some(tree))));
2365
2366 Ok(GetTreestateResponse::new(
2367 hash,
2368 height,
2369 time,
2370 None,
2373 Treestate::new(trees::Commitments::new(sapling_root, sapling_tree)),
2374 Treestate::new(trees::Commitments::new(orchard_root, orchard_tree)),
2375 ironwood,
2376 ))
2377 }
2378
2379 async fn z_get_subtrees_by_index(
2380 &self,
2381 pool: String,
2382 start_index: NoteCommitmentSubtreeIndex,
2383 limit: Option<NoteCommitmentSubtreeIndex>,
2384 ) -> Result<GetSubtreesByIndexResponse> {
2385 let mut read_state = self.read_state.clone();
2386
2387 const POOL_LIST: &[&str] = &["sapling", "orchard", "ironwood"];
2388
2389 if pool == "sapling" {
2390 let request = zebra_state::ReadRequest::SaplingSubtrees { start_index, limit };
2391 let response = read_state
2392 .ready()
2393 .and_then(|service| service.call(request))
2394 .await
2395 .map_misc_error()?;
2396
2397 let subtrees = match response {
2398 zebra_state::ReadResponse::SaplingSubtrees(subtrees) => subtrees,
2399 _ => unreachable!("unmatched response to a subtrees request"),
2400 };
2401
2402 let subtrees = subtrees
2403 .values()
2404 .map(|subtree| SubtreeRpcData {
2405 root: subtree.root.to_bytes().encode_hex(),
2406 end_height: subtree.end_height,
2407 })
2408 .collect();
2409
2410 Ok(GetSubtreesByIndexResponse {
2411 pool,
2412 start_index,
2413 subtrees,
2414 })
2415 } else if pool == "orchard" {
2416 let request = zebra_state::ReadRequest::OrchardSubtrees { start_index, limit };
2417 let response = read_state
2418 .ready()
2419 .and_then(|service| service.call(request))
2420 .await
2421 .map_misc_error()?;
2422
2423 let subtrees = match response {
2424 zebra_state::ReadResponse::OrchardSubtrees(subtrees) => subtrees,
2425 _ => unreachable!("unmatched response to a subtrees request"),
2426 };
2427
2428 let subtrees = subtrees
2429 .values()
2430 .map(|subtree| SubtreeRpcData {
2431 root: subtree.root.encode_hex(),
2432 end_height: subtree.end_height,
2433 })
2434 .collect();
2435
2436 Ok(GetSubtreesByIndexResponse {
2437 pool,
2438 start_index,
2439 subtrees,
2440 })
2441 } else if pool == "ironwood" {
2442 let request = zebra_state::ReadRequest::IronwoodSubtrees { start_index, limit };
2443 let response = read_state
2444 .ready()
2445 .and_then(|service| service.call(request))
2446 .await
2447 .map_misc_error()?;
2448
2449 let subtrees = match response {
2450 zebra_state::ReadResponse::IronwoodSubtrees(subtrees) => subtrees,
2451 _ => unreachable!("unmatched response to a subtrees request"),
2452 };
2453
2454 let subtrees = subtrees
2456 .values()
2457 .map(|subtree| SubtreeRpcData {
2458 root: subtree.root.encode_hex(),
2459 end_height: subtree.end_height,
2460 })
2461 .collect();
2462
2463 Ok(GetSubtreesByIndexResponse {
2464 pool,
2465 start_index,
2466 subtrees,
2467 })
2468 } else {
2469 Err(ErrorObject::owned(
2470 server::error::LegacyCode::Misc.into(),
2471 format!("invalid pool name, must be one of: {POOL_LIST:?}").as_str(),
2472 None::<()>,
2473 ))
2474 }
2475 }
2476
2477 async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>> {
2478 let mut read_state = self.read_state.clone();
2479 let latest_chain_tip = self.latest_chain_tip.clone();
2480
2481 let height_range = build_height_range(
2482 request.start,
2483 request.end,
2484 best_chain_tip_height(&latest_chain_tip)?,
2485 )?;
2486
2487 let valid_addresses = request.valid_addresses()?;
2488
2489 let cap = self.max_address_query_results;
2491 let request = zebra_state::ReadRequest::TransactionIdsByAddresses {
2492 addresses: valid_addresses,
2493 height_range,
2494 max_entries: Some(cap.saturating_add(1)),
2495 };
2496 let response = read_state
2497 .ready()
2498 .and_then(|service| service.call(request))
2499 .await
2500 .map_misc_error()?;
2501
2502 let hashes = match response {
2503 zebra_state::ReadResponse::AddressesTransactionIds(hashes) => {
2504 if hashes.len() > cap {
2505 return Err(format!(
2506 "more than {cap} transactions match; narrow the height range"
2507 ))
2508 .map_error(server::error::LegacyCode::InvalidParameter);
2509 }
2510
2511 let mut last_tx_location = TransactionLocation::from_usize(Height(0), 0);
2512
2513 hashes
2514 .iter()
2515 .map(|(tx_loc, tx_id)| {
2516 assert!(
2518 *tx_loc > last_tx_location,
2519 "Transactions were not in chain order:\n\
2520 {tx_loc:?} {tx_id:?} was after:\n\
2521 {last_tx_location:?}",
2522 );
2523
2524 last_tx_location = *tx_loc;
2525
2526 tx_id.to_string()
2527 })
2528 .collect()
2529 }
2530 _ => unreachable!("unmatched response to a TransactionsByAddresses request"),
2531 };
2532
2533 Ok(hashes)
2534 }
2535
2536 async fn get_address_utxos(
2537 &self,
2538 utxos_request: GetAddressUtxosRequest,
2539 ) -> Result<GetAddressUtxosResponse> {
2540 let mut read_state = self.read_state.clone();
2541 let mut response_utxos = vec![];
2542
2543 let valid_addresses = utxos_request.valid_addresses()?;
2544
2545 let start_height = Height(
2548 u32::try_from(utxos_request.start_height)
2549 .unwrap_or(u32::MAX)
2550 .min(Height::MAX.0),
2551 );
2552 let requested_entries = utxos_request.max_entries as usize;
2554 let cap = self.max_address_query_results;
2555 if requested_entries > cap {
2556 return Err(format!("maxEntries must be at most {cap}"))
2557 .map_error(server::error::LegacyCode::InvalidParameter);
2558 }
2559
2560 let max_entries = if requested_entries > 0 {
2562 requested_entries
2563 } else {
2564 cap.saturating_add(1)
2565 };
2566
2567 let request = zebra_state::ReadRequest::UtxosByAddresses {
2569 addresses: valid_addresses,
2570 height_range: start_height..=Height::MAX,
2571 max_entries: Some(max_entries),
2572 };
2573 let response = read_state
2574 .ready()
2575 .and_then(|service| service.call(request))
2576 .await
2577 .map_misc_error()?;
2578 let utxos = match response {
2579 zebra_state::ReadResponse::AddressUtxos(utxos) => utxos,
2580 _ => unreachable!("unmatched response to a UtxosByAddresses request"),
2581 };
2582
2583 let mut last_output_location = OutputLocation::from_usize(Height(0), 0, 0);
2584
2585 for utxo_data in utxos.utxos() {
2586 let address = utxo_data.0;
2587 let txid = *utxo_data.1;
2588 let height = utxo_data.2.height();
2589 let output_index = utxo_data.2.output_index();
2590 let script = utxo_data.3.lock_script.clone();
2591 let satoshis = u64::from(utxo_data.3.value);
2592
2593 let output_location = *utxo_data.2;
2594 assert!(
2596 output_location > last_output_location,
2597 "UTXOs were not in chain order:\n\
2598 {output_location:?} {address:?} {txid:?} was after:\n\
2599 {last_output_location:?}",
2600 );
2601
2602 let entry = Utxo {
2603 address,
2604 txid,
2605 output_index,
2606 script,
2607 satoshis,
2608 height,
2609 };
2610 response_utxos.push(entry);
2611
2612 last_output_location = output_location;
2613 }
2614
2615 if response_utxos.len() > cap {
2616 return Err(format!(
2617 "more than {cap} unspent outputs match; page with startHeight and maxEntries"
2618 ))
2619 .map_error(server::error::LegacyCode::InvalidParameter);
2620 }
2621
2622 if !utxos_request.chain_info {
2623 Ok(GetAddressUtxosResponse::Utxos(response_utxos))
2624 } else {
2625 let (height, hash) = utxos
2626 .last_height_and_hash()
2627 .ok_or_misc_error("No blocks in state")?;
2628
2629 Ok(GetAddressUtxosResponse::UtxosAndChainInfo(
2630 GetAddressUtxosResponseObject {
2631 utxos: response_utxos,
2632 hash,
2633 height,
2634 },
2635 ))
2636 }
2637 }
2638
2639 fn stop(&self) -> Result<String> {
2640 #[cfg(not(target_os = "windows"))]
2641 if self.network.is_regtest() {
2642 match nix::sys::signal::raise(nix::sys::signal::SIGINT) {
2643 Ok(_) => Ok("Zebra server stopping".to_string()),
2644 Err(error) => Err(ErrorObject::owned(
2645 ErrorCode::InternalError.code(),
2646 format!("Failed to shut down: {error}").as_str(),
2647 None::<()>,
2648 )),
2649 }
2650 } else {
2651 Err(ErrorObject::borrowed(
2652 ErrorCode::MethodNotFound.code(),
2653 "stop is only available on regtest networks",
2654 None,
2655 ))
2656 }
2657 #[cfg(target_os = "windows")]
2658 Err(ErrorObject::borrowed(
2659 ErrorCode::MethodNotFound.code(),
2660 "stop is not available in windows targets",
2661 None,
2662 ))
2663 }
2664
2665 fn get_block_count(&self) -> Result<u32> {
2666 best_chain_tip_height(&self.latest_chain_tip).map(|height| height.0)
2667 }
2668
2669 async fn get_block_hash(&self, index: i32) -> Result<GetBlockHashResponse> {
2670 let mut read_state = self.read_state.clone();
2671 let latest_chain_tip = self.latest_chain_tip.clone();
2672
2673 let tip_height = best_chain_tip_height(&latest_chain_tip)?;
2675
2676 let height = height_from_signed_int(index, tip_height)?;
2677
2678 let request = zebra_state::ReadRequest::BestChainBlockHash(height);
2679 let response = read_state
2680 .ready()
2681 .and_then(|service| service.call(request))
2682 .await
2683 .map_error(server::error::LegacyCode::default())?;
2684
2685 match response {
2686 zebra_state::ReadResponse::BlockHash(Some(hash)) => Ok(GetBlockHashResponse(hash)),
2687 zebra_state::ReadResponse::BlockHash(None) => Err(ErrorObject::borrowed(
2688 server::error::LegacyCode::InvalidParameter.into(),
2689 "Block not found",
2690 None,
2691 )),
2692 _ => unreachable!("unmatched response to a block request"),
2693 }
2694 }
2695
2696 async fn get_block_template(
2697 &self,
2698 parameters: Option<GetBlockTemplateParameters>,
2699 ) -> Result<GetBlockTemplateResponse> {
2700 let long_poll_started = self.template_clock_now();
2701
2702 use types::get_block_template::{
2703 check_parameters, check_synced_to_tip, fetch_chain_info, fetch_mempool_transactions,
2704 nsm_value_balance_for_next_block, validate_block_proposal,
2705 zip317::select_mempool_transactions,
2706 };
2707
2708 let mempool = self.mempool.clone();
2710 let mut latest_chain_tip = self.latest_chain_tip.clone();
2711 let sync_status = self.gbt.sync_status();
2712 let read_state = self.read_state.clone();
2713
2714 if let Some(HexData(block_proposal_bytes)) = parameters
2715 .as_ref()
2716 .and_then(GetBlockTemplateParameters::block_proposal_data)
2717 {
2718 return validate_block_proposal(
2719 self.gbt.block_verifier_router(),
2720 read_state,
2721 block_proposal_bytes,
2722 &self.network,
2723 latest_chain_tip,
2724 sync_status,
2725 )
2726 .await;
2727 }
2728
2729 check_parameters(¶meters)?;
2731
2732 let client_long_poll_id = parameters.as_ref().and_then(|params| params.long_poll_id);
2733
2734 let miner_params = self
2735 .gbt
2736 .miner_params()
2737 .ok_or_error(0, "miner parameters are required for get_block_template")?;
2738
2739 check_synced_to_tip(&self.network, latest_chain_tip.clone(), sync_status.clone())?;
2744
2745 if let Some(template) = self
2746 .precomputed_block_template(client_long_poll_id, long_poll_started)
2747 .await
2748 {
2749 return Ok(template.into());
2750 }
2751
2752 let (server_long_poll_id, chain_info, mempool_txs, mempool_tx_deps, submit_old) = loop {
2756 check_synced_to_tip(&self.network, latest_chain_tip.clone(), sync_status.clone())?;
2762 latest_chain_tip.mark_best_tip_seen();
2770
2771 let chain_info @ zebra_state::GetBlockTemplateChainInfo {
2778 tip_hash,
2779 tip_height,
2780 max_time,
2781 cur_time,
2782 ..
2783 } = fetch_chain_info(read_state.clone()).await?;
2784
2785 let Some((mempool_txs, mempool_tx_deps)) =
2796 fetch_mempool_transactions(mempool.clone(), tip_hash)
2797 .await?
2798 .or_else(|| client_long_poll_id.is_none().then(Default::default))
2802 else {
2803 continue;
2804 };
2805
2806 let server_long_poll_id = LongPollInput::new(
2808 tip_height,
2809 tip_hash,
2810 max_time,
2811 mempool_txs.iter().map(|tx| tx.transaction.id),
2812 )
2813 .generate_id();
2814
2815 let now = self.template_clock_now();
2819 let max_time_reached =
2820 client_long_poll_id.is_some() && long_poll_started <= max_time && now > max_time;
2821
2822 if Some(&server_long_poll_id) != client_long_poll_id.as_ref() || max_time_reached {
2827 let submit_old = client_long_poll_id.as_ref().map(|old_long_poll_id| {
2829 now <= max_time && server_long_poll_id.submit_old(old_long_poll_id)
2830 });
2831
2832 break (
2833 server_long_poll_id,
2834 chain_info,
2835 mempool_txs,
2836 mempool_tx_deps,
2837 submit_old,
2838 );
2839 }
2840
2841 let wait_for_mempool_request =
2851 tokio::time::sleep(Duration::from_secs(MEMPOOL_LONG_POLL_INTERVAL));
2852
2853 let mut wait_for_new_tip = latest_chain_tip.clone();
2856 let wait_for_new_tip = wait_for_new_tip.best_tip_changed();
2857
2858 let duration_until_max_time = max_time.saturating_duration_since(now);
2861 let wait_for_max_time =
2862 tokio::time::sleep(duration_until_max_time.to_std() + Duration::from_secs(1));
2863
2864 tokio::select! {
2870 biased;
2873
2874 _elapsed = wait_for_mempool_request => {
2876 tracing::debug!(
2877 ?max_time,
2878 ?cur_time,
2879 ?server_long_poll_id,
2880 ?client_long_poll_id,
2881 MEMPOOL_LONG_POLL_INTERVAL,
2882 "checking for a new mempool change after waiting a few seconds"
2883 );
2884 }
2885
2886 _ = wait_for_new_tip => {
2887 if let Some(template) = self
2891 .precomputed_block_template(client_long_poll_id, long_poll_started)
2892 .await
2893 {
2894 return Ok(template.into());
2895 }
2896 }
2897
2898 () = wait_for_max_time, if long_poll_started <= max_time => {
2901 tracing::info!(
2903 ?max_time,
2904 ?cur_time,
2905 ?server_long_poll_id,
2906 ?client_long_poll_id,
2907 "rechecking long poll after the max time deadline"
2908 );
2909
2910 }
2912 }
2913 };
2914
2915 tracing::debug!(
2922 mempool_tx_hashes = ?mempool_txs
2923 .iter()
2924 .map(|tx| tx.transaction.id.mined_id())
2925 .collect::<Vec<_>>(),
2926 "selecting transactions for the template from the mempool"
2927 );
2928
2929 let height = chain_info.tip_height.next().map_misc_error()?;
2930
2931 let coinbase_cache = self.gbt.coinbase_cache();
2933 let parent_nsm_value_balance = nsm_value_balance_for_next_block(&self.network, &chain_info);
2934
2935 let mempool_txs = select_mempool_transactions(
2936 &self.network,
2937 height,
2938 miner_params,
2939 mempool_txs,
2940 mempool_tx_deps,
2941 Some(&coinbase_cache),
2942 parent_nsm_value_balance,
2943 );
2944
2945 tracing::debug!(
2946 selected_mempool_tx_hashes = ?mempool_txs
2947 .iter()
2948 .map(|#[cfg(not(test))] tx, #[cfg(test)] (_, tx)| tx.transaction.id.mined_id())
2949 .collect::<Vec<_>>(),
2950 "selected transactions for the template from the mempool"
2951 );
2952
2953 let template = BlockTemplateResponse::new_internal(
2956 &self.network,
2957 &coinbase_cache,
2958 miner_params,
2959 &chain_info,
2960 server_long_poll_id,
2961 mempool_txs,
2962 submit_old,
2963 );
2964
2965 (template.max_time
2968 <= self
2969 .template_clock_now()
2970 .saturating_add(Duration32::from_hours(2)))
2971 .then(|| template.into())
2972 .ok_or_misc_error("local clock moved backwards while building the block template")
2973 }
2974
2975 async fn submit_block(
2976 &self,
2977 HexData(block_bytes): HexData,
2978 _parameters: Option<SubmitBlockParameters>,
2979 ) -> Result<SubmitBlockResponse> {
2980 let mut block_verifier_router = self.gbt.block_verifier_router();
2981
2982 let block: Block = match block_bytes.zcash_deserialize_into() {
2983 Ok(block_bytes) => block_bytes,
2984 Err(error) => {
2985 tracing::info!(
2986 ?error,
2987 "submit block failed: block bytes could not be deserialized into a structurally valid block"
2988 );
2989
2990 return Ok(SubmitBlockErrorResponse::Rejected.into());
2991 }
2992 };
2993
2994 let height = block
2995 .coinbase_height()
2996 .ok_or_error(0, "coinbase height not found")?;
2997 let block_hash = block.hash();
2998
2999 let block_verifier_router_response = block_verifier_router
3000 .ready()
3001 .await
3002 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
3003 .call(zebra_consensus::Request::Commit(Arc::new(block)))
3004 .await;
3005
3006 let chain_error = match block_verifier_router_response {
3007 Ok(hash) => {
3014 tracing::info!(?hash, ?height, "submit block accepted");
3015
3016 self.gbt
3017 .advertise_mined_block(hash, height)
3018 .map_error_with_prefix(0, "failed to send mined block to gossip task")?;
3019
3020 return Ok(SubmitBlockResponse::Accepted);
3021 }
3022
3023 Err(box_error) => {
3026 let error = box_error
3027 .downcast::<RouterError>()
3028 .map(|boxed_chain_error| *boxed_chain_error);
3029
3030 tracing::info!(
3031 ?error,
3032 ?block_hash,
3033 ?height,
3034 "submit block failed verification"
3035 );
3036
3037 error
3038 }
3039 };
3040
3041 let response = match chain_error {
3042 Ok(source) if source.is_duplicate_request() => SubmitBlockErrorResponse::Duplicate,
3043
3044 Ok(_verify_chain_error) => SubmitBlockErrorResponse::Rejected,
3060
3061 Err(_unknown_error_type) => SubmitBlockErrorResponse::Rejected,
3064 };
3065
3066 Ok(response.into())
3067 }
3068
3069 async fn get_mining_info(&self) -> Result<GetMiningInfoResponse> {
3070 let network = self.network.clone();
3071 let mut read_state = self.read_state.clone();
3072
3073 let chain_tip = self.latest_chain_tip.clone();
3074 let tip_height = chain_tip.best_tip_height().unwrap_or(Height(0)).0;
3075
3076 let mut current_block_tx = None;
3077 if tip_height > 0 {
3078 let mined_tx_ids = chain_tip.best_tip_mined_transaction_ids();
3079 current_block_tx =
3080 (!mined_tx_ids.is_empty()).then(|| mined_tx_ids.len().saturating_sub(1));
3081 }
3082
3083 let solution_rate_fut = self.get_network_sol_ps(None, None);
3084 let mut current_block_size = None;
3086 if tip_height > 0 {
3087 let request = zebra_state::ReadRequest::TipBlockSize;
3088 let response: zebra_state::ReadResponse = read_state
3089 .ready()
3090 .and_then(|service| service.call(request))
3091 .await
3092 .map_error(server::error::LegacyCode::default())?;
3093 current_block_size = match response {
3094 zebra_state::ReadResponse::TipBlockSize(Some(block_size)) => Some(block_size),
3095 _ => None,
3096 };
3097 }
3098
3099 Ok(GetMiningInfoResponse::new_internal(
3100 tip_height,
3101 current_block_size,
3102 current_block_tx,
3103 network,
3104 solution_rate_fut.await?,
3105 ))
3106 }
3107
3108 async fn get_network_sol_ps(
3109 &self,
3110 num_blocks: Option<i32>,
3111 height: Option<i32>,
3112 ) -> Result<u64> {
3113 let num_blocks = num_blocks.unwrap_or(DEFAULT_SOLUTION_RATE_WINDOW_SIZE);
3115 let num_blocks = (num_blocks > 0)
3118 .then(|| usize::try_from(num_blocks).expect("positive i32 fits in usize"));
3119 let height = height.and_then(|height| height.try_into_height().ok());
3120
3121 let mut read_state = self.read_state.clone();
3122
3123 let request = ReadRequest::SolutionRate { num_blocks, height };
3124
3125 let response = read_state
3126 .ready()
3127 .and_then(|service| service.call(request))
3128 .await
3129 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
3130
3131 let solution_rate = match response {
3132 ReadResponse::SolutionRate(solution_rate) => solution_rate.unwrap_or(0),
3134
3135 _ => unreachable!("unmatched response to a solution rate request"),
3136 };
3137
3138 solution_rate.try_into().map_misc_error()
3139 }
3140
3141 async fn get_network_info(&self) -> Result<GetNetworkInfoResponse> {
3142 let version = GetInfoResponse::version_from_string(&self.build_version)
3143 .expect("invalid version string");
3144
3145 let subversion = self.user_agent.clone();
3146
3147 let protocol_version = zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0;
3148
3149 let local_services = format!("{:016x}", PeerServices::NODE_NETWORK);
3151
3152 let timeoffset = 0;
3154
3155 let connections = self.address_book.recently_live_peers(Utc::now()).len();
3156
3157 let networks = vec![
3159 NetworkInfo::new("ipv4".to_string(), false, true, "".to_string(), false),
3160 NetworkInfo::new("ipv6".to_string(), false, true, "".to_string(), false),
3161 NetworkInfo::new("onion".to_string(), false, false, "".to_string(), false),
3162 ];
3163
3164 let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
3165 / (zebra_chain::amount::COIN as f64);
3166
3167 let local_addresses = vec![];
3169
3170 let warnings = "".to_string();
3172
3173 let response = GetNetworkInfoResponse {
3174 version,
3175 subversion,
3176 protocol_version,
3177 local_services,
3178 timeoffset,
3179 connections,
3180 networks,
3181 relay_fee,
3182 local_addresses,
3183 warnings,
3184 };
3185
3186 Ok(response)
3187 }
3188
3189 async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
3190 let address_book = self.address_book.clone();
3191 Ok(address_book
3192 .recently_live_peers(chrono::Utc::now())
3193 .into_iter()
3194 .map(|meta_addr| {
3195 let banscore = address_book.misbehavior_score(meta_addr.addr());
3196 PeerInfo::from_meta_addr(meta_addr, banscore)
3197 })
3198 .collect())
3199 }
3200
3201 async fn ping(&self) -> Result<()> {
3202 tracing::debug!("Receiving ping request via RPC");
3203
3204 Ok(())
3208 }
3209
3210 async fn validate_address(&self, raw_address: String) -> Result<ValidateAddressResponse> {
3211 let network = self.network.clone();
3212
3213 validate_address(network, raw_address)
3214 }
3215
3216 async fn z_validate_address(&self, raw_address: String) -> Result<ZValidateAddressResponse> {
3217 let network = self.network.clone();
3218
3219 z_validate_address(network, raw_address)
3220 }
3221
3222 async fn get_standard_fee(&self) -> Result<GetStandardFeeResponse> {
3223 const VERSION: u32 = 0;
3224
3225 let next_block_height = match self.latest_chain_tip.best_tip_height() {
3228 Some(tip_height) => tip_height.next().map_misc_error()?,
3229 None => Height::MIN,
3230 };
3231
3232 Ok(GetStandardFeeResponse::new(
3233 standard_fee(&self.network, next_block_height),
3234 VERSION,
3235 ))
3236 }
3237
3238 async fn get_block_subsidy(&self, height: Option<u32>) -> Result<GetBlockSubsidyResponse> {
3239 let net = self.network.clone();
3240
3241 let height = match height {
3242 Some(h) => {
3243 Height::try_from(h).map_error(server::error::LegacyCode::InvalidParameter)?
3244 }
3245 None => best_chain_tip_height(&self.latest_chain_tip)?,
3246 };
3247
3248 let subsidy = if zebra_chain::parameters::subsidy::nsm_reissuance_is_active(height, &net) {
3252 let parent_height = (height - 1).ok_or_misc_error("height 0 has no parent block")?;
3253
3254 let zebra_state::ReadResponse::BlockInfo(parent_block_info) = self
3255 .read_state
3256 .clone()
3257 .oneshot(zebra_state::ReadRequest::BlockInfo(parent_height.into()))
3258 .await
3259 .map_misc_error()?
3260 else {
3261 unreachable!("unmatched response to a BlockInfo request")
3262 };
3263
3264 let parent_chain_value_pools = *parent_block_info
3265 .ok_or_misc_error(
3266 "the block subsidy is only known up to the block after the best chain tip \
3267 from the NSM reissuance height",
3268 )?
3269 .value_pools();
3270
3271 zebra_chain::parameters::subsidy::block_subsidy_with_parent_pools(
3272 height,
3273 &net,
3274 parent_chain_value_pools,
3275 )
3276 .map_misc_error()?
3277 } else {
3278 block_subsidy(height, &net).map_misc_error()?
3279 };
3280
3281 let (lockbox_streams, mut funding_streams): (Vec<_>, Vec<_>) =
3282 funding_stream_values(height, &net, subsidy)
3283 .map_misc_error()?
3284 .into_iter()
3285 .partition(|(receiver, _)| matches!(receiver, FundingStreamReceiver::Deferred));
3287
3288 let [lockbox_total, funding_streams_total] =
3289 [&lockbox_streams, &funding_streams].map(|streams| {
3290 streams
3291 .iter()
3292 .map(|&(_, amount)| amount)
3293 .sum::<std::result::Result<Amount<_>, _>>()
3294 .map(Zec::from)
3295 .map_misc_error()
3296 });
3297
3298 funding_streams.sort_by_key(|(receiver, _funding_stream)| {
3300 ZCASHD_FUNDING_STREAM_ORDER
3301 .iter()
3302 .position(|zcashd_receiver| zcashd_receiver == receiver)
3303 });
3304
3305 let is_post_nu6 = NetworkUpgrade::current(&net, height) >= NetworkUpgrade::Nu6;
3307
3308 let [funding_streams, lockbox_streams] =
3310 [funding_streams, lockbox_streams].map(|streams| {
3311 streams
3312 .into_iter()
3313 .map(|(receiver, value)| {
3314 let address = funding_stream_address(height, &net, receiver);
3315 types::subsidy::FundingStream::new_internal(
3316 is_post_nu6,
3317 receiver,
3318 value,
3319 address,
3320 )
3321 })
3322 .collect()
3323 });
3324
3325 Ok(GetBlockSubsidyResponse {
3326 miner: miner_subsidy(height, &net, subsidy)
3327 .map_misc_error()?
3328 .into(),
3329 founders: founders_reward(&net, height).into(),
3330 funding_streams,
3331 lockbox_streams,
3332 funding_streams_total: funding_streams_total?,
3333 lockbox_total: lockbox_total?,
3334 total_block_subsidy: subsidy.into(),
3335 })
3336 }
3337
3338 async fn get_difficulty(&self) -> Result<f64> {
3339 chain_tip_difficulty(self.network.clone(), self.read_state.clone(), false).await
3340 }
3341
3342 async fn z_list_unified_receivers(
3343 &self,
3344 address: String,
3345 ) -> Result<ZListUnifiedReceiversResponse> {
3346 use zcash_address::unified::Container;
3347
3348 let (network, .., unified_address) = zcash_address::unified::Address::decode(&address)
3349 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
3350
3351 let mut p2pkh = None;
3352 let mut p2sh = None;
3353 let mut orchard = None;
3354 let mut sapling = None;
3355
3356 for item in unified_address.items() {
3357 match item {
3358 zcash_address::unified::Receiver::Orchard(data) => {
3359 Option::<orchard::Address>::from(orchard::Address::from_raw_address_bytes(
3360 &data,
3361 ))
3362 .ok_or("Unified Address contains an invalid Orchard receiver")
3363 .map_error(server::error::LegacyCode::InvalidParameter)?;
3364
3365 let addr = zcash_address::unified::Address::try_from_items(
3368 zcash_address::unified::Revision::R0,
3369 vec![zcash_address::unified::Uitem::Data(
3370 zcash_address::unified::Receiver::Orchard(data),
3371 )],
3372 )
3373 .expect("a single Orchard receiver is a valid Revision 0 unified address");
3374 orchard = Some(addr.encode(&network));
3375 }
3376 zcash_address::unified::Receiver::Sapling(data) => {
3377 let addr = zebra_chain::primitives::Address::try_from_sapling(network, data)
3378 .map_error(server::error::LegacyCode::InvalidParameter)?;
3379 sapling = Some(addr.payment_address().unwrap_or_default());
3380 }
3381 zcash_address::unified::Receiver::P2pkh(data) => {
3382 let addr =
3383 zebra_chain::primitives::Address::try_from_transparent_p2pkh(network, data)
3384 .expect("using data already decoded as valid");
3385 p2pkh = Some(addr.payment_address().unwrap_or_default());
3386 }
3387 zcash_address::unified::Receiver::P2sh(data) => {
3388 let addr =
3389 zebra_chain::primitives::Address::try_from_transparent_p2sh(network, data)
3390 .expect("using data already decoded as valid");
3391 p2sh = Some(addr.payment_address().unwrap_or_default());
3392 }
3393 _ => (),
3394 }
3395 }
3396
3397 Ok(ZListUnifiedReceiversResponse::new(
3398 orchard, sapling, p2pkh, p2sh,
3399 ))
3400 }
3401
3402 async fn invalidate_block(&self, block_hash: String) -> Result<()> {
3403 let block_hash = block_hash
3404 .parse()
3405 .map_error(server::error::LegacyCode::InvalidParameter)?;
3406
3407 self.state
3408 .clone()
3409 .oneshot(zebra_state::Request::InvalidateBlock(block_hash))
3410 .await
3411 .map(|rsp| assert_eq!(rsp, zebra_state::Response::Invalidated(block_hash)))
3412 .map_misc_error()
3413 }
3414
3415 async fn reconsider_block(&self, block_hash: String) -> Result<Vec<block::Hash>> {
3416 let block_hash = block_hash
3417 .parse()
3418 .map_error(server::error::LegacyCode::InvalidParameter)?;
3419
3420 self.state
3421 .clone()
3422 .oneshot(zebra_state::Request::ReconsiderBlock(block_hash))
3423 .await
3424 .map(|rsp| match rsp {
3425 zebra_state::Response::Reconsidered(block_hashes) => block_hashes,
3426 _ => unreachable!("unmatched response to a reconsider block request"),
3427 })
3428 .map_misc_error()
3429 }
3430
3431 async fn generate(&self, num_blocks: u32) -> Result<Vec<Hash>> {
3432 let mut rpc = self.clone();
3433 let network = self.network.clone();
3434
3435 if !network.disable_pow() {
3436 return Err(ErrorObject::borrowed(
3437 0,
3438 "generate is only supported on networks where PoW is disabled",
3439 None,
3440 ));
3441 }
3442
3443 let mut block_hashes = Vec::new();
3444 for _ in 0..num_blocks {
3445 rpc.gbt.randomize_coinbase_data();
3449
3450 let block_template = rpc
3451 .get_block_template(None)
3452 .await
3453 .map_error(server::error::LegacyCode::default())?;
3454
3455 let GetBlockTemplateResponse::TemplateMode(block_template) = block_template else {
3456 return Err(ErrorObject::borrowed(
3457 0,
3458 "error generating block template",
3459 None,
3460 ));
3461 };
3462
3463 let proposal_block = proposal_block_from_template(
3464 &block_template,
3465 BlockTemplateTimeSource::CurTime,
3466 &network,
3467 )
3468 .map_error(server::error::LegacyCode::default())?;
3469
3470 let hex_proposal_block = HexData(
3471 proposal_block
3472 .zcash_serialize_to_vec()
3473 .map_error(server::error::LegacyCode::default())?,
3474 );
3475
3476 let r = rpc
3477 .submit_block(hex_proposal_block, None)
3478 .await
3479 .map_error(server::error::LegacyCode::default())?;
3480 match r {
3481 SubmitBlockResponse::Accepted => { }
3482 SubmitBlockResponse::ErrorResponse(response) => {
3483 return Err(ErrorObject::owned(
3484 server::error::LegacyCode::Misc.into(),
3485 format!("block was rejected: {response:?}"),
3486 None::<()>,
3487 ));
3488 }
3489 }
3490
3491 block_hashes.push(GetBlockHashResponse(proposal_block.hash()));
3492 }
3493
3494 Ok(block_hashes)
3495 }
3496
3497 async fn generate_to_address(
3498 &self,
3499 num_blocks: u32,
3500 address: String,
3501 ) -> Result<Vec<GetBlockHashResponse>> {
3502 if !self.network.disable_pow() {
3503 return Err(ErrorObject::borrowed(
3504 0,
3505 "generatetoaddress is only supported on networks where PoW is disabled",
3506 None,
3507 ));
3508 }
3509
3510 let miner_address = address
3513 .parse()
3514 .map_error(server::error::LegacyCode::default())?;
3515 let miner_params = MinerParams::new(
3516 &self.network,
3517 crate::config::mining::Config {
3518 miner_address: Some(miner_address),
3519 ..Default::default()
3520 },
3521 )
3522 .map_error(server::error::LegacyCode::default())?;
3523
3524 let mut rpc = self.clone();
3528 rpc.gbt.set_miner_params(miner_params);
3529 rpc.generate(num_blocks).await
3530 }
3531
3532 async fn add_node(
3533 &self,
3534 addr: zebra_network::PeerSocketAddr,
3535 command: AddNodeCommand,
3536 ) -> Result<()> {
3537 if self.network.is_regtest() {
3538 match command {
3539 AddNodeCommand::Add => {
3540 tracing::info!(?addr, "adding peer address to the address book");
3541 if self.address_book.clone().add_peer(addr) {
3542 Ok(())
3543 } else {
3544 return Err(ErrorObject::owned(
3545 server::error::LegacyCode::ClientNodeAlreadyAdded.into(),
3546 format!("peer address was already present in the address book: {addr}"),
3547 None::<()>,
3548 ));
3549 }
3550 }
3551 }
3552 } else {
3553 return Err(ErrorObject::owned(
3554 ErrorCode::InvalidParams.code(),
3555 "addnode command is only supported on regtest",
3556 None::<()>,
3557 ));
3558 }
3559 }
3560
3561 fn openrpc(&self) -> openrpsee::openrpc::Response {
3562 let mut generator = openrpsee::openrpc::Generator::new();
3563
3564 let methods = METHODS
3565 .into_iter()
3566 .map(|(name, method)| method.generate(&mut generator, name))
3567 .collect();
3568
3569 Ok(openrpsee::openrpc::OpenRpc {
3570 openrpc: "1.3.2",
3571 info: openrpsee::openrpc::Info {
3572 title: env!("CARGO_PKG_NAME"),
3573 description: env!("CARGO_PKG_DESCRIPTION"),
3574 version: env!("CARGO_PKG_VERSION"),
3575 },
3576 methods,
3577 components: generator.into_components(),
3578 })
3579 }
3580 async fn get_tx_out(
3581 &self,
3582 txid: String,
3583 n: u32,
3584 include_mempool: Option<bool>,
3585 ) -> Result<GetTxOutResponse> {
3586 let txid = transaction::Hash::from_hex(txid)
3587 .map_error(server::error::LegacyCode::InvalidParameter)?;
3588
3589 let outpoint = transparent::OutPoint {
3590 hash: txid,
3591 index: n,
3592 };
3593
3594 if include_mempool.unwrap_or(true) {
3596 let rsp = self
3597 .mempool
3598 .clone()
3599 .oneshot(mempool::Request::UnspentOutput(outpoint))
3600 .await
3601 .map_misc_error()?;
3602
3603 match rsp {
3604 mempool::Response::TransparentOutput(Some(CreatedOrSpent::Created {
3606 output,
3607 tx_version,
3608 last_seen_hash,
3609 })) => {
3610 return Ok(GetTxOutResponse(Some(
3611 types::transaction::OutputObject::from_output(
3612 &output,
3613 last_seen_hash.to_string(),
3614 0,
3615 tx_version,
3616 false,
3617 self.network(),
3618 ),
3619 )))
3620 }
3621 mempool::Response::TransparentOutput(Some(CreatedOrSpent::Spent)) => {
3622 return Ok(GetTxOutResponse(None))
3623 }
3624 mempool::Response::TransparentOutput(None) => {}
3625 _ => unreachable!("unmatched response to an `UnspentOutput` request"),
3626 };
3627 }
3628
3629 let rsp = self
3636 .read_state
3637 .clone()
3638 .oneshot(zebra_state::ReadRequest::Transaction(txid))
3639 .await
3640 .map_misc_error()?;
3641
3642 match rsp {
3643 zebra_state::ReadResponse::Transaction(Some(tx)) => {
3644 let best_block_hash = tx.best_chain_tip_hash;
3645 let outputs = tx.tx.outputs();
3646 let index: usize = n.try_into().expect("u32 always fits in usize");
3647 let output = match outputs.get(index) {
3648 Some(output) => output,
3649 None => return Ok(GetTxOutResponse(None)),
3651 };
3652
3653 let is_spent = {
3655 let rsp = self
3656 .read_state
3657 .clone()
3658 .oneshot(zebra_state::ReadRequest::IsTransparentOutputSpent(outpoint))
3659 .await
3660 .map_misc_error()?;
3661
3662 match rsp {
3663 zebra_state::ReadResponse::IsTransparentOutputSpent(spent) => spent,
3664 _ => unreachable!(
3665 "unmatched response to an `IsTransparentOutputSpent` request"
3666 ),
3667 }
3668 };
3669
3670 if is_spent {
3671 return Ok(GetTxOutResponse(None));
3672 }
3673
3674 Ok(GetTxOutResponse(Some(
3675 types::transaction::OutputObject::from_output(
3676 output,
3677 best_block_hash.to_string(),
3678 tx.confirmations,
3679 tx.tx.version(),
3680 tx.tx.is_coinbase(),
3681 self.network(),
3682 ),
3683 )))
3684 }
3685 zebra_state::ReadResponse::Transaction(None) => Ok(GetTxOutResponse(None)),
3686 _ => unreachable!("unmatched response to a `Transaction` request"),
3687 }
3688 }
3689}
3690
3691pub fn best_chain_tip_height<Tip>(latest_chain_tip: &Tip) -> Result<Height>
3696where
3697 Tip: ChainTip + Clone + Send + Sync + 'static,
3698{
3699 latest_chain_tip
3700 .best_tip_height()
3701 .ok_or_misc_error("No blocks in state")
3702}
3703
3704#[allow(clippy::too_many_arguments)]
3708#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3709pub struct GetInfoResponse {
3710 #[getter(rename = "raw_version")]
3712 version: u64,
3713
3714 build: String,
3716
3717 subversion: String,
3719
3720 #[serde(rename = "protocolversion")]
3722 protocol_version: u32,
3723
3724 blocks: u32,
3726
3727 connections: usize,
3729
3730 #[serde(skip_serializing_if = "Option::is_none")]
3732 proxy: Option<String>,
3733
3734 difficulty: f64,
3736
3737 testnet: bool,
3739
3740 #[serde(rename = "paytxfee")]
3742 pay_tx_fee: f64,
3743
3744 #[serde(rename = "relayfee")]
3746 relay_fee: f64,
3747
3748 errors: String,
3750
3751 #[serde(rename = "errorstimestamp")]
3753 errors_timestamp: i64,
3754}
3755
3756#[deprecated(note = "Use `GetInfoResponse` instead")]
3757pub use self::GetInfoResponse as GetInfo;
3758
3759impl Default for GetInfoResponse {
3760 fn default() -> Self {
3761 GetInfoResponse {
3762 version: 0,
3763 build: "some build version".to_string(),
3764 subversion: "some subversion".to_string(),
3765 protocol_version: 0,
3766 blocks: 0,
3767 connections: 0,
3768 proxy: None,
3769 difficulty: 0.0,
3770 testnet: false,
3771 pay_tx_fee: 0.0,
3772 relay_fee: 0.0,
3773 errors: "no errors".to_string(),
3774 errors_timestamp: Utc::now().timestamp(),
3775 }
3776 }
3777}
3778
3779impl GetInfoResponse {
3780 #[allow(clippy::too_many_arguments)]
3782 #[deprecated(note = "Use `GetInfoResponse::new` instead")]
3783 pub fn from_parts(
3784 version: u64,
3785 build: String,
3786 subversion: String,
3787 protocol_version: u32,
3788 blocks: u32,
3789 connections: usize,
3790 proxy: Option<String>,
3791 difficulty: f64,
3792 testnet: bool,
3793 pay_tx_fee: f64,
3794 relay_fee: f64,
3795 errors: String,
3796 errors_timestamp: i64,
3797 ) -> Self {
3798 Self {
3799 version,
3800 build,
3801 subversion,
3802 protocol_version,
3803 blocks,
3804 connections,
3805 proxy,
3806 difficulty,
3807 testnet,
3808 pay_tx_fee,
3809 relay_fee,
3810 errors,
3811 errors_timestamp,
3812 }
3813 }
3814
3815 pub fn into_parts(
3817 self,
3818 ) -> (
3819 u64,
3820 String,
3821 String,
3822 u32,
3823 u32,
3824 usize,
3825 Option<String>,
3826 f64,
3827 bool,
3828 f64,
3829 f64,
3830 String,
3831 i64,
3832 ) {
3833 (
3834 self.version,
3835 self.build,
3836 self.subversion,
3837 self.protocol_version,
3838 self.blocks,
3839 self.connections,
3840 self.proxy,
3841 self.difficulty,
3842 self.testnet,
3843 self.pay_tx_fee,
3844 self.relay_fee,
3845 self.errors,
3846 self.errors_timestamp,
3847 )
3848 }
3849
3850 fn version_from_string(build_string: &str) -> Option<u64> {
3852 let semver_version = semver::Version::parse(build_string.strip_prefix('v')?).ok()?;
3853 let build_number = semver_version
3854 .build
3855 .as_str()
3856 .split('.')
3857 .next()
3858 .and_then(|num_str| num_str.parse::<u64>().ok())
3859 .unwrap_or_default();
3860
3861 let version_number = 1_000_000 * semver_version.major
3863 + 10_000 * semver_version.minor
3864 + 100 * semver_version.patch
3865 + build_number;
3866
3867 Some(version_number)
3868 }
3869}
3870
3871const END_OF_SERVICE_ESTIMATE_SAFETY_MARGIN: i64 = 24 * 60 * 60;
3877
3878#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3882pub struct GetDeprecationInfoResponse {
3883 #[serde(skip_serializing_if = "Option::is_none")]
3886 end_of_service: Option<EndOfService>,
3887}
3888
3889#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
3891pub struct EndOfService {
3892 #[getter(copy)]
3895 block_height: u32,
3896
3897 #[getter(copy)]
3903 estimated_time: i64,
3904}
3905
3906pub type BlockchainValuePoolBalances = [GetBlockchainInfoBalance; 6];
3908
3909#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters)]
3913pub struct GetBlockchainInfoResponse {
3914 chain: String,
3916
3917 #[getter(copy)]
3919 blocks: Height,
3920
3921 #[getter(copy)]
3924 headers: Height,
3925
3926 difficulty: f64,
3928
3929 #[serde(rename = "verificationprogress")]
3931 verification_progress: f64,
3932
3933 #[serde(rename = "chainwork")]
3935 chain_work: u64,
3936
3937 pruned: bool,
3939
3940 size_on_disk: u64,
3942
3943 commitments: u64,
3945
3946 #[serde(rename = "bestblockhash", with = "hex")]
3948 #[getter(copy)]
3949 best_block_hash: block::Hash,
3950
3951 #[serde(rename = "estimatedheight")]
3955 #[getter(copy)]
3956 estimated_height: Height,
3957
3958 #[serde(rename = "chainSupply")]
3960 chain_supply: GetBlockchainInfoBalance,
3961
3962 #[serde(rename = "valuePools")]
3964 value_pools: BlockchainValuePoolBalances,
3965
3966 upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
3968
3969 #[getter(copy)]
3971 consensus: TipConsensusBranch,
3972}
3973
3974impl Default for GetBlockchainInfoResponse {
3975 fn default() -> Self {
3976 Self {
3977 chain: "main".to_string(),
3978 blocks: Height(1),
3979 best_block_hash: block::Hash([0; 32]),
3980 estimated_height: Height(1),
3981 chain_supply: GetBlockchainInfoBalance::chain_supply(Default::default()),
3982 value_pools: GetBlockchainInfoBalance::zero_pools(),
3983 upgrades: IndexMap::new(),
3984 consensus: TipConsensusBranch {
3985 chain_tip: ConsensusBranchIdHex(ConsensusBranchId::default()),
3986 next_block: ConsensusBranchIdHex(ConsensusBranchId::default()),
3987 },
3988 headers: Height(1),
3989 difficulty: 0.0,
3990 verification_progress: 0.0,
3991 chain_work: 0,
3992 pruned: false,
3993 size_on_disk: 0,
3994 commitments: 0,
3995 }
3996 }
3997}
3998
3999impl GetBlockchainInfoResponse {
4000 #[allow(clippy::too_many_arguments)]
4004 pub fn new(
4005 chain: String,
4006 blocks: Height,
4007 best_block_hash: block::Hash,
4008 estimated_height: Height,
4009 chain_supply: GetBlockchainInfoBalance,
4010 value_pools: BlockchainValuePoolBalances,
4011 upgrades: IndexMap<ConsensusBranchIdHex, NetworkUpgradeInfo>,
4012 consensus: TipConsensusBranch,
4013 headers: Height,
4014 difficulty: f64,
4015 verification_progress: f64,
4016 chain_work: u64,
4017 pruned: bool,
4018 size_on_disk: u64,
4019 commitments: u64,
4020 ) -> Self {
4021 Self {
4022 chain,
4023 blocks,
4024 best_block_hash,
4025 estimated_height,
4026 chain_supply,
4027 value_pools,
4028 upgrades,
4029 consensus,
4030 headers,
4031 difficulty,
4032 verification_progress,
4033 chain_work,
4034 pruned,
4035 size_on_disk,
4036 commitments,
4037 }
4038 }
4039}
4040
4041#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize, JsonSchema)]
4043#[serde(from = "DGetAddressBalanceRequest")]
4044pub struct GetAddressBalanceRequest {
4045 addresses: Vec<String>,
4047}
4048
4049impl From<DGetAddressBalanceRequest> for GetAddressBalanceRequest {
4050 fn from(address_strings: DGetAddressBalanceRequest) -> Self {
4051 match address_strings {
4052 DGetAddressBalanceRequest::Addresses { addresses } => {
4053 GetAddressBalanceRequest { addresses }
4054 }
4055 DGetAddressBalanceRequest::Address(address) => GetAddressBalanceRequest {
4056 addresses: vec![address],
4057 },
4058 }
4059 }
4060}
4061
4062#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize, JsonSchema)]
4064#[serde(untagged)]
4065enum DGetAddressBalanceRequest {
4066 Addresses { addresses: Vec<String> },
4068 Address(String),
4070}
4071
4072#[deprecated(note = "Use `GetAddressBalanceRequest` instead.")]
4074pub type AddressStrings = GetAddressBalanceRequest;
4075
4076pub(crate) const MAX_REQUEST_ADDRESSES: usize = 10_000;
4078
4079pub(crate) const MAX_ADDRESS_QUERY_RESULTS: usize = 100_000;
4082
4083pub trait ValidateAddresses {
4085 fn valid_addresses(&self) -> Result<HashSet<Address>> {
4089 if self.addresses().len() > MAX_REQUEST_ADDRESSES {
4090 return Err(format!(
4091 "too many addresses: the limit is {MAX_REQUEST_ADDRESSES}"
4092 ))
4093 .map_error(server::error::LegacyCode::InvalidParameter);
4094 }
4095
4096 let valid_addresses: HashSet<Address> = self
4099 .addresses()
4100 .iter()
4101 .map(|address| {
4102 address
4103 .parse()
4104 .map_error(server::error::LegacyCode::InvalidAddressOrKey)
4105 })
4106 .collect::<Result<_>>()?;
4107
4108 Ok(valid_addresses)
4109 }
4110
4111 fn addresses(&self) -> &[String];
4113}
4114
4115impl ValidateAddresses for GetAddressBalanceRequest {
4116 fn addresses(&self) -> &[String] {
4117 &self.addresses
4118 }
4119}
4120
4121impl GetAddressBalanceRequest {
4122 pub fn new(addresses: Vec<String>) -> GetAddressBalanceRequest {
4124 GetAddressBalanceRequest { addresses }
4125 }
4126
4127 #[deprecated(
4129 note = "Use `AddressStrings::new` instead. Validity will be checked by the server."
4130 )]
4131 pub fn new_valid(addresses: Vec<String>) -> Result<GetAddressBalanceRequest> {
4132 let req = Self { addresses };
4133 req.valid_addresses()?;
4134 Ok(req)
4135 }
4136}
4137
4138#[derive(
4140 Clone,
4141 Copy,
4142 Debug,
4143 Default,
4144 Eq,
4145 PartialEq,
4146 Hash,
4147 serde::Serialize,
4148 serde::Deserialize,
4149 Getters,
4150 new,
4151)]
4152pub struct GetAddressBalanceResponse {
4153 balance: u64,
4155 pub received: u64,
4157}
4158
4159#[deprecated(note = "Use `GetAddressBalanceResponse` instead.")]
4160pub use self::GetAddressBalanceResponse as AddressBalance;
4161
4162#[derive(
4164 Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, JsonSchema,
4165)]
4166#[serde(from = "DGetAddressUtxosRequest")]
4167pub struct GetAddressUtxosRequest {
4168 addresses: Vec<String>,
4170 #[serde(default)]
4172 #[serde(rename = "chainInfo")]
4173 chain_info: bool,
4174 #[serde(default)]
4176 #[serde(rename = "startHeight")]
4177 start_height: u64,
4178 #[serde(default)]
4180 #[serde(rename = "maxEntries")]
4181 max_entries: u32,
4182}
4183
4184impl GetAddressUtxosRequest {
4185 pub fn new(addresses: Vec<String>, chain_info: bool) -> GetAddressUtxosRequest {
4187 GetAddressUtxosRequest {
4188 addresses,
4189 chain_info,
4190 start_height: 0,
4191 max_entries: 0,
4192 }
4193 }
4194
4195 pub fn with_limits(self, start_height: u64, max_entries: u32) -> GetAddressUtxosRequest {
4201 GetAddressUtxosRequest {
4202 start_height,
4203 max_entries,
4204 ..self
4205 }
4206 }
4207}
4208
4209impl From<DGetAddressUtxosRequest> for GetAddressUtxosRequest {
4210 fn from(request: DGetAddressUtxosRequest) -> Self {
4211 match request {
4212 DGetAddressUtxosRequest::Single(addr) => GetAddressUtxosRequest::new(vec![addr], false),
4213 DGetAddressUtxosRequest::Object {
4214 addresses,
4215 chain_info,
4216 start_height,
4217 max_entries,
4218 } => GetAddressUtxosRequest::new(addresses, chain_info)
4219 .with_limits(start_height, max_entries),
4220 }
4221 }
4222}
4223
4224#[derive(Debug, serde::Deserialize, JsonSchema)]
4226#[serde(untagged)]
4227enum DGetAddressUtxosRequest {
4228 Single(String),
4230 Object {
4232 addresses: Vec<String>,
4234 #[serde(default)]
4236 #[serde(rename = "chainInfo")]
4237 chain_info: bool,
4238 #[serde(default)]
4240 #[serde(rename = "startHeight")]
4241 start_height: u64,
4242 #[serde(default)]
4244 #[serde(rename = "maxEntries")]
4245 max_entries: u32,
4246 },
4247}
4248
4249impl ValidateAddresses for GetAddressUtxosRequest {
4250 fn addresses(&self) -> &[String] {
4251 &self.addresses
4252 }
4253}
4254
4255#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
4257pub struct ConsensusBranchIdHex(#[serde(with = "hex")] ConsensusBranchId);
4258
4259impl ConsensusBranchIdHex {
4260 pub fn new(consensus_branch_id: u32) -> Self {
4262 ConsensusBranchIdHex(consensus_branch_id.into())
4263 }
4264
4265 pub fn inner(&self) -> u32 {
4267 self.0.into()
4268 }
4269}
4270
4271#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4273pub struct NetworkUpgradeInfo {
4274 name: NetworkUpgrade,
4278
4279 #[serde(rename = "activationheight")]
4281 activation_height: Height,
4282
4283 status: NetworkUpgradeStatus,
4285}
4286
4287impl NetworkUpgradeInfo {
4288 pub fn from_parts(
4290 name: NetworkUpgrade,
4291 activation_height: Height,
4292 status: NetworkUpgradeStatus,
4293 ) -> Self {
4294 Self {
4295 name,
4296 activation_height,
4297 status,
4298 }
4299 }
4300
4301 pub fn into_parts(self) -> (NetworkUpgrade, Height, NetworkUpgradeStatus) {
4303 (self.name, self.activation_height, self.status)
4304 }
4305}
4306
4307#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4309pub enum NetworkUpgradeStatus {
4310 #[serde(rename = "active")]
4315 Active,
4316
4317 #[serde(rename = "disabled")]
4319 Disabled,
4320
4321 #[serde(rename = "pending")]
4323 Pending,
4324}
4325
4326#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4330pub struct TipConsensusBranch {
4331 #[serde(rename = "chaintip")]
4333 chain_tip: ConsensusBranchIdHex,
4334
4335 #[serde(rename = "nextblock")]
4337 next_block: ConsensusBranchIdHex,
4338}
4339
4340impl TipConsensusBranch {
4341 pub fn from_parts(chain_tip: u32, next_block: u32) -> Self {
4343 Self {
4344 chain_tip: ConsensusBranchIdHex::new(chain_tip),
4345 next_block: ConsensusBranchIdHex::new(next_block),
4346 }
4347 }
4348
4349 pub fn into_parts(self) -> (u32, u32) {
4351 (self.chain_tip.inner(), self.next_block.inner())
4352 }
4353}
4354
4355#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4361pub struct SendRawTransactionResponse(#[serde(with = "hex")] transaction::Hash);
4362
4363#[deprecated(note = "Use `SendRawTransactionResponse` instead")]
4364pub use self::SendRawTransactionResponse as SentTransactionHash;
4365
4366impl Default for SendRawTransactionResponse {
4367 fn default() -> Self {
4368 Self(transaction::Hash::from([0; 32]))
4369 }
4370}
4371
4372impl SendRawTransactionResponse {
4373 pub fn new(hash: transaction::Hash) -> Self {
4375 SendRawTransactionResponse(hash)
4376 }
4377
4378 #[deprecated(note = "Use `SentTransactionHash::hash` instead")]
4380 pub fn inner(&self) -> transaction::Hash {
4381 self.hash()
4382 }
4383
4384 pub fn hash(&self) -> transaction::Hash {
4386 self.0
4387 }
4388}
4389
4390#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4394#[serde(untagged)]
4395pub enum GetBlockResponse {
4396 Raw(#[serde(with = "hex")] SerializedBlock),
4398 Object(Box<BlockObject>),
4400}
4401
4402#[deprecated(note = "Use `GetBlockResponse` instead")]
4403pub use self::GetBlockResponse as GetBlock;
4404
4405impl Default for GetBlockResponse {
4406 fn default() -> Self {
4407 GetBlockResponse::Object(Box::new(BlockObject {
4408 hash: block::Hash([0; 32]),
4409 confirmations: 0,
4410 height: None,
4411 time: None,
4412 n_tx: 0,
4413 tx: Vec::new(),
4414 trees: GetBlockTrees::default(),
4415 size: None,
4416 version: None,
4417 merkle_root: None,
4418 block_commitments: None,
4419 final_sapling_root: None,
4420 final_orchard_root: None,
4421 nonce: None,
4422 bits: None,
4423 difficulty: None,
4424 chain_supply: None,
4425 value_pools: None,
4426 previous_block_hash: None,
4427 next_block_hash: None,
4428 solution: None,
4429 }))
4430 }
4431}
4432
4433#[allow(clippy::too_many_arguments)]
4435#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4436pub struct BlockObject {
4437 #[getter(copy)]
4439 #[serde(with = "hex")]
4440 hash: block::Hash,
4441
4442 confirmations: i64,
4445
4446 #[serde(skip_serializing_if = "Option::is_none")]
4448 #[getter(copy)]
4449 size: Option<i64>,
4450
4451 #[serde(skip_serializing_if = "Option::is_none")]
4453 #[getter(copy)]
4454 height: Option<Height>,
4455
4456 #[serde(skip_serializing_if = "Option::is_none")]
4458 #[getter(copy)]
4459 version: Option<u32>,
4460
4461 #[serde(with = "opthex", rename = "merkleroot")]
4463 #[serde(skip_serializing_if = "Option::is_none")]
4464 #[getter(copy)]
4465 merkle_root: Option<block::merkle::Root>,
4466
4467 #[serde(with = "opthex", rename = "blockcommitments")]
4470 #[serde(skip_serializing_if = "Option::is_none")]
4471 #[getter(copy)]
4472 block_commitments: Option<[u8; 32]>,
4473
4474 #[serde(with = "opthex", rename = "finalsaplingroot")]
4478 #[serde(skip_serializing_if = "Option::is_none")]
4479 #[getter(copy)]
4480 final_sapling_root: Option<[u8; 32]>,
4481
4482 #[serde(with = "opthex", rename = "finalorchardroot")]
4484 #[serde(skip_serializing_if = "Option::is_none")]
4485 #[getter(copy)]
4486 final_orchard_root: Option<[u8; 32]>,
4487
4488 #[serde(rename = "nTx")]
4492 n_tx: usize,
4493
4494 tx: Vec<GetBlockTransaction>,
4497
4498 #[serde(skip_serializing_if = "Option::is_none")]
4500 #[getter(copy)]
4501 time: Option<i64>,
4502
4503 #[serde(with = "opthex")]
4505 #[serde(skip_serializing_if = "Option::is_none")]
4506 #[getter(copy)]
4507 nonce: Option<[u8; 32]>,
4508
4509 #[serde(with = "opthex")]
4512 #[serde(skip_serializing_if = "Option::is_none")]
4513 #[getter(copy)]
4514 solution: Option<Solution>,
4515
4516 #[serde(with = "opthex")]
4518 #[serde(skip_serializing_if = "Option::is_none")]
4519 #[getter(copy)]
4520 bits: Option<CompactDifficulty>,
4521
4522 #[serde(skip_serializing_if = "Option::is_none")]
4525 #[getter(copy)]
4526 difficulty: Option<f64>,
4527
4528 #[serde(rename = "chainSupply")]
4533 #[serde(skip_serializing_if = "Option::is_none")]
4534 chain_supply: Option<GetBlockchainInfoBalance>,
4535
4536 #[serde(rename = "valuePools")]
4538 #[serde(skip_serializing_if = "Option::is_none")]
4539 value_pools: Option<BlockchainValuePoolBalances>,
4540
4541 #[getter(copy)]
4543 trees: GetBlockTrees,
4544
4545 #[serde(rename = "previousblockhash", skip_serializing_if = "Option::is_none")]
4547 #[serde(with = "opthex")]
4548 #[getter(copy)]
4549 previous_block_hash: Option<block::Hash>,
4550
4551 #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
4553 #[serde(with = "opthex")]
4554 #[getter(copy)]
4555 next_block_hash: Option<block::Hash>,
4556}
4557
4558#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4559#[serde(untagged)]
4560pub enum GetBlockTransaction {
4563 Hash(#[serde(with = "hex")] transaction::Hash),
4565 Object(Box<TransactionObject>),
4567}
4568
4569#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4573#[serde(untagged)]
4574pub enum GetBlockHeaderResponse {
4575 Raw(hex_data::HexData),
4577
4578 Object(Box<BlockHeaderObject>),
4580}
4581
4582#[deprecated(note = "Use `GetBlockHeaderResponse` instead")]
4583pub use self::GetBlockHeaderResponse as GetBlockHeader;
4584
4585#[allow(clippy::too_many_arguments)]
4586#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4587pub struct BlockHeaderObject {
4591 #[serde(with = "hex")]
4593 #[getter(copy)]
4594 hash: block::Hash,
4595
4596 confirmations: i64,
4599
4600 #[getter(copy)]
4602 height: Height,
4603
4604 version: u32,
4606
4607 #[serde(with = "hex", rename = "merkleroot")]
4609 #[getter(copy)]
4610 merkle_root: block::merkle::Root,
4611
4612 #[serde(with = "hex", rename = "blockcommitments")]
4615 #[getter(copy)]
4616 block_commitments: [u8; 32],
4617
4618 #[serde(with = "hex", rename = "finalsaplingroot")]
4620 #[getter(copy)]
4621 final_sapling_root: [u8; 32],
4622
4623 #[serde(skip)]
4626 sapling_tree_size: u64,
4627
4628 time: i64,
4630
4631 #[serde(with = "hex")]
4633 #[getter(copy)]
4634 nonce: [u8; 32],
4635
4636 #[serde(with = "hex")]
4638 #[getter(copy)]
4639 solution: Solution,
4640
4641 #[serde(with = "hex")]
4643 #[getter(copy)]
4644 bits: CompactDifficulty,
4645
4646 difficulty: f64,
4649
4650 #[serde(rename = "previousblockhash")]
4652 #[serde(with = "hex")]
4653 #[getter(copy)]
4654 previous_block_hash: block::Hash,
4655
4656 #[serde(rename = "nextblockhash", skip_serializing_if = "Option::is_none")]
4658 #[getter(copy)]
4659 #[serde(with = "opthex")]
4660 next_block_hash: Option<block::Hash>,
4661}
4662
4663#[deprecated(note = "Use `BlockHeaderObject` instead")]
4664pub use BlockHeaderObject as GetBlockHeaderObject;
4665
4666impl Default for GetBlockHeaderResponse {
4667 fn default() -> Self {
4668 GetBlockHeaderResponse::Object(Box::default())
4669 }
4670}
4671
4672impl Default for BlockHeaderObject {
4673 fn default() -> Self {
4674 let difficulty: ExpandedDifficulty = zebra_chain::work::difficulty::U256::one().into();
4675
4676 BlockHeaderObject {
4677 hash: block::Hash([0; 32]),
4678 confirmations: 0,
4679 height: Height::MIN,
4680 version: 4,
4681 merkle_root: block::merkle::Root([0; 32]),
4682 block_commitments: Default::default(),
4683 final_sapling_root: Default::default(),
4684 sapling_tree_size: Default::default(),
4685 time: 0,
4686 nonce: [0; 32],
4687 solution: Solution::for_proposal(),
4688 bits: difficulty.to_compact(),
4689 difficulty: 1.0,
4690 previous_block_hash: block::Hash([0; 32]),
4691 next_block_hash: Some(block::Hash([0; 32])),
4692 }
4693 }
4694}
4695
4696#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4702#[serde(transparent)]
4703pub struct GetBlockHashResponse(#[serde(with = "hex")] pub(crate) block::Hash);
4704
4705impl GetBlockHashResponse {
4706 pub fn new(hash: block::Hash) -> Self {
4708 GetBlockHashResponse(hash)
4709 }
4710
4711 pub fn hash(&self) -> block::Hash {
4713 self.0
4714 }
4715}
4716
4717#[deprecated(note = "Use `GetBlockHashResponse` instead")]
4718pub use self::GetBlockHashResponse as GetBlockHash;
4719
4720pub type Hash = GetBlockHashResponse;
4722
4723#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new)]
4725pub struct GetBlockHeightAndHashResponse {
4726 #[getter(copy)]
4728 height: block::Height,
4729 #[getter(copy)]
4731 hash: block::Hash,
4732}
4733
4734#[deprecated(note = "Use `GetBlockHeightAndHashResponse` instead.")]
4735pub use GetBlockHeightAndHashResponse as GetBestBlockHeightAndHash;
4736
4737impl Default for GetBlockHeightAndHashResponse {
4738 fn default() -> Self {
4739 Self {
4740 height: block::Height::MIN,
4741 hash: block::Hash([0; 32]),
4742 }
4743 }
4744}
4745
4746impl Default for GetBlockHashResponse {
4747 fn default() -> Self {
4748 GetBlockHashResponse(block::Hash([0; 32]))
4749 }
4750}
4751
4752#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
4756#[serde(untagged)]
4757pub enum GetRawTransactionResponse {
4758 Raw(#[serde(with = "hex")] SerializedTransaction),
4760 Object(Box<TransactionObject>),
4762}
4763
4764#[deprecated(note = "Use `GetRawTransactionResponse` instead")]
4765pub use self::GetRawTransactionResponse as GetRawTransaction;
4766
4767impl Default for GetRawTransactionResponse {
4768 fn default() -> Self {
4769 Self::Object(Box::default())
4770 }
4771}
4772
4773#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
4775#[serde(untagged)]
4776pub enum GetAddressUtxosResponse {
4777 Utxos(Vec<Utxo>),
4779 UtxosAndChainInfo(GetAddressUtxosResponseObject),
4781}
4782
4783#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4785pub struct GetAddressUtxosResponseObject {
4786 utxos: Vec<Utxo>,
4787 #[serde(with = "hex")]
4788 #[getter(copy)]
4789 hash: block::Hash,
4790 #[getter(copy)]
4791 height: block::Height,
4792}
4793
4794#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
4798pub struct Utxo {
4799 address: transparent::Address,
4801
4802 #[serde(with = "hex")]
4804 #[getter(copy)]
4805 txid: transaction::Hash,
4806
4807 #[serde(rename = "outputIndex")]
4809 #[getter(copy)]
4810 output_index: OutputIndex,
4811
4812 #[serde(with = "hex")]
4814 script: transparent::Script,
4815
4816 satoshis: u64,
4818
4819 #[getter(copy)]
4823 height: Height,
4824}
4825
4826#[deprecated(note = "Use `Utxo` instead")]
4827pub use self::Utxo as GetAddressUtxos;
4828
4829impl Default for Utxo {
4830 fn default() -> Self {
4831 Self {
4832 address: transparent::Address::from_pub_key_hash(
4833 zebra_chain::parameters::NetworkKind::default(),
4834 [0u8; 20],
4835 ),
4836 txid: transaction::Hash::from([0; 32]),
4837 output_index: OutputIndex::from_u64(0),
4838 script: transparent::Script::new(&[0u8; 10]),
4839 satoshis: u64::default(),
4840 height: Height(0),
4841 }
4842 }
4843}
4844
4845impl Utxo {
4846 #[deprecated(note = "Use `Utxo::new` instead")]
4848 pub fn from_parts(
4849 address: transparent::Address,
4850 txid: transaction::Hash,
4851 output_index: OutputIndex,
4852 script: transparent::Script,
4853 satoshis: u64,
4854 height: Height,
4855 ) -> Self {
4856 Utxo {
4857 address,
4858 txid,
4859 output_index,
4860 script,
4861 satoshis,
4862 height,
4863 }
4864 }
4865
4866 pub fn into_parts(
4868 &self,
4869 ) -> (
4870 transparent::Address,
4871 transaction::Hash,
4872 OutputIndex,
4873 transparent::Script,
4874 u64,
4875 Height,
4876 ) {
4877 (
4878 self.address,
4879 self.txid,
4880 self.output_index,
4881 self.script.clone(),
4882 self.satoshis,
4883 self.height,
4884 )
4885 }
4886}
4887
4888#[derive(
4892 Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Getters, new, JsonSchema,
4893)]
4894#[serde(from = "DGetAddressTxIdsRequest")]
4895pub struct GetAddressTxIdsRequest {
4896 addresses: Vec<String>,
4899 start: Option<u32>,
4901 end: Option<u32>,
4903}
4904
4905impl GetAddressTxIdsRequest {
4906 #[deprecated(note = "Use `GetAddressTxIdsRequest::new` instead.")]
4908 pub fn from_parts(addresses: Vec<String>, start: u32, end: u32) -> Self {
4909 GetAddressTxIdsRequest {
4910 addresses,
4911 start: Some(start),
4912 end: Some(end),
4913 }
4914 }
4915
4916 pub fn into_parts(&self) -> (Vec<String>, u32, u32) {
4918 (
4919 self.addresses.clone(),
4920 self.start.unwrap_or(0),
4921 self.end.unwrap_or(0),
4922 )
4923 }
4924}
4925
4926impl From<DGetAddressTxIdsRequest> for GetAddressTxIdsRequest {
4927 fn from(request: DGetAddressTxIdsRequest) -> Self {
4928 match request {
4929 DGetAddressTxIdsRequest::Single(addr) => GetAddressTxIdsRequest {
4930 addresses: vec![addr],
4931 start: None,
4932 end: None,
4933 },
4934 DGetAddressTxIdsRequest::Object {
4935 addresses,
4936 start,
4937 end,
4938 } => GetAddressTxIdsRequest {
4939 addresses,
4940 start,
4941 end,
4942 },
4943 }
4944 }
4945}
4946
4947#[derive(Debug, serde::Deserialize, JsonSchema)]
4949#[serde(untagged)]
4950enum DGetAddressTxIdsRequest {
4951 Single(String),
4953 Object {
4955 addresses: Vec<String>,
4957 start: Option<u32>,
4959 end: Option<u32>,
4961 },
4962}
4963
4964impl ValidateAddresses for GetAddressTxIdsRequest {
4965 fn addresses(&self) -> &[String] {
4966 &self.addresses
4967 }
4968}
4969
4970#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
4972pub struct GetBlockTrees {
4973 #[serde(skip_serializing_if = "SaplingTrees::is_empty")]
4974 sapling: SaplingTrees,
4975 #[serde(skip_serializing_if = "OrchardTrees::is_empty")]
4976 orchard: OrchardTrees,
4977 #[serde(default, skip_serializing_if = "IronwoodTrees::is_empty")]
4980 ironwood: IronwoodTrees,
4981}
4982
4983impl Default for GetBlockTrees {
4984 fn default() -> Self {
4985 GetBlockTrees {
4986 sapling: SaplingTrees { size: 0 },
4987 orchard: OrchardTrees { size: 0 },
4988 ironwood: IronwoodTrees { size: 0 },
4989 }
4990 }
4991}
4992
4993impl GetBlockTrees {
4994 pub fn new(sapling: u64, orchard: u64, ironwood: u64) -> Self {
4996 GetBlockTrees {
4997 sapling: SaplingTrees { size: sapling },
4998 orchard: OrchardTrees { size: orchard },
4999 ironwood: IronwoodTrees { size: ironwood },
5000 }
5001 }
5002
5003 pub fn sapling(self) -> u64 {
5005 self.sapling.size
5006 }
5007
5008 pub fn orchard(self) -> u64 {
5010 self.orchard.size
5011 }
5012
5013 pub fn ironwood(self) -> u64 {
5015 self.ironwood.size
5016 }
5017}
5018
5019#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
5021pub struct SaplingTrees {
5022 size: u64,
5023}
5024
5025impl SaplingTrees {
5026 fn is_empty(&self) -> bool {
5027 self.size == 0
5028 }
5029}
5030
5031#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
5033pub struct OrchardTrees {
5034 size: u64,
5035}
5036
5037impl OrchardTrees {
5038 fn is_empty(&self) -> bool {
5039 self.size == 0
5040 }
5041}
5042
5043#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
5045pub struct IronwoodTrees {
5046 size: u64,
5047}
5048
5049impl IronwoodTrees {
5050 fn is_empty(&self) -> bool {
5051 self.size == 0
5052 }
5053}
5054
5055fn build_height_range(
5071 start: Option<u32>,
5072 end: Option<u32>,
5073 chain_height: Height,
5074) -> Result<RangeInclusive<Height>> {
5075 let start = Height(start.unwrap_or(0)).min(chain_height);
5078
5079 let end = match end {
5081 Some(0) | None => chain_height,
5082 Some(val) => Height(val).min(chain_height),
5083 };
5084
5085 if start > end {
5086 return Err(ErrorObject::owned(
5087 ErrorCode::InvalidParams.code(),
5088 format!("start {start:?} must be less than or equal to end {end:?}"),
5089 None::<()>,
5090 ));
5091 }
5092
5093 Ok(start..=end)
5094}
5095
5096pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height> {
5104 if index >= 0 {
5105 let height = index.try_into().map_err(|_| {
5106 ErrorObject::borrowed(
5107 ErrorCode::InvalidParams.code(),
5108 "Index conversion failed",
5109 None,
5110 )
5111 })?;
5112 if height > tip_height.0 {
5113 return Err(ErrorObject::borrowed(
5114 ErrorCode::InvalidParams.code(),
5115 "Provided index is greater than the current tip",
5116 None,
5117 ));
5118 }
5119 Ok(Height(height))
5120 } else {
5121 let height = i32::try_from(tip_height.0)
5123 .map_err(|_| {
5124 ErrorObject::borrowed(
5125 ErrorCode::InvalidParams.code(),
5126 "Tip height conversion failed",
5127 None,
5128 )
5129 })?
5130 .checked_add(index + 1);
5131
5132 let sanitized_height = match height {
5133 None => {
5134 return Err(ErrorObject::borrowed(
5135 ErrorCode::InvalidParams.code(),
5136 "Provided index is not valid",
5137 None,
5138 ));
5139 }
5140 Some(h) => {
5141 if h < 0 {
5142 return Err(ErrorObject::borrowed(
5143 ErrorCode::InvalidParams.code(),
5144 "Provided negative index ends up with a negative height",
5145 None,
5146 ));
5147 }
5148 let h: u32 = h.try_into().map_err(|_| {
5149 ErrorObject::borrowed(
5150 ErrorCode::InvalidParams.code(),
5151 "Height conversion failed",
5152 None,
5153 )
5154 })?;
5155 if h > tip_height.0 {
5156 return Err(ErrorObject::borrowed(
5157 ErrorCode::InvalidParams.code(),
5158 "Provided index is greater than the current tip",
5159 None,
5160 ));
5161 }
5162
5163 h
5164 }
5165 };
5166
5167 Ok(Height(sanitized_height))
5168 }
5169}
5170
5171pub mod opthex {
5173 use hex::{FromHex, ToHex};
5174 use serde::{de, Deserialize, Deserializer, Serializer};
5175
5176 #[allow(missing_docs)]
5177 pub fn serialize<S, T>(data: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
5178 where
5179 S: Serializer,
5180 T: ToHex,
5181 {
5182 match data {
5183 Some(data) => {
5184 let s = data.encode_hex::<String>();
5185 serializer.serialize_str(&s)
5186 }
5187 None => serializer.serialize_none(),
5188 }
5189 }
5190
5191 #[allow(missing_docs)]
5192 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
5193 where
5194 D: Deserializer<'de>,
5195 T: FromHex,
5196 {
5197 let opt = Option::<String>::deserialize(deserializer)?;
5198 match opt {
5199 Some(s) => T::from_hex(&s)
5200 .map(Some)
5201 .map_err(|_e| de::Error::custom("failed to convert hex string")),
5202 None => Ok(None),
5203 }
5204 }
5205}
5206
5207pub mod arrayhex {
5209 use serde::{Deserializer, Serializer};
5210 use std::fmt;
5211
5212 #[allow(missing_docs)]
5213 pub fn serialize<S, const N: usize>(data: &[u8; N], serializer: S) -> Result<S::Ok, S::Error>
5214 where
5215 S: Serializer,
5216 {
5217 let hex_string = hex::encode(data);
5218 serializer.serialize_str(&hex_string)
5219 }
5220
5221 #[allow(missing_docs)]
5222 pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error>
5223 where
5224 D: Deserializer<'de>,
5225 {
5226 struct HexArrayVisitor<const N: usize>;
5227
5228 impl<const N: usize> serde::de::Visitor<'_> for HexArrayVisitor<N> {
5229 type Value = [u8; N];
5230
5231 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
5232 write!(formatter, "a hex string representing exactly {N} bytes")
5233 }
5234
5235 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
5236 where
5237 E: serde::de::Error,
5238 {
5239 let vec = hex::decode(v).map_err(E::custom)?;
5240 vec.clone().try_into().map_err(|_| {
5241 E::invalid_length(vec.len(), &format!("expected {N} bytes").as_str())
5242 })
5243 }
5244 }
5245
5246 deserializer.deserialize_str(HexArrayVisitor::<N>)
5247 }
5248}
5249
5250pub async fn chain_tip_difficulty<State>(
5252 network: Network,
5253 mut state: State,
5254 should_use_default: bool,
5255) -> Result<f64>
5256where
5257 State: ReadStateService,
5258{
5259 let request = ReadRequest::ChainInfo;
5260
5261 let response = state
5267 .ready()
5268 .and_then(|service| service.call(request))
5269 .await;
5270
5271 let response = match (should_use_default, response) {
5272 (_, Ok(res)) => res,
5273 (true, Err(_)) => {
5274 return Ok((U256::from(network.target_difficulty_limit()) >> 128).as_u128() as f64);
5275 }
5276 (false, Err(error)) => return Err(ErrorObject::owned(0, error.to_string(), None::<()>)),
5277 };
5278
5279 let chain_info = match response {
5280 ReadResponse::ChainInfo(info) => info,
5281 _ => unreachable!("unmatched response to a chain info request"),
5282 };
5283
5284 let pow_limit: U256 = network.target_difficulty_limit().into();
5307 let Some(difficulty) = chain_info.expected_difficulty.to_expanded() else {
5308 return Ok(0.0);
5309 };
5310
5311 let pow_limit = pow_limit >> 128;
5313 let difficulty = U256::from(difficulty) >> 128;
5314
5315 let pow_limit = pow_limit.as_u128() as f64;
5318 let difficulty = difficulty.as_u128() as f64;
5319
5320 Ok(pow_limit / difficulty)
5322}
5323
5324#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)]
5326pub enum AddNodeCommand {
5327 #[serde(rename = "add")]
5329 Add,
5330}
5331
5332#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
5336#[serde(transparent)]
5337pub struct GetTxOutResponse(Option<types::transaction::OutputObject>);