Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forward/Revert blocks to the specified height #68

Merged
merged 7 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend revert method doesn't work when blocks are finalized for some reason, even though it is described that providing true as the second argument can revert finalized blocks (in unsafe way).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.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