diff --git a/.github/workflows/checks-and-tests.yaml b/.github/workflows/checks-and-tests.yaml index 3e0f56b..a55c058 100644 --- a/.github/workflows/checks-and-tests.yaml +++ b/.github/workflows/checks-and-tests.yaml @@ -31,9 +31,7 @@ jobs: uses: actions/checkout@v3 - name: Install Protoc - uses: arduino/setup-protoc@v1 - with: - version: '3.x' + run: sudo apt -y install protobuf-compiler - name: Install & display rust toolchain run: rustup show diff --git a/README.md b/README.md index 4a9b144..d2b0a44 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Swanky node is a Substrate based blockchain configured to enable `pallet-contrac - `grandpa` & `aura` consensus were removed. Instead, [`instant-seal`/`manual-seal`](https://github.com/AstarNetwork/swanky-node#consensus-manual-seal--instant-seal) & [`delayed-finalize`](https://github.com/AstarNetwork/swanky-node#consensus-delayed-finalize) are used. Blocks are sealed (1) as soon as a transaction get in the pool (2) when `engine_createBlock` RPC called. Blocks are finalized configured delay sec after blocks are sealed. - Users' account Balance manipulation +- Block height manipulation. Developers can forward and revert blocks via RPC. - [pallet-dapps-staking](https://github.com/AstarNetwork/astar-frame/tree/polkadot-v0.9.39/frame/dapps-staking) and ChainExtension to interact with it. - [pallet-assets](https://github.com/paritytech/substrate/tree/polkadot-v0.9.39/frame/assets). - Pallet-assets chain-extension @@ -167,6 +168,44 @@ By default, either manual or instant seal does not result in block finalization In the above example, a setting of `5` seconds would result in the blocks being finalized five seconds after being sealed. In contrast, setting the value to `0` would lead to instant finalization, with the blocks being finalized immediately upon being sealed. +## Block height manipulation +Developers can forward blocks and revert blocks to requested block heights. + +### Forward blocks via RPC +Forwarding blocks to requested block height by calling `engine_forwardBlocksTo`. + +```bash +$ curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d '{ feat/forward-revert-blocks ✭ + "jsonrpc":"2.0", + "id":1, + "method":"engine_forwardBlocksTo", + "params": [120, null] + }' +``` + +#### Params +- **Height** + `height` denotes an integral value that signifies the desired block height towards which the user intends to progress. If the value is lower than current height, RPC returns an error. + +### Revert blocks via RPC +Reverting blocks to requested block height by calling `engine_revertBlocksTo`. + +Note that reverting finalized blocks only works when node is launched with archive mode `--state-pruning archive` (or `--pruning archive`) since reverting blocks requires past blocks' states. +When blocks' states are pruned, RPC won't revert finalized blocks. + +```bash +$ curl http://localhost:9933 -H "Content-Type:application/json;charset=utf-8" -d '{ feat/forward-revert-blocks ✭ + "jsonrpc":"2.0", + "id":1, + "method":"engine_revertBlocksTo", + "params": [50, null] + }' +``` + +#### Params +- **Height** + `height` denotes an integral value that represents the desired block height which the user intends to revert to. If the value is higher than current height, RPC returns an error. + ## Account Balance manipulation For local development purpose, developers can manipulate any users' account balance via RPC without requiring their accounts' signatures and transaction cost to pay. diff --git a/client/consensus/manual-seal/src/rpc.rs b/client/consensus/manual-seal/src/rpc.rs index d6b5a1d..d719179 100644 --- a/client/consensus/manual-seal/src/rpc.rs +++ b/client/consensus/manual-seal/src/rpc.rs @@ -3,6 +3,8 @@ use crate::error::Error; use futures::{ channel::{mpsc, oneshot}, + prelude::*, + stream::StreamExt, SinkExt, }; use jsonrpsee::{ @@ -11,7 +13,12 @@ use jsonrpsee::{ }; use sc_consensus::ImportedAux; use serde::{Deserialize, Serialize}; -use sp_runtime::EncodedJustification; +use sp_blockchain::HeaderBackend; +use sp_runtime::{ + traits::{Block as BlockT, Header}, + EncodedJustification, SaturatedConversion, +}; +use std::sync::Arc; /// Sender passed to the authorship task to report errors or successes. pub type Sender = Option>>; @@ -48,28 +55,45 @@ pub enum EngineCommand { /// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc. #[rpc(client, server)] -pub trait ManualSealApi { +pub trait ManualSealApi +where + Block: BlockT, +{ /// Instructs the manual-seal authorship task to create a new block #[method(name = "engine_createBlock")] async fn create_block( &self, create_empty: bool, finalize: bool, - parent_hash: Option, - ) -> RpcResult>; + parent_hash: Option, + ) -> RpcResult>; /// Instructs the manual-seal authorship task to finalize a block #[method(name = "engine_finalizeBlock")] async fn finalize_block( &self, - hash: Hash, + hash: Block::Hash, justification: Option, ) -> RpcResult; + + #[method(name = "engine_forwardBlocksTo")] + async fn forward_blocks_to( + &self, + height: <::Header as Header>::Number, + ) -> RpcResult<()>; + + #[method(name = "engine_revertBlocksTo")] + async fn revert_blocks_to( + &self, + height: <::Header as Header>::Number, + ) -> RpcResult<()>; } /// A struct that implements the [`ManualSealApiServer`]. -pub struct ManualSeal { - import_block_channel: mpsc::Sender>, +pub struct ManualSeal { + client: Arc, + backend: Arc, + import_block_channel: mpsc::Sender>, } /// return type of `engine_createBlock` @@ -81,21 +105,32 @@ pub struct CreatedBlock { pub aux: ImportedAux, } -impl ManualSeal { +impl ManualSeal { /// Create new `ManualSeal` with the given reference to the client. - pub fn new(import_block_channel: mpsc::Sender>) -> Self { - Self { import_block_channel } + pub fn new( + client: Arc, + backend: Arc, + import_block_channel: mpsc::Sender>, + ) -> Self { + Self { client, backend, import_block_channel } } } #[async_trait] -impl ManualSealApiServer for ManualSeal { +impl ManualSealApiServer for ManualSeal +where + Block: BlockT, + Client: sp_api::ProvideRuntimeApi, + Client: HeaderBackend, + Client: Send + Sync + 'static, + Backend: sc_client_api::backend::Backend + Send + Sync + 'static, +{ async fn create_block( &self, create_empty: bool, finalize: bool, - parent_hash: Option, - ) -> RpcResult> { + parent_hash: Option, + ) -> RpcResult> { let mut sink = self.import_block_channel.clone(); let (sender, receiver) = oneshot::channel(); // NOTE: this sends a Result over the channel. @@ -117,7 +152,7 @@ impl ManualSealApiServer for ManualSeal { async fn finalize_block( &self, - hash: Hash, + hash: Block::Hash, justification: Option, ) -> RpcResult { let mut sink = self.import_block_channel.clone(); @@ -126,6 +161,61 @@ impl ManualSealApiServer for ManualSeal { sink.send(command).await?; receiver.await.map(|_| true).map_err(|e| JsonRpseeError::to_call_error(e)) } + + async fn forward_blocks_to( + &self, + height: <::Header as Header>::Number, + ) -> RpcResult<()> { + let best_number = self.client.info().best_number; + if height <= best_number { + return Err(JsonRpseeError::Custom( + "Target height is lower than current best height".into(), + )) + } + + let diff = height - best_number; + let to_height = (0..diff.saturated_into::()) + .into_iter() + .map(|_| EngineCommand::SealNewBlock { + create_empty: true, + finalize: false, + parent_hash: None, + sender: None, + }) + .collect::>>(); + + let mut forward_blocks_stream = stream::iter(to_height).map(Ok); + + let mut sink = self.import_block_channel.clone(); + sink.send_all(&mut forward_blocks_stream).await?; + + Ok(()) + } + + async fn revert_blocks_to( + &self, + height: <::Header as Header>::Number, + ) -> RpcResult<()> { + let best_number = self.client.info().best_number; + if height >= best_number { + return Err(JsonRpseeError::Custom( + "Target height is higher than current best height".into(), + )) + } + + let diff = best_number - height; + + println!("Diff: {:?}", diff); + + let reverted = self + .backend + .revert(diff, true) + .map_err(|e| JsonRpseeError::Custom(format!("Backend Revert Error: {}", e)))?; + + println!("Reverted: {:?}", reverted); + + Ok(()) + } } /// report any errors or successes encountered by the authorship task back diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 219f69f..2701f9b 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -23,9 +23,11 @@ use sp_block_builder::BlockBuilder; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; /// Full client dependencies. -pub struct FullDeps { +pub struct FullDeps { /// The client instance to use. pub client: Arc, + /// The backend instance to use. + pub backend: Arc, /// Transaction pool instance. pub pool: Arc

, /// Whether to deny unsafe calls @@ -35,8 +37,8 @@ pub struct FullDeps { } /// Instantiate all full RPC extensions. -pub fn create_full( - deps: FullDeps, +pub fn create_full( + deps: FullDeps, ) -> Result, Box> where C: ProvideRuntimeApi, @@ -47,20 +49,22 @@ where C::Api: pallet_balances_rpc::BalancesRuntimeApi, C::Api: BlockBuilder, P: TransactionPool::Hash> + 'static, + B: sc_client_api::backend::Backend + Send + Sync + 'static, + P: TransactionPool + 'static, { use pallet_balances_rpc::{Balances, BalancesApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use substrate_frame_rpc_system::{System, SystemApiServer}; let mut io = RpcModule::new(()); - let FullDeps { client, pool, deny_unsafe, command_sink } = deps; + let FullDeps { client, backend, pool, deny_unsafe, command_sink } = deps; io.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge(Balances::new(client.clone(), pool.clone()).into_rpc())?; // The final RPC extension receives commands for the manual seal consensus engine. - io.merge(ManualSeal::new(command_sink).into_rpc())?; + io.merge(ManualSeal::new(client, backend, command_sink).into_rpc())?; Ok(io) } diff --git a/node/src/service.rs b/node/src/service.rs index 12e585c..4a49a32 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -169,11 +169,13 @@ pub fn new_full( let rpc_extensions_builder = { let client = client.clone(); + let backend = backend.clone(); let pool = transaction_pool.clone(); Box::new(move |deny_unsafe, _| { let deps = crate::rpc::FullDeps { client: client.clone(), + backend: backend.clone(), pool: pool.clone(), deny_unsafe, command_sink: rpc_command_sink.clone(),