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

test(rpc): Create a script that submits block proposals to zcashd #5944

Merged
merged 32 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a3dfddc
Revert "Update code that we're going to delete in the next PR anyway"
teor2345 Jan 17, 2023
1d2d54c
Initial zcash-test-block-template script without block proposal const…
teor2345 Jan 11, 2023
5ec5a07
Try creating block data using jq and printf
teor2345 Jan 11, 2023
c0b1189
Move proposal_block_from_template() to zebra-rpc and remove eyre depe…
teor2345 Jan 12, 2023
97d11a9
Implement FromStr for DateTime32 and Duration32
teor2345 Jan 12, 2023
14014f4
Basic block-template-to-proposal command without time source handling
teor2345 Jan 12, 2023
d0dbe0e
Move block proposal code into its own module
teor2345 Jan 12, 2023
cd21279
Use time source in block-template-to-proposal
teor2345 Jan 12, 2023
fa66376
Make block-template-to-proposal require the getblocktemplate-rpcs fea…
teor2345 Jan 13, 2023
21ad68e
Use block-template-to-proposal in the test script zcash-rpc-block-tem…
teor2345 Jan 13, 2023
46b7b2d
Apply new hex formatting to commitments and nonces in block proposal …
teor2345 Jan 13, 2023
64efc67
Re-add missing imports from rebase
teor2345 Jan 16, 2023
14b80da
Re-add missing conversions from rebase
teor2345 Jan 16, 2023
7646a8b
Update to new method name after rebase
teor2345 Jan 16, 2023
a6ee308
Derive a Default impl
teor2345 Jan 16, 2023
d6b8ac3
Restore a comment that was accidentally deleted during the rebase
teor2345 Jan 16, 2023
4f7e37b
Avoid a clippy::unwrap-in-result
teor2345 Jan 16, 2023
ce5f66c
Temporarily fix the code for a disabled test
teor2345 Jan 16, 2023
7752bd9
Fix tool build with Docker caches
teor2345 Jan 16, 2023
68166f5
Make clippy happy with the temporary fix
teor2345 Jan 16, 2023
cdd20b3
Give a pass/fail status for each proposal response
teor2345 Jan 17, 2023
925909a
Accept zcashd block templates in block-template-to-proposal
teor2345 Jan 17, 2023
d29b925
Fix pretty printing
teor2345 Jan 17, 2023
dd6194c
Zebra expects a longpollid, so give it a dummy value
teor2345 Jan 17, 2023
66c54af
Add "required" fields which Zebra requires
teor2345 Jan 17, 2023
760f54a
Use curtime as the dummy maxtime in zcashd templates
teor2345 Jan 17, 2023
5dc6f96
Support large block proposals by reading proposal data from a file
teor2345 Jan 17, 2023
9974c29
Test all valid time modes for each proposal
teor2345 Jan 17, 2023
51d44dc
Allow the user to set the time command
teor2345 Jan 17, 2023
21f64df
Put debug logs into their own files
teor2345 Jan 17, 2023
252c97b
Exit with an error status when any proposal is invalid
teor2345 Jan 17, 2023
a295fae
Log the date and time to make it easier to match errors to node logs
teor2345 Jan 17, 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
1 change: 1 addition & 0 deletions Cargo.lock

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

28 changes: 26 additions & 2 deletions zebra-chain/src/serialization/date_time.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
//! DateTime types with specific serialization invariants.

use std::{fmt, num::TryFromIntError};
use std::{
fmt,
num::{ParseIntError, TryFromIntError},
str::FromStr,
};

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use chrono::{TimeZone, Utc};

use super::{SerializationError, ZcashDeserialize, ZcashSerialize};
use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};

/// A date and time, represented by a 32-bit number of seconds since the UNIX epoch.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
Expand Down Expand Up @@ -347,6 +351,26 @@ impl TryFrom<&std::time::Duration> for Duration32 {
}
}

impl FromStr for DateTime32 {
type Err = ParseIntError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(DateTime32 {
timestamp: s.parse()?,
})
}
}

impl FromStr for Duration32 {
type Err = ParseIntError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Duration32 {
seconds: s.parse()?,
})
}
}

impl ZcashSerialize for DateTime32 {
fn zcash_serialize<W: std::io::Write>(&self, mut writer: W) -> Result<(), std::io::Error> {
writer.write_u32::<LittleEndian>(self.timestamp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use crate::methods::{
pub mod parameters;
pub mod proposal;

pub use parameters::*;
pub use proposal::*;
pub use parameters::{GetBlockTemplateCapability, GetBlockTemplateRequestMode, JsonParameters};
pub use proposal::{proposal_block_from_template, ProposalRejectReason, ProposalResponse};

/// A serialized `getblocktemplate` RPC response in template mode.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
Expand Down Expand Up @@ -286,7 +286,7 @@ impl GetBlockTemplate {
}
}

#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
/// A `getblocktemplate` RPC response.
pub enum Response {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
//! getblocktemplate proposal mode implementation.
//!
//! `ProposalResponse` is the output of the `getblocktemplate` RPC method in 'proposal' mode.

use super::{GetBlockTemplate, Response};
use std::{num::ParseIntError, str::FromStr, sync::Arc};

use zebra_chain::{
block::{self, Block, Height},
serialization::{DateTime32, SerializationError, ZcashDeserializeInto},
work::equihash::Solution,
};

use crate::methods::{
get_block_template_rpcs::types::{
default_roots::DefaultRoots,
get_block_template::{GetBlockTemplate, Response},
},
GetBlockHash,
};

/// Error response to a `getblocktemplate` RPC request in proposal mode.
///
/// See <https://en.bitcoin.it/wiki/BIP_0022#Appendix:_Example_Rejection_Reasons>
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProposalRejectReason {
/// Block proposal rejected as invalid.
Expand All @@ -15,7 +31,7 @@ pub enum ProposalRejectReason {
/// Response to a `getblocktemplate` RPC request in proposal mode.
///
/// See <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged, rename_all = "kebab-case")]
pub enum ProposalResponse {
/// Block proposal was rejected as invalid, returns `reject-reason` and server `capabilities`.
Expand Down Expand Up @@ -57,3 +73,140 @@ impl From<GetBlockTemplate> for Response {
Self::TemplateMode(Box::new(template))
}
}

/// The source of the time in the block proposal header.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum TimeSource {
/// The `curtime` field in the template.
/// This is the default time source.
#[default]
CurTime,

/// The `mintime` field in the template.
MinTime,

/// The `maxtime` field in the template.
MaxTime,

/// The supplied time, clamped within the template's `[mintime, maxtime]`.
Clamped(DateTime32),

/// The current local clock time, clamped within the template's `[mintime, maxtime]`.
ClampedNow,

/// The raw supplied time, ignoring the `mintime` and `maxtime` in the template.
///
/// Warning: this can create an invalid block proposal.
Raw(DateTime32),

/// The raw current local time, ignoring the `mintime` and `maxtime` in the template.
///
/// Warning: this can create an invalid block proposal.
RawNow,
}

impl TimeSource {
/// Returns the time from `template` using this time source.
pub fn time_from_template(&self, template: &GetBlockTemplate) -> DateTime32 {
use TimeSource::*;

match self {
CurTime => template.cur_time,
MinTime => template.min_time,
MaxTime => template.max_time,
Clamped(time) => (*time).clamp(template.min_time, template.max_time),
ClampedNow => DateTime32::now().clamp(template.min_time, template.max_time),
Raw(time) => *time,
RawNow => DateTime32::now(),
}
}

/// Returns true if this time source uses `max_time` in any way, including clamping.
pub fn uses_max_time(&self) -> bool {
use TimeSource::*;

match self {
CurTime | MinTime => false,
MaxTime | Clamped(_) | ClampedNow => true,
Raw(_) | RawNow => false,
}
}
}

impl FromStr for TimeSource {
type Err = ParseIntError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
use TimeSource::*;

match s.to_lowercase().as_str() {
"curtime" => Ok(CurTime),
"mintime" => Ok(MinTime),
"maxtime" => Ok(MaxTime),
"clampednow" => Ok(ClampedNow),
"rawnow" => Ok(RawNow),
s => match s.strip_prefix("raw") {
// "raw"u32
Some(raw_value) => Ok(Raw(raw_value.parse()?)),
// "clamped"u32 or just u32
// this is the default if the argument is just a number
None => Ok(Clamped(s.strip_prefix("clamped").unwrap_or(s).parse()?)),
},
}
}
}

/// Returns a block proposal generated from a [`GetBlockTemplate`] RPC response.
///
/// If `time_source` is not supplied, uses the current time from the template.
pub fn proposal_block_from_template(
template: GetBlockTemplate,
time_source: impl Into<Option<TimeSource>>,
) -> Result<Block, SerializationError> {
let GetBlockTemplate {
version,
height,
previous_block_hash: GetBlockHash(previous_block_hash),
default_roots:
DefaultRoots {
merkle_root,
block_commitments_hash,
..
},
bits: difficulty_threshold,
ref coinbase_txn,
transactions: ref tx_templates,
..
} = template;

if Height(height) > Height::MAX {
Err(SerializationError::Parse(
"height field must be lower than Height::MAX",
))?;
};

let time = time_source
.into()
.unwrap_or_default()
.time_from_template(&template);

let mut transactions = vec![coinbase_txn.data.as_ref().zcash_deserialize_into()?];

for tx_template in tx_templates {
transactions.push(tx_template.data.as_ref().zcash_deserialize_into()?);
}

Ok(Block {
header: Arc::new(block::Header {
version,
previous_block_hash,
merkle_root,
commitment_bytes: block_commitments_hash.bytes_in_serialized_order().into(),
time: time.into(),
difficulty_threshold,
nonce: [0; 32].into(),
solution: Solution::for_proposal(),
}),
transactions,
})
}
24 changes: 24 additions & 0 deletions zebra-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,30 @@ authors = ["Zcash Foundation <[email protected]>"]
license = "MIT OR Apache-2.0"
version = "1.0.0-beta.19"
edition = "2021"

# Prevent accidental publication of this utility crate.
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[[bin]]
name = "block-template-to-proposal"
# this setting is required for Zebra's Docker build caches
path = "src/bin/block-template-to-proposal/main.rs"
required-features = ["getblocktemplate-rpcs"]

[features]
default = []

# Production features that activate extra dependencies, or extra features in dependencies

# Experimental mining RPC support
getblocktemplate-rpcs = [
"zebra-rpc/getblocktemplate-rpcs",
"zebra-node-services/getblocktemplate-rpcs",
"zebra-chain/getblocktemplate-rpcs",
]

[dependencies]
color-eyre = "0.6.2"
# This is a transitive dependency via color-eyre.
Expand All @@ -21,3 +42,6 @@ tracing-subscriber = "0.3.16"

zebra-node-services = { path = "../zebra-node-services" }
zebra-chain = { path = "../zebra-chain" }

# Experimental feature getblocktemplate-rpcs
zebra-rpc = { path = "../zebra-rpc", optional = true }
25 changes: 25 additions & 0 deletions zebra-utils/src/bin/block-template-to-proposal/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! block-template-to-proposal arguments
//!
//! For usage please refer to the program help: `block-template-to-proposal --help`

use structopt::StructOpt;

use zebra_rpc::methods::get_block_template_rpcs::get_block_template::proposal::TimeSource;

/// block-template-to-proposal arguments
#[derive(Clone, Debug, Eq, PartialEq, StructOpt)]
pub struct Args {
/// The source of the time in the block proposal header.
/// Format: "curtime", "mintime", "maxtime", ["clamped"]u32, "raw"u32
/// Clamped times are clamped to the template's [`mintime`, `maxtime`].
/// Raw times are used unmodified: this can produce invalid proposals.
#[structopt(default_value = "CurTime", short, long)]
pub time_source: TimeSource,

/// The JSON block template.
/// If this argument is not supplied, the template is read from standard input.
///
/// The template and proposal structures are printed to stderr.
#[structopt(last = true)]
pub template: Option<String>,
}
Loading