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

add(rpc): Add extra getblock RPC fields used by some mining pools #6097

Merged
merged 7 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 101 additions & 13 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,29 +117,32 @@ pub trait Rpc {
raw_transaction_hex: String,
) -> BoxFuture<Result<SentTransactionHash>>;

/// Returns the requested block by height, as a [`GetBlock`] JSON string.
/// Returns the requested block by hash or height, as a [`GetBlock`] JSON string.
/// If the block is not in Zebra's state, returns
/// [error code `-8`.](https://github.com/zcash/zcash/issues/5758)
///
/// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html)
///
/// # Parameters
///
/// - `height`: (string, required) The height number for the block to be returned.
/// - `hash|height`: (string, required) The hash or height for the block to be returned.
/// - `verbosity`: (numeric, optional, default=1) 0 for hex encoded data, 1 for a json object,
/// and 2 for json object with transaction data.
///
/// # Notes
///
/// With verbosity=1, [`lightwalletd` only reads the `tx` field of the
/// result](https://github.com/zcash/lightwalletd/blob/dfac02093d85fb31fb9a8475b884dd6abca966c7/common/common.go#L152),
/// so we only return that for now.
/// and other clients only read the `hash` and `confirmations` fields,
/// so we only return a few fields for now.
///
/// `lightwalletd` only requests blocks by height, so we don't support
/// 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.
/// `lightwalletd` and mining clients also do not use verbosity=2, so we don't support it.
#[rpc(name = "getblock")]
fn get_block(&self, height: String, verbosity: Option<u8>) -> BoxFuture<Result<GetBlock>>;
fn get_block(
&self,
hash_or_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 @@ -578,6 +581,10 @@ where
})?;

if verbosity == 0 {
// # Performance
//
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
// so it needs to load block data very efficiently.
let request = zebra_state::ReadRequest::Block(hash_or_height);
let response = state
.ready()
Expand All @@ -601,6 +608,28 @@ where
_ => unreachable!("unmatched response to a block request"),
}
} else if verbosity == 1 {
// # Performance
//
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
// so it needs to load all its fields very efficiently.
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
//
// Currently, we get the transaction IDs from an index, which is much more
// efficient than loading all the block data and hashing all the transactions.

// TODO: look up the hash if we only have a height,
// and look up the height if we only have a hash
let hash = hash_or_height.hash().map(GetBlockHash);
let height = hash_or_height.height();

// TODO: do these state queries in parallel?
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

// Get transaction IDs from the transaction index
//
// # Concurrency
//
// A block's transaction IDs are never modified, so all possible responses are
// valid. Clients that query block heights must be able to handle chain forks,
// including getting transaction IDs from any chain fork.
let request = zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height);
let response = state
.ready()
Expand All @@ -611,19 +640,62 @@ where
message: error.to_string(),
data: None,
})?;

match response {
let tx = match response {
zebra_state::ReadResponse::TransactionIdsForBlock(Some(tx_ids)) => {
let tx_ids = tx_ids.iter().map(|tx_id| tx_id.encode_hex()).collect();
Ok(GetBlock::Object { tx: tx_ids })
tx_ids.iter().map(|tx_id| tx_id.encode_hex()).collect()
}
zebra_state::ReadResponse::TransactionIdsForBlock(None) => Err(Error {
code: MISSING_BLOCK_ERROR_CODE,
message: "Block not found".to_string(),
data: None,
}),
})?,
_ => unreachable!("unmatched response to a transaction_ids_for_block request"),
}
};

// Get block confirmations from the block height index
//
// # Concurrency
//
// All possible responses are valid, even if a block is added to the chain, or
// the best chain changes. Clients must be able to handle chain forks, including
// different confirmation values before or after added blocks, and switching
// between -1 and multiple different confirmation values.

// From <https://zcash.github.io/rpc/getblock.html>
const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;

let confirmations = if let Some(hash) = hash_or_height.hash() {
let request = zebra_state::ReadRequest::Depth(hash);
let response = state
.ready()
.and_then(|service| service.call(request))
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;

match response {
// Confirmations are one more than the depth.
// Depth is limited by height, so it will never overflow an i64.
zebra_state::ReadResponse::Depth(Some(depth)) => Some(i64::from(depth) + 1),
zebra_state::ReadResponse::Depth(None) => {
Some(NOT_IN_BEST_CHAIN_CONFIRMATIONS)
}
_ => unreachable!("unmatched response to a depth request"),
}
} else {
// TODO: make Depth support heights as well
arya2 marked this conversation as resolved.
Show resolved Hide resolved
None
};

Ok(GetBlock::Object {
hash,
confirmations,
height,
tx,
})
} else {
Err(Error {
code: ErrorCode::InvalidParams,
Expand Down Expand Up @@ -1221,7 +1293,22 @@ pub enum GetBlock {
Raw(#[serde(with = "hex")] SerializedBlock),
/// The block object.
Object {
/// The hash of the requested block.
#[serde(skip_serializing_if = "Option::is_none")]
hash: Option<GetBlockHash>,

/// The number of confirmations of this block in the best chain,
/// or -1 if it is not in the best chain.
#[serde(skip_serializing_if = "Option::is_none")]
confirmations: Option<i64>,

/// The height of the requested block.
#[serde(skip_serializing_if = "Option::is_none")]
height: Option<Height>,

/// List of transaction IDs in block order, hex-encoded.
//
// TODO: use a typed Vec<transaction::Hash> here
tx: Vec<String>,
},
}
Expand All @@ -1232,6 +1319,7 @@ pub enum GetBlock {
///
/// Also see the notes for the [`Rpc::get_best_block_hash`] and `get_block_hash` methods.
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
pub struct GetBlockHash(#[serde(with = "hex")] pub block::Hash);

/// Response to a `z_gettreestate` RPC request.
Expand Down
10 changes: 5 additions & 5 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,21 +825,21 @@ where
return Ok(validate_address::Response::invalid());
}

return Ok(if address.network() == network {
validate_address::Response {
if address.network() == network {
Ok(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()
});
Ok(validate_address::Response::invalid())
}
}
.boxed()
}
Expand Down
55 changes: 47 additions & 8 deletions zebra-rpc/src/methods/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,27 +103,61 @@ async fn test_rpc_response_data_for_network(network: Network) {
.expect("We should have an AddressBalance struct");
snapshot_rpc_getaddressbalance(get_address_balance, &settings);

// `getblock`, verbosity=0
// `getblock` variants
const BLOCK_HEIGHT: u32 = 1;
let block_hash = blocks[BLOCK_HEIGHT as usize].hash();

// `getblock`, verbosity=0, height
let get_block = rpc
.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);
snapshot_rpc_getblock_data(
"height_verbosity_0",
get_block,
block_data.get(&BLOCK_HEIGHT).unwrap(),
&settings,
);

// `getblock`, verbosity=0, hash
let get_block = rpc
.get_block(block_hash.to_string(), Some(0u8))
.await
.expect("We should have a GetBlock struct");
snapshot_rpc_getblock_data(
"hash_verbosity_0",
get_block,
block_data.get(&BLOCK_HEIGHT).unwrap(),
&settings,
);

// `getblock`, verbosity=1
// `getblock`, verbosity=1, height
let get_block = rpc
.get_block(BLOCK_HEIGHT.to_string(), Some(1u8))
.await
.expect("We should have a GetBlock struct");
snapshot_rpc_getblock_verbose("2_args", get_block, &settings);
snapshot_rpc_getblock_verbose("height_verbosity_1", get_block, &settings);

// `getblock`, no verbosity, defaults to 1
// `getblock`, verbosity=1, hash
let get_block = rpc
.get_block(block_hash.to_string(), Some(1u8))
.await
.expect("We should have a GetBlock struct");
snapshot_rpc_getblock_verbose("hash_verbosity_1", get_block, &settings);

// `getblock`, no verbosity - defaults to 1, height
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);
snapshot_rpc_getblock_verbose("height_verbosity_default", get_block, &settings);

// `getblock`, no verbosity - defaults to 1, hash
let get_block = rpc
.get_block(block_hash.to_string(), None)
.await
.expect("We should have a GetBlock struct");
snapshot_rpc_getblock_verbose("hash_verbosity_default", get_block, &settings);

// `getbestblockhash`
let get_best_block_hash = rpc
Expand Down Expand Up @@ -242,11 +276,16 @@ fn snapshot_rpc_getaddressbalance(address_balance: AddressBalance, settings: &in
/// Check `getblock` response, using `cargo insta`, JSON serialization, and block test vectors.
///
/// The snapshot file does not contain any data, but it does enforce the response format.
fn snapshot_rpc_getblock(block: GetBlock, block_data: &[u8], settings: &insta::Settings) {
fn snapshot_rpc_getblock_data(
variant: &'static str,
block: GetBlock,
block_data: &[u8],
settings: &insta::Settings,
) {
let block_data = hex::encode(block_data);

settings.bind(|| {
insta::assert_json_snapshot!("get_block", block, {
insta::assert_json_snapshot!(format!("get_block_data_{variant}"), block, {
"." => dynamic_redaction(move |value, _path| {
// assert that the block data matches, without creating a 1.5 kB snapshot file
assert_eq!(value.as_str().unwrap(), block_data);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
"[BlockData]"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
"[BlockData]"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"hash": "0007bc227e1c57a4a70e237cad00e7b7ce565155ab49166bc57397a26d339283",
"confirmations": 10,
"tx": [
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"hash": "025579869bcf52a989337342f5f57a84f3a28b968f7d6a8307902b065a668d23",
"confirmations": 10,
"tx": [
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"hash": "0007bc227e1c57a4a70e237cad00e7b7ce565155ab49166bc57397a26d339283",
"confirmations": 10,
"tx": [
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"hash": "025579869bcf52a989337342f5f57a84f3a28b968f7d6a8307902b065a668d23",
"confirmations": 10,
"tx": [
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"height": 1,
"tx": [
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"height": 1,
"tx": [
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"height": 1,
"tx": [
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source: zebra-rpc/src/methods/tests/snapshot.rs
expression: block
---
{
"height": 1,
"tx": [
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
]
Expand Down
Loading