diff --git a/Cargo.lock b/Cargo.lock index 5c9af3b7..74c3edaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,6 +776,7 @@ dependencies = [ "chrono", "ckb-async-runtime", "ckb-chain-spec", + "ckb-constant", "ckb-jsonrpc-types", "ckb-light-client-lib", "ckb-network", diff --git a/light-client-bin/Cargo.toml b/light-client-bin/Cargo.toml index 6772c556..e0beef4e 100644 --- a/light-client-bin/Cargo.toml +++ b/light-client-bin/Cargo.toml @@ -18,6 +18,7 @@ ckb-jsonrpc-types = "1" ckb-types = "1" ckb-traits = "1" ckb-systemtime = "1" +ckb-constant = "1" ckb-light-client-lib = { path = "../light-client-lib" } clap = { version = "4", features = ["cargo"] } diff --git a/light-client-bin/src/rpc.rs b/light-client-bin/src/rpc.rs index 6522057f..2a1f148f 100644 --- a/light-client-bin/src/rpc.rs +++ b/light-client-bin/src/rpc.rs @@ -23,6 +23,7 @@ use ckb_light_client_lib::{ }; use ckb_chain_spec::consensus::Consensus; +use ckb_constant::hardfork::{mainnet, testnet}; use ckb_jsonrpc_types::{ BlockView, EstimateCycles, HeaderView, JsonBytes, NodeAddress, RemoteNodeProtocol, Transaction, Uint32, @@ -804,13 +805,32 @@ impl NetRpc for NetRpcImpl { impl TransactionRpc for TransactionRpcImpl { fn send_transaction(&self, tx: Transaction) -> Result { + // Check sync status: reject if local tip hasn't passed the latest hardfork activation epoch + let local_tip_header = self.swc.storage().get_last_state().1.into_view(); + let local_tip_epoch = local_tip_header.epoch().number(); + + // Determine the required hardfork epoch based on network + let required_hardfork_epoch = match self.consensus.id.as_str() { + mainnet::CHAIN_SPEC_NAME => mainnet::CKB2023_START_EPOCH, + testnet::CHAIN_SPEC_NAME => testnet::CKB2023_START_EPOCH, + _ => 0, // For other networks (dev/integration tests), no hardfork check + }; + + if local_tip_epoch < required_hardfork_epoch { + return Err(Error::invalid_params(format!( + "light client is not synced past hardfork: local tip epoch {} < required hardfork epoch {}", + local_tip_epoch, + required_hardfork_epoch + ))); + } + let tx: packed::Transaction = tx.into(); let tx = tx.into_view(); let cycles = verify_tx( tx.clone(), &self.swc, Arc::clone(&self.consensus), - &self.swc.storage().get_last_state().1.into_view(), + &local_tip_header, ) .map_err(|e| Error::invalid_params(format!("invalid transaction: {:?}", e)))?; self.swc