Skip to content

Bug in block execution with pipe-execution #217

@vuviettai

Description

@vuviettai

Describe the bug

I encounter issue when run gravity node with pipe-execution

2025-12-09T09:12:13.923052Z  INFO 💡 [GravityBench] Both systems run in parallel - no conflicts expected
2025-12-09T09:12:13.923065Z  INFO Wait execute_block_barrier 1 => (1, 0)

thread 'tokio-5' panicked at /Users/viettai/workspace/codelight/scalar.org/gravity/gravity-reth/crates/pipe-exec-layer-ext-v2/execute/src/onchain_config/metadata_txn.rs:211:5:
Failed to execute blockPrologue: Revert { gas_used: 28665, output: 0x85f58ac50000000000000000000000000000000000000000000000000005fe8564fd98800000000000000000000000000000000000000000000000000000000000000000 }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
2025-12-09T09:12:16.919871Z  INFO Status connected_peers=0 latest_block=0

I read the code deeper in metadata_txn.rs and execute/src/lib.rs I have some question?
In the execute_ordered_block, we call transact_metadata_contract_call with timestamp argument

ordered_block.timestamp * 1_000_000,

in the Timestamp.sol updateGlobalTime method, the contract checks the condition

 if (currentTime != timestamp)  {
...
}

By default the currentTime is 0
Please help to set correct parameters after node started!

Steps to reproduce

Steps to reproduce

  1. Generate genesis.json with gravity-genesis-contract
  2. Run custom gravity node with generated genesis.json
fn main() {
    Cli::<EthereumChainSpecParser, GravityBenchArgs>::parse()
        .run(|builder, args| async move {
            info!("🚀 [GravityBench] Initializing gravity_node with dual-mode support");
            info!("   Mode 1: Dev mode miner - mines transactions from transaction pool");
            info!("   Mode 2: OrderedBlock injection - handles gravity_bench_submitBatch transactions");
            
            // Create simple transaction buffer for RPC
            let buffer = Arc::new(node::TransactionBuffer::new());
            // Create gravity bench node for OrderedBlock injection
            let genesis_timestamp = builder.config().chain.genesis_header().timestamp;
            let bench_node = Arc::new(
                GravityBenchNode::new(args.batch_size, args.block_interval_ms, buffer.clone(), genesis_timestamp)
                    .await
                    .expect("Failed to create GravityBenchNode"),
            );
   
            // Create execution args channel for pipe execution layer
            let (execution_args_tx, execution_args_rx) = oneshot::channel::<ExecutionArgs>();

            let handle = builder
                // Configure default ethereum node
                .node(EthereumNode::default())
                // Extend the RPC modules with our custom gravity_bench endpoints
                .extend_rpc_modules(move |ctx| {
                    if !args.enable_gravity_bench {
                        return Ok(());
                    }

                    // Create the RPC extension with transaction buffer
                    // Note: Pool mode support requires accessing the pool from RPC context,
                    // which is complex. For now, we only support pipe mode.
                    let rpc_ext = GravityBenchRpc::new(buffer);

                    // Merge gravity_bench namespace
                    ctx.modules
                        .merge_configured(GravityBenchApiServer::into_rpc(rpc_ext))?;

                    info!("✅ [GravityBench] RPC extension enabled");
                    info!("  - gravity_submitBatch(transactions: Vec<Bytes>, mode?: \"pool\"|\"pipe\") -> Vec<B256>");
                    info!("  - gravity_getBufferStatus() -> BufferStatus");
                    info!(
                        "⚙️ [GravityBench] Configuration: batch_size={}, block_interval_ms={}",
                        args.batch_size, args.block_interval_ms
                    );

                    Ok(())
                })
                .on_node_started(move |full_node| {
                    info!("✅ [GravityBench] Node started");
                    
                    if !args.enable_gravity_bench {
                        return Ok(());
                    }
                    
                    let execution_args_rx_clone = execution_args_rx;
                    
                    // Get required components from full node
                    let provider = full_node.provider.clone();
                    let chain_spec = full_node.chain_spec();
                    let eth_api = full_node.rpc_registry.eth_api().clone();
                    
                    // Get latest block information
                    let latest_block_number = match provider.last_block_number() {
                        Ok(num) => num,
                        Err(e) => {
                            warn!("❌ [GravityBench] Failed to get latest block number: {}", e);
                            return Ok(());
                        }
                    };
                    
                    let latest_block_hash = match provider.block_hash(latest_block_number) {
                        Ok(Some(hash)) => hash,
                        Ok(None) => {
                            warn!("❌ [GravityBench] Latest block hash not found");
                            return Ok(());
                        }
                        Err(e) => {
                            warn!("❌ [GravityBench] Failed to get latest block hash: {}", e);
                            return Ok(());
                        }
                    };
                    
                    let latest_block = match provider.block(BlockHashOrNumber::Number(latest_block_number)) {
                        Ok(Some(block)) => block,
                        Ok(None) => {
                            info!("ℹ️ [GravityBench] No blocks found, skipping setup (will sync on first block)");
                            return Ok(());
                        }
                        Err(e) => {
                            warn!("❌ [GravityBench] Failed to get latest block: {}", e);
                            return Ok(());
                        }
                    };
                    
                    info!("📦 [GravityBench] Creating pipe execution layer API");
                    info!("   Latest block: number={}, hash={:?}", latest_block_number, latest_block_hash);
                    
                    // Create storage wrapper
                    let storage = BlockViewStorage::new(provider.clone());
                    // Get chain_id from chain_spec
                    let chain_id = match chain_spec.chain.into_kind() {
                        greth::reth_chainspec::ChainKind::Named(n) => n as u64,
                        greth::reth_chainspec::ChainKind::Id(id) => id,
                    };
                    // Create pipe execution layer API
                    let pipeline_api = reth_pipe_exec_layer_ext_v2::new_pipe_exec_layer_api(
                        chain_spec,
                        storage,
                        latest_block.header,
                        latest_block_hash,
                        execution_args_rx_clone,
                        eth_api.clone(),
                    );
                    
                    info!("✅ [GravityBench] Pipe execution layer API created");
                    
                    // Create coordinator (similar to RethCoordinator in gravity_sdk)
                    use crate::node::coordinator::GravityBenchCoordinator;
                    let pipeline_api_arc = Arc::new(pipeline_api);
                    let coordinator = GravityBenchCoordinator::new(
                        pipeline_api_arc.clone(),
                        provider.clone(),
                        chain_id,
                    );
                    
                    // Start coordinator tasks (start_execution, start_commit_vote, start_commit)
                    coordinator.run();
                    info!("✅ [GravityBench] Coordinator started (execution, commit_vote, commit tasks)");
                    
                    // Initialize pipe API in bench node
                    // Wrap it using the same pattern as init_pipe_api
                    let pipe_api_arc = pipeline_api_arc.clone();
                    let bench_node_clone = bench_node.clone();
                    tokio::spawn(async move {
                        // Create wrapper that implements the trait (same as in setup.rs)
                        struct PipeApiWrapper<Storage, EthApi> {
                            inner: Arc<PipeExecLayerApi<Storage, EthApi>>,
                        }
                        
                        impl<Storage, EthApi> PipeExecLayerApiTrait for PipeApiWrapper<Storage, EthApi>
                        where
                            Storage: GravityStorage,
                            EthApi: EthCall,
                            EthApi::NetworkTypes: RpcTypes<TransactionRequest = TransactionRequest>,
                        {
                            fn push_ordered_block(&self, block: greth::reth_pipe_exec_layer_ext_v2::OrderedBlock) -> Option<()> {
                                self.inner.push_ordered_block(block)
                            }
                        }
                        
                        // Wrap the pipe API
                        let wrapped: Arc<dyn PipeExecLayerApiTrait> = Arc::new(PipeApiWrapper {
                            inner: pipe_api_arc,
                        });
                        
                        // Set it in the bench node's pipe_api mutex
                        let pipe_api_mutex = bench_node_clone.get_pipe_api();
                        let mut api_guard = pipe_api_mutex.lock().await;
                        *api_guard = Some(wrapped);
                        info!("✅ [GravityBench] Pipe API initialized for OrderedBlock injection");
                    });
                    
                    // Send execution args
                    let _ = execution_args_tx.send(ExecutionArgs {
                        block_number_to_block_id: std::collections::BTreeMap::new(),
                    });
                    
                    // Create and push empty NIL block to sync GlobalSystemTimestamp
                    let provider_clone = provider.clone();
                    let pipe_api_for_nil = pipeline_api_arc.clone();
                    let eth_api_for_contract_call = eth_api.clone();
                    
                    tokio::spawn(async move {
                        use alloy::primitives::{Address, keccak256};
                        use alloy_eips::eip4895::Withdrawals;
                        use greth::reth_pipe_exec_layer_ext_v2::OrderedBlock;
                        use node::epoch::get_current_epoch;
                        info!("🔄 [GravityBench] Creating empty NIL block for GlobalSystemTimestamp synchronization");
                        
                        // Get latest block information
                        let latest_block_number = match provider_clone.last_block_number() {
                            Ok(num) => num,
                            Err(e) => {
                                warn!("❌ [GravityBench] Failed to get last block number: {}, using 0", e);
                                0
                            }
                        };
                        
                        let latest_block_hash = provider_clone
                            .block_hash(latest_block_number)
                            .ok()
                            .flatten()
                            .unwrap_or(B256::ZERO);
                         
                        // Fetch current epoch from the EpochManager contract
                        let current_epoch = get_current_epoch(&eth_api_for_contract_call, latest_block_number).await;
                        
                        // Generate unique block ID
                        let block_id = {
                            let mut input = Vec::with_capacity(32);
                            input.extend_from_slice(&latest_block_number.to_be_bytes());
                            input.extend_from_slice(&genesis_timestamp.to_be_bytes());
                            input.extend_from_slice(b"nil_sync_startup");
                            input.extend_from_slice(&std::process::id().to_be_bytes());
                            keccak256(input)
                        };
                        
                        // Create empty NIL OrderedBlock
                        let empty_nil_block = OrderedBlock {
                            epoch: current_epoch,
                            parent_id: latest_block_hash,
                            id: block_id,
                            number: latest_block_number + 1,
                            timestamp: genesis_timestamp, // Placeholder - execution layer will read actual timestamp from contract
                            coinbase: Address::ZERO,
                            prev_randao: B256::ZERO,
                            withdrawals: Withdrawals::new(Vec::new()),
                            transactions: Vec::new(), // Empty - no transactions
                            senders: Vec::new(),      // Empty - no senders
                            proposer: None, //Some(node::PROPOSER_PUBKEY),
                            extra_data: Vec::new(),
                            randomness: alloy::primitives::U256::ZERO,
                            enable_randomness: false,
                        };
                        
                        info!(
                            "📤 [GravityBench] Pushing empty NIL block for GlobalSystemTimestamp sync: number={}, epoch={}, placeholder_timestamp={}, parent_id={:?}, block_id={:?}",
                            empty_nil_block.number,
                            empty_nil_block.epoch,
                            empty_nil_block.timestamp,
                            empty_nil_block.parent_id,
                            empty_nil_block.id
                        );
                        info!(
                            "💡 [GravityBench] Execution layer will read actual timestamp from Timestamp contract (proposer=None triggers sync)"
                        );
                        
                        // Push to execution layer
                        pipe_api_for_nil.push_ordered_block(empty_nil_block);
                        
                        info!("✅ [GravityBench] Empty NIL block pushed successfully for GlobalSystemTimestamp synchronization");
                    });
                    
                    // Start the OrderedBlock injection loop
                    // This runs in parallel with dev mode miner:
                    // - Dev mode miner: Mines transactions from pool (setup transactions)
                    // - OrderedBlock injection: Handles transactions from BlockBufferManager (test transactions)
                    // Pass provider so OrderedBlock injection can sync with LocalMiner blocks
                    let pipe_api = bench_node.get_pipe_api();
                    let provider_for_sync = Arc::new(provider.clone());
                    let engine_events = full_node.add_ons_handle.engine_events.new_listener();
                    tokio::spawn(async move {
                        info!("🔄 [GravityBench] Starting OrderedBlock injection background task");
                        info!("   This will handle transactions from gravity_bench_submitBatch");
                        info!("   Dev mode miner will continue to mine transactions from transaction pool");
                        info!("   OrderedBlock injection will sync with LocalMiner blocks automatically");
                        bench_node
                            .run_injection_loop(pipe_api, eth_api, provider_for_sync, engine_events)
                            .await;
                    });
                    
                    Ok(())
                })
                // Launch the node with debug capabilities (includes LocalMiner for dev mode)
                // The LocalMiner will mine transactions from the transaction pool
                // OrderedBlock injection will handle transactions from gravity_bench_submitBatch
                // Both can work simultaneously:
                // - Setup transactions (deploy, faucet) → transaction pool → LocalMiner mines them
                // - Test transactions (benchmark) → gravity_bench_submitBatch → OrderedBlock injection
                .launch_with_debug_capabilities()
                .await?;
            
            info!("🚀 [GravityBench] Node launched successfully!");
            info!("📋 [GravityBench] Dual-mode operation enabled:");
            info!("   ✅ Dev mode miner: Active - mines transactions from transaction pool");
            info!("   ✅ OrderedBlock injection: Active - handles gravity_bench_submitBatch transactions");
            info!("");
            info!("📊 [GravityBench] Transaction routing:");
            info!("   🔵 Setup phase: eth_sendRawTransaction → Transaction Pool → Dev Mode Miner → Block");
            info!("   🟢 Test phase: gravity_bench_submitBatch → Buffer → OrderedBlock Injection → Block");
            info!("");
            info!("💡 [GravityBench] Both systems run in parallel - no conflicts expected");

            handle.wait_for_node_exit().await
        })
        .unwrap();
}

In the on_node_started hook I try add an empty OrderedBlock

let empty_nil_block = OrderedBlock {
              epoch: current_epoch,
              parent_id: latest_block_hash,
              id: block_id,
              number: latest_block_number + 1,
              timestamp: genesis_timestamp, // Placeholder - execution layer will read actual timestamp from contract
              coinbase: Address::ZERO,
              prev_randao: B256::ZERO,
              withdrawals: Withdrawals::new(Vec::new()),
              transactions: Vec::new(), // Empty - no transactions
              senders: Vec::new(),      // Empty - no senders
              proposer: None, //Some(node::PROPOSER_PUBKEY),
              extra_data: Vec::new(),
              randomness: alloy::primitives::U256::ZERO,
              enable_randomness: false,
          };

Node logs


Platform(s)

Mac (Apple Silicon)

Container Type

Not running in a container

What version/commit are you on?

reth-ethereum-cli Version: 1.8.3-dev
Commit SHA: 12cd18f
Build Timestamp: 2025-12-09T09:09:05.134007000Z
Build Features: jemalloc
Build Profile: release

What database version are you on?

reth-ethereum-cli Version: 1.8.3-dev
Commit SHA: 12cd18f
Build Timestamp: 2025-12-09T09:09:05.134007000Z
Build Features: jemalloc
Build Profile: release

Which chain / network are you on?

dev network

What type of node are you running?

Archive (default)

What prune config do you use, if any?

No response

If you've built Reth from source, provide the full command you used

No response

Code of Conduct

  • I agree to follow the Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions