diff --git a/Cargo.lock b/Cargo.lock index 1b01242b15..dbfbdb44c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9698,6 +9698,9 @@ dependencies = [ "moveos-store", "moveos-types", "parking_lot 0.12.2", + "rooch-config", + "rooch-db", + "rooch-genesis", "rooch-store", "rooch-types", "schemars", diff --git a/crates/rooch-config/src/lib.rs b/crates/rooch-config/src/lib.rs index 5b78b122a5..99f6d23f0c 100644 --- a/crates/rooch-config/src/lib.rs +++ b/crates/rooch-config/src/lib.rs @@ -6,9 +6,11 @@ use clap::Parser; use moveos_config::{temp_dir, DataDirPath}; use once_cell::sync::Lazy; use rooch_types::crypto::RoochKeyPair; -use rooch_types::rooch_network::{BuiltinChainID, RoochChainID}; +use rooch_types::genesis_config::GenesisConfig; +use rooch_types::rooch_network::{BuiltinChainID, RoochChainID, RoochNetwork}; use serde::{Deserialize, Serialize}; use std::fs::create_dir_all; +use std::str::FromStr; use std::sync::Arc; use std::{fmt::Debug, path::Path, path::PathBuf}; @@ -72,6 +74,12 @@ pub struct RoochOpt { #[clap(long, short = 'n', help = R_OPT_NET_HELP)] pub chain_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[clap(long)] + /// The genesis config file path for custom chain network. + /// If the file path equals to builtin chain network name(local/dev/test/main), will use builtin genesis config. + pub genesis_config: Option, + #[clap(flatten)] pub store: StoreConfig, @@ -150,6 +158,7 @@ impl RoochOpt { let mut opt = RoochOpt { base_data_dir: Some("TMP".into()), chain_id: Some(BuiltinChainID::Local.into()), + genesis_config: None, store: StoreConfig::default(), port: None, eth_rpc_url: None, @@ -215,6 +224,37 @@ impl RoochOpt { self.port.unwrap_or(50051) } + pub fn chain_id(&self) -> RoochChainID { + self.chain_id.clone().unwrap_or_default() + } + + pub fn genesis_config(&self) -> Option { + self.genesis_config.clone().map(|path| { + let path = path.trim(); + let genesis_config: GenesisConfig = match BuiltinChainID::from_str(path) { + Ok(builtin_id) => builtin_id.genesis_config().clone(), + Err(_) => { + let content = + std::fs::read_to_string(path).expect("read genesis config file should ok"); + serde_yaml::from_str(&content).expect("parse genesis config should ok") + } + }; + genesis_config + }) + } + + pub fn network(&self) -> RoochNetwork { + match self.chain_id() { + RoochChainID::Builtin(id) => RoochNetwork::builtin(id), + RoochChainID::Custom(id) => { + let genesis_config = self + .genesis_config() + .expect("Genesis config is required for custom network."); + RoochNetwork::new(id, genesis_config) + } + } + } + pub fn store_config(&self) -> &StoreConfig { &self.store } diff --git a/crates/rooch-rpc-server/src/server/rooch_server.rs b/crates/rooch-rpc-server/src/server/rooch_server.rs index e217c07ecb..73ce5ee82d 100644 --- a/crates/rooch-rpc-server/src/server/rooch_server.rs +++ b/crates/rooch-rpc-server/src/server/rooch_server.rs @@ -369,11 +369,7 @@ impl RoochAPIServer for RoochServer { limit: Option>, descending_order: Option, ) -> RpcResult { - let last_sequencer_order = self - .rpc_service - .get_sequencer_order() - .await? - .map_or(0, |v| v.last_order); + let last_sequencer_order = self.rpc_service.get_sequencer_order().await?; let limit_of = min( limit diff --git a/crates/rooch-rpc-server/src/service/rpc_service.rs b/crates/rooch-rpc-server/src/service/rpc_service.rs index 91bfc21d25..fa7e42fe6d 100644 --- a/crates/rooch-rpc-server/src/service/rpc_service.rs +++ b/crates/rooch-rpc-server/src/service/rpc_service.rs @@ -21,7 +21,6 @@ use rooch_types::indexer::state::{ FieldStateFilter, IndexerFieldState, IndexerObjectState, IndexerStateID, ObjectStateFilter, }; use rooch_types::indexer::transaction::{IndexerTransaction, TransactionFilter}; -use rooch_types::sequencer::SequencerOrder; use rooch_types::transaction::{ExecuteTransactionResponse, LedgerTransaction, RoochTransaction}; use std::collections::HashMap; @@ -190,7 +189,7 @@ impl RpcService { Ok(resp) } - pub async fn get_sequencer_order(&self) -> Result> { + pub async fn get_sequencer_order(&self) -> Result { let resp = self.sequencer.get_sequencer_order().await?; Ok(resp) } diff --git a/crates/rooch-sequencer/Cargo.toml b/crates/rooch-sequencer/Cargo.toml index 7b3550b2f2..f258b84584 100644 --- a/crates/rooch-sequencer/Cargo.toml +++ b/crates/rooch-sequencer/Cargo.toml @@ -44,3 +44,6 @@ moveos-types = { workspace = true } rooch-types = { workspace = true } rooch-store = { workspace = true } +rooch-config = { workspace = true } +rooch-db = { workspace = true } +rooch-genesis = { workspace = true } \ No newline at end of file diff --git a/crates/rooch-sequencer/src/actor/sequencer.rs b/crates/rooch-sequencer/src/actor/sequencer.rs index a9f5b53dbe..d6a64eb15a 100644 --- a/crates/rooch-sequencer/src/actor/sequencer.rs +++ b/crates/rooch-sequencer/src/actor/sequencer.rs @@ -14,9 +14,8 @@ use moveos_types::h256::{self, H256}; use rooch_store::transaction_store::TransactionStore; use rooch_store::RoochStore; use rooch_types::crypto::{RoochKeyPair, Signature}; -use rooch_types::sequencer::SequencerOrder; use rooch_types::transaction::{LedgerTransaction, LedgerTxData}; -use tracing::{debug, info}; +use tracing::info; pub struct SequencerActor { last_order: u64, @@ -26,12 +25,12 @@ pub struct SequencerActor { impl SequencerActor { pub fn new(sequencer_key: RoochKeyPair, rooch_store: RoochStore) -> Result { - let last_order_opt = rooch_store + // The genesis tx order is 0, so the sequencer order should not be None + let last_order = rooch_store .get_meta_store() .get_sequencer_order()? - .map(|order| order.last_order); - // Reserve tx_order = 0 for genesis tx - let last_order = last_order_opt.unwrap_or(0u64); + .map(|order| order.last_order) + .ok_or_else(|| anyhow::anyhow!("Load sequencer tx order failed"))?; info!("Load latest sequencer order {:?}", last_order); Ok(Self { last_order, @@ -40,23 +39,16 @@ impl SequencerActor { }) } + pub fn last_order(&self) -> u64 { + self.last_order + } + pub fn sequence(&mut self, mut tx_data: LedgerTxData) -> Result { let now = SystemTime::now(); let tx_timestamp = now.duration_since(SystemTime::UNIX_EPOCH)?.as_millis() as u64; - let tx_order = if self.last_order == 0 { - let last_order_opt = self - .rooch_store - .get_meta_store() - .get_sequencer_order()? - .map(|order| order.last_order); - match last_order_opt { - Some(last_order) => last_order + 1, - None => 0, - } - } else { - self.last_order + 1 - }; + let tx_order = self.last_order + 1; + let hash = tx_data.tx_hash(); let mut witness_data = hash.as_ref().to_vec(); witness_data.extend(tx_order.to_le_bytes().iter()); @@ -72,8 +64,8 @@ impl SequencerActor { ); self.rooch_store.save_transaction(tx.clone())?; - debug!("sequencer tx: {} order: {:?}", hash, tx_order); - + info!("sequencer tx: {} order: {:?}", hash, tx_order); + self.last_order = tx_order; Ok(tx) } } @@ -129,10 +121,9 @@ impl Handler for SequencerActor { impl Handler for SequencerActor { async fn handle( &mut self, - msg: GetSequencerOrderMessage, + _msg: GetSequencerOrderMessage, _ctx: &mut ActorContext, - ) -> Result> { - let GetSequencerOrderMessage {} = msg; - self.rooch_store.get_meta_store().get_sequencer_order() + ) -> Result { + Ok(self.last_order) } } diff --git a/crates/rooch-sequencer/src/messages.rs b/crates/rooch-sequencer/src/messages.rs index c396e93811..0808883469 100644 --- a/crates/rooch-sequencer/src/messages.rs +++ b/crates/rooch-sequencer/src/messages.rs @@ -4,7 +4,6 @@ use anyhow::Result; use coerce::actor::message::Message; use moveos_types::h256::H256; -use rooch_types::sequencer::SequencerOrder; use rooch_types::transaction::{LedgerTransaction, LedgerTxData}; use serde::{Deserialize, Serialize}; @@ -51,5 +50,5 @@ impl Message for GetTxHashsMessage { pub struct GetSequencerOrderMessage {} impl Message for GetSequencerOrderMessage { - type Result = Result>; + type Result = Result; } diff --git a/crates/rooch-sequencer/src/proxy/mod.rs b/crates/rooch-sequencer/src/proxy/mod.rs index eae39abd1a..3751784c44 100644 --- a/crates/rooch-sequencer/src/proxy/mod.rs +++ b/crates/rooch-sequencer/src/proxy/mod.rs @@ -9,7 +9,6 @@ use crate::{actor::sequencer::SequencerActor, messages::TransactionSequenceMessa use anyhow::Result; use coerce::actor::ActorRef; use moveos_types::h256::H256; -use rooch_types::sequencer::SequencerOrder; use rooch_types::transaction::{LedgerTransaction, LedgerTxData}; #[derive(Clone)] @@ -45,7 +44,7 @@ impl SequencerProxy { self.actor.send(GetTxHashsMessage { tx_orders }).await? } - pub async fn get_sequencer_order(&self) -> Result> { + pub async fn get_sequencer_order(&self) -> Result { self.actor.send(GetSequencerOrderMessage {}).await? } } diff --git a/crates/rooch-sequencer/tests/test_sequencer.rs b/crates/rooch-sequencer/tests/test_sequencer.rs new file mode 100644 index 0000000000..c50cc66fef --- /dev/null +++ b/crates/rooch-sequencer/tests/test_sequencer.rs @@ -0,0 +1,90 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use coerce::actor::{system::ActorSystem, IntoActor}; +use rooch_config::RoochOpt; +use rooch_db::RoochDB; +use rooch_genesis::RoochGenesis; +use rooch_sequencer::{actor::sequencer::SequencerActor, proxy::SequencerProxy}; +use rooch_types::{ + crypto::RoochKeyPair, + transaction::{LedgerTxData, RoochTransaction}, +}; + +fn init_rooch_db(opt: &RoochOpt) -> Result { + let rooch_db = RoochDB::init(opt.store_config())?; + let network = opt.network(); + let genesis = RoochGenesis::build(network)?; + genesis.init_genesis(&rooch_db)?; + Ok(rooch_db) +} + +#[test] +fn test_sequencer() -> Result<()> { + let opt = RoochOpt::new_with_temp_store()?; + let mut last_tx_order = 0; + { + let rooch_db = init_rooch_db(&opt)?; + let sequencer_key = RoochKeyPair::generate_secp256k1(); + let mut sequencer = SequencerActor::new(sequencer_key, rooch_db.rooch_store)?; + assert_eq!(sequencer.last_order(), last_tx_order); + for _ in 0..10 { + let tx_data = LedgerTxData::L2Tx(RoochTransaction::mock()); + let ledger_tx = sequencer.sequence(tx_data)?; + assert_eq!(ledger_tx.sequence_info.tx_order, last_tx_order + 1); + last_tx_order = ledger_tx.sequence_info.tx_order; + } + assert_eq!(sequencer.last_order(), last_tx_order); + } + // load from db again + { + let rooch_db = RoochDB::init(opt.store_config())?; + let sequencer_key = RoochKeyPair::generate_secp256k1(); + let mut sequencer = SequencerActor::new(sequencer_key, rooch_db.rooch_store)?; + assert_eq!(sequencer.last_order(), last_tx_order); + let tx_data = LedgerTxData::L2Tx(RoochTransaction::mock()); + let ledger_tx = sequencer.sequence(tx_data)?; + assert_eq!(ledger_tx.sequence_info.tx_order, last_tx_order + 1); + } + Ok(()) +} + +// test concurrent +// Build a sequencer actor and sequence transactions concurrently +#[tokio::test] +async fn test_sequencer_concurrent() -> Result<()> { + let opt = RoochOpt::new_with_temp_store()?; + let rooch_db = init_rooch_db(&opt)?; + let sequencer_key = RoochKeyPair::generate_secp256k1(); + + let actor_system = ActorSystem::global_system(); + + let sequencer = SequencerActor::new(sequencer_key, rooch_db.rooch_store)? + .into_actor(Some("Sequencer"), &actor_system) + .await?; + let sequencer_proxy = SequencerProxy::new(sequencer.into()); + + // start n thread to sequence + let n = 10; + let mut handles = vec![]; + for _ in 0..n { + let sequencer_proxy = sequencer_proxy.clone(); + //Use tokio to spawn a new async task + let handle = tokio::task::spawn(async move { + for _ in 0..n { + let tx_data = LedgerTxData::L2Tx(RoochTransaction::mock()); + let _ = sequencer_proxy.sequence_transaction(tx_data).await.unwrap(); + } + }); + handles.push(handle); + } + for handle in handles { + handle.await?; + } + + let sequencer_order = sequencer_proxy.get_sequencer_order().await?; + assert_eq!(sequencer_order, n * n); + + Ok(()) +} diff --git a/crates/rooch-store/src/meta_store/mod.rs b/crates/rooch-store/src/meta_store/mod.rs index a0c6a78cf6..d7cf1f8f60 100644 --- a/crates/rooch-store/src/meta_store/mod.rs +++ b/crates/rooch-store/src/meta_store/mod.rs @@ -39,6 +39,12 @@ impl MetaDBStore { } pub fn save_sequencer_order(&self, sequencer_order: SequencerOrder) -> Result<()> { + let pre_sequencer_order = self.get_sequencer_order()?; + if let Some(pre_sequencer_order) = pre_sequencer_order { + if sequencer_order.last_order != pre_sequencer_order.last_order + 1 { + return Err(anyhow::anyhow!("Sequencer order is not continuous")); + } + } self.sequencer_order_store .put_sync(SEQUENCER_ORDER_KEY.to_string(), sequencer_order) } diff --git a/crates/rooch-types/src/rooch_network.rs b/crates/rooch-types/src/rooch_network.rs index 7eff023c4d..2fd15d1096 100644 --- a/crates/rooch-types/src/rooch_network.rs +++ b/crates/rooch-types/src/rooch_network.rs @@ -67,7 +67,8 @@ impl FromStr for BuiltinChainID { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - match s { + let s = s.to_lowercase(); + match s.as_str() { "local" => Ok(BuiltinChainID::Local), "dev" => Ok(BuiltinChainID::Dev), "test" => Ok(BuiltinChainID::Test), diff --git a/crates/rooch-types/src/transaction/rooch.rs b/crates/rooch-types/src/transaction/rooch.rs index a2da4d6d45..19fc970ea3 100644 --- a/crates/rooch-types/src/transaction/rooch.rs +++ b/crates/rooch-types/src/transaction/rooch.rs @@ -149,7 +149,6 @@ impl RoochTransaction { } //TODO use protest Arbitrary to generate mock data - #[cfg(test)] pub fn mock() -> RoochTransaction { use crate::{address::RoochSupportedAddress, crypto::RoochKeyPair}; use move_core_types::{