Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
271ab5b
feat(mining): advertise admitted blocks optimistically
evan-forbes Aug 20, 2026
f27cb7f
fix(mining): harden optimistic inventory
evan-forbes Aug 20, 2026
5a6a7a9
fix(mining): correct optimistic inventory validation
evan-forbes Aug 20, 2026
2b3b8fa
test(rpc): include workid in template fixture
evan-forbes Aug 20, 2026
0e85f29
fix(mining): harden optimistic block inventory
evan-forbes Aug 20, 2026
863e443
chore(rpc): regenerate OpenRPC artifact
evan-forbes Aug 22, 2026
7dc2212
fix(mining): require expected work before optimistic relay
evan-forbes Aug 23, 2026
e5e4312
test(mining): allow safe committed inventory fallback
evan-forbes Aug 23, 2026
dbfc7fc
fix(state): measure writer queue directly
evan-forbes Aug 22, 2026
683d186
perf(state): measure mined block admission timing
evan-forbes Aug 23, 2026
e112370
fix(state): import writer timing clock
evan-forbes Aug 23, 2026
bdf35bb
fix(state): make admission metrics composable
evan-forbes Aug 23, 2026
5d7473f
perf(state): measure contextual phases
evan-forbes Aug 22, 2026
abc601b
fix(state): make contextual phase metrics actionable
evan-forbes Aug 23, 2026
ee3bf0a
fix(mining): address final review hardening
evan-forbes Aug 24, 2026
a4c2ccb
Merge remote-tracking branch 'origin/main' into audit/pr748-hardening
evan-forbes Aug 24, 2026
522c701
docs(mining): clarify hardening invariants
evan-forbes Aug 24, 2026
7a234f5
Merge remote-tracking branch 'origin/feat/optimistic-mined-block-inve…
evan-forbes Aug 24, 2026
c883633
Merge remote-tracking branch 'origin/perf/contextual-profile-mileston…
evan-forbes Aug 24, 2026
9470485
fix(state): exclude proposals from contextual metrics
evan-forbes Aug 24, 2026
c13343e
Merge remote-tracking branch 'origin/main' into feat/optimistic-mined…
evan-forbes Aug 24, 2026
47b65ca
Merge remote-tracking branch 'origin/feat/optimistic-mined-block-inve…
evan-forbes Aug 24, 2026
1f15acc
Merge remote-tracking branch 'origin/perf/contextual-profile-mileston…
evan-forbes Aug 24, 2026
2927d67
Merge remote-tracking branch 'origin/main' into feat/optimistic-mined…
evan-forbes Aug 24, 2026
bcdb7c1
Merge remote-tracking branch 'origin/feat/optimistic-mined-block-inve…
evan-forbes Aug 24, 2026
a5c621a
Merge remote-tracking branch 'origin/perf/contextual-profile-mileston…
evan-forbes Aug 24, 2026
058c09f
fix(mining): isolate server templates from proposals
evan-forbes Aug 24, 2026
824bfe8
Merge remote-tracking branch 'origin/feat/optimistic-mined-block-inve…
evan-forbes Aug 25, 2026
6fbb192
Merge remote-tracking branch 'origin/perf/contextual-profile-mileston…
evan-forbes Aug 25, 2026
cb43ccc
fix(mining): harden optimistic block inventory
evan-forbes Aug 27, 2026
1b8f8b9
Merge remote-tracking branch 'origin/main' into audit/pr748-hardening
evan-forbes Aug 27, 2026
e76c84b
Merge remote-tracking branch 'origin/main' into HEAD
evan-forbes Sep 1, 2026
13b3945
Merge remote-tracking branch 'origin/main' into HEAD
evan-forbes Sep 3, 2026
a932a71
Merge branch 'feat/optimistic-mined-block-inventory' into perf/contex…
evan-forbes Sep 3, 2026
f0647f7
Merge branch 'perf/contextual-profile-milestone' into perf/contextual…
evan-forbes Sep 3, 2026
406674b
chore(mining): bump public API crate versions
evan-forbes Sep 3, 2026
ba0204d
Merge branch 'feat/optimistic-mined-block-inventory' into perf/contex…
evan-forbes Sep 3, 2026
b381e48
Merge branch 'perf/contextual-profile-milestone' into perf/contextual…
evan-forbes Sep 3, 2026
f715a59
chore(rpc): bump major version for mining API
evan-forbes Sep 3, 2026
948bd30
Merge branch 'feat/optimistic-mined-block-inventory' into perf/contex…
evan-forbes Sep 3, 2026
2199c04
Merge branch 'perf/contextual-profile-milestone' into perf/contextual…
evan-forbes Sep 3, 2026
e906cee
ci(semver): patch broken tinyvec release
evan-forbes Sep 3, 2026
513e79b
Merge branch 'feat/optimistic-mined-block-inventory' into perf/contex…
evan-forbes Sep 3, 2026
d71d7ea
Merge branch 'perf/contextual-profile-milestone' into perf/contextual…
evan-forbes Sep 3, 2026
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
44 changes: 44 additions & 0 deletions .github/workflows/scripts/patch_tinyvec_for_rustdoc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash

set -euo pipefail

# tinyvec 1.13.0 omits the alloc::vec macro import in its alloc-only build.
# cargo-semver-checks resolves dependencies outside the workspace lock file, so
# patch both its current and published-baseline builds through an isolated Cargo home.
readonly tinyvec_version=1.13.0
readonly original_cargo_home="${CARGO_HOME:-$HOME/.cargo}"
patch_root=$(mktemp -d "$RUNNER_TEMP/tinyvec-semver.XXXXXX")
readonly patch_root
readonly patched_cargo_home="$patch_root/cargo-home"
readonly patched_source="$patch_root/tinyvec-$tinyvec_version"

cargo info "tinyvec@$tinyvec_version" >/dev/null

mapfile -t source_candidates < <(
find "$original_cargo_home/registry/src" -mindepth 2 -maxdepth 2 -type d \
-name "tinyvec-$tinyvec_version"
)
if (( ${#source_candidates[@]} != 1 )); then
echo "expected one tinyvec $tinyvec_version source directory, found ${#source_candidates[@]}" >&2
exit 1
fi

cp -a "${source_candidates[0]}" "$patched_source"
readonly source_file="$patched_source/src/tinyvec.rs"
if [[ "$(grep -Fxc 'use alloc::vec::{self, Vec};' "$source_file")" != 1 ]]; then
echo "tinyvec $tinyvec_version no longer matches the expected broken source" >&2
exit 1
fi
sed -i 's/use alloc::vec::{self, Vec};/use alloc::{vec, vec::Vec};/' "$source_file"

mkdir -p "$patched_cargo_home"
ln -s "$original_cargo_home/registry" "$patched_cargo_home/registry"
if [[ -d "$original_cargo_home/git" ]]; then
ln -s "$original_cargo_home/git" "$patched_cargo_home/git"
fi
cat > "$patched_cargo_home/config.toml" <<EOF
[patch.crates-io]
tinyvec = { path = "$patched_source" }
EOF

echo "CARGO_HOME=$patched_cargo_home" >> "$GITHUB_ENV"
11 changes: 11 additions & 0 deletions .github/workflows/semver-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ on:
- "**/Cargo.lock"
- .github/workflows/semver-checks.yml
- .github/workflows/scripts/affected_semver_packages.py
- .github/workflows/scripts/patch_tinyvec_for_rustdoc.sh
- .github/workflows/scripts/test_affected_semver_packages.py
push:
branches: [main]
Expand All @@ -48,6 +49,7 @@ on:
- "**/Cargo.lock"
- .github/workflows/semver-checks.yml
- .github/workflows/scripts/affected_semver_packages.py
- .github/workflows/scripts/patch_tinyvec_for_rustdoc.sh
- .github/workflows/scripts/test_affected_semver_packages.py

# Run in the merge queue so queued changes are revalidated against the latest
Expand Down Expand Up @@ -202,6 +204,9 @@ jobs:

- uses: ./.github/actions/setup-zakura-build

- name: Patch tinyvec for alloc-only rustdoc builds
run: .github/workflows/scripts/patch_tinyvec_for_rustdoc.sh

- name: Check package against its stable crates.io baseline
env:
PACKAGE: ${{ matrix.package }}
Expand Down Expand Up @@ -252,6 +257,9 @@ jobs:

- uses: ./.github/actions/setup-zakura-build

- name: Patch tinyvec for alloc-only rustdoc builds
run: .github/workflows/scripts/patch_tinyvec_for_rustdoc.sh

- name: Install nightly rustdoc toolchain
run: rustup toolchain install nightly --profile minimal

Expand Down Expand Up @@ -299,6 +307,9 @@ jobs:

- uses: ./.github/actions/setup-zakura-build

- name: Patch tinyvec for alloc-only rustdoc builds
run: .github/workflows/scripts/patch_tinyvec_for_rustdoc.sh

- name: Check all crates and warm published release baselines
id: warm
env:
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions crates/zakura-consensus/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zakura-consensus"
version = "7.0.0"
version = "8.0.0"
authors.workspace = true
description = "Implementation of Zcash consensus checks for the Zakura node. Internal crate, published to support cargo install zakura"
license.workspace = true
Expand Down Expand Up @@ -68,7 +68,7 @@ tower-fallback = { package = "zakura-tower-fallback", path = "../tower-fallback/
tower-batch-control = { package = "zakura-tower-batch-control", path = "../tower-batch-control/", version = "1.3.0" }

zakura-script = { path = "../zakura-script", version = "3.2.1" }
zakura-state = { path = "../zakura-state", version = "7.0.0" }
zakura-state = { path = "../zakura-state", version = "8.0.0" }
zakura-node-services = { path = "../zakura-node-services", version = "3.2.1" }
zakura-chain = { path = "../zakura-chain", version = "6.0.0" }
zakura-header-chain = { path = "../zakura-header-chain", version = "1.0.0" }
Expand All @@ -93,7 +93,7 @@ toml = { workspace = true }

tokio = { workspace = true, features = ["full", "tracing", "test-util"] }

zakura-state = { path = "../zakura-state", version = "7.0.0", features = ["proptest-impl"] }
zakura-state = { path = "../zakura-state", version = "8.0.0", features = ["proptest-impl"] }
zakura-chain = { path = "../zakura-chain", version = "6.0.0", features = ["proptest-impl"] }
zakura-test = { path = "../zakura-test/", version = "2.1.0" }

Expand Down
178 changes: 160 additions & 18 deletions crates/zakura-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ use zakura_state as zs;
use crate::{error::*, primitives, transaction as tx, BoxError};

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

pub use request::Request;
pub use request::{PreparedCandidateSource, Request};

#[cfg(test)]
mod tests;
Expand All @@ -49,6 +50,7 @@ pub struct SemanticBlockVerifier<S, V> {
network: Network,
state_service: S,
transaction_verifier: V,
prepared_candidates: prepared::PreparedCandidateCache,
}

/// Block verification errors.
Expand Down Expand Up @@ -231,6 +233,7 @@ where
network: network.clone(),
state_service,
transaction_verifier,
prepared_candidates: Default::default(),
}
}
}
Expand Down Expand Up @@ -258,6 +261,7 @@ where
let mut state_service = self.state_service.clone();
let mut transaction_verifier = self.transaction_verifier.clone();
let network = self.network.clone();
let prepared_candidates = self.prepared_candidates.clone();

let block = request.block();

Expand All @@ -267,6 +271,7 @@ where
async move {
let hash = zakura_header_chain::validate_encoding_version_hash(&block.header)
.map_err(BlockError::from)?;
let preparation_start = request.should_cache().then(std::time::Instant::now);
// Check that this block is actually a new block.
tracing::trace!("checking that block is not already in state");
match state_service
Expand Down Expand Up @@ -295,6 +300,63 @@ where
Err(BlockError::MaxHeight(height, hash, block::Height::MAX))?;
}

if request.is_mined_commit() {
let solved_header_start = std::time::Instant::now();
if let Some(prepared::CachedPreparedCandidate {
source,
prepared: cached_prepared_block,
}) = prepared_candidates.lookup(&block, request.work_id(), &network)
{
let pow_policy = zakura_header_chain::PowPolicy::for_network(&network)?;
if pow_policy.is_authenticated_custom_waiver() {
check::difficulty_threshold_is_valid(
&block.header,
&network,
&height,
&hash,
)?;
} else {
check::difficulty_is_valid(&block.header, &network, &height, &hash)?;
check::equihash_solution_is_valid(&block.header, &network)?;
}
check::time_is_valid_at(&block.header, Utc::now(), &height, &hash)
.map_err(VerifyBlockError::Time)?;
for transaction in &block.transactions {
tx::check::lock_time_has_passed(transaction, height, block.header.time)
.map_err(VerifyBlockError::Transaction)?;
}
check::merkle_root_validity(
&network,
&block,
&cached_prepared_block.transaction_hashes,
)?;
metrics::histogram!("mining.solved_header_check.duration_seconds")
.record(solved_header_start.elapsed().as_secs_f64());

let mut prepared_block = cached_prepared_block.as_ref().clone();
prepared_block.block = block;
prepared_block.hash = hash;
prepared_block.height = height;
let admission = request.admission();
if source == PreparedCandidateSource::ServerTemplate {
if let Some(admission) = &admission {
if check_prepared_mined_relay_eligibility(
&mut state_service,
(&prepared_block).into(),
)
.await?
== zs::PreparedMinedRelayEligibility::Authorized
{
admission.authorize_optimistic_relay();
}
}
}
return commit_prepared_block(state_service, prepared_block, admission).await;
}
metrics::histogram!("mining.solved_header_check.duration_seconds")
.record(solved_header_start.elapsed().as_secs_f64());
}

// > 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>
Expand Down Expand Up @@ -443,7 +505,8 @@ where

// Return early for proposal requests.
if request.is_proposal() {
return match state_service
let cache_copy = request.should_cache().then(|| prepared_block.clone());
let response = match state_service
.ready()
.await
.map_err(VerifyBlockError::ValidateProposal)?
Expand All @@ -454,26 +517,105 @@ where
zs::Response::ValidBlockProposal => Ok(hash),
_ => unreachable!("wrong response for CheckBlockProposalValidity"),
};
}

match state_service
.ready()
.await
.map_err(|source| VerifyBlockError::StateService { source, hash })?
.call(zs::Request::CommitSemanticallyVerifiedBlock(prepared_block))
.await
{
Ok(zs::Response::Committed(committed_hash)) => {
assert_eq!(committed_hash, hash, "state must commit correct hash");
Ok(hash)
if let (Ok(_), Some(cache_copy)) = (&response, cache_copy) {
let candidate = cache_copy.block.clone();
prepared_candidates.insert(
&candidate,
request.work_id(),
request
.prepared_candidate_source()
.expect("cached preparation has a candidate source"),
cache_copy,
&network,
);
metrics::histogram!("mining.preparation.duration_seconds").record(
preparation_start
.expect("cached preparation records its start time")
.elapsed()
.as_secs_f64(),
);
}

Err(source) => Err(map_commit_error(source, hash)),

_ => unreachable!("wrong response for CommitSemanticallyVerifiedBlock"),
return response;
}

commit_prepared_block(state_service, prepared_block, request.admission()).await
}
.instrument(span)
.boxed()
}
}

async fn check_prepared_mined_relay_eligibility<S>(
state_service: &mut S,
block: zs::BlockCommitmentData,
) -> Result<zs::PreparedMinedRelayEligibility, VerifyBlockError>
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
{
let hash = block.block.hash();
let preflight_start = std::time::Instant::now();
let response = async {
state_service
.ready()
.await
.map_err(|source| VerifyBlockError::StateService { source, hash })?
.call(zs::Request::CheckPreparedMinedRelayEligibility(block))
.await
.map_err(|source| map_commit_error(source, hash))
}
.await;
metrics::histogram!("mining.prepared_relay_preflight.duration_seconds")
.record(preflight_start.elapsed().as_secs_f64());

match response? {
zs::Response::PreparedMinedRelayEligibility(eligibility) => Ok(eligibility),
_ => unreachable!("wrong response for prepared mined-block relay eligibility"),
}
}

async fn commit_prepared_block<S>(
mut state_service: S,
prepared_block: zs::SemanticallyVerifiedBlock,
admission: Option<zs::BlockAdmission>,
) -> Result<block::Hash, VerifyBlockError>
where
S: Service<zs::Request, Response = zs::Response, Error = BoxError> + Send + Clone + 'static,
S::Future: Send + 'static,
{
let hash = prepared_block.hash;
let is_mined_commit = admission.is_some();
let commit_start = std::time::Instant::now();
let ready_start = std::time::Instant::now();
let ready_state_service = state_service
.ready()
.await
.map_err(|source| VerifyBlockError::StateService { source, hash })?;
if is_mined_commit {
metrics::histogram!("state.semantic_commit.ready_wait.duration_seconds")
.record(ready_start.elapsed().as_secs_f64());
}

let request = match admission {
Some(admission) => zs::Request::CommitSemanticallyVerifiedBlockWithAdmission {
block: prepared_block,
admission,
requested_at: std::time::Instant::now(),
},
None => zs::Request::CommitSemanticallyVerifiedBlock(prepared_block),
};
let response = ready_state_service.call(request).await;
if is_mined_commit {
metrics::histogram!("mining.contextual_commit.duration_seconds")
.record(commit_start.elapsed().as_secs_f64());
}

match response {
Ok(zs::Response::Committed(committed_hash)) => {
assert_eq!(committed_hash, hash, "state must commit correct hash");
Ok(hash)
}
Err(source) => Err(map_commit_error(source, hash)),
_ => unreachable!("wrong response for semantic block commit"),
}
}
Loading