Skip to content

Reuse network for e2e test #809

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

Merged
merged 8 commits into from
Aug 5, 2025
Merged
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,7 @@ server.log
# Forge documentation
docs/
coverage/

TeleporterRegistryAddress.json
ValidatorAddresses.json

2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.38.0
github.com/pkg/errors v0.9.1
github.com/segmentio/encoding v0.5.1
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
go.uber.org/zap v1.27.0
Expand Down Expand Up @@ -119,6 +120,7 @@ require (
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.7.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,10 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo
github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU=
github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.5.1 h1:LhmgXA5/alniiqfc4cYYrxF6DbUQ3m8MVz4/LQIU1mg=
github.com/segmentio/encoding v0.5.1/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
Expand Down
30 changes: 30 additions & 0 deletions scripts/e2e_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@ Arguments:
--components component1,component2 Comma separated list of test suites to run. Valid components are:
$(echo $valid_components | tr ' ' '\n' | sort | tr '\n' ' ')
(default: all)
--network-dir path Path to the network directory.
If the directory does not exist or is empty, it will be used as the root network directory for a new network.
If the directory exists and is non-empty, the network will be reused.
If not set, a new network will be created at the default root network directory.
Options:
--help Print this help message
EOF
}

valid_components=$(ls -d $ICM_CONTRACTS_PATH/tests/suites/*/ | xargs -n 1 basename)
components=
reuse_network_dir=
root_dir=
network_dir=
reuse_network=false

while [ $# -gt 0 ]; do
case "$1" in
Expand All @@ -38,6 +46,13 @@ while [ $# -gt 0 ]; do
echo "Invalid components $2" && printHelp && exit 1
fi
shift;;
--network-dir)
if [[ $2 != --* ]]; then
reuse_network_dir=$2
else
echo "Invalid network directory $2" && printHelp && exit 1
fi
shift;;
--help)
printHelp && exit 0 ;;
*)
Expand All @@ -58,6 +73,18 @@ for component in $(echo $components | tr ',' ' '); do
fi
done

if [ -n "$reuse_network_dir" ]; then
if [ -d "$reuse_network_dir" ] && [ "$(ls -A "$reuse_network_dir")" ]; then
network_dir=$reuse_network_dir
reuse_network=true
echo "Reuse network directory: $network_dir"
else
echo "Network directory $reuse_network_dir does not exist or is empty. Creating a new network at root $reuse_network_dir."
mkdir -p "$reuse_network_dir"
root_dir=$reuse_network_dir
fi
fi

source "$ICM_CONTRACTS_PATH"/scripts/constants.sh
source "$ICM_CONTRACTS_PATH"/scripts/versions.sh

Expand Down Expand Up @@ -100,6 +127,9 @@ for component in $(echo $components | tr ',' ' '); do
echo "Running e2e tests for $component"

RUN_E2E=true SIG_AGG_PATH=$ICM_SERVICES_BUILD_PATH/signature-aggregator ./tests/suites/$component/$component.test \
--root-network-dir=${root_dir} \
--reuse-network=${reuse_network} \
--network-dir=${network_dir} \
--ginkgo.vv \
--ginkgo.label-filter=${GINKGO_LABEL_FILTER:-""} \
--ginkgo.focus=${GINKGO_FOCUS:-""} \
Expand Down
160 changes: 128 additions & 32 deletions tests/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"io/fs"
goLog "log"
"os"
"sort"
"time"

Expand Down Expand Up @@ -74,41 +76,22 @@ type L1Spec struct {
RequirePrimaryNetworkSigners bool
}

func NewLocalNetwork(
ctx context.Context,
func newTmpnetNetwork(
name string,
globalFundedKey *secp256k1.PrivateKey,
warpGenesisTemplateFile string,
l1Specs []L1Spec,
numPrimaryNetworkValidators int,
extraNodeCount int, // for use by tests, eg to add new L1 validators
l1Specs []L1Spec,
flagVars *e2e.FlagVars,
) *LocalNetwork {
// There must be at least one primary network validator per L1
Expect(numPrimaryNetworkValidators).Should(BeNumerically(">=", len(l1Specs)))

// Create extra nodes to be used to add more validators later
extraNodes := subnetEvmTestUtils.NewTmpnetNodes(extraNodeCount)

fundedKey, err := hex.DecodeString(fundedKeyStr)
Expect(err).Should(BeNil())
globalFundedKey, err := secp256k1.ToPrivateKey(fundedKey)
Expect(err).Should(BeNil())

globalFundedECDSAKey := globalFundedKey.ToECDSA()
Expect(err).Should(BeNil())

) *tmpnet.Network {
var l1s []*tmpnet.Subnet
deployedL1Specs := make(map[string]L1Spec)

bootstrapNodes := subnetEvmTestUtils.NewTmpnetNodes(numPrimaryNetworkValidators)
for i, l1Spec := range l1Specs {
// Create a single bootstrap node. This will be removed from the L1 validator set after it is converted,
// but will remain a primary network validator
initialL1Bootstrapper := bootstrapNodes[i] // One bootstrap node per L1

// Create validators to specify as the initial vdr set in the L1 conversion, and store them as extra nodes
initialVdrNodes := subnetEvmTestUtils.NewTmpnetNodes(l1Spec.NodeCount)
extraNodes = append(extraNodes, initialVdrNodes...)

l1 := subnetEvmTestUtils.NewTmpnetSubnet(
l1Spec.Name,
utils.InstantiateGenesisTemplate(
Expand All @@ -122,16 +105,18 @@ func NewLocalNetwork(
utils.WarpEnabledChainConfig,
initialL1Bootstrapper,
)
deployedL1Specs[l1Spec.Name] = l1Spec
l1.OwningKey = globalFundedKey
l1s = append(l1s, l1)
}

// Create new network
network := subnetEvmTestUtils.NewTmpnetNetwork(
name,
bootstrapNodes,
tmpnet.FlagsMap{},
l1s...,
)

Expect(network).ShouldNot(BeNil())

// Specify only a subset of the nodes to be bootstrapped
Expand All @@ -146,11 +131,86 @@ func NewLocalNetwork(
network.Genesis = genesis
network.PreFundedKeys = keysToFund

tc := e2e.NewTestContext()
runtimeCfg, err := flagVars.NodeRuntimeConfig()
Expect(err).Should(BeNil())
runtimeCfg.Process.ReuseDynamicPorts = true
network.DefaultRuntimeConfig = *runtimeCfg

return network
}

func NewLocalNetwork(
ctx context.Context,
name string,
warpGenesisTemplateFile string,
l1Specs []L1Spec,
numPrimaryNetworkValidators int,
extraNodeCount int, // for use by tests, eg to add new L1 validators
flagVars *e2e.FlagVars,
) *LocalNetwork {
// There must be at least one primary network validator per L1
Expect(numPrimaryNetworkValidators).Should(BeNumerically(">=", len(l1Specs)))

// Create extra nodes to be used to add more validators later
extraNodes := subnetEvmTestUtils.NewTmpnetNodes(extraNodeCount)

for _, l1Spec := range l1Specs {
initialVdrNodes := subnetEvmTestUtils.NewTmpnetNodes(l1Spec.NodeCount)
extraNodes = append(extraNodes, initialVdrNodes...)
}

fundedKey, err := hex.DecodeString(fundedKeyStr)
Expect(err).Should(BeNil())
globalFundedKey, err := secp256k1.ToPrivateKey(fundedKey)
Expect(err).Should(BeNil())

globalFundedECDSAKey := globalFundedKey.ToECDSA()
Expect(err).Should(BeNil())

deployedL1Specs := make(map[string]L1Spec)
for _, l1Spec := range l1Specs {
deployedL1Specs[l1Spec.Name] = l1Spec
}

isReuseNetwork := flagVars != nil && flagVars.NetworkDir() != ""

var network *tmpnet.Network
// All nodes are specified as bootstrap validators
var primaryNetworkValidators []*tmpnet.Node
if isReuseNetwork {
// Load existing network and restart nodes
network, err = tmpnet.ReadNetwork(context.Background(), logging.NoLog{}, flagVars.NetworkDir())
Expect(err).Should(BeNil())
Expect(network).ShouldNot(BeNil())

extraNodes = make([]*tmpnet.Node, 0)
for _, node := range network.Nodes {
err := node.Restart(ctx)
Expect(err).Should(BeNil(), "Failed to restart node %s: %v", node.NodeID, err)

if node.Flags[config.PartialSyncPrimaryNetworkKey] == "true" {
extraNodes = append(extraNodes, node)
} else {
primaryNetworkValidators = append(primaryNetworkValidators, node)
}
}

err := tmpnet.WaitForHealthyNodes(ctx, logging.NoLog{}, network.Nodes)
Expect(err).Should(BeNil())
} else {
network = newTmpnetNetwork(
name,
globalFundedKey,
warpGenesisTemplateFile,
numPrimaryNetworkValidators,
l1Specs,
flagVars,
)

primaryNetworkValidators = append(primaryNetworkValidators, network.Nodes...)
}

tc := e2e.NewTestContext()
env := e2e.NewTestEnvironment(tc, flagVars, network)
Expect(env).ShouldNot(BeNil())

Expand All @@ -161,14 +221,12 @@ func NewLocalNetwork(
goLog.Println("Network bootstrapped")

// Issue transactions to activate the proposerVM fork on the chains
for _, l1 := range network.Subnets {
utils.SetupProposerVM(ctx, globalFundedECDSAKey, network, l1.SubnetID)
if !isReuseNetwork {
for _, l1 := range network.Subnets {
utils.SetupProposerVM(ctx, globalFundedECDSAKey, network, l1.SubnetID)
}
}

// All nodes are specified as bootstrap validators
var primaryNetworkValidators []*tmpnet.Node
primaryNetworkValidators = append(primaryNetworkValidators, network.Nodes...)

localNetwork := &LocalNetwork{
Network: network,
extraNodes: extraNodes,
Expand Down Expand Up @@ -597,3 +655,41 @@ func (n *LocalNetwork) GetTwoL1s() (
Expect(len(l1s)).Should(BeNumerically(">=", 2))
return l1s[0], l1s[1]
}

func (n *LocalNetwork) SaveValidatorAddress(
fileName string,
) {
validatorAddresses := make(map[string]map[string]string)
for _, subnet := range n.GetL1Infos() {
validator, validatorSpec := n.GetValidatorManager(subnet.SubnetID)
validatorAddresses[subnet.BlockchainID.Hex()] = make(map[string]string)
validatorAddresses[subnet.BlockchainID.Hex()]["validator"] = validator.Address.Hex()
validatorAddresses[subnet.BlockchainID.Hex()]["spec"] = validatorSpec.Address.Hex()
}

jsonData, err := json.Marshal(validatorAddresses)
Expect(err).Should(BeNil())
err = os.WriteFile(fileName, jsonData, fs.ModePerm)
Expect(err).Should(BeNil())
}

func (n *LocalNetwork) SetValidatorAddressFromFile(fileName string) {
validatorAddresses := make(map[string]map[string]string)
data, err := os.ReadFile(fileName)
Expect(err).Should(BeNil())
err = json.Unmarshal(data, &validatorAddresses)
Expect(err).Should(BeNil())

// Set the validator manager for each L1
for _, subnet := range n.GetL1Infos() {
validatorAddress := common.HexToAddress(validatorAddresses[subnet.BlockchainID.Hex()]["validator"])
validatorSpecAddress := common.HexToAddress(validatorAddresses[subnet.BlockchainID.Hex()]["spec"])

n.validatorManagers[subnet.SubnetID] = ProxyAddress{
Address: validatorAddress,
}
n.validatorManagerSpecializations[subnet.SubnetID] = ProxyAddress{
Address: validatorSpecAddress,
}
}
}
8 changes: 7 additions & 1 deletion tests/suites/governance/governance_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package governance_test

import (
"context"
"flag"
"os"
"testing"
"time"
Expand All @@ -24,12 +25,17 @@ var (
e2eFlags *e2e.FlagVars
)

func TestMain(m *testing.M) {
e2eFlags = e2e.RegisterFlags()
flag.Parse()
os.Exit(m.Run())
}

func TestGovernance(t *testing.T) {
if os.Getenv("RUN_E2E") == "" {
t.Skip("Environment variable RUN_E2E not set; skipping E2E tests")
}

e2eFlags = e2e.RegisterFlags()
RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Governance e2e test")
}
Expand Down
Loading