diff --git a/Cargo.lock b/Cargo.lock index 6db6d1e1b0a..2e2bf2c8aa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5488,6 +5488,7 @@ dependencies = [ "tracing", "uint", "x25519-dalek", + "zcash_address", "zcash_encoding", "zcash_history", "zcash_note_encryption", @@ -5610,6 +5611,7 @@ dependencies = [ "tower", "tracing", "tracing-futures", + "zcash_address", "zebra-chain", "zebra-consensus", "zebra-network", diff --git a/zebra-chain/Cargo.toml b/zebra-chain/Cargo.toml index cc6472d52ca..a21d067e284 100644 --- a/zebra-chain/Cargo.toml +++ b/zebra-chain/Cargo.toml @@ -13,7 +13,9 @@ default = [] # Production features that activate extra functionality # Experimental mining RPC support -getblocktemplate-rpcs = [] +getblocktemplate-rpcs = [ + "zcash_address", +] # Test-only features @@ -89,6 +91,9 @@ ed25519-zebra = "3.1.0" redjubjub = "0.5.0" reddsa = "0.4.0" +# Experimental feature getblocktemplate-rpcs +zcash_address = { version = "0.2.0", optional = true } + # Optional testing dependencies proptest = { version = "0.10.1", optional = true } proptest-derive = { version = "0.3.0", optional = true } diff --git a/zebra-chain/src/primitives.rs b/zebra-chain/src/primitives.rs index 1c7154eb7d4..9b5056bc620 100644 --- a/zebra-chain/src/primitives.rs +++ b/zebra-chain/src/primitives.rs @@ -5,6 +5,13 @@ //! whose functionality is implemented elsewhere. mod proofs; + +#[cfg(feature = "getblocktemplate-rpcs")] +mod address; + +#[cfg(feature = "getblocktemplate-rpcs")] +pub use address::Address; + pub use ed25519_zebra as ed25519; pub use reddsa; pub use redjubjub; diff --git a/zebra-chain/src/primitives/address.rs b/zebra-chain/src/primitives/address.rs new file mode 100644 index 00000000000..fc8629b66a4 --- /dev/null +++ b/zebra-chain/src/primitives/address.rs @@ -0,0 +1,122 @@ +//! `zcash_address` conversion to `zebra_chain` address types. +//! +//! Usage: + +use zcash_primitives::sapling; + +use crate::{orchard, parameters::Network, transparent, BoxError}; + +/// Zcash address variants +// TODO: Add Sprout addresses +pub enum Address { + /// Transparent address + Transparent(transparent::Address), + + /// Sapling address + Sapling { + /// Address' network + network: Network, + + /// Sapling address + address: sapling::PaymentAddress, + }, + + /// Unified address + Unified { + /// Address' network + network: Network, + + /// Transparent address + transparent_address: transparent::Address, + + /// Sapling address + sapling_address: sapling::PaymentAddress, + + /// Orchard address + orchard_address: orchard::Address, + }, +} + +impl TryFrom for Network { + // TODO: better error type + type Error = BoxError; + + fn try_from(network: zcash_address::Network) -> Result { + match network { + zcash_address::Network::Main => Ok(Network::Mainnet), + zcash_address::Network::Test => Ok(Network::Testnet), + zcash_address::Network::Regtest => Err("unsupported Zcash network parameters".into()), + } + } +} + +impl From for zcash_address::Network { + fn from(network: Network) -> Self { + match network { + Network::Mainnet => zcash_address::Network::Main, + Network::Testnet => zcash_address::Network::Test, + } + } +} + +impl zcash_address::TryFromAddress for Address { + // TODO: crate::serialization::SerializationError + type Error = BoxError; + + fn try_from_transparent_p2pkh( + network: zcash_address::Network, + data: [u8; 20], + ) -> Result> { + Ok(Self::Transparent(transparent::Address::from_pub_key_hash( + network.try_into()?, + data, + ))) + } + + fn try_from_transparent_p2sh( + network: zcash_address::Network, + data: [u8; 20], + ) -> Result> { + Ok(Self::Transparent(transparent::Address::from_script_hash( + network.try_into()?, + data, + ))) + } + + fn try_from_sapling( + network: zcash_address::Network, + data: [u8; 43], + ) -> Result> { + let network = network.try_into()?; + sapling::PaymentAddress::from_bytes(&data) + .map(|address| Self::Sapling { address, network }) + .ok_or_else(|| BoxError::from("not a valid sapling address").into()) + } + + // TODO: Add sprout and unified/orchard converters +} + +impl Address { + /// Returns the network for the address. + pub fn network(&self) -> Network { + match &self { + Self::Transparent(address) => address.network(), + Self::Sapling { network, .. } | Self::Unified { network, .. } => *network, + } + } + + /// Returns true if the address is PayToScriptHash + /// Returns false if the address is PayToPublicKeyHash or shielded. + pub fn is_script_hash(&self) -> bool { + match &self { + Self::Transparent(address) => address.is_script_hash(), + Self::Sapling { .. } | Self::Unified { .. } => false, + } + } + + /// Returns true if address is of the [`Address::Transparent`] variant. + /// Returns false if otherwise. + pub fn is_transparent(&self) -> bool { + matches!(self, Self::Transparent(_)) + } +} diff --git a/zebra-rpc/Cargo.toml b/zebra-rpc/Cargo.toml index e782bd744e9..df38bacb2ba 100644 --- a/zebra-rpc/Cargo.toml +++ b/zebra-rpc/Cargo.toml @@ -15,6 +15,7 @@ default = [] # Experimental mining RPC support getblocktemplate-rpcs = [ "rand", + "zcash_address", "zebra-consensus/getblocktemplate-rpcs", "zebra-state/getblocktemplate-rpcs", "zebra-node-services/getblocktemplate-rpcs", @@ -58,6 +59,8 @@ serde = { version = "1.0.152", features = ["serde_derive"] } # Experimental feature getblocktemplate-rpcs rand = { version = "0.8.5", package = "rand", optional = true } +# ECC deps used by getblocktemplate-rpcs feature +zcash_address = { version = "0.2.0", optional = true } # Test-only feature proptest-impl proptest = { version = "0.10.1", optional = true } diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 454b27bc8d9..94f8c721581 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -139,7 +139,7 @@ pub trait Rpc { /// getting blocks by hash. (But we parse the height as a JSON string, not an integer). /// `lightwalletd` also does not use verbosity=2, so we don't support it. #[rpc(name = "getblock")] - fn get_block(&self, height: String, verbosity: u8) -> BoxFuture>; + fn get_block(&self, height: String, verbosity: Option) -> BoxFuture>; /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string. /// @@ -556,8 +556,16 @@ where // - use `height_from_signed_int()` to handle negative heights // (this might be better in the state request, because it needs the state height) // - create a function that handles block hashes or heights, and use it in `z_get_treestate()` - fn get_block(&self, hash_or_height: String, verbosity: u8) -> BoxFuture> { + fn get_block( + &self, + hash_or_height: String, + verbosity: Option, + ) -> BoxFuture> { + // From + const DEFAULT_GETBLOCK_VERBOSITY: u8 = 1; + let mut state = self.state.clone(); + let verbosity = verbosity.unwrap_or(DEFAULT_GETBLOCK_VERBOSITY); async move { let hash_or_height: HashOrHeight = diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index acc0a15d23d..004eb21c510 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -7,12 +7,15 @@ use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use tower::{buffer::Buffer, Service, ServiceExt}; +use zcash_address; + use zebra_chain::{ amount::Amount, block::{self, Block, Height}, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, parameters::Network, + primitives, serialization::ZcashDeserializeInto, transparent, }; @@ -43,6 +46,7 @@ use crate::methods::{ peer_info::PeerInfo, submit_block, subsidy::{BlockSubsidy, FundingStream}, + validate_address, }, }, height_from_signed_int, GetBlockHash, MISSING_BLOCK_ERROR_CODE, @@ -167,6 +171,13 @@ pub trait GetBlockTemplateRpc { #[rpc(name = "getpeerinfo")] fn get_peer_info(&self) -> BoxFuture>>; + /// Checks if a zcash address is valid. + /// Returns information about the given address if valid. + /// + /// zcashd reference: [`validateaddress`](https://zcash.github.io/rpc/validateaddress.html) + #[rpc(name = "validateaddress")] + fn validate_address(&self, address: String) -> BoxFuture>; + /// Returns the block subsidy reward of the block at `height`, taking into account the mining slow start. /// Returns an error if `height` is less than the height of the first halving for the current network. /// @@ -788,6 +799,51 @@ where .boxed() } + fn validate_address( + &self, + raw_address: String, + ) -> BoxFuture> { + let network = self.network; + + async move { + let Ok(address) = raw_address + .parse::() else { + return Ok(validate_address::Response::invalid()); + }; + + let address = match address + .convert::() { + Ok(address) => address, + Err(err) => { + tracing::debug!(?err, "conversion error"); + return Ok(validate_address::Response::invalid()); + } + }; + + // we want to match zcashd's behaviour + if !address.is_transparent() { + return Ok(validate_address::Response::invalid()); + } + + return Ok(if address.network() == network { + validate_address::Response { + address: Some(raw_address), + is_valid: true, + is_script: Some(address.is_script_hash()), + } + } else { + tracing::info!( + ?network, + address_network = ?address.network(), + "invalid address in validateaddress RPC: Zebra's configured network must match address network" + ); + + validate_address::Response::invalid() + }); + } + .boxed() + } + fn get_block_subsidy(&self, height: Option) -> BoxFuture> { let latest_chain_tip = self.latest_chain_tip.clone(); let network = self.network; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs index 717072d9cdc..305ed58c92b 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -9,4 +9,5 @@ pub mod peer_info; pub mod submit_block; pub mod subsidy; pub mod transaction; +pub mod validate_address; pub mod zec; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs index 365b8a2f79d..12edd8fdf12 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs @@ -2,5 +2,5 @@ //! for the `submitblock` RPC method. /// Deserialize hex-encoded strings to bytes. -#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct HexData(#[serde(with = "hex")] pub Vec); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/validate_address.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/validate_address.rs new file mode 100644 index 00000000000..41c98126c9c --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/validate_address.rs @@ -0,0 +1,27 @@ +//! Response type for the `validateaddress` RPC. + +/// `validateaddress` response +#[derive(Clone, Default, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct Response { + /// Whether the address is valid. + /// + /// If not, this is the only property returned. + #[serde(rename = "isvalid")] + pub is_valid: bool, + + /// The zcash address that has been validated. + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option, + + /// If the key is a script. + #[serde(rename = "isscript")] + #[serde(skip_serializing_if = "Option::is_none")] + pub is_script: Option, +} + +impl Response { + /// Creates an empty response with `isvalid` of false. + pub fn invalid() -> Self { + Self::default() + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index f25593e4ca0..6b1b26f523c 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -106,17 +106,24 @@ async fn test_rpc_response_data_for_network(network: Network) { // `getblock`, verbosity=0 const BLOCK_HEIGHT: u32 = 1; let get_block = rpc - .get_block(BLOCK_HEIGHT.to_string(), 0u8) + .get_block(BLOCK_HEIGHT.to_string(), Some(0u8)) .await .expect("We should have a GetBlock struct"); snapshot_rpc_getblock(get_block, block_data.get(&BLOCK_HEIGHT).unwrap(), &settings); // `getblock`, verbosity=1 let get_block = rpc - .get_block(BLOCK_HEIGHT.to_string(), 1u8) + .get_block(BLOCK_HEIGHT.to_string(), Some(1u8)) .await .expect("We should have a GetBlock struct"); - snapshot_rpc_getblock_verbose(get_block, &settings); + snapshot_rpc_getblock_verbose("2_args", get_block, &settings); + + // `getblock`, no verbosity, defaults to 1 + let get_block = rpc + .get_block(BLOCK_HEIGHT.to_string(), None) + .await + .expect("We should have a GetBlock struct"); + snapshot_rpc_getblock_verbose("1_arg", get_block, &settings); // `getbestblockhash` let get_best_block_hash = rpc @@ -251,8 +258,12 @@ fn snapshot_rpc_getblock(block: GetBlock, block_data: &[u8], settings: &insta::S } /// Check `getblock` response with verbosity=1, using `cargo insta` and JSON serialization. -fn snapshot_rpc_getblock_verbose(block: GetBlock, settings: &insta::Settings) { - settings.bind(|| insta::assert_json_snapshot!("get_block_verbose", block)); +fn snapshot_rpc_getblock_verbose( + variant: &'static str, + block: GetBlock, + settings: &insta::Settings, +) { + settings.bind(|| insta::assert_json_snapshot!(format!("get_block_verbose_{variant}"), block)); } /// Snapshot `getbestblockhash` response, using `cargo insta` and JSON serialization. diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 5c25d70ab9b..e273270967b 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -42,6 +42,7 @@ use crate::methods::{ peer_info::PeerInfo, submit_block, subsidy::BlockSubsidy, + validate_address, }, }, tests::utils::fake_history_tree, @@ -371,6 +372,24 @@ pub async fn test_responses( .expect("unexpected error in submitblock RPC call"); snapshot_rpc_submit_block_invalid(submit_block, &settings); + + // `validateaddress` + let founder_address = match network { + Network::Mainnet => "t3fqvkzrrNaMcamkQMwAyHRjfDdM2xQvDTR", + Network::Testnet => "t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi", + }; + + let validate_address = get_block_template_rpc + .validate_address(founder_address.to_string()) + .await + .expect("We should have a validate_address::Response"); + snapshot_rpc_validateaddress("basic", validate_address, &settings); + + let validate_address = get_block_template_rpc + .validate_address("".to_string()) + .await + .expect("We should have a validate_address::Response"); + snapshot_rpc_validateaddress("invalid", validate_address, &settings); } /// Snapshot `getblockcount` response, using `cargo insta` and JSON serialization. @@ -436,3 +455,14 @@ fn snapshot_rpc_getpeerinfo(get_peer_info: Vec, settings: &insta::Sett fn snapshot_rpc_getnetworksolps(get_network_sol_ps: u64, settings: &insta::Settings) { settings.bind(|| insta::assert_json_snapshot!("get_network_sol_ps", get_network_sol_ps)); } + +/// Snapshot `validateaddress` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_validateaddress( + variant: &'static str, + validate_address: validate_address::Response, + settings: &insta::Settings, +) { + settings.bind(|| { + insta::assert_json_snapshot!(format!("validate_address_{variant}"), validate_address) + }); +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_basic@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_basic@mainnet_10.snap new file mode 100644 index 00000000000..81ef95e0c8c --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_basic@mainnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: validate_address +--- +{ + "isvalid": true, + "address": "t3fqvkzrrNaMcamkQMwAyHRjfDdM2xQvDTR", + "isscript": true +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_basic@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_basic@testnet_10.snap new file mode 100644 index 00000000000..4da2812579f --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_basic@testnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: validate_address +--- +{ + "isvalid": true, + "address": "t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi", + "isscript": true +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_invalid@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_invalid@mainnet_10.snap new file mode 100644 index 00000000000..4477c64351a --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_invalid@mainnet_10.snap @@ -0,0 +1,7 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: validate_address +--- +{ + "isvalid": false +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_invalid@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_invalid@testnet_10.snap new file mode 100644 index 00000000000..4477c64351a --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/validate_address_invalid@testnet_10.snap @@ -0,0 +1,7 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: validate_address +--- +{ + "isvalid": false +} diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_1_arg@mainnet_10.snap similarity index 100% rename from zebra-rpc/src/methods/tests/snapshots/get_block_verbose@mainnet_10.snap rename to zebra-rpc/src/methods/tests/snapshots/get_block_verbose_1_arg@mainnet_10.snap diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_1_arg@testnet_10.snap similarity index 100% rename from zebra-rpc/src/methods/tests/snapshots/get_block_verbose@testnet_10.snap rename to zebra-rpc/src/methods/tests/snapshots/get_block_verbose_1_arg@testnet_10.snap diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_2_args@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_2_args@mainnet_10.snap new file mode 100644 index 00000000000..b9fc9794fa8 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_2_args@mainnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: block +--- +{ + "tx": [ + "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609" + ] +} diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_2_args@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_2_args@testnet_10.snap new file mode 100644 index 00000000000..dbc2ce43d5e --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_2_args@testnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: block +--- +{ + "tx": [ + "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75" + ] +} diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 2c23ea5f2c0..7317aaf0230 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -85,14 +85,14 @@ async fn rpc_getblock() { let expected_result = GetBlock::Raw(block.clone().into()); let get_block = rpc - .get_block(i.to_string(), 0u8) + .get_block(i.to_string(), Some(0u8)) .await .expect("We should have a GetBlock struct"); assert_eq!(get_block, expected_result); let get_block = rpc - .get_block(block.hash().to_string(), 0u8) + .get_block(block.hash().to_string(), Some(0u8)) .await .expect("We should have a GetBlock struct"); @@ -102,7 +102,25 @@ async fn rpc_getblock() { // Make calls with verbosity=1 and check response for (i, block) in blocks.iter().enumerate() { let get_block = rpc - .get_block(i.to_string(), 1u8) + .get_block(i.to_string(), Some(1u8)) + .await + .expect("We should have a GetBlock struct"); + + assert_eq!( + get_block, + GetBlock::Object { + tx: block + .transactions + .iter() + .map(|tx| tx.hash().encode_hex()) + .collect() + } + ); + } + + for (i, block) in blocks.iter().enumerate() { + let get_block = rpc + .get_block(i.to_string(), None) .await .expect("We should have a GetBlock struct"); @@ -144,7 +162,17 @@ async fn rpc_getblock_parse_error() { // Make sure we get an error if Zebra can't parse the block height. assert!(rpc - .get_block("not parsable as height".to_string(), 0u8) + .get_block("not parsable as height".to_string(), Some(0u8)) + .await + .is_err()); + + assert!(rpc + .get_block("not parsable as height".to_string(), Some(1u8)) + .await + .is_err()); + + assert!(rpc + .get_block("not parsable as height".to_string(), None) .await .is_err()); @@ -175,7 +203,7 @@ async fn rpc_getblock_missing_error() { // Make sure Zebra returns the correct error code `-8` for missing blocks // https://github.com/adityapk00/lightwalletd/blob/c1bab818a683e4de69cd952317000f9bb2932274/common/common.go#L251-L254 - let block_future = tokio::spawn(rpc.get_block("0".to_string(), 0u8)); + let block_future = tokio::spawn(rpc.get_block("0".to_string(), Some(0u8))); // Make the mock service respond with no block let response_handler = state @@ -1290,3 +1318,48 @@ async fn rpc_submitblock_errors() { // See zebrad::tests::acceptance::submit_block for success case. } + +#[cfg(feature = "getblocktemplate-rpcs")] +#[tokio::test(flavor = "multi_thread")] +async fn rpc_validateaddress() { + use get_block_template_rpcs::types::validate_address; + use zebra_chain::{chain_sync_status::MockSyncStatus, chain_tip::mock::MockChainTip}; + use zebra_network::address_book_peers::MockAddressBookPeers; + + let _init_guard = zebra_test::init(); + + let (mock_chain_tip, _mock_chain_tip_sender) = MockChainTip::new(); + + // Init RPC + let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( + Mainnet, + Default::default(), + Buffer::new(MockService::build().for_unit_tests(), 1), + MockService::build().for_unit_tests(), + mock_chain_tip, + MockService::build().for_unit_tests(), + MockSyncStatus::default(), + MockAddressBookPeers::default(), + ); + + let validate_address = get_block_template_rpc + .validate_address("t3fqvkzrrNaMcamkQMwAyHRjfDdM2xQvDTR".to_string()) + .await + .expect("we should have a validate_address::Response"); + + assert!( + validate_address.is_valid, + "Mainnet founder address should be valid on Mainnet" + ); + + let validate_address = get_block_template_rpc + .validate_address("t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".to_string()) + .await + .expect("We should have a validate_address::Response"); + + assert_eq!( + validate_address, + validate_address::Response::invalid(), + "Testnet founder address should be invalid on Mainnet" + ); +}