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

change(rpc): Add proposal capability to getblocktemplate #5870

Merged
merged 30 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
37d5afd
adds ValidateBlock request to state
arya2 Dec 15, 2022
6eb6048
adds `Request` enum in block verifier
arya2 Dec 15, 2022
162dedb
uses new Request in references to chain verifier
arya2 Dec 15, 2022
f7c5941
adds getblocktemplate proposal mode response type
arya2 Dec 15, 2022
9e0b8d1
makes getblocktemplate-rpcs feature in zebra-consensus select getbloc…
arya2 Dec 15, 2022
8420b75
Adds PR review revisions
arya2 Dec 16, 2022
e3b6d28
adds info log in CheckBlockProposalValidity
arya2 Dec 16, 2022
95cea02
Reverts replacement of match statement
arya2 Dec 16, 2022
31472a8
adds `GetBlockTemplate::capabilities` fn
arya2 Dec 16, 2022
ba302f7
conditions calling checkpoint verifier on !request.is_proposal
arya2 Dec 16, 2022
02b6758
updates references to validate_and_commit_non_finalized
arya2 Dec 16, 2022
2faa823
adds snapshot test, updates test vectors
arya2 Dec 16, 2022
32e6ac9
adds `should_count_metrics` to NonFinalizedState
arya2 Dec 16, 2022
a2ae3fb
Returns an error from chain verifier for block proposal requests belo…
arya2 Dec 16, 2022
40d002a
adds "proposal" to GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD
arya2 Dec 17, 2022
1b658a1
Merge branch 'main' into gbt-proposal-mode
arya2 Dec 19, 2022
da1b308
adds back block::Request to zebra-consensus lib
arya2 Dec 19, 2022
cb26b8a
updates snapshots
arya2 Dec 19, 2022
78e95d9
Removes unnecessary network arg
arya2 Dec 22, 2022
0c55a67
skips req in tracing intstrument for read state
arya2 Dec 22, 2022
616d280
Moves out block proposal validation to its own fn
arya2 Dec 22, 2022
8978541
corrects `difficulty_threshold_is_valid` docs
arya2 Dec 23, 2022
4551cf0
Update zebra-state/src/service.rs
arya2 Jan 5, 2023
e888039
Merge branch 'main' into gbt-proposal-mode
arya2 Jan 5, 2023
5cc7378
Apply suggestions from code review
arya2 Jan 10, 2023
67bf095
Update zebra-rpc/src/methods/get_block_template_rpcs.rs
arya2 Jan 10, 2023
f774822
check best chain tip
arya2 Jan 9, 2023
d75e17a
Update zebra-state/src/service.rs
arya2 Jan 10, 2023
9ce7d8a
Applies cleanup suggestions from code review
arya2 Jan 10, 2023
5a0850c
Merge branch 'main' into gbt-proposal-mode
mergify[bot] Jan 11, 2023
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
5 changes: 4 additions & 1 deletion zebra-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ edition = "2021"

[features]
default = []
getblocktemplate-rpcs = []
getblocktemplate-rpcs = [
"zebra-state/getblocktemplate-rpcs",
"zebra-chain/getblocktemplate-rpcs",
]
proptest-impl = ["proptest", "proptest-derive", "zebra-chain/proptest-impl", "zebra-state/proptest-impl"]

[dependencies]
Expand Down
54 changes: 41 additions & 13 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,17 @@ use thiserror::Error;
use tower::{Service, ServiceExt};
use tracing::Instrument;

use zebra_chain::{
amount::Amount,
block::{self, Block},
parameters::Network,
transparent,
work::equihash,
};
use zebra_chain::{amount::Amount, block, parameters::Network, transparent, work::equihash};
use zebra_state as zs;

use crate::{error::*, transaction as tx, BoxError};

pub mod check;
pub mod request;
pub mod subsidy;

pub use request::Request;
arya2 marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -74,6 +71,11 @@ pub enum VerifyBlockError {
// TODO: make this into a concrete type, and add it to is_duplicate_request() (#2908)
Commit(#[source] BoxError),

#[cfg(feature = "getblocktemplate-rpcs")]
#[error("unable to validate block proposal: failed semantic verification (proof of work is not checked for proposals)")]
// TODO: make this into a concrete type (see #5732)
ValidateProposal(#[source] BoxError),

#[error("invalid transaction")]
Transaction(#[from] TransactionError),
}
Expand Down Expand Up @@ -115,7 +117,7 @@ where
}
}

impl<S, V> Service<Arc<Block>> for BlockVerifier<S, V>
impl<S, V> Service<Request> for BlockVerifier<S, V>
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
Expand All @@ -134,11 +136,13 @@ where
Poll::Ready(Ok(()))
}

fn call(&mut self, block: Arc<Block>) -> Self::Future {
fn call(&mut self, request: Request) -> Self::Future {
let mut state_service = self.state_service.clone();
let mut transaction_verifier = self.transaction_verifier.clone();
let network = self.network;

let block = request.block();

// We don't include the block hash, because it's likely already in a parent span
let span = tracing::debug_span!("block", height = ?block.coinbase_height());

Expand Down Expand Up @@ -172,10 +176,17 @@ where
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
}

// Do the difficulty checks first, to raise the threshold for
// attacks that use any other fields.
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
check::equihash_solution_is_valid(&block.header)?;
// > The block data MUST be validated and checked against the server's usual
// > acceptance rules (excluding the check for a valid proof-of-work).
// <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
if request.is_proposal() {
check::difficulty_threshold_is_valid(&block.header, network, &height, &hash)?;
} else {
// Do the difficulty checks first, to raise the threshold for
// attacks that use any other fields.
check::difficulty_is_valid(&block.header, network, &height, &hash)?;
check::equihash_solution_is_valid(&block.header)?;
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
}

// Next, check the Merkle root validity, to ensure that
// the header binds to the transactions in the blocks.
Expand Down Expand Up @@ -279,6 +290,23 @@ where
new_outputs,
transaction_hashes,
};

// Return early for proposal requests when getblocktemplate-rpcs feature is enabled
#[cfg(feature = "getblocktemplate-rpcs")]
if request.is_proposal() {
return match state_service
.ready()
.await
.map_err(VerifyBlockError::ValidateProposal)?
.call(zs::Request::CheckBlockProposalValidity(prepared_block))
.await
.map_err(VerifyBlockError::ValidateProposal)?
{
zs::Response::ValidBlockProposal => Ok(hash),
_ => unreachable!("wrong response for CheckBlockProposalValidity"),
};
}

match state_service
.ready()
.await
Expand Down
34 changes: 26 additions & 8 deletions zebra-consensus/src/block/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,22 @@ pub fn coinbase_is_first(block: &Block) -> Result<Arc<transaction::Transaction>,
Ok(first.clone())
}

/// Returns `Ok(())` if `hash` passes:
/// - the target difficulty limit for `network` (PoWLimit), and
/// - the difficulty filter,
/// based on the fields in `header`.
/// Returns `Ok(ExpandedDifficulty)` if the`difficulty_threshold` of `header` is at least as difficult as
/// the target difficulty limit for `network` (PoWLimit)
///
/// If the block is invalid, returns an error containing `height` and `hash`.
pub fn difficulty_is_valid(
/// If the header difficulty threshold is invalid, returns an error containing `height` and `hash`.
pub fn difficulty_threshold_is_valid(
header: &Header,
network: Network,
height: &Height,
hash: &Hash,
) -> Result<(), BlockError> {
) -> Result<ExpandedDifficulty, BlockError> {
let difficulty_threshold = header
.difficulty_threshold
.to_expanded()
.ok_or(BlockError::InvalidDifficulty(*height, *hash))?;

// Note: the comparisons in this function are u256 integer comparisons, like
// Note: the comparison in this function is a u256 integer comparison, like
// zcashd and bitcoin. Greater values represent *less* work.

// The PowLimit check is part of `Threshold()` in the spec, but it doesn't
Expand All @@ -90,6 +88,26 @@ pub fn difficulty_is_valid(
))?;
}

Ok(difficulty_threshold)
}

/// Returns `Ok(())` if `hash` passes:
/// - the target difficulty limit for `network` (PoWLimit), and
/// - the difficulty filter,
/// based on the fields in `header`.
///
/// If the block is invalid, returns an error containing `height` and `hash`.
pub fn difficulty_is_valid(
header: &Header,
network: Network,
height: &Height,
hash: &Hash,
) -> Result<(), BlockError> {
let difficulty_threshold = difficulty_threshold_is_valid(header, network, height, hash)?;

// Note: the comparison in this function is a u256 integer comparison, like
// zcashd and bitcoin. Greater values represent *less* work.

// # Consensus
//
// > The block MUST pass the difficulty filter.
Expand Down
40 changes: 40 additions & 0 deletions zebra-consensus/src/block/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! Block verifier request type.

use std::sync::Arc;

use zebra_chain::block::Block;

#[derive(Debug, Clone, PartialEq, Eq)]
/// A request to the chain or block verifier
pub enum Request {
/// Performs semantic validation, then asks the state to perform contextual validation and commit the block
Commit(Arc<Block>),

#[cfg(feature = "getblocktemplate-rpcs")]
/// Performs semantic validation but skips checking proof of work,
/// then asks the state to perform contextual validation.
/// Does not commit the block to the state.
CheckProposal(Arc<Block>),
}

impl Request {
/// Returns inner block
pub fn block(&self) -> Arc<Block> {
Arc::clone(match self {
Request::Commit(block) => block,

#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckProposal(block) => block,
})
}

/// Returns `true` if the request is a proposal
pub fn is_proposal(&self) -> bool {
match self {
Request::Commit(_) => false,

#[cfg(feature = "getblocktemplate-rpcs")]
Request::CheckProposal(_) => true,
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
50 changes: 32 additions & 18 deletions zebra-consensus/src/block/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,18 @@ use crate::{parameters::SLOW_START_SHIFT, transaction};

use super::*;

static VALID_BLOCK_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
.unwrap()
.into();
let hash = Ok(block.as_ref().into());
vec![(block, hash)]
});
static VALID_BLOCK_TRANSCRIPT: Lazy<Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>> =
Lazy::new(|| {
let block: Arc<_> =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..])
.unwrap()
.into();
let hash = Ok(block.as_ref().into());
vec![(Request::Commit(block), hash)]
});

static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let mut block: Block =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();
Expand All @@ -55,23 +54,29 @@ static INVALID_TIME_BLOCK_TRANSCRIPT: Lazy<
.unwrap();
Arc::make_mut(&mut block.header).time = three_hours_in_the_future;

vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
vec![(
Request::Commit(Arc::new(block)),
Err(ExpectedTranscriptError::Any),
)]
});

static INVALID_HEADER_SOLUTION_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let mut block: Block =
Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap();

// Change nonce to something invalid
Arc::make_mut(&mut block.header).nonce = [0; 32];

vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))]
vec![(
Request::Commit(Arc::new(block)),
Err(ExpectedTranscriptError::Any),
)]
});

static INVALID_COINBASE_TRANSCRIPT: Lazy<
Vec<(Arc<Block>, Result<block::Hash, ExpectedTranscriptError>)>,
Vec<(Request, Result<block::Hash, ExpectedTranscriptError>)>,
> = Lazy::new(|| {
let header = block::Header::zcash_deserialize(&zebra_test::vectors::DUMMY_HEADER[..]).unwrap();

Expand Down Expand Up @@ -105,9 +110,18 @@ static INVALID_COINBASE_TRANSCRIPT: Lazy<
assert_eq!(block3.transactions.len(), 2);

vec![
(Arc::new(block1), Err(ExpectedTranscriptError::Any)),
(Arc::new(block2), Err(ExpectedTranscriptError::Any)),
(Arc::new(block3), Err(ExpectedTranscriptError::Any)),
(
Request::Commit(Arc::new(block1)),
Err(ExpectedTranscriptError::Any),
),
(
Request::Commit(Arc::new(block2)),
Err(ExpectedTranscriptError::Any),
),
(
Request::Commit(Arc::new(block3)),
Err(ExpectedTranscriptError::Any),
),
]
});

Expand Down
29 changes: 21 additions & 8 deletions zebra-consensus/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};

Expand All @@ -27,15 +26,14 @@ use tower::{buffer::Buffer, util::BoxService, Service, ServiceExt};
use tracing::{instrument, Span};

use zebra_chain::{
block::{self, Block, Height},
block::{self, Height},
parameters::Network,
};

use zebra_state as zs;

use crate::{
block::BlockVerifier,
block::VerifyBlockError,
block::{BlockVerifier, Request, VerifyBlockError},
checkpoint::{CheckpointList, CheckpointVerifier, VerifyCheckpointError},
error::TransactionError,
transaction, BoxError, Config,
Expand Down Expand Up @@ -129,7 +127,7 @@ impl VerifyChainError {
}
}

impl<S, V> Service<Arc<Block>> for ChainVerifier<S, V>
impl<S, V> Service<Request> for ChainVerifier<S, V>
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
Expand Down Expand Up @@ -167,14 +165,29 @@ where
Poll::Ready(Ok(()))
}

fn call(&mut self, block: Arc<Block>) -> Self::Future {
fn call(&mut self, request: Request) -> Self::Future {
let block = request.block();

match block.coinbase_height() {
#[cfg(feature = "getblocktemplate-rpcs")]
// There's currently no known use case for block proposals below the checkpoint height,
// so it's okay to immediately return an error here.
Some(height) if height <= self.max_checkpoint_height && request.is_proposal() => {
async {
// TODO: Add a `ValidateProposalError` enum with a `BelowCheckpoint` variant?
Err(VerifyBlockError::ValidateProposal(
"block proposals must be above checkpoint height".into(),
))?
}
.boxed()
}

Some(height) if height <= self.max_checkpoint_height => {
self.checkpoint.call(block).map_err(Into::into).boxed()
teor2345 marked this conversation as resolved.
Show resolved Hide resolved
}
// This also covers blocks with no height, which the block verifier
// will reject immediately.
_ => self.block.call(block).map_err(Into::into).boxed(),
_ => self.block.call(request).map_err(Into::into).boxed(),
}
}
}
Expand Down Expand Up @@ -219,7 +232,7 @@ pub async fn init<S>(
mut state_service: S,
debug_skip_parameter_preload: bool,
) -> (
Buffer<BoxService<Arc<Block>, block::Hash, VerifyChainError>, Arc<Block>>,
Buffer<BoxService<Request, block::Hash, VerifyChainError>, Request>,
Buffer<
BoxService<transaction::Request, transaction::Response, TransactionError>,
transaction::Request,
Expand Down
Loading