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

Respond to getblockchaininfo with genesis block when empty state #9138

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
68 changes: 43 additions & 25 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use zebra_chain::{
subtree::NoteCommitmentSubtreeIndex,
transaction::{self, SerializedTransaction, Transaction, UnminedTx},
transparent::{self, Address},
value_balance::ValueBalance,
work::{
difficulty::{CompactDifficulty, ExpandedDifficulty},
equihash::Solution,
Expand Down Expand Up @@ -550,35 +551,52 @@ where
// `chain` field
let chain = network.bip70_network_name();

let request = zebra_state::ReadRequest::TipPoolValues;
let response: zebra_state::ReadResponse = state
let (tip_height, tip_hash, tip_block_time, value_balance) = match state
.ready()
.and_then(|service| service.call(request))
.await
.map_misc_error()?;

let zebra_state::ReadResponse::TipPoolValues {
tip_height,
tip_hash,
value_balance,
} = response
else {
unreachable!("unmatched response to a TipPoolValues request")
};

let request = zebra_state::ReadRequest::BlockHeader(tip_hash.into());
let response: zebra_state::ReadResponse = state
.ready()
.and_then(|service| service.call(request))
.and_then(|service| service.call(zebra_state::ReadRequest::TipPoolValues))
.await
.map_misc_error()?;

let zebra_state::ReadResponse::BlockHeader { header, .. } = response else {
unreachable!("unmatched response to a BlockHeader request")
{
Ok(zebra_state::ReadResponse::TipPoolValues {
tip_height,
tip_hash,
value_balance,
}) => {
let request = zebra_state::ReadRequest::BlockHeader(tip_hash.into());
let response: zebra_state::ReadResponse = state
.ready()
.and_then(|service| service.call(request))
.await
.map_misc_error()?;

if let zebra_state::ReadResponse::BlockHeader { header, .. } = response {
(tip_height, tip_hash, header.time, value_balance)
} else {
unreachable!("unmatched response to a TipPoolValues request")
}
}
_ => {
let request =
zebra_state::ReadRequest::BlockHeader(HashOrHeight::Height(Height::MIN));
let response: zebra_state::ReadResponse = state
.ready()
.and_then(|service| service.call(request))
.await
.map_misc_error()?;

if let zebra_state::ReadResponse::BlockHeader {
header,
hash,
height,
..
} = response
{
(height, hash, header.time, ValueBalance::zero())
} else {
unreachable!("unmatched response to a BlockHeader request")
}
}
};

let tip_block_time = header.time;

let now = Utc::now();
let zebra_estimated_height =
NetworkChainTipHeightEstimator::new(tip_block_time, tip_height, &network)
Expand Down
112 changes: 108 additions & 4 deletions zebra-rpc/src/methods/tests/prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ use zebra_chain::{
amount::{Amount, NonNegative},
block::{self, Block, Height},
chain_tip::{mock::MockChainTip, ChainTip, NoChainTip},
parameters::{Network, NetworkUpgrade},
serialization::{ZcashDeserialize, ZcashSerialize},
parameters::{ConsensusBranchId, Network, NetworkUpgrade},
serialization::{ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
transaction::{self, Transaction, UnminedTx, VerifiedUnminedTx},
transparent,
value_balance::ValueBalance,
};
use zebra_node_services::mempool;
use zebra_state::BoxError;
use zebra_state::{BoxError, HashOrHeight};

use zebra_test::mock_service::MockService;

use crate::methods;
use crate::methods::{self, types::ValuePoolBalance};

use super::super::{
AddressBalance, AddressStrings, NetworkUpgradeStatus, RpcImpl, RpcServer, SentTransactionHash,
Expand Down Expand Up @@ -370,6 +370,8 @@ proptest! {
.await
.expect("getblockchaininfo should call mock state service with correct request")
.respond(Err(BoxError::from("no chain tip available yet")));

state.expect_request(zebra_state::ReadRequest::BlockHeader(HashOrHeight::Height(block::Height(0)))).await.expect("no chain tip available yet").respond(Err(BoxError::from("no chain tip available yet")));
}
};

Expand Down Expand Up @@ -493,6 +495,108 @@ proptest! {
})?;
}

/// Test the `get_blockchain_info` response when tip_pool request fails.
#[test]
fn get_blockchain_info_returns_genesis_when_tip_pool_fails(network in any::<Network>()) {
let (runtime, _init_guard) = zebra_test::init_async();
let _guard = runtime.enter();
let (mut mempool, mut state, rpc, mempool_tx_queue) = mock_services(network.clone(), NoChainTip);

// CORRECTNESS: Nothing in this test depends on real time, so we can speed it up.
tokio::time::pause();

elijahhampton marked this conversation as resolved.
Show resolved Hide resolved
let genesis_block = match network {
Network::Mainnet => {
let block_bytes = &zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES;
let block: Arc<Block> = block_bytes.zcash_deserialize_into().expect("block is valid");
block
},
Network::Testnet(_) => {
let block_bytes = &zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES;
let block: Arc<Block> = block_bytes.zcash_deserialize_into().expect("block is valid");
block
},
};

// Genesis block fields
let block_time = genesis_block.header.time;
let block_version = genesis_block.header.version;
let block_prev_block_hash = genesis_block.header.previous_block_hash;
let block_merkle_root = genesis_block.header.merkle_root;
let block_commitment_bytes = genesis_block.header.commitment_bytes;
let block_difficulty_threshold = genesis_block.header.difficulty_threshold;
let block_nonce = genesis_block.header.nonce;
let block_solution = genesis_block.header.solution;
let block_hash = genesis_block.header.hash();

runtime.block_on(async move {
let response_fut = rpc.get_blockchain_info();
let mock_state_handler = {
let mut state = state.clone();
async move {
state.expect_request(zebra_state::ReadRequest::TipPoolValues)
.await
.expect("getblockchaininfo should call mock state service with correct request")
.respond(Err(BoxError::from("tip values not available")));


state
.expect_request(zebra_state::ReadRequest::BlockHeader(HashOrHeight::Height(Height::MIN)))
.await
.expect("getblockchaininfo should call mock state service with correct request")
.respond(zebra_state::ReadResponse::BlockHeader {
header: Arc::new(block::Header {
time: block_time,
version: block_version,
previous_block_hash: block_prev_block_hash,
merkle_root: block_merkle_root,
commitment_bytes: block_commitment_bytes,
difficulty_threshold: block_difficulty_threshold,
nonce: block_nonce,
solution: block_solution
}),
hash: block_hash,
height: Height::MIN,
next_block_hash: None,
});
}
};

let (response, _) = tokio::join!(response_fut, mock_state_handler);

let response = response.expect("should succeed with genesis block info");

prop_assert_eq!(response.best_block_hash, genesis_block.header.hash());
prop_assert_eq!(response.chain, network.bip70_network_name());
prop_assert_eq!(response.blocks, Height::MIN);
prop_assert_eq!(response.value_pools, ValuePoolBalance::from_value_balance(ValueBalance::zero()));

let genesis_branch_id = NetworkUpgrade::current(&network, Height::MIN).branch_id().unwrap_or(ConsensusBranchId::RPC_MISSING_ID);
let next_height = (Height::MIN + 1).expect("genesis height plus one is next height and valid");
let next_branch_id = NetworkUpgrade::current(&network, next_height).branch_id().unwrap_or(ConsensusBranchId::RPC_MISSING_ID);

prop_assert_eq!(response.consensus.chain_tip.0, genesis_branch_id);
prop_assert_eq!(response.consensus.next_block.0, next_branch_id);

for (_, upgrade_info) in response.upgrades {
let status = if Height::MIN < upgrade_info.activation_height {
NetworkUpgradeStatus::Pending
} else {
NetworkUpgradeStatus::Active
};
prop_assert_eq!(upgrade_info.status, status);
}

mempool.expect_no_requests().await?;
state.expect_no_requests().await?;

// The queue task should continue without errors or panics
prop_assert!(mempool_tx_queue.now_or_never().is_none());

Ok(())
})?;
}

/// Test the `get_address_balance` RPC using an arbitrary set of addresses.
#[test]
fn queries_balance_for_valid_addresses(
Expand Down
Loading