From c1da9ec605599c71bf55da81d6840adde4701abd Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Fri, 20 Mar 2026 16:55:09 +0800 Subject: [PATCH 01/30] support add tee game type --- devnet/scripts/add-tee-game-type.sh | 399 ++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100755 devnet/scripts/add-tee-game-type.sh diff --git a/devnet/scripts/add-tee-game-type.sh b/devnet/scripts/add-tee-game-type.sh new file mode 100755 index 0000000..dddc2d4 --- /dev/null +++ b/devnet/scripts/add-tee-game-type.sh @@ -0,0 +1,399 @@ +#!/bin/bash +# add-tee-game-type.sh — Register TeeDisputeGame on an existing devnet +# +# Usage: +# ./scripts/add-tee-game-type.sh [--mock-verifier] [/path/to/tee-contracts] +# +# Flags: +# --mock-verifier Deploy MockTeeProofVerifier instead of TeeProofVerifier. +# Use when you want a fully mock verifier (no ECDSA signing needed). +# +# If no path is given, defaults to /tee-contracts. +# You can also set TEE_CONTRACTS_DIR explicitly: +# TEE_CONTRACTS_DIR=/path/to/tee-contracts ./scripts/add-tee-game-type.sh + +set -e + +# ── Parse flags ─────────────────────────────────────────────────────────────── +USE_MOCK_VERIFIER=false +POSITIONAL_ARGS=() +for arg in "$@"; do + case "$arg" in + --mock-verifier) USE_MOCK_VERIFIER=true ;; + *) POSITIONAL_ARGS+=("$arg") ;; + esac +done + +# Source environment variables +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEVNET_DIR="$(dirname "$SCRIPT_DIR")" +ENV_FILE="$DEVNET_DIR/.env" +source "$ENV_FILE" + +# Resolve tee-contracts directory: positional arg > TEE_CONTRACTS_DIR env > default +if [ "${#POSITIONAL_ARGS[@]}" -gt 0 ]; then + TEE_CONTRACTS_DIR="${POSITIONAL_ARGS[0]}" +elif [ -z "$TEE_CONTRACTS_DIR" ]; then + TEE_CONTRACTS_DIR="$DEVNET_DIR/tee-contracts" +fi + +if [ ! -f "$TEE_CONTRACTS_DIR/foundry.toml" ]; then + echo "❌ Cannot find tee-contracts at: $TEE_CONTRACTS_DIR" + echo " Pass the directory as the first argument or set TEE_CONTRACTS_DIR." + exit 1 +fi + +echo "=== Using tee-contracts: $TEE_CONTRACTS_DIR ===" + +# TeeDisputeGame game type (must match TEE_DISPUTE_GAME_TYPE in AccessManager.sol) +TEE_GAME_TYPE=1960 + +# Validate OWNER_TYPE configuration +if [ "$OWNER_TYPE" != "transactor" ] && [ "$OWNER_TYPE" != "safe" ]; then + echo "❌ Error: Invalid OWNER_TYPE '$OWNER_TYPE'. Must be 'transactor' or 'safe'" + exit 1 +fi + +echo "=== Using OWNER_TYPE: $OWNER_TYPE ===" + +# Resolve on-chain addresses +echo "=== Resolving on-chain addresses ===" +DISPUTE_GAME_FACTORY_ADDR=$(cast call --rpc-url $L1_RPC_URL $SYSTEM_CONFIG_PROXY_ADDRESS 'disputeGameFactory()(address)') +OPTIMISM_PORTAL_ADDR=$(cast call --rpc-url $L1_RPC_URL $SYSTEM_CONFIG_PROXY_ADDRESS 'optimismPortal()(address)') +ANCHOR_STATE_REGISTRY_ADDR=$(cast call --rpc-url $L1_RPC_URL $OPTIMISM_PORTAL_ADDR 'anchorStateRegistry()(address)') + +echo " Existing DGF: $DISPUTE_GAME_FACTORY_ADDR" +echo " OptimismPortal: $OPTIMISM_PORTAL_ADDR" +echo " Existing ASR: $ANCHOR_STATE_REGISTRY_ADDR" +echo "" + +# Export env vars consumed by DevnetAddTeeGame.s.sol +export PRIVATE_KEY="$DEPLOYER_PRIVATE_KEY" +export EXISTING_DGF="$DISPUTE_GAME_FACTORY_ADDR" +export EXISTING_ASR="$ANCHOR_STATE_REGISTRY_ADDR" +export SYSTEM_CONFIG_ADDRESS="$SYSTEM_CONFIG_PROXY_ADDRESS" +export DISPUTE_GAME_FINALITY_DELAY_SECONDS="${DISPUTE_GAME_FINALITY_DELAY_SECONDS:-5}" + +# TEE game timing — reuse devnet values for fast iteration +export MAX_CHALLENGE_DURATION="${MAX_CLOCK_DURATION:-20}" +export MAX_PROVE_DURATION="${MAX_CLOCK_DURATION:-20}" + +# Bond defaults (0.01 ETH) and access-manager fallback timeout (1 hour) +export CHALLENGER_BOND="${CHALLENGER_BOND:-10000000000000000}" +export FALLBACK_TIMEOUT="${FALLBACK_TIMEOUT:-3600}" +export INIT_BOND="${INIT_BOND:-10000000000000000}" + +if [ -n "$PROPOSER_ADDRESS" ]; then + export PROPOSER_ADDRESS +else + unset PROPOSER_ADDRESS +fi + +export USE_MOCK_VERIFIER +echo "=== Verifier mode: $([ "$USE_MOCK_VERIFIER" = "true" ] && echo "MockTeeProofVerifier (mock)" || echo "TeeProofVerifier + MockRiscZeroVerifier") ===" + +# ── Function: deploy TEE contracts via forge script ─────────────────────────── +deploy_tee_contracts() { + echo "=== Deploying TEE contracts via forge script ===" + + FORGE_LOG=$(mktemp) + # Ensure temp file is cleaned up on any exit (including set -e failures) + trap 'rm -f "${FORGE_LOG:-}"' RETURN + + # Auto-detect scripts subdirectory: new-style repos use "scripts/", old-style use "script/" + if [ -f "$TEE_CONTRACTS_DIR/scripts/DevnetAddTeeGame.s.sol" ]; then + FORGE_SCRIPT_SUBDIR="scripts" + else + FORGE_SCRIPT_SUBDIR="script" + fi + + # pushd/popd avoids persistent cwd change (plain `cd` inside a non-subshell + # function would affect the rest of the script) + pushd "$TEE_CONTRACTS_DIR" > /dev/null + forge script "${FORGE_SCRIPT_SUBDIR}/DevnetAddTeeGame.s.sol:DevnetAddTeeGame" \ + --rpc-url "$L1_RPC_URL" \ + --broadcast \ + --legacy \ + -vv 2>&1 | tee "$FORGE_LOG" + popd > /dev/null + + if ! grep -q "ONCHAIN EXECUTION COMPLETE" "$FORGE_LOG"; then + echo "" + echo "❌ Deployment failed — check output above." + exit 1 + fi + + # Extract deployed addresses from forge log (grep for console2.log lines) + _addr() { grep "$1" "$FORGE_LOG" | grep -oE '0x[0-9a-fA-F]{40}' | head -1; } + + TEE_GAME_IMPL=$(_addr "TeeDisputeGame impl") + NEW_ASR_ADDR=$(_addr "New AnchorStateRegistry") + + echo "" + echo " TeeDisputeGame impl: $TEE_GAME_IMPL" + echo " New AnchorStateRegistry: $NEW_ASR_ADDR" + echo "" + + if [ -z "$TEE_GAME_IMPL" ]; then + echo "❌ Failed to extract TeeDisputeGame impl address from forge log." + exit 1 + fi + if [ -z "$NEW_ASR_ADDR" ]; then + echo "❌ Failed to extract New AnchorStateRegistry address from forge log." + exit 1 + fi +} + +# ── Function: setRespectedGameType on new ASR ───────────────────────────────── +set_respected_game_type() { + echo "=== Setting Respected Game Type to $TEE_GAME_TYPE on new ASR ===" + echo " New ASR: $NEW_ASR_ADDR" + echo "" + + # The deployer IS the guardian on devnet (SystemConfig.guardian() == deployer) + TX_OUTPUT=$(cast send \ + --json \ + --legacy \ + --rpc-url $L1_RPC_URL \ + --private-key $DEPLOYER_PRIVATE_KEY \ + --from $(cast wallet address --private-key $DEPLOYER_PRIVATE_KEY) \ + $NEW_ASR_ADDR \ + 'setRespectedGameType(uint32)' \ + $TEE_GAME_TYPE) + + TX_HASH=$(echo "$TX_OUTPUT" | jq -r '.transactionHash // empty') + TX_STATUS=$(echo "$TX_OUTPUT" | jq -r '.status // empty') + echo "Transaction sent, TX_HASH: $TX_HASH" + + if [ "$TX_STATUS" = "0x1" ] || [ "$TX_STATUS" = "1" ]; then + echo " ✅ setRespectedGameType($TEE_GAME_TYPE) completed successfully" + else + echo " ❌ Transaction failed with status: $TX_STATUS" + echo "Full output: $TX_OUTPUT" + exit 1 + fi + echo "" +} + +# ── Function: register TEE game type via Transactor ─────────────────────────── +add_tee_game_type_via_transactor() { + echo "=== Registering TeeDisputeGame (type $TEE_GAME_TYPE) via Transactor ===" + echo " Transactor: $TRANSACTOR" + echo " Existing DGF: $DISPUTE_GAME_FACTORY_ADDR" + echo " TeeDisputeGame impl: $TEE_GAME_IMPL" + echo " Sender: $(cast wallet address --private-key $DEPLOYER_PRIVATE_KEY)" + echo "" + + # Build calldata for setImplementation(uint32,address) + SET_IMPL_CALLDATA=$(cast calldata 'setImplementation(uint32,address)' $TEE_GAME_TYPE $TEE_GAME_IMPL) + echo "setImplementation calldata: $SET_IMPL_CALLDATA" + + echo "Executing CALL via Transactor (setImplementation)..." + TX_OUTPUT=$(cast send \ + --json \ + --legacy \ + --rpc-url $L1_RPC_URL \ + --private-key $DEPLOYER_PRIVATE_KEY \ + --from $(cast wallet address --private-key $DEPLOYER_PRIVATE_KEY) \ + $TRANSACTOR \ + 'CALL(address,bytes,uint256)' \ + $DISPUTE_GAME_FACTORY_ADDR \ + $SET_IMPL_CALLDATA \ + 0) + + TX_HASH=$(echo "$TX_OUTPUT" | jq -r '.transactionHash // empty') + TX_STATUS=$(echo "$TX_OUTPUT" | jq -r '.status // empty') + echo "" + echo "Transaction sent, TX_HASH: $TX_HASH" + + if [ "$TX_STATUS" = "0x1" ] || [ "$TX_STATUS" = "1" ]; then + echo " ✅ setImplementation successful!" + else + echo " ❌ Transaction failed with status: $TX_STATUS" + echo "Full output: $TX_OUTPUT" + exit 1 + fi + echo "" + + # Build calldata for setInitBond(uint32,uint256) + SET_BOND_CALLDATA=$(cast calldata 'setInitBond(uint32,uint256)' $TEE_GAME_TYPE $INIT_BOND) + echo "setInitBond calldata: $SET_BOND_CALLDATA" + + echo "Executing CALL via Transactor (setInitBond)..." + TX_OUTPUT=$(cast send \ + --json \ + --legacy \ + --rpc-url $L1_RPC_URL \ + --private-key $DEPLOYER_PRIVATE_KEY \ + --from $(cast wallet address --private-key $DEPLOYER_PRIVATE_KEY) \ + $TRANSACTOR \ + 'CALL(address,bytes,uint256)' \ + $DISPUTE_GAME_FACTORY_ADDR \ + $SET_BOND_CALLDATA \ + 0) + + TX_HASH=$(echo "$TX_OUTPUT" | jq -r '.transactionHash // empty') + TX_STATUS=$(echo "$TX_OUTPUT" | jq -r '.status // empty') + echo "" + echo "Transaction sent, TX_HASH: $TX_HASH" + + if [ "$TX_STATUS" = "0x1" ] || [ "$TX_STATUS" = "1" ]; then + echo " ✅ setInitBond successful!" + else + echo " ❌ Transaction failed with status: $TX_STATUS" + echo "Full output: $TX_OUTPUT" + exit 1 + fi + echo "" +} + +# ── Function: register TEE game type via Safe ───────────────────────────────── +add_tee_game_type_via_safe() { + echo "=== Registering TeeDisputeGame (type $TEE_GAME_TYPE) via Safe ===" + echo " Safe: $SAFE_ADDRESS" + echo " Existing DGF: $DISPUTE_GAME_FACTORY_ADDR" + echo " TeeDisputeGame impl: $TEE_GAME_IMPL" + echo " Sender: $(cast wallet address --private-key $DEPLOYER_PRIVATE_KEY)" + echo "" + + DEPLOYER_ADDRESS=$(cast wallet address --private-key $DEPLOYER_PRIVATE_KEY) + + # Build signature like DeployOwnership.s.sol _callViaSafe method + DEPLOYER_ADDRESS_NO_PREFIX=${DEPLOYER_ADDRESS#0x} + ADDRESS_LENGTH=${#DEPLOYER_ADDRESS_NO_PREFIX} + ZEROS_NEEDED=$((64 - ADDRESS_LENGTH)) + ZEROS=$(printf "%0${ZEROS_NEEDED}d" 0) + DEPLOYER_ADDRESS_PADDED="${ZEROS}${DEPLOYER_ADDRESS_NO_PREFIX}" + + # Build signature: uint256(uint160(msg.sender)) + bytes32(0) + uint8(1) + PACKED_SIGNATURE="0x${DEPLOYER_ADDRESS_PADDED}000000000000000000000000000000000000000000000000000000000000000001" + + echo "Deployer address: $DEPLOYER_ADDRESS" + echo "Signature (abi.encodePacked format): $PACKED_SIGNATURE" + echo "" + + # setImplementation(uint32,address) + SET_IMPL_CALLDATA=$(cast calldata 'setImplementation(uint32,address)' $TEE_GAME_TYPE $TEE_GAME_IMPL) + echo "setImplementation calldata: $SET_IMPL_CALLDATA" + + echo "Executing execTransaction on Safe (setImplementation)..." + TX_OUTPUT=$(cast send \ + --json \ + --legacy \ + --rpc-url $L1_RPC_URL \ + --private-key $DEPLOYER_PRIVATE_KEY \ + --from $DEPLOYER_ADDRESS \ + $SAFE_ADDRESS \ + 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)' \ + $DISPUTE_GAME_FACTORY_ADDR \ + 0 \ + $SET_IMPL_CALLDATA \ + 0 \ + 0 \ + 0 \ + 0 \ + 0x0000000000000000000000000000000000000000 \ + 0x0000000000000000000000000000000000000000 \ + $PACKED_SIGNATURE) + + TX_HASH=$(echo "$TX_OUTPUT" | jq -r '.transactionHash // empty') + TX_STATUS=$(echo "$TX_OUTPUT" | jq -r '.status // empty') + echo "" + echo "Transaction sent, TX_HASH: $TX_HASH" + + if [ "$TX_STATUS" = "0x1" ] || [ "$TX_STATUS" = "1" ]; then + echo " ✅ setImplementation successful!" + else + echo " ❌ Transaction failed with status: $TX_STATUS" + echo "Full output: $TX_OUTPUT" + exit 1 + fi + echo "" + + # setInitBond(uint32,uint256) + SET_BOND_CALLDATA=$(cast calldata 'setInitBond(uint32,uint256)' $TEE_GAME_TYPE $INIT_BOND) + echo "setInitBond calldata: $SET_BOND_CALLDATA" + + echo "Executing execTransaction on Safe (setInitBond)..." + TX_OUTPUT=$(cast send \ + --json \ + --legacy \ + --rpc-url $L1_RPC_URL \ + --private-key $DEPLOYER_PRIVATE_KEY \ + --from $DEPLOYER_ADDRESS \ + $SAFE_ADDRESS \ + 'execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)' \ + $DISPUTE_GAME_FACTORY_ADDR \ + 0 \ + $SET_BOND_CALLDATA \ + 0 \ + 0 \ + 0 \ + 0 \ + 0x0000000000000000000000000000000000000000 \ + 0x0000000000000000000000000000000000000000 \ + $PACKED_SIGNATURE) + + TX_HASH=$(echo "$TX_OUTPUT" | jq -r '.transactionHash // empty') + TX_STATUS=$(echo "$TX_OUTPUT" | jq -r '.status // empty') + echo "" + echo "Transaction sent, TX_HASH: $TX_HASH" + + if [ "$TX_STATUS" = "0x1" ] || [ "$TX_STATUS" = "1" ]; then + echo " ✅ setInitBond successful!" + else + echo " ❌ Transaction failed with status: $TX_STATUS" + echo "Full output: $TX_OUTPUT" + exit 1 + fi + echo "" +} + +# ── Main execution ───────────────────────────────────────────────────────────── +if [ "${BASH_SOURCE[0]}" == "${0}" ]; then + # 1. Deploy contracts via forge script + deploy_tee_contracts + + # 2. setRespectedGameType(1960) on new ASR (deployer is guardian on devnet) + set_respected_game_type + + # 3. setImplementation + setInitBond on existing DGF via TRANSACTOR or Safe + if [ "$OWNER_TYPE" = "transactor" ]; then + add_tee_game_type_via_transactor + elif [ "$OWNER_TYPE" = "safe" ]; then + add_tee_game_type_via_safe + fi + + # 4. Verify + echo "=== Verifying TeeDisputeGame type was registered ===" + REGISTERED_IMPL=$(cast call --rpc-url $L1_RPC_URL $DISPUTE_GAME_FACTORY_ADDR 'gameImpls(uint32)(address)' $TEE_GAME_TYPE) + REGISTERED_BOND=$(cast call --rpc-url $L1_RPC_URL $DISPUTE_GAME_FACTORY_ADDR 'initBonds(uint32)(uint256)' $TEE_GAME_TYPE) + + if [ "$REGISTERED_IMPL" != "0x0000000000000000000000000000000000000000" ]; then + echo " ✅ Success! TeeDisputeGame type $TEE_GAME_TYPE registered." + echo " Implementation: $REGISTERED_IMPL" + else + echo " ❌ Warning: Could not verify game type was registered. Check transaction status." + fi + + if [ "$REGISTERED_BOND" = "$INIT_BOND" ]; then + echo " ✅ initBond correctly set to $REGISTERED_BOND wei." + else + echo " ❌ Warning: initBond mismatch — expected $INIT_BOND, got $REGISTERED_BOND." + fi + + echo "" + echo "========================================" + echo " TEE Game Type — Final Summary" + echo "========================================" + echo " Game Type: $TEE_GAME_TYPE" + echo " TeeDisputeGame impl: $TEE_GAME_IMPL" + echo " New AnchorStateRegistry: $NEW_ASR_ADDR" + echo " Existing DGF: $DISPUTE_GAME_FACTORY_ADDR" + echo "========================================" + echo "" + echo "Verify:" + echo " cast call $DISPUTE_GAME_FACTORY_ADDR 'gameImpls(uint32)(address)' 1960" + echo " cast call $NEW_ASR_ADDR 'respectedGameType()(uint32)'" + echo " ✅ add-tee-game-type completed successfully." +fi From 7a0118a5bbaab63bacf6f40794eb53b4575b4736 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Fri, 20 Mar 2026 18:44:58 +0800 Subject: [PATCH 02/30] fix check bug --- devnet/scripts/add-tee-game-type.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devnet/scripts/add-tee-game-type.sh b/devnet/scripts/add-tee-game-type.sh index dddc2d4..107f27c 100755 --- a/devnet/scripts/add-tee-game-type.sh +++ b/devnet/scripts/add-tee-game-type.sh @@ -367,7 +367,7 @@ if [ "${BASH_SOURCE[0]}" == "${0}" ]; then # 4. Verify echo "=== Verifying TeeDisputeGame type was registered ===" REGISTERED_IMPL=$(cast call --rpc-url $L1_RPC_URL $DISPUTE_GAME_FACTORY_ADDR 'gameImpls(uint32)(address)' $TEE_GAME_TYPE) - REGISTERED_BOND=$(cast call --rpc-url $L1_RPC_URL $DISPUTE_GAME_FACTORY_ADDR 'initBonds(uint32)(uint256)' $TEE_GAME_TYPE) + REGISTERED_BOND=$(cast call --rpc-url $L1_RPC_URL $DISPUTE_GAME_FACTORY_ADDR 'initBonds(uint32)(uint256)' $TEE_GAME_TYPE | awk '{print $1}') if [ "$REGISTERED_IMPL" != "0x0000000000000000000000000000000000000000" ]; then echo " ✅ Success! TeeDisputeGame type $TEE_GAME_TYPE registered." From 53c5781efe64fc80fb875e5d9daa9ab25a58333d Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Mon, 23 Mar 2026 15:55:07 +0800 Subject: [PATCH 03/30] add time and register logic --- devnet/scripts/add-tee-game-type.sh | 103 +++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 11 deletions(-) diff --git a/devnet/scripts/add-tee-game-type.sh b/devnet/scripts/add-tee-game-type.sh index 107f27c..a8d0dcc 100755 --- a/devnet/scripts/add-tee-game-type.sh +++ b/devnet/scripts/add-tee-game-type.sh @@ -2,11 +2,18 @@ # add-tee-game-type.sh — Register TeeDisputeGame on an existing devnet # # Usage: -# ./scripts/add-tee-game-type.sh [--mock-verifier] [/path/to/tee-contracts] +# ./scripts/add-tee-game-type.sh [FLAGS] [/path/to/tee-contracts] # # Flags: -# --mock-verifier Deploy MockTeeProofVerifier instead of TeeProofVerifier. -# Use when you want a fully mock verifier (no ECDSA signing needed). +# --mock-verifier Deploy MockTeeProofVerifier instead of TeeProofVerifier. +# Must be combined with --enclave
. +# --enclave Enclave address to register as a valid signer on the +# MockTeeProofVerifier via setRegistered(). Required when +# --mock-verifier is set. +# --max-challenge-duration Override MAX_CHALLENGE_DURATION (seconds). Falls back to +# MAX_CLOCK_DURATION env var, then default 20. +# --max-prove-duration Override MAX_PROVE_DURATION (seconds). Falls back to +# MAX_CLOCK_DURATION env var, then default 20. # # If no path is given, defaults to /tee-contracts. # You can also set TEE_CONTRACTS_DIR explicitly: @@ -16,12 +23,37 @@ set -e # ── Parse flags ─────────────────────────────────────────────────────────────── USE_MOCK_VERIFIER=false +MOCK_ENCLAVE_ADDRESS="" +ARG_MAX_CHALLENGE_DURATION="" +ARG_MAX_PROVE_DURATION="" POSITIONAL_ARGS=() -for arg in "$@"; do +i=1 +while [ $i -le $# ]; do + arg="${!i}" case "$arg" in - --mock-verifier) USE_MOCK_VERIFIER=true ;; + --mock-verifier) + USE_MOCK_VERIFIER=true + ;; + --enclave) + i=$((i + 1)) + next="${!i}" + if [ -z "$next" ] || [[ "$next" == --* ]]; then + echo "❌ --enclave requires an address argument" + exit 1 + fi + MOCK_ENCLAVE_ADDRESS="$next" + ;; + --max-challenge-duration) + i=$((i + 1)) + ARG_MAX_CHALLENGE_DURATION="${!i}" + ;; + --max-prove-duration) + i=$((i + 1)) + ARG_MAX_PROVE_DURATION="${!i}" + ;; *) POSITIONAL_ARGS+=("$arg") ;; esac + i=$((i + 1)) done # Source environment variables @@ -75,8 +107,8 @@ export SYSTEM_CONFIG_ADDRESS="$SYSTEM_CONFIG_PROXY_ADDRESS" export DISPUTE_GAME_FINALITY_DELAY_SECONDS="${DISPUTE_GAME_FINALITY_DELAY_SECONDS:-5}" # TEE game timing — reuse devnet values for fast iteration -export MAX_CHALLENGE_DURATION="${MAX_CLOCK_DURATION:-20}" -export MAX_PROVE_DURATION="${MAX_CLOCK_DURATION:-20}" +export MAX_CHALLENGE_DURATION="${ARG_MAX_CHALLENGE_DURATION:-${MAX_CLOCK_DURATION:-20}}" +export MAX_PROVE_DURATION="${ARG_MAX_PROVE_DURATION:-${MAX_CLOCK_DURATION:-20}}" # Bond defaults (0.01 ETH) and access-manager fallback timeout (1 hour) export CHALLENGER_BOND="${CHALLENGER_BOND:-10000000000000000}" @@ -89,8 +121,13 @@ else unset PROPOSER_ADDRESS fi +if [ "$USE_MOCK_VERIFIER" = "true" ] && [ -z "$MOCK_ENCLAVE_ADDRESS" ]; then + echo "❌ --mock-verifier requires --enclave
(e.g. --mock-verifier --enclave 0xABC...)" + exit 1 +fi + export USE_MOCK_VERIFIER -echo "=== Verifier mode: $([ "$USE_MOCK_VERIFIER" = "true" ] && echo "MockTeeProofVerifier (mock)" || echo "TeeProofVerifier + MockRiscZeroVerifier") ===" +echo "=== Verifier mode: $([ "$USE_MOCK_VERIFIER" = "true" ] && echo "MockTeeProofVerifier (mock), enclave: $MOCK_ENCLAVE_ADDRESS" || echo "TeeProofVerifier + MockRiscZeroVerifier") ===" # ── Function: deploy TEE contracts via forge script ─────────────────────────── deploy_tee_contracts() { @@ -144,6 +181,41 @@ deploy_tee_contracts() { fi } +# ── Function: register enclave with MockTeeProofVerifier ────────────────────── +register_enclave_with_mock_verifier() { + echo "=== Registering enclave address with MockTeeProofVerifier ===" + + # Query the game impl to get the mock verifier address + MOCK_VERIFIER_ADDR=$(cast call --rpc-url "$L1_RPC_URL" "$TEE_GAME_IMPL" 'teeProofVerifier()(address)') + echo " MockTeeProofVerifier: $MOCK_VERIFIER_ADDR" + echo " Enclave address: $MOCK_ENCLAVE_ADDRESS" + echo "" + + TX_OUTPUT=$(cast send \ + --json \ + --legacy \ + --rpc-url "$L1_RPC_URL" \ + --private-key "$DEPLOYER_PRIVATE_KEY" \ + --from "$(cast wallet address --private-key "$DEPLOYER_PRIVATE_KEY")" \ + "$MOCK_VERIFIER_ADDR" \ + 'setRegistered(address,bool)' \ + "$MOCK_ENCLAVE_ADDRESS" \ + true) + + TX_HASH=$(echo "$TX_OUTPUT" | jq -r '.transactionHash // empty') + TX_STATUS=$(echo "$TX_OUTPUT" | jq -r '.status // empty') + echo "Transaction sent, TX_HASH: $TX_HASH" + + if [ "$TX_STATUS" = "0x1" ] || [ "$TX_STATUS" = "1" ]; then + echo " ✅ setRegistered($MOCK_ENCLAVE_ADDRESS, true) completed successfully" + else + echo " ❌ Transaction failed with status: $TX_STATUS" + echo "Full output: $TX_OUTPUT" + exit 1 + fi + echo "" +} + # ── Function: setRespectedGameType on new ASR ───────────────────────────────── set_respected_game_type() { echo "=== Setting Respected Game Type to $TEE_GAME_TYPE on new ASR ===" @@ -354,17 +426,22 @@ if [ "${BASH_SOURCE[0]}" == "${0}" ]; then # 1. Deploy contracts via forge script deploy_tee_contracts - # 2. setRespectedGameType(1960) on new ASR (deployer is guardian on devnet) + # 2. If mock verifier, register the enclave address on the mock verifier + if [ "$USE_MOCK_VERIFIER" = "true" ]; then + register_enclave_with_mock_verifier + fi + + # 3. setRespectedGameType(1960) on new ASR (deployer is guardian on devnet) set_respected_game_type - # 3. setImplementation + setInitBond on existing DGF via TRANSACTOR or Safe + # 4. setImplementation + setInitBond on existing DGF via TRANSACTOR or Safe if [ "$OWNER_TYPE" = "transactor" ]; then add_tee_game_type_via_transactor elif [ "$OWNER_TYPE" = "safe" ]; then add_tee_game_type_via_safe fi - # 4. Verify + # 5. Verify echo "=== Verifying TeeDisputeGame type was registered ===" REGISTERED_IMPL=$(cast call --rpc-url $L1_RPC_URL $DISPUTE_GAME_FACTORY_ADDR 'gameImpls(uint32)(address)' $TEE_GAME_TYPE) REGISTERED_BOND=$(cast call --rpc-url $L1_RPC_URL $DISPUTE_GAME_FACTORY_ADDR 'initBonds(uint32)(uint256)' $TEE_GAME_TYPE | awk '{print $1}') @@ -390,6 +467,10 @@ if [ "${BASH_SOURCE[0]}" == "${0}" ]; then echo " TeeDisputeGame impl: $TEE_GAME_IMPL" echo " New AnchorStateRegistry: $NEW_ASR_ADDR" echo " Existing DGF: $DISPUTE_GAME_FACTORY_ADDR" + if [ "$USE_MOCK_VERIFIER" = "true" ]; then + echo " Mock Verifier: $MOCK_VERIFIER_ADDR" + echo " Enclave address: $MOCK_ENCLAVE_ADDRESS" + fi echo "========================================" echo "" echo "Verify:" From 53dace36786808f30dca329b4bc2c24592451394 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Tue, 24 Mar 2026 17:10:05 +0800 Subject: [PATCH 04/30] add list game scripts --- devnet/scripts/list-game.sh | 307 ++++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100755 devnet/scripts/list-game.sh diff --git a/devnet/scripts/list-game.sh b/devnet/scripts/list-game.sh new file mode 100755 index 0000000..e520243 --- /dev/null +++ b/devnet/scripts/list-game.sh @@ -0,0 +1,307 @@ +#!/bin/bash +# Dispute Game List Tool +# List dispute games by game type +# +# Usage: +# ./list-game.sh [game_type] [start_index end_index] +# +# Examples: +# ./list-game.sh # Prompt for game type, show all games +# ./list-game.sh 42 # List all games of type 42 +# ./list-game.sh 1960 # List all games of type 1960 +# ./list-game.sh 42 10 20 # List type 42 games from index 10 to 20 + +set -e + +# ════════════════════════════════════════════════════════════════ +# Configuration +# ════════════════════════════════════════════════════════════════ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEVNET_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Load environment variables +[ -f "$DEVNET_DIR/.env" ] && source "$DEVNET_DIR/.env" + +# Network configuration +FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS:-""} +L1_RPC=${L1_RPC_URL:-"http://localhost:8545"} +L2_RPC=${L2_RPC_URL:-"http://localhost:8123"} + +# Constants +GENESIS_PARENT_INDEX=4294967295 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' +BOLD='\033[1m' + +# Global arrays for caching game data +declare -a GAME_INDICES +declare -a GAME_ADDRS +declare -a GAME_STARTS +declare -a GAME_PARENTS +declare -a GAME_TYPES + +# ════════════════════════════════════════════════════════════════ +# Requirement Checking +# ════════════════════════════════════════════════════════════════ + +check_basic_requirements() { + if ! command -v cast &> /dev/null; then + echo -e "${RED}Error: 'cast' not found. Install Foundry: https://getfoundry.sh${NC}" + exit 1 + fi + + if [ -z "$FACTORY_ADDRESS" ]; then + echo -e "${RED}Error: DISPUTE_GAME_FACTORY_ADDRESS not set in $DEVNET_DIR/.env${NC}" + exit 1 + fi +} + +# ════════════════════════════════════════════════════════════════ +# Helper Functions +# ════════════════════════════════════════════════════════════════ + +# Count transactions in block range [start, end) +count_transactions() { + local start=$1 end=$2 total=0 + for ((block=start; block/dev/null | jq '.transactions | length' 2>/dev/null || echo "0") + total=$((total + count)) + done + echo "$total" +} + +# Get game status +get_game_status() { + local addr=$1 + local claim_hex=$(cast call $addr "claimData()" --rpc-url $L1_RPC 2>/dev/null) + local status_hex="0x$(echo "$claim_hex" | cut -c259-322)" + local status=$(cast --to-dec "$status_hex" 2>/dev/null || echo "0") + + case $status in + 0) echo "Unchallenged|0" ;; + 1) echo "Challenged|1" ;; + 2) echo "Unchal+Proof|2" ;; + 3) echo "Chal+Proof|3" ;; + 4) echo "Resolved|4" ;; + *) echo "Unknown|$status" ;; + esac +} + +# ════════════════════════════════════════════════════════════════ +# Display Functions +# ════════════════════════════════════════════════════════════════ + +show_header() { + local game_type=$1 + echo -e "${BLUE}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE}${BOLD} Dispute Game List Tool${NC}" + echo -e "${BLUE}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo -e " ${BOLD}Factory:${NC} ${FACTORY_ADDRESS:0:10}...${FACTORY_ADDRESS: -8}" + echo -e " ${BOLD}L1 RPC:${NC} $L1_RPC" + echo -e " ${BOLD}L2 RPC:${NC} $L2_RPC" + echo -e " ${BOLD}Game Type:${NC} ${game_type:-All}" + echo "" +} + +# ════════════════════════════════════════════════════════════════ +# Core Functions +# ════════════════════════════════════════════════════════════════ + +list_games() { + local game_type=$1 + local filter_start=$2 + local filter_end=$3 + + local total=$(cast call $FACTORY_ADDRESS "gameCount()(uint256)" --rpc-url $L1_RPC 2>/dev/null) + + if [ -z "$total" ] || [ "$total" = "0" ]; then + echo -e "${YELLOW}No games found.${NC}" + return 1 + fi + + local type_label="${game_type:-All Types}" + if [ -n "$filter_start" ]; then + echo -e "${BLUE}${BOLD} Games (Type $type_label) [Showing index $filter_start-$filter_end]${NC}" + else + echo -e "${BLUE}${BOLD} Games (Type $type_label)${NC}" + fi + echo -e "${CYAN}Total games in factory: $total${NC}" + echo -e "${YELLOW}Loading games of type $type_label...${NC}" + echo "" + + # Clear and collect all matching games + GAME_INDICES=() + GAME_ADDRS=() + GAME_STARTS=() + GAME_PARENTS=() + GAME_TYPES=() + + for ((i=0; i/dev/null) + [ -z "$game_data" ] && continue + + local type=$(echo "$game_data" | grep -oE '\([0-9]+' | head -1 | tr -d '(') + [ -n "$game_type" ] && [ "$type" != "$game_type" ] && continue + + local addr=$(echo "$game_data" | grep -oE '0x[a-fA-F0-9]{40}') + local l2_block=$(cast call $addr "l2BlockNumber()(uint256)" --rpc-url $L1_RPC 2>/dev/null | awk '{print $1}') + [ -z "$l2_block" ] && l2_block=0 + + local parent=$(cast call $addr "parentIndex()(uint32)" --rpc-url $L1_RPC 2>/dev/null | awk '{print $1}') + [ -z "$parent" ] && parent=$GENESIS_PARENT_INDEX + + GAME_INDICES+=("$i") + GAME_ADDRS+=("$addr") + GAME_STARTS+=("$l2_block") + GAME_PARENTS+=("$parent") + GAME_TYPES+=("$type") + done + + local count=${#GAME_INDICES[@]} + + if [ $count -eq 0 ]; then + echo -e "${YELLOW}No games of type ${game_type:-any} found.${NC}" + return 1 + fi + + # Count filtered games + local filtered_count=0 + if [ -n "$filter_start" ]; then + for idx in "${GAME_INDICES[@]}"; do + if [ $idx -ge $filter_start ] && [ $idx -le $filter_end ]; then + filtered_count=$((filtered_count + 1)) + fi + done + else + filtered_count=$count + fi + + if [ -n "$filter_start" ]; then + echo -e "${CYAN}Showing $filtered_count games (filtered from $count total games of type $type_label)${NC}" + else + echo -e "${CYAN}Total games of type $type_label: $count${NC}" + fi + echo "" + + if [ $filtered_count -eq 0 ]; then + echo -e "${YELLOW}No games found in range $filter_start-$filter_end${NC}" + return 1 + fi + + # Table header + printf "${BOLD}%-6s %-8s %-12s %-25s %-10s %-10s %-20s${NC}\n" \ + "Index" "Type" "Parent" "Block Range" "Blocks" "Txs" "Status" + echo "────────────────────────────────────────────────────────────────────────────────────────────────" + + for ((j=count-1; j>=0; j--)); do + local idx=${GAME_INDICES[j]} + + # Apply filter if specified + if [ -n "$filter_start" ]; then + if [ $idx -lt $filter_start ] || [ $idx -gt $filter_end ]; then + continue + fi + fi + + local addr=${GAME_ADDRS[j]} + local end=${GAME_STARTS[j]} + local parent_idx=${GAME_PARENTS[j]} + + # Calculate proof range + local start blocks parent_display + if [ "$parent_idx" = "$GENESIS_PARENT_INDEX" ]; then + start="?" + blocks="?" + parent_display="genesis" + else + parent_display="$parent_idx" + local parent_found=false + for ((k=0; k Date: Tue, 24 Mar 2026 17:13:54 +0800 Subject: [PATCH 05/30] simply --- devnet/scripts/list-game.sh | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/devnet/scripts/list-game.sh b/devnet/scripts/list-game.sh index e520243..9ab82ec 100755 --- a/devnet/scripts/list-game.sh +++ b/devnet/scripts/list-game.sh @@ -66,16 +66,6 @@ check_basic_requirements() { # Helper Functions # ════════════════════════════════════════════════════════════════ -# Count transactions in block range [start, end) -count_transactions() { - local start=$1 end=$2 total=0 - for ((block=start; block/dev/null | jq '.transactions | length' 2>/dev/null || echo "0") - total=$((total + count)) - done - echo "$total" -} - # Get game status get_game_status() { local addr=$1 @@ -196,9 +186,9 @@ list_games() { fi # Table header - printf "${BOLD}%-6s %-8s %-12s %-25s %-10s %-10s %-20s${NC}\n" \ - "Index" "Type" "Parent" "Block Range" "Blocks" "Txs" "Status" - echo "────────────────────────────────────────────────────────────────────────────────────────────────" + printf "${BOLD}%-6s %-8s %-12s %-25s %-10s %-20s${NC}\n" \ + "Index" "Type" "Parent" "Block Range" "Blocks" "Status" + echo "────────────────────────────────────────────────────────────────────────────────────────" for ((j=count-1; j>=0; j--)); do local idx=${GAME_INDICES[j]} @@ -237,20 +227,12 @@ list_games() { fi fi - # Count transactions - local txs - if [ "$start" = "?" ] || [ "$end" = "?" ]; then - txs="?" - else - txs=$(count_transactions $start $end) - fi - # Get status local status_info=$(get_game_status $addr) local status_text=$(echo "$status_info" | cut -d'|' -f1) - printf "%-6s %-8s %-12s %-25s %-10s %-10s %-20s\n" \ - "$idx" "${GAME_TYPES[j]}" "$parent_display" "$start-$end" "$blocks" "$txs" "$status_text" + printf "%-6s %-8s %-12s %-25s %-10s %-20s\n" \ + "$idx" "${GAME_TYPES[j]}" "$parent_display" "$start-$end" "$blocks" "$status_text" done echo "" From 12e5c0d1e2655794aa9f52c51bf8dfefee485f74 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Tue, 24 Mar 2026 17:59:35 +0800 Subject: [PATCH 06/30] port --- devnet/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devnet/docker-compose.yml b/devnet/docker-compose.yml index 0ead9d9..5a509eb 100644 --- a/devnet/docker-compose.yml +++ b/devnet/docker-compose.yml @@ -935,7 +935,7 @@ services: volumes: - ./op-succinct/.env.challenger:/app/.env ports: - - "9001:9001" + - "9002:9001" command: - challenger - --env-file=/app/.env From 3a057c4dd772f412404e104ca51d74acd0df374b Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Tue, 24 Mar 2026 18:19:55 +0800 Subject: [PATCH 07/30] anchor state --- devnet/5-run-op-succinct.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/devnet/5-run-op-succinct.sh b/devnet/5-run-op-succinct.sh index 1e11dcc..eb0d637 100755 --- a/devnet/5-run-op-succinct.sh +++ b/devnet/5-run-op-succinct.sh @@ -74,12 +74,21 @@ sed_inplace "s|^MOCK_MODE=.*|MOCK_MODE=$PROOF_MOCK_MODE|" "$OP_SUCCINCT_DIR"/.en sed_inplace "s|^L1_RPC=.*|L1_RPC=$L1_RPC_URL_IN_DOCKER|" "$OP_SUCCINCT_DIR"/.env.challenger sed_inplace "s|^L2_RPC=.*|L2_RPC=$L2_RPC_URL_IN_DOCKER|" "$OP_SUCCINCT_DIR"/.env.challenger sed_inplace "s|^FACTORY_ADDRESS=.*|FACTORY_ADDRESS=$DISPUTE_GAME_FACTORY_ADDRESS|" "$OP_SUCCINCT_DIR"/.env.challenger +grep -q "^RUST_LOG=" "$OP_SUCCINCT_DIR"/.env.challenger || echo "RUST_LOG=info" >> "$OP_SUCCINCT_DIR"/.env.challenger docker compose up op-succinct-fetch-config OP_DEPLOYER_ADDR=$(cast wallet a "$DEPLOYER_PRIVATE_KEY") cast send --private-key "$RICH_L1_PRIVATE_KEY" --value 1ether "$OP_DEPLOYER_ADDR" --legacy --rpc-url "$L1_RPC_URL" docker compose up op-succinct-contracts +# Update ANCHOR_STATE_REGISTRY_ADDRESS in .env.proposer with the address from the newly deployed game implementation +NEW_GAME_IMPL=$(cast call "$DISPUTE_GAME_FACTORY_ADDRESS" 'gameImpls(uint32)(address)' 42 -r "$L1_RPC_URL") +NEW_ANCHOR_STATE_REGISTRY=$(cast call "$NEW_GAME_IMPL" 'anchorStateRegistry()(address)' -r "$L1_RPC_URL") +sed_inplace "s|^ANCHOR_STATE_REGISTRY_ADDRESS=.*|ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY|" "$OP_SUCCINCT_DIR"/.env.proposer +grep -q "^ANCHOR_STATE_REGISTRY_ADDRESS=" "$OP_SUCCINCT_DIR"/.env.challenger \ + && sed_inplace "s|^ANCHOR_STATE_REGISTRY_ADDRESS=.*|ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY|" "$OP_SUCCINCT_DIR"/.env.challenger \ + || echo "ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY" >> "$OP_SUCCINCT_DIR"/.env.challenger + cast send "$ANCHOR_STATE_REGISTRY" "setRespectedGameType(uint32)" 42 --private-key="$DEPLOYER_PRIVATE_KEY" TARGET_HEIGHT=$(cast call "$ANCHOR_STATE_REGISTRY" "getAnchorRoot()(bytes32,uint256)" --json | jq -r '.[1]') From 11319ee8f98745d060fd72b2853bdfe1bafcc4a0 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Tue, 24 Mar 2026 18:21:41 +0800 Subject: [PATCH 08/30] compose --- devnet/5-run-op-succinct.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devnet/5-run-op-succinct.sh b/devnet/5-run-op-succinct.sh index eb0d637..b6c4857 100755 --- a/devnet/5-run-op-succinct.sh +++ b/devnet/5-run-op-succinct.sh @@ -110,8 +110,8 @@ docker compose up -d op-succinct-proposer echo " ✓ Proposer started" if [ "$MIN_RUN" = "false" ]; then - docker-compose down op-proposer - docker-compose down op-challenger + docker compose down op-proposer + docker compose down op-challenger echo " ✓ Older proposer and challenger stopped" fi From 8b8d757fa1ca0bfafd4e66ae1782600e349d7ff1 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Tue, 24 Mar 2026 18:24:44 +0800 Subject: [PATCH 09/30] proposer --- devnet/5-run-op-succinct.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devnet/5-run-op-succinct.sh b/devnet/5-run-op-succinct.sh index b6c4857..7cf83a2 100755 --- a/devnet/5-run-op-succinct.sh +++ b/devnet/5-run-op-succinct.sh @@ -84,7 +84,9 @@ docker compose up op-succinct-contracts # Update ANCHOR_STATE_REGISTRY_ADDRESS in .env.proposer with the address from the newly deployed game implementation NEW_GAME_IMPL=$(cast call "$DISPUTE_GAME_FACTORY_ADDRESS" 'gameImpls(uint32)(address)' 42 -r "$L1_RPC_URL") NEW_ANCHOR_STATE_REGISTRY=$(cast call "$NEW_GAME_IMPL" 'anchorStateRegistry()(address)' -r "$L1_RPC_URL") -sed_inplace "s|^ANCHOR_STATE_REGISTRY_ADDRESS=.*|ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY|" "$OP_SUCCINCT_DIR"/.env.proposer +grep -q "^ANCHOR_STATE_REGISTRY_ADDRESS=" "$OP_SUCCINCT_DIR"/.env.proposer \ + && sed_inplace "s|^ANCHOR_STATE_REGISTRY_ADDRESS=.*|ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY|" "$OP_SUCCINCT_DIR"/.env.proposer \ + || echo "ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY" >> "$OP_SUCCINCT_DIR"/.env.proposer grep -q "^ANCHOR_STATE_REGISTRY_ADDRESS=" "$OP_SUCCINCT_DIR"/.env.challenger \ && sed_inplace "s|^ANCHOR_STATE_REGISTRY_ADDRESS=.*|ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY|" "$OP_SUCCINCT_DIR"/.env.challenger \ || echo "ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY" >> "$OP_SUCCINCT_DIR"/.env.challenger From 739e8bb6a807389377a1894af10764b7aa373ebe Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Tue, 24 Mar 2026 18:40:39 +0800 Subject: [PATCH 10/30] optimize list game --- devnet/scripts/list-game.sh | 125 ++++++++++-------------------------- 1 file changed, 35 insertions(+), 90 deletions(-) diff --git a/devnet/scripts/list-game.sh b/devnet/scripts/list-game.sh index 9ab82ec..9992ac4 100755 --- a/devnet/scripts/list-game.sh +++ b/devnet/scripts/list-game.sh @@ -39,12 +39,10 @@ CYAN='\033[0;36m' NC='\033[0m' BOLD='\033[1m' -# Global arrays for caching game data -declare -a GAME_INDICES -declare -a GAME_ADDRS -declare -a GAME_STARTS -declare -a GAME_PARENTS -declare -a GAME_TYPES +# Maps indexed by game index, populated during list_games pass 1 +declare -A BLOCK_BY_INDEX # index -> l2BlockNumber +declare -A ADDR_BY_INDEX # index -> contract address +declare -A TYPE_BY_INDEX # index -> game type # ════════════════════════════════════════════════════════════════ # Requirement Checking @@ -123,17 +121,18 @@ list_games() { echo -e "${BLUE}${BOLD} Games (Type $type_label)${NC}" fi echo -e "${CYAN}Total games in factory: $total${NC}" - echo -e "${YELLOW}Loading games of type $type_label...${NC}" echo "" - # Clear and collect all matching games - GAME_INDICES=() - GAME_ADDRS=() - GAME_STARTS=() - GAME_PARENTS=() - GAME_TYPES=() + # Table header + printf "${BOLD}%-6s %-8s %-12s %-25s %-10s %-20s${NC}\n" \ + "Index" "Type" "Parent" "Block Range" "Blocks" "Status" + echo "────────────────────────────────────────────────────────────────────────────────────────" + + BLOCK_BY_INDEX=() + local count=0 - for ((i=0; i=0; i--)); do local game_data=$(cast call $FACTORY_ADDRESS "gameAtIndex(uint256)((uint32,uint64,address))" $i --rpc-url $L1_RPC 2>/dev/null) [ -z "$game_data" ] && continue @@ -143,99 +142,45 @@ list_games() { local addr=$(echo "$game_data" | grep -oE '0x[a-fA-F0-9]{40}') local l2_block=$(cast call $addr "l2BlockNumber()(uint256)" --rpc-url $L1_RPC 2>/dev/null | awk '{print $1}') [ -z "$l2_block" ] && l2_block=0 + BLOCK_BY_INDEX[$i]=$l2_block - local parent=$(cast call $addr "parentIndex()(uint32)" --rpc-url $L1_RPC 2>/dev/null | awk '{print $1}') - [ -z "$parent" ] && parent=$GENESIS_PARENT_INDEX - - GAME_INDICES+=("$i") - GAME_ADDRS+=("$addr") - GAME_STARTS+=("$l2_block") - GAME_PARENTS+=("$parent") - GAME_TYPES+=("$type") - done - - local count=${#GAME_INDICES[@]} - - if [ $count -eq 0 ]; then - echo -e "${YELLOW}No games of type ${game_type:-any} found.${NC}" - return 1 - fi - - # Count filtered games - local filtered_count=0 - if [ -n "$filter_start" ]; then - for idx in "${GAME_INDICES[@]}"; do - if [ $idx -ge $filter_start ] && [ $idx -le $filter_end ]; then - filtered_count=$((filtered_count + 1)) - fi - done - else - filtered_count=$count - fi - - if [ -n "$filter_start" ]; then - echo -e "${CYAN}Showing $filtered_count games (filtered from $count total games of type $type_label)${NC}" - else - echo -e "${CYAN}Total games of type $type_label: $count${NC}" - fi - echo "" - - if [ $filtered_count -eq 0 ]; then - echo -e "${YELLOW}No games found in range $filter_start-$filter_end${NC}" - return 1 - fi - - # Table header - printf "${BOLD}%-6s %-8s %-12s %-25s %-10s %-20s${NC}\n" \ - "Index" "Type" "Parent" "Block Range" "Blocks" "Status" - echo "────────────────────────────────────────────────────────────────────────────────────────" - - for ((j=count-1; j>=0; j--)); do - local idx=${GAME_INDICES[j]} - - # Apply filter if specified if [ -n "$filter_start" ]; then - if [ $idx -lt $filter_start ] || [ $idx -gt $filter_end ]; then - continue - fi + [ $i -lt $filter_start ] || [ $i -gt $filter_end ] && continue fi - local addr=${GAME_ADDRS[j]} - local end=${GAME_STARTS[j]} - local parent_idx=${GAME_PARENTS[j]} + local parent=$(cast call $addr "parentIndex()(uint32)" --rpc-url $L1_RPC 2>/dev/null | awk '{print $1}') + [ -z "$parent" ] && parent=$GENESIS_PARENT_INDEX - # Calculate proof range local start blocks parent_display - if [ "$parent_idx" = "$GENESIS_PARENT_INDEX" ]; then - start="?" - blocks="?" - parent_display="genesis" + if [ "$parent" = "$GENESIS_PARENT_INDEX" ]; then + start="?"; blocks="?"; parent_display="genesis" else - parent_display="$parent_idx" - local parent_found=false - for ((k=0; k/dev/null) + local pa=$(echo "$pd" | grep -oE '0x[a-fA-F0-9]{40}') + local pb=$(cast call $pa "l2BlockNumber()(uint256)" --rpc-url $L1_RPC 2>/dev/null | awk '{print $1}') + BLOCK_BY_INDEX[$parent]=${pb:-0} fi + start=${BLOCK_BY_INDEX[$parent]} + blocks=$((l2_block - start)) fi - # Get status local status_info=$(get_game_status $addr) local status_text=$(echo "$status_info" | cut -d'|' -f1) printf "%-6s %-8s %-12s %-25s %-10s %-20s\n" \ - "$idx" "${GAME_TYPES[j]}" "$parent_display" "$start-$end" "$blocks" "$status_text" + "$i" "$type" "$parent_display" "$start-$l2_block" "$blocks" "$status_text" + count=$((count + 1)) done echo "" + if [ $count -eq 0 ]; then + echo -e "${YELLOW}No games of type ${game_type:-any} found.${NC}" + return 1 + fi + echo -e "${CYAN}Total: $count games${NC}" return 0 } From 525f512e1f901388ed8ec037ad87fe684cc3804e Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Wed, 25 Mar 2026 14:56:00 +0800 Subject: [PATCH 11/30] add mock tee rpc tool --- tools/mockteerpc/Dockerfile | 21 ++ tools/mockteerpc/Makefile | 26 ++ tools/mockteerpc/README.md | 139 +++++++++++ tools/mockteerpc/cmd/mockteerpc/main.go | 163 +++++++++++++ tools/mockteerpc/go.mod | 20 ++ tools/mockteerpc/go.sum | 22 ++ tools/mockteerpc/mock_tee_rollup_server.go | 227 ++++++++++++++++++ .../mockteerpc/mock_tee_rollup_server_test.go | 62 +++++ 8 files changed, 680 insertions(+) create mode 100644 tools/mockteerpc/Dockerfile create mode 100644 tools/mockteerpc/Makefile create mode 100644 tools/mockteerpc/README.md create mode 100644 tools/mockteerpc/cmd/mockteerpc/main.go create mode 100644 tools/mockteerpc/go.mod create mode 100644 tools/mockteerpc/go.sum create mode 100644 tools/mockteerpc/mock_tee_rollup_server.go create mode 100644 tools/mockteerpc/mock_tee_rollup_server_test.go diff --git a/tools/mockteerpc/Dockerfile b/tools/mockteerpc/Dockerfile new file mode 100644 index 0000000..79f75e9 --- /dev/null +++ b/tools/mockteerpc/Dockerfile @@ -0,0 +1,21 @@ +FROM golang:1.23-alpine AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o mockteerpc ./cmd/mockteerpc + +FROM alpine:3.21 + +RUN apk --no-cache add ca-certificates + +WORKDIR /app +COPY --from=builder /app/mockteerpc . + +EXPOSE 8090 + +ENTRYPOINT ["./mockteerpc"] +CMD ["--addr", ":8090"] diff --git a/tools/mockteerpc/Makefile b/tools/mockteerpc/Makefile new file mode 100644 index 0000000..f3af958 --- /dev/null +++ b/tools/mockteerpc/Makefile @@ -0,0 +1,26 @@ +BINARY := mockteerpc +IMAGE := mockteerpc +TAG ?= latest + +.PHONY: build install run test docker-build docker-run clean + +build: + go build -o bin/$(BINARY) ./cmd/mockteerpc + +install: + go install ./cmd/mockteerpc + +run: + go run ./cmd/mockteerpc + +test: + go test ./... + +docker-build: + docker build -t $(IMAGE):$(TAG) . + +docker-run: + docker run --rm -p 8090:8090 $(IMAGE):$(TAG) + +clean: + rm -rf bin/ diff --git a/tools/mockteerpc/README.md b/tools/mockteerpc/README.md new file mode 100644 index 0000000..576815d --- /dev/null +++ b/tools/mockteerpc/README.md @@ -0,0 +1,139 @@ +# mockteerpc + +Standalone mock TeeRollup HTTP server for local development and testing. + +--- + +## Mock TeeRollup Server + +Simulates the `GET /v1/chain/confirmed_block_info` REST endpoint provided by a real TeeRollup service. + +**Behavior:** +- Starts at block height 1000 (configurable) +- Increments height by a random delta in **[1, 50]** every second +- `appHash` = `keccak256(big-endian uint64 of height)`, `"0x"` prefix, 66 characters +- `blockHash` = `keccak256(appHash)`, `"0x"` prefix, 66 characters + +--- + +## How to Run + +### Option 1: Direct `go run` + +```bash +cd tools/mockteerpc +go run ./cmd/mockteerpc + +# Custom listen address and initial height +go run ./cmd/mockteerpc --addr :9000 --init-height 5000 + +# 30% error rate + max 500ms delay +go run ./cmd/mockteerpc --error-rate 0.3 --delay 500ms +``` + +### Option 2: Build then run + +```bash +cd tools/mockteerpc +make build +./bin/mockteerpc --addr :8090 +``` + +### Option 3: Docker + +```bash +cd tools/mockteerpc + +# Build image +make docker-build + +# Run container (exposes :8090) +make docker-run + +# Custom flags +docker run --rm -p 9000:9000 mockteerpc:latest --addr :9000 --init-height 5000 +``` + +Startup output example: +``` +mock TeeRollup server listening on :8090 +initial height: 1000 +error rate: 0.0% +max delay: 1s +endpoint: GET /v1/chain/confirmed_block_info + +tick: height=1023 delta=23 +tick: height=1058 delta=35 +... +``` + +--- + +## curl Testing + +```bash +# Query current confirmed block info +curl -s http://localhost:8090/v1/chain/confirmed_block_info | jq . +``` + +Example response: +```json +{ + "code": 0, + "message": "OK", + "data": { + "height": 1023, + "appHash": "0x3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b", + "blockHash": "0x1234abcd..." + } +} +``` + +### Observe height growth continuously + +```bash +watch -n 0.5 'curl -s http://localhost:8090/v1/chain/confirmed_block_info | jq .data' +``` + +--- + +## Usage in Tests + +```go +import mockteerpc "github.com/okx/xlayer-toolkit/tools/mockteerpc" + +func TestMyFeature(t *testing.T) { + srv := mockteerpc.NewTeeRollupServer(t) // t.Cleanup closes automatically + + baseURL := srv.Addr() // e.g. "http://127.0.0.1:12345" + + height, appHash, blockHash := srv.CurrentInfo() + _ = height + _ = appHash + _ = blockHash +} +``` + +--- + +## CLI flags + +| Flag | Default | Description | +|----------------|---------|--------------------------------------------------------------------------| +| `--addr` | `:8090` | Listen address | +| `--init-height`| `1000` | Initial block height | +| `--error-rate` | `0` | Error response probability [0.0, 1.0], 0 means no errors | +| `--delay` | `1s` | Maximum random response delay, actual delay is random in [0, delay] | + +--- + +## Makefile targets + +| Target | Description | +|----------------|------------------------------------| +| `make build` | Build binary to `bin/mockteerpc` | +| `make run` | Run via `go run` | +| `make test` | Run all tests | +| `make docker-build` | Build Docker image | +| `make docker-run` | Run Docker container on :8090 | +| `make clean` | Remove `bin/` directory | diff --git a/tools/mockteerpc/cmd/mockteerpc/main.go b/tools/mockteerpc/cmd/mockteerpc/main.go new file mode 100644 index 0000000..d074483 --- /dev/null +++ b/tools/mockteerpc/cmd/mockteerpc/main.go @@ -0,0 +1,163 @@ +// Command mockteerpc runs a standalone mock TeeRollup HTTP server for local development and curl testing. +// +// Usage: +// +// go run ./mock/cmd/mockteerpc [--addr :8090] +package main + +import ( + "encoding/binary" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "log" + "math/rand" + "net/http" + "sync" + "time" + + "github.com/ethereum/go-ethereum/crypto" +) + +type response struct { + Code int `json:"code"` + Message string `json:"message"` + Data *data `json:"data"` +} + +type data struct { + Height uint64 `json:"height"` + AppHash string `json:"appHash"` + BlockHash string `json:"blockHash"` +} + +type server struct { + mu sync.RWMutex + height uint64 + errorRate float64 + maxDelay time.Duration +} + +func (s *server) tick() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for range ticker.C { + delta := uint64(rand.Intn(50) + 1) + s.mu.Lock() + s.height += delta + s.mu.Unlock() + s.mu.RLock() + log.Printf("tick: height=%d delta=%d", s.height, delta) + s.mu.RUnlock() + } +} + +func computeAppHash(height uint64) [32]byte { + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], height) + return crypto.Keccak256Hash(buf[:]) +} + +func computeBlockHash(appHash [32]byte) [32]byte { + return crypto.Keccak256Hash(appHash[:]) +} + +func (s *server) handleConfirmedBlockInfo(w http.ResponseWriter, r *http.Request) { + start := time.Now() + log.Printf("[mockteerpc] received %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) + + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + // Random delay in [0, maxDelay]. + if s.maxDelay > 0 { + delay := time.Duration(rand.Int63n(int64(s.maxDelay) + 1)) + time.Sleep(delay) + } + + w.Header().Set("Content-Type", "application/json") + + if s.errorRate > 0 && rand.Float64() < s.errorRate { + writeErrorResponse(w) + log.Printf("[mockteerpc] responded with error (took %s)", time.Since(start)) + return + } + + s.mu.RLock() + h := s.height + s.mu.RUnlock() + + appHash := computeAppHash(h) + blockHash := computeBlockHash(appHash) + + appHashStr := "0x" + hex.EncodeToString(appHash[:]) + resp := response{ + Code: 0, + Message: "OK", + Data: &data{ + Height: h, + AppHash: appHashStr, + BlockHash: "0x" + hex.EncodeToString(blockHash[:]), + }, + } + _ = json.NewEncoder(w).Encode(resp) + log.Printf("[mockteerpc] responded height=%d appHash=%s (took %s)", h, appHashStr[:10]+"...", time.Since(start)) +} + +func writeErrorResponse(w http.ResponseWriter) { + type nullableData struct { + Height *uint64 `json:"height"` + AppHash *string `json:"appHash"` + BlockHash *string `json:"blockHash"` + } + type respNoData struct { + Code int `json:"code"` + Message string `json:"message"` + } + type respWithData struct { + Code int `json:"code"` + Message string `json:"message"` + Data *nullableData `json:"data"` + } + + switch rand.Intn(3) { + case 0: // code != 0, no data field + _ = json.NewEncoder(w).Encode(respNoData{Code: 1, Message: "internal server error"}) + case 1: // code == 0, data is null + _ = json.NewEncoder(w).Encode(respWithData{Code: 0, Message: "OK", Data: nil}) + case 2: // code == 0, data present but all fields null + _ = json.NewEncoder(w).Encode(respWithData{Code: 0, Message: "OK", Data: &nullableData{}}) + } +} + +func main() { + addr := flag.String("addr", ":8090", "listen address") + initHeight := flag.Uint64("init-height", 1000, "initial block height") + errorRate := flag.Float64("error-rate", 0, "probability [0.0, 1.0] of returning an error response") + maxDelay := flag.Duration("delay", time.Second, "maximum random response delay (actual delay is random in [0, delay])") + flag.Parse() + + if *errorRate < 0 || *errorRate > 1 { + log.Fatalf("--error-rate must be in [0.0, 1.0], got %f", *errorRate) + } + + s := &server{height: *initHeight, errorRate: *errorRate, maxDelay: *maxDelay} + go s.tick() + + mux := http.NewServeMux() + mux.HandleFunc("/v1/chain/confirmed_block_info", s.handleConfirmedBlockInfo) + + fmt.Printf("mock TeeRollup server listening on %s\n", *addr) + fmt.Printf("initial height: %d\n", *initHeight) + fmt.Printf("error rate: %.1f%%\n", *errorRate*100) + fmt.Printf("max delay: %s\n", *maxDelay) + fmt.Println("endpoint: GET /v1/chain/confirmed_block_info") + fmt.Println() + + if err := http.ListenAndServe(*addr, mux); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/tools/mockteerpc/go.mod b/tools/mockteerpc/go.mod new file mode 100644 index 0000000..3eb0508 --- /dev/null +++ b/tools/mockteerpc/go.mod @@ -0,0 +1,20 @@ +module github.com/okx/xlayer-toolkit/tools/mockteerpc + +go 1.23.0 + +toolchain go1.24.1 + +require ( + github.com/ethereum/go-ethereum v1.15.7 + github.com/stretchr/testify v1.10.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/tools/mockteerpc/go.sum b/tools/mockteerpc/go.sum new file mode 100644 index 0000000..b03df42 --- /dev/null +++ b/tools/mockteerpc/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/go-ethereum v1.15.7 h1:vm1XXruZVnqtODBgqFaTclzP0xAvCvQIDKyFNUA1JpY= +github.com/ethereum/go-ethereum v1.15.7/go.mod h1:+S9k+jFzlyVTNcYGvqFhzN/SFhI6vA+aOY4T5tLSPL0= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/mockteerpc/mock_tee_rollup_server.go b/tools/mockteerpc/mock_tee_rollup_server.go new file mode 100644 index 0000000..4fb2650 --- /dev/null +++ b/tools/mockteerpc/mock_tee_rollup_server.go @@ -0,0 +1,227 @@ +package mockteerpc + +import ( + "encoding/binary" + "encoding/hex" + "encoding/json" + "log" + "math/rand" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" +) + +// TeeRollupResponse is the normal JSON shape returned by GET /v1/chain/confirmed_block_info. +type TeeRollupResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data struct { + Height uint64 `json:"height"` + AppHash string `json:"appHash"` + BlockHash string `json:"blockHash"` + } `json:"data"` +} + +// Option configures a TeeRollupServer. +type Option func(*TeeRollupServer) + +// WithErrorRate sets the probability [0.0, 1.0] that any given RPC call returns an error. +// Three error types are equally likely when an error occurs: +// 1. code != 0, no data field, only message. +// 2. code == 0, data is null. +// 3. code == 0, data is present but all fields (height, appHash, blockHash) are null. +func WithErrorRate(rate float64) Option { + return func(s *TeeRollupServer) { + s.errorRate = rate + } +} + +// WithMaxDelay sets the maximum random response delay. Each request sleeps for a +// random duration in [0, maxDelay]. Default is 1s. +func WithMaxDelay(d time.Duration) Option { + return func(s *TeeRollupServer) { + s.maxDelay = d + } +} + +// TeeRollupServer is a mock TeeRollup HTTP server for testing. +// Height starts at 1000 and increments by a random value in [1, 50] every second. +type TeeRollupServer struct { + server *httptest.Server + mu sync.RWMutex + height uint64 + errorRate float64 + maxDelay time.Duration + stopCh chan struct{} + doneCh chan struct{} + closeOnce sync.Once +} + +// NewTeeRollupServer starts the mock server and its background tick goroutine. +// Close() is registered via t.Cleanup so callers need not call it explicitly. +func NewTeeRollupServer(t *testing.T, opts ...Option) *TeeRollupServer { + t.Helper() + + m := &TeeRollupServer{ + height: 1000, + maxDelay: time.Second, + stopCh: make(chan struct{}), + doneCh: make(chan struct{}), + } + for _, opt := range opts { + opt(m) + } + + mux := http.NewServeMux() + mux.HandleFunc("/v1/chain/confirmed_block_info", m.handleConfirmedBlockInfo) + + m.server = httptest.NewServer(mux) + + go m.tick() + + t.Cleanup(m.Close) + return m +} + +// Addr returns the base URL (scheme + host) of the test server. +func (m *TeeRollupServer) Addr() string { + return m.server.URL +} + +// Close stops the tick goroutine and shuts down the HTTP server. +// Safe to call multiple times. +func (m *TeeRollupServer) Close() { + m.closeOnce.Do(func() { + close(m.stopCh) + <-m.doneCh + m.server.Close() + }) +} + +// CurrentInfo returns the current height, appHash and blockHash snapshot. +// Useful for assertions in tests without making an HTTP round-trip. +func (m *TeeRollupServer) CurrentInfo() (height uint64, appHash, blockHash [32]byte) { + m.mu.RLock() + h := m.height + m.mu.RUnlock() + + appHash = ComputeAppHash(h) + blockHash = ComputeBlockHash(appHash) + return h, appHash, blockHash +} + +// tick increments height by random(1, 50) every second until Close() is called. +func (m *TeeRollupServer) tick() { + defer close(m.doneCh) + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-m.stopCh: + return + case <-ticker.C: + delta := uint64(rand.Intn(50) + 1) // [1, 50] + m.mu.Lock() + m.height += delta + m.mu.Unlock() + } + } +} + +// handleConfirmedBlockInfo serves GET /v1/chain/confirmed_block_info. +func (m *TeeRollupServer) handleConfirmedBlockInfo(w http.ResponseWriter, r *http.Request) { + start := time.Now() + log.Printf("[mockteerpc] received %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) + + // Random delay in [0, maxDelay]. + if m.maxDelay > 0 { + delay := time.Duration(rand.Int63n(int64(m.maxDelay) + 1)) + time.Sleep(delay) + } + + w.Header().Set("Content-Type", "application/json") + + // Inject error according to configured error rate. + if m.errorRate > 0 && rand.Float64() < m.errorRate { + writeErrorResponse(w) + log.Printf("[mockteerpc] responded with error (took %s)", time.Since(start)) + return + } + + m.mu.RLock() + h := m.height + m.mu.RUnlock() + + appHash := ComputeAppHash(h) + blockHash := ComputeBlockHash(appHash) + + resp := TeeRollupResponse{Code: 0, Message: "OK"} + resp.Data.Height = h + resp.Data.AppHash = "0x" + hex.EncodeToString(appHash[:]) + resp.Data.BlockHash = "0x" + hex.EncodeToString(blockHash[:]) + + _ = json.NewEncoder(w).Encode(resp) + log.Printf("[mockteerpc] responded height=%d appHash=%s (took %s)", h, resp.Data.AppHash[:10]+"...", time.Since(start)) +} + +// writeErrorResponse writes one of three error shapes, chosen at random. +// +// Type 0: code != 0, no data field. +// Type 1: code == 0, data is null. +// Type 2: code == 0, data present but all fields are null. +func writeErrorResponse(w http.ResponseWriter) { + type nullableFields struct { + Height *uint64 `json:"height"` + AppHash *string `json:"appHash"` + BlockHash *string `json:"blockHash"` + } + // type 0: no data field + type respNoData struct { + Code int `json:"code"` + Message string `json:"message"` + } + // type 1 & 2: has data field (null or with null fields) + type respWithData struct { + Code int `json:"code"` + Message string `json:"message"` + Data *nullableFields `json:"data"` + } + + switch rand.Intn(3) { + case 0: // code != 0, no data field + _ = json.NewEncoder(w).Encode(respNoData{ + Code: 1, + Message: "internal server error", + }) + case 1: // code == 0, data is null + _ = json.NewEncoder(w).Encode(respWithData{ + Code: 0, + Message: "OK", + Data: nil, + }) + case 2: // code == 0, data present but all fields are null + _ = json.NewEncoder(w).Encode(respWithData{ + Code: 0, + Message: "OK", + Data: &nullableFields{}, // all pointer fields are nil → JSON null + }) + } +} + +// ComputeAppHash returns keccak256(big-endian uint64 bytes of height). +func ComputeAppHash(height uint64) [32]byte { + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], height) + return crypto.Keccak256Hash(buf[:]) +} + +// ComputeBlockHash returns keccak256(appHash[:]). +func ComputeBlockHash(appHash [32]byte) [32]byte { + return crypto.Keccak256Hash(appHash[:]) +} diff --git a/tools/mockteerpc/mock_tee_rollup_server_test.go b/tools/mockteerpc/mock_tee_rollup_server_test.go new file mode 100644 index 0000000..99f7a75 --- /dev/null +++ b/tools/mockteerpc/mock_tee_rollup_server_test.go @@ -0,0 +1,62 @@ +package mockteerpc_test + +import ( + "encoding/hex" + "encoding/json" + "net/http" + "testing" + "time" + + mockteerpc "github.com/okx/xlayer-toolkit/tools/mockteerpc" + "github.com/stretchr/testify/require" +) + +func TestTeeRollupServer_Basic(t *testing.T) { + srv := mockteerpc.NewTeeRollupServer(t) + + // --- first request --- + resp, err := http.Get(srv.Addr() + "/v1/chain/confirmed_block_info") //nolint:noctx + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + + var body mockteerpc.TeeRollupResponse + require.NoError(t, json.NewDecoder(resp.Body).Decode(&body)) + + require.Equal(t, 0, body.Code) + require.Equal(t, "OK", body.Message) + require.GreaterOrEqual(t, body.Data.Height, uint64(1000)) + require.Equal(t, 66, len(body.Data.AppHash), "appHash should be 0x + 64 hex chars") + require.Equal(t, 66, len(body.Data.BlockHash), "blockHash should be 0x + 64 hex chars") + + firstHeight := body.Data.Height + + // --- wait for at least one tick --- + time.Sleep(1500 * time.Millisecond) + + resp2, err := http.Get(srv.Addr() + "/v1/chain/confirmed_block_info") //nolint:noctx + require.NoError(t, err) + defer resp2.Body.Close() + + var body2 mockteerpc.TeeRollupResponse + require.NoError(t, json.NewDecoder(resp2.Body).Decode(&body2)) + + require.Greater(t, body2.Data.Height, firstHeight, "height should have increased after 1.5s") + + // --- verify CurrentInfo height is >= last observed HTTP height --- + h, _, _ := srv.CurrentInfo() + require.GreaterOrEqual(t, h, body2.Data.Height, + "CurrentInfo height should be >= last HTTP response height") + + // --- verify hash determinism --- + appHash := mockteerpc.ComputeAppHash(body2.Data.Height) + require.Equal(t, "0x"+hex.EncodeToString(appHash[:]), body2.Data.AppHash) + blockHash := mockteerpc.ComputeBlockHash(appHash) + require.Equal(t, "0x"+hex.EncodeToString(blockHash[:]), body2.Data.BlockHash) +} + +func TestTeeRollupServer_DoubleClose(t *testing.T) { + srv := mockteerpc.NewTeeRollupServer(t) + // Explicit close before t.Cleanup runs — must not panic. + require.NotPanics(t, srv.Close) +} From fbeda74e531e6e3d7d8ce74a5c2e04030f576d30 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Wed, 25 Mar 2026 15:07:14 +0800 Subject: [PATCH 12/30] add build docker --- tools/mockteerpc/Makefile | 5 ++++- tools/mockteerpc/README.md | 22 ++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/tools/mockteerpc/Makefile b/tools/mockteerpc/Makefile index f3af958..c0f3f8f 100644 --- a/tools/mockteerpc/Makefile +++ b/tools/mockteerpc/Makefile @@ -2,7 +2,7 @@ BINARY := mockteerpc IMAGE := mockteerpc TAG ?= latest -.PHONY: build install run test docker-build docker-run clean +.PHONY: build install run test docker docker-build docker-run clean build: go build -o bin/$(BINARY) ./cmd/mockteerpc @@ -16,6 +16,9 @@ run: test: go test ./... +docker: + docker build -t $(IMAGE):latest . + docker-build: docker build -t $(IMAGE):$(TAG) . diff --git a/tools/mockteerpc/README.md b/tools/mockteerpc/README.md index 576815d..148ba22 100644 --- a/tools/mockteerpc/README.md +++ b/tools/mockteerpc/README.md @@ -44,8 +44,8 @@ make build ```bash cd tools/mockteerpc -# Build image -make docker-build +# Build image (mockteerpc:latest) +make docker # Run container (exposes :8090) make docker-run @@ -129,11 +129,13 @@ func TestMyFeature(t *testing.T) { ## Makefile targets -| Target | Description | -|----------------|------------------------------------| -| `make build` | Build binary to `bin/mockteerpc` | -| `make run` | Run via `go run` | -| `make test` | Run all tests | -| `make docker-build` | Build Docker image | -| `make docker-run` | Run Docker container on :8090 | -| `make clean` | Remove `bin/` directory | +| Target | Description | +|---------------------|------------------------------------------| +| `make build` | Build binary to `bin/mockteerpc` | +| `make install` | Install binary to `$GOPATH/bin` | +| `make run` | Run via `go run` | +| `make test` | Run all tests | +| `make docker` | Build Docker image `mockteerpc:latest` | +| `make docker-build` | Build Docker image with custom `TAG` | +| `make docker-run` | Run Docker container on :8090 | +| `make clean` | Remove `bin/` directory | From 3b187c4831fbeb2590a9fb947ef1d32dabe85e41 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Wed, 25 Mar 2026 18:26:43 +0800 Subject: [PATCH 13/30] add run tee game --- .gitignore | 1 + devnet/0-all.sh | 3 +- devnet/7-run-tee-game.sh | 42 +++ devnet/docker-compose.yml | 61 ++++- devnet/example.env | 18 ++ devnet/init.sh | 80 +++++- devnet/op-succinct/example.env.challenger | 2 + devnet/op-succinct/example.env.proposer | 2 + devnet/scripts/get-game.sh | 312 ++++++++++++++++++++++ 9 files changed, 505 insertions(+), 16 deletions(-) create mode 100755 devnet/7-run-tee-game.sh create mode 100755 devnet/scripts/get-game.sh diff --git a/.gitignore b/.gitignore index 09d78bc..c8f7d6b 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ tools/adventure/txhashes.log **/build/ local +.omc diff --git a/devnet/0-all.sh b/devnet/0-all.sh index 24d710b..4c56f88 100755 --- a/devnet/0-all.sh +++ b/devnet/0-all.sh @@ -6,4 +6,5 @@ set -e ./3-op-init.sh ./4-op-start-service.sh ./5-run-op-succinct.sh -./6-run-kailua.sh \ No newline at end of file +#./6-run-kailua.sh +./7-run-tee-game.sh diff --git a/devnet/7-run-tee-game.sh b/devnet/7-run-tee-game.sh new file mode 100755 index 0000000..8965576 --- /dev/null +++ b/devnet/7-run-tee-game.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +source .env + +PWD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SCRIPTS_DIR=$PWD_DIR/scripts + +ENCLAVE_ADDRESS=$(cast wallet address --private-key "$OP_CHALLENGER_PRIVATE_KEY") + +echo "🔧 Adding TEE game type..." +echo " Image: $OP_CONTRACTS_TEE_IMAGE_TAG" +echo " Enclave address: $ENCLAVE_ADDRESS" + +# Create a temp .env with L1_RPC_URL replaced by docker-internal URL, +# because add-tee-game-type.sh sources .env internally and would overwrite -e overrides. +TEMP_ENV=$(mktemp) +trap "rm -f $TEMP_ENV" EXIT +sed "s|^L1_RPC_URL=.*|L1_RPC_URL=$L1_RPC_URL_IN_DOCKER|" .env > "$TEMP_ENV" + +docker run --rm \ + --network "$DOCKER_NETWORK" \ + -v "$(pwd)/scripts:/devnet/scripts" \ + -v "$TEMP_ENV:/devnet/.env" \ + "$OP_CONTRACTS_TEE_IMAGE_TAG" \ + bash /devnet/scripts/add-tee-game-type.sh \ + --max-challenge-duration 120 \ + --max-prove-duration 60 \ + --mock-verifier \ + --enclave "$ENCLAVE_ADDRESS" \ + /app/packages/contracts-bedrock + +echo "🚀 Starting mockteerpc..." +docker compose up -d mockteerpc + +echo "🚀 Starting tee-proposer..." +docker compose up -d tee-proposer + +echo "🚀 Starting tee-challenger..." +docker compose up -d tee-challenger + +echo "✅ TEE game setup complete!" diff --git a/devnet/docker-compose.yml b/devnet/docker-compose.yml index 5a509eb..55b82c0 100644 --- a/devnet/docker-compose.yml +++ b/devnet/docker-compose.yml @@ -558,7 +558,7 @@ services: - --rpc.port=8560 - --rollup-rpc=http://op-seq:9545 - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} - - --proposal-interval=300s + - --proposal-interval=30s - --private-key=${OP_PROPOSER_PRIVATE_KEY} - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} - --game-type=${GAME_TYPE:-0} @@ -611,6 +611,65 @@ services: - --game-window=${GAME_WINDOW}s - --honest-actors=${PROPOSER_ADDRESS} + mockteerpc: + image: "${MOCKTEERPC_IMAGE_TAG}" + container_name: mockteerpc + command: + - --delay + - 1000ms + - --init-height + - "500000000" + ports: + - "8090:8090" + + tee-proposer: + image: "${OP_STACK_TEE_IMAGE_TAG}" + container_name: tee-proposer + environment: + - DISPUTE_GAME_FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS} + - OP_PROPOSER_PRIVATE_KEY=${OP_PROPOSER_PRIVATE_KEY} + command: + - /app/op-proposer/bin/op-proposer + - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} + - --tee-rollup-rpc=http://mockteerpc:8090 + - --game-type=${TEE_GAME_TYPE:-1960} + - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} + - --private-key=${OP_PROPOSER_PRIVATE_KEY} + - --poll-interval=2s + - --proposal-interval=35s + - --rpc.port=7302 + - --log.level=info + depends_on: + - mockteerpc + - op-batcher + + tee-challenger: + image: "${OP_STACK_TEE_IMAGE_TAG}" + container_name: tee-challenger + environment: + - DISPUTE_GAME_FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS} + - OP_CHALLENGER_PRIVATE_KEY=${OP_CHALLENGER_PRIVATE_KEY} + volumes: + - ./data/tee-challenger-data:/data + command: + - /app/op-challenger/bin/op-challenger + - --log.level=debug + - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} + - --l1-beacon=${L1_BEACON_URL_IN_DOCKER} + - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} + - --private-key=${OP_CHALLENGER_PRIVATE_KEY} + - --game-types=tee + - --datadir=/data + - --http-poll-interval=2s + - --additional-bond-claimants=${PROPOSER_ADDRESS} + - --tee-prover-rpc=http://mockteerpc:8090 + - --tee-prove-poll-interval=2s + - --tee-prove-timeout=120s + - --game-window=86400s + depends_on: + - mockteerpc + - tee-proposer + op-geth-seq2: image: "${OP_GETH_IMAGE_TAG}" container_name: op-geth-seq2 diff --git a/devnet/example.env b/devnet/example.env index 2461d30..7dda856 100644 --- a/devnet/example.env +++ b/devnet/example.env @@ -2,6 +2,7 @@ # OP Stack Configuration # ============================================================================== OP_STACK_LOCAL_DIRECTORY= +OP_STACK_BRANCH=dev SKIP_OP_STACK_BUILD=true OP_STACK_IMAGE_TAG=op-stack:latest @@ -61,6 +62,22 @@ KAILUA_LOCAL_DIRECTORY= SKIP_KAILUA_BUILD=true KAILUA_IMAGE_TAG=kailua:latest +# ============================================================================== +# OP Stack TEE Configuration (independent from op-stack, built from tz/dev branch) +# ============================================================================== +OP_STACK_TEE_LOCAL_DIRECTORY= +OP_STACK_TEE_BRANCH=tz/dev +SKIP_OP_STACK_TEE_BUILD=true +OP_STACK_TEE_IMAGE_TAG=op-stack:tee +SKIP_OP_CONTRACTS_TEE_BUILD=true +OP_CONTRACTS_TEE_IMAGE_TAG=op-contracts:tee + +# ============================================================================== +# MockTeeRPC Configuration +# ============================================================================== +SKIP_MOCKTEERPC_BUILD=true +MOCKTEERPC_IMAGE_TAG=mockteerpc:latest + # ============================================================================== # Build Configuration # ============================================================================== @@ -147,6 +164,7 @@ TEMP_GAME_WINDOW=60 MAX_CLOCK_DURATION=20 CLOCK_EXTENSION=10 GAME_WINDOW=60 +TEE_GAME_TYPE=1960 # AnchorStateRegistry configure DISPUTE_GAME_FINALITY_DELAY_SECONDS=5 diff --git a/devnet/init.sh b/devnet/init.sh index 64afbf2..ff8b285 100755 --- a/devnet/init.sh +++ b/devnet/init.sh @@ -24,6 +24,19 @@ function build_and_tag_image() { cd - } +function git_switch_branch() { + local dir=$1 + local branch=$2 + local remote + cd "$dir" + remote=$(git remote | head -1) + echo "🔄 Switching to branch: $branch (remote: $remote)" + git fetch "$remote" + git checkout "$branch" + git pull "$remote" "$branch" + cd - +} + # Build OP_STACK image if [ "$SKIP_OP_STACK_BUILD" = "true" ]; then echo "⏭️ Skipping op-stack build" @@ -32,6 +45,12 @@ else echo "❌ Please set OP_STACK_LOCAL_DIRECTORY in .env" exit 1 else + if [ -n "$OP_STACK_BRANCH" ]; then + git_switch_branch "$OP_STACK_LOCAL_DIRECTORY" "$OP_STACK_BRANCH" + else + echo "📍 Using op-stack branch: $(cd "$OP_STACK_LOCAL_DIRECTORY" && git branch --show-current)" + fi + echo "🔨 Building op-stack" cd "$OP_STACK_LOCAL_DIRECTORY" git submodule update --init --recursive @@ -40,6 +59,40 @@ else fi fi +# Build OP_STACK_TEE and OP_CONTRACTS_TEE images +if [ "$SKIP_OP_STACK_TEE_BUILD" = "true" ] && [ "$SKIP_OP_CONTRACTS_TEE_BUILD" = "true" ]; then + echo "⏭️ Skipping op-stack-tee and op-contracts-tee build" +else + # Use dedicated directory if set, otherwise fall back to OP_STACK_LOCAL_DIRECTORY + if [ -n "$OP_STACK_TEE_LOCAL_DIRECTORY" ]; then + OP_STACK_TEE_DIR="$OP_STACK_TEE_LOCAL_DIRECTORY" + elif [ -n "$OP_STACK_LOCAL_DIRECTORY" ]; then + OP_STACK_TEE_DIR="$OP_STACK_LOCAL_DIRECTORY" + else + echo "❌ Please set OP_STACK_TEE_LOCAL_DIRECTORY or OP_STACK_LOCAL_DIRECTORY in .env" + exit 1 + fi + + git_switch_branch "$OP_STACK_TEE_DIR" "$OP_STACK_TEE_BRANCH" + cd "$OP_STACK_TEE_DIR" + git submodule update --init --recursive + cd - + + if [ "$SKIP_OP_STACK_TEE_BUILD" = "true" ]; then + echo "⏭️ Skipping op-stack-tee build" + else + echo "🔨 Building $OP_STACK_TEE_IMAGE_TAG" + build_and_tag_image "op-stack-tee" "$OP_STACK_TEE_IMAGE_TAG" "$OP_STACK_TEE_DIR" "Dockerfile-opstack" + fi + + if [ "$SKIP_OP_CONTRACTS_TEE_BUILD" = "true" ]; then + echo "⏭️ Skipping op-contracts-tee build" + else + echo "🔨 Building $OP_CONTRACTS_TEE_IMAGE_TAG" + build_and_tag_image "op-contracts-tee" "$OP_CONTRACTS_TEE_IMAGE_TAG" "$OP_STACK_TEE_DIR" "Dockerfile-contracts" + fi +fi + # Build OP_GETH image if [ "$SKIP_OP_GETH_BUILD" = "true" ]; then echo "⏭️ Skipping op-geth build" @@ -58,12 +111,7 @@ else # Switch to specified branch if provided if [ -n "$OP_GETH_BRANCH" ]; then - echo "🔄 Switching op-geth to branch: $OP_GETH_BRANCH" - cd "$OP_GETH_DIR" - git fetch origin - git checkout "$OP_GETH_BRANCH" - git pull origin "$OP_GETH_BRANCH" - cd - + git_switch_branch "$OP_GETH_DIR" "$OP_GETH_BRANCH" else echo "📍 Using op-geth default branch" fi @@ -97,16 +145,11 @@ else exit 1 else echo "🔨 Building $OP_RETH_IMAGE_TAG" - cd "$OP_RETH_LOCAL_DIRECTORY" if [ -n "$OP_RETH_BRANCH" ]; then - echo "🔄 Switching op-reth to branch: $OP_RETH_BRANCH" - git fetch origin - git checkout "$OP_RETH_BRANCH" - git pull origin "$OP_RETH_BRANCH" + git_switch_branch "$OP_RETH_LOCAL_DIRECTORY" "$OP_RETH_BRANCH" else - echo "📍 Using op-reth branch: $(git branch --show-current)" + echo "📍 Using op-reth branch: $(cd "$OP_RETH_LOCAL_DIRECTORY" && git branch --show-current)" fi - cd - # Check if profiling is enabled and build accordingly if [ "$RETH_PROFILING_ENABLED" = "true" ]; then @@ -147,8 +190,17 @@ else exit 1 else echo "🔨 Building kailua image" - + cd "$KAILUA_LOCAL_DIRECTORY" build_and_tag_image "kailua" "$KAILUA_IMAGE_TAG" "$KAILUA_LOCAL_DIRECTORY" "Dockerfile.local" fi fi + +# Build MockTeeRPC image +if [ "$SKIP_MOCKTEERPC_BUILD" = "true" ]; then + echo "⏭️ Skipping mockteerpc build" +else + echo "🔨 Building $MOCKTEERPC_IMAGE_TAG" + MOCKTEERPC_DIR="$PWD_DIR/../tools/mockteerpc" + build_and_tag_image "mockteerpc" "$MOCKTEERPC_IMAGE_TAG" "$MOCKTEERPC_DIR" "Dockerfile" +fi diff --git a/devnet/op-succinct/example.env.challenger b/devnet/op-succinct/example.env.challenger index e1f0179..3421243 100644 --- a/devnet/op-succinct/example.env.challenger +++ b/devnet/op-succinct/example.env.challenger @@ -8,6 +8,8 @@ L2_RPC=http://op-geth-seq:8545 FACTORY_ADDRESS=0x57bbef86ab6744c67d5bf9a9f5d09dca71bd7453 +ANCHOR_STATE_REGISTRY_ADDRESS= + GAME_TYPE=42 PRIVATE_KEY=0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 diff --git a/devnet/op-succinct/example.env.proposer b/devnet/op-succinct/example.env.proposer index 0c906a9..f55b65d 100644 --- a/devnet/op-succinct/example.env.proposer +++ b/devnet/op-succinct/example.env.proposer @@ -10,6 +10,8 @@ L2_NODE_RPC=http://op-seq:9545 FACTORY_ADDRESS=0x57bbef86ab6744c67d5bf9a9f5d09dca71bd7453 +ANCHOR_STATE_REGISTRY_ADDRESS= + GAME_TYPE=42 PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d diff --git a/devnet/scripts/get-game.sh b/devnet/scripts/get-game.sh new file mode 100755 index 0000000..ab291c5 --- /dev/null +++ b/devnet/scripts/get-game.sh @@ -0,0 +1,312 @@ +#!/bin/bash +# get-game.sh — Show detailed info for a single dispute game by index. +# +# Usage: +# ./get-game.sh +# +# Examples: +# ./get-game.sh 0 # Show game at index 0 +# ./get-game.sh 42 # Show game at index 42 + +set -euo pipefail + +# ════════════════════════════════════════════════════════════════ +# Configuration +# ════════════════════════════════════════════════════════════════ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEVNET_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +[ -f "$DEVNET_DIR/.env" ] && source "$DEVNET_DIR/.env" + +FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS:-""} +L1_RPC=${L1_RPC_URL:-"http://localhost:8545"} +L2_RPC=${L2_RPC_URL:-"http://localhost:8123"} + +GENESIS_PARENT_INDEX=4294967295 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' +BOLD='\033[1m' + +# ════════════════════════════════════════════════════════════════ +# Usage / Arg Parsing +# ════════════════════════════════════════════════════════════════ +usage() { + echo "Usage: $0 " + echo "" + echo " game_id Index of the dispute game in the factory" + echo "" + echo "Examples:" + echo " $0 0" + echo " $0 42" + exit 1 +} + +if [[ $# -ne 1 ]] || ! [[ "$1" =~ ^[0-9]+$ ]]; then + usage +fi + +GAME_ID="$1" + +# ════════════════════════════════════════════════════════════════ +# Requirements +# ════════════════════════════════════════════════════════════════ +if ! command -v cast &>/dev/null; then + echo -e "${RED}Error: 'cast' not found. Install Foundry: https://getfoundry.sh${NC}" + exit 1 +fi + +if [[ -z "$FACTORY_ADDRESS" ]]; then + echo -e "${RED}Error: DISPUTE_GAME_FACTORY_ADDRESS not set in $DEVNET_DIR/.env${NC}" + exit 1 +fi + +# ════════════════════════════════════════════════════════════════ +# Helpers +# ════════════════════════════════════════════════════════════════ + +proposal_status_name() { + case "$1" in + 0) echo "Unchallenged" ;; + 1) echo "Challenged" ;; + 2) echo "UnchallengedAndValidProofProvided" ;; + 3) echo "ChallengedAndValidProofProvided" ;; + 4) echo "Resolved" ;; + *) echo "Unknown($1)" ;; + esac +} + +game_status_name() { + case "$1" in + 0) echo "IN_PROGRESS" ;; + 1) echo "CHALLENGER_WINS" ;; + 2) echo "DEFENDER_WINS" ;; + *) echo "Unknown($1)" ;; + esac +} + +bond_mode_name() { + case "$1" in + 0) echo "UNDECIDED" ;; + 1) echo "NORMAL" ;; + 2) echo "REFUND" ;; + *) echo "Unknown($1)" ;; + esac +} + +fmt_ts_field() { + local raw="$1" + local num + num=$(echo "$raw" | awk '{print $1}') + if [[ "$num" == "0" || "$num" == "N/A" || -z "$num" ]]; then + echo "N/A" + return + fi + local human + human=$(date -r "$num" "+%Y-%m-%d %H:%M:%S" 2>/dev/null \ + || date -d "@$num" "+%Y-%m-%d %H:%M:%S" 2>/dev/null \ + || echo "?") + echo "${num} (${human})" +} + +fmt_duration() { + local secs="$1" + if [[ "$secs" == "N/A" ]]; then echo "N/A"; return; fi + printf "%dh %dm %ds" "$((secs/3600))" "$(((secs%3600)/60))" "$((secs%60))" +} + +row() { printf " %-32s %s\n" "$1" "$2"; } +section() { echo " ┌─── $1"; } +section_end() { echo " └$(printf '─%.0s' {1..100})┘"; } +phase() { printf " ├─ %s\n" "$1"; } +trow() { printf " │ %-26s %s\n" "$1" "$2"; } + +# ════════════════════════════════════════════════════════════════ +# Header +# ════════════════════════════════════════════════════════════════ +echo -e "${BLUE}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${BLUE}${BOLD} Dispute Game Inspector${NC}" +echo -e "${BLUE}${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo "" +echo -e " ${BOLD}Factory:${NC} ${FACTORY_ADDRESS:0:10}...${FACTORY_ADDRESS: -8}" +echo -e " ${BOLD}L1 RPC:${NC} $L1_RPC" +echo -e " ${BOLD}L2 RPC:${NC} $L2_RPC" +echo -e " ${BOLD}Game ID:${NC} $GAME_ID" +echo "" + +# ════════════════════════════════════════════════════════════════ +# Validate game ID within range +# ════════════════════════════════════════════════════════════════ +TOTAL=$(cast call "$FACTORY_ADDRESS" "gameCount()(uint256)" --rpc-url "$L1_RPC") +echo -e " ${CYAN}Total games in factory: $TOTAL${NC}" +echo "" + +if [[ "$TOTAL" -eq 0 ]]; then + echo -e "${YELLOW}No games yet.${NC}" + exit 0 +fi + +if [[ "$GAME_ID" -ge "$TOTAL" ]]; then + echo -e "${RED}Error: game ID $GAME_ID out of range (0 – $((TOTAL-1)))${NC}" + exit 1 +fi + +# ════════════════════════════════════════════════════════════════ +# Factory record +# ════════════════════════════════════════════════════════════════ +INFO=$(cast call "$FACTORY_ADDRESS" "gameAtIndex(uint256)(uint8,uint64,address)" "$GAME_ID" --rpc-url "$L1_RPC") +GAME_TYPE=$(echo "$INFO" | awk 'NR==1') +ADDR=$(echo "$INFO" | awk 'NR==3') + +echo "╔══════════════════════════════════════════════════════════════╗" +printf "║ GAME #%-6s │ GameType: %-33s║\n" "$GAME_ID" "$GAME_TYPE" +echo "╚══════════════════════════════════════════════════════════════╝" + +# ════════════════════════════════════════════════════════════════ +# Fetch all fields +# ════════════════════════════════════════════════════════════════ + +# Immutables +MAX_CHAL_DUR=$(cast call "$ADDR" "maxChallengeDuration()(uint64)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +MAX_PROVE_DUR=$(cast call "$ADDR" "maxProveDuration()(uint64)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +CHAL_BOND=$( cast call "$ADDR" "challengerBond()(uint256)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") + +# Identity +GAME_CREATOR=$( cast call "$ADDR" "gameCreator()(address)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +PROPOSER_ADDR=$( cast call "$ADDR" "proposer()(address)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +WAS_RESPECTED=$( cast call "$ADDR" "wasRespectedGameTypeWhenCreated()(bool)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") + +# Proposal range +L2_BLOCK=$( cast call "$ADDR" "l2BlockNumber()(uint256)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +PARENT_IDX=$( cast call "$ADDR" "parentIndex()(uint32)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +STARTING_BN=$( cast call "$ADDR" "startingBlockNumber()(uint256)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +STARTING_HASH=$(cast call "$ADDR" "startingRootHash()(bytes32)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +ROOT_CLAIM=$( cast call "$ADDR" "rootClaim()(bytes32)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +BLOCK_HASH=$( cast call "$ADDR" "blockHash()(bytes32)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +STATE_HASH=$( cast call "$ADDR" "stateHash()(bytes32)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") + +# ClaimData struct: (uint32 parentIndex, address counteredBy, address prover, +# bytes32 claim, uint8 status, uint64 deadline) +CLAIM_RAW=$(cast call "$ADDR" "claimData()(uint32,address,address,bytes32,uint8,uint64)" \ + --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +if [[ "$CLAIM_RAW" != "N/A" ]]; then + CD_COUNTERED=$( echo "$CLAIM_RAW" | awk 'NR==2') + CD_PROVER=$( echo "$CLAIM_RAW" | awk 'NR==3') + CD_STATUS_RAW=$( echo "$CLAIM_RAW" | awk 'NR==5') + CD_DEADLINE=$( echo "$CLAIM_RAW" | awk 'NR==6') +else + CD_COUNTERED="N/A"; CD_PROVER="N/A"; CD_STATUS_RAW="N/A"; CD_DEADLINE="N/A" +fi + +# Game-level state +GAME_STATUS_RAW=$(cast call "$ADDR" "status()(uint8)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +CREATED_AT_RAW=$( cast call "$ADDR" "createdAt()(uint64)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +RESOLVED_AT_RAW=$(cast call "$ADDR" "resolvedAt()(uint64)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +BOND_MODE_RAW=$( cast call "$ADDR" "bondDistributionMode()(uint8)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") +GAME_OVER=$( cast call "$ADDR" "gameOver()(bool)" --rpc-url "$L1_RPC" 2>/dev/null || echo "N/A") + +# ════════════════════════════════════════════════════════════════ +# Derived values +# ════════════════════════════════════════════════════════════════ +CD_STATUS=$(proposal_status_name "$CD_STATUS_RAW") +GAME_STATUS=$(game_status_name "$GAME_STATUS_RAW") +BOND_MODE=$(bond_mode_name "$BOND_MODE_RAW") + +CREATED_AT_FMT=$( fmt_ts_field "$CREATED_AT_RAW") +RESOLVED_AT_FMT=$(fmt_ts_field "$RESOLVED_AT_RAW") +DEADLINE_FMT=$( fmt_ts_field "$(echo "$CD_DEADLINE" | awk '{print $1}')") + +MAX_CHAL_FMT="N/A" +MAX_PROVE_FMT="N/A" +if [[ "$MAX_CHAL_DUR" != "N/A" ]]; then + MAX_CHAL_NUM=$(echo "$MAX_CHAL_DUR" | awk '{print $1}') + MAX_CHAL_FMT="${MAX_CHAL_NUM}s ($(fmt_duration "$MAX_CHAL_NUM"))" +fi +if [[ "$MAX_PROVE_DUR" != "N/A" ]]; then + MAX_PROVE_NUM=$(echo "$MAX_PROVE_DUR" | awk '{print $1}') + MAX_PROVE_FMT="${MAX_PROVE_NUM}s ($(fmt_duration "$MAX_PROVE_NUM"))" +fi + +CHAL_BOND_FMT="N/A" +if [[ "$CHAL_BOND" != "N/A" ]]; then + CHAL_BOND_ETH=$(cast to-unit "$CHAL_BOND" ether 2>/dev/null || echo "?") + CHAL_BOND_FMT="${CHAL_BOND_ETH} ETH (${CHAL_BOND} wei)" +fi + +# Parent block range +PARENT_DISPLAY="$PARENT_IDX" +BLOCK_RANGE="N/A" +if [[ "$PARENT_IDX" == "$GENESIS_PARENT_INDEX" ]]; then + PARENT_DISPLAY="genesis" + BLOCK_RANGE="? – ${L2_BLOCK}" +else + PARENT_DATA=$(cast call "$FACTORY_ADDRESS" "gameAtIndex(uint256)(uint8,uint64,address)" \ + "$PARENT_IDX" --rpc-url "$L1_RPC" 2>/dev/null || echo "") + if [[ -n "$PARENT_DATA" ]]; then + PARENT_ADDR=$(echo "$PARENT_DATA" | awk 'NR==3') + PARENT_L2=$(cast call "$PARENT_ADDR" "l2BlockNumber()(uint256)" --rpc-url "$L1_RPC" 2>/dev/null | awk '{print $1}') + BLOCK_RANGE="${PARENT_L2} – ${L2_BLOCK} ($(( $(echo "$L2_BLOCK" | awk '{print $1}') - PARENT_L2 )) blocks)" + fi +fi + +# ════════════════════════════════════════════════════════════════ +# Output +# ════════════════════════════════════════════════════════════════ + +# Section 1: Identity & Config +echo "" +section "[1] Identity & Config ──────────────────────────────────────────────────────────────────────────┐" +phase "Identity" +trow "Address:" "$ADDR" +trow "GameType:" "$GAME_TYPE" +trow "GameCreator:" "$GAME_CREATOR" +trow "Proposer:" "$PROPOSER_ADDR" +trow "WasRespectedGameType:" "$WAS_RESPECTED" +phase "Config" +trow "MaxChallengeDuration:" "$MAX_CHAL_FMT" +trow "MaxProveDuration:" "$MAX_PROVE_FMT" +trow "ChallengerBond:" "$CHAL_BOND_FMT" +section_end + +# Section 2: Proposal +echo "" +section "[2] Proposal ──────────────────────────────────────────────────────────────────────────────────┐" +phase "Starting State" +trow "ParentIndex:" "$PARENT_DISPLAY" +trow "StartingBlockNumber:" "$STARTING_BN" +trow "StartingRootHash:" "$STARTING_HASH" +phase "Target State" +trow "L2BlockNumber:" "$L2_BLOCK" +trow "BlockRange:" "$BLOCK_RANGE" +trow "BlockHash:" "$BLOCK_HASH" +trow "StateHash:" "$STATE_HASH" +trow "RootClaim:" "$ROOT_CLAIM" +section_end + +# Section 3: Lifecycle State +echo "" +section "[3] Lifecycle State ────────────────────────────────────────────────────────────────────────────┐" +phase "Initialize" +trow "CreatedAt:" "$CREATED_AT_FMT" +phase "Challenge Window" +trow "CounteredBy:" "$CD_COUNTERED" +trow "ClaimData.status:" "$CD_STATUS" +trow "ClaimData.deadline:" "$DEADLINE_FMT" +phase "Prove" +trow "Prover:" "$CD_PROVER" +trow "GameOver:" "$GAME_OVER" +phase "Resolve" +trow "GameStatus:" "$GAME_STATUS" +trow "ResolvedAt:" "$RESOLVED_AT_FMT" +phase "CloseGame/ClaimCredit" +trow "BondDistributionMode:" "$BOND_MODE" +section_end + +echo "" +echo -e "${BLUE}${BOLD}════════════════════════════════════════════════════════════════════════════════════════════════════════${NC}" +echo -e "${GREEN}${BOLD}Done.${NC}" From 4191fa3395a3fe2b19395f4b4d252c0f336f1d5e Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Wed, 25 Mar 2026 18:36:06 +0800 Subject: [PATCH 14/30] optimize --- devnet/0-all.sh | 2 +- devnet/7-run-tee-game.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/devnet/0-all.sh b/devnet/0-all.sh index 4c56f88..4708411 100755 --- a/devnet/0-all.sh +++ b/devnet/0-all.sh @@ -6,5 +6,5 @@ set -e ./3-op-init.sh ./4-op-start-service.sh ./5-run-op-succinct.sh -#./6-run-kailua.sh +./6-run-kailua.sh ./7-run-tee-game.sh diff --git a/devnet/7-run-tee-game.sh b/devnet/7-run-tee-game.sh index 8965576..c98326e 100755 --- a/devnet/7-run-tee-game.sh +++ b/devnet/7-run-tee-game.sh @@ -6,6 +6,37 @@ source .env PWD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPTS_DIR=$PWD_DIR/scripts +# Check required images exist +MISSING_IMAGES=() +for IMG_VAR in OP_CONTRACTS_TEE_IMAGE_TAG OP_STACK_TEE_IMAGE_TAG MOCKTEERPC_IMAGE_TAG; do + IMG="${!IMG_VAR}" + if ! docker image inspect "$IMG" > /dev/null 2>&1; then + MISSING_IMAGES+=("$IMG_VAR=$IMG") + fi +done + +if [ ${#MISSING_IMAGES[@]} -gt 0 ]; then + echo "❌ The following required Docker images are missing:" + for ENTRY in "${MISSING_IMAGES[@]}"; do + echo " - $ENTRY" + done + echo "" + echo "To build them, set the corresponding SKIP flags to 'false' in .env:" + for ENTRY in "${MISSING_IMAGES[@]}"; do + VAR_NAME="${ENTRY%%=*}" + case "$VAR_NAME" in + OP_CONTRACTS_TEE_IMAGE_TAG) echo " SKIP_OP_CONTRACTS_TEE_BUILD=false" ;; + OP_STACK_TEE_IMAGE_TAG) echo " SKIP_OP_STACK_TEE_BUILD=false" ;; + MOCKTEERPC_IMAGE_TAG) echo " SKIP_MOCKTEERPC_BUILD=false" ;; + esac + done + echo "" + echo "Then run: bash init.sh" + echo "" + echo "After init.sh completes, re-run this script: bash 7-run-tee-game.sh" + exit 1 +fi + ENCLAVE_ADDRESS=$(cast wallet address --private-key "$OP_CHALLENGER_PRIVATE_KEY") echo "🔧 Adding TEE game type..." @@ -40,3 +71,6 @@ echo "🚀 Starting tee-challenger..." docker compose up -d tee-challenger echo "✅ TEE game setup complete!" +echo "" +echo "To list all games, run:" +echo " bash scripts/list-game.sh" From 8778ad8dc3a47db05913f9f7776ae1117f47fb43 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Wed, 25 Mar 2026 18:38:06 +0800 Subject: [PATCH 15/30] recover --- devnet/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devnet/docker-compose.yml b/devnet/docker-compose.yml index 55b82c0..ac61b2c 100644 --- a/devnet/docker-compose.yml +++ b/devnet/docker-compose.yml @@ -558,7 +558,7 @@ services: - --rpc.port=8560 - --rollup-rpc=http://op-seq:9545 - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} - - --proposal-interval=30s + - --proposal-interval=300s - --private-key=${OP_PROPOSER_PRIVATE_KEY} - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} - --game-type=${GAME_TYPE:-0} From e27180ad0ce85adf62d1a5486ee2d3ae63f6aa82 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Wed, 25 Mar 2026 18:49:54 +0800 Subject: [PATCH 16/30] use config --- devnet/7-run-tee-game.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devnet/7-run-tee-game.sh b/devnet/7-run-tee-game.sh index c98326e..f008cfd 100755 --- a/devnet/7-run-tee-game.sh +++ b/devnet/7-run-tee-game.sh @@ -55,8 +55,8 @@ docker run --rm \ -v "$TEMP_ENV:/devnet/.env" \ "$OP_CONTRACTS_TEE_IMAGE_TAG" \ bash /devnet/scripts/add-tee-game-type.sh \ - --max-challenge-duration 120 \ - --max-prove-duration 60 \ + --max-challenge-duration "$GAME_WINDOW" \ + --max-prove-duration "$GAME_WINDOW" \ --mock-verifier \ --enclave "$ENCLAVE_ADDRESS" \ /app/packages/contracts-bedrock From 9888591a61cf04d81209db1b39d66db5006a2be4 Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Thu, 26 Mar 2026 10:37:01 +0800 Subject: [PATCH 17/30] feat: add mockteeprover and split TEE services into docker-compose-tee.yml - Add tools/mockteeprover: mock TEE prover with /task/ API (no /v1 prefix) - Extract TEE containers (mockteerpc, mockteeprover, tee-proposer, tee-challenger) into docker-compose-tee.yml - Fix tee-challenger: use mockteeprover:8690 instead of mockteerpc:8090, read GAME_WINDOW from env - Update 7-run-tee-game.sh to use combined compose files and check MOCKTEEPROVER_IMAGE_TAG Co-Authored-By: Claude Opus 4.6 --- devnet/7-run-tee-game.sh | 13 +- devnet/docker-compose-tee.yml | 72 ++++++++ devnet/docker-compose.yml | 59 ------ tools/mockteeprover/Dockerfile | 15 ++ tools/mockteeprover/README.md | 91 ++++++++++ tools/mockteeprover/go.mod | 15 ++ tools/mockteeprover/go.sum | 24 +++ tools/mockteeprover/main.go | 287 ++++++++++++++++++++++++++++++ tools/mockteeprover/main_test.go | 207 +++++++++++++++++++++ tools/mockteeprover/proof.go | 132 ++++++++++++++ tools/mockteeprover/proof_test.go | 126 +++++++++++++ 11 files changed, 973 insertions(+), 68 deletions(-) create mode 100644 devnet/docker-compose-tee.yml create mode 100644 tools/mockteeprover/Dockerfile create mode 100644 tools/mockteeprover/README.md create mode 100644 tools/mockteeprover/go.mod create mode 100644 tools/mockteeprover/go.sum create mode 100644 tools/mockteeprover/main.go create mode 100644 tools/mockteeprover/main_test.go create mode 100644 tools/mockteeprover/proof.go create mode 100644 tools/mockteeprover/proof_test.go diff --git a/devnet/7-run-tee-game.sh b/devnet/7-run-tee-game.sh index f008cfd..c189a21 100755 --- a/devnet/7-run-tee-game.sh +++ b/devnet/7-run-tee-game.sh @@ -8,7 +8,7 @@ SCRIPTS_DIR=$PWD_DIR/scripts # Check required images exist MISSING_IMAGES=() -for IMG_VAR in OP_CONTRACTS_TEE_IMAGE_TAG OP_STACK_TEE_IMAGE_TAG MOCKTEERPC_IMAGE_TAG; do +for IMG_VAR in OP_CONTRACTS_TEE_IMAGE_TAG OP_STACK_TEE_IMAGE_TAG MOCKTEERPC_IMAGE_TAG MOCKTEEPROVER_IMAGE_TAG; do IMG="${!IMG_VAR}" if ! docker image inspect "$IMG" > /dev/null 2>&1; then MISSING_IMAGES+=("$IMG_VAR=$IMG") @@ -28,6 +28,7 @@ if [ ${#MISSING_IMAGES[@]} -gt 0 ]; then OP_CONTRACTS_TEE_IMAGE_TAG) echo " SKIP_OP_CONTRACTS_TEE_BUILD=false" ;; OP_STACK_TEE_IMAGE_TAG) echo " SKIP_OP_STACK_TEE_BUILD=false" ;; MOCKTEERPC_IMAGE_TAG) echo " SKIP_MOCKTEERPC_BUILD=false" ;; + MOCKTEEPROVER_IMAGE_TAG) echo " SKIP_MOCKTEEPROVER_BUILD=false" ;; esac done echo "" @@ -61,14 +62,8 @@ docker run --rm \ --enclave "$ENCLAVE_ADDRESS" \ /app/packages/contracts-bedrock -echo "🚀 Starting mockteerpc..." -docker compose up -d mockteerpc - -echo "🚀 Starting tee-proposer..." -docker compose up -d tee-proposer - -echo "🚀 Starting tee-challenger..." -docker compose up -d tee-challenger +echo "🚀 Starting TEE services..." +docker compose -f docker-compose.yml -f docker-compose-tee.yml up -d mockteerpc mockteeprover tee-proposer tee-challenger echo "✅ TEE game setup complete!" echo "" diff --git a/devnet/docker-compose-tee.yml b/devnet/docker-compose-tee.yml new file mode 100644 index 0000000..0274883 --- /dev/null +++ b/devnet/docker-compose-tee.yml @@ -0,0 +1,72 @@ +networks: + default: + name: ${DOCKER_NETWORK:-dev-op} + +services: + mockteerpc: + image: "${MOCKTEERPC_IMAGE_TAG}" + container_name: mockteerpc + command: + - --delay + - 1000ms + - --init-height + - "500000000" + ports: + - "8090:8090" + + mockteeprover: + image: "${MOCKTEEPROVER_IMAGE_TAG}" + container_name: mockteeprover + environment: + - SIGNER_PRIVATE_KEY=${TEE_SIGNER_PRIVATE_KEY} + - TASK_DELAY=${TEE_TASK_DELAY:-2s} + ports: + - "8690:8690" + + tee-proposer: + image: "${OP_STACK_TEE_IMAGE_TAG}" + container_name: tee-proposer + environment: + - DISPUTE_GAME_FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS} + - OP_PROPOSER_PRIVATE_KEY=${OP_PROPOSER_PRIVATE_KEY} + command: + - /app/op-proposer/bin/op-proposer + - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} + - --tee-rollup-rpc=http://mockteerpc:8090 + - --game-type=${TEE_GAME_TYPE:-1960} + - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} + - --private-key=${OP_PROPOSER_PRIVATE_KEY} + - --poll-interval=2s + - --proposal-interval=35s + - --rpc.port=7302 + - --log.level=info + depends_on: + - mockteerpc + - op-batcher + + tee-challenger: + image: "${OP_STACK_TEE_IMAGE_TAG}" + container_name: tee-challenger + environment: + - DISPUTE_GAME_FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS} + - OP_CHALLENGER_PRIVATE_KEY=${OP_CHALLENGER_PRIVATE_KEY} + volumes: + - ./data/tee-challenger-data:/data + command: + - /app/op-challenger/bin/op-challenger + - --log.level=debug + - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} + - --l1-beacon=${L1_BEACON_URL_IN_DOCKER} + - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} + - --private-key=${OP_CHALLENGER_PRIVATE_KEY} + - --game-types=tee + - --datadir=/data + - --http-poll-interval=2s + - --additional-bond-claimants=${PROPOSER_ADDRESS} + - --tee-prover-rpc=http://mockteeprover:8690 + - --tee-prove-poll-interval=2s + - --tee-prove-timeout=120s + - --game-window=${GAME_WINDOW}s + depends_on: + - mockteeprover + - tee-proposer diff --git a/devnet/docker-compose.yml b/devnet/docker-compose.yml index ac61b2c..5a509eb 100644 --- a/devnet/docker-compose.yml +++ b/devnet/docker-compose.yml @@ -611,65 +611,6 @@ services: - --game-window=${GAME_WINDOW}s - --honest-actors=${PROPOSER_ADDRESS} - mockteerpc: - image: "${MOCKTEERPC_IMAGE_TAG}" - container_name: mockteerpc - command: - - --delay - - 1000ms - - --init-height - - "500000000" - ports: - - "8090:8090" - - tee-proposer: - image: "${OP_STACK_TEE_IMAGE_TAG}" - container_name: tee-proposer - environment: - - DISPUTE_GAME_FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS} - - OP_PROPOSER_PRIVATE_KEY=${OP_PROPOSER_PRIVATE_KEY} - command: - - /app/op-proposer/bin/op-proposer - - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} - - --tee-rollup-rpc=http://mockteerpc:8090 - - --game-type=${TEE_GAME_TYPE:-1960} - - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} - - --private-key=${OP_PROPOSER_PRIVATE_KEY} - - --poll-interval=2s - - --proposal-interval=35s - - --rpc.port=7302 - - --log.level=info - depends_on: - - mockteerpc - - op-batcher - - tee-challenger: - image: "${OP_STACK_TEE_IMAGE_TAG}" - container_name: tee-challenger - environment: - - DISPUTE_GAME_FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS} - - OP_CHALLENGER_PRIVATE_KEY=${OP_CHALLENGER_PRIVATE_KEY} - volumes: - - ./data/tee-challenger-data:/data - command: - - /app/op-challenger/bin/op-challenger - - --log.level=debug - - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} - - --l1-beacon=${L1_BEACON_URL_IN_DOCKER} - - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} - - --private-key=${OP_CHALLENGER_PRIVATE_KEY} - - --game-types=tee - - --datadir=/data - - --http-poll-interval=2s - - --additional-bond-claimants=${PROPOSER_ADDRESS} - - --tee-prover-rpc=http://mockteerpc:8090 - - --tee-prove-poll-interval=2s - - --tee-prove-timeout=120s - - --game-window=86400s - depends_on: - - mockteerpc - - tee-proposer - op-geth-seq2: image: "${OP_GETH_IMAGE_TAG}" container_name: op-geth-seq2 diff --git a/tools/mockteeprover/Dockerfile b/tools/mockteeprover/Dockerfile new file mode 100644 index 0000000..16e1d94 --- /dev/null +++ b/tools/mockteeprover/Dockerfile @@ -0,0 +1,15 @@ +FROM golang:1.22-alpine AS builder + +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -o /mock-tee-prover . + +FROM alpine:3.19 +RUN apk add --no-cache ca-certificates wget +COPY --from=builder /mock-tee-prover /usr/local/bin/mock-tee-prover + +EXPOSE 8690 + +ENTRYPOINT ["mock-tee-prover"] diff --git a/tools/mockteeprover/README.md b/tools/mockteeprover/README.md new file mode 100644 index 0000000..5bc3a01 --- /dev/null +++ b/tools/mockteeprover/README.md @@ -0,0 +1,91 @@ +# mockteeprover + +Standalone mock TEE Prover HTTP server for local development and testing. + +--- + +## Mock TEE Prover Server + +Simulates the TEE Prover task API used by `op-challenger`. Accepts prove requests, generates mock batch proofs with ECDSA signatures, and returns them after a configurable delay. + +**Behavior:** +- `POST /task/` — creates a prove task, returns `taskId` +- `GET /task/{taskId}` — polls task status (`Running` → `Finished` after delay) +- `GET /health` — health check +- Admin endpoints for testing: `/admin/fail-next`, `/admin/never-finish`, `/admin/reset`, `/admin/stats` + +**Proof generation:** +- Signs `keccak256(abi.encode(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block))` with the configured ECDSA key +- Returns ABI-encoded `BatchProof[]` matching `TeeDisputeGame.sol` + +--- + +## How to Run + +### Option 1: Direct `go run` + +```bash +cd tools/mockteeprover + +# Requires SIGNER_PRIVATE_KEY (the TEE signer key registered in TeeProofVerifier) +SIGNER_PRIVATE_KEY=0x... go run . + +# Custom listen address and task delay +SIGNER_PRIVATE_KEY=0x... LISTEN_ADDR=:9000 TASK_DELAY=5s go run . +``` + +### Option 2: Docker + +```bash +cd tools/mockteeprover +docker build -t mockteeprover:latest . +docker run --rm -p 8690:8690 -e SIGNER_PRIVATE_KEY=0x... mockteeprover:latest +``` + +--- + +## curl Testing + +```bash +# Health check +curl -s http://localhost:8690/health | jq . + +# Submit a prove task +curl -s -X POST http://localhost:8690/task/ \ + -H 'Content-Type: application/json' \ + -d '{ + "startBlkHeight": 100, + "endBlkHeight": 200, + "startBlkHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "endBlkHash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "startBlkStateHash": "0x0000000000000000000000000000000000000000000000000000000000000003", + "endBlkStateHash": "0x0000000000000000000000000000000000000000000000000000000000000004" + }' | jq . + +# Poll task status (replace TASK_ID) +curl -s http://localhost:8690/task/TASK_ID | jq . + +# View stats +curl -s http://localhost:8690/admin/stats | jq . +``` + +--- + +## Environment Variables + +| Variable | Default | Description | +|----------------------|----------|----------------------------------------------------------| +| `SIGNER_PRIVATE_KEY` | required | ECDSA private key for signing batch proofs (hex, with or without 0x prefix) | +| `LISTEN_ADDR` | `:8690` | Listen address | +| `TASK_DELAY` | `2s` | Time before a task transitions from Running to Finished | + +--- + +## Admin Endpoints + +| Endpoint | Method | Description | +|------------------------|--------|--------------------------------------------------| +| `/admin/fail-next` | POST | Next created task will immediately fail (one-shot)| +| `/admin/never-finish` | POST | New tasks stay Running forever until reset | +| `/admin/reset` | POST | Clear all control flags | +| `/admin/stats` | GET | Show submitted request count and control flags | diff --git a/tools/mockteeprover/go.mod b/tools/mockteeprover/go.mod new file mode 100644 index 0000000..7686b3a --- /dev/null +++ b/tools/mockteeprover/go.mod @@ -0,0 +1,15 @@ +module github.com/okx/xlayer-toolkit/tools/mockteeprover + +go 1.22.0 + +require ( + github.com/ethereum/go-ethereum v1.14.12 + github.com/google/uuid v1.6.0 +) + +require ( + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/holiman/uint256 v1.3.1 // indirect + golang.org/x/crypto v0.22.0 // indirect + golang.org/x/sys v0.22.0 // indirect +) diff --git a/tools/mockteeprover/go.sum b/tools/mockteeprover/go.sum new file mode 100644 index 0000000..d9f43d0 --- /dev/null +++ b/tools/mockteeprover/go.sum @@ -0,0 +1,24 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/ethereum/go-ethereum v1.14.12 h1:8hl57x77HSUo+cXExrURjU/w1VhL+ShCTJrTwcCQSe4= +github.com/ethereum/go-ethereum v1.14.12/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= +github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/mockteeprover/main.go b/tools/mockteeprover/main.go new file mode 100644 index 0000000..66dae8e --- /dev/null +++ b/tools/mockteeprover/main.go @@ -0,0 +1,287 @@ +package main + +import ( + "crypto/ecdsa" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/google/uuid" +) + +// ProveRequest matches op-challenger/game/tee/prover_client.go ProveRequest. +type ProveRequest struct { + StartBlkHeight uint64 `json:"startBlkHeight"` + EndBlkHeight uint64 `json:"endBlkHeight"` + StartBlkHash string `json:"startBlkHash"` + EndBlkHash string `json:"endBlkHash"` + StartBlkStateHash string `json:"startBlkStateHash"` + EndBlkStateHash string `json:"endBlkStateHash"` +} + +type task struct { + ID string + Status string // "Running", "Finished", "Failed" + CreatedAt time.Time + FinishAt time.Time // when the task should transition to Finished + Request ProveRequest + ProofBytes []byte // set when Finished + FailCode int // error code when Failed +} + +type response struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type MockTEEProver struct { + mu sync.Mutex + tasks map[string]*task + signerKey *ecdsa.PrivateKey + taskDelay time.Duration + + // control flags + failNext bool + neverFinish bool + + // stats + submittedRequests []ProveRequest +} + +func NewMockTEEProver(signerKey *ecdsa.PrivateKey, taskDelay time.Duration) *MockTEEProver { + return &MockTEEProver{ + tasks: make(map[string]*task), + signerKey: signerKey, + taskDelay: taskDelay, + } +} + +// POST /task/ +func (m *MockTEEProver) handleCreateTask(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + writeJSON(w, response{Code: -1, Message: "method not allowed"}) + return + } + + var req ProveRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeJSON(w, response{Code: 10001, Message: fmt.Sprintf("invalid request: %v", err)}) + return + } + + m.mu.Lock() + m.submittedRequests = append(m.submittedRequests, req) + + id := uuid.New().String() + t := &task{ + ID: id, + Status: "Running", + CreatedAt: time.Now(), + FinishAt: time.Now().Add(m.taskDelay), + Request: req, + } + + if m.failNext { + t.Status = "Failed" + t.FailCode = 10000 // retryable + m.failNext = false + } else if m.neverFinish { + // stays Running forever + t.FinishAt = time.Now().Add(24 * time.Hour) + } + + m.tasks[id] = t + m.mu.Unlock() + + log.Printf("[POST /task/] created task %s for blocks %d→%d (status=%s)", id, req.StartBlkHeight, req.EndBlkHeight, t.Status) + + writeJSON(w, response{ + Code: 0, + Message: "ok", + Data: map[string]string{"taskId": id}, + }) +} + +// GET /task/{taskId} +func (m *MockTEEProver) handleGetTask(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeJSON(w, response{Code: -1, Message: "method not allowed"}) + return + } + + taskID := extractTaskID(r.URL.Path) + if taskID == "" { + writeJSON(w, response{Code: 10004, Message: "task not found"}) + return + } + + m.mu.Lock() + t, ok := m.tasks[taskID] + if !ok { + m.mu.Unlock() + writeJSON(w, response{Code: 10004, Message: "task not found"}) + return + } + + // Transition Running → Finished if delay has elapsed + if t.Status == "Running" && time.Now().After(t.FinishAt) { + proofBytes, err := generateProofBytes(t.Request, m.signerKey) + if err != nil { + log.Printf("[GET /task/%s] ERROR generating proof: %v", taskID, err) + t.Status = "Failed" + t.FailCode = 20001 + } else { + t.Status = "Finished" + t.ProofBytes = proofBytes + } + } + + // Build response data + data := map[string]interface{}{ + "taskId": t.ID, + "status": t.Status, + } + if t.Status == "Finished" { + data["proofBytes"] = hexutil.Encode(t.ProofBytes) + } + if t.Status == "Failed" { + data["detail"] = fmt.Sprintf("mock failure (code=%d)", t.FailCode) + } + m.mu.Unlock() + + log.Printf("[GET /task/%s] status=%s", taskID, t.Status) + + writeJSON(w, response{Code: 0, Message: "ok", Data: data}) +} + +// POST /admin/fail-next +func (m *MockTEEProver) handleFailNext(w http.ResponseWriter, r *http.Request) { + m.mu.Lock() + m.failNext = true + m.mu.Unlock() + log.Println("[admin] fail-next enabled") + writeJSON(w, response{Code: 0, Message: "fail-next enabled"}) +} + +// POST /admin/never-finish +func (m *MockTEEProver) handleNeverFinish(w http.ResponseWriter, r *http.Request) { + m.mu.Lock() + m.neverFinish = true + m.mu.Unlock() + log.Println("[admin] never-finish enabled") + writeJSON(w, response{Code: 0, Message: "never-finish enabled"}) +} + +// POST /admin/reset +func (m *MockTEEProver) handleReset(w http.ResponseWriter, r *http.Request) { + m.mu.Lock() + m.failNext = false + m.neverFinish = false + m.mu.Unlock() + log.Println("[admin] reset all control flags") + writeJSON(w, response{Code: 0, Message: "reset"}) +} + +// GET /admin/stats +func (m *MockTEEProver) handleStats(w http.ResponseWriter, r *http.Request) { + m.mu.Lock() + stats := map[string]interface{}{ + "task_count": len(m.submittedRequests), + "requests": m.submittedRequests, + "fail_next": m.failNext, + "never_finish": m.neverFinish, + "active_tasks": len(m.tasks), + } + m.mu.Unlock() + writeJSON(w, stats) +} + +// GET /health +func (m *MockTEEProver) handleHealth(w http.ResponseWriter, r *http.Request) { + writeJSON(w, map[string]string{"status": "ok"}) +} + +func (m *MockTEEProver) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + + switch { + case path == "/health": + m.handleHealth(w, r) + case path == "/task/" && r.Method == http.MethodPost: + m.handleCreateTask(w, r) + case strings.HasPrefix(path, "/task/") && r.Method == http.MethodGet: + m.handleGetTask(w, r) + case path == "/admin/fail-next": + m.handleFailNext(w, r) + case path == "/admin/never-finish": + m.handleNeverFinish(w, r) + case path == "/admin/reset": + m.handleReset(w, r) + case path == "/admin/stats": + m.handleStats(w, r) + default: + http.NotFound(w, r) + } +} + +func extractTaskID(path string) string { + // /task/{taskId} or /task/{taskId}/ + path = strings.TrimPrefix(path, "/task/") + path = strings.TrimSuffix(path, "/") + if path == "" { + return "" + } + return path +} + +func writeJSON(w http.ResponseWriter, v interface{}) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(v); err != nil { + log.Printf("ERROR writing JSON response: %v", err) + } +} + +func main() { + signerKeyHex := os.Getenv("SIGNER_PRIVATE_KEY") + if signerKeyHex == "" { + log.Fatal("SIGNER_PRIVATE_KEY environment variable is required") + } + signerKeyHex = strings.TrimPrefix(signerKeyHex, "0x") + + signerKey, err := crypto.HexToECDSA(signerKeyHex) + if err != nil { + log.Fatalf("invalid SIGNER_PRIVATE_KEY: %v", err) + } + + signerAddr := crypto.PubkeyToAddress(signerKey.PublicKey) + log.Printf("Mock TEE Prover starting, signer address: %s", signerAddr.Hex()) + + listenAddr := os.Getenv("LISTEN_ADDR") + if listenAddr == "" { + listenAddr = ":8690" + } + + taskDelay := 2 * time.Second + if d := os.Getenv("TASK_DELAY"); d != "" { + parsed, err := time.ParseDuration(d) + if err != nil { + log.Fatalf("invalid TASK_DELAY: %v", err) + } + taskDelay = parsed + } + + prover := NewMockTEEProver(signerKey, taskDelay) + + log.Printf("Listening on %s (task_delay=%s)", listenAddr, taskDelay) + if err := http.ListenAndServe(listenAddr, prover); err != nil { + log.Fatalf("server error: %v", err) + } +} diff --git a/tools/mockteeprover/main_test.go b/tools/mockteeprover/main_test.go new file mode 100644 index 0000000..45f7e0d --- /dev/null +++ b/tools/mockteeprover/main_test.go @@ -0,0 +1,207 @@ +package main + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" +) + +func setupTestServer(t *testing.T) (*MockTEEProver, *httptest.Server) { + t.Helper() + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + prover := NewMockTEEProver(key, 100*time.Millisecond) + server := httptest.NewServer(prover) + t.Cleanup(server.Close) + return prover, server +} + +func postTask(t *testing.T, serverURL string, req ProveRequest) string { + t.Helper() + body, _ := json.Marshal(req) + resp, err := http.Post(serverURL+"/task/", "application/json", bytes.NewReader(body)) + if err != nil { + t.Fatalf("POST /task/ failed: %v", err) + } + defer resp.Body.Close() + + var r response + json.NewDecoder(resp.Body).Decode(&r) + if r.Code != 0 { + t.Fatalf("POST /task/ returned code %d: %s", r.Code, r.Message) + } + data := r.Data.(map[string]interface{}) + return data["taskId"].(string) +} + +func getTask(t *testing.T, serverURL, taskID string) map[string]interface{} { + t.Helper() + resp, err := http.Get(serverURL + "/task/" + taskID) + if err != nil { + t.Fatalf("GET /task/%s failed: %v", taskID, err) + } + defer resp.Body.Close() + + var r response + json.NewDecoder(resp.Body).Decode(&r) + return r.Data.(map[string]interface{}) +} + +func TestHealthEndpoint(t *testing.T) { + _, server := setupTestServer(t) + + resp, err := http.Get(server.URL + "/health") + if err != nil { + t.Fatalf("GET /health failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + t.Errorf("expected 200, got %d", resp.StatusCode) + } +} + +func TestCreateAndPollTask(t *testing.T) { + _, server := setupTestServer(t) + + req := ProveRequest{ + StartBlkHeight: 100, + EndBlkHeight: 200, + StartBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000001", + EndBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000002", + StartBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000003", + EndBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000004", + } + + taskID := postTask(t, server.URL, req) + if taskID == "" { + t.Fatal("got empty taskId") + } + + // Immediately should be Running + data := getTask(t, server.URL, taskID) + if data["status"] != "Running" { + t.Errorf("expected Running, got %s", data["status"]) + } + + // Wait for task delay to elapse + time.Sleep(200 * time.Millisecond) + + // Should be Finished now + data = getTask(t, server.URL, taskID) + if data["status"] != "Finished" { + t.Errorf("expected Finished, got %s", data["status"]) + } + if data["proofBytes"] == nil || data["proofBytes"] == "" { + t.Error("expected proofBytes to be set") + } +} + +func TestFailNext(t *testing.T) { + _, server := setupTestServer(t) + + // Enable fail-next + http.Post(server.URL+"/admin/fail-next", "", nil) + + req := ProveRequest{ + StartBlkHeight: 1, + EndBlkHeight: 2, + StartBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000001", + EndBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000002", + StartBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000003", + EndBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000004", + } + + taskID := postTask(t, server.URL, req) + data := getTask(t, server.URL, taskID) + if data["status"] != "Failed" { + t.Errorf("expected Failed, got %s", data["status"]) + } + + // Next task should succeed (fail-next is one-shot) + taskID2 := postTask(t, server.URL, req) + time.Sleep(200 * time.Millisecond) + data2 := getTask(t, server.URL, taskID2) + if data2["status"] != "Finished" { + t.Errorf("expected Finished for second task, got %s", data2["status"]) + } +} + +func TestNeverFinish(t *testing.T) { + _, server := setupTestServer(t) + + http.Post(server.URL+"/admin/never-finish", "", nil) + + req := ProveRequest{ + StartBlkHeight: 1, + EndBlkHeight: 2, + StartBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000001", + EndBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000002", + StartBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000003", + EndBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000004", + } + + taskID := postTask(t, server.URL, req) + time.Sleep(200 * time.Millisecond) + + data := getTask(t, server.URL, taskID) + if data["status"] != "Running" { + t.Errorf("expected Running (never-finish), got %s", data["status"]) + } + + // Reset and verify new tasks finish normally + http.Post(server.URL+"/admin/reset", "", nil) + taskID2 := postTask(t, server.URL, req) + time.Sleep(200 * time.Millisecond) + data2 := getTask(t, server.URL, taskID2) + if data2["status"] != "Finished" { + t.Errorf("expected Finished after reset, got %s", data2["status"]) + } +} + +func TestStats(t *testing.T) { + _, server := setupTestServer(t) + + req := ProveRequest{ + StartBlkHeight: 10, + EndBlkHeight: 20, + StartBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000001", + EndBlkHash: "0x0000000000000000000000000000000000000000000000000000000000000002", + StartBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000003", + EndBlkStateHash: "0x0000000000000000000000000000000000000000000000000000000000000004", + } + + postTask(t, server.URL, req) + postTask(t, server.URL, req) + + resp, _ := http.Get(server.URL + "/admin/stats") + defer resp.Body.Close() + + var stats map[string]interface{} + json.NewDecoder(resp.Body).Decode(&stats) + + count := int(stats["task_count"].(float64)) + if count != 2 { + t.Errorf("expected 2 tasks in stats, got %d", count) + } +} + +func TestTaskNotFound(t *testing.T) { + _, server := setupTestServer(t) + + resp, _ := http.Get(server.URL + "/task/nonexistent-id") + defer resp.Body.Close() + + var r response + json.NewDecoder(resp.Body).Decode(&r) + if r.Code != 10004 { + t.Errorf("expected code 10004, got %d", r.Code) + } +} diff --git a/tools/mockteeprover/proof.go b/tools/mockteeprover/proof.go new file mode 100644 index 0000000..3c8ff16 --- /dev/null +++ b/tools/mockteeprover/proof.go @@ -0,0 +1,132 @@ +package main + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// BatchProof mirrors the Solidity struct in TeeDisputeGame.sol: +// +// struct BatchProof { +// bytes32 startBlockHash; +// bytes32 startStateHash; +// bytes32 endBlockHash; +// bytes32 endStateHash; +// uint256 l2Block; +// bytes signature; +// } +type BatchProof struct { + StartBlockHash [32]byte + StartStateHash [32]byte + EndBlockHash [32]byte + EndStateHash [32]byte + L2Block *big.Int + Signature []byte +} + +// batchProofABIType is the ABI type for BatchProof[] used in abi.encode. +var batchProofABIType abi.Arguments + +func init() { + tupleType, err := abi.NewType("tuple[]", "", []abi.ArgumentMarshaling{ + {Name: "startBlockHash", Type: "bytes32"}, + {Name: "startStateHash", Type: "bytes32"}, + {Name: "endBlockHash", Type: "bytes32"}, + {Name: "endStateHash", Type: "bytes32"}, + {Name: "l2Block", Type: "uint256"}, + {Name: "signature", Type: "bytes"}, + }) + if err != nil { + panic(fmt.Sprintf("failed to create BatchProof ABI type: %v", err)) + } + batchProofABIType = abi.Arguments{{Type: tupleType}} +} + +// digestABIArgs is used to compute keccak256(abi.encode(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block)). +var digestABIArgs abi.Arguments + +func init() { + bytes32Ty, _ := abi.NewType("bytes32", "", nil) + uint256Ty, _ := abi.NewType("uint256", "", nil) + digestABIArgs = abi.Arguments{ + {Type: bytes32Ty}, + {Type: bytes32Ty}, + {Type: bytes32Ty}, + {Type: bytes32Ty}, + {Type: uint256Ty}, + } +} + +// computeBatchDigest computes keccak256(abi.encode(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block)). +func computeBatchDigest(startBlockHash, startStateHash, endBlockHash, endStateHash [32]byte, l2Block *big.Int) common.Hash { + packed, err := digestABIArgs.Pack(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block) + if err != nil { + panic(fmt.Sprintf("failed to pack batch digest: %v", err)) + } + return crypto.Keccak256Hash(packed) +} + +// signBatchDigest signs a batch digest with the given ECDSA private key. +// Returns 65-byte signature [r(32) || s(32) || v(1)] with v = 27 or 28 (Solidity ecrecover convention). +func signBatchDigest(digest common.Hash, key *ecdsa.PrivateKey) ([]byte, error) { + sig, err := crypto.Sign(digest.Bytes(), key) + if err != nil { + return nil, fmt.Errorf("failed to sign batch digest: %w", err) + } + // go-ethereum Sign returns v as 0/1; Solidity ecrecover expects 27/28 + sig[64] += 27 + return sig, nil +} + +// generateProofBytes constructs an ABI-encoded BatchProof[] from a ProveRequest. +// It creates a single BatchProof covering the full range, signs it, and encodes. +func generateProofBytes(req ProveRequest, signerKey *ecdsa.PrivateKey) ([]byte, error) { + startBlockHash := common.HexToHash(req.StartBlkHash) + startStateHash := common.HexToHash(req.StartBlkStateHash) + endBlockHash := common.HexToHash(req.EndBlkHash) + endStateHash := common.HexToHash(req.EndBlkStateHash) + l2Block := new(big.Int).SetUint64(req.EndBlkHeight) + + digest := computeBatchDigest( + [32]byte(startBlockHash), + [32]byte(startStateHash), + [32]byte(endBlockHash), + [32]byte(endStateHash), + l2Block, + ) + + sig, err := signBatchDigest(digest, signerKey) + if err != nil { + return nil, err + } + + // Build a single-element BatchProof array + proofs := []struct { + StartBlockHash [32]byte `abi:"startBlockHash"` + StartStateHash [32]byte `abi:"startStateHash"` + EndBlockHash [32]byte `abi:"endBlockHash"` + EndStateHash [32]byte `abi:"endStateHash"` + L2Block *big.Int `abi:"l2Block"` + Signature []byte `abi:"signature"` + }{ + { + StartBlockHash: [32]byte(startBlockHash), + StartStateHash: [32]byte(startStateHash), + EndBlockHash: [32]byte(endBlockHash), + EndStateHash: [32]byte(endStateHash), + L2Block: l2Block, + Signature: sig, + }, + } + + encoded, err := batchProofABIType.Pack(proofs) + if err != nil { + return nil, fmt.Errorf("failed to ABI-encode BatchProof[]: %w", err) + } + return encoded, nil +} diff --git a/tools/mockteeprover/proof_test.go b/tools/mockteeprover/proof_test.go new file mode 100644 index 0000000..29701e9 --- /dev/null +++ b/tools/mockteeprover/proof_test.go @@ -0,0 +1,126 @@ +package main + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestGenerateProofBytes(t *testing.T) { + // Generate a test signer key + signerKey, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate key: %v", err) + } + signerAddr := crypto.PubkeyToAddress(signerKey.PublicKey) + + req := ProveRequest{ + StartBlkHeight: 100, + EndBlkHeight: 200, + StartBlkHash: "0x" + common.Bytes2Hex(common.LeftPadBytes([]byte{0x01}, 32)), + EndBlkHash: "0x" + common.Bytes2Hex(common.LeftPadBytes([]byte{0x02}, 32)), + StartBlkStateHash: "0x" + common.Bytes2Hex(common.LeftPadBytes([]byte{0x03}, 32)), + EndBlkStateHash: "0x" + common.Bytes2Hex(common.LeftPadBytes([]byte{0x04}, 32)), + } + + proofBytes, err := generateProofBytes(req, signerKey) + if err != nil { + t.Fatalf("generateProofBytes failed: %v", err) + } + + if len(proofBytes) == 0 { + t.Fatal("proofBytes is empty") + } + + // Decode using abi.ConvertType to a known Go type + decoded, err := batchProofABIType.Unpack(proofBytes) + if err != nil { + t.Fatalf("failed to unpack proofBytes: %v", err) + } + + var proofs []BatchProof + if err := batchProofABIType.Copy(&proofs, decoded); err != nil { + t.Fatalf("failed to copy decoded proofs: %v", err) + } + + if len(proofs) != 1 { + t.Fatalf("expected 1 proof, got %d", len(proofs)) + } + + proof := proofs[0] + + // Verify l2Block + if proof.L2Block.Uint64() != 200 { + t.Errorf("expected l2Block=200, got %d", proof.L2Block.Uint64()) + } + + // Verify signature by recovering signer + digest := computeBatchDigest( + proof.StartBlockHash, + proof.StartStateHash, + proof.EndBlockHash, + proof.EndStateHash, + proof.L2Block, + ) + + // Convert v back from 27/28 to 0/1 for crypto.Ecrecover + sig := make([]byte, 65) + copy(sig, proof.Signature) + sig[64] -= 27 + + pubKey, err := crypto.Ecrecover(digest.Bytes(), sig) + if err != nil { + t.Fatalf("Ecrecover failed: %v", err) + } + recoveredAddr := common.BytesToAddress(crypto.Keccak256(pubKey[1:])[12:]) + + if recoveredAddr != signerAddr { + t.Errorf("recovered address %s != signer %s", recoveredAddr.Hex(), signerAddr.Hex()) + } + + t.Logf("proofBytes length: %d bytes", len(proofBytes)) + t.Logf("signer: %s, recovered: %s", signerAddr.Hex(), recoveredAddr.Hex()) +} + +func TestComputeBatchDigest(t *testing.T) { + // Deterministic inputs + startBlockHash := [32]byte{0x01} + startStateHash := [32]byte{0x02} + endBlockHash := [32]byte{0x03} + endStateHash := [32]byte{0x04} + l2Block := big.NewInt(100) + + d1 := computeBatchDigest(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block) + d2 := computeBatchDigest(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block) + + if d1 != d2 { + t.Error("digest should be deterministic") + } + + // Different input should produce different digest + d3 := computeBatchDigest(startBlockHash, startStateHash, endBlockHash, endStateHash, big.NewInt(101)) + if d1 == d3 { + t.Error("different input should produce different digest") + } +} + +func TestSignBatchDigest(t *testing.T) { + key, _ := crypto.GenerateKey() + digest := common.HexToHash("0xdeadbeef") + + sig, err := signBatchDigest(digest, key) + if err != nil { + t.Fatalf("signBatchDigest failed: %v", err) + } + + if len(sig) != 65 { + t.Fatalf("expected 65 byte signature, got %d", len(sig)) + } + + // v should be 27 or 28 + if sig[64] != 27 && sig[64] != 28 { + t.Errorf("v byte should be 27 or 28, got %d", sig[64]) + } +} From 86d1532e6b105c17133a937c5ababeb1e234adc4 Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Thu, 26 Mar 2026 10:48:54 +0800 Subject: [PATCH 18/30] feat: add mockteeprover config to example.env Co-Authored-By: Claude Opus 4.6 --- devnet/example.env | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/devnet/example.env b/devnet/example.env index 7dda856..bcb8968 100644 --- a/devnet/example.env +++ b/devnet/example.env @@ -78,6 +78,12 @@ OP_CONTRACTS_TEE_IMAGE_TAG=op-contracts:tee SKIP_MOCKTEERPC_BUILD=true MOCKTEERPC_IMAGE_TAG=mockteerpc:latest +# ============================================================================== +# MockTeeProver Configuration +# ============================================================================== +SKIP_MOCKTEEPROVER_BUILD=true +MOCKTEEPROVER_IMAGE_TAG=mockteeprover:latest + # ============================================================================== # Build Configuration # ============================================================================== From 8e3a51b5da9d3943210bd4392069cf979c00adb6 Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Thu, 26 Mar 2026 14:14:07 +0800 Subject: [PATCH 19/30] feat: add MockTeeProver image build step in init.sh Co-Authored-By: Claude Opus 4.6 --- devnet/init.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/devnet/init.sh b/devnet/init.sh index ff8b285..7d28c49 100755 --- a/devnet/init.sh +++ b/devnet/init.sh @@ -204,3 +204,14 @@ else MOCKTEERPC_DIR="$PWD_DIR/../tools/mockteerpc" build_and_tag_image "mockteerpc" "$MOCKTEERPC_IMAGE_TAG" "$MOCKTEERPC_DIR" "Dockerfile" fi + +# Build MockTeeProver image +if [ "$SKIP_MOCKTEEPROVER_BUILD" = "true" ]; then + echo "⏭️ Skipping mockteeprover build" +else + echo "🔨 Building $MOCKTEEPROVER_IMAGE_TAG" + MOCKTEEPROVER_DIR="$PWD_DIR/../tools/mockteeprover" + build_and_tag_image "mockteeprover" "$MOCKTEEPROVER_IMAGE_TAG" "$MOCKTEEPROVER_DIR" "Dockerfile" +fi + + From e4797b4c2d4ace65270afaa447ef2026557b4711 Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Thu, 26 Mar 2026 15:29:42 +0800 Subject: [PATCH 20/30] feat: add TEE signer key and fix tee-challenger game-window - Add TEE_SIGNER_PRIVATE_KEY to example.env for mockteeprover - Hardcode tee-challenger game-window to 86400s to prevent games falling out of window before resolution Co-Authored-By: Claude Opus 4.6 --- devnet/docker-compose-tee.yml | 2 +- devnet/example.env | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/devnet/docker-compose-tee.yml b/devnet/docker-compose-tee.yml index 0274883..9354e31 100644 --- a/devnet/docker-compose-tee.yml +++ b/devnet/docker-compose-tee.yml @@ -66,7 +66,7 @@ services: - --tee-prover-rpc=http://mockteeprover:8690 - --tee-prove-poll-interval=2s - --tee-prove-timeout=120s - - --game-window=${GAME_WINDOW}s + - --game-window=86400s depends_on: - mockteeprover - tee-proposer diff --git a/devnet/example.env b/devnet/example.env index bcb8968..6bb0fbe 100644 --- a/devnet/example.env +++ b/devnet/example.env @@ -83,6 +83,7 @@ MOCKTEERPC_IMAGE_TAG=mockteerpc:latest # ============================================================================== SKIP_MOCKTEEPROVER_BUILD=true MOCKTEEPROVER_IMAGE_TAG=mockteeprover:latest +TEE_SIGNER_PRIVATE_KEY=0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e # ============================================================================== # Build Configuration From f20297501183a5bc043715766fc3dbb8ade37a2f Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Thu, 26 Mar 2026 15:38:52 +0800 Subject: [PATCH 21/30] fix: use correct TEE signer key matching registered enclave address Co-Authored-By: Claude Opus 4.6 --- devnet/example.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devnet/example.env b/devnet/example.env index 6bb0fbe..0e3fb0a 100644 --- a/devnet/example.env +++ b/devnet/example.env @@ -83,7 +83,7 @@ MOCKTEERPC_IMAGE_TAG=mockteerpc:latest # ============================================================================== SKIP_MOCKTEEPROVER_BUILD=true MOCKTEEPROVER_IMAGE_TAG=mockteeprover:latest -TEE_SIGNER_PRIVATE_KEY=0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e +TEE_SIGNER_PRIVATE_KEY=0x8b3a350cf5c34c9194ca9aa3f146b2b9afed22cd83d3c5f6a3f2f243ce220c01 # ============================================================================== # Build Configuration From 2bbfe7640b90fdce902bade6131db94e775a689b Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Thu, 26 Mar 2026 17:14:21 +0800 Subject: [PATCH 22/30] fix: update mockteeprover routes from /task/ to /tee/task/ Co-Authored-By: Claude Opus 4.6 --- tools/mockteeprover/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/mockteeprover/main.go b/tools/mockteeprover/main.go index 66dae8e..ff53f1e 100644 --- a/tools/mockteeprover/main.go +++ b/tools/mockteeprover/main.go @@ -215,9 +215,9 @@ func (m *MockTEEProver) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { case path == "/health": m.handleHealth(w, r) - case path == "/task/" && r.Method == http.MethodPost: + case path == "/tee/task/" && r.Method == http.MethodPost: m.handleCreateTask(w, r) - case strings.HasPrefix(path, "/task/") && r.Method == http.MethodGet: + case strings.HasPrefix(path, "/tee/task/") && r.Method == http.MethodGet: m.handleGetTask(w, r) case path == "/admin/fail-next": m.handleFailNext(w, r) @@ -234,7 +234,7 @@ func (m *MockTEEProver) ServeHTTP(w http.ResponseWriter, r *http.Request) { func extractTaskID(path string) string { // /task/{taskId} or /task/{taskId}/ - path = strings.TrimPrefix(path, "/task/") + path = strings.TrimPrefix(path, "/tee/task/") path = strings.TrimSuffix(path, "/") if path == "" { return "" From 619553fd826f4e497898acc61b51085af6421443 Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Fri, 27 Mar 2026 15:05:14 +0800 Subject: [PATCH 23/30] refactor(tee-challenger): use proposer key and add selective claim resolution - Switch tee-challenger private key from OP_CHALLENGER to OP_PROPOSER - Add --selective-claim-resolution flag - Remove --additional-bond-claimants (tx sender is now proposer) - Add CHALLENGER_ADDRESS env var support in add-tee-game-type.sh Co-Authored-By: Claude Sonnet 4.6 --- devnet/docker-compose-tee.yml | 6 +++--- devnet/scripts/add-tee-game-type.sh | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/devnet/docker-compose-tee.yml b/devnet/docker-compose-tee.yml index 9354e31..5d97acc 100644 --- a/devnet/docker-compose-tee.yml +++ b/devnet/docker-compose-tee.yml @@ -49,7 +49,7 @@ services: container_name: tee-challenger environment: - DISPUTE_GAME_FACTORY_ADDRESS=${DISPUTE_GAME_FACTORY_ADDRESS} - - OP_CHALLENGER_PRIVATE_KEY=${OP_CHALLENGER_PRIVATE_KEY} + - OP_PROPOSER_PRIVATE_KEY=${OP_PROPOSER_PRIVATE_KEY} volumes: - ./data/tee-challenger-data:/data command: @@ -58,14 +58,14 @@ services: - --l1-eth-rpc=${L1_RPC_URL_IN_DOCKER} - --l1-beacon=${L1_BEACON_URL_IN_DOCKER} - --game-factory-address=${DISPUTE_GAME_FACTORY_ADDRESS} - - --private-key=${OP_CHALLENGER_PRIVATE_KEY} + - --private-key=${OP_PROPOSER_PRIVATE_KEY} - --game-types=tee - --datadir=/data - --http-poll-interval=2s - - --additional-bond-claimants=${PROPOSER_ADDRESS} - --tee-prover-rpc=http://mockteeprover:8690 - --tee-prove-poll-interval=2s - --tee-prove-timeout=120s + - --selective-claim-resolution - --game-window=86400s depends_on: - mockteeprover diff --git a/devnet/scripts/add-tee-game-type.sh b/devnet/scripts/add-tee-game-type.sh index a8d0dcc..cf25c9c 100755 --- a/devnet/scripts/add-tee-game-type.sh +++ b/devnet/scripts/add-tee-game-type.sh @@ -106,6 +106,7 @@ export EXISTING_ASR="$ANCHOR_STATE_REGISTRY_ADDR" export SYSTEM_CONFIG_ADDRESS="$SYSTEM_CONFIG_PROXY_ADDRESS" export DISPUTE_GAME_FINALITY_DELAY_SECONDS="${DISPUTE_GAME_FINALITY_DELAY_SECONDS:-5}" + # TEE game timing — reuse devnet values for fast iteration export MAX_CHALLENGE_DURATION="${ARG_MAX_CHALLENGE_DURATION:-${MAX_CLOCK_DURATION:-20}}" export MAX_PROVE_DURATION="${ARG_MAX_PROVE_DURATION:-${MAX_CLOCK_DURATION:-20}}" @@ -121,6 +122,12 @@ else unset PROPOSER_ADDRESS fi +if [ -n "$CHALLENGER_ADDRESS" ]; then + export CHALLENGER_ADDRESS +else + unset CHALLENGER_ADDRESS +fi + if [ "$USE_MOCK_VERIFIER" = "true" ] && [ -z "$MOCK_ENCLAVE_ADDRESS" ]; then echo "❌ --mock-verifier requires --enclave
(e.g. --mock-verifier --enclave 0xABC...)" exit 1 From ac8676fd68b1363fad51fdf7051c4aee7586baaf Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Fri, 27 Mar 2026 17:02:10 +0800 Subject: [PATCH 24/30] chore: update TEE signer key and add CHALLENGER_ADDRESS in example.env - Change TEE_SIGNER_PRIVATE_KEY to proposer key - Add CHALLENGER_ADDRESS for new contract deployment Co-Authored-By: Claude Sonnet 4.6 --- devnet/example.env | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devnet/example.env b/devnet/example.env index 0e3fb0a..9523c41 100644 --- a/devnet/example.env +++ b/devnet/example.env @@ -83,7 +83,7 @@ MOCKTEERPC_IMAGE_TAG=mockteerpc:latest # ============================================================================== SKIP_MOCKTEEPROVER_BUILD=true MOCKTEEPROVER_IMAGE_TAG=mockteeprover:latest -TEE_SIGNER_PRIVATE_KEY=0x8b3a350cf5c34c9194ca9aa3f146b2b9afed22cd83d3c5f6a3f2f243ce220c01 +TEE_SIGNER_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d # ============================================================================== # Build Configuration @@ -141,6 +141,7 @@ OP_CHALLENGER_PRIVATE_KEY=0x8b3a350cf5c34c9194ca9aa3f146b2b9afed22cd83d3c5f6a3f2 # ============================================================================== DEPLOYER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 PROPOSER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +CHALLENGER_ADDRESS=0x7d18A1B858253b5588f61fb5739d52e4b84e2cdA ADMIN_OWNER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 SAFE_ADDRESS=0x0000000000000000000000000000000000000000 From aec4c35e280dcb8c4b4640f035035980c02c4128 Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Fri, 27 Mar 2026 17:19:16 +0800 Subject: [PATCH 25/30] fix: derive enclave address from TEE_SIGNER_PRIVATE_KEY instead of OP_CHALLENGER Co-Authored-By: Claude Sonnet 4.6 --- devnet/7-run-tee-game.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devnet/7-run-tee-game.sh b/devnet/7-run-tee-game.sh index c189a21..dee38d5 100755 --- a/devnet/7-run-tee-game.sh +++ b/devnet/7-run-tee-game.sh @@ -38,7 +38,7 @@ if [ ${#MISSING_IMAGES[@]} -gt 0 ]; then exit 1 fi -ENCLAVE_ADDRESS=$(cast wallet address --private-key "$OP_CHALLENGER_PRIVATE_KEY") +ENCLAVE_ADDRESS=$(cast wallet address --private-key "$TEE_SIGNER_PRIVATE_KEY") echo "🔧 Adding TEE game type..." echo " Image: $OP_CONTRACTS_TEE_IMAGE_TAG" From 543732f64ac6c8d3168c35af72246588a5733d8d Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Fri, 27 Mar 2026 18:11:13 +0800 Subject: [PATCH 26/30] fix url prob --- devnet/5-run-op-succinct.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devnet/5-run-op-succinct.sh b/devnet/5-run-op-succinct.sh index 7cf83a2..9236e23 100755 --- a/devnet/5-run-op-succinct.sh +++ b/devnet/5-run-op-succinct.sh @@ -58,7 +58,7 @@ sed_inplace "s|^STARTING_L2_BLOCK_NUMBER=.*|STARTING_L2_BLOCK_NUMBER=$((FORK_BLO sed_inplace "s|^OP_SUCCINCT_MOCK=.*|OP_SUCCINCT_MOCK=$PROOF_MOCK_MODE|" "$OP_SUCCINCT_DIR"/.env.deploy -STARTING_L2_BLOCK_NUMBER=$(cast call "$ANCHOR_STATE_REGISTRY" "getAnchorRoot()(bytes32,uint256)" --json | jq -r '.[1]') +STARTING_L2_BLOCK_NUMBER=$(cast call "$ANCHOR_STATE_REGISTRY" "getAnchorRoot()(bytes32,uint256)" --json -r "$L1_RPC_URL" | jq -r '.[1]') sed_inplace "s|^STARTING_L2_BLOCK_NUMBER=.*|STARTING_L2_BLOCK_NUMBER=$STARTING_L2_BLOCK_NUMBER|" "$OP_SUCCINCT_DIR"/.env.deploy # update .env.proposer @@ -91,9 +91,9 @@ grep -q "^ANCHOR_STATE_REGISTRY_ADDRESS=" "$OP_SUCCINCT_DIR"/.env.challenger \ && sed_inplace "s|^ANCHOR_STATE_REGISTRY_ADDRESS=.*|ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY|" "$OP_SUCCINCT_DIR"/.env.challenger \ || echo "ANCHOR_STATE_REGISTRY_ADDRESS=$NEW_ANCHOR_STATE_REGISTRY" >> "$OP_SUCCINCT_DIR"/.env.challenger -cast send "$ANCHOR_STATE_REGISTRY" "setRespectedGameType(uint32)" 42 --private-key="$DEPLOYER_PRIVATE_KEY" +cast send "$ANCHOR_STATE_REGISTRY" "setRespectedGameType(uint32)" 42 --private-key="$DEPLOYER_PRIVATE_KEY" --rpc-url "$L1_RPC_URL" -TARGET_HEIGHT=$(cast call "$ANCHOR_STATE_REGISTRY" "getAnchorRoot()(bytes32,uint256)" --json | jq -r '.[1]') +TARGET_HEIGHT=$(cast call "$ANCHOR_STATE_REGISTRY" "getAnchorRoot()(bytes32,uint256)" --json -r "$L1_RPC_URL" | jq -r '.[1]') while true; do CURRENT_HEIGHT=$(cast bn -r "$L2_RPC_URL" finalized 2>/dev/null || echo "0") From 74992915f6a629f36f96c6261b49672e66e8a9db Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Fri, 27 Mar 2026 18:16:13 +0800 Subject: [PATCH 27/30] feat(mockteeprover): add EIP-712 signing to match new TeeDisputeGame contract Co-Authored-By: Claude Sonnet 4.6 --- devnet/7-run-tee-game.sh | 7 +++ devnet/docker-compose-tee.yml | 2 + tools/mockteeprover/main.go | 34 ++++++++++-- tools/mockteeprover/proof.go | 99 +++++++++++++++++++++++++++++------ 4 files changed, 124 insertions(+), 18 deletions(-) diff --git a/devnet/7-run-tee-game.sh b/devnet/7-run-tee-game.sh index dee38d5..703f5b5 100755 --- a/devnet/7-run-tee-game.sh +++ b/devnet/7-run-tee-game.sh @@ -62,6 +62,13 @@ docker run --rm \ --enclave "$ENCLAVE_ADDRESS" \ /app/packages/contracts-bedrock +# Query L1 chain ID and TEE proof verifier address for EIP-712 domain separator +export L1_CHAIN_ID=$(cast chain-id --rpc-url "$L1_RPC_URL") +TEE_GAME_IMPL=$(cast call --rpc-url "$L1_RPC_URL" "$DISPUTE_GAME_FACTORY_ADDRESS" 'gameImpls(uint32)(address)' "$TEE_GAME_TYPE") +export TEE_PROOF_VERIFIER_ADDRESS=$(cast call --rpc-url "$L1_RPC_URL" "$TEE_GAME_IMPL" 'teeProofVerifier()(address)') +echo " L1 Chain ID: $L1_CHAIN_ID" +echo " TEE Proof Verifier: $TEE_PROOF_VERIFIER_ADDRESS" + echo "🚀 Starting TEE services..." docker compose -f docker-compose.yml -f docker-compose-tee.yml up -d mockteerpc mockteeprover tee-proposer tee-challenger diff --git a/devnet/docker-compose-tee.yml b/devnet/docker-compose-tee.yml index 5d97acc..d2c66a1 100644 --- a/devnet/docker-compose-tee.yml +++ b/devnet/docker-compose-tee.yml @@ -19,6 +19,8 @@ services: container_name: mockteeprover environment: - SIGNER_PRIVATE_KEY=${TEE_SIGNER_PRIVATE_KEY} + - CHAIN_ID=${L1_CHAIN_ID:-1337} + - VERIFYING_CONTRACT=${TEE_PROOF_VERIFIER_ADDRESS} - TASK_DELAY=${TEE_TASK_DELAY:-2s} ports: - "8690:8690" diff --git a/tools/mockteeprover/main.go b/tools/mockteeprover/main.go index ff53f1e..c4930cc 100644 --- a/tools/mockteeprover/main.go +++ b/tools/mockteeprover/main.go @@ -5,12 +5,15 @@ import ( "encoding/json" "fmt" "log" + "math/big" "net/http" "os" + "strconv" "strings" "sync" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" @@ -46,6 +49,7 @@ type MockTEEProver struct { mu sync.Mutex tasks map[string]*task signerKey *ecdsa.PrivateKey + domainSep common.Hash taskDelay time.Duration // control flags @@ -56,10 +60,11 @@ type MockTEEProver struct { submittedRequests []ProveRequest } -func NewMockTEEProver(signerKey *ecdsa.PrivateKey, taskDelay time.Duration) *MockTEEProver { +func NewMockTEEProver(signerKey *ecdsa.PrivateKey, domainSep common.Hash, taskDelay time.Duration) *MockTEEProver { return &MockTEEProver{ tasks: make(map[string]*task), signerKey: signerKey, + domainSep: domainSep, taskDelay: taskDelay, } } @@ -133,7 +138,7 @@ func (m *MockTEEProver) handleGetTask(w http.ResponseWriter, r *http.Request) { // Transition Running → Finished if delay has elapsed if t.Status == "Running" && time.Now().After(t.FinishAt) { - proofBytes, err := generateProofBytes(t.Request, m.signerKey) + proofBytes, err := generateProofBytes(t.Request, m.signerKey, m.domainSep) if err != nil { log.Printf("[GET /task/%s] ERROR generating proof: %v", taskID, err) t.Status = "Failed" @@ -264,6 +269,29 @@ func main() { signerAddr := crypto.PubkeyToAddress(signerKey.PublicKey) log.Printf("Mock TEE Prover starting, signer address: %s", signerAddr.Hex()) + // EIP-712 domain config + chainIDStr := os.Getenv("CHAIN_ID") + if chainIDStr == "" { + log.Fatal("CHAIN_ID environment variable is required") + } + chainID, err := strconv.ParseInt(chainIDStr, 10, 64) + if err != nil { + log.Fatalf("invalid CHAIN_ID: %v", err) + } + + verifyingContractHex := os.Getenv("VERIFYING_CONTRACT") + if verifyingContractHex == "" { + log.Fatal("VERIFYING_CONTRACT environment variable is required") + } + verifyingContract := common.HexToAddress(verifyingContractHex) + + domainCfg := EIP712DomainConfig{ + ChainID: big.NewInt(chainID), + VerifyingContract: verifyingContract, + } + domainSep := computeDomainSeparator(domainCfg) + log.Printf("EIP-712 domain separator: %s (chainId=%d, verifyingContract=%s)", domainSep.Hex(), chainID, verifyingContract.Hex()) + listenAddr := os.Getenv("LISTEN_ADDR") if listenAddr == "" { listenAddr = ":8690" @@ -278,7 +306,7 @@ func main() { taskDelay = parsed } - prover := NewMockTEEProver(signerKey, taskDelay) + prover := NewMockTEEProver(signerKey, domainSep, taskDelay) log.Printf("Listening on %s (task_delay=%s)", listenAddr, taskDelay) if err := http.ListenAndServe(listenAddr, prover); err != nil { diff --git a/tools/mockteeprover/proof.go b/tools/mockteeprover/proof.go index 3c8ff16..b65318b 100644 --- a/tools/mockteeprover/proof.go +++ b/tools/mockteeprover/proof.go @@ -10,6 +10,24 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) +// EIP-712 constants matching TeeDisputeGame.sol +var ( + // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") + domainTypehash = crypto.Keccak256Hash([]byte("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")) + // keccak256("TeeDisputeGame") + domainNameHash = crypto.Keccak256Hash([]byte("TeeDisputeGame")) + // keccak256("1") + domainVersionHash = crypto.Keccak256Hash([]byte("1")) + // keccak256("BatchProof(bytes32 startBlockHash,bytes32 startStateHash,bytes32 endBlockHash,bytes32 endStateHash,uint256 l2Block)") + batchProofTypehash = crypto.Keccak256Hash([]byte("BatchProof(bytes32 startBlockHash,bytes32 startStateHash,bytes32 endBlockHash,bytes32 endStateHash,uint256 l2Block)")) +) + +// EIP712DomainConfig holds the chain-specific EIP-712 domain parameters. +type EIP712DomainConfig struct { + ChainID *big.Int + VerifyingContract common.Address +} + // BatchProof mirrors the Solidity struct in TeeDisputeGame.sol: // // struct BatchProof { @@ -47,30 +65,80 @@ func init() { batchProofABIType = abi.Arguments{{Type: tupleType}} } -// digestABIArgs is used to compute keccak256(abi.encode(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block)). -var digestABIArgs abi.Arguments +// structHashABIArgs is used to compute keccak256(abi.encode(BATCH_PROOF_TYPEHASH, ...fields...)). +var structHashABIArgs abi.Arguments func init() { bytes32Ty, _ := abi.NewType("bytes32", "", nil) uint256Ty, _ := abi.NewType("uint256", "", nil) - digestABIArgs = abi.Arguments{ - {Type: bytes32Ty}, - {Type: bytes32Ty}, - {Type: bytes32Ty}, - {Type: bytes32Ty}, - {Type: uint256Ty}, + structHashABIArgs = abi.Arguments{ + {Type: bytes32Ty}, // BATCH_PROOF_TYPEHASH + {Type: bytes32Ty}, // startBlockHash + {Type: bytes32Ty}, // startStateHash + {Type: bytes32Ty}, // endBlockHash + {Type: bytes32Ty}, // endStateHash + {Type: uint256Ty}, // l2Block } } -// computeBatchDigest computes keccak256(abi.encode(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block)). -func computeBatchDigest(startBlockHash, startStateHash, endBlockHash, endStateHash [32]byte, l2Block *big.Int) common.Hash { - packed, err := digestABIArgs.Pack(startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block) +// domainSeparatorABIArgs is used to compute the EIP-712 domain separator. +var domainSeparatorABIArgs abi.Arguments + +func init() { + bytes32Ty, _ := abi.NewType("bytes32", "", nil) + uint256Ty, _ := abi.NewType("uint256", "", nil) + addressTy, _ := abi.NewType("address", "", nil) + domainSeparatorABIArgs = abi.Arguments{ + {Type: bytes32Ty}, // DOMAIN_TYPEHASH + {Type: bytes32Ty}, // nameHash + {Type: bytes32Ty}, // versionHash + {Type: uint256Ty}, // chainId + {Type: addressTy}, // verifyingContract + } +} + +// computeDomainSeparator computes the EIP-712 domain separator matching TeeDisputeGame._domainSeparator(). +func computeDomainSeparator(cfg EIP712DomainConfig) common.Hash { + packed, err := domainSeparatorABIArgs.Pack( + [32]byte(domainTypehash), + [32]byte(domainNameHash), + [32]byte(domainVersionHash), + cfg.ChainID, + cfg.VerifyingContract, + ) if err != nil { - panic(fmt.Sprintf("failed to pack batch digest: %v", err)) + panic(fmt.Sprintf("failed to pack domain separator: %v", err)) } return crypto.Keccak256Hash(packed) } +// computeEIP712Digest computes the full EIP-712 digest: +// keccak256("\x19\x01" || domainSeparator || structHash) +// where structHash = keccak256(abi.encode(BATCH_PROOF_TYPEHASH, startBlockHash, startStateHash, endBlockHash, endStateHash, l2Block)) +func computeEIP712Digest(domainSep common.Hash, startBlockHash, startStateHash, endBlockHash, endStateHash [32]byte, l2Block *big.Int) common.Hash { + // structHash = keccak256(abi.encode(BATCH_PROOF_TYPEHASH, ...)) + packed, err := structHashABIArgs.Pack( + [32]byte(batchProofTypehash), + startBlockHash, + startStateHash, + endBlockHash, + endStateHash, + l2Block, + ) + if err != nil { + panic(fmt.Sprintf("failed to pack struct hash: %v", err)) + } + structHash := crypto.Keccak256Hash(packed) + + // EIP-712: keccak256("\x19\x01" || domainSeparator || structHash) + raw := make([]byte, 2+32+32) + raw[0] = 0x19 + raw[1] = 0x01 + copy(raw[2:34], domainSep.Bytes()) + copy(raw[34:66], structHash.Bytes()) + return crypto.Keccak256Hash(raw) +} + // signBatchDigest signs a batch digest with the given ECDSA private key. // Returns 65-byte signature [r(32) || s(32) || v(1)] with v = 27 or 28 (Solidity ecrecover convention). func signBatchDigest(digest common.Hash, key *ecdsa.PrivateKey) ([]byte, error) { @@ -84,15 +152,16 @@ func signBatchDigest(digest common.Hash, key *ecdsa.PrivateKey) ([]byte, error) } // generateProofBytes constructs an ABI-encoded BatchProof[] from a ProveRequest. -// It creates a single BatchProof covering the full range, signs it, and encodes. -func generateProofBytes(req ProveRequest, signerKey *ecdsa.PrivateKey) ([]byte, error) { +// It creates a single BatchProof covering the full range, signs it with EIP-712, and encodes. +func generateProofBytes(req ProveRequest, signerKey *ecdsa.PrivateKey, domainSep common.Hash) ([]byte, error) { startBlockHash := common.HexToHash(req.StartBlkHash) startStateHash := common.HexToHash(req.StartBlkStateHash) endBlockHash := common.HexToHash(req.EndBlkHash) endStateHash := common.HexToHash(req.EndBlkStateHash) l2Block := new(big.Int).SetUint64(req.EndBlkHeight) - digest := computeBatchDigest( + digest := computeEIP712Digest( + domainSep, [32]byte(startBlockHash), [32]byte(startStateHash), [32]byte(endBlockHash), From 55a8bc57237e7e37ed80e2e9d6ce2bc4d1686deb Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Mon, 30 Mar 2026 10:47:39 +0800 Subject: [PATCH 28/30] feat(mockteeprover): support per-request chainId and teeProofVerifierAddr overrides Co-Authored-By: Claude Sonnet 4.6 --- tools/mockteeprover/main.go | 50 ++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/tools/mockteeprover/main.go b/tools/mockteeprover/main.go index c4930cc..4080b98 100644 --- a/tools/mockteeprover/main.go +++ b/tools/mockteeprover/main.go @@ -27,6 +27,10 @@ type ProveRequest struct { EndBlkHash string `json:"endBlkHash"` StartBlkStateHash string `json:"startBlkStateHash"` EndBlkStateHash string `json:"endBlkStateHash"` + + // Optional EIP-712 domain hints. If present, override system defaults. + ChainID *uint64 `json:"chainId,omitempty"` + TeeProofVerifierAddr *string `json:"teeProofVerifierAddr,omitempty"` } type task struct { @@ -46,11 +50,12 @@ type response struct { } type MockTEEProver struct { - mu sync.Mutex - tasks map[string]*task - signerKey *ecdsa.PrivateKey - domainSep common.Hash - taskDelay time.Duration + mu sync.Mutex + tasks map[string]*task + signerKey *ecdsa.PrivateKey + defaultDomainCfg EIP712DomainConfig + defaultDomainSep common.Hash + taskDelay time.Duration // control flags failNext bool @@ -60,13 +65,29 @@ type MockTEEProver struct { submittedRequests []ProveRequest } -func NewMockTEEProver(signerKey *ecdsa.PrivateKey, domainSep common.Hash, taskDelay time.Duration) *MockTEEProver { +func NewMockTEEProver(signerKey *ecdsa.PrivateKey, domainCfg EIP712DomainConfig, taskDelay time.Duration) *MockTEEProver { return &MockTEEProver{ - tasks: make(map[string]*task), - signerKey: signerKey, - domainSep: domainSep, - taskDelay: taskDelay, + tasks: make(map[string]*task), + signerKey: signerKey, + defaultDomainCfg: domainCfg, + defaultDomainSep: computeDomainSeparator(domainCfg), + taskDelay: taskDelay, + } +} + +// resolveDomainSep returns a domain separator based on request overrides or defaults. +func (m *MockTEEProver) resolveDomainSep(req ProveRequest) common.Hash { + if req.ChainID == nil && req.TeeProofVerifierAddr == nil { + return m.defaultDomainSep + } + cfg := m.defaultDomainCfg + if req.ChainID != nil { + cfg.ChainID = new(big.Int).SetUint64(*req.ChainID) + } + if req.TeeProofVerifierAddr != nil { + cfg.VerifyingContract = common.HexToAddress(*req.TeeProofVerifierAddr) } + return computeDomainSeparator(cfg) } // POST /task/ @@ -138,7 +159,8 @@ func (m *MockTEEProver) handleGetTask(w http.ResponseWriter, r *http.Request) { // Transition Running → Finished if delay has elapsed if t.Status == "Running" && time.Now().After(t.FinishAt) { - proofBytes, err := generateProofBytes(t.Request, m.signerKey, m.domainSep) + domainSep := m.resolveDomainSep(t.Request) + proofBytes, err := generateProofBytes(t.Request, m.signerKey, domainSep) if err != nil { log.Printf("[GET /task/%s] ERROR generating proof: %v", taskID, err) t.Status = "Failed" @@ -289,8 +311,8 @@ func main() { ChainID: big.NewInt(chainID), VerifyingContract: verifyingContract, } - domainSep := computeDomainSeparator(domainCfg) - log.Printf("EIP-712 domain separator: %s (chainId=%d, verifyingContract=%s)", domainSep.Hex(), chainID, verifyingContract.Hex()) + log.Printf("EIP-712 default domain: chainId=%d, verifyingContract=%s, separator=%s", + chainID, verifyingContract.Hex(), computeDomainSeparator(domainCfg).Hex()) listenAddr := os.Getenv("LISTEN_ADDR") if listenAddr == "" { @@ -306,7 +328,7 @@ func main() { taskDelay = parsed } - prover := NewMockTEEProver(signerKey, domainSep, taskDelay) + prover := NewMockTEEProver(signerKey, domainCfg, taskDelay) log.Printf("Listening on %s (task_delay=%s)", listenAddr, taskDelay) if err := http.ListenAndServe(listenAddr, prover); err != nil { From 0c374da013f194acc3dcc1999679b18b1742cf9f Mon Sep 17 00:00:00 2001 From: haogeng xie <903932861@qq.com> Date: Mon, 30 Mar 2026 11:07:08 +0800 Subject: [PATCH 29/30] fix(op-succinct): fix TRANSACTOR key mismatch and add PROPOSER/CHALLENGER_ADDRESSES Co-Authored-By: Claude Sonnet 4.6 --- devnet/5-run-op-succinct.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devnet/5-run-op-succinct.sh b/devnet/5-run-op-succinct.sh index 9236e23..3a3f069 100755 --- a/devnet/5-run-op-succinct.sh +++ b/devnet/5-run-op-succinct.sh @@ -53,7 +53,9 @@ sed_inplace "s|^L2_NODE_RPC=.*|L2_NODE_RPC=$L2_NODE_RPC_URL_IN_DOCKER|" "$OP_SUC sed_inplace "s|^FACTORY_ADDRESS=.*|FACTORY_ADDRESS=$DISPUTE_GAME_FACTORY_ADDRESS|" "$OP_SUCCINCT_DIR"/.env.deploy sed_inplace "s|^OPTIMISM_PORTAL2_ADDRESS=.*|OPTIMISM_PORTAL2_ADDRESS=$OPTIMISM_PORTAL_PROXY_ADDRESS|" "$OP_SUCCINCT_DIR"/.env.deploy sed_inplace "s|^ANCHOR_STATE_REGISTRY=.*|ANCHOR_STATE_REGISTRY=$ANCHOR_STATE_REGISTRY|" "$OP_SUCCINCT_DIR"/.env.deploy -sed_inplace "s|^TRANSACTOR_ADDRESS=.*|TRANSACTOR_ADDRESS=$TRANSACTOR|" "$OP_SUCCINCT_DIR"/.env.deploy +sed_inplace "s|^TRANSACTOR=.*|TRANSACTOR=$TRANSACTOR|" "$OP_SUCCINCT_DIR"/.env.deploy +sed_inplace "s|^PROPOSER_ADDRESSES=.*|PROPOSER_ADDRESSES=$PROPOSER_ADDRESS|" "$OP_SUCCINCT_DIR"/.env.deploy +sed_inplace "s|^CHALLENGER_ADDRESSES=.*|CHALLENGER_ADDRESSES=$CHALLENGER_ADDRESS|" "$OP_SUCCINCT_DIR"/.env.deploy sed_inplace "s|^STARTING_L2_BLOCK_NUMBER=.*|STARTING_L2_BLOCK_NUMBER=$((FORK_BLOCK + 1))|" "$OP_SUCCINCT_DIR"/.env.deploy sed_inplace "s|^OP_SUCCINCT_MOCK=.*|OP_SUCCINCT_MOCK=$PROOF_MOCK_MODE|" "$OP_SUCCINCT_DIR"/.env.deploy From 27c4cf9af4dab66a1811fc4c248c35bb34962d47 Mon Sep 17 00:00:00 2001 From: JimmyShi22 <417711026@qq.com> Date: Mon, 30 Mar 2026 16:32:43 +0800 Subject: [PATCH 30/30] rm /v1 --- tools/mockteerpc/README.md | 8 ++++---- tools/mockteerpc/cmd/mockteerpc/main.go | 4 ++-- tools/mockteerpc/mock_tee_rollup_server.go | 6 +++--- tools/mockteerpc/mock_tee_rollup_server_test.go | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/mockteerpc/README.md b/tools/mockteerpc/README.md index 148ba22..de4690c 100644 --- a/tools/mockteerpc/README.md +++ b/tools/mockteerpc/README.md @@ -6,7 +6,7 @@ Standalone mock TeeRollup HTTP server for local development and testing. ## Mock TeeRollup Server -Simulates the `GET /v1/chain/confirmed_block_info` REST endpoint provided by a real TeeRollup service. +Simulates the `GET /chain/confirmed_block_info` REST endpoint provided by a real TeeRollup service. **Behavior:** - Starts at block height 1000 (configurable) @@ -60,7 +60,7 @@ mock TeeRollup server listening on :8090 initial height: 1000 error rate: 0.0% max delay: 1s -endpoint: GET /v1/chain/confirmed_block_info +endpoint: GET /chain/confirmed_block_info tick: height=1023 delta=23 tick: height=1058 delta=35 @@ -73,7 +73,7 @@ tick: height=1058 delta=35 ```bash # Query current confirmed block info -curl -s http://localhost:8090/v1/chain/confirmed_block_info | jq . +curl -s http://localhost:8090/chain/confirmed_block_info | jq . ``` Example response: @@ -92,7 +92,7 @@ Example response: ### Observe height growth continuously ```bash -watch -n 0.5 'curl -s http://localhost:8090/v1/chain/confirmed_block_info | jq .data' +watch -n 0.5 'curl -s http://localhost:8090/chain/confirmed_block_info | jq .data' ``` --- diff --git a/tools/mockteerpc/cmd/mockteerpc/main.go b/tools/mockteerpc/cmd/mockteerpc/main.go index d074483..9b458a0 100644 --- a/tools/mockteerpc/cmd/mockteerpc/main.go +++ b/tools/mockteerpc/cmd/mockteerpc/main.go @@ -148,13 +148,13 @@ func main() { go s.tick() mux := http.NewServeMux() - mux.HandleFunc("/v1/chain/confirmed_block_info", s.handleConfirmedBlockInfo) + mux.HandleFunc("/chain/confirmed_block_info", s.handleConfirmedBlockInfo) fmt.Printf("mock TeeRollup server listening on %s\n", *addr) fmt.Printf("initial height: %d\n", *initHeight) fmt.Printf("error rate: %.1f%%\n", *errorRate*100) fmt.Printf("max delay: %s\n", *maxDelay) - fmt.Println("endpoint: GET /v1/chain/confirmed_block_info") + fmt.Println("endpoint: GET /chain/confirmed_block_info") fmt.Println() if err := http.ListenAndServe(*addr, mux); err != nil { diff --git a/tools/mockteerpc/mock_tee_rollup_server.go b/tools/mockteerpc/mock_tee_rollup_server.go index 4fb2650..60dce99 100644 --- a/tools/mockteerpc/mock_tee_rollup_server.go +++ b/tools/mockteerpc/mock_tee_rollup_server.go @@ -15,7 +15,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -// TeeRollupResponse is the normal JSON shape returned by GET /v1/chain/confirmed_block_info. +// TeeRollupResponse is the normal JSON shape returned by GET /chain/confirmed_block_info. type TeeRollupResponse struct { Code int `json:"code"` Message string `json:"message"` @@ -77,7 +77,7 @@ func NewTeeRollupServer(t *testing.T, opts ...Option) *TeeRollupServer { } mux := http.NewServeMux() - mux.HandleFunc("/v1/chain/confirmed_block_info", m.handleConfirmedBlockInfo) + mux.HandleFunc("/chain/confirmed_block_info", m.handleConfirmedBlockInfo) m.server = httptest.NewServer(mux) @@ -134,7 +134,7 @@ func (m *TeeRollupServer) tick() { } } -// handleConfirmedBlockInfo serves GET /v1/chain/confirmed_block_info. +// handleConfirmedBlockInfo serves GET /chain/confirmed_block_info. func (m *TeeRollupServer) handleConfirmedBlockInfo(w http.ResponseWriter, r *http.Request) { start := time.Now() log.Printf("[mockteerpc] received %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) diff --git a/tools/mockteerpc/mock_tee_rollup_server_test.go b/tools/mockteerpc/mock_tee_rollup_server_test.go index 99f7a75..18e62e7 100644 --- a/tools/mockteerpc/mock_tee_rollup_server_test.go +++ b/tools/mockteerpc/mock_tee_rollup_server_test.go @@ -15,7 +15,7 @@ func TestTeeRollupServer_Basic(t *testing.T) { srv := mockteerpc.NewTeeRollupServer(t) // --- first request --- - resp, err := http.Get(srv.Addr() + "/v1/chain/confirmed_block_info") //nolint:noctx + resp, err := http.Get(srv.Addr() + "/chain/confirmed_block_info") //nolint:noctx require.NoError(t, err) defer resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode) @@ -34,7 +34,7 @@ func TestTeeRollupServer_Basic(t *testing.T) { // --- wait for at least one tick --- time.Sleep(1500 * time.Millisecond) - resp2, err := http.Get(srv.Addr() + "/v1/chain/confirmed_block_info") //nolint:noctx + resp2, err := http.Get(srv.Addr() + "/chain/confirmed_block_info") //nolint:noctx require.NoError(t, err) defer resp2.Body.Close()