From d0d2336f07b65a173e2ed11f185c16cb4f8ffd92 Mon Sep 17 00:00:00 2001 From: keroro Date: Tue, 1 Nov 2022 17:17:03 +0800 Subject: [PATCH] all in one --- .gitignore | 1 + crates/benches/benches/benchmarks/smt.rs | 2 + crates/benches/benches/benchmarks/sudt.rs | 1 + crates/block-producer/src/block_producer.rs | 73 ++++++++++++++--- crates/block-producer/src/challenger.rs | 2 +- crates/block-producer/src/custodian.rs | 29 ++++--- crates/block-producer/src/produce_block.rs | 19 ++++- crates/block-producer/src/runner.rs | 3 +- crates/block-producer/src/stake.rs | 29 ++++--- crates/block-producer/src/withdrawal.rs | 38 ++++++--- .../block-producer/src/withdrawal_unlocker.rs | 20 +++-- crates/chain/src/chain.rs | 81 ++++++++++++------- crates/challenge/src/offchain/mock_block.rs | 15 +++- crates/challenge/src/revert.rs | 27 +++++-- crates/config/src/config.rs | 13 +++ crates/db/src/schema.rs | 4 +- .../src/account_lock_manage/secp256k1.rs | 6 ++ crates/generator/src/genesis.rs | 1 + crates/generator/src/utils.rs | 23 +++--- .../generator/src/verification/withdrawal.rs | 5 +- .../src/subcommand/import_block.rs | 2 +- crates/mem-pool/src/custodian.rs | 43 ++++++---- crates/mem-pool/src/deposit.rs | 6 +- crates/mem-pool/src/pool.rs | 68 +++++++++++----- crates/mem-pool/src/withdrawal.rs | 24 +++++- crates/replay-chain/src/setup.rs | 3 +- crates/rpc-client/src/ckb_client.rs | 5 ++ crates/rpc-client/src/indexer_client.rs | 10 ++- crates/rpc-client/src/rpc_client.rs | 24 +++--- crates/rpc-client/src/withdrawal.rs | 48 ++++++----- crates/store/src/traits/chain_store.rs | 7 +- .../src/transaction/store_transaction.rs | 34 ++++++-- crates/tests/src/testing_tool/chain.rs | 8 +- crates/tests/src/tests/export_import_block.rs | 6 +- crates/tests/src/tests/mem_block_repackage.rs | 2 + .../src/tests/unlock_withdrawal_to_owner.rs | 31 ++++--- crates/tools/src/deposit_ckb.rs | 5 +- crates/tools/src/main.rs | 12 ++- crates/tools/src/stat/mod.rs | 5 +- crates/tools/src/withdraw.rs | 6 +- 40 files changed, 514 insertions(+), 227 deletions(-) diff --git a/.gitignore b/.gitignore index 5803593e1..53ae39e51 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build/ .tmp/ crates/benches/smt_data/db/ crates/tests/mem_block/ +*.swp diff --git a/crates/benches/benches/benchmarks/smt.rs b/crates/benches/benches/benchmarks/smt.rs index cdddcd3eb..60fe478fb 100644 --- a/crates/benches/benches/benchmarks/smt.rs +++ b/crates/benches/benches/benchmarks/smt.rs @@ -143,6 +143,7 @@ impl BenchExecutionEnvironment { let rollup_context = RollupContext { rollup_config: genesis_config.rollup_config.clone().into(), rollup_script_hash: ROLLUP_TYPE_HASH.into(), + ..Default::default() }; let backend_manage = { @@ -414,6 +415,7 @@ impl BenchExecutionEnvironment { Vec::new(), Default::default(), Vec::new(), + Default::default(), ) .unwrap(); diff --git a/crates/benches/benches/benchmarks/sudt.rs b/crates/benches/benches/benchmarks/sudt.rs index e95d84b19..1d7feb31d 100644 --- a/crates/benches/benches/benchmarks/sudt.rs +++ b/crates/benches/benches/benchmarks/sudt.rs @@ -88,6 +88,7 @@ fn run_contract_get_result( let rollup_ctx = RollupContext { rollup_config: rollup_config.clone(), rollup_script_hash: [42u8; 32].into(), + ..Default::default() }; let generator = Generator::new( backend_manage, diff --git a/crates/block-producer/src/block_producer.rs b/crates/block-producer/src/block_producer.rs index 3342fecdb..9e388da83 100644 --- a/crates/block-producer/src/block_producer.rs +++ b/crates/block-producer/src/block_producer.rs @@ -22,6 +22,8 @@ use gw_mem_pool::{ }; use gw_rpc_client::{contract::ContractsCellDepManager, rpc_client::RPCClient}; use gw_store::Store; +use gw_types::core::Timepoint; +use gw_types::offchain::{global_state_from_slice, CompatibleFinalizedTimepoint}; use gw_types::{ bytes::Bytes, offchain::{DepositInfo, InputCellInfo, RollupContext}, @@ -32,8 +34,9 @@ use gw_types::{ prelude::*, }; use gw_utils::{ - fee::fill_tx_fee_with_local, genesis_info::CKBGenesisInfo, local_cells::LocalCellsManager, - query_rollup_cell, since::Since, transaction_skeleton::TransactionSkeleton, wallet::Wallet, + fee::fill_tx_fee_with_local, genesis_info::CKBGenesisInfo, get_confirmed_header_timestamp, + local_cells::LocalCellsManager, query_l1_header_by_timestamp, query_rollup_cell, since::Since, + transaction_skeleton::TransactionSkeleton, wallet::Wallet, }; use std::{collections::HashSet, sync::Arc, time::Instant}; use tokio::sync::Mutex; @@ -48,9 +51,17 @@ fn generate_custodian_cells( deposit_cells: &[DepositInfo], ) -> Vec<(CellOutput, Bytes)> { let block_hash: H256 = block.hash().into(); - let block_number = block.raw().number().unpack(); + let block_timepoint = { + let block_number = block.raw().number().unpack(); + if rollup_context.determine_global_state_version(block_number) < 2 { + Timepoint::from_block_number(block_number) + } else { + let block_timestamp = block.raw().timestamp().unpack(); + Timepoint::from_timestamp(block_timestamp) + } + }; let to_custodian = |deposit| -> _ { - to_custodian_cell(rollup_context, &block_hash, block_number, deposit) + to_custodian_cell(rollup_context, &block_hash, &block_timepoint, deposit) .expect("sanitized deposit") }; @@ -157,10 +168,24 @@ impl BlockProducer { let smt = db.reverted_block_smt()?; smt.root().to_owned() }; + + // By applying finality mechanism based on L1 timestamp, we assign the L1 timestamp, + // obtained from a L1 header_dep, to GlobalState.last_finalized_timepoint. + // + // But L1 chain rollback makes things difficult. It must be a relation between + // GlobalState or l2block and L1 header_dep, while L1 rollback, the relation should + // be updated in time. + // + // Currently, we choose a L1 confirmed block header to be L1 header_dep, to avoid + // rollback. + let l1_confirmed_header_timestamp = + get_confirmed_header_timestamp(&self.rpc_client).await?; + let param = ProduceBlockParam { stake_cell_owner_lock_hash: self.wallet.lock_script().hash().into(), reverted_block_root, rollup_config_hash: self.rollup_config_hash, + l1_confirmed_header_timestamp, block_param, }; let db = self.store.begin_transaction(); @@ -220,6 +245,33 @@ impl BlockProducer { tx_skeleton .cell_deps_mut() .push(contracts_dep.omni_lock.clone().into()); + // header_deps, used for obtaining l1 time in the state-validator-script + let block_number = block.raw().number().unpack(); + if 2 <= rollup_context.determine_global_state_version(block_number) { + let finality_as_duration = rollup_context.rollup_config.finality_as_duration(); + let last_finalized_timestamp = match Timepoint::from_full_value( + global_state.last_finalized_block_number().unpack(), + ) { + Timepoint::Timestamp(last_finalized_timestamp) => last_finalized_timestamp, + Timepoint::BlockNumber(_) => { + unreachable!("2 <= global_state_version, last_finalized_timepoint should be timestamp-based"); + } + }; + let l1_confirmed_timestamp = + last_finalized_timestamp.saturating_add(finality_as_duration); + let l1_confirmed_header = + query_l1_header_by_timestamp(&self.rpc_client, l1_confirmed_timestamp) + .await? + .unwrap_or_else(|| { + panic!( + "get_l1_header_by_timestamp l1_timestamp={}, l2block={:#x}", + l1_confirmed_timestamp, block_number + ) + }); + tx_skeleton + .header_deps_mut() + .push(ckb_types::prelude::Unpack::unpack(&l1_confirmed_header.hash()).pack()); + } // Package pending revert withdrawals and custodians let db = { self.chain.lock().await.store().begin_transaction() }; @@ -321,16 +373,17 @@ impl BlockProducer { .outputs_mut() .push((generated_stake.output, generated_stake.output_data)); - let last_finalized_block_number = self - .generator - .rollup_context() - .last_finalized_block_number(block.raw().number().unpack() - 1); + let prev_global_state = global_state_from_slice(&rollup_cell.data)?; + let prev_compatible_finalized_timepoint = CompatibleFinalizedTimepoint::from_global_state( + &prev_global_state, + rollup_context.rollup_config.finality_blocks().unpack(), + ); let finalized_custodians = gw_mem_pool::custodian::query_finalized_custodians( rpc_client, &self.store.get_snapshot(), withdrawal_extras.iter().map(|w| w.request()), rollup_context, - last_finalized_block_number, + &prev_compatible_finalized_timepoint, local_cells_manager, ) .await? @@ -339,7 +392,7 @@ impl BlockProducer { local_cells_manager, rpc_client, finalized_custodians, - last_finalized_block_number, + &prev_compatible_finalized_timepoint, ) .await? .expect_any(); diff --git a/crates/block-producer/src/challenger.rs b/crates/block-producer/src/challenger.rs index 456773d90..2fc5143b2 100644 --- a/crates/block-producer/src/challenger.rs +++ b/crates/block-producer/src/challenger.rs @@ -446,7 +446,7 @@ impl Challenger { let burn_lock = self.config.challenger_config.burn_lock.clone().into(); let revert = Revert::new( - &self.rollup_context, + self.rollup_context.clone(), prev_state, &challenge_cell, &stake_cells, diff --git a/crates/block-producer/src/custodian.rs b/crates/block-producer/src/custodian.rs index 76beb3961..2c25ccf65 100644 --- a/crates/block-producer/src/custodian.rs +++ b/crates/block-producer/src/custodian.rs @@ -7,6 +7,8 @@ use gw_rpc_client::{ indexer_types::{Order, SearchKey, SearchKeyFilter}, rpc_client::{QueryResult, RPCClient}, }; +use gw_types::core::Timepoint; +use gw_types::offchain::CompatibleFinalizedTimepoint; use gw_types::{ core::ScriptHashType, offchain::{CellInfo, CollectedCustodianCells}, @@ -16,16 +18,14 @@ use gw_types::{ use gw_utils::local_cells::{ collect_local_and_indexer_cells, CollectLocalAndIndexerCursor, LocalCellsManager, }; -use tracing::instrument; pub const MAX_CUSTODIANS: usize = 50; -#[instrument(skip_all, fields(last_finalized_block_number = last_finalized_block_number))] pub async fn query_mergeable_custodians( local_cells_manager: &LocalCellsManager, rpc_client: &RPCClient, collected_custodians: CollectedCustodianCells, - last_finalized_block_number: u64, + compatible_finalized_timepoint: &CompatibleFinalizedTimepoint, ) -> Result> { if collected_custodians.cells_info.len() >= MAX_CUSTODIANS { return Ok(QueryResult::Full(collected_custodians)); @@ -35,7 +35,7 @@ pub async fn query_mergeable_custodians( local_cells_manager, rpc_client, collected_custodians, - last_finalized_block_number, + compatible_finalized_timepoint, MAX_CUSTODIANS, ) .await?; @@ -46,17 +46,16 @@ pub async fn query_mergeable_custodians( query_mergeable_sudt_custodians( rpc_client, query_result.expect_any(), - last_finalized_block_number, + compatible_finalized_timepoint, local_cells_manager, ) .await } -#[instrument(skip_all, fields(last_finalized_block_number = last_finalized_block_number))] async fn query_mergeable_sudt_custodians( rpc_client: &RPCClient, collected: CollectedCustodianCells, - last_finalized_block_number: u64, + compatible_finalized_timepoint: &CompatibleFinalizedTimepoint, local_cells_manager: &LocalCellsManager, ) -> Result> { if collected.cells_info.len() >= MAX_CUSTODIANS { @@ -67,7 +66,7 @@ async fn query_mergeable_sudt_custodians( local_cells_manager, rpc_client, collected, - last_finalized_block_number, + compatible_finalized_timepoint, MAX_CUSTODIANS, ) .await @@ -77,7 +76,7 @@ async fn query_mergeable_ckb_custodians( local_cells_manager: &LocalCellsManager, rpc_client: &RPCClient, mut collected: CollectedCustodianCells, - last_finalized_block_number: u64, + compatible_finalized_timepoint: &CompatibleFinalizedTimepoint, max_cells: usize, ) -> Result> { const MIN_MERGE_CELLS: usize = 5; @@ -135,9 +134,9 @@ async fn query_mergeable_ckb_custodians( Ok(r) => r, Err(_) => continue, }; - if custodian_lock_args_reader.deposit_block_number().unpack() - > last_finalized_block_number - { + if !compatible_finalized_timepoint.is_finalized(&Timepoint::from_full_value( + custodian_lock_args_reader.deposit_block_number().unpack(), + )) { continue; } @@ -171,7 +170,7 @@ pub async fn query_mergeable_sudt_custodians_cells( local_cells_manager: &LocalCellsManager, rpc_client: &RPCClient, mut collected: CollectedCustodianCells, - last_finalized_block_number: u64, + compatible_finalized_timepoint: &CompatibleFinalizedTimepoint, max_cells: usize, ) -> Result> { const MAX_MERGE_SUDTS: usize = 5; @@ -233,7 +232,7 @@ pub async fn query_mergeable_sudt_custodians_cells( }; let sudt_type_scripts = rpc_client - .query_random_sudt_type_script(last_finalized_block_number, MAX_MERGE_SUDTS) + .query_random_sudt_type_script(compatible_finalized_timepoint, MAX_MERGE_SUDTS) .await?; log::info!("merge {} random sudt type scripts", sudt_type_scripts.len()); let mut collected_set: HashSet<_> = { @@ -247,7 +246,7 @@ pub async fn query_mergeable_sudt_custodians_cells( let query_result = rpc_client .query_mergeable_sudt_custodians_cells_by_sudt_type_script( &sudt_type_script, - last_finalized_block_number, + compatible_finalized_timepoint, remain, &collected_set, ) diff --git a/crates/block-producer/src/produce_block.rs b/crates/block-producer/src/produce_block.rs index 0b88c6643..546b4cfb1 100644 --- a/crates/block-producer/src/produce_block.rs +++ b/crates/block-producer/src/produce_block.rs @@ -17,6 +17,7 @@ use gw_store::{ state::state_db::StateContext, traits::chain_store::ChainStore, transaction::StoreTransaction, Store, }; +use gw_types::core::Timepoint; use gw_types::{ core::Status, offchain::{BlockParam, DepositInfo, FinalizedCustodianCapacity}, @@ -41,6 +42,7 @@ pub struct ProduceBlockParam { pub stake_cell_owner_lock_hash: H256, pub reverted_block_root: H256, pub rollup_config_hash: H256, + pub l1_confirmed_header_timestamp: u64, pub block_param: BlockParam, } @@ -58,6 +60,7 @@ pub fn produce_block( stake_cell_owner_lock_hash, reverted_block_root, rollup_config_hash, + l1_confirmed_header_timestamp, block_param: BlockParam { number, @@ -155,18 +158,26 @@ pub fn produce_block( .count(block_count.pack()) .build() }; - let last_finalized_block_number = - number.saturating_sub(rollup_context.rollup_config.finality_blocks().unpack()); + + let last_finalized_timepoint = if rollup_context.determine_global_state_version(number) < 2 { + let finality_as_blocks = rollup_context.rollup_config.finality_as_blocks(); + Timepoint::from_block_number(number.saturating_sub(finality_as_blocks)) + } else { + let finality_as_duration = rollup_context.rollup_config.finality_as_duration(); + Timepoint::from_timestamp( + l1_confirmed_header_timestamp.saturating_sub(finality_as_duration), + ) + }; let global_state = GlobalState::new_builder() .account(post_merkle_state) .block(post_block) .tip_block_hash(block.hash().pack()) .tip_block_timestamp(block.raw().timestamp()) - .last_finalized_block_number(last_finalized_block_number.pack()) + .last_finalized_block_number(last_finalized_timepoint.full_value().pack()) .reverted_block_root(Into::<[u8; 32]>::into(reverted_block_root).pack()) .rollup_config_hash(rollup_config_hash.pack()) .status((Status::Running as u8).into()) - .version(1u8.into()) + .version(rollup_context.determine_global_state_version(number).into()) .build(); Ok(ProduceBlockResult { block, diff --git a/crates/block-producer/src/runner.rs b/crates/block-producer/src/runner.rs index b2fb2b8a1..8f6c962c6 100644 --- a/crates/block-producer/src/runner.rs +++ b/crates/block-producer/src/runner.rs @@ -300,6 +300,7 @@ impl BaseInitComponents { let rollup_script_hash: [u8; 32] = config.genesis.rollup_type_hash.clone().into(); rollup_script_hash.into() }, + timestamp_based_finality_fork_block: Some(config.fork_blocks.timestamp_based_finality), }; let rollup_type_script: Script = config.chain.rollup_type_script.clone().into(); let rpc_client = { @@ -574,7 +575,7 @@ pub async fn run(config: Config, skip_config_check: bool) -> Result<()> { } let chain = Arc::new(Mutex::new( Chain::create( - &rollup_config, + rollup_config.clone(), &config.chain.rollup_type_script.clone().into(), &config.chain, store.clone(), diff --git a/crates/block-producer/src/stake.rs b/crates/block-producer/src/stake.rs index fd6cad24c..966736026 100644 --- a/crates/block-producer/src/stake.rs +++ b/crates/block-producer/src/stake.rs @@ -9,6 +9,8 @@ use gw_rpc_client::{ indexer_types::{Order, SearchKey, SearchKeyFilter}, rpc_client::RPCClient, }; +use gw_types::core::Timepoint; +use gw_types::offchain::CompatibleFinalizedTimepoint; use gw_types::{ core::ScriptHashType, offchain::{CellInfo, InputCellInfo, RollupContext}, @@ -36,12 +38,20 @@ pub async fn generate( local_cells_manager: &LocalCellsManager, ) -> Result { let owner_lock_hash = lock_script.hash(); + let stake_timepoint = { + let block_number: u64 = block.raw().number().unpack(); + if rollup_context.determine_global_state_version(block_number) < 2 { + Timepoint::from_block_number(block_number) + } else { + let block_timestamp: u64 = block.raw().timestamp().unpack(); + Timepoint::from_timestamp(block_timestamp) + } + }; let lock_args: Bytes = { let stake_lock_args = StakeLockArgs::new_builder() .owner_lock_hash(owner_lock_hash.pack()) - .stake_block_number(block.raw().number()) + .stake_block_number(stake_timepoint.full_value().pack()) .build(); - let rollup_type_hash = rollup_context.rollup_script_hash.as_slice().iter(); rollup_type_hash .chain(stake_lock_args.as_slice().iter()) @@ -127,14 +137,14 @@ pub async fn generate( /// query stake /// -/// return cell which stake_block_number is less than last_finalized_block_number if the args isn't none -/// otherwise return stake cell randomly +/// Returns a finalized stake_state_cell if `compatible_finalize_timepoint_opt` is some, +/// otherwise returns a random stake_state_cell. pub async fn query_stake( client: &CKBIndexerClient, rollup_context: &RollupContext, owner_lock_hash: [u8; 32], required_staking_capacity: u64, - last_finalized_block_number: Option, + compatible_finalize_timepoint_opt: Option, local_cells_manager: &LocalCellsManager, ) -> Result> { let lock = Script::new_builder() @@ -169,10 +179,11 @@ pub async fn query_stake( Ok(r) => r, Err(_) => return false, }; - match last_finalized_block_number { - Some(last_finalized_block_number) => { - stake_lock_args.stake_block_number().unpack() <= last_finalized_block_number - && stake_lock_args.owner_lock_hash().as_slice() == owner_lock_hash + match &compatible_finalize_timepoint_opt { + Some(compatible_finalized_timepoint) => { + compatible_finalized_timepoint.is_finalized(&Timepoint::from_full_value( + stake_lock_args.stake_block_number().unpack(), + )) && stake_lock_args.owner_lock_hash().as_slice() == owner_lock_hash } None => stake_lock_args.owner_lock_hash().as_slice() == owner_lock_hash, } diff --git a/crates/block-producer/src/withdrawal.rs b/crates/block-producer/src/withdrawal.rs index 7ba2c6509..af07ceb2b 100644 --- a/crates/block-producer/src/withdrawal.rs +++ b/crates/block-producer/src/withdrawal.rs @@ -18,6 +18,7 @@ use gw_types::{ prelude::*, }; +use gw_types::offchain::CompatibleFinalizedTimepoint; use std::{ collections::HashMap, time::{SystemTime, UNIX_EPOCH}, @@ -224,13 +225,16 @@ pub fn unlock_to_owner( }; let global_state = global_state_from_slice(&rollup_cell.data)?; - let last_finalized_block_number: u64 = global_state.last_finalized_block_number().unpack(); + let compatible_finalized_timepoint = CompatibleFinalizedTimepoint::from_global_state( + &global_state, + rollup_context.rollup_config.finality_blocks().unpack(), + ); let l1_sudt_script_hash = rollup_context.rollup_config.l1_sudt_script_type_hash(); for withdrawal_cell in withdrawal_cells { // Double check if let Err(err) = gw_rpc_client::withdrawal::verify_unlockable_to_owner( &withdrawal_cell, - last_finalized_block_number, + &compatible_finalized_timepoint, &l1_sudt_script_hash, ) { log::error!("[unlock withdrawal] unexpected verify failed {}", err); @@ -301,8 +305,10 @@ mod test { use gw_common::{h256_ext::H256Ext, H256}; use gw_config::ContractsCellDep; - use gw_types::core::{DepType, ScriptHashType}; - use gw_types::offchain::{CellInfo, CollectedCustodianCells, InputCellInfo}; + use gw_types::core::{DepType, ScriptHashType, Timepoint}; + use gw_types::offchain::{ + CellInfo, CollectedCustodianCells, CompatibleFinalizedTimepoint, InputCellInfo, + }; use gw_types::packed::{ CellDep, CellInput, CellOutput, GlobalState, L2Block, OutPoint, RawL2Block, RawWithdrawalRequest, Script, UnlockWithdrawalViaFinalize, UnlockWithdrawalWitness, @@ -324,6 +330,7 @@ mod test { .withdrawal_script_type_hash(H256::from_u32(100).pack()) .finality_blocks(1u64.pack()) .build(), + ..Default::default() }; let sudt_script = Script::new_builder() @@ -386,16 +393,17 @@ mod test { .unwrap(); let (output, data) = generated.unwrap().outputs.first().unwrap().to_owned(); + let block_timepoint = Timepoint::from_block_number(block.raw().number().unpack()); let (expected_output, expected_data) = gw_generator::utils::build_withdrawal_cell_output( &rollup_context, &withdrawal_extra, &block.hash().into(), - block.raw().number().unpack(), + &block_timepoint, Some(sudt_script.clone()), ) .unwrap(); - assert_eq!(expected_output.as_slice(), output.as_slice()); + assert_eq!(expected_output.to_string(), output.to_string()); assert_eq!(expected_data, data); // Check our generate withdrawal can be queried and unlocked to owner @@ -404,11 +412,14 @@ mod test { data, ..Default::default() }; - let last_finalized_block_number = - block.raw().number().unpack() + rollup_context.rollup_config.finality_blocks().unpack(); + // Make sure the withdrawal is finalized for `global_state` + let compatible_finalized_timepoint = CompatibleFinalizedTimepoint::from_block_number( + block.raw().number().unpack(), + rollup_context.rollup_config.finality_blocks().unpack(), + ); gw_rpc_client::withdrawal::verify_unlockable_to_owner( &info, - last_finalized_block_number, + &compatible_finalized_timepoint, &sudt_script.code_hash(), ) .expect("pass verification"); @@ -417,9 +428,9 @@ mod test { #[test] fn test_unlock_to_owner() { // Output should only change lock to owner lock - let last_finalized_block_number = 100u64; + let last_finalized_timepoint = Timepoint::from_block_number(100); let global_state = GlobalState::new_builder() - .last_finalized_block_number(last_finalized_block_number.pack()) + .last_finalized_block_number(last_finalized_timepoint.full_value().pack()) .build(); let rollup_type = Script::new_builder() @@ -449,6 +460,7 @@ mod test { .l1_sudt_script_type_hash(sudt_script.code_hash()) .finality_blocks(1u64.pack()) .build(), + ..Default::default() }; let contracts_dep = { @@ -481,7 +493,7 @@ mod test { let withdrawal_without_owner_lock = { let lock_args = WithdrawalLockArgs::new_builder() .owner_lock_hash(owner_lock.hash().pack()) - .withdrawal_block_number((last_finalized_block_number - 1).pack()) + .withdrawal_block_number(last_finalized_timepoint.full_value().pack()) .build(); let mut args = rollup_type.hash().to_vec(); @@ -497,7 +509,7 @@ mod test { let withdrawal_with_owner_lock = { let lock_args = WithdrawalLockArgs::new_builder() .owner_lock_hash(owner_lock.hash().pack()) - .withdrawal_block_number((last_finalized_block_number - 1).pack()) + .withdrawal_block_number(last_finalized_timepoint.full_value().pack()) .build(); let mut args = rollup_type.hash().to_vec(); diff --git a/crates/block-producer/src/withdrawal_unlocker.rs b/crates/block-producer/src/withdrawal_unlocker.rs index 0b82d755d..d715f46d5 100644 --- a/crates/block-producer/src/withdrawal_unlocker.rs +++ b/crates/block-producer/src/withdrawal_unlocker.rs @@ -9,9 +9,11 @@ use gw_common::H256; use gw_config::{ContractsCellDep, DebugConfig}; use gw_rpc_client::contract::ContractsCellDepManager; use gw_rpc_client::rpc_client::RPCClient; -use gw_types::offchain::{global_state_from_slice, CellInfo, RollupContext, TxStatus}; +use gw_types::offchain::{ + global_state_from_slice, CellInfo, CompatibleFinalizedTimepoint, RollupContext, TxStatus, +}; use gw_types::packed::{OutPoint, Transaction}; -use gw_types::prelude::{Pack, Unpack}; +use gw_types::prelude::*; use gw_utils::fee::fill_tx_fee; use gw_utils::genesis_info::CKBGenesisInfo; use gw_utils::transaction_skeleton::TransactionSkeleton; @@ -153,7 +155,7 @@ pub trait BuildUnlockWithdrawalToOwner { async fn query_unlockable_withdrawals( &self, - last_finalized_block_number: u64, + compatible_finalized_timepoint: &CompatibleFinalizedTimepoint, unlocked: &HashSet, ) -> Result>; @@ -171,10 +173,14 @@ pub trait BuildUnlockWithdrawalToOwner { } }; + let rollup_context = self.rollup_context(); let global_state = global_state_from_slice(&rollup_cell.data)?; - let last_finalized_block_number: u64 = global_state.last_finalized_block_number().unpack(); + let compatible_finalized_timepoint = CompatibleFinalizedTimepoint::from_global_state( + &global_state, + rollup_context.rollup_config.finality_blocks().unpack(), + ); let unlockable_withdrawals = self - .query_unlockable_withdrawals(last_finalized_block_number, unlocked) + .query_unlockable_withdrawals(&compatible_finalized_timepoint, unlocked) .await?; log::info!( "[unlock withdrawal] find unlockable finalized withdrawals {}", @@ -247,12 +253,12 @@ impl BuildUnlockWithdrawalToOwner for DefaultUnlocker { async fn query_unlockable_withdrawals( &self, - last_finalized_block_number: u64, + compatible_finalized_timepoint: &CompatibleFinalizedTimepoint, unlocked: &HashSet, ) -> Result> { self.rpc_client .query_finalized_owner_lock_withdrawal_cells( - last_finalized_block_number, + compatible_finalized_timepoint, unlocked, Self::MAX_WITHDRAWALS_PER_TX, ) diff --git a/crates/chain/src/chain.rs b/crates/chain/src/chain.rs index d5d568a41..25be8e5f8 100644 --- a/crates/chain/src/chain.rs +++ b/crates/chain/src/chain.rs @@ -27,6 +27,7 @@ use gw_types::{ }, prelude::{Builder as GWBuilder, Entity as GWEntity, Pack as GWPack, Unpack as GWUnpack}, }; +use gw_utils::calc_finalizing_range; use std::{collections::HashSet, convert::TryFrom, sync::Arc, time::Instant}; use tokio::sync::Mutex; use tracing::instrument; @@ -139,7 +140,7 @@ impl LocalState { pub struct Chain { rollup_type_script_hash: [u8; 32], - rollup_config_hash: [u8; 32], + rollup_config: RollupConfig, store: Store, challenge_target: Option, last_sync_event: SyncEvent, @@ -151,7 +152,7 @@ pub struct Chain { impl Chain { pub fn create( - rollup_config: &RollupConfig, + rollup_config: RollupConfig, rollup_type_script: &Script, config: &ChainConfig, store: Store, @@ -160,7 +161,7 @@ impl Chain { ) -> Result { // convert serde types to gw-types assert_eq!( - rollup_config, + &rollup_config, &generator.rollup_context().rollup_config, "check generator rollup config" ); @@ -178,7 +179,6 @@ impl Chain { tip, last_global_state, }; - let rollup_config_hash = rollup_config.hash(); let skipped_invalid_block_list = config .skipped_invalid_block_list .iter() @@ -196,7 +196,7 @@ impl Chain { generator, mem_pool, rollup_type_script_hash, - rollup_config_hash, + rollup_config, skipped_invalid_block_list, }) } @@ -218,8 +218,8 @@ impl Chain { &self.generator } - pub fn rollup_config_hash(&self) -> &[u8; 32] { - &self.rollup_config_hash + pub fn rollup_config_hash(&self) -> [u8; 32] { + self.rollup_config.hash() } pub fn rollup_type_script_hash(&self) -> &[u8; 32] { @@ -571,6 +571,11 @@ impl Chain { Ok(()) } + /// Calculate and store the finalized_custodian_capacity for block block_number. + /// + /// Initialize by the block parent's finalized_custodian_capacity; + /// increase with finalizing deposit requests after applying this block; + /// decrease with the block's withdrawals. pub fn calculate_and_store_finalized_custodians( &mut self, db: &StoreTransaction, @@ -579,37 +584,44 @@ impl Chain { let block_hash = db .get_block_hash_by_number(block_number)? .context("get block hash")?; - let withdrawals = db - .get_block(&block_hash)? - .context("get block")? - .withdrawals(); + let finalizing_range = db + .get_block_finalizing_range(&block_hash) + .context("get block finalizing range")?; + let finalizing_from_number: u64 = finalizing_range.from_block_number().unpack(); + let finalizing_to_number: u64 = finalizing_range.to_block_number().unpack(); + if finalizing_from_number == finalizing_to_number { + return Ok(()); + } let mut finalized_custodians = db .get_block_post_finalized_custodian_capacity(block_number - 1) .context("get parent block remaining finalized custodians")? .as_reader() .unpack(); - let last_finalized_block = self - .generator - .rollup_context() - .last_finalized_block_number(block_number - 1); - let deposits = db - .get_block_deposit_info_vec(last_finalized_block) - .context("get last finalized block deposit")?; - for deposit in deposits { - let deposit = deposit.request(); - finalized_custodians.capacity = finalized_custodians - .capacity - .checked_add(deposit.capacity().unpack().into()) - .context("add capacity overflow")?; - finalized_custodians - .checked_add_sudt( - deposit.sudt_script_hash().unpack(), - deposit.amount().unpack(), - deposit.script(), - ) - .context("add sudt overflow")?; + for finalizing_number in finalizing_from_number + 1..finalizing_to_number + 1 { + let deposits = db + .get_block_deposit_info_vec(finalizing_number) + .context("get finalizing block deposit info vec")?; + for deposit in deposits { + let deposit = deposit.request(); + finalized_custodians.capacity = finalized_custodians + .capacity + .checked_add(deposit.capacity().unpack().into()) + .context("add capacity overflow")?; + finalized_custodians + .checked_add_sudt( + deposit.sudt_script_hash().unpack(), + deposit.amount().unpack(), + deposit.script(), + ) + .context("add sudt overflow")?; + } } + + let withdrawals = db + .get_block(&block_hash)? + .context("get block")? + .withdrawals(); for w in withdrawals.as_reader().iter() { finalized_custodians.capacity = finalized_custodians .capacity @@ -624,6 +636,7 @@ impl Chain { .context("withdrawal not enough sudt amount")?; } } + db.set_block_post_finalized_custodian_capacity( block_number, &finalized_custodians.pack().as_reader(), @@ -1045,6 +1058,12 @@ impl Chain { )?; db.insert_asset_scripts(deposit_asset_scripts)?; db.attach_block(l2block.clone())?; + + { + let finalizing_range = calc_finalizing_range(&self.rollup_config, db, &l2block)?; + db.set_block_finalizing_range(&l2block.hash().into(), &finalizing_range.as_reader())?; + } + self.local_state.tip = l2block; self.local_state.last_global_state = global_state; Ok(None) diff --git a/crates/challenge/src/offchain/mock_block.rs b/crates/challenge/src/offchain/mock_block.rs index c1815cd57..e995f53ba 100644 --- a/crates/challenge/src/offchain/mock_block.rs +++ b/crates/challenge/src/offchain/mock_block.rs @@ -17,7 +17,7 @@ use gw_generator::traits::StateExt; use gw_store::state::mem_state_db::MemStateTree; use gw_store::transaction::StoreTransaction; use gw_traits::CodeStore; -use gw_types::core::{ChallengeTargetType, Status}; +use gw_types::core::{ChallengeTargetType, Status, Timepoint}; use gw_types::offchain::{RollupContext, RunResult}; use gw_types::packed::{ AccountMerkleState, BlockMerkleState, Byte32, Bytes, CCTransactionSignatureWitness, @@ -321,13 +321,20 @@ impl MockBlockParam { .build() }; - let last_finalized_block_number = self.number.saturating_sub(self.finality_blocks); - + let last_finalized_timepoint = if self + .rollup_context + .determine_global_state_version(self.number) + < 2 + { + Timepoint::from_block_number(self.number.saturating_sub(self.finality_blocks)) + } else { + Timepoint::unset() + }; let global_state = GlobalState::new_builder() .account(post_account) .block(post_block) .tip_block_hash(raw_block.hash().pack()) - .last_finalized_block_number(last_finalized_block_number.pack()) + .last_finalized_block_number(last_finalized_timepoint.full_value().pack()) .reverted_block_root(self.reverted_block_root.clone()) .rollup_config_hash(self.rollup_config_hash.clone()) .status((Status::Halting as u8).into()) diff --git a/crates/challenge/src/revert.rs b/crates/challenge/src/revert.rs index cfac0a868..c0a911dbf 100644 --- a/crates/challenge/src/revert.rs +++ b/crates/challenge/src/revert.rs @@ -5,7 +5,7 @@ use ckb_types::prelude::Reader; use ckb_types::prelude::{Builder, Entity}; use gw_common::smt::Blake2bHasher; use gw_common::H256; -use gw_types::core::Status; +use gw_types::core::{Status, Timepoint}; use gw_types::offchain::{CellInfo, RollupContext}; use gw_types::packed::BlockMerkleState; use gw_types::packed::ChallengeLockArgsReader; @@ -19,6 +19,7 @@ use gw_types::prelude::Unpack; use gw_types::{bytes::Bytes, prelude::Pack}; pub struct Revert<'a> { + rollup_context: RollupContext, finality_blocks: u64, reward_burn_rate: u8, prev_global_state: GlobalState, @@ -38,7 +39,7 @@ pub struct RevertOutput { impl<'a> Revert<'a> { pub fn new( - rollup_context: &RollupContext, + rollup_context: RollupContext, prev_global_state: GlobalState, challenge_cell: &'a CellInfo, stake_cells: &'a [CellInfo], @@ -49,6 +50,7 @@ impl<'a> Revert<'a> { let finality_blocks = rollup_context.rollup_config.finality_blocks().unpack(); Revert { + rollup_context, finality_blocks, prev_global_state, challenge_cell, @@ -94,11 +96,20 @@ impl<'a> Revert<'a> { .count(block_count) .build() }; - let last_finalized_block_number = { - let number = first_reverted_block.number().unpack(); - number - .saturating_sub(1) - .saturating_sub(self.finality_blocks) + let last_finalized_timepoint = if self + .rollup_context + .determine_global_state_version(first_reverted_block.number().unpack()) + < 2 + { + Timepoint::from_block_number( + first_reverted_block + .number() + .unpack() + .saturating_sub(1) + .saturating_sub(self.finality_blocks), + ) + } else { + Timepoint::unset() }; let running_status: u8 = Status::Running.into(); @@ -109,7 +120,7 @@ impl<'a> Revert<'a> { .block(block_merkle_state) .tip_block_hash(first_reverted_block.parent_block_hash()) .tip_block_timestamp(self.revert_witness.new_tip_block.timestamp()) - .last_finalized_block_number(last_finalized_block_number.pack()) + .last_finalized_block_number(last_finalized_timepoint.full_value().pack()) .reverted_block_root(self.post_reverted_block_root.pack()) .status(running_status.into()) .build(); diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 1700688bf..b0dc4481b 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -25,6 +25,8 @@ pub struct Config { #[serde(default)] pub contract_log_config: ContractLogConfig, pub backend_switches: Vec, + #[serde(default)] + pub fork_blocks: ForkBlocksConfig, pub genesis: GenesisConfig, pub chain: ChainConfig, pub rpc_client: RPCClientConfig, @@ -256,6 +258,17 @@ pub struct BackendConfig { pub backend_type: BackendType, } +/// Hard-fork changes and activation heights. +#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ForkBlocksConfig { + /// Use L1 timestamp to check finality + /// + /// See also https://talk.nervos.org/t/optimize-godwoken-finality-and-on-chain-cost/6739 + /// + /// NOTE: By applying this change, we will bump global_state.version from 1 to 2. + pub timestamp_based_finality: u64, +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct DebugConfig { pub output_l1_tx_cycles: bool, diff --git a/crates/db/src/schema.rs b/crates/db/src/schema.rs index cceb98abb..e6725545d 100644 --- a/crates/db/src/schema.rs +++ b/crates/db/src/schema.rs @@ -3,7 +3,7 @@ /// Column families alias type pub type Col = u8; /// Total column number -pub const COLUMNS: u32 = 37; +pub const COLUMNS: u32 = 38; /// Column store meta data pub const COLUMN_META: Col = 0; /// Column store chain index @@ -70,6 +70,8 @@ pub const COLUMN_BLOCK_SUBMIT_TX_HASH: Col = 7; pub const COLUMN_BLOCK_DEPOSIT_INFO_VEC: Col = 16; /// block number (in big endian) -> FinalizedCustodianCapacity. pub const COLUMN_BLOCK_POST_FINALIZED_CUSTODIAN_CAPACITY: Col = 36; +/// block hash -> finalizing block number range +pub const COLUMN_BLOCK_FINALIZING_RANGE: Col = 37; /// chain id pub const META_CHAIN_ID_KEY: &[u8] = b"CHAIN_ID"; diff --git a/crates/generator/src/account_lock_manage/secp256k1.rs b/crates/generator/src/account_lock_manage/secp256k1.rs index 13f30682f..53628b4de 100644 --- a/crates/generator/src/account_lock_manage/secp256k1.rs +++ b/crates/generator/src/account_lock_manage/secp256k1.rs @@ -346,6 +346,7 @@ mod tests { rollup_config: RollupConfig::new_builder() .chain_id(chain_id.pack()) .build(), + ..Default::default() }; eth.verify_tx(&ctx, sender_address, sender_script, receiver_script, tx) .expect("verify signature"); @@ -409,6 +410,7 @@ mod tests { rollup_config: RollupConfig::new_builder() .chain_id(chain_id.pack()) .build(), + ..Default::default() }; let eth = Secp256k1Eth::default(); eth.verify_tx(&ctx, sender_reg_addr, sender_script, receive_script, tx) @@ -476,6 +478,7 @@ mod tests { rollup_config: RollupConfig::new_builder() .chain_id(chain_id.pack()) .build(), + ..Default::default() }; eth.verify_tx(&ctx, sender_address, sender_script, receiver_script, tx) @@ -536,6 +539,7 @@ mod tests { rollup_config: RollupConfig::new_builder() .chain_id(chain_id.pack()) .build(), + ..Default::default() }; eth.verify_tx(&ctx, sender_address, sender_script, receiver_script, tx) .expect("verify signature"); @@ -581,6 +585,7 @@ mod tests { rollup_config: RollupConfig::new_builder() .chain_id(chain_id.pack()) .build(), + ..Default::default() }; eth.verify_tx(&ctx, sender_address, sender_script, receiver_script, tx) .expect("verify signature"); @@ -643,6 +648,7 @@ mod tests { let ctx = RollupContext { rollup_script_hash: Default::default(), rollup_config: RollupConfig::new_builder().chain_id(0.pack()).build(), + ..Default::default() }; let eth = Secp256k1Eth::default(); eth.verify_tx(&ctx, sender_address, sender_script, receiver_script, tx) diff --git a/crates/generator/src/genesis.rs b/crates/generator/src/genesis.rs index 991d8c6b7..e9d0e4950 100644 --- a/crates/generator/src/genesis.rs +++ b/crates/generator/src/genesis.rs @@ -52,6 +52,7 @@ pub fn build_genesis_from_store( rollup_script_hash.into() }, rollup_config: config.rollup_config.clone().into(), + ..Default::default() }; // initialize store db.set_block_smt_root(H256::zero())?; diff --git a/crates/generator/src/utils.rs b/crates/generator/src/utils.rs index fe6bf690a..e943eb0e2 100644 --- a/crates/generator/src/utils.rs +++ b/crates/generator/src/utils.rs @@ -3,6 +3,7 @@ use std::convert::TryInto; use gw_common::{builtins::CKB_SUDT_ACCOUNT_ID, state::State, H256}; use gw_config::BackendType; use gw_traits::CodeStore; +use gw_types::core::Timepoint; use gw_types::{ bytes::Bytes, core::{AllowedContractType, ScriptHashType}, @@ -43,7 +44,7 @@ pub fn build_withdrawal_cell_output( rollup_context: &RollupContext, req: &WithdrawalRequestExtra, block_hash: &H256, - block_number: u64, + block_timepoint: &Timepoint, opt_asset_script: Option