feat(rpc): Add more fields to `getmininginfo` call (#8860)
* add additional fields to getmininginfo * update openapi spec * fix zebra-state standalone build * make sure fields are not present when tip is 0
This commit is contained in:
parent
082cdad1c1
commit
3f94303bb2
58
openapi.yaml
58
openapi.yaml
|
@ -28,7 +28,7 @@ paths:
|
||||||
default: getinfo
|
default: getinfo
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: dX2SRjFwfc
|
default: uf2E54tQkk
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -61,7 +61,7 @@ paths:
|
||||||
default: getblockchaininfo
|
default: getblockchaininfo
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: LoRrjyRM4l
|
default: Sbre3vivr8
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -99,7 +99,7 @@ paths:
|
||||||
default: getaddressbalance
|
default: getaddressbalance
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: WWIvpPiJo0
|
default: f5qarOBgzK
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -147,7 +147,7 @@ paths:
|
||||||
default: sendrawtransaction
|
default: sendrawtransaction
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: '5tVg2R9ZeI'
|
default: IlNHvAcSMS
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -196,7 +196,7 @@ paths:
|
||||||
default: getblock
|
default: getblock
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: vZ5KPOdiue
|
default: s9678BM3Lc
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -239,7 +239,7 @@ paths:
|
||||||
default: getbestblockhash
|
default: getbestblockhash
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: IifeYgN2ZK
|
default: FGQPJY8Tp8
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -272,7 +272,7 @@ paths:
|
||||||
default: getbestblockheightandhash
|
default: getbestblockheightandhash
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: tNLKsWqtNW
|
default: c2MfkL7xP9
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -305,7 +305,7 @@ paths:
|
||||||
default: getrawmempool
|
default: getrawmempool
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: IZ6todle9t
|
default: BugnNFhJpA
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -343,7 +343,7 @@ paths:
|
||||||
default: z_gettreestate
|
default: z_gettreestate
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: SSZAwyUO6t
|
default: fCUQvR1BVa
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -393,7 +393,7 @@ paths:
|
||||||
default: z_getsubtreesbyindex
|
default: z_getsubtreesbyindex
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: '3fJMQ0Hfxt'
|
default: TtPnptV6EU
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -432,7 +432,7 @@ paths:
|
||||||
default: getrawtransaction
|
default: getrawtransaction
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: RTdE1YnNxy
|
default: QqYeOGSzje
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -480,7 +480,7 @@ paths:
|
||||||
default: getaddresstxids
|
default: getaddresstxids
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: ifahwzVoYe
|
default: AsWWVyqp8x
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -528,7 +528,7 @@ paths:
|
||||||
default: getaddressutxos
|
default: getaddressutxos
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: PcPdZ7aiKy
|
default: Qscn5dUFgD
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -571,7 +571,7 @@ paths:
|
||||||
default: stop
|
default: stop
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: rWlJLGe7VJ
|
default: WuIaPXV5fO
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -604,7 +604,7 @@ paths:
|
||||||
default: getblockcount
|
default: getblockcount
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: f4p3Cb4sDu
|
default: '5F9M7Wp0oI'
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -642,7 +642,7 @@ paths:
|
||||||
default: getblockhash
|
default: getblockhash
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: '3QXvqbEWqb'
|
default: f7hdgVjctr
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -690,7 +690,7 @@ paths:
|
||||||
default: getblocktemplate
|
default: getblocktemplate
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: GXKjn81k0D
|
default: pq0uXn3YGs
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -728,7 +728,7 @@ paths:
|
||||||
default: submitblock
|
default: submitblock
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: cwGy92Mwn9
|
default: bs4v4JmVw3
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -761,7 +761,7 @@ paths:
|
||||||
default: getmininginfo
|
default: getmininginfo
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: '4ZFY9ljh5I'
|
default: pp5xV6v3pm
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -776,7 +776,7 @@ paths:
|
||||||
properties:
|
properties:
|
||||||
result:
|
result:
|
||||||
type: object
|
type: object
|
||||||
default: '{"networksolps":0,"networkhashps":0,"chain":"","testnet":false}'
|
default: '{"blocks":0,"networksolps":0,"networkhashps":0,"chain":"","testnet":false}'
|
||||||
/getnetworksolps:
|
/getnetworksolps:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
@ -794,7 +794,7 @@ paths:
|
||||||
default: getnetworksolps
|
default: getnetworksolps
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: tJlKGzARjU
|
default: '7bU98TeCV6'
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -827,7 +827,7 @@ paths:
|
||||||
default: getnetworkhashps
|
default: getnetworkhashps
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: '7pUkOt26PB'
|
default: fskOJeXqjo
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -860,7 +860,7 @@ paths:
|
||||||
default: getpeerinfo
|
default: getpeerinfo
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: JjnSrPKeyS
|
default: jPV8ufjDdt
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -898,7 +898,7 @@ paths:
|
||||||
default: validateaddress
|
default: validateaddress
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: pxZQt6VQ9U
|
default: xOyxICseV9
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -936,7 +936,7 @@ paths:
|
||||||
default: z_validateaddress
|
default: z_validateaddress
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: x2R2oRhdZE
|
default: xa6PoC4uN6
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -974,7 +974,7 @@ paths:
|
||||||
default: getblocksubsidy
|
default: getblocksubsidy
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: vkhYJS3FH8
|
default: vYEVtnVK9o
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -1017,7 +1017,7 @@ paths:
|
||||||
default: getdifficulty
|
default: getdifficulty
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: bC6q9c3xYO
|
default: tVzSTZu2sD
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -1055,7 +1055,7 @@ paths:
|
||||||
default: z_listunifiedreceivers
|
default: z_listunifiedreceivers
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: EQvPXkcJC2
|
default: le2NmJBmPt
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
@ -1093,7 +1093,7 @@ paths:
|
||||||
default: generate
|
default: generate
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
default: w41FKROii3
|
default: vVVOWxHqlN
|
||||||
params:
|
params:
|
||||||
type: array
|
type: array
|
||||||
items: {}
|
items: {}
|
||||||
|
|
|
@ -106,7 +106,7 @@ impl ChainTip for MockChainTip {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
|
fn best_tip_mined_transaction_ids(&self) -> Arc<[transaction::Hash]> {
|
||||||
unreachable!("Method not used in tests");
|
Arc::new([])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn estimate_distance_to_network_chain_tip(
|
fn estimate_distance_to_network_chain_tip(
|
||||||
|
|
|
@ -1012,9 +1012,39 @@ where
|
||||||
|
|
||||||
fn get_mining_info(&self) -> BoxFuture<Result<get_mining_info::Response>> {
|
fn get_mining_info(&self) -> BoxFuture<Result<get_mining_info::Response>> {
|
||||||
let network = self.network.clone();
|
let network = self.network.clone();
|
||||||
|
let mut state = self.state.clone();
|
||||||
|
|
||||||
|
let chain_tip = self.latest_chain_tip.clone();
|
||||||
|
let tip_height = chain_tip.best_tip_height().unwrap_or(Height(0)).0;
|
||||||
|
|
||||||
|
let mut current_block_tx = None;
|
||||||
|
if tip_height > 0 {
|
||||||
|
let mined_tx_ids = chain_tip.best_tip_mined_transaction_ids();
|
||||||
|
current_block_tx =
|
||||||
|
(!mined_tx_ids.is_empty()).then(|| mined_tx_ids.len().saturating_sub(1));
|
||||||
|
}
|
||||||
|
|
||||||
let solution_rate_fut = self.get_network_sol_ps(None, None);
|
let solution_rate_fut = self.get_network_sol_ps(None, None);
|
||||||
async move {
|
async move {
|
||||||
|
// Get the current block size.
|
||||||
|
let mut current_block_size = None;
|
||||||
|
if tip_height > 0 {
|
||||||
|
let request = zebra_state::ReadRequest::TipBlockSize;
|
||||||
|
let response: zebra_state::ReadResponse = state
|
||||||
|
.ready()
|
||||||
|
.and_then(|service| service.call(request))
|
||||||
|
.await
|
||||||
|
.map_server_error()?;
|
||||||
|
current_block_size = match response {
|
||||||
|
zebra_state::ReadResponse::TipBlockSize(Some(block_size)) => Some(block_size),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Ok(get_mining_info::Response::new(
|
Ok(get_mining_info::Response::new(
|
||||||
|
tip_height,
|
||||||
|
current_block_size,
|
||||||
|
current_block_tx,
|
||||||
network,
|
network,
|
||||||
solution_rate_fut.await?,
|
solution_rate_fut.await?,
|
||||||
))
|
))
|
||||||
|
|
|
@ -5,6 +5,18 @@ use zebra_chain::parameters::Network;
|
||||||
/// Response to a `getmininginfo` RPC request.
|
/// Response to a `getmininginfo` RPC request.
|
||||||
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
|
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
|
/// The current tip height.
|
||||||
|
#[serde(rename = "blocks")]
|
||||||
|
tip_height: u32,
|
||||||
|
|
||||||
|
/// The size of the last mined block if any.
|
||||||
|
#[serde(rename = "currentblocksize", skip_serializing_if = "Option::is_none")]
|
||||||
|
current_block_size: Option<usize>,
|
||||||
|
|
||||||
|
/// The number of transactions in the last mined block if any.
|
||||||
|
#[serde(rename = "currentblocktx", skip_serializing_if = "Option::is_none")]
|
||||||
|
current_block_tx: Option<usize>,
|
||||||
|
|
||||||
/// The estimated network solution rate in Sol/s.
|
/// The estimated network solution rate in Sol/s.
|
||||||
networksolps: u64,
|
networksolps: u64,
|
||||||
|
|
||||||
|
@ -20,8 +32,17 @@ pub struct Response {
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
/// Creates a new `getmininginfo` response
|
/// Creates a new `getmininginfo` response
|
||||||
pub fn new(network: Network, networksolps: u64) -> Self {
|
pub fn new(
|
||||||
|
tip_height: u32,
|
||||||
|
current_block_size: Option<usize>,
|
||||||
|
current_block_tx: Option<usize>,
|
||||||
|
network: Network,
|
||||||
|
networksolps: u64,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
tip_height,
|
||||||
|
current_block_size,
|
||||||
|
current_block_tx,
|
||||||
networksolps,
|
networksolps,
|
||||||
networkhashps: networksolps,
|
networkhashps: networksolps,
|
||||||
chain: network.bip70_network_name(),
|
chain: network.bip70_network_name(),
|
||||||
|
|
|
@ -3,6 +3,8 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
expression: get_mining_info
|
expression: get_mining_info
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
|
"blocks": 1687104,
|
||||||
|
"currentblocksize": 1617,
|
||||||
"networksolps": 2,
|
"networksolps": 2,
|
||||||
"networkhashps": 2,
|
"networkhashps": 2,
|
||||||
"chain": "main",
|
"chain": "main",
|
||||||
|
|
|
@ -3,6 +3,8 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||||
expression: get_mining_info
|
expression: get_mining_info
|
||||||
---
|
---
|
||||||
{
|
{
|
||||||
|
"blocks": 1842420,
|
||||||
|
"currentblocksize": 1618,
|
||||||
"networksolps": 0,
|
"networksolps": 0,
|
||||||
"networkhashps": 0,
|
"networkhashps": 0,
|
||||||
"chain": "test",
|
"chain": "test",
|
||||||
|
|
|
@ -1063,6 +1063,11 @@ pub enum ReadRequest {
|
||||||
/// Returns [`ReadResponse::ValidBlockProposal`] when successful, or an error if
|
/// Returns [`ReadResponse::ValidBlockProposal`] when successful, or an error if
|
||||||
/// the block fails contextual validation.
|
/// the block fails contextual validation.
|
||||||
CheckBlockProposalValidity(SemanticallyVerifiedBlock),
|
CheckBlockProposalValidity(SemanticallyVerifiedBlock),
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Returns [`ReadResponse::TipBlockSize(usize)`](ReadResponse::TipBlockSize)
|
||||||
|
/// with the current best chain tip block size in bytes.
|
||||||
|
TipBlockSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadRequest {
|
impl ReadRequest {
|
||||||
|
@ -1098,6 +1103,8 @@ impl ReadRequest {
|
||||||
ReadRequest::SolutionRate { .. } => "solution_rate",
|
ReadRequest::SolutionRate { .. } => "solution_rate",
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
ReadRequest::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
|
ReadRequest::CheckBlockProposalValidity(_) => "check_block_proposal_validity",
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadRequest::TipBlockSize => "tip_block_size",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,6 +229,10 @@ pub enum ReadResponse {
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
/// Response to [`ReadRequest::CheckBlockProposalValidity`]
|
/// Response to [`ReadRequest::CheckBlockProposalValidity`]
|
||||||
ValidBlockProposal,
|
ValidBlockProposal,
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Response to [`ReadRequest::TipBlockSize`]
|
||||||
|
TipBlockSize(Option<usize>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure with the information needed from the state to build a `getblocktemplate` RPC response.
|
/// A structure with the information needed from the state to build a `getblocktemplate` RPC response.
|
||||||
|
@ -315,7 +319,7 @@ impl TryFrom<ReadResponse> for Response {
|
||||||
ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal),
|
ReadResponse::ValidBlockProposal => Ok(Response::ValidBlockProposal),
|
||||||
|
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
ReadResponse::ChainInfo(_) | ReadResponse::SolutionRate(_) => {
|
ReadResponse::ChainInfo(_) | ReadResponse::SolutionRate(_) | ReadResponse::TipBlockSize(_) => {
|
||||||
Err("there is no corresponding Response for this ReadResponse")
|
Err("there is no corresponding Response for this ReadResponse")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,9 @@ use zebra_chain::{
|
||||||
subtree::NoteCommitmentSubtreeIndex,
|
subtree::NoteCommitmentSubtreeIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
use zebra_chain::{block::Height, serialization::ZcashSerialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::{
|
constants::{
|
||||||
MAX_FIND_BLOCK_HASHES_RESULTS, MAX_FIND_BLOCK_HEADERS_RESULTS_FOR_ZEBRA,
|
MAX_FIND_BLOCK_HASHES_RESULTS, MAX_FIND_BLOCK_HEADERS_RESULTS_FOR_ZEBRA,
|
||||||
|
@ -1905,6 +1908,46 @@ impl Service<ReadRequest> for ReadStateService {
|
||||||
})
|
})
|
||||||
.wait_for_panics()
|
.wait_for_panics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
ReadRequest::TipBlockSize => {
|
||||||
|
let state = self.clone();
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
span.in_scope(move || {
|
||||||
|
// Get the best chain tip height.
|
||||||
|
let tip_height = state
|
||||||
|
.non_finalized_state_receiver
|
||||||
|
.with_watch_data(|non_finalized_state| {
|
||||||
|
read::tip_height(non_finalized_state.best_chain(), &state.db)
|
||||||
|
})
|
||||||
|
.unwrap_or(Height(0));
|
||||||
|
|
||||||
|
// Get the block at the best chain tip height.
|
||||||
|
let block = state.non_finalized_state_receiver.with_watch_data(
|
||||||
|
|non_finalized_state| {
|
||||||
|
read::block(
|
||||||
|
non_finalized_state.best_chain(),
|
||||||
|
&state.db,
|
||||||
|
tip_height.into(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The work is done in the future.
|
||||||
|
timer.finish(module_path!(), line!(), "ReadRequest::TipBlockSize");
|
||||||
|
|
||||||
|
// Respond with the length of the obtained block if any.
|
||||||
|
match block {
|
||||||
|
Some(b) => Ok(ReadResponse::TipBlockSize(Some(
|
||||||
|
b.zcash_serialize_to_vec()?.len(),
|
||||||
|
))),
|
||||||
|
None => Ok(ReadResponse::TipBlockSize(None)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.wait_for_panics()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue