Skip to content
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

feat(superchain): enable local testing #762

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ The configs are consumed by downstream OP Stack software, i.e. `op-geth` and `op

A second module exists in this repo whose purpose is to validate the config exported by the `superchain` module. It is a separate module to avoid import cycles and polluting downstream dependencies with things like `go-ethereum` (which is used in the validation tests).

> In order to run validation tests locally you must have access to a L1 archive node. If you are an:
>
> - OP Labs core dev: you must set Tailscale to use the Tailnet exit node in order to be able to connect to CI L1 archive nodes
>
> - external contributor: you must provide your own L1 archive node and set its RPC endpoint in the `l1.test_rpc` field in the superchain config files (e.g. `superchain/configs/{mainnet,sepolia,sepolia-dev-0}/superchain.yaml`)

## `ops` Go module

This module contains the CLI tool for generating `superchain` compliant configs and extra data to the registry.
Expand Down Expand Up @@ -180,6 +186,7 @@ See [Superchain Upgrades] OP Stack specifications.
[Superchain Upgrades]: https://specs.optimism.io/protocol/superchain-upgrades.html

## CircleCI Checks

The following CircleCI checks are not mandatory for submitting a pull request, but they should be reviewed:

- `ci/circleci: compute-genesis-diff`
Expand Down
3 changes: 3 additions & 0 deletions superchain/configs/configs.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"L1": {
"ChainID": 1,
"PublicRPC": "https://ethereum-rpc.publicnode.com",
"TestRPC": "https://ci-mainnet-l1-archive.optimism.io",
"Explorer": "https://etherscan.io"
},
"ProtocolVersionsAddr": "0x8062AbC286f5e7D9428a0Ccb9AbD71e50d93b935",
Expand Down Expand Up @@ -1546,6 +1547,7 @@
"L1": {
"ChainID": 11155111,
"PublicRPC": "https://ethereum-sepolia-rpc.publicnode.com",
"TestRPC": "https://ci-sepolia-l1-archive.optimism.io",
"Explorer": "https://sepolia.etherscan.io"
},
"ProtocolVersionsAddr": "0x79ADD5713B383DAa0a138d3C4780C7A1804a8090",
Expand Down Expand Up @@ -2728,6 +2730,7 @@
"L1": {
"ChainID": 11155111,
"PublicRPC": "https://ethereum-sepolia-rpc.publicnode.com",
"TestRPC": "https://ci-sepolia-l1-archive.optimism.io",
"Explorer": "https://sepolia.etherscan.io"
},
"ProtocolVersionsAddr": "0x252CbE9517F731C618961D890D534183822dcC8d",
Expand Down
1 change: 1 addition & 0 deletions superchain/configs/mainnet/superchain.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ holocene_time = 1736445601 # Thu 09 Jan 2025 18:00:01 UTC
[l1]
chain_id = 1
public_rpc = "https://ethereum-rpc.publicnode.com"
test_rpc = "https://ci-mainnet-l1-archive.optimism.io"
explorer = "https://etherscan.io"
1 change: 1 addition & 0 deletions superchain/configs/sepolia-dev-0/superchain.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ holocene_time = 1731682800 # Fri Nov 15 15:00:00 UTC 2024
[l1]
chain_id = 11155111
public_rpc = "https://ethereum-sepolia-rpc.publicnode.com"
test_rpc = "https://ci-sepolia-l1-archive.optimism.io"
explorer = "https://sepolia.etherscan.io"
1 change: 1 addition & 0 deletions superchain/configs/sepolia/superchain.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ holocene_time = 1732633200 # Tue Nov 26 15:00:00 UTC 2024
[l1]
chain_id = 11155111
public_rpc = "https://ethereum-sepolia-rpc.publicnode.com"
test_rpc = "https://ci-sepolia-l1-archive.optimism.io"
explorer = "https://sepolia.etherscan.io"
26 changes: 14 additions & 12 deletions superchain/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ func init() {
if err != nil {
panic(fmt.Errorf("failed to read superchain dir: %w", err))
}

runningInCodegen := os.Getenv("CODEGEN") != ""
runningInCI := os.Getenv("CI") == "true"
runningInTest := os.Getenv("TEST_DIRECTORY") != ""

// Impute endpoints only if we're not in codegen mode.
replaceL1Rpc := !runningInCodegen && (runningInCI || runningInTest)

// iterate over superchain-target entries
for _, s := range superchainTargets {

Expand Down Expand Up @@ -70,19 +78,13 @@ func init() {
GenesisSystemConfigs[chainConfig.ChainID] = &chainConfig.Genesis.SystemConfig
}

// Impute endpoints only if we're not in codegen mode.
if os.Getenv("CODEGEN") == "" {
runningInCI := os.Getenv("CI")
switch superchainEntry.Superchain {
case "mainnet":
if runningInCI == "true" {
superchainEntry.Config.L1.PublicRPC = "https://ci-mainnet-l1-archive.optimism.io"
}
case "sepolia", "sepolia-dev-0":
if runningInCI == "true" {
superchainEntry.Config.L1.PublicRPC = "https://ci-sepolia-l1-archive.optimism.io"
}
if replaceL1Rpc {
testRpc := superchainEntry.Config.L1.TestRPC
if testRpc == "" {
panic(fmt.Errorf("missing test RPC endpoint for superchain %q", superchainEntry.Superchain))
}

superchainEntry.Config.L1.PublicRPC = testRpc
}

Superchains[superchainEntry.Superchain] = &superchainEntry
Expand Down
1 change: 1 addition & 0 deletions superchain/superchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ type Genesis struct {
type SuperchainL1Info struct {
ChainID uint64 `toml:"chain_id"`
PublicRPC string `toml:"public_rpc"`
TestRPC string `toml:"test_rpc"`
Explorer string `toml:"explorer"`
}

Expand Down
2 changes: 2 additions & 0 deletions superchain/superchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ isthmus_time = 7
[l1]
chain_id = 314
public_rpc = "https://disney.com"
test_rpc = "https://ci.disney.com"
explorer = "https://disneyscan.io"
`

Expand All @@ -216,6 +217,7 @@ isthmus_time = 7
expectL1Info := SuperchainL1Info{
ChainID: 314,
PublicRPC: "https://disney.com",
TestRPC: "https://ci.disney.com",
Explorer: "https://disneyscan.io",
}

Expand Down
2 changes: 0 additions & 2 deletions validation/data-availability-type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (

func testDataAvailabilityType(t *testing.T, chain *ChainConfig) {
rpcEndpoint := Superchains[chain.Superchain].Config.L1.PublicRPC
require.NotEmpty(t, rpcEndpoint)

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
4 changes: 4 additions & 0 deletions validation/exclusions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ var exclusions = map[string]map[uint64]bool{
10: true, // op-mainnet
1740: true, // metal-sepolia
},
PublicRPCTest: {
11155421: true, // sepolia-dev-0/oplabs-devnet-0 No Public RPC declared
11763072: true, // sepolia-dev-0/base-devnet-0 No Public RPC declared
},
ChainIDRPCTest: {
11155421: true, // sepolia-dev-0/oplabs-devnet-0 No Public RPC declared
11763072: true, // sepolia-dev-0/base-devnet-0 No Public RPC declared
Expand Down
2 changes: 0 additions & 2 deletions validation/key-handover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ func testKeyHandover(t *testing.T, chain *ChainConfig) {
chainID := chain.ChainID
superchain := OPChains[chainID].Superchain
rpcEndpoint := Superchains[superchain].Config.L1.PublicRPC
require.NotEmpty(t, rpcEndpoint, "no rpc specified")

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
2 changes: 0 additions & 2 deletions validation/optimism-portal-2-params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ func testOptimismPortal2Params(t *testing.T, chain *ChainConfig) {
require.NoError(t, err)

rpcEndpoint := Superchains[chain.Superchain].Config.L1.PublicRPC

require.NotEmpty(t, rpcEndpoint, "no public endpoint for chain")
client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
18 changes: 18 additions & 0 deletions validation/public-rpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package validation

import (
"testing"

. "github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)

func testPublicRPC(t *testing.T, chain *ChainConfig) {
rpcEndpoint := chain.PublicRPC
require.NotEmpty(t, rpcEndpoint, "no public_rpc endpoint specified")

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint '%s'", rpcEndpoint)
defer client.Close()
}
3 changes: 0 additions & 3 deletions validation/resource-config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ import (

func testResourceConfig(t *testing.T, chain *ChainConfig) {
rpcEndpoint := Superchains[chain.Superchain].Config.L1.PublicRPC

require.NotEmpty(t, rpcEndpoint)

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
2 changes: 0 additions & 2 deletions validation/security-configs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ func testL1SecurityConfig(t *testing.T, chain *ChainConfig) {
chainID := chain.ChainID

rpcEndpoint := Superchains[chain.Superchain].Config.L1.PublicRPC
require.NotEmpty(t, rpcEndpoint, "no rpc specified")

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
2 changes: 0 additions & 2 deletions validation/start-block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import (

func testStartBlock(t *testing.T, chain *ChainConfig) {
rpcEndpoint := Superchains[chain.Superchain].Config.L1.PublicRPC
require.NotEmpty(t, rpcEndpoint)

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
2 changes: 0 additions & 2 deletions validation/superchain-config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ func testSuperchainConfig(t *testing.T, chain *ChainConfig) {
require.NotNil(t, opcm, "Superchain does not declare a op_contracts_manager_proxy_addr")

rpcEndpoint := Superchains[chain.Superchain].Config.L1.PublicRPC
require.NotEmpty(t, rpcEndpoint, "no rpc specified")

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
1 change: 0 additions & 1 deletion validation/superchain-genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ func testGenesisHash(t *testing.T, chain *ChainConfig) {
func testGenesisHashAgainstRPC(t *testing.T, chain *ChainConfig) {
declaredGenesisHash := chain.Genesis.L2.Hash
rpcEndpoint := chain.PublicRPC

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
2 changes: 0 additions & 2 deletions validation/superchain-version.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import (

func checkForStandardVersions(t *testing.T, chain *ChainConfig) {
rpcEndpoint := Superchains[chain.Superchain].Config.L1.PublicRPC
require.NotEmpty(t, rpcEndpoint)

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint %s", rpcEndpoint)

Expand Down
31 changes: 31 additions & 0 deletions validation/validation_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
package validation

import (
"context"
"math/big"
"testing"

. "github.com/ethereum-optimism/superchain-registry/superchain"
"github.com/ethereum-optimism/superchain-registry/validation/common"
"github.com/stretchr/testify/require"

ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)

// Test names
const (
GenesisHashTest = "Genesis_Hash"
GenesisRPCTest = "Genesis_RPC"
UniquenessTest = "Uniqueness"
PublicRPCTest = "Public_RPC"
ChainIDRPCTest = "ChainID_RPC"
OptimismConfigTest = "Optimism_Config"
GovernedByOptimismTest = "Governed_By_Optimism"
Expand Down Expand Up @@ -45,7 +52,30 @@ func applyExclusions(chain *ChainConfig, f subTestForChain) subTest {
}
}

func preflightChecks(t *testing.T) {
// Check that all superchains have an accessible L1 archive RPC endpoint configured
for name, chain := range Superchains {
rpcEndpoint := chain.Config.L1.PublicRPC

require.NotEmpty(t, rpcEndpoint, "no public_rpc specified for superchain '%s'", name)

client, err := ethclient.Dial(rpcEndpoint)
require.NoErrorf(t, err, "could not dial rpc endpoint '%s' for superchain '%s'", rpcEndpoint, name)
defer client.Close()

_, err = client.ChainID(context.Background())
require.NoErrorf(t, err, "could not query node at '%s' for superchain '%s'", rpcEndpoint, name)

superchainConfigAddr := *chain.Config.SuperchainConfigAddr

_, err = client.NonceAt(context.Background(), ethCommon.Address(superchainConfigAddr), big.NewInt(1))
require.NoErrorf(t, err, "node at '%s' for superchain '%s' is not an archive node. please set an L1 archive node RPC url in the `test_rpc` field of the superchain config file", rpcEndpoint, name)
}
}

func TestValidation(t *testing.T) {
preflightChecks(t)

// Entry point for validation checks which run
// on each OP chain.
for _, chain := range OPChains {
Expand Down Expand Up @@ -79,6 +109,7 @@ func testUniversal(t *testing.T, chain *ChainConfig) {
t.Run(GenesisHashTest, applyExclusions(chain, testGenesisHash))
t.Run(GenesisRPCTest, applyExclusions(chain, testGenesisHashAgainstRPC))
t.Run(UniquenessTest, applyExclusions(chain, testIsGloballyUnique))
t.Run(PublicRPCTest, applyExclusions(chain, testPublicRPC))
t.Run(ChainIDRPCTest, applyExclusions(chain, testChainIDFromRPC))
t.Run(OptimismConfigTest, applyExclusions(chain, testOptimismConfig))
t.Run(GovernedByOptimismTest, applyExclusions(chain, testGovernedByOptimism))
Expand Down