From 1bb8a9c924bde4258d7085c7d7cf5457b8d7c815 Mon Sep 17 00:00:00 2001 From: Arya Date: Tue, 17 Jan 2023 02:09:07 -0500 Subject: [PATCH] change(rpc): Add getpeerinfo RPC method (#5951) * adds ValidateBlock request to state * adds `Request` enum in block verifier skips solution check for BlockProposal requests calls CheckBlockValidity instead of Commit block for BlockProposal requests * uses new Request in references to chain verifier * adds getblocktemplate proposal mode response type * makes getblocktemplate-rpcs feature in zebra-consensus select getblocktemplate-rpcs in zebra-state * Adds PR review revisions * adds info log in CheckBlockProposalValidity * Reverts replacement of match statement * adds `GetBlockTemplate::capabilities` fn * conditions calling checkpoint verifier on !request.is_proposal * updates references to validate_and_commit_non_finalized * adds snapshot test, updates test vectors * adds `should_count_metrics` to NonFinalizedState * Returns an error from chain verifier for block proposal requests below checkpoint height adds feature flags * adds "proposal" to GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD * adds back block::Request to zebra-consensus lib * updates snapshots * Removes unnecessary network arg * skips req in tracing intstrument for read state * Moves out block proposal validation to its own fn * corrects `difficulty_threshold_is_valid` docs adds/fixes some comments, adds TODOs general cleanup from a self-review. * Update zebra-state/src/service.rs * Apply suggestions from code review Co-authored-by: teor * Update zebra-rpc/src/methods/get_block_template_rpcs.rs Co-authored-by: teor * check best chain tip * Update zebra-state/src/service.rs Co-authored-by: teor * Applies cleanup suggestions from code review * updates gbt acceptance test to make a block proposal * fixes json parsing mistake * adds retries * returns reject reason if there are no retries left * moves result deserialization to RPCRequestClient method, adds docs, moves jsonrpc_core to dev-dependencies * moves sleep(EXPECTED_TX_TIME) out of loop * updates/adds info logs in retry loop * Revert "moves sleep(EXPECTED_TX_TIME) out of loop" This reverts commit f7f0926f4050519687a79afc16656c3f345c004b. * adds `allow(dead_code)` * tests with curtime, mintime, & maxtime * Fixes doc comment * Logs error responses from chain_verifier CheckProposal requests * Removes retry loop, adds num_txs log * removes verbose info log * sorts mempool_txs before generating merkle root * Make imports conditional on a feature * Disable new CI tests until bugs are fixed * adds support for getpeerinfo RPC * adds vector test * Always passes address_book to RpcServer * Update zebra-network/src/address_book.rs Co-authored-by: teor * Renames PeerObserver to AddressBookPeers * adds getpeerinfo acceptance test * Asserts that addresses parse into SocketAddr * adds launches_lightwalletd field to TestType::LaunchWithEmptyState and uses it from the get_peer_info test * renames peer_observer mod * uses SocketAddr as `addr` field type in PeerInfo Co-authored-by: teor Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zebra-network/src/address_book.rs | 55 ++++++++----- zebra-network/src/address_book_peers.rs | 17 ++++ zebra-network/src/address_book_peers/mock.rs | 25 ++++++ zebra-network/src/lib.rs | 2 + .../src/methods/get_block_template_rpcs.rs | 40 +++++++-- .../methods/get_block_template_rpcs/types.rs | 1 + .../types/peer_info.rs | 20 +++++ .../tests/snapshot/get_block_template_rpcs.rs | 27 +++++++ .../snapshots/get_peer_info@mainnet_10.snap | 9 +++ .../snapshots/get_peer_info@testnet_10.snap | 9 +++ zebra-rpc/src/methods/tests/vectors.rs | 81 +++++++++++++++++++ zebra-rpc/src/server.rs | 7 +- zebra-rpc/src/server/tests/vectors.rs | 7 ++ zebrad/src/commands/start.rs | 1 + zebrad/tests/acceptance.rs | 17 +++- .../tests/common/get_block_template_rpcs.rs | 1 + .../get_block_template_rpcs/get_peer_info.rs | 62 ++++++++++++++ zebrad/tests/common/test_type.rs | 35 ++++---- 18 files changed, 370 insertions(+), 46 deletions(-) create mode 100644 zebra-network/src/address_book_peers.rs create mode 100644 zebra-network/src/address_book_peers/mock.rs create mode 100644 zebra-rpc/src/methods/get_block_template_rpcs/types/peer_info.rs create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@mainnet_10.snap create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@testnet_10.snap create mode 100644 zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs diff --git a/zebra-network/src/address_book.rs b/zebra-network/src/address_book.rs index 9838950fe..75fc39df4 100644 --- a/zebra-network/src/address_book.rs +++ b/zebra-network/src/address_book.rs @@ -1,7 +1,13 @@ //! The `AddressBook` manages information about what peers exist, when they were //! seen, and what services they provide. -use std::{cmp::Reverse, iter::Extend, net::SocketAddr, time::Instant}; +use std::{ + cmp::Reverse, + iter::Extend, + net::SocketAddr, + sync::{Arc, Mutex}, + time::Instant, +}; use chrono::Utc; use ordered_map::OrderedMap; @@ -12,9 +18,8 @@ use zebra_chain::parameters::Network; use crate::{ constants, meta_addr::MetaAddrChange, protocol::external::canonical_socket_addr, - types::MetaAddr, PeerAddrState, + types::MetaAddr, AddressBookPeers, PeerAddrState, }; - #[cfg(test)] mod tests; @@ -288,7 +293,7 @@ impl AddressBook { ?updated, ?previous, total_peers = self.by_addr.len(), - recent_peers = self.recently_live_peers(chrono_now).count(), + recent_peers = self.recently_live_peers(chrono_now).len(), "calculated updated address book entry", ); @@ -317,7 +322,7 @@ impl AddressBook { ?updated, ?previous, total_peers = self.by_addr.len(), - recent_peers = self.recently_live_peers(chrono_now).count(), + recent_peers = self.recently_live_peers(chrono_now).len(), "updated address book entry", ); @@ -340,7 +345,7 @@ impl AddressBook { surplus = ?surplus_peer, ?updated, total_peers = self.by_addr.len(), - recent_peers = self.recently_live_peers(chrono_now).count(), + recent_peers = self.recently_live_peers(chrono_now).len(), "removed surplus address book entry", ); } @@ -370,7 +375,7 @@ impl AddressBook { trace!( ?removed_addr, total_peers = self.by_addr.len(), - recent_peers = self.recently_live_peers(chrono_now).count(), + recent_peers = self.recently_live_peers(chrono_now).len(), ); if let Some(entry) = self.by_addr.remove(&removed_addr) { @@ -452,20 +457,6 @@ impl AddressBook { .cloned() } - /// Return an iterator over peers we've seen recently, - /// in reconnection attempt order. - pub fn recently_live_peers( - &'_ self, - now: chrono::DateTime, - ) -> impl Iterator + DoubleEndedIterator + '_ { - let _guard = self.span.enter(); - - self.by_addr - .descending_values() - .filter(move |peer| peer.was_recently_live(now)) - .cloned() - } - /// Returns the number of entries in this address book. pub fn len(&self) -> usize { self.by_addr.len() @@ -501,7 +492,7 @@ impl AddressBook { let failed = self.state_peers(PeerAddrState::Failed).count(); let attempt_pending = self.state_peers(PeerAddrState::AttemptPending).count(); - let recently_live = self.recently_live_peers(now).count(); + let recently_live = self.recently_live_peers(now).len(); let recently_stopped_responding = responded .checked_sub(recently_live) .expect("all recently live peers must have responded"); @@ -597,6 +588,26 @@ impl AddressBook { } } +impl AddressBookPeers for AddressBook { + fn recently_live_peers(&self, now: chrono::DateTime) -> Vec { + let _guard = self.span.enter(); + + self.by_addr + .descending_values() + .filter(|peer| peer.was_recently_live(now)) + .cloned() + .collect() + } +} + +impl AddressBookPeers for Arc> { + fn recently_live_peers(&self, now: chrono::DateTime) -> Vec { + self.lock() + .expect("panic in a previous thread that was holding the mutex") + .recently_live_peers(now) + } +} + impl Extend for AddressBook { fn extend(&mut self, iter: T) where diff --git a/zebra-network/src/address_book_peers.rs b/zebra-network/src/address_book_peers.rs new file mode 100644 index 000000000..308268050 --- /dev/null +++ b/zebra-network/src/address_book_peers.rs @@ -0,0 +1,17 @@ +//! A AddressBookPeers trait for getting the [`MetaAddr`] of recently live peers. + +use chrono::Utc; + +use crate::meta_addr::MetaAddr; + +#[cfg(any(test, feature = "proptest-impl"))] +pub mod mock; + +#[cfg(any(test, feature = "proptest-impl"))] +pub use mock::MockAddressBookPeers; + +/// Method signatures for getting [`MetaAddr`]s of recently live peers. +pub trait AddressBookPeers { + /// Return an Vec of peers we've seen recently, in reconnection attempt order. + fn recently_live_peers(&self, now: chrono::DateTime) -> Vec; +} diff --git a/zebra-network/src/address_book_peers/mock.rs b/zebra-network/src/address_book_peers/mock.rs new file mode 100644 index 000000000..58253530f --- /dev/null +++ b/zebra-network/src/address_book_peers/mock.rs @@ -0,0 +1,25 @@ +//! Mock [`AddressBookPeers`] for use in tests. + +use crate::{meta_addr::MetaAddr, AddressBookPeers}; + +/// A mock [`AddressBookPeers`] implementation that's always empty. +#[derive(Default, Clone)] +pub struct MockAddressBookPeers { + /// Return value for mock `recently_live_peers` method. + recently_live_peers: Vec, +} + +impl MockAddressBookPeers { + /// Creates a new [`MockAddressBookPeers`] + pub fn new(recently_live_peers: Vec) -> Self { + Self { + recently_live_peers, + } + } +} + +impl AddressBookPeers for MockAddressBookPeers { + fn recently_live_peers(&self, _now: chrono::DateTime) -> Vec { + self.recently_live_peers.clone() + } +} diff --git a/zebra-network/src/lib.rs b/zebra-network/src/lib.rs index 75dbda0d1..5e5f884f8 100644 --- a/zebra-network/src/lib.rs +++ b/zebra-network/src/lib.rs @@ -144,6 +144,7 @@ extern crate bitflags; pub type BoxError = Box; mod address_book; +pub mod address_book_peers; mod address_book_updater; mod config; pub mod constants; @@ -171,6 +172,7 @@ pub use crate::isolated::{ pub use crate::{ address_book::AddressBook, + address_book_peers::AddressBookPeers, config::Config, isolated::{connect_isolated, connect_isolated_tcp_direct}, meta_addr::PeerAddrState, diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 8182f822e..8f56d77eb 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -16,6 +16,7 @@ use zebra_chain::{ transparent, }; use zebra_consensus::VerifyChainError; +use zebra_network::AddressBookPeers; use zebra_node_services::mempool; use zebra_state::{ReadRequest, ReadResponse}; @@ -31,7 +32,7 @@ use crate::methods::{ }, types::{ get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData, - long_poll::LongPollInput, submit_block, + long_poll::LongPollInput, peer_info::PeerInfo, submit_block, }, }, height_from_signed_int, GetBlockHash, MISSING_BLOCK_ERROR_CODE, @@ -149,10 +150,16 @@ pub trait GetBlockTemplateRpc { ) -> BoxFuture> { self.get_network_sol_ps(num_blocks, height) } + + /// Returns data about each connected network node. + /// + /// zcashd reference: [`getpeerinfo`](https://zcash.github.io/rpc/getpeerinfo.html) + #[rpc(name = "getpeerinfo")] + fn get_peer_info(&self) -> BoxFuture>>; } /// RPC method implementations. -pub struct GetBlockTemplateRpcImpl +pub struct GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -170,6 +177,7 @@ where + Sync + 'static, SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static, + AddressBook: AddressBookPeers, { // Configuration // @@ -197,10 +205,13 @@ where /// The chain sync status, used for checking if Zebra is likely close to the network chain tip. sync_status: SyncStatus, + + /// Address book of peers, used for `getpeerinfo`. + address_book: AddressBook, } -impl - GetBlockTemplateRpcImpl +impl + GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -222,8 +233,10 @@ where + Sync + 'static, SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static, + AddressBook: AddressBookPeers + Clone + Send + Sync + 'static, { /// Create a new instance of the handler for getblocktemplate RPCs. + #[allow(clippy::too_many_arguments)] pub fn new( network: Network, mining_config: config::Config, @@ -232,6 +245,7 @@ where latest_chain_tip: Tip, chain_verifier: ChainVerifier, sync_status: SyncStatus, + address_book: AddressBook, ) -> Self { Self { network, @@ -241,12 +255,13 @@ where latest_chain_tip, chain_verifier, sync_status, + address_book, } } } -impl GetBlockTemplateRpc - for GetBlockTemplateRpcImpl +impl GetBlockTemplateRpc + for GetBlockTemplateRpcImpl where Mempool: Service< mempool::Request, @@ -271,6 +286,7 @@ where + 'static, >::Future: Send, SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static, + AddressBook: AddressBookPeers + Clone + Send + Sync + 'static, { fn get_block_count(&self) -> Result { best_chain_tip_height(&self.latest_chain_tip).map(|height| height.0) @@ -704,6 +720,18 @@ where } .boxed() } + + fn get_peer_info(&self) -> BoxFuture>> { + let address_book = self.address_book.clone(); + async move { + Ok(address_book + .recently_live_peers(chrono::Utc::now()) + .into_iter() + .map(PeerInfo::from) + .collect()) + } + .boxed() + } } // Put support functions in a submodule, to keep this file small. diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs index f3c7fd1eb..7de7ceaf6 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -5,5 +5,6 @@ pub mod get_block_template; pub mod get_mining_info; pub mod hex_data; pub mod long_poll; +pub mod peer_info; pub mod submit_block; pub mod transaction; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/peer_info.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/peer_info.rs new file mode 100644 index 000000000..681f5f32a --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/peer_info.rs @@ -0,0 +1,20 @@ +//! An array of [`PeerInfo`] is the output of the `getpeerinfo` RPC method. + +use std::net::SocketAddr; + +use zebra_network::types::MetaAddr; + +/// Item of the `getpeerinfo` response +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PeerInfo { + /// The IP address and port of the peer + pub addr: SocketAddr, +} + +impl From for PeerInfo { + fn from(meta_addr: MetaAddr) -> Self { + Self { + addr: meta_addr.addr(), + } + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 0c747de34..d7ef39bed 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -5,6 +5,8 @@ //! cargo insta test --review --features getblocktemplate-rpcs --delete-unreferenced-snapshots //! ``` +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use hex::FromHex; use insta::Settings; use tower::{buffer::Buffer, Service}; @@ -19,6 +21,7 @@ use zebra_chain::{ transparent, work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256}, }; +use zebra_network::{address_book_peers::MockAddressBookPeers, types::MetaAddr}; use zebra_node_services::mempool; use zebra_state::{GetBlockTemplateChainInfo, ReadRequest, ReadResponse}; @@ -36,6 +39,7 @@ use crate::methods::{ get_mining_info, hex_data::HexData, long_poll::{LongPollId, LONG_POLL_ID_LENGTH}, + peer_info::PeerInfo, submit_block, }, }, @@ -113,6 +117,14 @@ pub async fn test_responses( mock_chain_tip_sender.send_best_tip_hash(fake_tip_hash); mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(0)); + let mock_address_book = + MockAddressBookPeers::new(vec![MetaAddr::new_initial_peer(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + network.default_port(), + )) + .into_new_meta_addr() + .unwrap()]); + // get an rpc instance with continuous blockchain state let get_block_template_rpc = GetBlockTemplateRpcImpl::new( network, @@ -122,6 +134,7 @@ pub async fn test_responses( mock_chain_tip.clone(), chain_verifier.clone(), mock_sync_status.clone(), + mock_address_book, ); // `getblockcount` @@ -145,6 +158,13 @@ pub async fn test_responses( .expect("We should have a success response"); snapshot_rpc_getmininginfo(get_mining_info, &settings); + // `getpeerinfo` + let get_peer_info = get_block_template_rpc + .get_peer_info() + .await + .expect("We should have a success response"); + snapshot_rpc_getpeerinfo(get_peer_info, &settings); + // `getnetworksolps` (and `getnetworkhashps`) let get_network_sol_ps = get_block_template_rpc .get_network_sol_ps(None, None) @@ -169,6 +189,7 @@ pub async fn test_responses( mock_chain_tip.clone(), chain_verifier, mock_sync_status.clone(), + MockAddressBookPeers::default(), ); // Basic variant (default mode and no extra features) @@ -308,6 +329,7 @@ pub async fn test_responses( mock_chain_tip, mock_chain_verifier.clone(), mock_sync_status, + MockAddressBookPeers::default(), ); let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(Some( @@ -391,6 +413,11 @@ fn snapshot_rpc_getmininginfo( settings.bind(|| insta::assert_json_snapshot!("get_mining_info", get_mining_info)); } +/// Snapshot `getpeerinfo` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getpeerinfo(get_peer_info: Vec, settings: &insta::Settings) { + settings.bind(|| insta::assert_json_snapshot!("get_peer_info", get_peer_info)); +} + /// Snapshot `getnetworksolps` response, using `cargo insta` and JSON serialization. fn snapshot_rpc_getnetworksolps(get_network_sol_ps: u64, settings: &insta::Settings) { settings.bind(|| insta::assert_json_snapshot!("get_network_sol_ps", get_network_sol_ps)); diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@mainnet_10.snap new file mode 100644 index 000000000..71d1375eb --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@mainnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: get_peer_info +--- +[ + { + "addr": "127.0.0.1:8233" + } +] diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@testnet_10.snap new file mode 100644 index 000000000..e9ad3a7f9 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_peer_info@testnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: get_peer_info +--- +[ + { + "addr": "127.0.0.1:18233" + } +] diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 8b3b8475e..2c23ea5f2 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -625,6 +625,7 @@ async fn rpc_getaddressutxos_response() { #[cfg(feature = "getblocktemplate-rpcs")] async fn rpc_getblockcount() { use zebra_chain::chain_sync_status::MockSyncStatus; + use zebra_network::address_book_peers::MockAddressBookPeers; let _init_guard = zebra_test::init(); @@ -667,6 +668,7 @@ async fn rpc_getblockcount() { latest_chain_tip.clone(), chain_verifier, MockSyncStatus::default(), + MockAddressBookPeers::default(), ); // Get the tip height using RPC method `get_block_count` @@ -684,6 +686,7 @@ async fn rpc_getblockcount() { #[tokio::test(flavor = "multi_thread")] async fn rpc_getblockcount_empty_state() { use zebra_chain::chain_sync_status::MockSyncStatus; + use zebra_network::address_book_peers::MockAddressBookPeers; let _init_guard = zebra_test::init(); @@ -715,6 +718,7 @@ async fn rpc_getblockcount_empty_state() { latest_chain_tip.clone(), chain_verifier, MockSyncStatus::default(), + MockAddressBookPeers::default(), ); // Get the tip height using RPC method `get_block_count @@ -729,10 +733,78 @@ async fn rpc_getblockcount_empty_state() { mempool.expect_no_requests().await; } +#[cfg(feature = "getblocktemplate-rpcs")] +#[tokio::test(flavor = "multi_thread")] +async fn rpc_getpeerinfo() { + use zebra_chain::chain_sync_status::MockSyncStatus; + use zebra_network::address_book_peers::MockAddressBookPeers; + + let _init_guard = zebra_test::init(); + let network = Mainnet; + + // Get a mempool handle + let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); + // Create an empty state + let (state, read_state, latest_chain_tip, _chain_tip_change) = + zebra_state::init_test_services(Mainnet); + + let ( + chain_verifier, + _transaction_verifier, + _parameter_download_task_handle, + _max_checkpoint_height, + ) = zebra_consensus::chain::init( + zebra_consensus::Config::default(), + network, + state.clone(), + true, + ) + .await; + + let mock_peer_address = + zebra_network::types::MetaAddr::new_initial_peer(std::net::SocketAddr::new( + std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), + network.default_port(), + )) + .into_new_meta_addr() + .unwrap(); + + let mock_address_book = MockAddressBookPeers::new(vec![mock_peer_address]); + + // Init RPC + let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( + network, + Default::default(), + Buffer::new(mempool.clone(), 1), + read_state, + latest_chain_tip.clone(), + chain_verifier, + MockSyncStatus::default(), + mock_address_book, + ); + + // Call `get_peer_info` + let get_peer_info = get_block_template_rpc + .get_peer_info() + .await + .expect("We should have an array of addresses"); + + assert_eq!( + get_peer_info + .into_iter() + .next() + .expect("there should be a mock peer address"), + mock_peer_address.into() + ); + + mempool.expect_no_requests().await; +} + #[cfg(feature = "getblocktemplate-rpcs")] #[tokio::test(flavor = "multi_thread")] async fn rpc_getblockhash() { use zebra_chain::chain_sync_status::MockSyncStatus; + use zebra_network::address_book_peers::MockAddressBookPeers; let _init_guard = zebra_test::init(); @@ -769,6 +841,7 @@ async fn rpc_getblockhash() { latest_chain_tip.clone(), tower::ServiceBuilder::new().service(chain_verifier), MockSyncStatus::default(), + MockAddressBookPeers::default(), ); // Query the hashes using positive indexes @@ -801,6 +874,7 @@ async fn rpc_getblockhash() { #[tokio::test(flavor = "multi_thread")] async fn rpc_getmininginfo() { use zebra_chain::chain_sync_status::MockSyncStatus; + use zebra_network::address_book_peers::MockAddressBookPeers; let _init_guard = zebra_test::init(); @@ -823,6 +897,7 @@ async fn rpc_getmininginfo() { latest_chain_tip.clone(), MockService::build().for_unit_tests(), MockSyncStatus::default(), + MockAddressBookPeers::default(), ); get_block_template_rpc @@ -835,6 +910,7 @@ async fn rpc_getmininginfo() { #[tokio::test(flavor = "multi_thread")] async fn rpc_getnetworksolps() { use zebra_chain::chain_sync_status::MockSyncStatus; + use zebra_network::address_book_peers::MockAddressBookPeers; let _init_guard = zebra_test::init(); @@ -857,6 +933,7 @@ async fn rpc_getnetworksolps() { latest_chain_tip.clone(), MockService::build().for_unit_tests(), MockSyncStatus::default(), + MockAddressBookPeers::default(), ); let get_network_sol_ps_inputs = [ @@ -910,6 +987,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { work::difficulty::{CompactDifficulty, ExpandedDifficulty, U256}, }; use zebra_consensus::MAX_BLOCK_SIGOPS; + use zebra_network::address_book_peers::MockAddressBookPeers; use zebra_state::{GetBlockTemplateChainInfo, ReadRequest, ReadResponse}; use crate::methods::{ @@ -969,6 +1047,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { mock_chain_tip, chain_verifier, mock_sync_status.clone(), + MockAddressBookPeers::default(), ); // Fake the ChainInfo response @@ -1140,6 +1219,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { #[tokio::test(flavor = "multi_thread")] async fn rpc_submitblock_errors() { use zebra_chain::chain_sync_status::MockSyncStatus; + use zebra_network::address_book_peers::MockAddressBookPeers; use crate::methods::get_block_template_rpcs::types::{hex_data::HexData, submit_block}; @@ -1179,6 +1259,7 @@ async fn rpc_submitblock_errors() { latest_chain_tip.clone(), chain_verifier, MockSyncStatus::default(), + MockAddressBookPeers::default(), ); // Try to submit pre-populated blocks and assert that it responds with duplicate. diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index d3548a057..5c40ba584 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -19,6 +19,7 @@ use tracing::{Instrument, *}; use zebra_chain::{ block, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, parameters::Network, }; +use zebra_network::AddressBookPeers; use zebra_node_services::mempool; use crate::{ @@ -69,7 +70,7 @@ impl RpcServer { // // TODO: put some of the configs or services in their own struct? #[allow(clippy::too_many_arguments)] - pub fn spawn( + pub fn spawn( config: Config, #[cfg(feature = "getblocktemplate-rpcs")] mining_config: get_block_template_rpcs::config::Config, @@ -83,6 +84,8 @@ impl RpcServer { chain_verifier: ChainVerifier, #[cfg_attr(not(feature = "getblocktemplate-rpcs"), allow(unused_variables))] sync_status: SyncStatus, + #[cfg_attr(not(feature = "getblocktemplate-rpcs"), allow(unused_variables))] + address_book: AddressBook, latest_chain_tip: Tip, network: Network, ) -> (JoinHandle<()>, JoinHandle<()>, Option) @@ -114,6 +117,7 @@ impl RpcServer { + 'static, >::Future: Send, SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static, + AddressBook: AddressBookPeers + Clone + Send + Sync + 'static, { if let Some(listen_addr) = config.listen_addr { info!("Trying to open RPC endpoint at {}...", listen_addr,); @@ -144,6 +148,7 @@ impl RpcServer { latest_chain_tip.clone(), chain_verifier, sync_status, + address_book, ); io.extend_with(get_block_template_rpc_impl.to_delegate()); diff --git a/zebra-rpc/src/server/tests/vectors.rs b/zebra-rpc/src/server/tests/vectors.rs index 33699460a..ad7bb8b05 100644 --- a/zebra-rpc/src/server/tests/vectors.rs +++ b/zebra-rpc/src/server/tests/vectors.rs @@ -11,6 +11,7 @@ use tower::buffer::Buffer; use zebra_chain::{ chain_sync_status::MockSyncStatus, chain_tip::NoChainTip, parameters::Network::*, }; +use zebra_network::address_book_peers::MockAddressBookPeers; use zebra_node_services::BoxError; use zebra_test::mock_service::MockService; @@ -61,6 +62,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { Buffer::new(state.clone(), 1), Buffer::new(chain_verifier.clone(), 1), MockSyncStatus::default(), + MockAddressBookPeers::default(), NoChainTip, Mainnet, ); @@ -146,6 +148,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool, do_shutdown: bo Buffer::new(state.clone(), 1), Buffer::new(chain_verifier.clone(), 1), MockSyncStatus::default(), + MockAddressBookPeers::default(), NoChainTip, Mainnet, ); @@ -225,6 +228,7 @@ fn rpc_server_spawn_port_conflict() { Buffer::new(state.clone(), 1), Buffer::new(chain_verifier.clone(), 1), MockSyncStatus::default(), + MockAddressBookPeers::default(), NoChainTip, Mainnet, ); @@ -241,6 +245,7 @@ fn rpc_server_spawn_port_conflict() { Buffer::new(state.clone(), 1), Buffer::new(chain_verifier.clone(), 1), MockSyncStatus::default(), + MockAddressBookPeers::default(), NoChainTip, Mainnet, ); @@ -331,6 +336,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { Buffer::new(state.clone(), 1), Buffer::new(chain_verifier.clone(), 1), MockSyncStatus::default(), + MockAddressBookPeers::default(), NoChainTip, Mainnet, ); @@ -347,6 +353,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { Buffer::new(state.clone(), 1), Buffer::new(chain_verifier.clone(), 1), MockSyncStatus::default(), + MockAddressBookPeers::default(), NoChainTip, Mainnet, ); diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index 9c47ece80..dd5935824 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -186,6 +186,7 @@ impl StartCmd { read_only_state_service, chain_verifier.clone(), sync_status.clone(), + address_book.clone(), latest_chain_tip.clone(), config.network.network, ); diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index fc6bdec57..e532f4509 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -1483,7 +1483,9 @@ fn non_blocking_logger() -> Result<()> { #[test] #[cfg(not(target_os = "windows"))] fn lightwalletd_integration() -> Result<()> { - lightwalletd_integration_test(LaunchWithEmptyState) + lightwalletd_integration_test(LaunchWithEmptyState { + launches_lightwalletd: true, + }) } /// Make sure `zebrad` can sync from peers, but don't actually launch `lightwalletd`. @@ -1547,7 +1549,9 @@ fn lightwalletd_full_sync() -> Result<()> { #[ignore] #[cfg(not(target_os = "windows"))] async fn lightwalletd_test_suite() -> Result<()> { - lightwalletd_integration_test(LaunchWithEmptyState)?; + lightwalletd_integration_test(LaunchWithEmptyState { + launches_lightwalletd: true, + })?; // Only runs when ZEBRA_CACHED_STATE_DIR is set. lightwalletd_integration_test(UpdateZebraCachedStateNoRpc)?; @@ -2174,6 +2178,15 @@ async fn lightwalletd_wallet_grpc_tests() -> Result<()> { common::lightwalletd::wallet_grpc_test::run().await } +/// Test successful getpeerinfo rpc call +/// +/// See [`common::get_block_template_rpcs::get_peer_info`] for more information. +#[tokio::test] +#[cfg(feature = "getblocktemplate-rpcs")] +async fn get_peer_info() -> Result<()> { + common::get_block_template_rpcs::get_peer_info::run().await +} + /// Test successful getblocktemplate rpc call /// /// See [`common::get_block_template_rpcs::get_block_template`] for more information. diff --git a/zebrad/tests/common/get_block_template_rpcs.rs b/zebrad/tests/common/get_block_template_rpcs.rs index 6fe12254a..c9d936c8e 100644 --- a/zebrad/tests/common/get_block_template_rpcs.rs +++ b/zebrad/tests/common/get_block_template_rpcs.rs @@ -1,4 +1,5 @@ //! Acceptance tests for getblocktemplate RPC methods in Zebra. pub(crate) mod get_block_template; +pub(crate) mod get_peer_info; pub(crate) mod submit_block; diff --git a/zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs b/zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs new file mode 100644 index 000000000..bbd120960 --- /dev/null +++ b/zebrad/tests/common/get_block_template_rpcs/get_peer_info.rs @@ -0,0 +1,62 @@ +//! Tests that `getpeerinfo` RPC method responds with info about at least 1 peer. + +use color_eyre::eyre::{Context, Result}; + +use zebra_chain::parameters::Network; +use zebra_rpc::methods::get_block_template_rpcs::types::peer_info::PeerInfo; + +use crate::common::{ + launch::{can_spawn_zebrad_for_rpc, spawn_zebrad_for_rpc}, + rpc_client::RPCRequestClient, + test_type::TestType, +}; + +pub(crate) async fn run() -> Result<()> { + let _init_guard = zebra_test::init(); + + let test_type = TestType::LaunchWithEmptyState { + launches_lightwalletd: false, + }; + let test_name = "get_peer_info_test"; + let network = Network::Mainnet; + + // Skip the test unless the user specifically asked for it and there is a zebrad_state_path + if !can_spawn_zebrad_for_rpc(test_name, test_type) { + return Ok(()); + } + + tracing::info!(?network, "running getpeerinfo test using zebrad",); + + let (mut zebrad, zebra_rpc_address) = + spawn_zebrad_for_rpc(network, test_name, test_type, true)? + .expect("Already checked zebra state path with can_spawn_zebrad_for_rpc"); + + let rpc_address = zebra_rpc_address.expect("getpeerinfo test must have RPC port"); + + // Wait until port is open. + zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?; + + tracing::info!(?rpc_address, "zebrad opened its RPC port",); + + // call `getpeerinfo` RPC method + let peer_info_result: Vec = RPCRequestClient::new(rpc_address) + .json_result_from_call("getpeerinfo", "[]".to_string()) + .await?; + + assert!( + !peer_info_result.is_empty(), + "getpeerinfo should return info for at least 1 peer" + ); + + zebrad.kill(false)?; + + let output = zebrad.wait_with_output()?; + let output = output.assert_failure()?; + + // [Note on port conflict](#Note on port conflict) + output + .assert_was_killed() + .wrap_err("Possible port conflict. Are there other acceptance tests running?")?; + + Ok(()) +} diff --git a/zebrad/tests/common/test_type.rs b/zebrad/tests/common/test_type.rs index 385a38ddf..a94420160 100644 --- a/zebrad/tests/common/test_type.rs +++ b/zebrad/tests/common/test_type.rs @@ -23,7 +23,10 @@ use TestType::*; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum TestType { /// Launch with an empty Zebra and lightwalletd state. - LaunchWithEmptyState, + LaunchWithEmptyState { + /// Configures whether the test uses lightwalletd. + launches_lightwalletd: bool, + }, /// Do a full sync from an empty lightwalletd state. /// @@ -66,7 +69,7 @@ impl TestType { // - FullSyncFromGenesis, UpdateCachedState, UpdateZebraCachedStateNoRpc: // skip the test if it is not available match self { - LaunchWithEmptyState => false, + LaunchWithEmptyState { .. } => false, FullSyncFromGenesis { .. } | UpdateCachedState | UpdateZebraCachedStateNoRpc @@ -77,11 +80,10 @@ impl TestType { /// Does this test need a Zebra rpc server? pub fn needs_zebra_rpc_server(&self) -> bool { match self { - UpdateZebraCachedStateWithRpc => true, - UpdateZebraCachedStateNoRpc - | LaunchWithEmptyState - | FullSyncFromGenesis { .. } - | UpdateCachedState => self.launches_lightwalletd(), + UpdateZebraCachedStateWithRpc | LaunchWithEmptyState { .. } => true, + UpdateZebraCachedStateNoRpc | FullSyncFromGenesis { .. } | UpdateCachedState => { + self.launches_lightwalletd() + } } } @@ -89,7 +91,10 @@ impl TestType { pub fn launches_lightwalletd(&self) -> bool { match self { UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => false, - LaunchWithEmptyState | FullSyncFromGenesis { .. } | UpdateCachedState => true, + FullSyncFromGenesis { .. } | UpdateCachedState => true, + LaunchWithEmptyState { + launches_lightwalletd, + } => *launches_lightwalletd, } } @@ -100,7 +105,7 @@ impl TestType { // - FullSyncFromGenesis: use it if available, timeout if it is already populated // - UpdateCachedState: skip the test if it is not available match self { - LaunchWithEmptyState + LaunchWithEmptyState { .. } | FullSyncFromGenesis { .. } | UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => false, @@ -111,7 +116,7 @@ impl TestType { /// Does this test allow a `lightwalletd` cached state, even if it is not required? pub fn allow_lightwalletd_cached_state(&self) -> bool { match self { - LaunchWithEmptyState => false, + LaunchWithEmptyState { .. } => false, FullSyncFromGenesis { allow_lightwalletd_cached_state, } => *allow_lightwalletd_cached_state, @@ -122,7 +127,7 @@ impl TestType { /// Can this test create a new `LIGHTWALLETD_DATA_DIR` cached state? pub fn can_create_lightwalletd_cached_state(&self) -> bool { match self { - LaunchWithEmptyState => false, + LaunchWithEmptyState { .. } => false, FullSyncFromGenesis { .. } | UpdateCachedState => true, UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => false, } @@ -232,7 +237,7 @@ impl TestType { /// Returns the `zebrad` timeout for this test type. pub fn zebrad_timeout(&self) -> Duration { match self { - LaunchWithEmptyState => LIGHTWALLETD_DELAY, + LaunchWithEmptyState { .. } => LIGHTWALLETD_DELAY, FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY, UpdateCachedState | UpdateZebraCachedStateNoRpc => LIGHTWALLETD_UPDATE_TIP_DELAY, UpdateZebraCachedStateWithRpc => FINISH_PARTIAL_SYNC_TIMEOUT, @@ -249,7 +254,7 @@ impl TestType { // We use the same timeouts for zebrad and lightwalletd, // because the tests check zebrad and lightwalletd concurrently. match self { - LaunchWithEmptyState => LIGHTWALLETD_DELAY, + LaunchWithEmptyState { .. } => LIGHTWALLETD_DELAY, FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY, UpdateCachedState | UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => { LIGHTWALLETD_UPDATE_TIP_DELAY @@ -270,7 +275,7 @@ impl TestType { // Fail if we need a cached Zebra state, but it's empty zebrad_failure_messages.push("loaded Zebra state cache .*tip.*=.*None".to_string()); } - if *self == LaunchWithEmptyState { + if matches!(*self, LaunchWithEmptyState { .. }) { // Fail if we need an empty Zebra state, but it has blocks zebrad_failure_messages .push(r"loaded Zebra state cache .*tip.*=.*Height\([1-9][0-9]*\)".to_string()); @@ -311,7 +316,7 @@ impl TestType { lightwalletd_failure_messages.push("Found [1-9][0-9]* blocks in cache".to_string()); } - let lightwalletd_ignore_messages = if *self == LaunchWithEmptyState { + let lightwalletd_ignore_messages = if matches!(*self, LaunchWithEmptyState { .. }) { LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES.iter() } else { NO_MATCHES_REGEX_ITER.iter()