Add dev_getBlockStats RPC (#10939)

* Add chain_getBlockStats rpc

* Fix broken doc link

* Apply suggestions from code review

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* fmt

* Fix compilation

* Move Blockstats

* Apply suggestions from code review

Co-authored-by: David <dvdplm@gmail.com>

* fmt

Co-authored-by: ascjones <ascjones@gmail.com>
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: David <dvdplm@gmail.com>
This commit is contained in:
Alexander Theißen
2022-03-22 09:23:47 +01:00
committed by GitHub
parent be11700658
commit be6b6adbb4
9 changed files with 319 additions and 3 deletions
+118
View File
@@ -0,0 +1,118 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the [`DevApi`] trait providing debug utilities for Substrate based
//! blockchains.
#[cfg(test)]
mod tests;
pub use sc_rpc_api::dev::{BlockStats, DevApi};
use sc_client_api::{BlockBackend, HeaderBackend};
use sc_rpc_api::{
dev::error::{Error, Result},
DenyUnsafe,
};
use sp_api::{ApiExt, Core, ProvideRuntimeApi};
use sp_core::Encode;
use sp_runtime::{
generic::{BlockId, DigestItem},
traits::{Block as BlockT, Header},
};
use std::{
marker::{PhantomData, Send, Sync},
sync::Arc,
};
type HasherOf<Block> = <<Block as BlockT>::Header as Header>::Hashing;
/// The Dev API. All methods are unsafe.
pub struct Dev<Block: BlockT, Client> {
client: Arc<Client>,
deny_unsafe: DenyUnsafe,
_phantom: PhantomData<Block>,
}
impl<Block: BlockT, Client> Dev<Block, Client> {
/// Create a new Dev API.
pub fn new(client: Arc<Client>, deny_unsafe: DenyUnsafe) -> Self {
Self { client, deny_unsafe, _phantom: PhantomData::default() }
}
}
impl<Block, Client> DevApi<Block::Hash> for Dev<Block, Client>
where
Block: BlockT + 'static,
Client: BlockBackend<Block>
+ HeaderBackend<Block>
+ ProvideRuntimeApi<Block>
+ Send
+ Sync
+ 'static,
Client::Api: Core<Block>,
{
fn block_stats(&self, hash: Block::Hash) -> Result<Option<BlockStats>> {
self.deny_unsafe.check_if_safe()?;
let block = {
let block = self
.client
.block(&BlockId::Hash(hash))
.map_err(|e| Error::BlockQueryError(Box::new(e)))?;
if let Some(block) = block {
let (mut header, body) = block.block.deconstruct();
// Remove the `Seal` to ensure we have the number of digests as expected by the
// runtime.
header.digest_mut().logs.retain(|item| !matches!(item, DigestItem::Seal(_, _)));
Block::new(header, body)
} else {
return Ok(None)
}
};
let parent_header = {
let parent_hash = *block.header().parent_hash();
let parent_header = self
.client
.header(BlockId::Hash(parent_hash))
.map_err(|e| Error::BlockQueryError(Box::new(e)))?;
if let Some(header) = parent_header {
header
} else {
return Ok(None)
}
};
let block_len = block.encoded_size() as u64;
let num_extrinsics = block.extrinsics().len() as u64;
let pre_root = *parent_header.state_root();
let mut runtime_api = self.client.runtime_api();
runtime_api.record_proof();
runtime_api
.execute_block(&BlockId::Hash(parent_header.hash()), block)
.map_err(|_| Error::BlockExecutionFailed)?;
let witness = runtime_api
.extract_proof()
.expect("We enabled proof recording. A proof must be available; qed");
let witness_len = witness.encoded_size() as u64;
let witness_compact_len = witness
.into_compact_proof::<HasherOf<Block>>(pre_root)
.map_err(|_| Error::WitnessCompactionFailed)?
.encoded_size() as u64;
Ok(Some(BlockStats { witness_len, witness_compact_len, block_len, num_extrinsics }))
}
}
+58
View File
@@ -0,0 +1,58 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use assert_matches::assert_matches;
use futures::executor;
use sc_block_builder::BlockBuilderProvider;
use sp_blockchain::HeaderBackend;
use sp_consensus::BlockOrigin;
use substrate_test_runtime_client::{prelude::*, runtime::Block};
#[test]
fn block_stats_work() {
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = <Dev<Block, _>>::new(client.clone(), DenyUnsafe::No);
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
// Can't gather stats for a block without a parent.
assert_eq!(api.block_stats(client.genesis_hash()).unwrap(), None);
assert_eq!(
api.block_stats(client.info().best_hash).unwrap(),
Some(BlockStats {
witness_len: 597,
witness_compact_len: 500,
block_len: 99,
num_extrinsics: 0,
}),
);
}
#[test]
fn deny_unsafe_works() {
let mut client = Arc::new(substrate_test_runtime_client::new());
let api = <Dev<Block, _>>::new(client.clone(), DenyUnsafe::Yes);
let block = client.new_block(Default::default()).unwrap().build().unwrap().block;
executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
assert_matches!(api.block_stats(client.info().best_hash), Err(Error::UnsafeRpcCalled(_)));
}
+1
View File
@@ -34,6 +34,7 @@ pub use sc_rpc_api::{DenyUnsafe, Metadata};
pub mod author;
pub mod chain;
pub mod dev;
pub mod offchain;
pub mod state;
pub mod system;