Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,24 @@ jobs:
"python3 integration_test/scripts/runner.py integration_test/seidb/state_store_test.yaml",
],
},
{
name: "EVM Module (FlatKV Import)",
scripts: [
"docker exec sei-node-0 integration_test/contracts/deploy_flatkv_evm_fixture.sh",
"python3 integration_test/scripts/runner.py integration_test/seidb/flatkv_evm_test.yaml",
"./integration_test/contracts/import_flatkv_evm_cluster.sh",
"python3 integration_test/scripts/runner.py integration_test/seidb/flatkv_evm_test.yaml",
"docker exec sei-node-0 integration_test/contracts/verify_flatkv_evm_store.sh",
],
},
{
name: "EVM Module",
env: "GIGA_STORAGE=true",
scripts: [
"./integration_test/evm_module/scripts/evm_tests.sh",
"docker exec sei-node-0 integration_test/contracts/deploy_flatkv_evm_fixture.sh",
"python3 integration_test/scripts/runner.py integration_test/seidb/flatkv_evm_test.yaml",
"docker exec sei-node-0 integration_test/contracts/verify_flatkv_evm_store.sh",
]
},
{
Expand Down
206 changes: 206 additions & 0 deletions integration_test/contracts/deploy_flatkv_evm_fixture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
#!/bin/bash

set -euo pipefail

export PATH="$PATH:/root/.foundry/bin:/root/go/bin"

RPC_URL=${EVM_RPC_URL:-http://localhost:8545}
FROM=${FLATKV_EVM_FIXTURE_FROM:-admin}
PASSWORD=${FLATKV_EVM_FIXTURE_PASSWORD:-12345678}
CHAIN_ID=${FLATKV_EVM_FIXTURE_CHAIN_ID:-sei}
RECIPIENT_ADDR=${FLATKV_EVM_FIXTURE_RECIPIENT:-0x70997970C51812dc3A010C7d01b50e0d17dc79C8}
MISSING_ADDR=${FLATKV_EVM_FIXTURE_MISSING:-0xc1cadaffffffffffffffffffffffffffffffffff}
TRANSFER_VALUE_WEI=${FLATKV_EVM_FIXTURE_TRANSFER_VALUE_WEI:-1}
KEYRING_ARGS=()
if [ -n "${FLATKV_EVM_FIXTURE_KEYRING_BACKEND:-}" ]; then
KEYRING_ARGS+=(--keyring-backend "$FLATKV_EVM_FIXTURE_KEYRING_BACKEND")
fi

# Constructor:
# sstore(0, 42)
# return runtime bytecode that returns 42 for any call.
STORAGE_CONTRACT_INIT_CODE=0x602a600055600a600f600039600a6000f3602a60005260206000f3
STORAGE_SLOT_ZERO=0x0000000000000000000000000000000000000000000000000000000000000000

seihome=$(git rev-parse --show-toplevel)
out_dir="$seihome/integration_test/contracts"

write_fixture() {
local name=$1
local value=$2
printf "%s\n" "$value" > "$out_dir/$name"
}

run_seid() {
printf "%s\n" "$PASSWORD" | seid "$@"
}

wait_for_evm_rpc() {
local timeout=120
local elapsed=0
until cast block-number --rpc-url "$RPC_URL" >/dev/null 2>&1; do
if [ "$elapsed" -ge "$timeout" ]; then
echo "EVM RPC did not become ready within ${timeout}s" >&2
exit 1
fi
sleep 2
elapsed=$((elapsed + 2))
done
}

block_number() {
cast block-number --rpc-url "$RPC_URL"
}

query_balance() {
cast balance "$1" --block "$2" --rpc-url "$RPC_URL"
}

query_balance_hex() {
cast to-hex "$(query_balance "$1" "$2")"
}

query_storage() {
cast storage "$1" "$2" --block "$3" --rpc-url "$RPC_URL"
}

query_code() {
cast code "$1" --block "$2" --rpc-url "$RPC_URL"
}

extract_tx_hash() {
grep -oE '0x[a-fA-F0-9]{64}' | head -1
}

wait_for_receipt() {
local tx_hash=$1
local timeout=${2:-60}
local elapsed=0
local response

until [ "$elapsed" -ge "$timeout" ]; do
response=$(curl -s -X POST -H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getTransactionReceipt\",\"params\":[\"$tx_hash\"]}" \
"$RPC_URL" || true)
if printf "%s\n" "$response" | jq -e '.result != null' >/dev/null 2>&1; then
printf "%s\n" "$response"
return 0
fi
sleep 1
elapsed=$((elapsed + 1))
done

echo "Timed out waiting for EVM receipt $tx_hash" >&2
return 1
}

require_success_receipt() {
local name=$1
local receipt=$2
local status
status=$(printf "%s\n" "$receipt" | jq -r '.result.status // empty')
if [ "$status" != "0x1" ] && [ "$status" != "1" ]; then
echo "FlatKV EVM $name failed:" >&2
printf "%s\n" "$receipt" >&2
exit 1
fi
}

echo "Generating FlatKV EVM historical fixture via $RPC_URL..."
wait_for_evm_rpc

initial_height=$(block_number)
write_fixture "flatkv_evm_initial_block_height.txt" "$initial_height"
write_fixture "flatkv_evm_recipient_addr.txt" "$RECIPIENT_ADDR"
write_fixture "flatkv_evm_missing_addr.txt" "$MISSING_ADDR"
write_fixture "flatkv_evm_storage_slot.txt" "$STORAGE_SLOT_ZERO"

run_seid tx evm associate-address \
--from "$FROM" \
"${KEYRING_ARGS[@]}" \
--chain-id "$CHAIN_ID" \
-b block \
-y >/tmp/flatkv_evm_associate.out 2>&1 || true

echo "Sending native EVM transfer to create/update recipient account..."
if ! transfer_out=$(run_seid tx evm send "$RECIPIENT_ADDR" "$TRANSFER_VALUE_WEI" \
--from "$FROM" \
"${KEYRING_ARGS[@]}" \
--chain-id "$CHAIN_ID" \
--evm-rpc "$RPC_URL" \
-b sync \
-y 2>&1); then
echo "FlatKV EVM transfer command failed:" >&2
printf "%s\n" "$transfer_out" >&2
exit 1
fi
printf "%s\n" "$transfer_out" >/tmp/flatkv_evm_transfer.out
transfer_tx=$(printf "%s\n" "$transfer_out" | extract_tx_hash || true)
if [ -z "$transfer_tx" ]; then
echo "Failed to extract FlatKV EVM transfer tx hash:" >&2
printf "%s\n" "$transfer_out" >&2
exit 1
fi
transfer_receipt=$(wait_for_receipt "$transfer_tx")
require_success_receipt "transfer" "$transfer_receipt"
printf "%s\n" "$transfer_receipt" >/tmp/flatkv_evm_transfer_receipt.json

balance_height=$(block_number)
balance_expected=$(query_balance_hex "$RECIPIENT_ADDR" "$balance_height")
write_fixture "flatkv_evm_balance_block_height.txt" "$balance_height"
write_fixture "flatkv_evm_balance_expected.txt" "$balance_expected"

echo "Deploying storage/code fixture contract..."
contract_hex_file=/tmp/flatkv_evm_storage_contract.hex
printf "%s" "${STORAGE_CONTRACT_INIT_CODE#0x}" > "$contract_hex_file"
if ! deploy_out=$(run_seid tx evm deploy "$contract_hex_file" \
--from "$FROM" \
"${KEYRING_ARGS[@]}" \
--chain-id "$CHAIN_ID" \
--evm-rpc "$RPC_URL" \
-b sync \
-y 2>&1); then
echo "FlatKV EVM deploy command failed:" >&2
printf "%s\n" "$deploy_out" >&2
exit 1
fi
deploy_tx=$(printf "%s\n" "$deploy_out" | extract_tx_hash || true)
if [ -z "$deploy_tx" ]; then
echo "Failed to extract FlatKV EVM deploy tx hash:" >&2
printf "%s\n" "$deploy_out" >&2
exit 1
fi
deploy_receipt=$(wait_for_receipt "$deploy_tx")
require_success_receipt "contract deployment" "$deploy_receipt"
printf "%s\n" "$deploy_receipt" > "$out_dir/flatkv_evm_deploy_receipt.json"

contract_addr=$(printf "%s\n" "$deploy_receipt" | jq -r '.result.contractAddress // empty')
if [ -z "$contract_addr" ] || [ "$contract_addr" = "null" ]; then
contract_addr=$(printf "%s\n" "$deploy_out" | sed -n 's/^Deployed to: //p' | tail -1)
fi
if [ -z "$contract_addr" ] || [ "$contract_addr" = "null" ]; then
echo "Failed to extract contract address from deploy receipt:" >&2
printf "%s\n" "$deploy_receipt" >&2
exit 1
fi

contract_height=$(block_number)
storage_expected=$(query_storage "$contract_addr" "$STORAGE_SLOT_ZERO" "$contract_height")
code_expected=$(query_code "$contract_addr" "$contract_height")

write_fixture "flatkv_evm_contract_addr.txt" "$contract_addr"
write_fixture "flatkv_evm_contract_block_height.txt" "$contract_height"
write_fixture "flatkv_evm_storage_expected.txt" "$storage_expected"
write_fixture "flatkv_evm_code_expected.txt" "$code_expected"

missing_balance_expected=$(query_balance_hex "$MISSING_ADDR" "$contract_height")
missing_storage_expected=$(query_storage "$MISSING_ADDR" "$STORAGE_SLOT_ZERO" "$contract_height")
write_fixture "flatkv_evm_missing_balance_expected.txt" "$missing_balance_expected"
write_fixture "flatkv_evm_missing_storage_expected.txt" "$missing_storage_expected"

latest_height=$(block_number)
write_fixture "flatkv_evm_latest_fixture_block_height.txt" "$latest_height"

echo "FlatKV EVM fixture generated:"
echo " recipient=$RECIPIENT_ADDR balance_height=$balance_height balance=$balance_expected"
echo " contract=$contract_addr contract_height=$contract_height storage=$storage_expected"
145 changes: 145 additions & 0 deletions integration_test/contracts/import_flatkv_evm_cluster.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/bin/bash

set -euo pipefail

PROJECT_ROOT=$(git rev-parse --show-toplevel)
NODE_COUNT=${FLATKV_EVM_IMPORT_NODE_COUNT:-4}

dump_node_log() {
local node=$1
echo "==================== ${node} seid log (last 200 lines) ====================" >&2
local node_id=${node#sei-node-}
docker exec "$node" tail -200 "/sei-protocol/sei-chain/build/generated/logs/seid-${node_id}.log" >&2 || true
echo "==================== ${node} end log ====================" >&2
}

wait_for_height() {
local min_height=$1
local timeout=${2:-180}
local elapsed=0
local height=0

until [ "$elapsed" -ge "$timeout" ]; do
height=$(docker exec sei-node-0 build/seid status 2>/dev/null | jq -r ".SyncInfo.latest_block_height // 0" || echo 0)
if [ "$height" -gt "$min_height" ]; then
echo "sei-node-0 reached height $height"
return 0
fi
echo "Still waiting for sei-node-0 to advance past height $min_height (height=$height elapsed=${elapsed}s/${timeout}s)"
sleep 5
elapsed=$((elapsed + 5))
done

echo "Timed out waiting for sei-node-0 to advance past height $min_height (last height: $height)" >&2
for i in $(seq 0 $((NODE_COUNT - 1))); do
dump_node_log "sei-node-$i"
done
return 1
}

# wait_for_evm_rpc polls each node's EVM HTTP endpoint until it responds, so
# the post-restart flatkv_evm_test.yaml run can't race the seid restart and
# hit connection refused on http://localhost:8545. Tendermint typically
# advances a height or two before the in-process EVM RPC server finishes
# binding 8545, so wait_for_height alone is not a sufficient readiness gate
# for the next test phase.
wait_for_evm_rpc() {
local timeout=${1:-120}
for i in $(seq 0 $((NODE_COUNT - 1))); do
local node="sei-node-$i"
local elapsed=0
until docker exec "$node" bash -lc 'curl -sf -o /dev/null -X POST -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_blockNumber\",\"params\":[]}" http://localhost:8545'; do
if [ "$elapsed" -ge "$timeout" ]; then
echo "EVM RPC on $node did not become ready within ${timeout}s after restart" >&2
dump_node_log "$node"
return 1
fi
echo "Waiting for EVM RPC on $node (elapsed=${elapsed}s/${timeout}s)"
sleep 3
elapsed=$((elapsed + 3))
done
echo "EVM RPC on $node is responding"
done
}

echo "Building seidb import tool..."
# Go lives at /usr/local/go/bin/go in the container (see docker/localnode/Dockerfile)
# but is not on the default PATH for non-interactive shells, so call it absolutely.
GO_BIN=${GO_BIN:-/usr/local/go/bin/go}
docker exec -e GOPROXY="${GOPROXY:-https://proxy.golang.org,direct}" sei-node-0 bash -c "cd /sei-protocol/sei-chain && $GO_BIN build -o build/seidb ./sei-db/tools/cmd/seidb"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seidb binary built only on node-0 but executed on all nodes

High Severity

The seidb tool is compiled via docker exec only on sei-node-0, but the import loop at lines 94–96 runs build/seidb import-flatkv-from-memiavl on every node (sei-node-0 through sei-node-3). If the containers don't share the /sei-protocol/sei-chain/build/ directory (i.e., each has its own filesystem layer), the binary won't exist on nodes 1–3 and the import will fail with a "command not found" or "no such file" error.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 268cd4b. Configure here.


start_height=$(docker exec sei-node-0 build/seid status | jq -r ".SyncInfo.latest_block_height")
echo "Stopping seid processes at height $start_height..."
for i in $(seq 0 $((NODE_COUNT - 1))); do
docker exec "sei-node-$i" pkill -f "seid start" >/dev/null 2>&1 || true
done

echo "Waiting for seid processes to stop..."
for i in $(seq 0 $((NODE_COUNT - 1))); do
stopped=false
for _ in $(seq 1 30); do
if ! docker exec "sei-node-$i" pgrep -f "seid start" >/dev/null 2>&1; then
stopped=true
break
fi
sleep 1
done
if [ "$stopped" != "true" ]; then
echo "sei-node-$i did not stop within 30s" >&2
exit 1
fi
done

echo "Importing evm module from memiavl into FlatKV on all validators..."
for i in $(seq 0 $((NODE_COUNT - 1))); do
docker exec "sei-node-$i" bash -lc "cd /sei-protocol/sei-chain && build/seidb import-flatkv-from-memiavl --modules=evm --data-dir /root/.sei/data"
done

echo "Applying GIGA_STORAGE config and restarting seid processes..."
for i in $(seq 0 $((NODE_COUNT - 1))); do
docker exec -e GIGA_STORAGE=true "sei-node-$i" /usr/bin/config_override.sh
# The import tool moves only SC-layer EVM data into FlatKV. SS history
# for EVM stays in the existing combined cosmos pebbledb, so we must keep
# evm-ss-split=false to avoid the rootmulti startup panic:
# "EVM SS directory ... does not exist but Cosmos SS already has history".
# Switching the SS layer to split mode mid-life requires a separate state-sync
# workflow which is out of scope for this SC import test.
docker exec "sei-node-$i" sed -i 's/evm-ss-split = true/evm-ss-split = false/' /root/.sei/config/app.toml
# Lattice hash must also stay off across the import boundary. Pre-import
# the chain ran without FlatKV, so tendermint persisted AppHash = memiavl-only
# for all blocks up to the import height. Turning sc-enable-lattice-hash
# on now would fold the FlatKV LtHash into the AppHash and the replay check
# at startup would fail with "state.AppHash does not match AppHash after replay".
# dual_write does not require lattice hash (see sei-db/config/toml_test.go);
# only split_write does. A real production rollout would coordinate this
# transition via a chain upgrade at an agreed height.
docker exec "sei-node-$i" sed -i 's/sc-enable-lattice-hash = true/sc-enable-lattice-hash = false/' /root/.sei/config/app.toml
done
# `docker exec -d` is required: start_sei.sh backgrounds seid then exits, and a
# non-detached docker exec session would close stdout/stderr, killing seid.
# See integration_test/autobahn/autobahn_test.go::restartNode for the precedent.
for i in $(seq 0 $((NODE_COUNT - 1))); do
docker exec -d -e "ID=$i" "sei-node-$i" /usr/bin/start_sei.sh
done

# Confirm each seid actually came up before waiting on block production, so a
# crash on startup is reported promptly instead of after the 4 minute timeout.
sleep 5
for i in $(seq 0 $((NODE_COUNT - 1))); do
if ! docker exec "sei-node-$i" pgrep -f "seid start" >/dev/null 2>&1; then
echo "ERROR: sei-node-$i did not stay running after restart" >&2
dump_node_log "sei-node-$i"
exit 1
fi
done

wait_for_height "$start_height" 240

# Tendermint advancing past start_height does NOT imply the in-process EVM
# RPC HTTP server has finished binding 8545. The downstream
# integration_test/seidb/flatkv_evm_test.yaml docker-execs `cast` against
# http://localhost:8545; gate on that endpoint explicitly so it can't race
# the seid restart.
wait_for_evm_rpc 120

echo "FlatKV EVM import completed for $NODE_COUNT validators in $PROJECT_ROOT"
Loading
Loading