diff --git a/crates/rooch-benchmarks/src/tx_exec.rs b/crates/rooch-benchmarks/src/tx_exec.rs index ada48ac68b..51ba1e9fe3 100644 --- a/crates/rooch-benchmarks/src/tx_exec.rs +++ b/crates/rooch-benchmarks/src/tx_exec.rs @@ -71,6 +71,7 @@ pub fn tx_exec_benchmark(c: &mut Criterion) { .execute_l1_block(l1_block_with_body.clone()) .unwrap() } + LedgerTxData::L1Tx(tx) => binding_test.execute_l1_tx(tx).unwrap(), LedgerTxData::L2Tx(tx) => binding_test.execute(tx).unwrap(), } }); diff --git a/crates/rooch-executor/src/actor/executor.rs b/crates/rooch-executor/src/actor/executor.rs index ad5267d111..73619233b6 100644 --- a/crates/rooch-executor/src/actor/executor.rs +++ b/crates/rooch-executor/src/actor/executor.rs @@ -3,7 +3,7 @@ use super::messages::{ ExecuteTransactionMessage, ExecuteTransactionResult, GetRootMessage, ValidateL1BlockMessage, - ValidateL2TxMessage, + ValidateL1TxMessage, ValidateL2TxMessage, }; use anyhow::Result; use async_trait::async_trait; @@ -28,7 +28,9 @@ use rooch_types::framework::ethereum::EthereumModule; use rooch_types::framework::transaction_validator::TransactionValidator; use rooch_types::framework::{system_post_execute_functions, system_pre_execute_functions}; use rooch_types::multichain_id::RoochMultiChainID; -use rooch_types::transaction::{AuthenticatorInfo, L1Block, L1BlockWithBody, RoochTransaction}; +use rooch_types::transaction::{ + AuthenticatorInfo, L1Block, L1BlockWithBody, L1Transaction, RoochTransaction, +}; use tracing::{debug, warn}; pub struct ExecutorActor { @@ -98,7 +100,7 @@ impl ExecutorActor { l1_block: L1BlockWithBody, sequencer_address: BitcoinAddress, ) -> Result { - ctx.add(TxValidateResult::new_l1_block(sequencer_address))?; + ctx.add(TxValidateResult::new_l1_block_or_tx(sequencer_address))?; //In the future, we should verify the block PoW difficulty or PoS validator signature before the sequencer decentralized let L1BlockWithBody { block: @@ -140,6 +142,30 @@ impl ExecutorActor { } } + pub fn validate_l1_tx( + &self, + mut ctx: TxContext, + l1_tx: L1Transaction, + sequencer_address: BitcoinAddress, + ) -> Result { + ctx.add(TxValidateResult::new_l1_block_or_tx(sequencer_address))?; + + match RoochMultiChainID::try_from(l1_tx.chain_id.id())? { + RoochMultiChainID::Bitcoin => { + let action = VerifiedMoveAction::Function { + call: BitcoinModule::create_execute_l1_tx_call(l1_tx.block_hash, l1_tx.txid)?, + bypass_visibility: true, + }; + Ok(VerifiedMoveOSTransaction::new( + self.root.clone(), + ctx, + action, + )) + } + id => Err(anyhow::anyhow!("Chain {} not supported yet", id)), + } + } + pub fn validate_l2_tx(&self, mut tx: RoochTransaction) -> Result { let sender = tx.sender(); let tx_hash = tx.tx_hash(); @@ -273,6 +299,17 @@ impl Handler for ExecutorActor { } } +#[async_trait] +impl Handler for ExecutorActor { + async fn handle( + &mut self, + msg: ValidateL1TxMessage, + _ctx: &mut ActorContext, + ) -> Result { + self.validate_l1_tx(msg.ctx, msg.l1_tx, msg.sequencer_address) + } +} + #[async_trait] impl Handler for ExecutorActor { async fn handle( diff --git a/crates/rooch-executor/src/actor/messages.rs b/crates/rooch-executor/src/actor/messages.rs index 92e5de974b..13d29258e6 100644 --- a/crates/rooch-executor/src/actor/messages.rs +++ b/crates/rooch-executor/src/actor/messages.rs @@ -18,7 +18,7 @@ use moveos_types::transaction::TransactionExecutionInfo; use moveos_types::transaction::TransactionOutput; use moveos_types::transaction::VerifiedMoveOSTransaction; use rooch_types::address::{BitcoinAddress, MultiChainAddress}; -use rooch_types::transaction::{L1BlockWithBody, RoochTransaction}; +use rooch_types::transaction::{L1BlockWithBody, L1Transaction, RoochTransaction}; use serde::{Deserialize, Serialize}; #[derive(Debug)] @@ -41,6 +41,17 @@ impl Message for ValidateL1BlockMessage { type Result = Result; } +#[derive(Debug)] +pub struct ValidateL1TxMessage { + pub ctx: TxContext, + pub l1_tx: L1Transaction, + pub sequencer_address: BitcoinAddress, +} + +impl Message for ValidateL1TxMessage { + type Result = Result; +} + #[derive(Debug)] pub struct ExecuteTransactionMessage { pub tx: VerifiedMoveOSTransaction, diff --git a/crates/rooch-executor/src/proxy/mod.rs b/crates/rooch-executor/src/proxy/mod.rs index 5b5c305407..e17833dd53 100644 --- a/crates/rooch-executor/src/proxy/mod.rs +++ b/crates/rooch-executor/src/proxy/mod.rs @@ -4,7 +4,7 @@ use crate::actor::messages::{ GetAnnotatedStatesByStateMessage, GetEventsByEventHandleMessage, GetEventsByEventIDsMessage, GetTxExecutionInfosByHashMessage, ListAnnotatedStatesMessage, ListStatesMessage, - RefreshStateMessage, ValidateL1BlockMessage, + RefreshStateMessage, ValidateL1BlockMessage, ValidateL1TxMessage, }; use crate::actor::reader_executor::ReaderExecutorActor; use crate::actor::{ @@ -38,7 +38,7 @@ use moveos_types::{ use rooch_types::address::BitcoinAddress; use rooch_types::bitcoin::network::BitcoinNetwork; use rooch_types::framework::chain_id::ChainID; -use rooch_types::transaction::{L1BlockWithBody, RoochTransaction}; +use rooch_types::transaction::{L1BlockWithBody, L1Transaction, RoochTransaction}; use tokio::runtime::Handle; #[derive(Clone)] @@ -77,6 +77,21 @@ impl ExecutorProxy { .await? } + pub async fn validate_l1_tx( + &self, + ctx: TxContext, + l1_tx: L1Transaction, + sequencer_address: BitcoinAddress, + ) -> Result { + self.actor + .send(ValidateL1TxMessage { + ctx, + l1_tx, + sequencer_address, + }) + .await? + } + //TODO ensure the execute result pub async fn execute_transaction( &self, diff --git a/crates/rooch-framework-tests/src/binding_test.rs b/crates/rooch-framework-tests/src/binding_test.rs index 1632ed27bf..d0c7b5f6b6 100644 --- a/crates/rooch-framework-tests/src/binding_test.rs +++ b/crates/rooch-framework-tests/src/binding_test.rs @@ -18,9 +18,11 @@ use rooch_db::RoochDB; use rooch_executor::actor::reader_executor::ReaderExecutorActor; use rooch_executor::actor::{executor::ExecutorActor, messages::ExecuteTransactionResult}; use rooch_genesis::RoochGenesis; +use rooch_types::address::BitcoinAddress; use rooch_types::crypto::RoochKeyPair; use rooch_types::rooch_network::{BuiltinChainID, RoochNetwork}; -use rooch_types::transaction::{L1BlockWithBody, RoochTransaction}; +use rooch_types::transaction::{L1BlockWithBody, L1Transaction, RoochTransaction}; +use std::collections::VecDeque; use std::env; use std::path::Path; use std::sync::Arc; @@ -41,6 +43,7 @@ pub struct RustBindingTest { //we keep the opt to ensure the temp dir is not be deleted before the test end opt: RoochOpt, sequencer: AccountAddress, + sequencer_bitcoin_address: BitcoinAddress, kp: RoochKeyPair, pub executor: ExecutorActor, pub reader_executor: ReaderExecutorActor, @@ -79,6 +82,7 @@ impl RustBindingTest { opt, root, sequencer: sequencer.to_rooch_address().into(), + sequencer_bitcoin_address: sequencer, kp, executor, reader_executor, @@ -119,30 +123,84 @@ impl RustBindingTest { } pub fn execute_l1_block(&mut self, l1_block: L1BlockWithBody) -> Result<()> { - let sequence_number = self.get_account_sequence_number(self.sequencer)?; - let ctx = self.create_bt_blk_tx_ctx(sequence_number, l1_block.clone()); - let verified_tx: VerifiedMoveOSTransaction = + let ctx = self.create_l1_block_ctx(&l1_block)?; + let verified_tx: VerifiedMoveOSTransaction = self.executor.validate_l1_block( + ctx, + l1_block.clone(), + self.sequencer_bitcoin_address.clone(), + )?; + self.execute_verified_tx(verified_tx)?; + + if l1_block.block.chain_id.is_bitcoin() { + let block = + bcs::from_bytes::(&l1_block.block_body)?; + let mut l1_txs = block + .txdata + .iter() + .map(|tx| { + L1Transaction::new( + l1_block.block.chain_id, + l1_block.block.block_hash.clone(), + tx.id.to_vec(), + ) + }) + .collect::>(); + let coinbase_tx = l1_txs.pop_front().expect("coinbase tx should exist"); + l1_txs.push_back(coinbase_tx); + for tx in &block.txdata[1..] { + let l1_tx = L1Transaction::new( + l1_block.block.chain_id, + l1_block.block.block_hash.clone(), + tx.id.to_vec(), + ); + self.execute_l1_tx(l1_tx)?; + } + let coinbase_tx = L1Transaction::new( + l1_block.block.chain_id, + l1_block.block.block_hash.clone(), + block.txdata[0].id.to_vec(), + ); + self.execute_l1_tx(coinbase_tx)?; + } + Ok(()) + } + + pub fn execute_l1_tx(&mut self, l1_tx: L1Transaction) -> Result<()> { + let ctx = self.create_l1_tx_ctx(l1_tx.clone())?; + let verified_tx = self.executor - .validate_l1_block(ctx, l1_block, self.kp.public().bitcoin_address()?)?; + .validate_l1_tx(ctx, l1_tx, self.sequencer_bitcoin_address.clone())?; self.execute_verified_tx(verified_tx) } - pub fn create_bt_blk_tx_ctx( - &mut self, - sequence_number: u64, - l1_block: L1BlockWithBody, - ) -> TxContext { + pub fn create_l1_block_ctx(&self, l1_block: &L1BlockWithBody) -> Result { + let sequence_number = self.get_account_sequence_number(self.sequencer)?; let max_gas_amount = GasScheduleConfig::INITIAL_MAX_GAS_AMOUNT * 1000; let tx_hash = l1_block.block.tx_hash(); let tx_size = l1_block.block.tx_size(); - TxContext::new( + Ok(TxContext::new( + self.sequencer, + sequence_number, + max_gas_amount, + tx_hash, + tx_size, + )) + } + + pub fn create_l1_tx_ctx(&self, l1_tx: L1Transaction) -> Result { + let sequence_number = self.get_account_sequence_number(self.sequencer)?; + let max_gas_amount = GasScheduleConfig::INITIAL_MAX_GAS_AMOUNT * 1000; + let tx_hash = l1_tx.tx_hash(); + let tx_size = l1_tx.tx_size(); + + Ok(TxContext::new( self.sequencer, sequence_number, max_gas_amount, tx_hash, tx_size, - ) + )) } pub fn execute_as_result(&mut self, tx: RoochTransaction) -> Result { diff --git a/crates/rooch-open-rpc-spec/schemas/openrpc.json b/crates/rooch-open-rpc-spec/schemas/openrpc.json index 3259a21391..2943c4321e 100644 --- a/crates/rooch-open-rpc-spec/schemas/openrpc.json +++ b/crates/rooch-open-rpc-spec/schemas/openrpc.json @@ -1659,6 +1659,34 @@ } } }, + { + "type": "object", + "required": [ + "block_hash", + "chain_id", + "txid", + "type" + ], + "properties": { + "block_hash": { + "$ref": "#/components/schemas/alloc::vec::Vec" + }, + "chain_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "txid": { + "$ref": "#/components/schemas/alloc::vec::Vec" + }, + "type": { + "type": "string", + "enum": [ + "l1_tx" + ] + } + } + }, { "type": "object", "required": [ diff --git a/crates/rooch-pipeline-processor/src/actor/messages.rs b/crates/rooch-pipeline-processor/src/actor/messages.rs index 57467e3023..7bd8db8b05 100644 --- a/crates/rooch-pipeline-processor/src/actor/messages.rs +++ b/crates/rooch-pipeline-processor/src/actor/messages.rs @@ -6,7 +6,7 @@ use coerce::actor::message::Message; use moveos_types::moveos_std::tx_context::TxContext; use rooch_types::{ address::BitcoinAddress, - transaction::{ExecuteTransactionResponse, L1BlockWithBody, RoochTransaction}, + transaction::{ExecuteTransactionResponse, L1BlockWithBody, L1Transaction, RoochTransaction}, }; #[derive(Clone)] @@ -28,3 +28,14 @@ pub struct ExecuteL1BlockMessage { impl Message for ExecuteL1BlockMessage { type Result = Result; } + +#[derive(Clone)] +pub struct ExecuteL1TxMessage { + pub ctx: TxContext, + pub tx: L1Transaction, + pub sequencer_address: BitcoinAddress, +} + +impl Message for ExecuteL1TxMessage { + type Result = Result; +} diff --git a/crates/rooch-pipeline-processor/src/actor/processor.rs b/crates/rooch-pipeline-processor/src/actor/processor.rs index 9835094fbc..3d30a22c00 100644 --- a/crates/rooch-pipeline-processor/src/actor/processor.rs +++ b/crates/rooch-pipeline-processor/src/actor/processor.rs @@ -1,7 +1,7 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use super::messages::{ExecuteL1BlockMessage, ExecuteL2TxMessage}; +use super::messages::{ExecuteL1BlockMessage, ExecuteL1TxMessage, ExecuteL2TxMessage}; use anyhow::Result; use async_trait::async_trait; use coerce::actor::{context::ActorContext, message::Handler, Actor}; @@ -16,8 +16,8 @@ use rooch_sequencer::proxy::SequencerProxy; use rooch_types::{ address::BitcoinAddress, transaction::{ - ExecuteTransactionResponse, L1BlockWithBody, LedgerTransaction, LedgerTxData, - RoochTransaction, + ExecuteTransactionResponse, L1BlockWithBody, L1Transaction, LedgerTransaction, + LedgerTxData, RoochTransaction, }, }; use tracing::debug; @@ -65,6 +65,23 @@ impl PipelineProcessorActor { self.execute_tx(ledger_tx, moveos_tx).await } + pub async fn execute_l1_tx( + &mut self, + ctx: TxContext, + l1_tx: L1Transaction, + sequencer_address: BitcoinAddress, + ) -> Result { + let moveos_tx = self + .executor + .validate_l1_tx(ctx, l1_tx.clone(), sequencer_address) + .await?; + let ledger_tx = self + .sequencer + .sequence_transaction(LedgerTxData::L1Tx(l1_tx)) + .await?; + self.execute_tx(ledger_tx, moveos_tx).await + } + pub async fn execute_l2_tx( &mut self, mut tx: RoochTransaction, @@ -153,3 +170,15 @@ impl Handler for PipelineProcessorActor { .await } } + +#[async_trait] +impl Handler for PipelineProcessorActor { + async fn handle( + &mut self, + msg: ExecuteL1TxMessage, + _ctx: &mut ActorContext, + ) -> Result { + self.execute_l1_tx(msg.ctx, msg.tx, msg.sequencer_address) + .await + } +} diff --git a/crates/rooch-pipeline-processor/src/proxy/mod.rs b/crates/rooch-pipeline-processor/src/proxy/mod.rs index 678718951e..8a5d59241a 100644 --- a/crates/rooch-pipeline-processor/src/proxy/mod.rs +++ b/crates/rooch-pipeline-processor/src/proxy/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::actor::{ - messages::{ExecuteL1BlockMessage, ExecuteL2TxMessage}, + messages::{ExecuteL1BlockMessage, ExecuteL1TxMessage, ExecuteL2TxMessage}, processor::PipelineProcessorActor, }; use anyhow::Result; @@ -10,7 +10,9 @@ use coerce::actor::ActorRef; use moveos_types::moveos_std::tx_context::TxContext; use rooch_types::{ address::BitcoinAddress, - transaction::{rooch::RoochTransaction, ExecuteTransactionResponse, L1BlockWithBody}, + transaction::{ + rooch::RoochTransaction, ExecuteTransactionResponse, L1BlockWithBody, L1Transaction, + }, }; #[derive(Clone)] @@ -41,6 +43,21 @@ impl PipelineProcessorProxy { }) .await? } + + pub async fn execute_l1_tx( + &self, + ctx: TxContext, + tx: L1Transaction, + sequencer_address: BitcoinAddress, + ) -> Result { + self.actor + .send(ExecuteL1TxMessage { + ctx, + tx, + sequencer_address, + }) + .await? + } } impl From> for PipelineProcessorProxy { diff --git a/crates/rooch-relayer/src/actor/relayer.rs b/crates/rooch-relayer/src/actor/relayer.rs index 02705a3dd8..f32ef2adff 100644 --- a/crates/rooch-relayer/src/actor/relayer.rs +++ b/crates/rooch-relayer/src/actor/relayer.rs @@ -1,6 +1,8 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 +use std::collections::VecDeque; + use super::bitcoin_relayer::BitcoinRelayer; use super::ethereum_relayer::EthereumRelayer; use super::messages::RelayTick; @@ -20,6 +22,7 @@ use rooch_executor::proxy::ExecutorProxy; use rooch_pipeline_processor::proxy::PipelineProcessorProxy; use rooch_types::address::BitcoinAddress; use rooch_types::crypto::RoochKeyPair; +use rooch_types::transaction::L1Transaction; use tracing::{error, info, warn}; pub struct RelayerActor { @@ -89,7 +92,11 @@ impl RelayerActor { let block_height = l1_block.block.block_height; let result = self .processor - .execute_l1_block(ctx, l1_block, self.sequencer_bitcoin_address.clone()) + .execute_l1_block( + ctx, + l1_block.clone(), + self.sequencer_bitcoin_address.clone(), + ) .await?; match result.execution_info.status { @@ -98,6 +105,70 @@ impl RelayerActor { "Relayer execute relay block(hash: {}, height: {}) success", block_hash, block_height ); + //TODO lazy execute txs to handle reorg, currently we directly execute txs + if l1_block.block.chain_id.is_bitcoin() { + let block = + bcs::from_bytes::( + &l1_block.block_body, + )?; + let block_hash = l1_block.block.block_hash.clone(); + let mut l1_txs = block + .txdata + .iter() + .map(|tx| { + L1Transaction::new( + l1_block.block.chain_id, + block_hash.clone(), + tx.id.to_vec(), + ) + }) + .collect::>(); + + //move coinbase tx to the last + let coinbase_tx = + l1_txs.pop_front().expect("coinbase tx should exist"); + l1_txs.push_back(coinbase_tx); + + for l1_tx in l1_txs { + let sequence_number = self + .executor + .get_sequence_number(self.sequencer_address) + .await?; + let tx_hash = l1_tx.tx_hash(); + let txid = hex::encode(&l1_tx.txid); + let ctx = TxContext::new( + self.sequencer_address, + sequence_number, + self.max_gas_amount, + tx_hash, + l1_tx.tx_size(), + ); + let result = self + .processor + .execute_l1_tx( + ctx, + l1_tx, + self.sequencer_bitcoin_address.clone(), + ) + .await?; + + match result.execution_info.status { + KeptVMStatus::Executed => { + info!( + "Relayer execute relay tx(txid: {}) success", + txid + ); + } + _ => { + error!( + "Relayer execute relay tx(txid: {}) failed, status: {:?}", + txid, result.execution_info.status + ); + break; + } + } + } + } } _ => { //TODO should we stop the service if the relayer failed diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs b/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs index 0f112a3279..fb476962c7 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs @@ -7,7 +7,9 @@ use crate::jsonrpc_types::{ TransactionView, }; use rooch_types::indexer::transaction::TransactionFilter; -use rooch_types::transaction::{L1Block, LedgerTransaction, LedgerTxData, TransactionWithInfo}; +use rooch_types::transaction::{ + L1Block, L1Transaction, LedgerTransaction, LedgerTxData, TransactionWithInfo, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -28,10 +30,28 @@ impl From for L1BlockView { } } +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct L1TransactionView { + pub chain_id: u64, + pub block_hash: BytesView, + pub txid: BytesView, +} + +impl From for L1TransactionView { + fn from(tx: L1Transaction) -> Self { + Self { + chain_id: tx.chain_id.id(), + block_hash: tx.block_hash.into(), + txid: tx.txid.into(), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum LedgerTxDataView { L1Block(L1BlockView), + L1Tx(L1TransactionView), L2Tx(TransactionView), } @@ -42,6 +62,7 @@ impl LedgerTxDataView { ) -> Self { match data { LedgerTxData::L1Block(block) => LedgerTxDataView::L1Block(block.into()), + LedgerTxData::L1Tx(tx) => LedgerTxDataView::L1Tx(tx.into()), LedgerTxData::L2Tx(tx) => LedgerTxDataView::L2Tx( TransactionView::new_from_rooch_transaction(tx, sender_bitcoin_address), ), diff --git a/crates/rooch-types/src/bitcoin/mod.rs b/crates/rooch-types/src/bitcoin/mod.rs index bb9ca2a192..1bc8961c0d 100644 --- a/crates/rooch-types/src/bitcoin/mod.rs +++ b/crates/rooch-types/src/bitcoin/mod.rs @@ -29,6 +29,7 @@ pub mod brc20; pub mod genesis; pub mod network; pub mod ord; +pub mod pending_block; pub mod types; pub mod utxo; @@ -41,6 +42,12 @@ pub struct BitcoinBlockStore { pub height_to_hash: ObjectID, /// block hash -> block height table id pub hash_to_height: ObjectID, + /// tx id -> tx table id + pub txs: ObjectID, + /// tx id -> tx table id + pub tx_to_height: ObjectID, + /// tx id list table id + pub tx_ids: ObjectID, } impl BitcoinBlockStore { @@ -62,6 +69,9 @@ impl MoveStructState for BitcoinBlockStore { ObjectID::type_layout(), ObjectID::type_layout(), ObjectID::type_layout(), + ObjectID::type_layout(), + ObjectID::type_layout(), + ObjectID::type_layout(), ]) } } @@ -83,7 +93,7 @@ impl<'a> BitcoinModule<'a> { ident_str!("submit_new_block"); pub const GET_GENESIS_BLOCK_HEIGHT_FUNCTION_NAME: &'static IdentStr = ident_str!("get_genesis_block_height"); - // pub const PROCESS_UTXOS_ENTRY_FUNCTION_NAME: &'static IdentStr = ident_str!("process_utxos"); + pub const EXECUTE_L1_TX_FUNCTION_NAME: &'static IdentStr = ident_str!("execute_l1_tx"); pub fn get_block(&self, block_hash: BlockHash) -> Result> { let call = Self::create_function_call( @@ -99,7 +109,7 @@ impl<'a> BitcoinModule<'a> { .map(|mut values| { let value = values.pop().expect("should have one return value"); bcs::from_bytes::>(&value.value) - .expect("should be a valid MoveOption") + .expect("should be a valid MoveOption
") })?; Ok(block_header.into()) } @@ -118,7 +128,7 @@ impl<'a> BitcoinModule<'a> { .map(|mut values| { let value = values.pop().expect("should have one return value"); bcs::from_bytes::>(&value.value) - .expect("should be a valid MoveOption") + .expect("should be a valid MoveOption
") })?; Ok(block_header.into()) } @@ -208,6 +218,16 @@ impl<'a> BitcoinModule<'a> { ], )) } + + pub fn create_execute_l1_tx_call(block_hash: Vec, txid: Vec) -> Result { + let block_hash = AccountAddress::from_bytes(block_hash)?; + let txid = AccountAddress::from_bytes(txid)?; + Ok(Self::create_function_call( + Self::EXECUTE_L1_TX_FUNCTION_NAME, + vec![], + vec![MoveValue::Address(block_hash), MoveValue::Address(txid)], + )) + } } impl<'a> ModuleBinding<'a> for BitcoinModule<'a> { diff --git a/crates/rooch-types/src/bitcoin/pending_block.rs b/crates/rooch-types/src/bitcoin/pending_block.rs new file mode 100644 index 0000000000..a4a8b14608 --- /dev/null +++ b/crates/rooch-types/src/bitcoin/pending_block.rs @@ -0,0 +1,95 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::addresses::BITCOIN_MOVE_ADDRESS; +use anyhow::Result; +use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr}; +use moveos_types::{ + module_binding::{ModuleBinding, MoveFunctionCaller}, + move_std::option::MoveOption, + moveos_std::tx_context::TxContext, + state::MoveStructState, + state::MoveStructType, +}; +use serde::{Deserialize, Serialize}; + +pub const MODULE_NAME: &IdentStr = ident_str!("pending_block"); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PendingTxs { + pub block_hash: AccountAddress, + pub txs: Vec, +} + +impl MoveStructType for PendingTxs { + const MODULE_NAME: &'static IdentStr = MODULE_NAME; + const STRUCT_NAME: &'static IdentStr = ident_str!("PendingTxs"); + const ADDRESS: AccountAddress = BITCOIN_MOVE_ADDRESS; +} + +impl MoveStructState for PendingTxs { + fn struct_layout() -> move_core_types::value::MoveStructLayout { + move_core_types::value::MoveStructLayout::new(vec![ + move_core_types::value::MoveTypeLayout::Address, + move_core_types::value::MoveTypeLayout::Vector(Box::new( + move_core_types::value::MoveTypeLayout::Address, + )), + ]) + } +} + +/// Rust bindings for BitcoinMove bitcoin module +pub struct PendingBlockModule<'a> { + caller: &'a dyn MoveFunctionCaller, +} + +impl<'a> PendingBlockModule<'a> { + pub const GET_READY_PENDING_TXS_FUNCTION_NAME: &'static IdentStr = + ident_str!("get_ready_pending_txs"); + pub const GET_LATEST_BLOCK_HEIGHT_FUNCTION_NAME: &'static IdentStr = + ident_str!("get_latest_block_height"); + + pub fn get_ready_pending_txs(&self) -> Result> { + let call = + Self::create_function_call(Self::GET_READY_PENDING_TXS_FUNCTION_NAME, vec![], vec![]); + let ctx = TxContext::new_readonly_ctx(AccountAddress::ZERO); + let pending_txs_opt = + self.caller + .call_function(&ctx, call)? + .into_result() + .map(|mut values| { + let value = values.pop().expect("should have one return value"); + bcs::from_bytes::>(&value.value) + .expect("should be a valid MoveOption") + })?; + Ok(pending_txs_opt.into()) + } + + pub fn get_latest_block_height(&self) -> Result> { + let call = + Self::create_function_call(Self::GET_LATEST_BLOCK_HEIGHT_FUNCTION_NAME, vec![], vec![]); + let ctx = TxContext::new_readonly_ctx(AccountAddress::ZERO); + let height = self + .caller + .call_function(&ctx, call)? + .into_result() + .map(|mut values| { + let value = values.pop().expect("should have one return value"); + bcs::from_bytes::>(&value.value) + .expect("should be a valid MoveOption") + })?; + Ok(height.into()) + } +} + +impl<'a> ModuleBinding<'a> for PendingBlockModule<'a> { + const MODULE_NAME: &'static IdentStr = MODULE_NAME; + const MODULE_ADDRESS: AccountAddress = BITCOIN_MOVE_ADDRESS; + + fn new(caller: &'a impl MoveFunctionCaller) -> Self + where + Self: Sized, + { + Self { caller } + } +} diff --git a/crates/rooch-types/src/framework/auth_validator.rs b/crates/rooch-types/src/framework/auth_validator.rs index ca46701a6c..c9bcdad1ca 100644 --- a/crates/rooch-types/src/framework/auth_validator.rs +++ b/crates/rooch-types/src/framework/auth_validator.rs @@ -175,7 +175,7 @@ impl MoveStructState for TxValidateResult { } impl TxValidateResult { - pub fn new_l1_block(sequencer_address: BitcoinAddress) -> Self { + pub fn new_l1_block_or_tx(sequencer_address: BitcoinAddress) -> Self { Self { auth_validator_id: BuiltinAuthValidator::Bitcoin.flag().into(), auth_validator: MoveOption::none(), diff --git a/crates/rooch-types/src/indexer/transaction.rs b/crates/rooch-types/src/indexer/transaction.rs index 536de343b1..91d26a8ffb 100644 --- a/crates/rooch-types/src/indexer/transaction.rs +++ b/crates/rooch-types/src/indexer/transaction.rs @@ -44,6 +44,7 @@ impl IndexerTransaction { let status = serde_json::to_string(&execution_info.status)?; let (auth_validator_id, authenticator_payload) = match &transaction.data { LedgerTxData::L1Block(_block) => (0, vec![]), + LedgerTxData::L1Tx(_tx) => (0, vec![]), LedgerTxData::L2Tx(tx) => ( tx.authenticator().auth_validator_id, tx.authenticator().payload.clone(), diff --git a/crates/rooch-types/src/transaction/ledger_transaction.rs b/crates/rooch-types/src/transaction/ledger_transaction.rs index 9824d2f146..6b436f8967 100644 --- a/crates/rooch-types/src/transaction/ledger_transaction.rs +++ b/crates/rooch-types/src/transaction/ledger_transaction.rs @@ -34,9 +34,40 @@ pub struct L1BlockWithBody { pub block_body: Vec, } +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub struct L1Transaction { + pub chain_id: MultiChainID, + pub block_hash: Vec, + /// The original L1 transaction id, usually the hash of the transaction + pub txid: Vec, +} + +impl L1Transaction { + pub fn new(chain_id: MultiChainID, block_hash: Vec, txid: Vec) -> Self { + Self { + chain_id, + block_hash, + txid, + } + } + + pub fn tx_hash(&self) -> H256 { + moveos_types::h256::sha3_256_of(self.encode().as_slice()) + } + + pub fn encode(&self) -> Vec { + bcs::to_bytes(self).expect("encode transaction should success") + } + + pub fn tx_size(&self) -> u64 { + bcs::serialized_size(self).expect("serialize transaction size should success") as u64 + } +} + #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] pub enum LedgerTxData { L1Block(L1Block), + L1Tx(L1Transaction), L2Tx(RoochTransaction), } @@ -45,6 +76,7 @@ impl LedgerTxData { match self { LedgerTxData::L1Block(block) => block.tx_hash(), LedgerTxData::L2Tx(tx) => tx.tx_hash(), + LedgerTxData::L1Tx(tx) => tx.tx_hash(), } } @@ -52,6 +84,7 @@ impl LedgerTxData { match self { LedgerTxData::L1Block(_) => None, LedgerTxData::L2Tx(tx) => Some(tx.sender()), + LedgerTxData::L1Tx(_) => None, } } } diff --git a/crates/rooch-types/src/transaction/mod.rs b/crates/rooch-types/src/transaction/mod.rs index 7a87eb10e6..58da36370b 100644 --- a/crates/rooch-types/src/transaction/mod.rs +++ b/crates/rooch-types/src/transaction/mod.rs @@ -18,7 +18,9 @@ pub mod rooch; use crate::indexer::transaction::IndexerTransaction; pub use authenticator::Authenticator; -pub use ledger_transaction::{L1Block, L1BlockWithBody, LedgerTransaction, LedgerTxData}; +pub use ledger_transaction::{ + L1Block, L1BlockWithBody, L1Transaction, LedgerTransaction, LedgerTxData, +}; pub use rooch::{RoochTransaction, RoochTransactionData}; #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] diff --git a/frameworks/bitcoin-move/doc/pending_block.md b/frameworks/bitcoin-move/doc/pending_block.md index f27917f580..d842c5b02c 100644 --- a/frameworks/bitcoin-move/doc/pending_block.md +++ b/frameworks/bitcoin-move/doc/pending_block.md @@ -11,6 +11,7 @@ PendingStore is used to store the pending blocks and txs, and handle the reorg - [Resource `PendingStore`](#0x4_pending_block_PendingStore) - [Struct `InprocessBlock`](#0x4_pending_block_InprocessBlock) - [Struct `ReorgEvent`](#0x4_pending_block_ReorgEvent) +- [Struct `PendingTxs`](#0x4_pending_block_PendingTxs) - [Constants](#@Constants_0) - [Function `genesis_init`](#0x4_pending_block_genesis_init) - [Function `new_pending_block_id`](#0x4_pending_block_new_pending_block_id) @@ -23,6 +24,8 @@ PendingStore is used to store the pending blocks and txs, and handle the reorg - [Function `inprocess_block_tx`](#0x4_pending_block_inprocess_block_tx) - [Function `inprocess_block_header`](#0x4_pending_block_inprocess_block_header) - [Function `inprocess_block_height`](#0x4_pending_block_inprocess_block_height) +- [Function `get_ready_pending_txs`](#0x4_pending_block_get_ready_pending_txs) +- [Function `get_latest_block_height`](#0x4_pending_block_get_latest_block_height)
use 0x1::option;
@@ -93,6 +96,17 @@ This is a hot potato struct, can not be store and drop
 
 
 
+
+
+## Struct `PendingTxs`
+
+
+
+
struct PendingTxs has copy, drop, store
+
+ + + ## Constants @@ -288,3 +302,26 @@ This is a hot potato struct, can not be store and drop
public(friend) fun inprocess_block_height(inprocess_block: &pending_block::InprocessBlock): u64
 
+ + + + + +## Function `get_ready_pending_txs` + +Get the pending txs which are ready to be processed + + +
public fun get_ready_pending_txs(): option::Option<pending_block::PendingTxs>
+
+ + + + + +## Function `get_latest_block_height` + + + +
public fun get_latest_block_height(): option::Option<u64>
+
diff --git a/frameworks/bitcoin-move/sources/bitcoin.move b/frameworks/bitcoin-move/sources/bitcoin.move index 1e27f512dd..9a246d7218 100644 --- a/frameworks/bitcoin-move/sources/bitcoin.move +++ b/frameworks/bitcoin-move/sources/bitcoin.move @@ -254,6 +254,7 @@ module bitcoin_move::bitcoin{ /// The the sequencer submit a new Bitcoin block /// This function is a system function, only the sequencer can call it + /// TODO rename to execute_l1_block fun submit_new_block(block_height: u64, block_hash: address, block_bytes: vector){ let block = bcs::from_bytes(block_bytes); let block_header = types::header(&block); @@ -264,19 +265,10 @@ module bitcoin_move::bitcoin{ let timestamp_seconds = (time as u64); let module_signer = signer::module_signer(); timestamp::try_update_global_time(&module_signer, timestamp::seconds_to_milliseconds(timestamp_seconds)); - - // We temporarily conform the txs here, we will remove this after refator the relayer - let (_, txs) = types::unpack_block(block); - let coinbase_tx = vector::remove(&mut txs, 0); - vector::for_each(txs, |tx| { - let txid = types::tx_id(&tx); - conform_tx(block_hash, txid); - }); - //process coinbase tx last - conform_tx(block_hash, types::tx_id(&coinbase_tx)); } - fun conform_tx(block_hash: address, txid: address){ + /// This is the execute_l1_tx entry point + fun execute_l1_tx(block_hash: address, txid: address){ let btc_block_store_obj = borrow_block_store_mut(); let btc_block_store = object::borrow_mut(btc_block_store_obj); let inprocess_block = pending_block::process_pending_tx(block_hash, txid); @@ -403,7 +395,16 @@ module bitcoin_move::bitcoin{ public fun submit_new_block_for_test(block_height: u64, block: Block){ let block_hash = types::header_to_hash(types::header(&block)); let block_bytes = bcs::to_bytes(&block); - submit_new_block(block_height, block_hash, block_bytes); + submit_new_block(block_height, block_hash, block_bytes); + // We directly conform the txs for convenience test + let (_, txs) = types::unpack_block(block); + let coinbase_tx = vector::remove(&mut txs, 0); + vector::for_each(txs, |tx| { + let txid = types::tx_id(&tx); + execute_l1_tx(block_hash, txid); + }); + //process coinbase tx last + execute_l1_tx(block_hash, types::tx_id(&coinbase_tx)); } } \ No newline at end of file diff --git a/frameworks/bitcoin-move/sources/pending_block.move b/frameworks/bitcoin-move/sources/pending_block.move index a9a1506500..a5bac70a00 100644 --- a/frameworks/bitcoin-move/sources/pending_block.move +++ b/frameworks/bitcoin-move/sources/pending_block.move @@ -73,12 +73,25 @@ module bitcoin_move::pending_block{ } } + fun borrow_store(): &PendingStore { + let obj_id = object::named_object_id(); + let store_obj = object::borrow_object(obj_id); + object::borrow(store_obj) + } + fun borrow_mut_store(): &mut PendingStore { let obj_id = object::named_object_id(); let store_obj = object::borrow_mut_object_extend(obj_id); object::borrow_mut(store_obj) } + fun borrow_pending_block(block_hash: address): &Object{ + let block_id = new_pending_block_id(block_hash); + let block_obj_id = object::custom_object_id(block_id); + assert!(object::exists_object(block_obj_id), ErrorPendingBlockNotFound); + object::borrow_object(block_obj_id) + } + fun borrow_mut_pending_block(block_hash: address): &mut Object{ let block_id = new_pending_block_id(block_hash); let block_obj_id = object::custom_object_id(block_id); @@ -234,4 +247,40 @@ module bitcoin_move::pending_block{ let block_obj = object::borrow(&inprocess_block.block_obj); block_obj.block_height } + + // ============== Pending Block Query ============== + + struct PendingTxs has copy, drop, store{ + block_hash: address, + txs: vector
, + } + + /// Get the pending txs which are ready to be processed + public fun get_ready_pending_txs(): Option{ + let store = borrow_store(); + if(option::is_none(&store.latest_block_height)){ + return option::none() + }; + let latest_block_height = *option::borrow(&store.latest_block_height); + let ready_block_height = latest_block_height - store.reorg_pending_blocks; + if(!simple_map::contains_key(&store.pending_blocks, &ready_block_height)){ + return option::none() + }; + let block_hash = *simple_map::borrow(&store.pending_blocks, &ready_block_height); + let block_obj = borrow_pending_block(block_hash); + let tx_ids: vector
= *object::borrow_field(block_obj, TX_IDS_KEY); + let unprocessed_tx_ids : vector
= vector::filter(tx_ids, |txid| { + object::contains_field(block_obj, *txid) + }); + let pending_txs = PendingTxs{ + block_hash: block_hash, + txs: unprocessed_tx_ids, + }; + option::some(pending_txs) + } + + public fun get_latest_block_height(): Option{ + let store = borrow_store(); + store.latest_block_height + } } \ No newline at end of file