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
88 changes: 88 additions & 0 deletions .github/workflows/firewood-state-access-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Firewood State Access Test

on:
workflow_dispatch:
inputs:
test:
description: 'Test name to run (e.g., firewood-archive-250k). Leave empty to use custom inputs below.'
default: ''
# Custom inputs (used when test is not provided)
start-block:
description: 'The start block for the test.'
default: ''
end-block:
description: 'The end block for the test.'
default: ''
current-state-dir-src:
description: 'The current state directory. Supports S3 directory/zip and local directories.'
default: ''
runner:
description: 'Runner to execute the state access test. Input to the runs-on field of the job.'
required: true
timeout-minutes:
description: 'Timeout in minutes for the job.'
default: '60'
schedule:
- cron: '0 9 * * *' # Runs every day at 09:00 UTC (04:00 EST)
# XXX: REMOVE BEFORE MERGING
pull_request:
Comment on lines +27 to +28
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will remove before merging.


jobs:
define-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.define-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- name: Define Matrix
id: define-matrix
shell: bash -x {0}
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
{
echo "matrix<<EOF"
printf '{ "include": [{ "test": "%s", "start-block": "%s", "end-block": "%s", "current-state-dir-src": "%s", "runner": "%s", "timeout-minutes": %s }] }\n' \
"${{ github.event.inputs.test }}" \
"${{ github.event.inputs.start-block }}" \
"${{ github.event.inputs.end-block }}" \
"${{ github.event.inputs.current-state-dir-src }}" \
"${{ github.event.inputs.runner }}" \
"${{ github.event.inputs.timeout-minutes }}"
echo EOF
} >> "$GITHUB_OUTPUT"
else
{
echo "matrix<<EOF"
echo '{ "include": [{ "test": "firewood-archive-250k" }] }'
echo EOF
} >> "$GITHUB_OUTPUT"
fi

firewood-state-access-test:
needs: define-matrix
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.define-matrix.outputs.matrix) }}
timeout-minutes: ${{ matrix.timeout-minutes || 60 }}
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
permissions:
id-token: write
contents: read
steps:
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f #v31
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_S3_READ_ONLY_ROLE }}
aws-region: 'us-east-2'
role-duration-seconds: '43200'
- uses: actions/checkout@v4
- name: Run state access test with Firewood archive
shell: nix develop --impure --command bash -x {0}
run: ./scripts/run_task.sh test-firewood-state-access -- "${{ matrix.test || '' }}"
env:
START_BLOCK: ${{ matrix.start-block }}
END_BLOCK: ${{ matrix.end-block }}
CURRENT_STATE_DIR_SRC: ${{ matrix.current-state-dir-src }}
4 changes: 4 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ tasks:
cmds:
- cmd: bash -x ./scripts/tests.e2e.kube.sh --ginkgo.focus-file=xsvm.go {{.CLI_ARGS}}

test-firewood-state-access:
desc: Runs Firewood state access test
cmd: ./scripts/tests.firewood_state_access.sh {{.CLI_ARGS}}

# To use a different fuzz time, run `task test-fuzz FUZZTIME=[value in seconds]`.
# A value of `-1` will run until it encounters a failing output.

Expand Down
10 changes: 5 additions & 5 deletions scripts/benchmark_cchain_range.sh
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ else
${METRICS_SERVER_PORT:+--metrics-server-port="${METRICS_SERVER_PORT}"} \
${METRICS_COLLECTOR_ENABLED:+--metrics-collector-enabled="${METRICS_COLLECTOR_ENABLED}"}

if [[ -n "${PUSH_POST_STATE:-}" ]]; then
echo "=== Pushing post-state to S3 ==="
"${SCRIPT_DIR}/copy_dir.sh" "${CURRENT_STATE_DIR}/" "${PUSH_POST_STATE}"
fi
fi
if [[ -n "${PUSH_POST_STATE:-}" ]]; then
echo "=== Pushing post-state to S3 ==="
"${SCRIPT_DIR}/copy_dir.sh" "${CURRENT_STATE_DIR}/" "${PUSH_POST_STATE}"
fi
fi
14 changes: 9 additions & 5 deletions scripts/import_cchain_data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ set -euo pipefail
# Import C-Chain blocks and state from S3
#
# Required env vars:
# BLOCK_DIR_SRC - S3 object key for blocks (e.g., cchain-mainnet-blocks-200-ldb)
# CURRENT_STATE_DIR_SRC - S3 object key for state (e.g., cchain-current-state-hashdb-full-100)
# EXECUTION_DATA_DIR - Local directory to store imported data
#
# Optional env vars:
# BLOCK_DIR_SRC - S3 object key for blocks (e.g., cchain-mainnet-blocks-200-ldb)
#
# Result:
# Creates $EXECUTION_DATA_DIR/blocks and $EXECUTION_DATA_DIR/current-state
# Creates $EXECUTION_DATA_DIR/current-state (always)
# Creates $EXECUTION_DATA_DIR/blocks (if BLOCK_DIR_SRC is set)

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
S3_BOOTSTRAP_BUCKET="${S3_BOOTSTRAP_BUCKET:-s3://avalanchego-bootstrap-testing}"

: "${BLOCK_DIR_SRC:?BLOCK_DIR_SRC must be set}"
: "${CURRENT_STATE_DIR_SRC:?CURRENT_STATE_DIR_SRC must be set}"
: "${EXECUTION_DATA_DIR:?EXECUTION_DATA_DIR must be set}"

echo "=== Importing blocks from S3 ==="
"${SCRIPT_DIR}/copy_dir.sh" "${S3_BOOTSTRAP_BUCKET}/${BLOCK_DIR_SRC}/**" "${EXECUTION_DATA_DIR}/blocks"
if [[ -n "${BLOCK_DIR_SRC:-}" ]]; then
echo "=== Importing blocks from S3 ==="
"${SCRIPT_DIR}/copy_dir.sh" "${S3_BOOTSTRAP_BUCKET}/${BLOCK_DIR_SRC}/**" "${EXECUTION_DATA_DIR}/blocks"
fi

echo "=== Importing state from S3 ==="
"${SCRIPT_DIR}/copy_dir.sh" "${S3_BOOTSTRAP_BUCKET}/${CURRENT_STATE_DIR_SRC}/**" "${EXECUTION_DATA_DIR}/current-state"
104 changes: 104 additions & 0 deletions scripts/tests.firewood_state_access.sh
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've went with creating a new script rather than extending ./scripts/benchmark_cchain_range.sh since the state access tests simply reads over the current state, while both the reexecution and chaos tests start with the current state and write to it. This causes common terms such as START_BLOCK and END_BLOCK to take on different meanings depending on the context they're used in (state access test vs reexecution/chaos tests) and so I went with creating a new script to make things clearer.

Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env bash

set -euo pipefail

# Firewood state access script
#
# Usage:
# ./tests.firewood_state_access.sh
#
# To see available tests, use `help` as the test name or invoke without a test
# name and without required env vars.
#
# Note: state access tests can only be run with Firewood archival states.
#
# Environment variables:
# Data sources (provide S3 sources OR local paths):
# CURRENT_STATE_DIR_SRC: S3 object key for state (triggers S3 import).
# CURRENT_STATE_DIR: Path to local current state directory.
#
# Required:
# START_BLOCK: The starting block height to query (inclusive).
# END_BLOCK: The ending block height to query (inclusive).

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# CI-aware error function
error() {
if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then
echo "::error::$1"
else
echo "Error: $1" >&2
fi
exit 1
}

show_usage() {
cat <<EOF
Usage: $0 [test-name]

Available tests:
help - Show this help message

firewood-archive-250k - Query the first 250k blocks with Firewood archive.
EOF
}

# Set defaults based on test name (if provided)
TEST_NAME="${1:-}"
if [[ -n "$TEST_NAME" ]]; then
shift
case "$TEST_NAME" in
help)
show_usage
exit 0
;;
firewood-archive-250k)
CURRENT_STATE_DIR_SRC="${CURRENT_STATE_DIR_SRC:-cchain-current-state-firewood-archive-250k}"
START_BLOCK="${START_BLOCK:-0}"
END_BLOCK="${END_BLOCK:-250_000}"
;;
*)
error "Unknown test '$TEST_NAME'"
;;
esac
fi

# Determine data source: S3 import or local path
if [[ -n "${CURRENT_STATE_DIR_SRC:-}" ]]; then
# S3 mode - import data
TIMESTAMP=$(date '+%Y%m%d-%H%M%S')
EXECUTION_DATA_DIR="${EXECUTION_DATA_DIR:-/tmp/reexec-${TEST_NAME:-custom}-${TIMESTAMP}}"

CURRENT_STATE_DIR_SRC="${CURRENT_STATE_DIR_SRC}" \
EXECUTION_DATA_DIR="${EXECUTION_DATA_DIR}" \
"${SCRIPT_DIR}/import_cchain_data.sh"

CURRENT_STATE_DIR="${EXECUTION_DATA_DIR}/current-state"
elif [[ -z "${CURRENT_STATE_DIR:-}" ]]; then
show_usage
echo ""
echo "Env vars status:"
echo " S3 source:"
[[ -n "${CURRENT_STATE_DIR_SRC:-}" ]] && echo " CURRENT_STATE_DIR_SRC: ${CURRENT_STATE_DIR_SRC}" || echo " CURRENT_STATE_DIR_SRC: (not set)"
echo " Local path:"
[[ -n "${CURRENT_STATE_DIR:-}" ]] && echo " CURRENT_STATE_DIR: ${CURRENT_STATE_DIR}" || echo " CURRENT_STATE_DIR: (not set)"
echo " Block range:"
[[ -n "${START_BLOCK:-}" ]] && echo " START_BLOCK: ${START_BLOCK}" || echo " START_BLOCK: (not set)"
[[ -n "${END_BLOCK:-}" ]] && echo " END_BLOCK: ${END_BLOCK}" || echo " END_BLOCK: (not set)"
exit 1
fi

# Validate block range
if [[ -z "${START_BLOCK:-}" || -z "${END_BLOCK:-}" ]]; then
error "START_BLOCK and END_BLOCK are required"
fi

echo "=== State Access Test: ${TEST_NAME:-custom} ==="
echo "Querying between: ${START_BLOCK} and ${END_BLOCK}"

echo "=== Running Test ==="
go run ./tests/reexecute/stateaccess \
--current-state-dir="${CURRENT_STATE_DIR}" \
--start-block="${START_BLOCK}" \
--end-block="${END_BLOCK}"
92 changes: 92 additions & 0 deletions tests/reexecute/stateaccess/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (C) 2019, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package main

import (
"context"
"flag"
"math/big"
"net/http/httptest"
"path/filepath"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/ethclient"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/api/metrics"
"github.com/ava-labs/avalanchego/database/leveldb"
"github.com/ava-labs/avalanchego/graft/coreth/plugin/evm"
"github.com/ava-labs/avalanchego/tests"
"github.com/ava-labs/avalanchego/tests/reexecute"
"github.com/ava-labs/avalanchego/utils/logging"
)

var (
currentStateDirArg string
startBlockArg uint64
endBlockArg uint64
)

func init() {
evm.RegisterAllLibEVMExtras()

flag.StringVar(&currentStateDirArg, "current-state-dir", currentStateDirArg, "Current state directory including VM DB and Chain Data Directory.")
flag.Uint64Var(&startBlockArg, "start-block", 101, "Start block to begin execution (inclusive).")
flag.Uint64Var(&endBlockArg, "end-block", 200, "End block to end execution (inclusive).")
flag.Parse()
}

// main verifies that historical state is accessible for archival node by
// iterating over a range of blocks and querying the nonce of the zero address
// at each block height. This confirms the node can serve state queries for
// arbitrary historical blocks.
func main() {
tc := tests.NewTestContext(tests.NewDefaultLogger("state-access"))
defer tc.RecoverAndExit()

r := require.New(tc)
ctx := context.Background()

var (
vmDBDir = filepath.Join(currentStateDirArg, "db")
chainDataDir = filepath.Join(currentStateDirArg, "chain-data-dir")
)

db, err := leveldb.New(vmDBDir, nil, logging.NoLog{}, prometheus.NewRegistry())
r.NoError(err)

firewoodArchiveConfig := `{
"state-scheme": "firewood",
"snapshot-cache": 0,
"pruning-enabled": false,
"state-sync-enabled": false
}`

vm, err := reexecute.NewMainnetCChainVM(
ctx,
db,
chainDataDir,
[]byte(firewoodArchiveConfig),
metrics.NewPrefixGatherer(),
prometheus.NewRegistry(),
)
r.NoError(err)

handlers, err := vm.CreateHandlers(ctx)
r.NoError(err)

ethRPCEndpoint := "/rpc"
server := httptest.NewServer(handlers[ethRPCEndpoint])
tc.DeferCleanup(server.Close)

client, err := ethclient.Dial(server.URL)
r.NoError(err)

for i := startBlockArg; i <= endBlockArg; i++ {
nonce, err := client.NonceAt(ctx, common.Address{}, big.NewInt(int64(i)))
r.NoErrorf(err, "failed to get nonce at block %d", i)
r.Zero(nonce)
}
}