Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): Implement the z_validateaddress RPC #6185

Merged
merged 14 commits into from
Feb 20, 2023
21 changes: 10 additions & 11 deletions zebra-chain/src/primitives/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

use zcash_primitives::sapling;

use crate::{orchard, parameters::Network, transparent, BoxError};
use crate::{parameters::Network, transparent, BoxError};

/// Zcash address variants
// TODO: Add Sprout addresses
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
pub enum Address {
/// Transparent address
Transparent(transparent::Address),
Expand All @@ -26,14 +25,8 @@ pub enum Address {
/// Address' network
network: Network,

/// Transparent address
transparent_address: transparent::Address,

/// Sapling address
sapling_address: sapling::PaymentAddress,

/// Orchard address
orchard_address: orchard::Address,
/// Unified address
address: zcash_address::unified::Address,
upbqdn marked this conversation as resolved.
Show resolved Hide resolved
},
}

Expand Down Expand Up @@ -93,7 +86,13 @@ impl zcash_address::TryFromAddress for Address {
.ok_or_else(|| BoxError::from("not a valid sapling address").into())
}

// TODO: Add sprout and unified/orchard converters
fn try_from_unified(
network: zcash_address::Network,
address: zcash_address::unified::Address,
) -> Result<Self, zcash_address::ConversionError<Self::Error>> {
let network = network.try_into()?;
Ok(Self::Unified { network, address })
}
}

impl Address {
Expand Down
55 changes: 55 additions & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ use crate::methods::{
height_from_signed_int, GetBlockHash, MISSING_BLOCK_ERROR_CODE,
};

use self::types::z_validate_address::{self, AddressType};
upbqdn marked this conversation as resolved.
Show resolved Hide resolved

pub mod config;
pub mod constants;
pub mod get_block_template;
Expand Down Expand Up @@ -179,6 +181,16 @@ pub trait GetBlockTemplateRpc {
#[rpc(name = "validateaddress")]
fn validate_address(&self, address: String) -> BoxFuture<Result<validate_address::Response>>;

/// Checks if a zcash address is valid.
/// Returns information about the given address if valid.
///
/// zcashd reference: [`z_validateaddress`](https://zcash.github.io/rpc/z_validateaddress.html)
#[rpc(name = "z_validateaddress")]
fn z_validate_address(
&self,
address: String,
) -> BoxFuture<Result<types::z_validate_address::Response>>;

/// 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.
///
Expand Down Expand Up @@ -851,6 +863,49 @@ where
.boxed()
}

fn z_validate_address(
&self,
raw_address: String,
) -> BoxFuture<Result<types::z_validate_address::Response>> {
let network = self.network;

async move {
let Ok(address) = raw_address
.parse::<zcash_address::ZcashAddress>() else {
return Ok(z_validate_address::Response::invalid());
};

let address = match address
.convert::<primitives::Address>() {
Ok(address) => address,
Err(err) => {
tracing::debug!(?err, "conversion error");
return Ok(z_validate_address::Response::invalid());
}
};

if address.network() == network {
Ok(z_validate_address::Response {
is_valid: true,
address: Some(raw_address),
address_type: Some(AddressType::from(&address)),
is_mine: Some(false),
})
} else {
tracing::info!(
?network,
address_network = ?address.network(),
"invalid address network in z_validateaddress RPC: address is for {:?} but Zebra is on {:?}",
address.network(),
network
);
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

Ok(z_validate_address::Response::invalid())
}
}
.boxed()
}

fn get_block_subsidy(&self, height: Option<u32>) -> BoxFuture<Result<BlockSubsidy>> {
let latest_chain_tip = self.latest_chain_tip.clone();
let network = self.network;
Expand Down
1 change: 1 addition & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ pub mod submit_block;
pub mod subsidy;
pub mod transaction;
pub mod validate_address;
pub mod z_validate_address;
pub mod zec;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! Response type for the `z_validateaddress` RPC.

use zebra_chain::primitives::Address;

/// `z_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<String>,

/// The type of the address.
#[serde(skip_serializing_if = "Option::is_none")]
pub address_type: Option<AddressType>,

/// Whether the address is yours or not.
///
/// Always false for now since Zebra doesn't have a wallet yet.
#[serde(rename = "ismine")]
#[serde(skip_serializing_if = "Option::is_none")]
pub is_mine: Option<bool>,
}

impl Response {
/// Creates an empty response with `isvalid` of false.
pub fn invalid() -> Self {
Self::default()
}
}

/// Address types supported by the `z_validateaddress` RPC according to
/// <https://zcash.github.io/rpc/z_validateaddress.html>.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum AddressType {
/// The `p2pkh` address type.
P2pkh,
/// The `p2sh` address type.
P2sh,
/// The `sapling` address type.
Sapling,
/// The `unified` address type.
Unified,
}

impl From<&Address> for AddressType {
fn from(address: &Address) -> Self {
match address {
Address::Transparent(_) => {
if address.is_script_hash() {
Self::P2sh
} else {
Self::P2pkh
}
}
Address::Sapling { .. } => Self::Sapling,
Address::Unified { .. } => Self::Unified,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use crate::methods::{
peer_info::PeerInfo,
submit_block,
subsidy::BlockSubsidy,
validate_address,
validate_address, z_validate_address,
},
},
tests::utils::fake_history_tree,
Expand Down Expand Up @@ -395,6 +395,24 @@ pub async fn test_responses<State, ReadState>(
.expect("We should have a validate_address::Response");
snapshot_rpc_validateaddress("invalid", validate_address, &settings);

// `z_validateaddress`
let founder_address = match network {
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
Network::Mainnet => "t3fqvkzrrNaMcamkQMwAyHRjfDdM2xQvDTR",
Network::Testnet => "t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi",
};

let z_validate_address = get_block_template_rpc
.z_validate_address(founder_address.to_string())
.await
.expect("We should have a z_validate_address::Response");
snapshot_rpc_z_validateaddress("basic", z_validate_address, &settings);

let z_validate_address = get_block_template_rpc
.z_validate_address("".to_string())
.await
.expect("We should have a z_validate_address::Response");
snapshot_rpc_z_validateaddress("invalid", z_validate_address, &settings);

// getdifficulty

// Fake the ChainInfo response
Expand Down Expand Up @@ -499,6 +517,17 @@ fn snapshot_rpc_validateaddress(
});
}

/// Snapshot `z_validateaddress` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_z_validateaddress(
variant: &'static str,
z_validate_address: z_validate_address::Response,
settings: &insta::Settings,
) {
settings.bind(|| {
insta::assert_json_snapshot!(format!("z_validate_address_{variant}"), z_validate_address)
});
}
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

/// Snapshot `getdifficulty` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getdifficulty(difficulty: f64, settings: &insta::Settings) {
settings.bind(|| insta::assert_json_snapshot!("get_difficulty", difficulty));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
upbqdn marked this conversation as resolved.
Show resolved Hide resolved
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
assertion_line: 527
expression: z_validate_address
---
{
"isvalid": true,
"address": "t3fqvkzrrNaMcamkQMwAyHRjfDdM2xQvDTR",
"address_type": "p2sh",
"ismine": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
assertion_line: 527
expression: z_validate_address
---
{
"isvalid": true,
"address": "t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi",
"address_type": "p2sh",
"ismine": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
assertion_line: 527
expression: z_validate_address
---
{
"isvalid": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
assertion_line: 527
expression: z_validate_address
---
{
"isvalid": false
}
45 changes: 45 additions & 0 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,51 @@ async fn rpc_validateaddress() {
);
}

#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_z_validateaddress() {
use get_block_template_rpcs::types::z_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 z_validate_address = get_block_template_rpc
.z_validate_address("t3fqvkzrrNaMcamkQMwAyHRjfDdM2xQvDTR".to_string())
.await
.expect("we should have a z_validate_address::Response");

assert!(
z_validate_address.is_valid,
"Mainnet founder address should be valid on Mainnet"
);

let z_validate_address = get_block_template_rpc
.z_validate_address("t2UNzUUx8mWBCRYPRezvA363EYXyEpHokyi".to_string())
.await
.expect("We should have a z_validate_address::Response");

assert_eq!(
z_validate_address,
z_validate_address::Response::invalid(),
"Testnet founder address should be invalid on Mainnet"
);
}

#[cfg(feature = "getblocktemplate-rpcs")]
#[tokio::test(flavor = "multi_thread")]
async fn rpc_getdifficulty() {
Expand Down