Commit 9a99e0ac authored by Diederik Loerakker's avatar Diederik Loerakker Committed by GitHub

Bedrock: use Engine API authentication (#2591)

* feat: new --l2.jwt-secret flag to authenticate engine API comms with l2, remove l2.eth flag, update e2e tests

* feat: update bedrock ops with jwt secret flag
Co-authored-by: default avatarMatthew Slipper <me@matthewslipper.com>
parent c17e3d62
...@@ -6,8 +6,6 @@ import ( ...@@ -6,8 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"strconv"
"strings"
"time" "time"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
...@@ -98,8 +96,8 @@ func initL1Geth(cfg *SystemConfig, wallet *hdwallet.Wallet, genesis *core.Genesi ...@@ -98,8 +96,8 @@ func initL1Geth(cfg *SystemConfig, wallet *hdwallet.Wallet, genesis *core.Genesi
} }
nodeConfig := &node.Config{ nodeConfig := &node.Config{
Name: "l1-geth", Name: "l1-geth",
WSHost: cfg.L1WsAddr, WSHost: "127.0.0.1",
WSPort: cfg.L1WsPort, WSPort: 0,
WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"},
HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"},
} }
...@@ -107,27 +105,21 @@ func initL1Geth(cfg *SystemConfig, wallet *hdwallet.Wallet, genesis *core.Genesi ...@@ -107,27 +105,21 @@ func initL1Geth(cfg *SystemConfig, wallet *hdwallet.Wallet, genesis *core.Genesi
return createGethNode(false, nodeConfig, ethConfig, []*ecdsa.PrivateKey{pk}) return createGethNode(false, nodeConfig, ethConfig, []*ecdsa.PrivateKey{pk})
} }
func initL2Geth(name, addr string, l2ChainID *big.Int, genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { // init a geth node.
func initL2Geth(name string, l2ChainID *big.Int, genesis *core.Genesis, jwtPath string) (*node.Node, *eth.Ethereum, error) {
ethConfig := &ethconfig.Config{ ethConfig := &ethconfig.Config{
NetworkId: l2ChainID.Uint64(), NetworkId: l2ChainID.Uint64(),
Genesis: genesis, Genesis: genesis,
} }
// Parsing ws://127.0.0.1:9091 for "127.0.0.1" and "9091"
s := strings.Split(addr, ":")
_, host, ok := strings.Cut(s[1], "//")
if !ok {
return nil, nil, fmt.Errorf("could not find ws host in %s", addr)
}
port, err := strconv.ParseInt(s[2], 10, 32)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse port from address: %w", err)
}
nodeConfig := &node.Config{ nodeConfig := &node.Config{
Name: fmt.Sprintf("l2-geth-%v", name), Name: fmt.Sprintf("l2-geth-%v", name),
WSHost: host, WSHost: "127.0.0.1",
WSPort: int(port), WSPort: 0,
AuthAddr: "127.0.0.1",
AuthPort: 0,
WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, WSModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"},
HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"}, HTTPModules: []string{"debug", "admin", "eth", "txpool", "net", "rpc", "web3", "personal", "engine"},
JWTSecret: jwtPath,
} }
return createGethNode(true, nodeConfig, ethConfig, nil) return createGethNode(true, nodeConfig, ethConfig, nil)
} }
......
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
hdwallet "github.com/miguelmota/go-ethereum-hdwallet" hdwallet "github.com/miguelmota/go-ethereum-hdwallet"
) )
...@@ -75,10 +76,12 @@ type SystemConfig struct { ...@@ -75,10 +76,12 @@ type SystemConfig struct {
L2OOCfg L2OOContractConfig L2OOCfg L2OOContractConfig
DepositCFG DepositContractConfig DepositCFG DepositContractConfig
L1WsAddr string
L1WsPort int
L1ChainID *big.Int L1ChainID *big.Int
L2ChainID *big.Int L2ChainID *big.Int
JWTFilePath string
JWTSecret [32]byte
Nodes map[string]*rollupNode.Config // Per node config. Don't use populate rollup.Config Nodes map[string]*rollupNode.Config // Per node config. Don't use populate rollup.Config
Loggers map[string]log.Logger Loggers map[string]log.Logger
RollupConfig rollup.Config // Shared rollup configs RollupConfig rollup.Config // Shared rollup configs
...@@ -293,8 +296,9 @@ func (cfg SystemConfig) start() (*System, error) { ...@@ -293,8 +296,9 @@ func (cfg SystemConfig) start() (*System, error) {
} }
sys.nodes["l1"] = l1Node sys.nodes["l1"] = l1Node
sys.backends["l1"] = l1Backend sys.backends["l1"] = l1Backend
for name, l2Cfg := range cfg.Nodes {
node, backend, err := initL2Geth(name, l2Cfg.L2EngineAddrs[0], cfg.L2ChainID, l2Genesis) for name := range cfg.Nodes {
node, backend, err := initL2Geth(name, cfg.L2ChainID, l2Genesis, cfg.JWTFilePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -324,14 +328,28 @@ func (cfg SystemConfig) start() (*System, error) { ...@@ -324,14 +328,28 @@ func (cfg SystemConfig) start() (*System, error) {
} }
} }
// Configure connections to L1 and L2 for rollup nodes.
// TODO: refactor testing to use in-process rpc connections instead of websockets.
for name, rollupCfg := range cfg.Nodes {
rollupCfg.L1 = &rollupNode.L1EndpointConfig{
L1NodeAddr: l1Node.WSEndpoint(),
L1TrustRPC: false,
}
rollupCfg.L2s = &rollupNode.L2EndpointsConfig{
L2EngineAddrs: []string{sys.nodes[name].WSAuthEndpoint()},
L2EngineJWTSecrets: [][32]byte{cfg.JWTSecret},
}
}
// Geth Clients // Geth Clients
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() defer cancel()
l1Client, err := ethclient.DialContext(ctx, fmt.Sprintf("ws://%s:%d", cfg.L1WsAddr, cfg.L1WsPort)) l1Srv, err := l1Node.RPCHandler()
if err != nil { if err != nil {
didErrAfterStart = true didErrAfterStart = true
return nil, err return nil, err
} }
l1Client := ethclient.NewClient(rpc.DialInProc(l1Srv))
sys.Clients["l1"] = l1Client sys.Clients["l1"] = l1Client
for name, node := range sys.nodes { for name, node := range sys.nodes {
client, err := ethclient.DialContext(ctx, node.WSEndpoint()) client, err := ethclient.DialContext(ctx, node.WSEndpoint())
...@@ -512,8 +530,8 @@ func (cfg SystemConfig) start() (*System, error) { ...@@ -512,8 +530,8 @@ func (cfg SystemConfig) start() (*System, error) {
// L2Output Submitter // L2Output Submitter
sys.l2OutputSubmitter, err = l2os.NewL2OutputSubmitter(l2os.Config{ sys.l2OutputSubmitter, err = l2os.NewL2OutputSubmitter(l2os.Config{
L1EthRpc: "ws://127.0.0.1:9090", L1EthRpc: sys.nodes["l1"].WSEndpoint(),
L2EthRpc: sys.cfg.Nodes["sequencer"].L2NodeAddr, L2EthRpc: sys.nodes["sequencer"].WSEndpoint(),
RollupRpc: rollupEndpoint, RollupRpc: rollupEndpoint,
L2OOAddress: sys.L2OOContractAddr.String(), L2OOAddress: sys.L2OOContractAddr.String(),
PollInterval: 50 * time.Millisecond, PollInterval: 50 * time.Millisecond,
...@@ -544,8 +562,8 @@ func (cfg SystemConfig) start() (*System, error) { ...@@ -544,8 +562,8 @@ func (cfg SystemConfig) start() (*System, error) {
// Batch Submitter // Batch Submitter
sys.batchSubmitter, err = bss.NewBatchSubmitter(bss.Config{ sys.batchSubmitter, err = bss.NewBatchSubmitter(bss.Config{
L1EthRpc: "ws://127.0.0.1:9090", L1EthRpc: sys.nodes["l1"].WSEndpoint(),
L2EthRpc: sys.cfg.Nodes["sequencer"].L2NodeAddr, L2EthRpc: sys.nodes["sequencer"].WSEndpoint(),
RollupRpc: rollupEndpoint, RollupRpc: rollupEndpoint,
MinL1TxSize: 1, MinL1TxSize: 1,
MaxL1TxSize: 120000, MaxL1TxSize: 120000,
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"flag" "flag"
"fmt" "fmt"
"math/big" "math/big"
"os"
"path"
"testing" "testing"
"time" "time"
...@@ -24,6 +26,7 @@ import ( ...@@ -24,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
...@@ -59,8 +62,18 @@ const ( ...@@ -59,8 +62,18 @@ const (
var ( var (
batchInboxAddress = common.Address{0xff, 0x02} batchInboxAddress = common.Address{0xff, 0x02}
testingJWTSecret = [32]byte{123}
) )
func writeDefaultJWT(t *testing.T) string {
// Sadly the geth node config cannot load JWT secret from memory, it has to be a file
jwtPath := path.Join(t.TempDir(), "jwt_secret")
if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(testingJWTSecret[:])), 0600); err != nil {
t.Fatalf("failed to prepare jwt file for geth: %v", err)
}
return jwtPath
}
func defaultSystemConfig(t *testing.T) SystemConfig { func defaultSystemConfig(t *testing.T) SystemConfig {
return SystemConfig{ return SystemConfig{
Mnemonic: "squirrel green gallery layer logic title habit chase clog actress language enrich body plate fun pledge gap abuse mansion define either blast alien witness", Mnemonic: "squirrel green gallery layer logic title habit chase clog actress language enrich body plate fun pledge gap abuse mansion define either blast alien witness",
...@@ -86,22 +99,13 @@ func defaultSystemConfig(t *testing.T) SystemConfig { ...@@ -86,22 +99,13 @@ func defaultSystemConfig(t *testing.T) SystemConfig {
CliqueSignerDerivationPath: cliqueSignerHDPath, CliqueSignerDerivationPath: cliqueSignerHDPath,
L1InfoPredeployAddress: derive.L1InfoPredeployAddr, L1InfoPredeployAddress: derive.L1InfoPredeployAddr,
L1BlockTime: 2, L1BlockTime: 2,
L1WsAddr: "127.0.0.1",
L1WsPort: 9090,
L1ChainID: big.NewInt(900), L1ChainID: big.NewInt(900),
L2ChainID: big.NewInt(901), L2ChainID: big.NewInt(901),
JWTFilePath: writeDefaultJWT(t),
JWTSecret: testingJWTSecret,
Nodes: map[string]*rollupNode.Config{ Nodes: map[string]*rollupNode.Config{
"verifier": { "verifier": {},
L1NodeAddr: "ws://127.0.0.1:9090",
L2EngineAddrs: []string{"ws://127.0.0.1:9091"},
L2NodeAddr: "ws://127.0.0.1:9091",
L1TrustRPC: false,
},
"sequencer": { "sequencer": {
L1NodeAddr: "ws://127.0.0.1:9090",
L2EngineAddrs: []string{"ws://127.0.0.1:9092"},
L2NodeAddr: "ws://127.0.0.1:9092",
L1TrustRPC: false,
Sequencer: true, Sequencer: true,
// Submitter PrivKey is set in system start for rollup nodes where sequencer = true // Submitter PrivKey is set in system start for rollup nodes where sequencer = true
RPC: node.RPCConfig{ RPC: node.RPCConfig{
...@@ -786,7 +790,7 @@ func TestWithdrawals(t *testing.T) { ...@@ -786,7 +790,7 @@ func TestWithdrawals(t *testing.T) {
header, err = l2Seq.HeaderByNumber(ctx, blockNumber) header, err = l2Seq.HeaderByNumber(ctx, blockNumber)
require.Nil(t, err) require.Nil(t, err)
rpc, err := rpc.Dial(cfg.Nodes["sequencer"].L2NodeAddr) rpc, err := rpc.Dial(sys.nodes["sequencer"].WSEndpoint())
require.Nil(t, err) require.Nil(t, err)
l2client := withdrawals.NewClient(rpc) l2client := withdrawals.NewClient(rpc)
......
...@@ -63,14 +63,16 @@ func main() { ...@@ -63,14 +63,16 @@ func main() {
func RollupNodeMain(ctx *cli.Context) error { func RollupNodeMain(ctx *cli.Context) error {
log.Info("Initializing Rollup Node") log.Info("Initializing Rollup Node")
cfg, err := opnode.NewConfig(ctx) logCfg, err := opnode.NewLogConfig(ctx)
if err != nil { if err != nil {
log.Error("Unable to create the rollup node config", "error", err) log.Error("Unable to create the log config", "error", err)
return err return err
} }
logCfg, err := opnode.NewLogConfig(ctx) log := logCfg.NewLogger()
cfg, err := opnode.NewConfig(ctx, log)
if err != nil { if err != nil {
log.Error("Unable to create the log config", "error", err) log.Error("Unable to create the rollup node config", "error", err)
return err return err
} }
snapshotLog, err := opnode.NewSnapshotLogger(ctx) snapshotLog, err := opnode.NewSnapshotLogger(ctx)
...@@ -79,7 +81,7 @@ func RollupNodeMain(ctx *cli.Context) error { ...@@ -79,7 +81,7 @@ func RollupNodeMain(ctx *cli.Context) error {
return err return err
} }
n, err := node.New(context.Background(), cfg, logCfg.NewLogger(), snapshotLog, VersionWithMeta) n, err := node.New(context.Background(), cfg, log, snapshotLog, VersionWithMeta)
if err != nil { if err != nil {
log.Error("Unable to create the rollup node", "error", err) log.Error("Unable to create the rollup node", "error", err)
return err return err
......
...@@ -31,12 +31,6 @@ var ( ...@@ -31,12 +31,6 @@ var (
Required: true, Required: true,
EnvVar: prefixEnvVar("ROLLUP_CONFIG"), EnvVar: prefixEnvVar("ROLLUP_CONFIG"),
} }
L2EthNodeAddr = cli.StringFlag{
Name: "l2.eth",
Usage: "Address of L2 User JSON-RPC endpoint to use (eth namespace required)",
Required: true,
EnvVar: prefixEnvVar("L2_ETH_RPC"),
}
RPCListenAddr = cli.StringFlag{ RPCListenAddr = cli.StringFlag{
Name: "rpc.addr", Name: "rpc.addr",
Usage: "RPC listening address", Usage: "RPC listening address",
...@@ -56,7 +50,14 @@ var ( ...@@ -56,7 +50,14 @@ var (
Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data", Usage: "Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent L1 data",
EnvVar: prefixEnvVar("L1_TRUST_RPC"), EnvVar: prefixEnvVar("L1_TRUST_RPC"),
} }
L2EngineJWTSecret = cli.StringSliceFlag{
Name: "l2.jwt-secret",
Usage: "Paths to JWT secret keys, one per L2 endpoint, in the same order as the provided l2 addresses. " +
"Keys are 32 bytes, hex encoded in a file. A new key per endpoint will be generated if left empty.",
Required: false,
Value: &cli.StringSlice{},
EnvVar: prefixEnvVar("L2_ENGINE_AUTH"),
}
SequencingEnabledFlag = cli.BoolFlag{ SequencingEnabledFlag = cli.BoolFlag{
Name: "sequencing.enabled", Name: "sequencing.enabled",
Usage: "enable sequencing", Usage: "enable sequencing",
...@@ -92,13 +93,13 @@ var requiredFlags = []cli.Flag{ ...@@ -92,13 +93,13 @@ var requiredFlags = []cli.Flag{
L1NodeAddr, L1NodeAddr,
L2EngineAddrs, L2EngineAddrs,
RollupConfig, RollupConfig,
L2EthNodeAddr,
RPCListenAddr, RPCListenAddr,
RPCListenPort, RPCListenPort,
} }
var optionalFlags = append([]cli.Flag{ var optionalFlags = append([]cli.Flag{
L1TrustRPC, L1TrustRPC,
L2EngineJWTSecret,
SequencingEnabledFlag, SequencingEnabledFlag,
LogLevelFlag, LogLevelFlag,
LogFormatFlag, LogFormatFlag,
......
package node
import (
"context"
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-node/backoff"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
type L2EndpointsSetup interface {
// Setup a RPC client to a L2 execution engine to process rollup blocks with.
Setup(ctx context.Context, log log.Logger) (cl []*rpc.Client, err error)
Check() error
}
type L1EndpointSetup interface {
// Setup a RPC client to a L1 node to pull rollup input-data from.
Setup(ctx context.Context, log log.Logger) (cl *rpc.Client, trust bool, err error)
}
type L2EndpointsConfig struct {
L2EngineAddrs []string // Addresses of L2 Engine JSON-RPC endpoints to use (engine and eth namespace required)
// JWT secrets for L2 Engine API authentication during HTTP or initial Websocket communication, one per L2 engine.
// Any value for an IPC connection.
L2EngineJWTSecrets [][32]byte
}
var _ L2EndpointsSetup = (*L2EndpointsConfig)(nil)
func (cfg *L2EndpointsConfig) Check() error {
if len(cfg.L2EngineAddrs) == 0 {
return errors.New("need at least one L2 engine to connect to")
}
if len(cfg.L2EngineAddrs) != len(cfg.L2EngineJWTSecrets) {
return fmt.Errorf("have %d L2 engines, but %d authentication secrets", len(cfg.L2EngineAddrs), len(cfg.L2EngineJWTSecrets))
}
return nil
}
func (cfg *L2EndpointsConfig) Setup(ctx context.Context, log log.Logger) ([]*rpc.Client, error) {
if err := cfg.Check(); err != nil {
return nil, err
}
var out []*rpc.Client
for i, addr := range cfg.L2EngineAddrs {
auth := rpc.NewJWTAuthProvider(cfg.L2EngineJWTSecrets[i])
l2Node, err := dialRPCClientWithBackoff(ctx, log, addr, auth)
if err != nil {
// close clients again if we cannot complete the full setup
for _, cl := range out {
cl.Close()
}
return out, err
}
out = append(out, l2Node)
}
return out, nil
}
// PreparedL2Endpoints enables testing with in-process pre-setup RPC connections to L2 engines
type PreparedL2Endpoints struct {
Clients []*rpc.Client
}
func (p *PreparedL2Endpoints) Check() error {
if len(p.Clients) == 0 {
return errors.New("need at least one L2 engine to connect to")
}
return nil
}
var _ L2EndpointsSetup = (*PreparedL2Endpoints)(nil)
func (p *PreparedL2Endpoints) Setup(ctx context.Context, log log.Logger) ([]*rpc.Client, error) {
return p.Clients, nil
}
type L1EndpointConfig struct {
L1NodeAddr string // Address of L1 User JSON-RPC endpoint to use (eth namespace required)
// L1TrustRPC: if we trust the L1 RPC we do not have to validate L1 response contents like headers
// against block hashes, or cached transaction sender addresses.
// Thus we can sync faster at the risk of the source RPC being wrong.
L1TrustRPC bool
}
var _ L1EndpointSetup = (*L1EndpointConfig)(nil)
func (cfg *L1EndpointConfig) Setup(ctx context.Context, log log.Logger) (cl *rpc.Client, trust bool, err error) {
l1Node, err := dialRPCClientWithBackoff(ctx, log, cfg.L1NodeAddr, nil)
if err != nil {
return nil, false, fmt.Errorf("failed to dial L1 address (%s): %w", cfg.L1NodeAddr, err)
}
return l1Node, cfg.L1TrustRPC, nil
}
// PreparedL1Endpoint enables testing with an in-process pre-setup RPC connection to L1
type PreparedL1Endpoint struct {
Client *rpc.Client
TrustRPC bool
}
var _ L1EndpointSetup = (*PreparedL1Endpoint)(nil)
func (p *PreparedL1Endpoint) Setup(ctx context.Context, log log.Logger) (cl *rpc.Client, trust bool, err error) {
return p.Client, p.TrustRPC, nil
}
// Dials a JSON-RPC endpoint repeatedly, with a backoff, until a client connection is established. Auth is optional.
func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string, auth rpc.HeaderAuthProvider) (*rpc.Client, error) {
bOff := backoff.Exponential()
var ret *rpc.Client
err := backoff.Do(10, bOff, func() error {
var client *rpc.Client
var err error
if auth == nil {
client, err = rpc.DialContext(ctx, addr)
} else {
client, err = rpc.DialWithAuth(ctx, addr, auth)
}
if err != nil {
if client == nil {
return fmt.Errorf("failed to dial address (%s): %w", addr, err)
}
log.Warn("failed to dial address, but may connect later", "addr", addr, "err", err)
}
ret = client
return nil
})
if err != nil {
return nil, err
}
return ret, nil
}
...@@ -9,15 +9,8 @@ import ( ...@@ -9,15 +9,8 @@ import (
) )
type Config struct { type Config struct {
// L1 and L2 nodes L1 L1EndpointSetup
L1NodeAddr string // Address of L1 User JSON-RPC endpoint to use (eth namespace required) L2s L2EndpointsSetup
L2EngineAddrs []string // Addresses of L2 Engine JSON-RPC endpoints to use (engine and eth namespace required)
L2NodeAddr string // Address of L2 User JSON-RPC endpoint to use (eth namespace required)
// L1TrustRPC: if we trust the L1 RPC we do not have to validate L1 response contents like headers
// against block hashes, or cached transaction sender addresses.
// Thus we can sync faster at the risk of the source RPC being wrong.
L1TrustRPC bool
Rollup rollup.Config Rollup rollup.Config
...@@ -43,6 +36,9 @@ type RPCConfig struct { ...@@ -43,6 +36,9 @@ type RPCConfig struct {
// Check verifies that the given configuration makes sense // Check verifies that the given configuration makes sense
func (cfg *Config) Check() error { func (cfg *Config) Check() error {
if err := cfg.L2s.Check(); err != nil {
return fmt.Errorf("l2 endpoint config error: %v", err)
}
if err := cfg.Rollup.Check(); err != nil { if err := cfg.Rollup.Check(); err != nil {
return fmt.Errorf("rollup config error: %v", err) return fmt.Errorf("rollup config error: %v", err)
} }
......
...@@ -2,6 +2,7 @@ package node ...@@ -2,6 +2,7 @@ package node
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"sync" "sync"
"time" "time"
...@@ -12,8 +13,6 @@ import ( ...@@ -12,8 +13,6 @@ import (
multierror "github.com/hashicorp/go-multierror" multierror "github.com/hashicorp/go-multierror"
"github.com/ethereum-optimism/optimism/op-node/backoff"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/l1" "github.com/ethereum-optimism/optimism/op-node/l1"
"github.com/ethereum-optimism/optimism/op-node/l2" "github.com/ethereum-optimism/optimism/op-node/l2"
...@@ -47,26 +46,6 @@ type OpNode struct { ...@@ -47,26 +46,6 @@ type OpNode struct {
// The OpNode handles incoming gossip // The OpNode handles incoming gossip
var _ p2p.GossipIn = (*OpNode)(nil) var _ p2p.GossipIn = (*OpNode)(nil)
func dialRPCClientWithBackoff(ctx context.Context, log log.Logger, addr string) (*rpc.Client, error) {
bOff := backoff.Exponential()
var ret *rpc.Client
err := backoff.Do(10, bOff, func() error {
client, err := rpc.DialContext(ctx, addr)
if err != nil {
if client == nil {
return fmt.Errorf("failed to dial address (%s): %w", addr, err)
}
log.Warn("failed to dial address, but may connect later", "addr", addr, "err", err)
}
ret = client
return nil
})
if err != nil {
return nil, err
}
return ret, nil
}
func New(ctx context.Context, cfg *Config, log log.Logger, snapshotLog log.Logger, appVersion string) (*OpNode, error) { func New(ctx context.Context, cfg *Config, log log.Logger, snapshotLog log.Logger, appVersion string) (*OpNode, error) {
if err := cfg.Check(); err != nil { if err := cfg.Check(); err != nil {
return nil, err return nil, err
...@@ -97,7 +76,7 @@ func (n *OpNode) init(ctx context.Context, cfg *Config, snapshotLog log.Logger) ...@@ -97,7 +76,7 @@ func (n *OpNode) init(ctx context.Context, cfg *Config, snapshotLog log.Logger)
if err := n.initL1(ctx, cfg); err != nil { if err := n.initL1(ctx, cfg); err != nil {
return err return err
} }
if err := n.initL2(ctx, cfg, snapshotLog); err != nil { if err := n.initL2s(ctx, cfg, snapshotLog); err != nil {
return err return err
} }
if err := n.initP2PSigner(ctx, cfg); err != nil { if err := n.initP2PSigner(ctx, cfg); err != nil {
...@@ -123,14 +102,12 @@ func (n *OpNode) initTracer(ctx context.Context, cfg *Config) error { ...@@ -123,14 +102,12 @@ func (n *OpNode) initTracer(ctx context.Context, cfg *Config) error {
} }
func (n *OpNode) initL1(ctx context.Context, cfg *Config) error { func (n *OpNode) initL1(ctx context.Context, cfg *Config) error {
l1Node, err := dialRPCClientWithBackoff(ctx, n.log, cfg.L1NodeAddr) l1Node, trustRPC, err := cfg.L1.Setup(ctx, n.log)
if err != nil { if err != nil {
return fmt.Errorf("failed to dial L1 address (%s): %w", cfg.L1NodeAddr, err) return fmt.Errorf("failed to get L1 RPC client: %w", err)
} }
// TODO: we may need to authenticate the connection with L1 n.l1Source, err = l1.NewSource(l1Node, n.log, l1.DefaultConfig(&cfg.Rollup, trustRPC))
// l1Node.SetHeader()
n.l1Source, err = l1.NewSource(l1Node, n.log, l1.DefaultConfig(&cfg.Rollup, cfg.L1TrustRPC))
if err != nil { if err != nil {
return fmt.Errorf("failed to create L1 source: %v", err) return fmt.Errorf("failed to create L1 source: %v", err)
} }
...@@ -153,47 +130,44 @@ func (n *OpNode) initL1(ctx context.Context, cfg *Config) error { ...@@ -153,47 +130,44 @@ func (n *OpNode) initL1(ctx context.Context, cfg *Config) error {
} }
// AttachEngine attaches an engine to the rollup node. // AttachEngine attaches an engine to the rollup node.
func (n *OpNode) AttachEngine(ctx context.Context, cfg *Config, addr string, snapshotLog log.Logger) error { func (n *OpNode) AttachEngine(ctx context.Context, cfg *Config, tag string, cl *rpc.Client, snapshotLog log.Logger) error {
n.l2Lock.Lock() n.l2Lock.Lock()
defer n.l2Lock.Unlock() defer n.l2Lock.Unlock()
l2Node, err := dialRPCClientWithBackoff(ctx, n.log, addr) engLog := n.log.New("engine", tag)
if err != nil {
return err
}
engLog := n.log.New("engine", addr) client, err := l2.NewSource(cl, &cfg.Rollup.Genesis, engLog)
// TODO: we may need to authenticate the connection with L2
// backend.SetHeader()
client, err := l2.NewSource(l2Node, &cfg.Rollup.Genesis, engLog)
if err != nil { if err != nil {
l2Node.Close() cl.Close()
return err return err
} }
snap := snapshotLog.New("engine_addr", addr) snap := snapshotLog.New("engine_addr", tag)
engine := driver.NewDriver(cfg.Rollup, client, n.l1Source, n, engLog, snap, cfg.Sequencer) engine := driver.NewDriver(cfg.Rollup, client, n.l1Source, n, engLog, snap, cfg.Sequencer)
n.l2Nodes = append(n.l2Nodes, l2Node) n.l2Nodes = append(n.l2Nodes, cl)
n.l2Engines = append(n.l2Engines, engine) n.l2Engines = append(n.l2Engines, engine)
return nil return nil
} }
func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger) error { func (n *OpNode) initL2s(ctx context.Context, cfg *Config, snapshotLog log.Logger) error {
for i, addr := range cfg.L2EngineAddrs { clients, err := cfg.L2s.Setup(ctx, n.log)
if err := n.AttachEngine(ctx, cfg, addr, snapshotLog); err != nil { if err != nil {
return fmt.Errorf("failed to attach configured engine %d (%s): %v", i, addr, err) return fmt.Errorf("failed to setup L2 execution-engine RPC client(s): %v", err)
}
for i, cl := range clients {
if err := n.AttachEngine(ctx, cfg, fmt.Sprintf("eng_%d", i), cl, snapshotLog); err != nil {
return fmt.Errorf("failed to attach configured engine %d: %v", i, err)
} }
} }
return nil return nil
} }
func (n *OpNode) initRPCServer(ctx context.Context, cfg *Config) error { func (n *OpNode) initRPCServer(ctx context.Context, cfg *Config) error {
l2Node, err := dialRPCClientWithBackoff(ctx, n.log, cfg.L2NodeAddr) if len(n.l2Nodes) == 0 {
if err != nil { return errors.New("need at least one L2 node to serve rollup RPC")
return fmt.Errorf("failed to dial l2 address (%s): %w", cfg.L2NodeAddr, err)
} }
l2Node := n.l2Nodes[0]
// TODO: attach the p2p node ID to the snapshot logger // TODO: attach the p2p node ID to the snapshot logger
client, err := l2.NewReadOnlySource(l2Node, &cfg.Rollup.Genesis, n.log) client, err := l2.NewReadOnlySource(l2Node, &cfg.Rollup.Genesis, n.log)
......
...@@ -90,7 +90,7 @@ func TestOutputAtBlock(t *testing.T) { ...@@ -90,7 +90,7 @@ func TestOutputAtBlock(t *testing.T) {
assert.NoError(t, server.Start()) assert.NoError(t, server.Start())
defer server.Stop() defer server.Stop()
client, err := dialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String()) client, err := dialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String(), nil)
assert.NoError(t, err) assert.NoError(t, err)
var out []l2.Bytes32 var out []l2.Bytes32
...@@ -115,7 +115,7 @@ func TestVersion(t *testing.T) { ...@@ -115,7 +115,7 @@ func TestVersion(t *testing.T) {
assert.NoError(t, server.Start()) assert.NoError(t, server.Start())
defer server.Stop() defer server.Stop()
client, err := dialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String()) client, err := dialRPCClientWithBackoff(context.Background(), log, "http://"+server.Addr().String(), nil)
assert.NoError(t, err) assert.NoError(t, err)
var out string var out string
......
package opnode package opnode
import ( import (
"crypto/rand"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"os" "os"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -15,7 +21,7 @@ import ( ...@@ -15,7 +21,7 @@ import (
) )
// NewConfig creates a Config from the provided flags or environment variables. // NewConfig creates a Config from the provided flags or environment variables.
func NewConfig(ctx *cli.Context) (*node.Config, error) { func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
rollupConfig, err := NewRollupConfig(ctx) rollupConfig, err := NewRollupConfig(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -33,11 +39,19 @@ func NewConfig(ctx *cli.Context) (*node.Config, error) { ...@@ -33,11 +39,19 @@ func NewConfig(ctx *cli.Context) (*node.Config, error) {
return nil, fmt.Errorf("failed to load p2p config: %v", err) return nil, fmt.Errorf("failed to load p2p config: %v", err)
} }
l1Endpoint, err := NewL1EndpointConfig(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load l1 endpoint info: %v", err)
}
l2Endpoints, err := NewL2EndpointsConfig(ctx, log)
if err != nil {
return nil, fmt.Errorf("failed to load l2 endpoints info: %v", err)
}
cfg := &node.Config{ cfg := &node.Config{
L1NodeAddr: ctx.GlobalString(flags.L1NodeAddr.Name), L1: l1Endpoint,
L2EngineAddrs: ctx.GlobalStringSlice(flags.L2EngineAddrs.Name), L2s: l2Endpoints,
L2NodeAddr: ctx.GlobalString(flags.L2EthNodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name),
Rollup: *rollupConfig, Rollup: *rollupConfig,
Sequencer: enableSequencing, Sequencer: enableSequencing,
RPC: node.RPCConfig{ RPC: node.RPCConfig{
...@@ -53,6 +67,48 @@ func NewConfig(ctx *cli.Context) (*node.Config, error) { ...@@ -53,6 +67,48 @@ func NewConfig(ctx *cli.Context) (*node.Config, error) {
return cfg, nil return cfg, nil
} }
func NewL1EndpointConfig(ctx *cli.Context) (*node.L1EndpointConfig, error) {
return &node.L1EndpointConfig{
L1NodeAddr: ctx.GlobalString(flags.L1NodeAddr.Name),
L1TrustRPC: ctx.GlobalBool(flags.L1TrustRPC.Name),
}, nil
}
func NewL2EndpointsConfig(ctx *cli.Context, log log.Logger) (*node.L2EndpointsConfig, error) {
l2Addrs := ctx.GlobalStringSlice(flags.L2EngineAddrs.Name)
engineJWTSecrets := ctx.GlobalStringSlice(flags.L2EngineJWTSecret.Name)
var secrets [][32]byte
for i, fileName := range engineJWTSecrets {
fileName = strings.TrimSpace(fileName)
if fileName == "" {
return nil, fmt.Errorf("file-name of jwt secret %d is empty", i)
}
if data, err := os.ReadFile(fileName); err == nil {
jwtSecret := common.FromHex(strings.TrimSpace(string(data)))
if len(jwtSecret) != 32 {
return nil, fmt.Errorf("invalid jwt secret in path %s, not 32 hex-formatted bytes", fileName)
}
var secret [32]byte
copy(secret[:], jwtSecret)
secrets = append(secrets, secret)
} else {
log.Warn("Failed to read JWT secret from file, generating a new one now. Configure L2 geth with --authrpc.jwt-secret=" + fmt.Sprintf("%q", fileName))
var secret [32]byte
if _, err := io.ReadFull(rand.Reader, secret[:]); err != nil {
return nil, fmt.Errorf("failed to generate jwt secret: %v", err)
}
secrets = append(secrets, secret)
if err := os.WriteFile(fileName, []byte(hexutil.Encode(secret[:])), 0600); err != nil {
return nil, err
}
}
}
return &node.L2EndpointsConfig{
L2EngineAddrs: l2Addrs,
L2EngineJWTSecrets: secrets,
}, nil
}
func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) { func NewRollupConfig(ctx *cli.Context) (*rollup.Config, error) {
rollupConfigPath := ctx.GlobalString(flags.RollupConfig.Name) rollupConfigPath := ctx.GlobalString(flags.RollupConfig.Name)
file, err := os.Open(rollupConfigPath) file, err := os.Open(rollupConfigPath)
......
...@@ -30,6 +30,11 @@ services: ...@@ -30,6 +30,11 @@ services:
volumes: volumes:
- "l2_data:/db" - "l2_data:/db"
- ${PWD}/../.devnet/genesis-l2.json:/genesis.json - ${PWD}/../.devnet/genesis-l2.json:/genesis.json
- ${PWD}/test-jwt-secret.txt:/config/test-jwt-secret.txt
entrypoint: # pass the L2 specific flags by overriding the entry-point and adding extra arguments
- "/bin/sh"
- "/entrypoint.sh"
- "--authrpc.jwt-secret=/config/test-jwt-secret.txt"
op-node: op-node:
depends_on: depends_on:
...@@ -42,10 +47,10 @@ services: ...@@ -42,10 +47,10 @@ services:
op-node op-node
--l1=ws://l1:8546 --l1=ws://l1:8546
--l2=ws://l2:8546 --l2=ws://l2:8546
--l2.jwt-secret=/config/test-jwt-secret.txt
--sequencing.enabled --sequencing.enabled
--p2p.sequencer.key=/config/p2p-sequencer-key.txt --p2p.sequencer.key=/config/p2p-sequencer-key.txt
--rollup.config=/rollup.json --rollup.config=/rollup.json
--l2.eth=http://l2:8545
--rpc.addr=0.0.0.0 --rpc.addr=0.0.0.0
--rpc.port=8545 --rpc.port=8545
--p2p.listen.ip=0.0.0.0 --p2p.listen.ip=0.0.0.0
...@@ -59,6 +64,7 @@ services: ...@@ -59,6 +64,7 @@ services:
volumes: volumes:
- ${PWD}/p2p-sequencer-key.txt:/config/p2p-sequencer-key.txt - ${PWD}/p2p-sequencer-key.txt:/config/p2p-sequencer-key.txt
- ${PWD}/p2p-node-key.txt:/config/p2p-node-key.txt - ${PWD}/p2p-node-key.txt:/config/p2p-node-key.txt
- ${PWD}/test-jwt-secret.txt:/config/test-jwt-secret.txt
- ${PWD}/../.devnet/rollup.json:/rollup.json - ${PWD}/../.devnet/rollup.json:/rollup.json
- op_log:/op_log - op_log:/op_log
......
688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment