change(rpc): Populate `blockcommitmenthash` and `defaultroot` fields in the getblocktemplate RPC (#5751)

* populate `blockcommitmenthash` and `defaultroot` missing fields

* remove assertion line manually from snaps

* fix some imports and docs

* fix some docs

* add a consistency check

* Rename a constant to FINALIZED_STATE_QUERY_RETRIES and use it everywhere

* Move tip query inside retry, split tip into tip_height and tip_hash

* Return retry failures rather than panicking

* Query relevant chain inside the retry

* Check the entire state for consistency, not just the finalized tip

Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
Alfredo Garcia 2022-12-07 19:39:11 -03:00 committed by GitHub
parent 4664ab289c
commit 678c519032
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 267 additions and 110 deletions

View File

@ -9,11 +9,10 @@ use tower::{buffer::Buffer, Service, ServiceExt};
use zebra_chain::{
amount::{self, Amount, NonNegative},
block::Height,
block::{
self,
merkle::{self, AuthDataRoot},
Block, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION,
Block, ChainHistoryBlockTxAuthCommitmentHash, Height, MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION,
},
chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip,
@ -338,7 +337,7 @@ where
// The tip estimate may not be the same as the one coming from the state
// but this is ok for an estimate
let (estimated_distance_to_chain_tip, tip_height) = latest_chain_tip
let (estimated_distance_to_chain_tip, estimated_tip_height) = latest_chain_tip
.estimate_distance_to_network_chain_tip(network)
.ok_or_else(|| Error {
code: ErrorCode::ServerError(0),
@ -349,7 +348,7 @@ where
if !sync_status.is_close_to_tip() || estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP {
tracing::info!(
estimated_distance_to_chain_tip,
?tip_height,
?estimated_tip_height,
"Zebra has not synced to the chain tip"
);
@ -377,15 +376,12 @@ where
})?;
let chain_info = match response {
ReadResponse::ChainInfo(Some(chain_info)) => chain_info,
ReadResponse::ChainInfo(chain_info) => chain_info,
_ => unreachable!("we should always have enough state data here to get a `GetBlockTemplateChainInfo`"),
};
// Get the tip data from the state call
let tip_height = chain_info.tip.0;
let tip_hash = chain_info.tip.1;
let block_height = (tip_height + 1).expect("tip is far below Height::MAX");
let block_height = (chain_info.tip_height + 1).expect("tip is far below Height::MAX");
let outputs =
standard_coinbase_outputs(network, block_height, miner_address, miner_fee);
@ -394,6 +390,16 @@ where
let (merkle_root, auth_data_root) =
calculate_transaction_roots(&coinbase_tx, &mempool_txs);
let history_tree = chain_info.history_tree;
// TODO: move expensive cryptography to a rayon thread?
let chain_history_root = history_tree.hash().expect("history tree can't be empty");
// TODO: move expensive cryptography to a rayon thread?
let block_commitments_hash = ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
&chain_history_root,
&auth_data_root,
);
// Convert into TransactionTemplates
let mempool_txs = mempool_txs.iter().map(Into::into).collect();
@ -404,15 +410,15 @@ where
version: ZCASH_BLOCK_VERSION,
previous_block_hash: GetBlockHash(tip_hash),
block_commitments_hash: [0; 32].into(),
light_client_root_hash: [0; 32].into(),
final_sapling_root_hash: [0; 32].into(),
previous_block_hash: GetBlockHash(chain_info.tip_hash),
block_commitments_hash,
light_client_root_hash: block_commitments_hash,
final_sapling_root_hash: block_commitments_hash,
default_roots: DefaultRoots {
merkle_root,
chain_history_root: [0; 32].into(),
chain_history_root,
auth_data_root,
block_commitments_hash: [0; 32].into(),
block_commitments_hash,
},
transactions: mempool_txs,

View File

@ -2,4 +2,6 @@
mod prop;
mod snapshot;
#[cfg(feature = "getblocktemplate-rpcs")]
pub mod utils;
mod vectors;

View File

@ -31,6 +31,7 @@ use crate::methods::{
self,
types::{get_block_template::GetBlockTemplate, hex_data::HexData, submit_block},
},
tests::utils::fake_history_tree,
GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
};
@ -154,13 +155,15 @@ pub async fn test_responses<State, ReadState>(
.clone()
.expect_request_that(|req| matches!(req, ReadRequest::ChainInfo))
.await
.respond(ReadResponse::ChainInfo(Some(GetBlockTemplateChainInfo {
.respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo {
expected_difficulty: CompactDifficulty::from(ExpandedDifficulty::from(U256::one())),
tip: (fake_tip_height, fake_tip_hash),
tip_height: fake_tip_height,
tip_hash: fake_tip_hash,
cur_time: fake_cur_time,
min_time: fake_min_time,
max_time: fake_max_time,
})));
history_tree: fake_history_tree(network),
}));
});
let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(None));

View File

@ -6,14 +6,14 @@ expression: block_template
"capabilities": [],
"version": 4,
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
"blockcommitmentshash": "0000000000000000000000000000000000000000000000000000000000000000",
"lightclientroothash": "0000000000000000000000000000000000000000000000000000000000000000",
"finalsaplingroothash": "0000000000000000000000000000000000000000000000000000000000000000",
"blockcommitmentshash": "fe03d8236b0835c758f59d279230ebaee2128754413103b9edb17c07451c2c82",
"lightclientroothash": "fe03d8236b0835c758f59d279230ebaee2128754413103b9edb17c07451c2c82",
"finalsaplingroothash": "fe03d8236b0835c758f59d279230ebaee2128754413103b9edb17c07451c2c82",
"defaultroots": {
"merkleroot": "6b370584714ab567c9c014ce72d325ab6c5927e181ac891acb35e6d4b6cc19a1",
"chainhistoryroot": "0000000000000000000000000000000000000000000000000000000000000000",
"chainhistoryroot": "94470fa66ebd1a5fdb109a5aa3f3204f14de3a42135e71aa7f4c44055847e0b5",
"authdataroot": "0dbb78de9fdcd494307971e36dd049fc82d0ee9ee53aec8fd2a54dc0e426289b",
"blockcommitmentshash": "0000000000000000000000000000000000000000000000000000000000000000"
"blockcommitmentshash": "fe03d8236b0835c758f59d279230ebaee2128754413103b9edb17c07451c2c82"
},
"transactions": [],
"coinbasetxn": {

View File

@ -6,14 +6,14 @@ expression: block_template
"capabilities": [],
"version": 4,
"previousblockhash": "0000000000d723156d9b65ffcf4984da7a19675ed7e2f06d9e5d5188af087bf8",
"blockcommitmentshash": "0000000000000000000000000000000000000000000000000000000000000000",
"lightclientroothash": "0000000000000000000000000000000000000000000000000000000000000000",
"finalsaplingroothash": "0000000000000000000000000000000000000000000000000000000000000000",
"blockcommitmentshash": "cb1f1c6a5ad5ff9c4a170e3b747a24f3aec79817adba9a9451f19914481bb422",
"lightclientroothash": "cb1f1c6a5ad5ff9c4a170e3b747a24f3aec79817adba9a9451f19914481bb422",
"finalsaplingroothash": "cb1f1c6a5ad5ff9c4a170e3b747a24f3aec79817adba9a9451f19914481bb422",
"defaultroots": {
"merkleroot": "623400cc122baa015d3a4209f5903ebe215170c7e6e74831dce8372c5fd5b3cc",
"chainhistoryroot": "0000000000000000000000000000000000000000000000000000000000000000",
"chainhistoryroot": "03bc75f00c307a05aed2023819e18c2672cbe15fbd3200944997def141967387",
"authdataroot": "a44375f0c0dd5ba612bd7b0efd77683cde8edf5055aff9fbfda443cc8d46bd3e",
"blockcommitmentshash": "0000000000000000000000000000000000000000000000000000000000000000"
"blockcommitmentshash": "cb1f1c6a5ad5ff9c4a170e3b747a24f3aec79817adba9a9451f19914481bb422"
},
"transactions": [],
"coinbasetxn": {

View File

@ -0,0 +1,39 @@
//! Utility functions for RPC method tests.
use std::sync::Arc;
use zebra_chain::{
block::Block,
history_tree::{HistoryTree, NonEmptyHistoryTree},
parameters::Network,
sapling::tree::Root,
serialization::ZcashDeserialize,
};
use zebra_test::vectors;
/// Create a history tree with one single block for a network by using Zebra test vectors.
pub fn fake_history_tree(network: Network) -> Arc<HistoryTree> {
let (block, sapling_root) = match network {
Network::Mainnet => (
&vectors::BLOCK_MAINNET_1046400_BYTES[..],
*vectors::SAPLING_FINAL_ROOT_MAINNET_1046400_BYTES,
),
Network::Testnet => (
&vectors::BLOCK_TESTNET_1116000_BYTES[..],
*vectors::SAPLING_FINAL_ROOT_TESTNET_1116000_BYTES,
),
};
let block = Arc::<Block>::zcash_deserialize(block).expect("block should deserialize");
let first_sapling_root = Root::try_from(sapling_root).unwrap();
let history_tree = NonEmptyHistoryTree::from_block(
Network::Mainnet,
block,
&first_sapling_root,
&Default::default(),
)
.unwrap();
Arc::new(HistoryTree::from(history_tree))
}

View File

@ -795,8 +795,11 @@ async fn rpc_getblocktemplate() {
use chrono::{TimeZone, Utc};
use crate::methods::get_block_template_rpcs::constants::{
GET_BLOCK_TEMPLATE_MUTABLE_FIELD, GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD,
use crate::methods::{
get_block_template_rpcs::constants::{
GET_BLOCK_TEMPLATE_MUTABLE_FIELD, GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD,
},
tests::utils::fake_history_tree,
};
use zebra_chain::{
amount::{Amount, NonNegative},
@ -856,13 +859,15 @@ async fn rpc_getblocktemplate() {
.clone()
.expect_request_that(|req| matches!(req, ReadRequest::ChainInfo))
.await
.respond(ReadResponse::ChainInfo(Some(GetBlockTemplateChainInfo {
.respond(ReadResponse::ChainInfo(GetBlockTemplateChainInfo {
expected_difficulty: CompactDifficulty::from(ExpandedDifficulty::from(U256::one())),
tip: (fake_tip_height, fake_tip_hash),
tip_height: fake_tip_height,
tip_hash: fake_tip_hash,
cur_time: fake_cur_time,
min_time: fake_min_time,
max_time: fake_max_time,
})));
history_tree: fake_history_tree(Mainnet),
}));
});
let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(None));

View File

@ -61,8 +61,21 @@ impl From<block::Hash> for HashOrHeight {
}
impl From<block::Height> for HashOrHeight {
fn from(hash: block::Height) -> Self {
Self::Height(hash)
fn from(height: block::Height) -> Self {
Self::Height(height)
}
}
impl From<(block::Height, block::Hash)> for HashOrHeight {
fn from((_height, hash): (block::Height, block::Hash)) -> Self {
// Hash is more specific than height for the non-finalized state
hash.into()
}
}
impl From<(block::Hash, block::Height)> for HashOrHeight {
fn from((hash, _height): (block::Hash, block::Height)) -> Self {
hash.into()
}
}

View File

@ -132,16 +132,20 @@ pub enum ReadResponse {
#[cfg(feature = "getblocktemplate-rpcs")]
/// Response to [`ReadRequest::ChainInfo`](crate::ReadRequest::ChainInfo) with the state
/// information needed by the `getblocktemplate` RPC method.
ChainInfo(Option<GetBlockTemplateChainInfo>),
ChainInfo(GetBlockTemplateChainInfo),
}
#[cfg(feature = "getblocktemplate-rpcs")]
/// A structure with the information needed from the state to build a `getblocktemplate` RPC response.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GetBlockTemplateChainInfo {
/// The current state tip height and hash.
/// The block template for the candidate block is the next block after this block.
pub tip: (block::Height, block::Hash),
/// The current state tip height.
/// The block template OAfor the candidate block is the next block after this block.
pub tip_height: block::Height,
/// The current state tip height.
/// The block template for the candidate block has this hash as the previous block hash.
pub tip_hash: block::Hash,
/// The expected difficulty of the candidate block.
pub expected_difficulty: CompactDifficulty,
@ -154,6 +158,9 @@ pub struct GetBlockTemplateChainInfo {
/// The maximum time the miner can use in this block.
pub max_time: chrono::DateTime<chrono::Utc>,
/// The history tree of the current best chain.
pub history_tree: Arc<zebra_chain::history_tree::HistoryTree>,
}
/// Conversion from read-only [`ReadResponse`]s to read-write [`Response`]s.

View File

@ -1607,21 +1607,16 @@ impl Service<ReadRequest> for ReadStateService {
// In that case, the `getblocktemplate` RPC will return an error because Zebra
// is not synced to the tip. That check happens before the RPC makes this request.
let get_block_template_info =
read::tip(latest_non_finalized_state.best_chain(), &state.db).map(
|tip| {
read::difficulty::difficulty_and_time_info(
&latest_non_finalized_state,
&state.db,
tip,
state.network,
)
},
read::difficulty::get_block_template_chain_info(
&latest_non_finalized_state,
&state.db,
state.network,
);
// The work is done in the future.
timer.finish(module_path!(), line!(), "ReadRequest::ChainInfo");
Ok(ReadResponse::ChainInfo(get_block_template_info))
get_block_template_info.map(ReadResponse::ChainInfo)
})
})
.map(|join_result| join_result.expect("panic in ReadRequest::ChainInfo"))

View File

@ -35,10 +35,17 @@ pub use block::{
};
#[cfg(feature = "getblocktemplate-rpcs")]
pub use {block::hash, difficulty::difficulty_and_time_info};
pub use {block::hash, difficulty::get_block_template_chain_info};
pub use find::{
best_tip, block_locator, chain_contains_hash, depth, find_chain_hashes, find_chain_headers,
hash_by_height, height_by_hash, tip, tip_height,
};
pub use tree::{orchard_tree, sapling_tree};
/// If a finalized state query is interrupted by a new finalized block,
/// retry this many times.
///
/// Once we're at the tip, we expect up to 2 blocks to arrive at the same time.
/// If any more arrive, the client should wait until we're synchronised with our peers.
pub const FINALIZED_STATE_QUERY_RETRIES: usize = 3;

View File

@ -3,10 +3,3 @@
pub mod balance;
pub mod tx_id;
pub mod utxo;
/// If the transparent address index queries are interrupted by a new finalized block,
/// retry this many times.
///
/// Once we're at the tip, we expect up to 2 blocks to arrive at the same time.
/// If any more arrive, the client should wait until we're synchronised with our peers.
const FINALIZED_ADDRESS_INDEX_RETRIES: usize = 3;

View File

@ -20,12 +20,12 @@ use zebra_chain::{
};
use crate::{
service::{finalized_state::ZebraDb, non_finalized_state::Chain},
service::{
finalized_state::ZebraDb, non_finalized_state::Chain, read::FINALIZED_STATE_QUERY_RETRIES,
},
BoxError,
};
use super::FINALIZED_ADDRESS_INDEX_RETRIES;
/// Returns the total transparent balance for the supplied [`transparent::Address`]es.
///
/// If the addresses do not exist in the non-finalized `chain` or finalized `db`, returns zero.
@ -37,7 +37,9 @@ pub fn transparent_balance(
let mut balance_result = finalized_transparent_balance(db, &addresses);
// Retry the finalized balance query if it was interrupted by a finalizing block
for _ in 0..FINALIZED_ADDRESS_INDEX_RETRIES {
//
// TODO: refactor this into a generic retry(finalized_closure, process_and_check_closure) fn
for _ in 0..FINALIZED_STATE_QUERY_RETRIES {
if balance_result.is_ok() {
break;
}

View File

@ -19,12 +19,12 @@ use std::{
use zebra_chain::{block::Height, transaction, transparent};
use crate::{
service::{finalized_state::ZebraDb, non_finalized_state::Chain},
service::{
finalized_state::ZebraDb, non_finalized_state::Chain, read::FINALIZED_STATE_QUERY_RETRIES,
},
BoxError, TransactionLocation,
};
use super::FINALIZED_ADDRESS_INDEX_RETRIES;
/// Returns the transaction IDs that sent or received funds from the supplied [`transparent::Address`]es,
/// within `query_height_range`, in chain order.
///
@ -44,7 +44,9 @@ where
// Retry the finalized tx ID query if it was interrupted by a finalizing block,
// and the non-finalized chain doesn't overlap the changed heights.
for _ in 0..=FINALIZED_ADDRESS_INDEX_RETRIES {
//
// TODO: refactor this into a generic retry(finalized_closure, process_and_check_closure) fn
for _ in 0..=FINALIZED_STATE_QUERY_RETRIES {
let (finalized_tx_ids, finalized_tip_range) =
finalized_transparent_tx_ids(db, &addresses, query_height_range.clone());

View File

@ -19,12 +19,12 @@ use std::{
use zebra_chain::{block::Height, parameters::Network, transaction, transparent};
use crate::{
service::{finalized_state::ZebraDb, non_finalized_state::Chain},
service::{
finalized_state::ZebraDb, non_finalized_state::Chain, read::FINALIZED_STATE_QUERY_RETRIES,
},
BoxError, OutputLocation, TransactionLocation,
};
use super::FINALIZED_ADDRESS_INDEX_RETRIES;
/// The full range of address heights.
///
/// The genesis coinbase transactions are ignored by a consensus rule,
@ -108,7 +108,9 @@ where
// Retry the finalized UTXO query if it was interrupted by a finalizing block,
// and the non-finalized chain doesn't overlap the changed heights.
for attempt in 0..=FINALIZED_ADDRESS_INDEX_RETRIES {
//
// TODO: refactor this into a generic retry(finalized_closure, process_and_check_closure) fn
for attempt in 0..=FINALIZED_STATE_QUERY_RETRIES {
debug!(?attempt, ?address_count, "starting address UTXO query");
let (finalized_utxos, finalized_tip_range) = finalized_address_utxos(db, &addresses);

View File

@ -1,11 +1,12 @@
//! Get context and calculate difficulty for the next block.
use std::borrow::Borrow;
use std::sync::Arc;
use chrono::{DateTime, Duration, TimeZone, Utc};
use zebra_chain::{
block::{Block, Hash, Height},
block::{self, Block, Height},
history_tree::HistoryTree,
parameters::{Network, NetworkUpgrade, POST_BLOSSOM_POW_TARGET_SPACING},
work::difficulty::CompactDifficulty,
};
@ -18,70 +19,133 @@ use crate::{
AdjustedDifficulty,
},
finalized_state::ZebraDb,
read::{self, tree::history_tree, FINALIZED_STATE_QUERY_RETRIES},
NonFinalizedState,
},
GetBlockTemplateChainInfo,
BoxError, GetBlockTemplateChainInfo,
};
/// Returns the [`GetBlockTemplateChainInfo`] for the current best chain.
///
/// # Panics
///
/// If we don't have enough blocks in the state.
pub fn difficulty_and_time_info(
/// - If we don't have enough blocks in the state.
/// - If a consistency check fails `RETRIES` times.
pub fn get_block_template_chain_info(
non_finalized_state: &NonFinalizedState,
db: &ZebraDb,
tip: (Height, Hash),
network: Network,
) -> GetBlockTemplateChainInfo {
let relevant_chain = any_ancestor_blocks(non_finalized_state, db, tip.1);
difficulty_and_time(relevant_chain, tip, network)
) -> Result<GetBlockTemplateChainInfo, BoxError> {
let mut relevant_chain_and_history_tree_result =
relevant_chain_and_history_tree(non_finalized_state, db);
// Retry the finalized state query if it was interrupted by a finalizing block.
//
// TODO: refactor this into a generic retry(finalized_closure, process_and_check_closure) fn
for _ in 0..FINALIZED_STATE_QUERY_RETRIES {
if relevant_chain_and_history_tree_result.is_ok() {
break;
}
relevant_chain_and_history_tree_result =
relevant_chain_and_history_tree(non_finalized_state, db);
}
let (tip_height, tip_hash, relevant_chain, history_tree) =
relevant_chain_and_history_tree_result?;
Ok(difficulty_time_and_history_tree(
relevant_chain,
tip_height,
tip_hash,
network,
history_tree,
))
}
/// Returns the [`GetBlockTemplateChainInfo`] for the current best chain.
/// Do a consistency check by checking the finalized tip before and after all other database queries.
/// Returns and error if the tip obtained before and after is not the same.
///
/// See [`difficulty_and_time_info()`] for details.
fn difficulty_and_time<C>(
relevant_chain: C,
tip: (Height, Hash),
network: Network,
) -> GetBlockTemplateChainInfo
where
C: IntoIterator,
C::Item: Borrow<Block>,
C::IntoIter: ExactSizeIterator,
{
/// # Panics
///
/// - If we don't have enough blocks in the state.
fn relevant_chain_and_history_tree(
non_finalized_state: &NonFinalizedState,
db: &ZebraDb,
) -> Result<
(
Height,
block::Hash,
[Arc<Block>; POW_ADJUSTMENT_BLOCK_SPAN],
Arc<HistoryTree>,
),
BoxError,
> {
let state_tip_before_queries = read::best_tip(non_finalized_state, db).ok_or_else(|| {
BoxError::from("Zebra's state is empty, wait until it syncs to the chain tip")
})?;
let relevant_chain = any_ancestor_blocks(non_finalized_state, db, state_tip_before_queries.1);
let relevant_chain: Vec<_> = relevant_chain
.into_iter()
.take(POW_ADJUSTMENT_BLOCK_SPAN)
.collect();
let relevant_chain = relevant_chain.try_into().map_err(|_error| {
"Zebra's state only has a few blocks, wait until it syncs to the chain tip"
})?;
let history_tree = history_tree(
non_finalized_state.best_chain(),
db,
state_tip_before_queries.into(),
)
.expect("tip hash should exist in the chain");
let state_tip_after_queries =
read::best_tip(non_finalized_state, db).expect("already checked for an empty tip");
if state_tip_before_queries != state_tip_after_queries {
return Err("Zebra is committing too many blocks to the state, \
wait until it syncs to the chain tip"
.into());
}
Ok((
state_tip_before_queries.0,
state_tip_before_queries.1,
relevant_chain,
history_tree,
))
}
/// Returns the [`GetBlockTemplateChainInfo`] for the current best chain.
///
/// See [`get_block_template_chain_info()`] for details.
fn difficulty_time_and_history_tree(
relevant_chain: [Arc<Block>; POW_ADJUSTMENT_BLOCK_SPAN],
tip_height: Height,
tip_hash: block::Hash,
network: Network,
history_tree: Arc<HistoryTree>,
) -> GetBlockTemplateChainInfo {
let relevant_data: Vec<(CompactDifficulty, DateTime<Utc>)> = relevant_chain
.iter()
.map(|block| {
(
block.borrow().header.difficulty_threshold,
block.borrow().header.time,
)
})
.map(|block| (block.header.difficulty_threshold, block.header.time))
.collect();
// The getblocktemplate RPC returns an error if Zebra is not synced to the tip.
// So this will never happen in production code.
assert_eq!(
relevant_data.len(),
POW_ADJUSTMENT_BLOCK_SPAN,
"getblocktemplate RPC called with a near-empty state: should have returned an error",
);
let cur_time = chrono::Utc::now();
// Get the median-time-past, which doesn't depend on the time or the previous block height.
// `context` will always have the correct length, because this function takes an array.
//
// TODO: split out median-time-past into its own struct?
let median_time_past =
AdjustedDifficulty::new_from_header_time(cur_time, tip.0, network, relevant_data.clone())
.median_time_past();
let median_time_past = AdjustedDifficulty::new_from_header_time(
cur_time,
tip_height,
network,
relevant_data.clone(),
)
.median_time_past();
// > For each block other than the genesis block , nTime MUST be strictly greater than
// > the median-time-past of that block.
@ -110,20 +174,22 @@ where
// Now that we have a valid time, get the difficulty for that time.
let difficulty_adjustment = AdjustedDifficulty::new_from_header_time(
cur_time,
tip.0,
tip_height,
network,
relevant_data.iter().cloned(),
);
let mut result = GetBlockTemplateChainInfo {
tip,
tip_height,
tip_hash,
expected_difficulty: difficulty_adjustment.expected_difficulty_threshold(),
min_time,
cur_time,
max_time,
history_tree,
};
adjust_difficulty_and_time_for_testnet(&mut result, network, tip.0, relevant_data);
adjust_difficulty_and_time_for_testnet(&mut result, network, tip_height, relevant_data);
result
}

View File

@ -61,3 +61,18 @@ where
.and_then(|chain| chain.as_ref().orchard_tree(hash_or_height))
.or_else(|| db.orchard_tree(hash_or_height))
}
#[cfg(feature = "getblocktemplate-rpcs")]
/// Get the history tree of the provided chain.
pub fn history_tree<C>(
chain: Option<C>,
db: &ZebraDb,
hash_or_height: HashOrHeight,
) -> Option<Arc<zebra_chain::history_tree::HistoryTree>>
where
C: AsRef<Chain>,
{
chain
.and_then(|chain| chain.as_ref().history_tree(hash_or_height))
.or_else(|| Some(db.history_tree()))
}