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