Skip to content

Commit

Permalink
Merge pull request #4128 from EthanYuan/fast-forward-epochs
Browse files Browse the repository at this point in the history
feat(rpc): Introduce the new method `generate_epochs` in the `IntegrationTest` module
  • Loading branch information
doitian authored Sep 9, 2023
2 parents 6aeb1ee + 8a0ed56 commit 2c7fce6
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 11 deletions.
59 changes: 59 additions & 0 deletions rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ The crate `ckb-rpc`'s minimum supported rustc version is 1.71.1.
* [Method `process_block_without_verify`](#method-process_block_without_verify)
* [Method `truncate`](#method-truncate)
* [Method `generate_block`](#method-generate_block)
* [Method `generate_epochs`](#method-generate_epochs)
* [Method `notify_transaction`](#method-notify_transaction)
* [Method `generate_block_with_template`](#method-generate_block_with_template)
* [Method `calculate_dao_field`](#method-calculate_dao_field)
Expand Down Expand Up @@ -3214,6 +3215,64 @@ Response
```


#### Method `generate_epochs`
* `generate_epochs(num_epochs)`
* `num_epochs`: [`EpochNumberWithFraction`](#type-epochnumberwithfraction)
* result: [`EpochNumberWithFraction`](#type-epochnumberwithfraction)

Generate epochs during development, can be useful for scenarios like testing DAO-related functionalities.

Returns the updated epoch number after generating the specified number of epochs.

###### Params

* `num_epochs` - The number of epochs to generate.

###### Examples

Request

Generating 2 epochs:


```
{
"id": 42,
"jsonrpc": "2.0",
"method": "generate_epochs",
"params": ["0x2"]
}
```


The input parameter “0x2” will be normalized to “0x10000000002”(the correct [`EpochNumberWithFraction`](#type-epochnumberwithfraction) type) within the method. Therefore, if you want to generate epochs as integers, you can simply pass an integer as long as it does not exceed 16777215 (24 bits).

Generating 1/2 epoch:


```
{
"id": 42,
"jsonrpc": "2.0",
"method": "generate_epochs",
"params": ["0x20001000000"]
}
```


Response


```
{
"id": 42,
"jsonrpc": "2.0",
"result": "0xa0001000003",
"error": null
}
```


#### Method `notify_transaction`
* `notify_transaction(transaction)`
* `transaction`: [`Transaction`](#type-transaction)
Expand Down
85 changes: 84 additions & 1 deletion rpc/src/module/test.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::error::RPCError;
use ckb_chain::chain::ChainController;
use ckb_dao::DaoCalculator;
use ckb_jsonrpc_types::{Block, BlockTemplate, Byte32, Transaction};
use ckb_jsonrpc_types::{Block, BlockTemplate, Byte32, EpochNumberWithFraction, Transaction};
use ckb_logger::error;
use ckb_network::{NetworkController, SupportProtocols};
use ckb_shared::{shared::Shared, Snapshot};
Expand Down Expand Up @@ -170,6 +170,62 @@ pub trait IntegrationTestRpc {
#[rpc(name = "generate_block")]
fn generate_block(&self) -> Result<H256>;

/// Generate epochs during development, can be useful for scenarios
/// like testing DAO-related functionalities.
///
/// Returns the updated epoch number after generating the specified number of epochs.
///
/// ## Params
///
/// * `num_epochs` - The number of epochs to generate.
///
/// ## Examples
///
/// Request
///
/// Generating 2 epochs:
///
/// ```json
/// {
/// "id": 42,
/// "jsonrpc": "2.0",
/// "method": "generate_epochs",
/// "params": ["0x2"]
/// }
/// ```
///
/// The input parameter "0x2" will be normalized to "0x10000000002"(the correct
/// [`EpochNumberWithFraction`](#type-epochnumberwithfraction) type) within the method.
/// Therefore, if you want to generate epochs as integers, you can simply pass an integer
/// as long as it does not exceed 16777215 (24 bits).
///
/// Generating 1/2 epoch:
///
/// ```text
/// {
/// "id": 42,
/// "jsonrpc": "2.0",
/// "method": "generate_epochs",
/// "params": ["0x20001000000"]
/// }
/// ```
///
/// Response
///
/// ```json
/// {
/// "id": 42,
/// "jsonrpc": "2.0",
/// "result": "0xa0001000003",
/// "error": null
/// }
/// ```
#[rpc(name = "generate_epochs")]
fn generate_epochs(
&self,
num_epochs: EpochNumberWithFraction,
) -> Result<EpochNumberWithFraction>;

/// Add transaction to tx-pool.
///
/// ## Params
Expand Down Expand Up @@ -522,6 +578,33 @@ impl IntegrationTestRpc for IntegrationTestRpcImpl {
self.process_and_announce_block(block_template.into())
}

fn generate_epochs(
&self,
num_epochs: EpochNumberWithFraction,
) -> Result<EpochNumberWithFraction> {
let tip_block_number = self.shared.snapshot().tip_header().number();
let mut current_epoch = self
.shared
.snapshot()
.epoch_ext()
.number_with_fraction(tip_block_number);
let target_epoch = current_epoch.to_rational()
+ core::EpochNumberWithFraction::from_full_value(num_epochs.into()).to_rational();

let tx_pool = self.shared.tx_pool_controller();
while current_epoch.to_rational() < target_epoch {
let block_template = tx_pool
.get_block_template(None, None, None)
.map_err(|err| RPCError::custom(RPCError::Invalid, err.to_string()))?
.map_err(|err| RPCError::custom(RPCError::CKBInternalError, err.to_string()))?;
current_epoch =
core::EpochNumberWithFraction::from_full_value(block_template.epoch.into());
self.process_and_announce_block(block_template.into())?;
}

Ok(current_epoch.full_value().into())
}

fn notify_transaction(&self, tx: Transaction) -> Result<H256> {
let tx: packed::Transaction = tx.into();
let tx: core::TransactionView = tx.into_view();
Expand Down
1 change: 1 addition & 0 deletions rpc/src/tests/examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ fn before_rpc_example(suite: &RpcTestSuite, example: &mut RpcTestExample) -> boo
);
}
("generate_block", 42) => return false,
("generate_epochs", 42) => return false,
("get_fee_rate_statics", 42) => return false,
("get_fee_rate_statistics", 42) => return false,
("generate_block_with_template", 42) => return false,
Expand Down
9 changes: 4 additions & 5 deletions rpc/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use crate::{RpcServer, ServiceBuilder};
use ckb_app_config::{BlockAssemblerConfig, NetworkConfig, RpcConfig, RpcModule};
use ckb_chain::chain::{ChainController, ChainService};
use ckb_chain_spec::consensus::Consensus;
use ckb_dao::DaoCalculator;
use ckb_jsonrpc_types::ScriptHashType;
use ckb_launcher::SharedBuilder;
use ckb_network::{Flags, NetworkService, NetworkState};
use ckb_reward_calculator::RewardCalculator;
use ckb_shared::{Shared, Snapshot};
use ckb_store::ChainStore;
use ckb_test_chain_utils::{
always_success_cell, always_success_cellbase, always_success_consensus,
};
use ckb_test_chain_utils::{always_success_cell, always_success_cellbase};
use ckb_types::{
core::{
cell::resolve_transaction, BlockBuilder, BlockView, HeaderView, TransactionBuilder,
Expand Down Expand Up @@ -206,9 +205,9 @@ fn always_success_transaction() -> TransactionView {

// setup a chain with 20 blocks and enable `Chain`, `Miner` and `Pool` rpc modules for unit test
// there is a similar fn `setup_rpc_test_suite` which enables all rpc modules, may be refactored into one fn with different paramsters in other PRs
fn setup() -> RpcTestSuite {
fn setup(consensus: Consensus) -> RpcTestSuite {
let (shared, mut pack) = SharedBuilder::with_temp_db()
.consensus(always_success_consensus())
.consensus(consensus)
.block_assembler_config(Some(BlockAssemblerConfig {
code_hash: h256!("0x0"),
args: Default::default(),
Expand Down
4 changes: 2 additions & 2 deletions rpc/src/tests/module/miner.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::tests::{always_success_transaction, setup, RpcTestRequest};
use ckb_store::ChainStore;
use ckb_test_chain_utils::always_success_cell;
use ckb_test_chain_utils::{always_success_cell, always_success_consensus};
use ckb_types::{
core::{capacity_bytes, Capacity, TransactionBuilder},
packed::{CellDep, CellInput, CellOutputBuilder, OutPoint},
Expand All @@ -12,7 +12,7 @@ use std::{sync::Arc, thread::sleep, time::Duration};
#[test]
#[ignore]
fn test_get_block_template_cache() {
let suite = setup();
let suite = setup(always_success_consensus());
// block template cache will expire when new uncle block is added to the chain
{
let response_old = suite.rpc(&RpcTestRequest {
Expand Down
1 change: 1 addition & 0 deletions rpc/src/tests/module/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod miner;
mod pool;
mod test;
5 changes: 2 additions & 3 deletions rpc/src/tests/module/pool.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::{thread::sleep, time::Duration};

use ckb_store::ChainStore;
use ckb_test_chain_utils::always_success_cell;
use ckb_test_chain_utils::ckb_testnet_consensus;
use ckb_test_chain_utils::{always_success_cell, always_success_consensus, ckb_testnet_consensus};
use ckb_types::{
core::{self, Capacity, TransactionBuilder},
packed::{self, CellDep, CellInput, CellOutputBuilder, OutPoint},
Expand Down Expand Up @@ -139,7 +138,7 @@ fn test_default_outputs_validator() {
#[test]
#[ignore]
fn test_send_transaction_exceeded_maximum_ancestors_count() {
let suite = setup();
let suite = setup(always_success_consensus());

let store = suite.shared.store();
let tip = store.get_tip_header().unwrap();
Expand Down
115 changes: 115 additions & 0 deletions rpc/src/tests/module/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#![allow(clippy::inconsistent_digit_grouping)]

use ckb_chain_spec::consensus::build_genesis_epoch_ext;
use ckb_store::ChainStore;
use ckb_test_chain_utils::always_success_consensus;
use ckb_types::{
core::{Capacity, EpochNumberWithFraction},
utilities::DIFF_TWO,
};

use crate::tests::{setup, RpcTestRequest, RpcTestSuite};

const GENESIS_EPOCH_LENGTH: u64 = 30;

#[test]
fn test_generate_epochs() {
let suite = setup_rpc();
assert_eq!(
get_current_epoch(&suite),
EpochNumberWithFraction::new(0, 20, GENESIS_EPOCH_LENGTH)
);

// generate 1 epoch
suite.rpc(&RpcTestRequest {
id: 42,
jsonrpc: "2.0".to_string(),
method: "generate_epochs".to_string(),
params: vec!["0x1".into()],
});
assert_eq!(
get_current_epoch(&suite),
EpochNumberWithFraction::new(1, 20, GENESIS_EPOCH_LENGTH)
);

// generate 1(0/1) epoch
suite.rpc(&RpcTestRequest {
id: 42,
jsonrpc: "2.0".to_string(),
method: "generate_epochs".to_string(),
params: vec!["0x10000000001".into()],
});
assert_eq!(
"0x10000000001".to_string(),
Into::<ckb_jsonrpc_types::Uint64>::into(EpochNumberWithFraction::new(1, 0, 1)).to_string(),
);
assert_eq!(
get_current_epoch(&suite),
EpochNumberWithFraction::new(2, 20, GENESIS_EPOCH_LENGTH)
);

// generate 1/2 epoch
suite.rpc(&RpcTestRequest {
id: 42,
jsonrpc: "2.0".to_string(),
method: "generate_epochs".to_string(),
params: vec!["0x20001000000".into()],
});
assert_eq!(
get_current_epoch(&suite),
EpochNumberWithFraction::new(3, 5, GENESIS_EPOCH_LENGTH)
);

// generate 3/2 epoch
suite.rpc(&RpcTestRequest {
id: 42,
jsonrpc: "2.0".to_string(),
method: "generate_epochs".to_string(),
params: vec!["0x20003000000".into()],
});
assert_eq!(
get_current_epoch(&suite),
EpochNumberWithFraction::new(4, 20, GENESIS_EPOCH_LENGTH)
);

// generate 0/2 epoch
suite.rpc(&RpcTestRequest {
id: 42,
jsonrpc: "2.0".to_string(),
method: "generate_epochs".to_string(),
params: vec!["0x20000000000".into()],
});
assert_eq!(
get_current_epoch(&suite),
EpochNumberWithFraction::new(4, 20, GENESIS_EPOCH_LENGTH)
);
}

// setup a chain for integration test rpc
fn setup_rpc() -> RpcTestSuite {
const INITIAL_PRIMARY_EPOCH_REWARD: Capacity = Capacity::shannons(1_917_808_21917808);
const DEFAULT_EPOCH_DURATION_TARGET: u64 = 240;
const DEFAULT_ORPHAN_RATE_TARGET: (u32, u32) = (1, 40);
let epoch_ext = build_genesis_epoch_ext(
INITIAL_PRIMARY_EPOCH_REWARD,
DIFF_TWO,
GENESIS_EPOCH_LENGTH,
DEFAULT_EPOCH_DURATION_TARGET,
DEFAULT_ORPHAN_RATE_TARGET,
);
let mut consensus = always_success_consensus();
consensus.genesis_epoch_ext = epoch_ext;
consensus.epoch_duration_target = 240;
consensus.permanent_difficulty_in_dummy = true;

setup(consensus)
}

fn get_current_epoch(suite: &RpcTestSuite) -> EpochNumberWithFraction {
let store = suite.shared.store();
let tip_block_number = store.get_tip_header().unwrap().number();
store
.get_current_epoch_ext()
.unwrap()
.number_with_fraction(tip_block_number)
}

0 comments on commit 2c7fce6

Please sign in to comment.