diff --git a/Cargo.lock b/Cargo.lock index 9c91dd4f8..59cc1dd4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1976,6 +1976,7 @@ dependencies = [ "async-jsonrpc-client", "async-native-tls", "async-std", + "async-trait", "ckb-crypto", "ckb-fixed-hash", "ckb-types", @@ -2120,6 +2121,7 @@ dependencies = [ "async-jsonrpc-client", "async-native-tls", "async-std", + "async-trait", "bytes 1.0.1", "ckb-crypto", "ckb-fixed-hash", diff --git a/crates/block-producer/Cargo.toml b/crates/block-producer/Cargo.toml index f12fb3206..a193e892e 100644 --- a/crates/block-producer/Cargo.toml +++ b/crates/block-producer/Cargo.toml @@ -45,3 +45,4 @@ smol = "1.2.5" lazy_static = "1.4" sqlx = { version = "0.5", features = [ "runtime-async-std-native-tls", "postgres", "sqlite", "chrono", "decimal" ] } hex = "0.4" +async-trait = "0.1" diff --git a/crates/block-producer/src/lib.rs b/crates/block-producer/src/lib.rs index f2f163206..956854f19 100644 --- a/crates/block-producer/src/lib.rs +++ b/crates/block-producer/src/lib.rs @@ -6,6 +6,7 @@ pub mod produce_block; pub mod rpc_client; pub mod runner; pub mod stake; +pub mod test_mode_control; pub mod transaction_skeleton; pub mod types; pub mod utils; diff --git a/crates/block-producer/src/runner.rs b/crates/block-producer/src/runner.rs index c96a4e1fc..4704db43b 100644 --- a/crates/block-producer/src/runner.rs +++ b/crates/block-producer/src/runner.rs @@ -1,13 +1,13 @@ use crate::{ - block_producer::BlockProducer, poller::ChainUpdater, rpc_client::RPCClient, types::ChainEvent, - utils::CKBGenesisInfo, + block_producer::BlockProducer, poller::ChainUpdater, rpc_client::RPCClient, + test_mode_control::TestModeControl, types::ChainEvent, utils::CKBGenesisInfo, }; use anyhow::{anyhow, Context, Result}; use async_jsonrpc_client::HttpClient; use futures::{executor::block_on, select, FutureExt}; use gw_chain::chain::Chain; use gw_common::H256; -use gw_config::Config; +use gw_config::{Config, TestMode}; use gw_db::{config::Config as DBConfig, schema::COLUMNS, RocksDB}; use gw_generator::{ account_lock_manage::{secp256k1::Secp256k1Eth, AccountLockManage}, @@ -15,6 +15,7 @@ use gw_generator::{ genesis::init_genesis, Generator, RollupContext, }; +use gw_jsonrpc_types::test_mode::TestModePayload; use gw_mem_pool::pool::MemPool; use gw_rpc_server::{registry::Registry, server::start_jsonrpc_server}; use gw_store::Store; @@ -40,6 +41,7 @@ async fn poll_loop( rpc_client: RPCClient, chain_updater: ChainUpdater, block_producer: BlockProducer, + test_mode_control: TestModeControl, poll_interval: Duration, ) -> Result<()> { struct Inner { @@ -99,13 +101,21 @@ async fn poll_loop( err ); } - if let Err(err) = inner.block_producer.handle_event(event.clone()).await { - log::error!( - "Error occured when polling block_producer, event: {:?}, error: {}", - event, - err - ); + + // TODO: implement test mode challenge control + if TestMode::Disable == test_mode_control.mode() + || TestMode::Enable == test_mode_control.mode() + && Some(TestModePayload::None) == test_mode_control.take_payload().await + { + if let Err(err) = inner.block_producer.handle_event(event.clone()).await { + log::error!( + "Error occured when polling block_producer, event: {:?}, error: {}", + event, + err + ); + } } + // } // }) // .detach(); @@ -215,7 +225,15 @@ pub fn run(config: Config) -> Result<()> { )); // RPC registry - let rpc_registry = Registry::new(store.clone(), mem_pool.clone(), generator.clone()); + let test_mode_control = + TestModeControl::create(config.test_mode, rpc_client.clone(), &block_producer_config)?; + let rpc_registry = Registry::new( + store.clone(), + mem_pool.clone(), + generator.clone(), + config.test_mode, + test_mode_control.clone(), + ); // create web3 indexer let web3_indexer = match config.web3_indexer { @@ -301,10 +319,14 @@ pub fn run(config: Config) -> Result<()> { log::info!("Rollup config hash: {}", rollup_config_hash); } + if TestMode::Enable == config.test_mode { + log::info!("Test mode enabled!!!"); + } + smol::block_on(async { select! { _ = ctrl_c.recv().fuse() => log::info!("Exiting..."), - e = poll_loop(rpc_client, chain_updater, block_producer, Duration::from_secs(3)).fuse() => { + e = poll_loop(rpc_client, chain_updater, block_producer, test_mode_control ,Duration::from_secs(3)).fuse() => { log::error!("Error in main poll loop: {:?}", e); } e = start_jsonrpc_server(rpc_address, rpc_registry).fuse() => { diff --git a/crates/block-producer/src/test_mode_control.rs b/crates/block-producer/src/test_mode_control.rs new file mode 100644 index 000000000..0d7e21375 --- /dev/null +++ b/crates/block-producer/src/test_mode_control.rs @@ -0,0 +1,116 @@ +use crate::poa::{PoA, ShouldIssueBlock}; +use crate::rpc_client::RPCClient; +use crate::types::InputCellInfo; +use crate::wallet::Wallet; + +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use ckb_types::prelude::{Builder, Entity}; +use gw_common::H256; +use gw_config::{BlockProducerConfig, TestMode}; +use gw_jsonrpc_types::{ + godwoken::GlobalState, + test_mode::{ShouldProduceBlock, TestModePayload}, +}; +use gw_rpc_server::registry::TestModeRPC; +use gw_types::{packed::CellInput, prelude::Unpack}; +use smol::lock::Mutex; + +use std::sync::Arc; + +#[derive(Clone)] +pub struct TestModeControl { + mode: TestMode, + payload: Arc>>, + rpc_client: RPCClient, + poa: Arc>, +} + +impl TestModeControl { + pub fn create( + mode: TestMode, + rpc_client: RPCClient, + config: &BlockProducerConfig, + ) -> Result { + let wallet = Wallet::from_config(&config.wallet_config).with_context(|| "init wallet")?; + let poa = PoA::new( + rpc_client.clone(), + wallet.lock_script().to_owned(), + config.poa_lock_dep.clone().into(), + config.poa_state_dep.clone().into(), + ); + + Ok(TestModeControl { + mode, + payload: Arc::new(Mutex::new(None)), + rpc_client, + poa: Arc::new(Mutex::new(poa)), + }) + } + + pub fn mode(&self) -> TestMode { + self.mode + } + + pub async fn get_payload(&self) -> Option { + self.payload.lock().await.to_owned() + } + + pub async fn take_payload(&self) -> Option { + self.payload.lock().await.take() + } +} + +#[async_trait] +impl TestModeRPC for TestModeControl { + async fn get_global_state(&self) -> Result { + let rollup_cell = { + let opt = self.rpc_client.query_rollup_cell().await?; + opt.ok_or_else(|| anyhow!("rollup cell not found"))? + }; + + let global_state = gw_types::packed::GlobalState::from_slice(&rollup_cell.data) + .map_err(|_| anyhow!("parse rollup up global state"))?; + + Ok(global_state.into()) + } + + async fn produce_block(&self, payload: TestModePayload) -> Result<()> { + *self.payload.lock().await = Some(payload); + + Ok(()) + } + + async fn should_produce_block(&self) -> Result { + let rollup_cell = { + let opt = self.rpc_client.query_rollup_cell().await?; + opt.ok_or_else(|| anyhow!("rollup cell not found"))? + }; + + let tip_hash: H256 = { + let l1_tip_hash_number = self.rpc_client.get_tip().await?; + let tip_hash: [u8; 32] = l1_tip_hash_number.block_hash().unpack(); + tip_hash.into() + }; + + let ret = { + let median_time = self.rpc_client.get_block_median_time(tip_hash).await?; + let poa_cell_input = InputCellInfo { + input: CellInput::new_builder() + .previous_output(rollup_cell.out_point.clone()) + .build(), + cell: rollup_cell.clone(), + }; + + let mut poa = self.poa.lock().await; + poa.should_issue_next_block(median_time, &poa_cell_input) + .await? + }; + + Ok(match ret { + ShouldIssueBlock::Yes => ShouldProduceBlock::Yes, + ShouldIssueBlock::YesIfFull => ShouldProduceBlock::YesIfFull, + ShouldIssueBlock::No => ShouldProduceBlock::No, + }) + } +} diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index d6c27dd50..b99ac1009 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct Config { + pub test_mode: TestMode, pub backends: Vec, pub store: StoreConfig, pub genesis: GenesisConfig, @@ -87,3 +88,16 @@ pub struct Web3IndexerConfig { pub polyjuice_script_type_hash: H256, pub eth_account_lock_hash: H256, } + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TestMode { + Enable, + Disable, +} + +impl Default for TestMode { + fn default() -> Self { + TestMode::Disable + } +} diff --git a/crates/jsonrpc-types/src/lib.rs b/crates/jsonrpc-types/src/lib.rs index 4160fa5e0..f917ec712 100644 --- a/crates/jsonrpc-types/src/lib.rs +++ b/crates/jsonrpc-types/src/lib.rs @@ -2,3 +2,4 @@ pub mod blockchain; pub mod godwoken; // re-exports pub use ckb_jsonrpc_types; +pub mod test_mode; diff --git a/crates/jsonrpc-types/src/test_mode.rs b/crates/jsonrpc-types/src/test_mode.rs new file mode 100644 index 000000000..a92429e22 --- /dev/null +++ b/crates/jsonrpc-types/src/test_mode.rs @@ -0,0 +1,34 @@ +use ckb_jsonrpc_types::{Uint32, Uint64}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ShouldProduceBlock { + Yes, + YesIfFull, + No, +} + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ChallengeType { + TxExecution, + TxSignature, + WithdrawalSignature, +} + +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum TestModePayload { + None, + BadBlock { + target_index: Uint32, + target_type: ChallengeType, + }, + Challenge { + block_number: Uint64, + target_index: Uint32, + target_type: ChallengeType, + }, + WaitForChallengeMaturity, +} diff --git a/crates/rpc-server/Cargo.toml b/crates/rpc-server/Cargo.toml index b72ee4e15..6a512f317 100644 --- a/crates/rpc-server/Cargo.toml +++ b/crates/rpc-server/Cargo.toml @@ -39,3 +39,4 @@ serde_json = "1.0" smol = "1.2.5" tokio = { version = "1.0.1", default-features = false, features = ["rt-multi-thread"] } bytes-v10 = { version = "1.0", package = "bytes" } +async-trait = "0.1" diff --git a/crates/rpc-server/src/registry.rs b/crates/rpc-server/src/registry.rs index 1193dac33..fd196ff8b 100644 --- a/crates/rpc-server/src/registry.rs +++ b/crates/rpc-server/src/registry.rs @@ -1,11 +1,14 @@ use anyhow::Result; +use async_trait::async_trait; use ckb_types::prelude::{Builder, Entity}; use gw_common::{state::State, H256}; +use gw_config::TestMode; use gw_generator::{sudt::build_l2_sudt_script, Generator}; use gw_jsonrpc_types::{ blockchain::Script, ckb_jsonrpc_types::{JsonBytes, Uint128, Uint32}, - godwoken::{L2BlockView, RunResult, TxReceipt}, + godwoken::{GlobalState, L2BlockView, RunResult, TxReceipt}, + test_mode::{ShouldProduceBlock, TestModePayload}, }; use gw_store::{ state_db::{CheckPoint, StateDBMode, StateDBTransaction, SubState}, @@ -25,6 +28,14 @@ type RPCServer = Arc>; type MemPool = Mutex; type AccountID = Uint32; type JsonH256 = ckb_fixed_hash::H256; +type BoxedTestsRPCImpl = Box; + +#[async_trait] +pub trait TestModeRPC { + async fn get_global_state(&self) -> Result; + async fn produce_block(&self, payload: TestModePayload) -> Result<()>; + async fn should_produce_block(&self) -> Result; +} fn to_h256(v: JsonH256) -> H256 { let h: [u8; 32] = v.into(); @@ -40,14 +51,27 @@ pub struct Registry { generator: Arc, mem_pool: Arc, store: Store, + test_mode: TestMode, + tests_rpc_impl: Arc, } impl Registry { - pub fn new(store: Store, mem_pool: Arc, generator: Arc) -> Self { + pub fn new( + store: Store, + mem_pool: Arc, + generator: Arc, + test_mode: TestMode, + tests_rpc_impl: T, + ) -> Self + where + T: TestModeRPC + Send + Sync + 'static, + { Self { mem_pool, store, generator, + test_mode, + tests_rpc_impl: Arc::new(Box::new(tests_rpc_impl)), } } @@ -84,6 +108,15 @@ impl Registry { .with_method("submit_withdrawal_request", submit_withdrawal_request) .with_method("compute_l2_sudt_script_hash", compute_l2_sudt_script_hash); + // Tests + if TestMode::Enable == self.test_mode { + server = server + .with_data(Data(Arc::clone(&self.tests_rpc_impl))) + .with_method("tests_produce_block", tests_produce_block) + .with_method("tests_should_produce_block", tests_should_produce_block) + .with_method("tests_get_global_state", tests_get_global_state); + } + Ok(server.finish()) } } @@ -384,3 +417,20 @@ async fn compute_l2_sudt_script_hash( build_l2_sudt_script(generator.rollup_context(), &to_h256(l1_sudt_script_hash)); Ok(to_jsonh256(l2_sudt_script.hash().into())) } + +async fn tests_produce_block( + Params((payload,)): Params<(TestModePayload,)>, + tests_rpc_impl: Data, +) -> Result<()> { + tests_rpc_impl.produce_block(payload).await +} + +async fn tests_get_global_state(tests_rpc_impl: Data) -> Result { + tests_rpc_impl.get_global_state().await +} + +async fn tests_should_produce_block( + tests_rpc_impl: Data, +) -> Result { + tests_rpc_impl.should_produce_block().await +} diff --git a/crates/tools/src/generate_config.rs b/crates/tools/src/generate_config.rs index 26028c438..6c8a0619c 100644 --- a/crates/tools/src/generate_config.rs +++ b/crates/tools/src/generate_config.rs @@ -7,7 +7,7 @@ use ckb_sdk::HttpRpcClient; use ckb_types::prelude::Entity; use gw_config::{ BackendConfig, BlockProducerConfig, ChainConfig, Config, GenesisConfig, RPCClientConfig, - RPCServerConfig, StoreConfig, WalletConfig, Web3IndexerConfig, + RPCServerConfig, StoreConfig, TestMode, WalletConfig, Web3IndexerConfig, }; use gw_jsonrpc_types::godwoken::L2BlockCommittedInfo; @@ -180,6 +180,7 @@ pub fn generate_config( rpc_server, block_producer, web3_indexer, + test_mode: TestMode::Disable, }; let output_content = toml::to_string_pretty(&config).expect("serde toml to string pretty");