feature(rpc): Extend `getinfo` (#9261)
* introduce new fields to `GetInfo` * add address book to methods for getinfo connections field * add the version field to getinfo * track last node error or warning and display it in getinfo rpc method * add the rest of the fields, minor cleanup * fix the tests * clippy
This commit is contained in:
parent
b4211aa1cf
commit
5cf5178d50
|
@ -6174,6 +6174,7 @@ dependencies = [
|
|||
"proptest",
|
||||
"prost",
|
||||
"rand 0.8.5",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.11",
|
||||
|
|
|
@ -62,6 +62,7 @@ jsonrpsee-types = { workspace = true }
|
|||
jsonrpsee-proc-macros = { workspace = true }
|
||||
hyper = { workspace = true }
|
||||
http-body-util = { workspace = true }
|
||||
semver = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
indexmap = { workspace = true, features = ["serde"] }
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ use zebra_chain::{
|
|||
},
|
||||
};
|
||||
use zebra_consensus::ParameterCheckpoint;
|
||||
use zebra_network::address_book_peers::AddressBookPeers;
|
||||
use zebra_node_services::mempool;
|
||||
use zebra_state::{
|
||||
HashOrHeight, OutputIndex, OutputLocation, ReadRequest, ReadResponse, TransactionLocation,
|
||||
|
@ -89,7 +90,7 @@ pub trait Rpc {
|
|||
/// Some fields from the zcashd reference are missing from Zebra's [`GetInfo`]. It only contains the fields
|
||||
/// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L91-L95)
|
||||
#[method(name = "getinfo")]
|
||||
fn get_info(&self) -> Result<GetInfo>;
|
||||
async fn get_info(&self) -> Result<GetInfo>;
|
||||
|
||||
/// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct.
|
||||
///
|
||||
|
@ -358,7 +359,7 @@ pub trait Rpc {
|
|||
|
||||
/// RPC method implementations.
|
||||
#[derive(Clone)]
|
||||
pub struct RpcImpl<Mempool, State, Tip>
|
||||
pub struct RpcImpl<Mempool, State, Tip, AddressBook>
|
||||
where
|
||||
Mempool: Service<
|
||||
mempool::Request,
|
||||
|
@ -379,6 +380,7 @@ where
|
|||
+ 'static,
|
||||
State::Future: Send,
|
||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
|
||||
{
|
||||
// Configuration
|
||||
//
|
||||
|
@ -414,9 +416,19 @@ where
|
|||
//
|
||||
/// A sender component of a channel used to send transactions to the mempool queue.
|
||||
queue_sender: broadcast::Sender<UnminedTx>,
|
||||
|
||||
/// Peer address book.
|
||||
address_book: AddressBook,
|
||||
|
||||
/// The last warning or error event logged by the server.
|
||||
last_event: LoggedLastEvent,
|
||||
}
|
||||
|
||||
impl<Mempool, State, Tip> Debug for RpcImpl<Mempool, State, Tip>
|
||||
/// A type alias for the last event logged by the server.
|
||||
pub type LoggedLastEvent =
|
||||
Arc<std::sync::Mutex<Option<(String, tracing::Level, chrono::DateTime<Utc>)>>>;
|
||||
|
||||
impl<Mempool, State, Tip, AddressBook> Debug for RpcImpl<Mempool, State, Tip, AddressBook>
|
||||
where
|
||||
Mempool: Service<
|
||||
mempool::Request,
|
||||
|
@ -437,6 +449,7 @@ where
|
|||
+ 'static,
|
||||
State::Future: Send,
|
||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// Skip fields without Debug impls, and skip channels
|
||||
|
@ -450,7 +463,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Mempool, State, Tip> RpcImpl<Mempool, State, Tip>
|
||||
impl<Mempool, State, Tip, AddressBook> RpcImpl<Mempool, State, Tip, AddressBook>
|
||||
where
|
||||
Mempool: Service<
|
||||
mempool::Request,
|
||||
|
@ -471,6 +484,7 @@ where
|
|||
+ 'static,
|
||||
State::Future: Send,
|
||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
|
||||
{
|
||||
/// Create a new instance of the RPC handler.
|
||||
//
|
||||
|
@ -486,6 +500,8 @@ where
|
|||
mempool: Mempool,
|
||||
state: State,
|
||||
latest_chain_tip: Tip,
|
||||
address_book: AddressBook,
|
||||
last_event: LoggedLastEvent,
|
||||
) -> (Self, JoinHandle<()>)
|
||||
where
|
||||
VersionString: ToString + Clone + Send + 'static,
|
||||
|
@ -511,6 +527,8 @@ where
|
|||
state: state.clone(),
|
||||
latest_chain_tip: latest_chain_tip.clone(),
|
||||
queue_sender,
|
||||
address_book,
|
||||
last_event,
|
||||
};
|
||||
|
||||
// run the process queue
|
||||
|
@ -525,7 +543,7 @@ where
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Mempool, State, Tip> RpcServer for RpcImpl<Mempool, State, Tip>
|
||||
impl<Mempool, State, Tip, AddressBook> RpcServer for RpcImpl<Mempool, State, Tip, AddressBook>
|
||||
where
|
||||
Mempool: Service<
|
||||
mempool::Request,
|
||||
|
@ -546,11 +564,56 @@ where
|
|||
+ 'static,
|
||||
State::Future: Send,
|
||||
Tip: ChainTip + Clone + Send + Sync + 'static,
|
||||
AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn get_info(&self) -> Result<GetInfo> {
|
||||
async fn get_info(&self) -> Result<GetInfo> {
|
||||
let version = GetInfo::version(&self.build_version).ok_or(ErrorObject::owned(
|
||||
server::error::LegacyCode::Misc.into(),
|
||||
"invalid version string",
|
||||
None::<()>,
|
||||
))?;
|
||||
|
||||
// TODO: Change to use `currently_live_peers()` after #9214
|
||||
let connections = self.address_book.recently_live_peers(Utc::now()).len();
|
||||
|
||||
let last_error_recorded = self.last_event.lock().expect("mutex poisoned").clone();
|
||||
let (last_event, _last_event_level, last_event_time) = last_error_recorded.unwrap_or((
|
||||
GetInfo::default().errors,
|
||||
tracing::Level::INFO,
|
||||
Utc::now(),
|
||||
));
|
||||
|
||||
let tip_height = self
|
||||
.latest_chain_tip
|
||||
.best_tip_height()
|
||||
.unwrap_or(Height::MIN);
|
||||
let testnet = self.network.is_a_test_network();
|
||||
|
||||
// This field is behind the `ENABLE_WALLET` feature flag in zcashd:
|
||||
// https://github.com/zcash/zcash/blob/v6.1.0/src/rpc/misc.cpp#L113
|
||||
// However it is not documented as optional:
|
||||
// https://github.com/zcash/zcash/blob/v6.1.0/src/rpc/misc.cpp#L70
|
||||
// For compatibility, we keep the field in the response, but always return 0.
|
||||
let pay_tx_fee = 0.0;
|
||||
|
||||
let relay_fee = zebra_chain::transaction::zip317::MIN_MEMPOOL_TX_FEE_RATE as f64
|
||||
/ (zebra_chain::amount::COIN as f64);
|
||||
let difficulty = chain_tip_difficulty(self.network.clone(), self.state.clone()).await?;
|
||||
|
||||
let response = GetInfo {
|
||||
version,
|
||||
build: self.build_version.clone(),
|
||||
subversion: self.user_agent.clone(),
|
||||
protocol_version: zebra_network::constants::CURRENT_NETWORK_PROTOCOL_VERSION.0,
|
||||
blocks: tip_height.0,
|
||||
connections,
|
||||
proxy: None,
|
||||
difficulty,
|
||||
testnet,
|
||||
pay_tx_fee,
|
||||
relay_fee,
|
||||
errors: last_event,
|
||||
errors_timestamp: last_event_time.to_string(),
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
|
@ -1542,33 +1605,161 @@ where
|
|||
/// Response to a `getinfo` RPC request.
|
||||
///
|
||||
/// See the notes for the [`Rpc::get_info` method].
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GetInfo {
|
||||
/// The node version
|
||||
version: u64,
|
||||
|
||||
/// The node version build number
|
||||
build: String,
|
||||
|
||||
/// The server sub-version identifier, used as the network protocol user-agent
|
||||
subversion: String,
|
||||
|
||||
/// The protocol version
|
||||
#[serde(rename = "protocolversion")]
|
||||
protocol_version: u32,
|
||||
|
||||
/// The current number of blocks processed in the server
|
||||
blocks: u32,
|
||||
|
||||
/// The total (inbound and outbound) number of connections the node has
|
||||
connections: usize,
|
||||
|
||||
/// The proxy (if any) used by the server. Currently always `None` in Zebra.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
proxy: Option<String>,
|
||||
|
||||
/// The current network difficulty
|
||||
difficulty: f64,
|
||||
|
||||
/// True if the server is running in testnet mode, false otherwise
|
||||
testnet: bool,
|
||||
|
||||
/// The minimum transaction fee in ZEC/kB
|
||||
#[serde(rename = "paytxfee")]
|
||||
pay_tx_fee: f64,
|
||||
|
||||
/// The minimum relay fee for non-free transactions in ZEC/kB
|
||||
#[serde(rename = "relayfee")]
|
||||
relay_fee: f64,
|
||||
|
||||
/// The last error or warning message, or "no errors" if there are no errors
|
||||
errors: String,
|
||||
|
||||
/// The time of the last error or warning message, or "no errors timestamp" if there are no errors
|
||||
#[serde(rename = "errorstimestamp")]
|
||||
errors_timestamp: String,
|
||||
}
|
||||
|
||||
impl Default for GetInfo {
|
||||
fn default() -> Self {
|
||||
GetInfo {
|
||||
version: 0,
|
||||
build: "some build version".to_string(),
|
||||
subversion: "some subversion".to_string(),
|
||||
protocol_version: 0,
|
||||
blocks: 0,
|
||||
connections: 0,
|
||||
proxy: None,
|
||||
difficulty: 0.0,
|
||||
testnet: false,
|
||||
pay_tx_fee: 0.0,
|
||||
relay_fee: 0.0,
|
||||
errors: "no errors".to_string(),
|
||||
errors_timestamp: "no errors timestamp".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetInfo {
|
||||
/// Constructs [`GetInfo`] from its constituent parts.
|
||||
pub fn from_parts(build: String, subversion: String) -> Self {
|
||||
Self { build, subversion }
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn from_parts(
|
||||
version: u64,
|
||||
build: String,
|
||||
subversion: String,
|
||||
protocol_version: u32,
|
||||
blocks: u32,
|
||||
connections: usize,
|
||||
proxy: Option<String>,
|
||||
difficulty: f64,
|
||||
testnet: bool,
|
||||
pay_tx_fee: f64,
|
||||
relay_fee: f64,
|
||||
errors: String,
|
||||
errors_timestamp: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
version,
|
||||
build,
|
||||
subversion,
|
||||
protocol_version,
|
||||
blocks,
|
||||
connections,
|
||||
proxy,
|
||||
difficulty,
|
||||
testnet,
|
||||
pay_tx_fee,
|
||||
relay_fee,
|
||||
errors,
|
||||
errors_timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the contents of ['GetInfo'].
|
||||
pub fn into_parts(self) -> (String, String) {
|
||||
(self.build, self.subversion)
|
||||
pub fn into_parts(
|
||||
self,
|
||||
) -> (
|
||||
u64,
|
||||
String,
|
||||
String,
|
||||
u32,
|
||||
u32,
|
||||
usize,
|
||||
Option<String>,
|
||||
f64,
|
||||
bool,
|
||||
f64,
|
||||
f64,
|
||||
String,
|
||||
String,
|
||||
) {
|
||||
(
|
||||
self.version,
|
||||
self.build,
|
||||
self.subversion,
|
||||
self.protocol_version,
|
||||
self.blocks,
|
||||
self.connections,
|
||||
self.proxy,
|
||||
self.difficulty,
|
||||
self.testnet,
|
||||
self.pay_tx_fee,
|
||||
self.relay_fee,
|
||||
self.errors,
|
||||
self.errors_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create the node version number.
|
||||
pub fn version(build_string: &str) -> Option<u64> {
|
||||
let semver_version = semver::Version::parse(build_string.strip_prefix('v')?).ok()?;
|
||||
let build_number = semver_version
|
||||
.build
|
||||
.as_str()
|
||||
.split('.')
|
||||
.next()
|
||||
.and_then(|num_str| num_str.parse::<u64>().ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
// https://github.com/zcash/zcash/blob/v6.1.0/src/clientversion.h#L55-L59
|
||||
let version_number = 1_000_000 * semver_version.major
|
||||
+ 10_000 * semver_version.minor
|
||||
+ 100 * semver_version.patch
|
||||
+ build_number;
|
||||
|
||||
Some(version_number)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ use zebra_chain::{
|
|||
};
|
||||
|
||||
use zebra_consensus::ParameterCheckpoint;
|
||||
use zebra_network::address_book_peers::MockAddressBookPeers;
|
||||
use zebra_node_services::mempool;
|
||||
use zebra_state::{BoxError, GetBlockTemplateChainInfo};
|
||||
|
||||
|
@ -968,6 +969,7 @@ fn mock_services<Tip>(
|
|||
zebra_state::ReadRequest,
|
||||
>,
|
||||
Tip,
|
||||
MockAddressBookPeers,
|
||||
>,
|
||||
tokio::task::JoinHandle<()>,
|
||||
)
|
||||
|
@ -978,7 +980,7 @@ where
|
|||
let state = MockService::build().for_prop_tests();
|
||||
|
||||
let (rpc, mempool_tx_queue) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
network,
|
||||
false,
|
||||
|
@ -986,6 +988,8 @@ where
|
|||
mempool.clone(),
|
||||
Buffer::new(state.clone(), 1),
|
||||
chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
(mempool, state, rpc, mempool_tx_queue)
|
||||
|
|
|
@ -25,6 +25,7 @@ use zebra_chain::{
|
|||
serialization::ZcashDeserializeInto,
|
||||
subtree::NoteCommitmentSubtreeData,
|
||||
};
|
||||
use zebra_network::address_book_peers::MockAddressBookPeers;
|
||||
use zebra_node_services::BoxError;
|
||||
use zebra_state::{ReadRequest, ReadResponse, MAX_ON_DISK_HEIGHT};
|
||||
use zebra_test::mock_service::MockService;
|
||||
|
@ -116,6 +117,8 @@ async fn test_z_get_treestate() {
|
|||
Buffer::new(MockService::build().for_unit_tests::<_, _, BoxError>(), 1),
|
||||
state,
|
||||
tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Request the treestate by a hash.
|
||||
|
@ -198,7 +201,7 @@ async fn test_rpc_response_data_for_network(network: &Network) {
|
|||
|
||||
// Init RPC
|
||||
let (rpc, _rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"/Zebra:RPC test/",
|
||||
network.clone(),
|
||||
false,
|
||||
|
@ -206,6 +209,8 @@ async fn test_rpc_response_data_for_network(network: &Network) {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
read_state,
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// We only want a snapshot of the `getblocksubsidy` and `getblockchaininfo` methods for the non-default Testnet (with an NU6 activation height).
|
||||
|
@ -220,7 +225,10 @@ async fn test_rpc_response_data_for_network(network: &Network) {
|
|||
}
|
||||
|
||||
// `getinfo`
|
||||
let get_info = rpc.get_info().expect("We should have a GetInfo struct");
|
||||
let get_info = rpc
|
||||
.get_info()
|
||||
.await
|
||||
.expect("We should have a GetInfo struct");
|
||||
snapshot_rpc_getinfo(get_info, &settings);
|
||||
|
||||
// `getblockchaininfo`
|
||||
|
@ -522,7 +530,7 @@ async fn test_mocked_rpc_response_data_for_network(network: &Network) {
|
|||
let mempool = MockService::build().for_unit_tests();
|
||||
|
||||
let (rpc, _) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"/Zebra:RPC test/",
|
||||
network.clone(),
|
||||
false,
|
||||
|
@ -530,6 +538,8 @@ async fn test_mocked_rpc_response_data_for_network(network: &Network) {
|
|||
mempool,
|
||||
state.clone(),
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Test the response format from `z_getsubtreesbyindex` for Sapling.
|
||||
|
@ -599,6 +609,9 @@ fn snapshot_rpc_getinfo(info: GetInfo, settings: &insta::Settings) {
|
|||
// replace with:
|
||||
"[SubVersion]"
|
||||
}),
|
||||
".errorstimestamp" => dynamic_redaction(|_value, _path| {
|
||||
"[LastErrorTimestamp]"
|
||||
}),
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
---
|
||||
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||
assertion_line: 161
|
||||
expression: info
|
||||
---
|
||||
{
|
||||
"build": "vRPC test",
|
||||
"subversion": "[SubVersion]"
|
||||
"version": 100,
|
||||
"build": "v0.0.1",
|
||||
"subversion": "[SubVersion]",
|
||||
"protocolversion": 170120,
|
||||
"blocks": 10,
|
||||
"connections": 0,
|
||||
"difficulty": 1.0,
|
||||
"testnet": false,
|
||||
"paytxfee": 0.0,
|
||||
"relayfee": 0.000001,
|
||||
"errors": "no errors",
|
||||
"errorstimestamp": "[LastErrorTimestamp]"
|
||||
}
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
---
|
||||
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||
assertion_line: 161
|
||||
expression: info
|
||||
---
|
||||
{
|
||||
"build": "vRPC test",
|
||||
"subversion": "[SubVersion]"
|
||||
"version": 100,
|
||||
"build": "v0.0.1",
|
||||
"subversion": "[SubVersion]",
|
||||
"protocolversion": 170120,
|
||||
"blocks": 10,
|
||||
"connections": 0,
|
||||
"difficulty": 1.0,
|
||||
"testnet": true,
|
||||
"paytxfee": 0.0,
|
||||
"relayfee": 0.000001,
|
||||
"errors": "no errors",
|
||||
"errorstimestamp": "[LastErrorTimestamp]"
|
||||
}
|
||||
|
|
|
@ -14,9 +14,10 @@ use zebra_chain::{
|
|||
serialization::{ZcashDeserializeInto, ZcashSerialize},
|
||||
transaction::UnminedTxId,
|
||||
};
|
||||
use zebra_network::address_book_peers::MockAddressBookPeers;
|
||||
use zebra_node_services::BoxError;
|
||||
|
||||
use zebra_state::{LatestChainTip, ReadStateService};
|
||||
use zebra_state::{GetBlockTemplateChainInfo, LatestChainTip, ReadStateService};
|
||||
use zebra_test::mock_service::MockService;
|
||||
|
||||
use super::super::*;
|
||||
|
@ -29,7 +30,7 @@ async fn rpc_getinfo() {
|
|||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"/Zebra:RPC test/",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -37,13 +38,36 @@ async fn rpc_getinfo() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(state.clone(), 1),
|
||||
NoChainTip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
let get_info = rpc.get_info().expect("We should have a GetInfo struct");
|
||||
let getinfo_future = tokio::spawn(async move { rpc.get_info().await });
|
||||
|
||||
// Make the mock service respond with
|
||||
let response_handler = state
|
||||
.expect_request(zebra_state::ReadRequest::ChainInfo)
|
||||
.await;
|
||||
response_handler.respond(zebra_state::ReadResponse::ChainInfo(
|
||||
GetBlockTemplateChainInfo {
|
||||
tip_hash: Mainnet.genesis_hash(),
|
||||
tip_height: Height::MIN,
|
||||
history_tree: Default::default(),
|
||||
expected_difficulty: Default::default(),
|
||||
cur_time: zebra_chain::serialization::DateTime32::now(),
|
||||
min_time: zebra_chain::serialization::DateTime32::now(),
|
||||
max_time: zebra_chain::serialization::DateTime32::now(),
|
||||
},
|
||||
));
|
||||
|
||||
let get_info = getinfo_future
|
||||
.await
|
||||
.expect("getinfo future should not panic")
|
||||
.expect("getinfo future should not return an error");
|
||||
|
||||
// make sure there is a `build` field in the response,
|
||||
// and that is equal to the provided string, with an added 'v' version prefix.
|
||||
assert_eq!(get_info.build, "vRPC test");
|
||||
assert_eq!(get_info.build, "v0.0.1");
|
||||
|
||||
// make sure there is a `subversion` field,
|
||||
// and that is equal to the Zebra user agent.
|
||||
|
@ -120,7 +144,7 @@ async fn rpc_getblock() {
|
|||
|
||||
// Init RPC
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -128,6 +152,8 @@ async fn rpc_getblock() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
read_state.clone(),
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Make height calls with verbosity=0 and check response
|
||||
|
@ -461,7 +487,7 @@ async fn rpc_getblock_parse_error() {
|
|||
|
||||
// Init RPC
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -469,6 +495,8 @@ async fn rpc_getblock_parse_error() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(state.clone(), 1),
|
||||
NoChainTip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Make sure we get an error if Zebra can't parse the block height.
|
||||
|
@ -504,7 +532,7 @@ async fn rpc_getblock_missing_error() {
|
|||
|
||||
// Init RPC
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -512,6 +540,8 @@ async fn rpc_getblock_missing_error() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(state.clone(), 1),
|
||||
NoChainTip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Make sure Zebra returns the correct error code `-8` for missing blocks
|
||||
|
@ -566,7 +596,7 @@ async fn rpc_getblockheader() {
|
|||
|
||||
// Init RPC
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -574,6 +604,8 @@ async fn rpc_getblockheader() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
read_state.clone(),
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Make height calls with verbose=false and check response
|
||||
|
@ -677,7 +709,7 @@ async fn rpc_getbestblockhash() {
|
|||
|
||||
// Init RPC
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -685,6 +717,8 @@ async fn rpc_getbestblockhash() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
read_state,
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Get the tip hash using RPC method `get_best_block_hash`
|
||||
|
@ -723,7 +757,7 @@ async fn rpc_getrawtransaction() {
|
|||
|
||||
// Init RPC
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -731,6 +765,8 @@ async fn rpc_getrawtransaction() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
read_state.clone(),
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// Test case where transaction is in mempool.
|
||||
|
@ -899,7 +935,7 @@ async fn rpc_getaddresstxids_invalid_arguments() {
|
|||
zebra_state::populated_state(blocks.clone(), &Mainnet).await;
|
||||
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -907,6 +943,8 @@ async fn rpc_getaddresstxids_invalid_arguments() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(read_state.clone(), 1),
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// call the method with an invalid address string
|
||||
|
@ -1048,7 +1086,7 @@ async fn rpc_getaddresstxids_response_with(
|
|||
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||
|
||||
let (rpc, rpc_tx_queue_task_handle) = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
network.clone(),
|
||||
false,
|
||||
|
@ -1056,6 +1094,8 @@ async fn rpc_getaddresstxids_response_with(
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(read_state.clone(), 1),
|
||||
latest_chain_tip.clone(),
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// call the method with valid arguments
|
||||
|
@ -1100,7 +1140,7 @@ async fn rpc_getaddressutxos_invalid_arguments() {
|
|||
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
|
||||
|
||||
let rpc = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -1108,6 +1148,8 @@ async fn rpc_getaddressutxos_invalid_arguments() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(state.clone(), 1),
|
||||
NoChainTip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// call the method with an invalid address string
|
||||
|
@ -1145,7 +1187,7 @@ async fn rpc_getaddressutxos_response() {
|
|||
zebra_state::populated_state(blocks.clone(), &Mainnet).await;
|
||||
|
||||
let rpc = RpcImpl::new(
|
||||
"RPC test",
|
||||
"0.0.1",
|
||||
"RPC test",
|
||||
Mainnet,
|
||||
false,
|
||||
|
@ -1153,6 +1195,8 @@ async fn rpc_getaddressutxos_response() {
|
|||
Buffer::new(mempool.clone(), 1),
|
||||
Buffer::new(read_state.clone(), 1),
|
||||
latest_chain_tip,
|
||||
MockAddressBookPeers::new(vec![]),
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
// call the method with a valid address
|
||||
|
|
|
@ -24,7 +24,7 @@ use zebra_node_services::mempool;
|
|||
|
||||
use crate::{
|
||||
config::Config,
|
||||
methods::{RpcImpl, RpcServer as _},
|
||||
methods::{LoggedLastEvent, RpcImpl, RpcServer as _},
|
||||
server::{
|
||||
http_request_compatibility::HttpRequestMiddlewareLayer,
|
||||
rpc_call_compatibility::FixRpcResponseMiddleware,
|
||||
|
@ -122,6 +122,7 @@ impl RpcServer {
|
|||
network: Network,
|
||||
#[cfg_attr(not(feature = "getblocktemplate-rpcs"), allow(unused_variables))]
|
||||
mined_block_sender: Option<watch::Sender<(block::Hash, block::Height)>>,
|
||||
last_event: LoggedLastEvent,
|
||||
) -> Result<(ServerTask, JoinHandle<()>), tower::BoxError>
|
||||
where
|
||||
VersionString: ToString + Clone + Send + 'static,
|
||||
|
@ -171,7 +172,7 @@ impl RpcServer {
|
|||
latest_chain_tip.clone(),
|
||||
block_verifier_router,
|
||||
sync_status,
|
||||
address_book,
|
||||
address_book.clone(),
|
||||
mined_block_sender,
|
||||
);
|
||||
|
||||
|
@ -188,6 +189,8 @@ impl RpcServer {
|
|||
mempool,
|
||||
state,
|
||||
latest_chain_tip,
|
||||
address_book,
|
||||
last_event,
|
||||
);
|
||||
|
||||
let http_middleware_layer = if config.enable_cookie_auth {
|
||||
|
|
|
@ -57,6 +57,7 @@ async fn rpc_server_spawn() {
|
|||
NoChainTip,
|
||||
Mainnet,
|
||||
None,
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
);
|
||||
|
||||
info!("spawned RPC server, checking services...");
|
||||
|
@ -117,6 +118,7 @@ async fn rpc_spawn_unallocated_port(do_shutdown: bool) {
|
|||
NoChainTip,
|
||||
Mainnet,
|
||||
None,
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
)
|
||||
.await
|
||||
.expect("");
|
||||
|
@ -173,6 +175,7 @@ async fn rpc_server_spawn_port_conflict() {
|
|||
NoChainTip,
|
||||
Mainnet,
|
||||
None,
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -193,6 +196,7 @@ async fn rpc_server_spawn_port_conflict() {
|
|||
NoChainTip,
|
||||
Mainnet,
|
||||
None,
|
||||
crate::methods::LoggedLastEvent::new(None.into()),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
|
|
@ -36,6 +36,11 @@ fn fatal_error(app_name: String, err: &dyn std::error::Error) -> ! {
|
|||
/// Application state
|
||||
pub static APPLICATION: AppCell<ZebradApp> = AppCell::new();
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// The last log event that occurred in the application.
|
||||
pub static ref LAST_LOG_EVENT: Arc<std::sync::Mutex<Option<(String, tracing::Level, chrono::DateTime<chrono::Utc>)>>> = Arc::new(std::sync::Mutex::new(None));
|
||||
}
|
||||
|
||||
/// Returns the `zebrad` version for this build, in SemVer 2.0 format.
|
||||
///
|
||||
/// Includes `git describe` build metatata if available:
|
||||
|
|
|
@ -90,7 +90,7 @@ use zebra_rpc::server::RpcServer;
|
|||
use zebra_rpc::methods::get_block_template_rpcs::types::submit_block::SubmitBlockChannel;
|
||||
|
||||
use crate::{
|
||||
application::{build_version, user_agent},
|
||||
application::{build_version, user_agent, LAST_LOG_EVENT},
|
||||
components::{
|
||||
inbound::{self, InboundSetupData, MAX_INBOUND_RESPONSE_TIME},
|
||||
mempool::{self, Mempool},
|
||||
|
@ -273,6 +273,7 @@ impl StartCmd {
|
|||
Some(submit_block_channel.sender()),
|
||||
#[cfg(not(feature = "getblocktemplate-rpcs"))]
|
||||
None,
|
||||
LAST_LOG_EVENT.clone(),
|
||||
);
|
||||
rpc_task_handle.await.unwrap()
|
||||
} else {
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use abscissa_core::{Component, FrameworkError, Shutdown};
|
||||
|
||||
use tracing::{field::Visit, Level};
|
||||
use tracing_appender::non_blocking::{NonBlocking, NonBlockingBuilder, WorkerGuard};
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::{
|
||||
|
@ -13,7 +16,7 @@ use tracing_subscriber::{
|
|||
layer::SubscriberExt,
|
||||
reload::Handle,
|
||||
util::SubscriberInitExt,
|
||||
EnvFilter,
|
||||
EnvFilter, Layer,
|
||||
};
|
||||
use zebra_chain::parameters::Network;
|
||||
|
||||
|
@ -184,7 +187,13 @@ impl Tracing {
|
|||
#[cfg(not(feature = "filter-reload"))]
|
||||
let filter_handle = None;
|
||||
|
||||
let subscriber = logger.finish().with(ErrorLayer::default());
|
||||
let warn_error_layer = LastWarnErrorLayer {
|
||||
last_event: crate::application::LAST_LOG_EVENT.clone(),
|
||||
};
|
||||
let subscriber = logger
|
||||
.finish()
|
||||
.with(warn_error_layer)
|
||||
.with(ErrorLayer::default());
|
||||
|
||||
(subscriber, filter_handle)
|
||||
};
|
||||
|
@ -426,3 +435,46 @@ impl Drop for Tracing {
|
|||
howudoin::disable();
|
||||
}
|
||||
}
|
||||
|
||||
// Visitor to extract only the "message" field from a log event.
|
||||
struct MessageVisitor {
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
impl Visit for MessageVisitor {
|
||||
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
|
||||
if field.name() == "message" {
|
||||
self.message = Some(format!("{:?}", value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layer to store the last WARN or ERROR log event.
|
||||
#[derive(Debug, Clone)]
|
||||
struct LastWarnErrorLayer {
|
||||
last_event: Arc<Mutex<Option<(String, Level, chrono::DateTime<chrono::Utc>)>>>,
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for LastWarnErrorLayer
|
||||
where
|
||||
S: tracing::Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
|
||||
{
|
||||
fn on_event(
|
||||
&self,
|
||||
event: &tracing::Event<'_>,
|
||||
_ctx: tracing_subscriber::layer::Context<'_, S>,
|
||||
) {
|
||||
let level = *event.metadata().level();
|
||||
let timestamp = chrono::Utc::now();
|
||||
|
||||
if level == Level::WARN || level == Level::ERROR {
|
||||
let mut visitor = MessageVisitor { message: None };
|
||||
event.record(&mut visitor);
|
||||
|
||||
if let Some(message) = visitor.message {
|
||||
let mut last_event = self.last_event.lock().expect("lock poisoned");
|
||||
*last_event = Some((message, level, timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1600,6 +1600,9 @@ async fn rpc_endpoint(parallel_cpu_threads: bool) -> Result<()> {
|
|||
// Create an http client
|
||||
let client = RpcRequestClient::new(rpc_address);
|
||||
|
||||
// Run `zebrad` for a few seconds before testing the endpoint
|
||||
std::thread::sleep(LAUNCH_DELAY);
|
||||
|
||||
// Make the call to the `getinfo` RPC method
|
||||
let res = client.call("getinfo", "[]".to_string()).await?;
|
||||
|
||||
|
|
Loading…
Reference in New Issue