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 2 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
116 changes: 111 additions & 5 deletions zebra-rpc/src/methods/tests/prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@

use zebra_chain::{
amount::{Amount, NonNegative},
block::{self, Block, Height},
block::{self, Block, Header, Height},

Check warning on line 15 in zebra-rpc/src/methods/tests/prop.rs

View workflow job for this annotation

GitHub Actions / Check Cargo.lock is up to date

unused imports: `DateTime32`, `Header`, and `work::equihash::Solution`

Check failure on line 15 in zebra-rpc/src/methods/tests/prop.rs

View workflow job for this annotation

GitHub Actions / Build zebra-rpc crate

unused imports: `DateTime32`, `Header`, and `work::equihash::Solution`

Check warning on line 15 in zebra-rpc/src/methods/tests/prop.rs

View workflow job for this annotation

GitHub Actions / Test stable on ubuntu-latest

unused imports: `DateTime32`, `Header`, and `work::equihash::Solution`

Check warning on line 15 in zebra-rpc/src/methods/tests/prop.rs

View workflow job for this annotation

GitHub Actions / Test beta on ubuntu-latest

unused imports: `DateTime32`, `Header`, and `work::equihash::Solution`

Check warning on line 15 in zebra-rpc/src/methods/tests/prop.rs

View workflow job for this annotation

GitHub Actions / Test stable on macos-latest

unused imports: `DateTime32`, `Header`, and `work::equihash::Solution`

Check warning on line 15 in zebra-rpc/src/methods/tests/prop.rs

View workflow job for this annotation

GitHub Actions / Test stable on windows-latest

unused imports: `DateTime32`, `Header`, and `work::equihash::Solution`

Check warning on line 15 in zebra-rpc/src/methods/tests/prop.rs

View workflow job for this annotation

GitHub Actions / Test beta on windows-latest

unused imports: `DateTime32`, `Header`, and `work::equihash::Solution`
chain_tip::{mock::MockChainTip, ChainTip, NoChainTip},
parameters::{Network, NetworkUpgrade},
serialization::{ZcashDeserialize, ZcashSerialize},
parameters::{ConsensusBranchId, Network, NetworkUpgrade},
serialization::{DateTime32, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize},
transaction::{self, Transaction, UnminedTx, VerifiedUnminedTx},
transparent,
value_balance::ValueBalance,
work::equihash::Solution,
};
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 +371,8 @@
.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 +496,109 @@
})?;
}

/// 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);


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

// 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

// 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