Skip to content

Add test to re-execute specified range of mainnet C-Chain blocks #4019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
17 changes: 17 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,20 @@ tasks:
cmds:
- task: build
- cmd: bash -x ./scripts/tests.upgrade.sh {{.CLI_ARGS}}

import-cchain-reexecute-range:
desc: Imports the C-Chain block and state data to re-execute. Defaults to execute the block range (100, 200].
vars:
EXECUTION_DATA_DIR_DST:
sh: echo "${EXECUTION_DATA_DIR:-$(mktemp -d)}" # TODO: fix tempdir param
SOURCE_BLOCK_DIR: '{{.SOURCE_BLOCK_DIR | default "s3://avalanchego-bootstrap-testing/cchain-mainnet-blocks-200.zip"}}'
CURRENT_STATE_DIR: '{{.CURRENT_STATE_DIR | default "s3://avalanchego-bootstrap-testing/cchain-current-state-100-firewood.zip"}}'
cmds:
- cmd: bash -x ./scripts/bench.import_cchain_data.sh {{.EXECUTION_DATA_DIR_DST}} {{.SOURCE_BLOCK_DIR}} {{.CURRENT_STATE_DIR}}

reexecute-cchain-range:
desc: Benchmark re-execute a range of blocks on the C-Chain. Defaults to execute the block range (100, 200].
vars:
EXECUTION_DATA_DIR: '{{.EXECUTION_DATA_DIR | default "execution-data"}}'
cmds:
- cmd: go test -run=TestReexecuteRange github.com/ava-labs/avalanchego/tests/bench/c --source-block-dir={{.EXECUTION_DATA_DIR}}/blocks --target-dir={{.EXECUTION_DATA_DIR}}/current-state
33 changes: 33 additions & 0 deletions scripts/bench.import_cchain_data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash

set -euo pipefail

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

if [ $# -ne 3 ]; then
echo "Error: incorrect number of arguments provided $#"
echo "Usage: $0 <current-state-dir-dst> <block-source-src> <current-state-dir-src>"
echo "Example: $0 /path/to/current-state-dst /path/to/block-source-src /path/to/current-state-src"
echo "Expected resulting file structure:"
echo "<current-state-dir-dst>/"
echo "├── blocks/ # Copied from <block-source-src>"
echo "└── current-state/ # Copied from <current-state-dir-src>"
exit 1
fi

function import_cchain_data() {
local current_state_dir_arg=$1
local source_block_dir_arg=$2
local current_state_dir_src_arg=$3

local source_block_dir_dst="${current_state_dir_arg}/blocks"
local current_state_dir_dst="${current_state_dir_arg}/current-state"

echo "Copying block source db from $source_block_dir_arg to $source_block_dir_dst"
"$SCRIPT_DIR/copy_dir.sh" "$source_block_dir_arg" "$source_block_dir_dst"

echo "Copying current state directory from $current_state_dir_src_arg to $current_state_dir_dst"
"$SCRIPT_DIR/copy_dir.sh" "$current_state_dir_src_arg" "$current_state_dir_dst"
}

import_cchain_data $@
2 changes: 1 addition & 1 deletion scripts/build_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ AVALANCHE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd )
# Load the constants
source "$AVALANCHE_PATH"/scripts/constants.sh

EXCLUDED_TARGETS="| grep -v /mocks | grep -v proto | grep -v tests/e2e | grep -v tests/load/c | grep -v tests/upgrade | grep -v tests/fixture/bootstrapmonitor/e2e"
EXCLUDED_TARGETS="| grep -v /mocks | grep -v proto | grep -v tests/e2e | grep -v tests/load/c | grep -v tests/upgrade | grep -v tests/fixture/bootstrapmonitor/e2e | grep -v tests/bench"

if [[ "$(go env GOOS)" == "windows" ]]; then
# Test discovery for the antithesis test setups is broken due to
Expand Down
53 changes: 53 additions & 0 deletions scripts/copy_dir.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash

set -euo pipefail

# Usage: ./scripts/copy_dir.sh source_directory destination_directory
# Sources can be S3 URLs (s3://bucket/path) or a local file path
# Assumes s5cmd has been installed and is available in the PATH

if [ $# -ne 2 ]; then
echo "Usage: $0 <source_directory> <destination_directory>"
echo "S3 Example: $0 's3://bucket1/path1' /dest/dir"
echo "Local Example: $0 '/local/path1' /dest/dir"
exit 1
fi

SRC="$1"
DST="$2"

# Ensure destination directory exists
mkdir -p "$DST"

# Function to copy from a single source to destination
copy_source() {
local source="$1"
local dest="$2"

# Check if source starts with s3://
if [[ "$source" == s3://* ]]; then
echo "Copying from S3: $source -> $dest"
# Use s5cmd to copy from S3
time s5cmd cp "$source" "$dest"

# If we copied a zip, extract it in place
if [[ "$source" == *.zip ]]; then
echo "Extracting zip file in place"
time unzip "$dest"/*.zip -d "$dest"
rm "$dest"/*.zip
fi
else
echo "Copying from local filesystem: $source -> $dest"
# Use cp for local filesystem with recursive support
if [ -d "$source" ]; then
time cp -r "$source"/* "$dest/"
elif [ -f "$source" ]; then
time cp "$source" "$dest/"
else
echo "Warning: Source not found: $source"
return 1
fi
fi
}

copy_source $SRC $DST
242 changes: 242 additions & 0 deletions tests/bench/c/vm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package vm

import (
"context"
"flag"
"fmt"
"path/filepath"
"testing"

Check failure on line 12 in tests/bench/c/vm_test.go

View workflow job for this annotation

GitHub Actions / Lint

File is not properly formatted (gci)
"github.com/ava-labs/avalanchego/api/metrics"
"github.com/ava-labs/avalanchego/chains/atomic"
"github.com/ava-labs/avalanchego/database/leveldb"
"github.com/ava-labs/avalanchego/genesis"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow"
"github.com/ava-labs/avalanchego/snow/engine/common"
"github.com/ava-labs/avalanchego/snow/engine/enginetest"
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
"github.com/ava-labs/avalanchego/snow/validators/validatorstest"
"github.com/ava-labs/avalanchego/tests"
"github.com/ava-labs/avalanchego/upgrade"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/coreth/plugin/evm"
"github.com/ava-labs/libevm/core/rawdb"
"github.com/ava-labs/libevm/rlp"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)

const (
sourceDBCacheSize = 128
sourceDBHandles = 1024
)

var (
mainnetXChainID = ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM")
mainnetCChainID = ids.FromStringOrPanic("2q9e4r6Mu3U68nU1fYjgbR6JvwrRx36CohpAX5UQxse55x1Q5")
mainnetAvaxAssetID = ids.FromStringOrPanic("FvwEAhmxKfeiG8SnEvq42hc6whRyY3EFYAvebMqDNDGCgxN5Z")
)

var (
sourceBlockDir string
targetDir string
startBlock uint64
endBlock uint64
)

func TestMain(m *testing.M) {
// Source directory must be a leveldb dir with the required blocks accessible via rawdb.ReadBlock.
flag.StringVar(&sourceBlockDir, "source-block-dir", sourceBlockDir, "DB directory storing executable block range.")
// Target directory assumes the same structure as bench.import_cchain_data.sh:
// - vmdb/
// - chain-data-dir/
flag.StringVar(&targetDir, "target-dir", targetDir, "Target directory for the current state including VM DB and Chain Data Directory.")
flag.Uint64Var(&startBlock, "start-block", 100, "Start block to begin execution (exclusive).")
flag.Uint64Var(&endBlock, "end-block", 200, "End block to end execution (inclusive).")

flag.Parse()
m.Run()
}

// TODO:
// - pull metrics into tmpnet ingestion and integrate w/ existing dashboards
// - add scoped access tokens to AvalancheGo CI for required S3 bucket ONLY
// - separate general purpose VM setup from C-Chain specific setup
func TestReexecuteRange(t *testing.T) {
r := require.New(t)

var (
targetDBDir = filepath.Join(targetDir, "vmdb")
firewoodDB = filepath.Join(targetDBDir, "chain-data-dir")
)

blockChan, err := createBlockChanFromRawDB(sourceBlockDir, 1, 100, 100)
r.NoError(err)

sourceVM, err := newMainnetCChainVM(
context.Background(),
targetDBDir,
t.TempDir(),
firewoodDB,
[]byte(`{"pruning-enabled": false}`),
)
r.NoError(err)
defer sourceVM.Shutdown(context.Background())

Check failure on line 90 in tests/bench/c/vm_test.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `sourceVM.Shutdown` is not checked (errcheck)

executor := newVMExecutor(sourceVM)
err = executor.executeSequence(context.Background(), blockChan)
r.NoError(err)
}

func newMainnetCChainVM(
ctx context.Context,
dbDir string,
atomicMemoryDBDir string,
chainDataDir string,
configBytes []byte,
) (*evm.VM, error) {
vm := evm.VM{}

log := tests.NewDefaultLogger("mainnet-vm-reexecution")

baseDBRegistry := prometheus.NewRegistry()
db, err := leveldb.New(dbDir, nil, log, baseDBRegistry)
if err != nil {
return nil, fmt.Errorf("failed to create base level db: %w", err)
}

blsKey, err := localsigner.New()
if err != nil {
return nil, fmt.Errorf("failed to create BLS key: %w", err)
}

blsPublicKey := blsKey.PublicKey()
warpSigner := warp.NewSigner(blsKey, constants.MainnetID, mainnetCChainID)

genesisConfig := genesis.GetConfig(constants.MainnetID)

sharedMemoryDB, err := leveldb.New(atomicMemoryDBDir, nil, log, prometheus.NewRegistry() /* ignore metrics from shared memory db */)
if err != nil {
return nil, fmt.Errorf("failed to create shared memory db: %w", err)
}
atomicMemory := atomic.NewMemory(sharedMemoryDB)

if err := vm.Initialize(
ctx,
&snow.Context{
NetworkID: constants.MainnetID,
SubnetID: constants.PrimaryNetworkID,
ChainID: mainnetCChainID,
NodeID: ids.GenerateTestNodeID(),
PublicKey: blsPublicKey,
NetworkUpgrades: upgrade.Mainnet,

XChainID: mainnetXChainID,
CChainID: mainnetCChainID,
AVAXAssetID: mainnetAvaxAssetID,

Log: log,
SharedMemory: atomicMemory.NewSharedMemory(mainnetCChainID),
BCLookup: ids.NewAliaser(),
Metrics: metrics.NewPrefixGatherer(),

WarpSigner: warpSigner,

ValidatorState: &validatorstest.State{},
ChainDataDir: chainDataDir,
},
db,
[]byte(genesisConfig.CChainGenesis),
nil,
configBytes,
make(chan common.Message, 1),
nil,
&enginetest.Sender{},
); err != nil {
return nil, fmt.Errorf("failed to initialize VM: %w", err)
}

return &vm, nil
}

type BlockResult struct {
BlockBytes []byte
Err error
}

type VMExecutor struct {
vm block.ChainVM
}

func newVMExecutor(vm block.ChainVM) *VMExecutor {
return &VMExecutor{
vm: vm,
}
}

func (e *VMExecutor) execute(ctx context.Context, blockBytes []byte) error {
blk, err := e.vm.ParseBlock(ctx, blockBytes)
if err != nil {
return err
}

if err := blk.Verify(ctx); err != nil {
return err
}

return blk.Accept(ctx)
}

func (e *VMExecutor) executeSequence(ctx context.Context, blkChan <-chan BlockResult) error {
for blkResult := range blkChan {
if blkResult.Err != nil {
return blkResult.Err
}
if err := e.execute(ctx, blkResult.BlockBytes); err != nil {
return err
}
}

return nil
}

func createBlockChanFromRawDB(sourceDir string, startBlock, endBlock uint64, chanSize int) (<-chan BlockResult, error) {
ch := make(chan BlockResult, chanSize)

db, err := rawdb.NewLevelDBDatabase(sourceDir, sourceDBCacheSize, sourceDBHandles, "", true)
if err != nil {
return nil, err
}

go func() {
defer func() {
_ = db.Close() // TODO: handle error
close(ch)
}()

for i := startBlock; i <= endBlock; i++ {
block := rawdb.ReadBlock(db, rawdb.ReadCanonicalHash(db, i), i)
blockBytes, err := rlp.EncodeToBytes(block)
if err != nil {
ch <- BlockResult{
BlockBytes: nil,
Err: err,
}
return
}

ch <- BlockResult{
BlockBytes: blockBytes,
Err: nil,
}
}
}()

return ch, nil
}
Loading