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

merge queue: embarking main (dc43dca) and [#6086 + #6092] together #6096

Closed
wants to merge 12 commits into from
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ default = []
# Production features that activate extra functionality

# Experimental mining RPC support
getblocktemplate-rpcs = []
getblocktemplate-rpcs = [
"zcash_address",
]

# Test-only features

Expand Down Expand Up @@ -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 }
Expand Down
7 changes: 7 additions & 0 deletions zebra-chain/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
122 changes: 122 additions & 0 deletions zebra-chain/src/primitives/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//! `zcash_address` conversion to `zebra_chain` address types.
//!
//! Usage: <https://docs.rs/zcash_address/0.2.0/zcash_address/trait.TryFromAddress.html#examples>

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<zcash_address::Network> for Network {
// TODO: better error type
type Error = BoxError;

fn try_from(network: zcash_address::Network) -> Result<Self, Self::Error> {
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<Network> 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<Self, zcash_address::ConversionError<Self::Error>> {
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<Self, zcash_address::ConversionError<Self::Error>> {
Ok(Self::Transparent(transparent::Address::from_script_hash(
network.try_into()?,
data,
)))
}

fn try_from_sapling(
network: zcash_address::Network,
data: [u8; 43],
) -> Result<Self, zcash_address::ConversionError<Self::Error>> {
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(_))
}
}
3 changes: 3 additions & 0 deletions zebra-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 }
Expand Down
12 changes: 10 additions & 2 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Result<GetBlock>>;
fn get_block(&self, height: String, verbosity: Option<u8>) -> BoxFuture<Result<GetBlock>>;

/// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string.
///
Expand Down Expand Up @@ -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<Result<GetBlock>> {
fn get_block(
&self,
hash_or_height: String,
verbosity: Option<u8>,
) -> BoxFuture<Result<GetBlock>> {
// From <https://zcash.github.io/rpc/getblock.html>
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 =
Expand Down
56 changes: 56 additions & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -167,6 +171,13 @@ pub trait GetBlockTemplateRpc {
#[rpc(name = "getpeerinfo")]
fn get_peer_info(&self) -> BoxFuture<Result<Vec<PeerInfo>>>;

/// 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<Result<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 @@ -788,6 +799,51 @@ where
.boxed()
}

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

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

let address = match address
.convert::<primitives::Address>() {
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<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 @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>);
Original file line number Diff line number Diff line change
@@ -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<String>,

/// If the key is a script.
#[serde(rename = "isscript")]
#[serde(skip_serializing_if = "Option::is_none")]
pub is_script: Option<bool>,
}

impl Response {
/// Creates an empty response with `isvalid` of false.
pub fn invalid() -> Self {
Self::default()
}
}
21 changes: 16 additions & 5 deletions zebra-rpc/src/methods/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Loading