Skip to content

Commit

Permalink
Forward/Revert blocks to the specified height (#68)
Browse files Browse the repository at this point in the history
* forward blocks to the specified height

* revert blocks

* cargo fmt

* fix ci

* updated README

* fix typos

---------

Co-authored-by: PierreOssun <pierre.giraud@mailfence.com>
  • Loading branch information
shunsukew and PierreOssun committed Jun 26, 2023
1 parent 38def84 commit a0da157
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 22 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/checks-and-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
118 changes: 104 additions & 14 deletions client/consensus/manual-seal/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
use crate::error::Error;
use futures::{
channel::{mpsc, oneshot},
prelude::*,
stream::StreamExt,
SinkExt,
};
use jsonrpsee::{
Expand All @@ -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<T> = Option<oneshot::Sender<std::result::Result<T, Error>>>;
Expand Down Expand Up @@ -48,28 +55,45 @@ pub enum EngineCommand<Hash> {

/// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc.
#[rpc(client, server)]
pub trait ManualSealApi<Hash> {
pub trait ManualSealApi<Block>
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<Hash>,
) -> RpcResult<CreatedBlock<Hash>>;
parent_hash: Option<Block::Hash>,
) -> RpcResult<CreatedBlock<Block::Hash>>;

/// 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<EncodedJustification>,
) -> RpcResult<bool>;

#[method(name = "engine_forwardBlocksTo")]
async fn forward_blocks_to(
&self,
height: <<Block as BlockT>::Header as Header>::Number,
) -> RpcResult<()>;

#[method(name = "engine_revertBlocksTo")]
async fn revert_blocks_to(
&self,
height: <<Block as BlockT>::Header as Header>::Number,
) -> RpcResult<()>;
}

/// A struct that implements the [`ManualSealApiServer`].
pub struct ManualSeal<Hash> {
import_block_channel: mpsc::Sender<EngineCommand<Hash>>,
pub struct ManualSeal<Block: BlockT, Client, Backend> {
client: Arc<Client>,
backend: Arc<Backend>,
import_block_channel: mpsc::Sender<EngineCommand<Block::Hash>>,
}

/// return type of `engine_createBlock`
Expand All @@ -81,21 +105,32 @@ pub struct CreatedBlock<Hash> {
pub aux: ImportedAux,
}

impl<Hash> ManualSeal<Hash> {
impl<Block: BlockT, Client, Backend> ManualSeal<Block, Client, Backend> {
/// Create new `ManualSeal` with the given reference to the client.
pub fn new(import_block_channel: mpsc::Sender<EngineCommand<Hash>>) -> Self {
Self { import_block_channel }
pub fn new(
client: Arc<Client>,
backend: Arc<Backend>,
import_block_channel: mpsc::Sender<EngineCommand<Block::Hash>>,
) -> Self {
Self { client, backend, import_block_channel }
}
}

#[async_trait]
impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {
impl<Block, Client, Backend> ManualSealApiServer<Block> for ManualSeal<Block, Client, Backend>
where
Block: BlockT,
Client: sp_api::ProvideRuntimeApi<Block>,
Client: HeaderBackend<Block>,
Client: Send + Sync + 'static,
Backend: sc_client_api::backend::Backend<Block> + Send + Sync + 'static,
{
async fn create_block(
&self,
create_empty: bool,
finalize: bool,
parent_hash: Option<Hash>,
) -> RpcResult<CreatedBlock<Hash>> {
parent_hash: Option<Block::Hash>,
) -> RpcResult<CreatedBlock<Block::Hash>> {
let mut sink = self.import_block_channel.clone();
let (sender, receiver) = oneshot::channel();
// NOTE: this sends a Result over the channel.
Expand All @@ -117,7 +152,7 @@ impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {

async fn finalize_block(
&self,
hash: Hash,
hash: Block::Hash,
justification: Option<EncodedJustification>,
) -> RpcResult<bool> {
let mut sink = self.import_block_channel.clone();
Expand All @@ -126,6 +161,61 @@ impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {
sink.send(command).await?;
receiver.await.map(|_| true).map_err(|e| JsonRpseeError::to_call_error(e))
}

async fn forward_blocks_to(
&self,
height: <<Block as BlockT>::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::<u64>())
.into_iter()
.map(|_| EngineCommand::SealNewBlock {
create_empty: true,
finalize: false,
parent_hash: None,
sender: None,
})
.collect::<Vec<EngineCommand<Block::Hash>>>();

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: <<Block as BlockT>::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
Expand Down
14 changes: 9 additions & 5 deletions node/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ use sp_block_builder::BlockBuilder;
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};

/// Full client dependencies.
pub struct FullDeps<C, P> {
pub struct FullDeps<C, B, P> {
/// The client instance to use.
pub client: Arc<C>,
/// The backend instance to use.
pub backend: Arc<B>,
/// Transaction pool instance.
pub pool: Arc<P>,
/// Whether to deny unsafe calls
Expand All @@ -35,8 +37,8 @@ pub struct FullDeps<C, P> {
}

/// Instantiate all full RPC extensions.
pub fn create_full<C, P>(
deps: FullDeps<C, P>,
pub fn create_full<C, B, P>(
deps: FullDeps<C, B, P>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
where
C: ProvideRuntimeApi<Block>,
Expand All @@ -47,20 +49,22 @@ where
C::Api: pallet_balances_rpc::BalancesRuntimeApi<Block, AccountId, Balance>,
C::Api: BlockBuilder<Block>,
P: TransactionPool<Block = Block, Hash = <Block as BlockT>::Hash> + 'static,
B: sc_client_api::backend::Backend<Block> + 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)
}
2 changes: 2 additions & 0 deletions node/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit a0da157

Please sign in to comment.