From 4b8be9e1b64784e79cf00545db3dd15d9e858919 Mon Sep 17 00:00:00 2001 From: Lightman <915311741@qq.com> Date: Tue, 6 Jan 2026 22:36:17 +0800 Subject: [PATCH 1/2] [improve] Support mint precompile --- Cargo.lock | 9 +- Cargo.toml | 5 +- .../pipe-exec-layer-ext-v2/execute/Cargo.toml | 2 + .../pipe-exec-layer-ext-v2/execute/src/lib.rs | 360 +++++++++++++----- .../execute/src/mint_precompile.rs | 156 ++++++++ .../src/onchain_config/metadata_txn.rs | 109 ++++-- .../execute/src/onchain_config/mod.rs | 10 +- 7 files changed, 515 insertions(+), 136 deletions(-) create mode 100644 crates/pipe-exec-layer-ext-v2/execute/src/mint_precompile.rs diff --git a/Cargo.lock b/Cargo.lock index 2c6e6bc1c..455164f3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1032,7 +1032,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "api-types" version = "0.1.0" -source = "git+https://github.com/Galxe/gravity-aptos?rev=7b2d7949583169cc5c997856b4a0d17fec56ebf6#7b2d7949583169cc5c997856b4a0d17fec56ebf6" +source = "git+https://github.com/Galxe/gravity-aptos?rev=977f5b9388183c8a14c0ddcb4e2ac9f265d45184#977f5b9388183c8a14c0ddcb4e2ac9f265d45184" dependencies = [ "anyhow", "async-trait", @@ -9987,9 +9987,11 @@ dependencies = [ "eyre", "gravity-primitives", "gravity-storage", + "grevm", "hex", "metrics", "once_cell", + "parking_lot", "rand 0.9.2", "rayon", "reth-chain-state", @@ -14803,3 +14805,8 @@ dependencies = [ "cc", "pkg-config", ] + +[[patch.unused]] +name = "alloy-tx-macros" +version = "1.0.37" +source = "git+https://github.com/alloy-rs/alloy?tag=v1.0.37#8f6c1489f89e90649dac378008d8913e3b8a4c98" diff --git a/Cargo.toml b/Cargo.toml index ccec43e0b..dd77bfd26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -377,7 +377,7 @@ codegen-units = 1 [workspace.dependencies] # reth -gravity-api-types = { package = "api-types", git = "https://github.com/Galxe/gravity-aptos", rev = "7b2d7949583169cc5c997856b4a0d17fec56ebf6" } +gravity-api-types = { package = "api-types", git = "https://github.com/Galxe/gravity-aptos", rev = "977f5b9388183c8a14c0ddcb4e2ac9f265d45184" } op-reth = { path = "crates/optimism/bin" } reth = { path = "bin/reth" } reth-storage-rpc-provider = { path = "crates/storage/rpc-provider" } @@ -773,7 +773,8 @@ visibility = "0.1.1" walkdir = "2.3.3" vergen-git2 = "9.1.0" -# [patch.crates-io] +[patch.crates-io] +alloy-tx-macros = { git = "https://github.com/alloy-rs/alloy", tag = "v1.0.37" } # alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } diff --git a/crates/pipe-exec-layer-ext-v2/execute/Cargo.toml b/crates/pipe-exec-layer-ext-v2/execute/Cargo.toml index f578e4b61..b98fc5523 100644 --- a/crates/pipe-exec-layer-ext-v2/execute/Cargo.toml +++ b/crates/pipe-exec-layer-ext-v2/execute/Cargo.toml @@ -29,6 +29,7 @@ reth-rpc-eth-api.workspace = true revm-primitives.workspace = true gravity-storage.workspace = true gravity-primitives.workspace = true +grevm.workspace = true alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-eips.workspace = true @@ -43,6 +44,7 @@ revm.workspace = true # misc tracing.workspace = true +parking_lot.workspace = true reth-metrics.workspace = true metrics.workspace = true serde_json.workspace = true diff --git a/crates/pipe-exec-layer-ext-v2/execute/src/lib.rs b/crates/pipe-exec-layer-ext-v2/execute/src/lib.rs index 076574f4c..a71f4cd38 100644 --- a/crates/pipe-exec-layer-ext-v2/execute/src/lib.rs +++ b/crates/pipe-exec-layer-ext-v2/execute/src/lib.rs @@ -2,6 +2,7 @@ #[macro_use] mod channel; mod metrics; +pub mod mint_precompile; pub mod onchain_config; use alloy_sol_types::SolEvent; @@ -23,11 +24,12 @@ use alloy_primitives::{ }; use alloy_rpc_types_eth::TransactionRequest; use gravity_primitives::get_gravity_config; +use grevm::ParallelState; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use reth_chain_state::{ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates}; use reth_chainspec::{ChainSpec, EthereumHardforks}; use reth_ethereum_primitives::{Block, BlockBody, Receipt, TransactionSigned}; -use reth_evm::{ConfigureEvm, NextBlockEnvAttributes, ParallelDatabase}; +use reth_evm::{ConfigureEvm, Evm, NextBlockEnvAttributes, ParallelDatabase}; use reth_evm_ethereum::EthEvmConfig; use reth_execution_types::{BlockExecutionOutput, ExecutionOutcome}; use reth_pipe_exec_layer_event_bus::{ @@ -44,7 +46,7 @@ use reth_rpc_eth_api::RpcTypes; use revm::{ database::{states::bundle_state::BundleRetention, State}, state::AccountInfo, - DatabaseCommit, + Database, DatabaseCommit, }; use std::{ collections::BTreeMap, @@ -56,7 +58,7 @@ use std::{ }; use gravity_storage::GravityStorage; -use onchain_config::{transact_metadata_contract_call, OnchainConfigFetcher}; +use onchain_config::OnchainConfigFetcher; use reth_rpc_eth_api::helpers::EthCall; use reth_trie::{HashedPostState, KeccakKeyHasher}; use tokio::sync::{ @@ -65,12 +67,15 @@ use tokio::sync::{ }; use tracing::*; -use crate::onchain_config::{ - construct_validator_txns_envelope, - dkg::{convert_dkg_start_event_to_api, DKGStartEvent}, - observed_jwk::convert_into_api_provider_jwks, - types::ObservedJWKsUpdated, - SYSTEM_CALLER, +use crate::{ + mint_precompile::create_mint_token_precompile, + onchain_config::{ + construct_metadata_txn, construct_validator_txn_from_extra_data, + dkg::{convert_dkg_start_event_to_api, DKGStartEvent}, + observed_jwk::convert_into_api_provider_jwks, + types::ObservedJWKsUpdated, + transact_system_txn, SystemTxnResult, NATIVE_MINT_PRECOMPILE_ADDR, SYSTEM_CALLER, + }, }; /// Metadata about an executed block @@ -276,6 +281,21 @@ struct ExecuteOrderedBlockResult { epoch: u64, } +/// Result of system transaction execution +/// +/// System transactions may trigger an epoch change, which requires early return. +/// This enum represents both outcomes. +enum SystemTxnExecutionOutcome { + /// Normal execution completed, continue with block processing + Continue { + metadata_result: SystemTxnResult, + accumulated_state_changes: revm::state::EvmState, + validator_results: Vec, + }, + /// Epoch changed, return early with the result + EpochChanged(ExecuteOrderedBlockResult), +} + impl Core { fn epoch(&self) -> u64 { self.epoch.load(Ordering::Acquire) @@ -566,6 +586,195 @@ impl Core { (RecoveredBlock::new_unhashed(block, senders), txs_info) } + /// Execute all system transactions (metadata, DKG, JWK) sequentially + /// + /// This function encapsulates the execution of all system-level transactions + /// that must be processed before the parallel user transaction execution. + /// + /// Returns `SystemTxnExecutionOutcome::EpochChanged` if a new epoch was triggered, + /// otherwise returns `SystemTxnExecutionOutcome::Continue` with the results. + fn execute_system_transactions( + evm_config: &EthEvmConfig, + chain_spec: &ChainSpec, + state: &Arc, + evm_env: reth_evm::EvmEnv, + ordered_block: &OrderedBlock, + base_fee: u64, + epoch: u64, + block_id: B256, + parent_id: B256, + block_number: u64, + ) -> SystemTxnExecutionOutcome { + let mut inner_state = + State::builder().with_database_ref(state).with_bundle_update().build(); + let mut evm = evm_config.evm_with_env(&mut inner_state, evm_env); + + // Create shared state for precompile - keep a reference so we can extract changes later + let state_for_precompile = { + let report_db_metrics = get_gravity_config().report_db_metrics; + let parallel_state = ParallelState::new(state.clone(), true, report_db_metrics); + Arc::new(parking_lot::Mutex::new(parallel_state)) + }; + let state_for_precompile_ref = state_for_precompile.clone(); + let precompile = create_mint_token_precompile(state_for_precompile); + evm.precompiles_mut() + .apply_precompile(&NATIVE_MINT_PRECOMPILE_ADDR, move |_| Some(precompile)); + + // Get system caller nonce and gas price for constructing all system transactions + let system_call_account = + evm.db_mut().basic(SYSTEM_CALLER).unwrap().expect("SYSTEM_CALLER not exists"); + let gas_price = evm.block().basefee as u128; + let mut current_nonce = system_call_account.nonce; + // Construct and execute metadata transaction using unified entry point + let metadata_txn = construct_metadata_txn( + current_nonce, + gas_price, + ordered_block.timestamp_us, + ordered_block.proposer_index, + ); + current_nonce = metadata_txn.nonce() + 1; + + let (metadata_txn_result, metadata_state_changes) = + transact_system_txn(&mut evm, metadata_txn); + + // Commit metadata state changes immediately so subsequent txns see nonce update + evm.db_mut().commit(metadata_state_changes.clone()); + + // Accumulate state changes for returning to executor + let mut accumulated_state_changes = metadata_state_changes; + + // Check for epoch change + if let Some((new_epoch, validators)) = metadata_txn_result.emit_new_epoch() { + drop(evm); + assert_eq!(new_epoch, epoch + 1); + info!(target: "execute_ordered_block", + id=?block_id, + parent_id=?parent_id, + number=?block_number, + new_epoch=?new_epoch, + "emit new epoch, discard the block" + ); + inner_state.merge_transitions(BundleRetention::Reverts); + return SystemTxnExecutionOutcome::EpochChanged( + metadata_txn_result.into_executed_ordered_block_result( + chain_spec, + ordered_block, + base_fee, + inner_state.take_bundle(), + validators, + ), + ); + } + + debug!(target: "execute_ordered_block", + metadata_txn_result=?metadata_txn_result, + "metadata transaction result" + ); + + // Execute validator transactions (DKG and JWK) one by one + // DKG transactions are executed first since they may trigger epoch changes + let mut validator_txn_results: Vec = Vec::new(); + + // Sort extra_data: DKG first, then JWK + let mut sorted_extra_data: Vec<_> = ordered_block.extra_data.iter().collect(); + sorted_extra_data.sort_by_key(|data| match data { + ExtraDataType::DKG(_) => 0, // DKG comes first + ExtraDataType::JWK(_) => 1, // JWK comes second + }); + + for (index, extra_data) in sorted_extra_data.iter().enumerate() { + let is_dkg = matches!(extra_data, ExtraDataType::DKG(_)); + let txn = construct_validator_txn_from_extra_data(extra_data, current_nonce, gas_price) + .expect("Failed to construct validator transaction"); + current_nonce += 1; + + debug!(target: "execute_ordered_block", + index=?index, + nonce=?current_nonce, + is_dkg=?is_dkg, + "executing validator transaction one by one" + ); + + let (validator_result, validator_state_changes) = transact_system_txn(&mut evm, txn); + + // Commit state changes immediately so subsequent txns see nonce update + evm.db_mut().commit(validator_state_changes.clone()); + + // Merge state changes into accumulated changes + for (addr, account) in validator_state_changes { + accumulated_state_changes.insert(addr, account); + } + + // DKG transactions may trigger epoch change + if is_dkg { + if let Some((new_epoch, validators)) = validator_result.emit_new_epoch() { + drop(evm); + assert_eq!(new_epoch, epoch + 1); + info!(target: "execute_ordered_block", + id=?block_id, + parent_id=?parent_id, + number=?block_number, + new_epoch=?new_epoch, + "DKG triggered new epoch, discard the block" + ); + inner_state.merge_transitions(BundleRetention::Reverts); + return SystemTxnExecutionOutcome::EpochChanged( + validator_result.into_executed_ordered_block_result( + chain_spec, + ordered_block, + base_fee, + inner_state.take_bundle(), + validators, + ), + ); + } + } + + info!(target: "execute_ordered_block", + index=?index, + gas_used=?validator_result.result.gas_used(), + "validator transaction executed successfully" + ); + + validator_txn_results.push(validator_result); + } + + drop(evm); + + // Extract changes from precompile state and merge into accumulated_state_changes + { + let mut precompile_state = state_for_precompile_ref.lock(); + precompile_state.merge_transitions(BundleRetention::Reverts); + let precompile_bundle = precompile_state.take_bundle(); + + // Convert BundleState to EvmState and merge + for (address, account) in precompile_bundle.state { + if let Some(info) = account.info { + use revm::state::{Account, AccountStatus, EvmStorageSlot}; + accumulated_state_changes.insert( + address, + Account { + info, + storage: account + .storage + .into_iter() + .map(|(k, v)| (k, EvmStorageSlot::new(v.present_value, 0))) + .collect(), + status: AccountStatus::Touched, + transaction_id: 0, + }, + ); + } + } + } + + SystemTxnExecutionOutcome::Continue { + metadata_result: metadata_txn_result, + accumulated_state_changes, + validator_results: validator_txn_results, + } + } + /// Extract gravity events from execution receipts /// Returns (gravity_events, epoch_change_result) where epoch_change_result is Some((new_epoch, /// validators)) if epoch changed @@ -573,9 +782,8 @@ impl Core { &self, receipts: &[Receipt], block_number: u64, - ) -> (Vec, Option<(u64, Bytes)>) { + ) -> Vec { let mut gravity_events = vec![]; - let mut epoch_change_result = None; for receipt in receipts { debug!(target: "execute_ordered_block", @@ -584,20 +792,6 @@ impl Core { "extract gravity events from receipt" ); for log in &receipt.logs { - // Check for NewEpochEvent event (epoch change) - if let Ok(event) = crate::onchain_config::types::NewEpochEvent::decode_log(log) { - debug!(target: "execute_ordered_block", - number=?block_number, - new_epoch=?event.newEpoch, - "detected epoch change from NewEpochEvent" - ); - let validator_bytes = - crate::onchain_config::types::convert_active_validators_to_bcs( - &event.validatorSet, - ); - epoch_change_result = Some((event.newEpoch, validator_bytes)); - } - if let Ok(event) = ObservedJWKsUpdated::decode_log(&log) { info!(target: "execute_ordered_block", number=?block_number, @@ -623,7 +817,7 @@ impl Core { } } } - (gravity_events, epoch_change_result) + gravity_events } fn execute_ordered_block( @@ -637,7 +831,7 @@ impl Core { assert_eq!(block_number, parent_header.number + 1); let epoch = ordered_block.epoch; - let state = self.storage.get_state_view().unwrap(); + let state = Arc::new(self.storage.get_state_view().unwrap()); let evm_env = self .evm_config @@ -658,69 +852,51 @@ impl Core { block_number=?block_number, ); let base_fee = evm_env.block_env.basefee; + // let mut evm = self.evm_config.evm_with_env(&mut state, evm_env); + // evm apply precompile + // for xx in xx { evm.execute_transaction(tx) } + // cases + + // Execute system transactions (metadata, DKG, JWK) sequentially + let (metadata_txn_result, accumulated_state_changes, validator_txn_results) = + match Self::execute_system_transactions( + &self.evm_config, + &self.chain_spec, + &state, + evm_env, + &ordered_block, + base_fee, + epoch, + block_id, + parent_id, + block_number, + ) { + SystemTxnExecutionOutcome::EpochChanged(result) => return result, + SystemTxnExecutionOutcome::Continue { + metadata_result, + accumulated_state_changes, + validator_results, + } => (metadata_result, accumulated_state_changes, validator_results), + }; - let (metadata_txn_result, state_changes) = { - let mut state = State::builder().with_database_ref(&state).with_bundle_update().build(); - let mut evm = self.evm_config.evm_with_env(&mut state, evm_env); - let (metadata_txn_result, state_changes) = transact_metadata_contract_call( - &mut evm, - ordered_block.timestamp_us, - ordered_block.proposer_index, - ); - drop(evm); - - if let Some((new_epoch, validators)) = metadata_txn_result.emit_new_epoch() { - // New epoch triggered, advance epoch and discard the block. - assert_eq!(new_epoch, epoch + 1); - info!(target: "execute_ordered_block", - id=?block_id, - parent_id=?parent_id, - number=?block_number, - new_epoch=?new_epoch, - "emit new epoch, discard the block" - ); - state.commit(state_changes); - state.merge_transitions(BundleRetention::Reverts); - return metadata_txn_result.into_executed_ordered_block_result( - &self.chain_spec, - &ordered_block, - base_fee, - state.take_bundle(), - validators, - ); - } - debug!(target: "execute_ordered_block", - metadata_txn_result=?metadata_txn_result, - "metadata transaction result" - ); - (metadata_txn_result, state_changes) - }; - - let validator_txns = if !ordered_block.extra_data.is_empty() { - construct_validator_txns_envelope( - &ordered_block.extra_data, - metadata_txn_result.txn.nonce(), - metadata_txn_result.txn.gas_price().expect("metadata txn gas price is not set"), - ) - .unwrap() - } else { - vec![] - }; - + // No longer pass validator_txns to create_block_for_executor since they are executed + // separately let (block, txs_info) = - self.create_block_for_executor(ordered_block, base_fee, &state, validator_txns); + self.create_block_for_executor(ordered_block, base_fee, &state, vec![]); info!(target: "execute_ordered_block", id=?block_id, parent_id=?parent_id, number=?block_number, num_txs=?block.transaction_count(), + validator_txn_count=?validator_txn_results.len(), "ready to execute block" ); let mut executor = self.evm_config.parallel_executor(state); - // Apply metadata transaction result to executor state - executor.commit_changes(state_changes); + // Apply all pre-executed transaction state changes (metadata + validator txns) to executor + // state + executor.commit_changes(accumulated_state_changes); let outcome = executor.execute(&block).unwrap_or_else(|err| { serde_json::to_writer_pretty( std::io::BufWriter::new(std::fs::File::create(format!("{block_id}.json")).unwrap()), @@ -747,34 +923,22 @@ impl Core { gravity_events: vec![], epoch, }; - metadata_txn_result.insert_to_executed_ordered_block_result(&mut result); + metadata_txn_result.insert_to_executed_ordered_block_result(&mut result, 0); + // Insert validator transaction results one by one after the metadata transaction + // Position 1 is right after the metadata transaction at position 0 + for (index, validator_result) in validator_txn_results.into_iter().enumerate() { + validator_result.insert_to_executed_ordered_block_result(&mut result, 1 + index); + } debug!(target: "execute_ordered_block", number=?result.block.number, receipts_len=?result.execution_output.receipts.len(), - "insert metadata transaction result to executed ordered block result" + "insert metadata and validator transaction results to executed ordered block result" ); - let (mut gravity_events, epoch_change_result) = self.extract_gravity_events_from_receipts( + let gravity_events = self.extract_gravity_events_from_receipts( &result.execution_output.receipts, result.block.number, ); - // Check if any transaction (including JWK transactions) triggered a new epoch - // TODO(gravity_lightman): We need further more tests to test this branch - if let Some((new_epoch, validators)) = epoch_change_result { - // New epoch triggered, advance epoch and discard the block. - assert_eq!(new_epoch, epoch + 1); - info!(target: "execute_ordered_block", - id=?block_id, - parent_id=?parent_id, - number=?block_number, - new_epoch=?new_epoch, - "emit new epoch from transaction execution, discard the block" - ); - // Add NewEpoch event to gravity_events - gravity_events.push(GravityEvent::NewEpoch(new_epoch, validators.into())); - result.epoch = new_epoch; - } - result.gravity_events.extend(gravity_events); result } diff --git a/crates/pipe-exec-layer-ext-v2/execute/src/mint_precompile.rs b/crates/pipe-exec-layer-ext-v2/execute/src/mint_precompile.rs new file mode 100644 index 000000000..2e032094d --- /dev/null +++ b/crates/pipe-exec-layer-ext-v2/execute/src/mint_precompile.rs @@ -0,0 +1,156 @@ +//! Mint Token Precompile Contract +//! +//! This precompile allows authorized callers (JWK Manager) to mint tokens +//! directly to specified recipient addresses. + +use alloy_primitives::{address, map::HashMap, Address, Bytes, U256}; +use grevm::ParallelState; +use parking_lot::Mutex; +use reth_evm::{ + precompiles::{DynPrecompile, PrecompileInput}, + ParallelDatabase, +}; +use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult}; +use std::sync::Arc; +use tracing::{info, warn}; + +/// Authorized caller address (JWK Manager at 0x2018) +/// +/// Only this address is allowed to call the mint precompile. +pub const AUTHORIZED_CALLER: Address = address!("0x0000000000000000000000000000000000002018"); + +/// Function ID for mint operation +const FUNC_MINT: u8 = 0x01; + +/// Base gas cost for mint operation +const MINT_BASE_GAS: u64 = 21000; + +/// Creates a mint token precompile contract instance with state access. +/// +/// The precompile contract allows authorized callers to submit mint requests +/// and directly modifies the recipient's balance in the state. +/// +/// # Arguments +/// +/// * `state` - Shared ParallelState wrapped in `Arc>` for thread-safe access +/// +/// # Returns +/// +/// A dynamic precompile that can be registered with the EVM +pub fn create_mint_token_precompile( + state: Arc>>, +) -> DynPrecompile { + let precompile_id = PrecompileId::custom("mint_token"); + + (precompile_id, move |input: PrecompileInput<'_>| -> PrecompileResult { + mint_token_handler(input, state.clone()) + }) + .into() +} + +/// Mint Token handler function +/// +/// # Security +/// +/// - Only JWK Manager (0x2018) is allowed to call this precompile +/// - Calls from other addresses will be rejected with an error +/// +/// # Parameter format (53 bytes) +/// +/// | Offset | Size | Description | +/// |--------|------|-------------| +/// | 0 | 1 | Function ID (0x01) | +/// | 1 | 20 | Recipient address | +/// | 21 | 32 | Amount (U256, big-endian) | +/// +/// # Errors +/// +/// - `Unauthorized caller` - Caller is not the authorized JWK Manager +/// - `Invalid input length` - Input data is less than 53 bytes +/// - `Invalid function ID` - Function ID is not 0x01 +/// - `Invalid or zero amount` - Amount is zero or exceeds u128::MAX +fn mint_token_handler( + input: PrecompileInput<'_>, + state: Arc>>, +) -> PrecompileResult { + // 1. Validate caller address + if input.caller != AUTHORIZED_CALLER { + warn!( + target: "evm::precompile::mint_token", + caller = ?input.caller, + authorized = ?AUTHORIZED_CALLER, + "Unauthorized caller" + ); + return Err(PrecompileError::Other("Unauthorized caller".into())); + } + + // 2. Parameter length check (1 + 20 + 32 = 53 bytes) + const EXPECTED_LEN: usize = 1 + 20 + 32; + if input.data.len() < EXPECTED_LEN { + warn!( + target: "evm::precompile::mint_token", + input_len = input.data.len(), + expected = EXPECTED_LEN, + "Invalid input length" + ); + return Err(PrecompileError::Other( + format!("Invalid input length: {}, expected {}", input.data.len(), EXPECTED_LEN).into(), + )); + } + + // 3. Parse and validate function ID + if input.data[0] != FUNC_MINT { + warn!( + target: "evm::precompile::mint_token", + func_id = input.data[0], + expected = FUNC_MINT, + "Invalid function ID" + ); + return Err(PrecompileError::Other( + format!("Invalid function ID: {:#x}, expected {:#x}", input.data[0], FUNC_MINT).into(), + )); + } + + // 4. Parse recipient address (bytes 1-20) + let recipient = Address::from_slice(&input.data[1..21]); + + // 5. Parse amount (bytes 21-52) + let amount_u256 = U256::from_be_slice(&input.data[21..53]); + let amount: u128 = amount_u256.try_into().map_err(|_| { + warn!( + target: "evm::precompile::mint_token", + ?recipient, + amount = ?amount_u256, + "Amount exceeds u128::MAX" + ); + PrecompileError::Other("Amount exceeds u128::MAX".into()) + })?; + + if amount == 0 { + warn!(target: "evm::precompile::mint_token", ?recipient, "Zero amount"); + return Err(PrecompileError::Other("Zero amount not allowed".into())); + } + + // 6. Execute mint operation + let mut state_guard = state.lock(); + if let Err(e) = state_guard.increment_balances(HashMap::from([(recipient, amount)])) { + warn!( + target: "evm::precompile::mint_token", + ?recipient, + amount, + error = ?e, + "Failed to increment balance" + ); + return Err(PrecompileError::Other("Failed to mint tokens".into())); + } + drop(state_guard); + + info!( + target: "evm::precompile::mint_token", + ?recipient, + amount, + "Minted tokens successfully" + ); + + Ok(PrecompileOutput { gas_used: MINT_BASE_GAS, bytes: Bytes::new(), reverted: false }) +} diff --git a/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/metadata_txn.rs b/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/metadata_txn.rs index eac7926fe..425c4dc4f 100644 --- a/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/metadata_txn.rs +++ b/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/metadata_txn.rs @@ -31,16 +31,29 @@ use std::fmt::Debug; pub const NIL_PROPOSER_INDEX: u64 = u64::MAX; /// Result of a metadata transaction execution +/// Merge new state changes into accumulated state changes +/// +/// This is a helper function to accumulate state changes from multiple +/// sequential transaction executions. +pub fn merge_state_changes(accumulated: &mut EvmState, new_changes: EvmState) { + for (addr, account) in new_changes { + accumulated.insert(addr, account); + } +} + +/// Result of a system transaction execution (metadata, DKG, or JWK) +/// This is a unified structure for all system-level transactions that are executed before +/// the parallel executor. #[derive(Debug)] -pub struct MetadataTxnResult { - /// Result of the metadata transaction execution +pub struct SystemTxnResult { + /// Result of the system transaction execution pub result: ExecutionResult, - /// The metadata transaction + /// The system transaction pub txn: TransactionSigned, } -impl MetadataTxnResult { - /// Check if the transaction emitted a `NewEpochEvent` event +impl SystemTxnResult { + /// Check if the transaction emitted a `NewEpoch` event pub fn emit_new_epoch(&self) -> Option<(u64, Bytes)> { for log in self.result.logs() { match NewEpochEvent::decode_log(log) { @@ -55,7 +68,8 @@ impl MetadataTxnResult { None } - /// Convert the metadata transaction result into a full executed block result + /// Convert the system transaction result into a full executed block result + /// Used when new epoch is triggered and the block needs to be discarded. pub(crate) fn into_executed_ordered_block_result( self, chain_spec: &ChainSpec, @@ -122,31 +136,76 @@ impl MetadataTxnResult { } } - /// Insert this metadata transaction into an existing executed block result + /// Insert this system transaction into an existing executed block result at the specified + /// position Position 0 is reserved for metadata tx, positions 1+ are for validator + /// transactions pub(crate) fn insert_to_executed_ordered_block_result( self, - result: &mut ExecuteOrderedBlockResult, + result: &mut crate::ExecuteOrderedBlockResult, + insert_position: usize, ) { let gas_used = self.result.gas_used(); result.block.header.gas_used += gas_used; result.execution_output.gas_used += gas_used; - result.execution_output.receipts.iter_mut().for_each(|receipt| { + + // Calculate cumulative_gas_used for this system transaction: + // It should be the cumulative gas of the previous transaction (at insert_position - 1) + // plus this transaction's gas_used + let cumulative_gas_used = if insert_position == 0 { + // First transaction, cumulative equals its own gas_used + gas_used + } else { + // Get cumulative from the previous receipt and add this tx's gas + result + .execution_output + .receipts + .get(insert_position - 1) + .map(|prev| prev.cumulative_gas_used + gas_used) + .unwrap_or(gas_used) + }; + + // Update all receipts AFTER insert_position to add this tx's gas + for receipt in result.execution_output.receipts.iter_mut().skip(insert_position) { receipt.cumulative_gas_used += gas_used; - }); + } + result.execution_output.receipts.insert( - 0, + insert_position, Receipt { tx_type: self.txn.tx_type(), success: true, - cumulative_gas_used: gas_used, + cumulative_gas_used, logs: self.result.into_logs(), }, ); - result.block.body.transactions.insert(0, self.txn); - result.senders.insert(0, SYSTEM_CALLER); + result.block.body.transactions.insert(insert_position, self.txn); + result.senders.insert(insert_position, SYSTEM_CALLER); } } +/// Execute a single system transaction (metadata, DKG, or JWK) +/// +/// This is the unified entry point for executing all system-level transactions. +/// These transactions are executed one by one before the parallel executor. +pub fn transact_system_txn( + evm: &mut impl Evm, + txn: TransactionSigned, +) -> (SystemTxnResult, EvmState) { + use reth_evm::IntoTxEnv; + use reth_primitives::Recovered; + + let tx_env = Recovered::new_unchecked(txn.clone(), SYSTEM_CALLER).into_tx_env(); + let result = evm.transact_raw(tx_env).unwrap(); + + assert!( + result.result.is_success(), + "Failed to execute system transaction: {:?}", + result.result + ); + + (SystemTxnResult { result: result.result, txn }, result.state) +} + /// Create a new system call transaction fn new_system_call_txn( contract: Address, @@ -178,11 +237,12 @@ fn new_system_call_txn( /// /// @param proposer_index Index of the proposer in the active validator set, /// or None for NIL blocks (will use NIL_PROPOSER_INDEX = u64::MAX) -pub fn transact_metadata_contract_call( - evm: &mut impl Evm, +pub fn construct_metadata_txn( + nonce: u64, + gas_price: u128, timestamp_us: u64, proposer_index: Option, -) -> (MetadataTxnResult, EvmState) { +) -> TransactionSigned { // For NIL blocks, use NIL_PROPOSER_INDEX (type(uint64).max in Solidity) let proposer_idx = proposer_index.unwrap_or(NIL_PROPOSER_INDEX); @@ -193,18 +253,5 @@ pub fn transact_metadata_contract_call( }; let input: Bytes = call.abi_encode().into(); - let system_call_account = - evm.db_mut().basic(SYSTEM_CALLER).unwrap().expect("SYSTEM_CALLER not exists"); - let txn = new_system_call_txn( - BLOCK_ADDR, - system_call_account.nonce, - evm.block().basefee as u128, - input, - ); - let tx_env = Recovered::new_unchecked(txn.clone(), SYSTEM_CALLER).into_tx_env(); - let result = evm.transact_raw(tx_env).unwrap(); - - assert!(result.result.is_success(), "Failed to execute onBlockStart: {:?}", result.result); - - (MetadataTxnResult { result: result.result, txn }, result.state) + new_system_call_txn(BLOCK_ADDR, nonce, gas_price, input) } diff --git a/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/mod.rs b/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/mod.rs index d51f380ad..af5a6779d 100644 --- a/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/mod.rs +++ b/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/mod.rs @@ -18,7 +18,9 @@ pub mod validator_set; pub use base::{ConfigFetcher, OnchainConfigFetcher}; pub use consensus_config::ConsensusConfigFetcher; pub use epoch::EpochFetcher; -pub use metadata_txn::{transact_metadata_contract_call, MetadataTxnResult}; +pub use metadata_txn::{ + construct_metadata_txn, transact_system_txn, SystemTxnResult, +}; pub use types::{ convert_active_validators_to_bcs, convert_validator_consensus_info, ValidatorConsensusInfo, ValidatorStatus, @@ -118,7 +120,7 @@ pub fn construct_validator_txns_envelope( let current_nonce = system_caller_nonce + index as u64; // Process data based on ExtraDataType variant - match process_extra_data(data, current_nonce, gas_price) { + match construct_validator_txn_from_extra_data(data, current_nonce, gas_price) { Ok(transaction) => txns.push(transaction), Err(e) => { return Err(format!("Failed to process extra data at index {}: {}", index, e)); @@ -129,12 +131,12 @@ pub fn construct_validator_txns_envelope( Ok(txns) } -/// Process extra data based on its ExtraDataType variant +/// Construct a single validator transaction from ExtraDataType /// /// Supports: /// - JWK/Oracle updates (ExtraDataType::JWK) - includes both RSA JWKs and blockchain events /// - DKG transcripts (ExtraDataType::DKG) -fn process_extra_data( +pub fn construct_validator_txn_from_extra_data( data: &gravity_api_types::ExtraDataType, nonce: u64, gas_price: u128, From 150964c5dd1dc2e69b8f6247359fba0d1102ee49 Mon Sep 17 00:00:00 2001 From: Lightman <915311741@qq.com> Date: Wed, 28 Jan 2026 21:35:40 +0800 Subject: [PATCH 2/2] tmp --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 455164f3e..a1f158a68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1032,7 +1032,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "api-types" version = "0.1.0" -source = "git+https://github.com/Galxe/gravity-aptos?rev=977f5b9388183c8a14c0ddcb4e2ac9f265d45184#977f5b9388183c8a14c0ddcb4e2ac9f265d45184" +source = "git+https://github.com/Galxe/gravity-aptos?rev=7b2d7949583169cc5c997856b4a0d17fec56ebf6#7b2d7949583169cc5c997856b4a0d17fec56ebf6" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index dd77bfd26..65e96d3bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -377,7 +377,7 @@ codegen-units = 1 [workspace.dependencies] # reth -gravity-api-types = { package = "api-types", git = "https://github.com/Galxe/gravity-aptos", rev = "977f5b9388183c8a14c0ddcb4e2ac9f265d45184" } +gravity-api-types = { package = "api-types", git = "https://github.com/Galxe/gravity-aptos", rev = "7b2d7949583169cc5c997856b4a0d17fec56ebf6" } op-reth = { path = "crates/optimism/bin" } reth = { path = "bin/reth" } reth-storage-rpc-provider = { path = "crates/storage/rpc-provider" }