Commit ec268fad authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into jg/l1_info_mismatch_logging

parents 6826213f 4f0206c0
---
'@eth-optimism/fault-detector': minor
---
Updates the fault detector to support Bedrock networks.
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
🎈 Thanks for your help improving the project! We are so happy to have you! 🎈 Thanks for your help improving the project! We are so happy to have you!
**No contribution is too small and all contributions are valued.**
There are plenty of ways to contribute, in particular we appreciate support in the following areas: There are plenty of ways to contribute, in particular we appreciate support in the following areas:
- Reporting issues. For security issues see [Security policy](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md). - Reporting issues. For security issues see [Security policy](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md).
......
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
This is a utility for running a local Bedrock devnet. It is designed to replace the legacy Bash-based devnet runner as part of a progressive migration away from Bash automation. This is a utility for running a local Bedrock devnet. It is designed to replace the legacy Bash-based devnet runner as part of a progressive migration away from Bash automation.
The easiest way to invoke this script is to run `make devnet-up-deploy` from the root of this repository. Otherwise, to use this script run `python3 main.py --monorepo-path=<path to the monorepo>`. You may need to set `PYTHONPATH` to this directory if you are invoking the script from somewhere other than `bedrock-devnet`. The easiest way to invoke this script is to run `make devnet-up-deploy` from the root of this repository. Otherwise, to use this script run `python3 main.py --monorepo-dir=<path to the monorepo>`. You may need to set `PYTHONPATH` to this directory if you are invoking the script from somewhere other than `bedrock-devnet`.
\ No newline at end of file
...@@ -164,6 +164,7 @@ github.com/ethereum-optimism/op-geth v0.0.0-20221104125741-d6c1bb9a110d h1:rDRvY ...@@ -164,6 +164,7 @@ github.com/ethereum-optimism/op-geth v0.0.0-20221104125741-d6c1bb9a110d h1:rDRvY
github.com/ethereum-optimism/op-geth v0.0.0-20221104125741-d6c1bb9a110d/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo= github.com/ethereum-optimism/op-geth v0.0.0-20221104125741-d6c1bb9a110d/go.mod h1:/6CsT5Ceen2WPLI/oCA3xMcZ5sWMF/D46SjM/ayY0Oo=
github.com/ethereum-optimism/op-geth v0.0.0-20221215174217-c69b1f12761e h1:kdpBVWv7Rs/LbM8o8QyJlEBNiA2sw1GEhGyn4pkpesw= github.com/ethereum-optimism/op-geth v0.0.0-20221215174217-c69b1f12761e h1:kdpBVWv7Rs/LbM8o8QyJlEBNiA2sw1GEhGyn4pkpesw=
github.com/ethereum-optimism/op-geth v0.0.0-20221215174217-c69b1f12761e/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221215174217-c69b1f12761e/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-signer v0.1.1/go.mod h1:HK+9do1IJmDr9aEc44ECweYbCLDfGGYO7PBwTqDNrqI=
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
......
...@@ -5,6 +5,8 @@ import ( ...@@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -120,6 +122,9 @@ type MigrationData struct { ...@@ -120,6 +122,9 @@ type MigrationData struct {
func (m *MigrationData) ToWithdrawals() ([]*crossdomain.LegacyWithdrawal, error) { func (m *MigrationData) ToWithdrawals() ([]*crossdomain.LegacyWithdrawal, error) {
messages := make([]*crossdomain.LegacyWithdrawal, 0) messages := make([]*crossdomain.LegacyWithdrawal, 0)
for _, msg := range m.OvmMessages { for _, msg := range m.OvmMessages {
if msg.Who != predeploys.L2CrossDomainMessengerAddr {
continue
}
wd, err := msg.ToLegacyWithdrawal() wd, err := msg.ToLegacyWithdrawal()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -130,6 +135,9 @@ func (m *MigrationData) ToWithdrawals() ([]*crossdomain.LegacyWithdrawal, error) ...@@ -130,6 +135,9 @@ func (m *MigrationData) ToWithdrawals() ([]*crossdomain.LegacyWithdrawal, error)
} }
} }
for _, msg := range m.EvmMessages { for _, msg := range m.EvmMessages {
if msg.Who != predeploys.L2CrossDomainMessengerAddr {
continue
}
wd, err := msg.ToLegacyWithdrawal() wd, err := msg.ToLegacyWithdrawal()
if err != nil { if err != nil {
return nil, err return nil, err
......
...@@ -3,7 +3,6 @@ package eth ...@@ -3,7 +3,6 @@ package eth
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -52,12 +51,16 @@ func (res *AccountResult) Verify(stateRoot common.Hash) error { ...@@ -52,12 +51,16 @@ func (res *AccountResult) Verify(stateRoot common.Hash) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to verify storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key, path, res.StorageHash, err) return fmt.Errorf("failed to verify storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key, path, res.StorageHash, err)
} }
if !bytes.Equal(val, val) { comparison, err := rlp.EncodeToBytes(entry.Value.ToInt().Bytes())
if err != nil {
return fmt.Errorf("failed to encode storage value %d with key %s (path %x) in storage trie %s: %w", i, entry.Key, path, res.StorageHash, err)
}
if !bytes.Equal(val, comparison) {
return fmt.Errorf("value %d in storage proof does not match proven value at key %s (path %x)", i, entry.Key, path) return fmt.Errorf("value %d in storage proof does not match proven value at key %s (path %x)", i, entry.Key, path)
} }
} }
accountClaimed := []any{uint64(res.Nonce), (*big.Int)(res.Balance).Bytes(), res.StorageHash, res.CodeHash} accountClaimed := []any{uint64(res.Nonce), res.Balance.ToInt().Bytes(), res.StorageHash, res.CodeHash}
accountClaimedValue, err := rlp.EncodeToBytes(accountClaimed) accountClaimedValue, err := rlp.EncodeToBytes(accountClaimed)
if err != nil { if err != nil {
return fmt.Errorf("failed to encode account from retrieved values: %w", err) return fmt.Errorf("failed to encode account from retrieved values: %w", err)
......
This diff is collapsed.
...@@ -4,8 +4,11 @@ import ( ...@@ -4,8 +4,11 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"sync"
"time" "time"
"github.com/libp2p/go-libp2p/core/network"
lconf "github.com/libp2p/go-libp2p/config" lconf "github.com/libp2p/go-libp2p/config"
"github.com/libp2p/go-libp2p/core/connmgr" "github.com/libp2p/go-libp2p/core/connmgr"
"github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/host"
...@@ -34,6 +37,11 @@ type extraHost struct { ...@@ -34,6 +37,11 @@ type extraHost struct {
host.Host host.Host
gater ConnectionGater gater ConnectionGater
connMgr connmgr.ConnManager connMgr connmgr.ConnManager
log log.Logger
staticPeers []*peer.AddrInfo
quitC chan struct{}
} }
func (e *extraHost) ConnectionGater() ConnectionGater { func (e *extraHost) ConnectionGater() ConnectionGater {
...@@ -44,6 +52,73 @@ func (e *extraHost) ConnectionManager() connmgr.ConnManager { ...@@ -44,6 +52,73 @@ func (e *extraHost) ConnectionManager() connmgr.ConnManager {
return e.connMgr return e.connMgr
} }
func (e *extraHost) Close() error {
close(e.quitC)
return e.Host.Close()
}
func (e *extraHost) initStaticPeers() {
for _, addr := range e.staticPeers {
e.Peerstore().AddAddrs(addr.ID, addr.Addrs, time.Hour*24*7)
// We protect the peer, so the connection manager doesn't decide to prune it.
// We tag it with "static" so other protects/unprotects with different tags don't affect this protection.
e.connMgr.Protect(addr.ID, "static")
// Try to dial the node in the background
go func(addr *peer.AddrInfo) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
if err := e.dialStaticPeer(ctx, addr); err != nil {
e.log.Warn("error dialing static peer", "peer", addr.ID, "err", err)
}
}(addr)
}
}
func (e *extraHost) dialStaticPeer(ctx context.Context, addr *peer.AddrInfo) error {
e.log.Info("dialing static peer", "peer", addr.ID, "addrs", addr.Addrs)
if _, err := e.Network().DialPeer(ctx, addr.ID); err != nil {
return err
}
return nil
}
func (e *extraHost) monitorStaticPeers() {
tick := time.NewTicker(time.Minute)
defer tick.Stop()
for {
select {
case <-tick.C:
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
var wg sync.WaitGroup
e.log.Debug("polling static peers", "peers", len(e.staticPeers))
for _, addr := range e.staticPeers {
connectedness := e.Network().Connectedness(addr.ID)
e.log.Trace("static peer connectedness", "peer", addr.ID, "connectedness", connectedness)
if connectedness == network.Connected {
continue
}
wg.Add(1)
go func(addr *peer.AddrInfo) {
e.log.Warn("static peer disconnected, reconnecting", "peer", addr.ID)
if err := e.dialStaticPeer(ctx, addr); err != nil {
e.log.Warn("error reconnecting to static peer", "peer", addr.ID, "err", err)
}
wg.Done()
}(addr)
}
wg.Wait()
cancel()
case <-e.quitC:
return
}
}
}
var _ ExtraHostFeatures = (*extraHost)(nil) var _ ExtraHostFeatures = (*extraHost)(nil)
func (conf *Config) Host(log log.Logger, reporter metrics.Reporter) (host.Host, error) { func (conf *Config) Host(log log.Logger, reporter metrics.Reporter) (host.Host, error) {
...@@ -142,26 +217,28 @@ func (conf *Config) Host(log log.Logger, reporter metrics.Reporter) (host.Host, ...@@ -142,26 +217,28 @@ func (conf *Config) Host(log log.Logger, reporter metrics.Reporter) (host.Host,
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, peerAddr := range conf.StaticPeers {
staticPeers := make([]*peer.AddrInfo, len(conf.StaticPeers))
for i, peerAddr := range conf.StaticPeers {
addr, err := peer.AddrInfoFromP2pAddr(peerAddr) addr, err := peer.AddrInfoFromP2pAddr(peerAddr)
if err != nil { if err != nil {
return nil, fmt.Errorf("bad peer address: %w", err) return nil, fmt.Errorf("bad peer address: %w", err)
} }
h.Peerstore().AddAddrs(addr.ID, addr.Addrs, time.Hour*24*7) staticPeers[i] = addr
// We protect the peer, so the connection manager doesn't decide to prune it.
// We tag it with "static" so other protects/unprotects with different tags don't affect this protection.
connMngr.Protect(addr.ID, "static")
// Try to dial the node in the background
go func() {
log.Info("Dialing static peer", "peer", addr.ID, "addrs", addr.Addrs)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
if _, err := h.Network().DialPeer(ctx, addr.ID); err != nil {
log.Warn("Failed to dial static peer", "peer", addr.ID, "addrs", addr.Addrs)
}
}()
} }
out := &extraHost{Host: h, connMgr: connMngr}
out := &extraHost{
Host: h,
connMgr: connMngr,
log: log,
staticPeers: staticPeers,
quitC: make(chan struct{}),
}
out.initStaticPeers()
if len(conf.StaticPeers) > 0 {
go out.monitorStaticPeers()
}
// Only add the connection gater if it offers the full interface we're looking for. // Only add the connection gater if it offers the full interface we're looking for.
if g, ok := connGtr.(ConnectionGater); ok { if g, ok := connGtr.(ConnectionGater); ok {
out.gater = g out.gater = g
......
...@@ -5,8 +5,9 @@ go 1.18 ...@@ -5,8 +5,9 @@ go 1.18
require ( require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-node v0.10.12 github.com/ethereum-optimism/optimism/op-node v0.10.12
github.com/ethereum-optimism/optimism/op-signer v0.1.0 github.com/ethereum-optimism/optimism/op-signer v0.1.1
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/fsnotify/fsnotify v1.6.0
github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_golang v1.13.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
...@@ -25,9 +26,7 @@ require ( ...@@ -25,9 +26,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dyson/certman v0.3.0 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-kit/kit v0.10.0 // indirect github.com/go-kit/kit v0.10.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
......
...@@ -123,8 +123,6 @@ github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0 ...@@ -123,8 +123,6 @@ github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dyson/certman v0.3.0 h1:S7WCUim5faT/OiBhiY3u5cMaiC9MNKiA+8PJDXLaIYQ=
github.com/dyson/certman v0.3.0/go.mod h1:RMWlyA9op6D9SxOBRRX3sxnParehv9gf52WWUJPd1JA=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
...@@ -141,8 +139,8 @@ github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjB ...@@ -141,8 +139,8 @@ github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjB
github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/optimism/op-node v0.10.12 h1:yOxMThwwz1rEDDM0xTjS+6jqJwgKRtrYM6h4Pdf0zno= github.com/ethereum-optimism/optimism/op-node v0.10.12 h1:yOxMThwwz1rEDDM0xTjS+6jqJwgKRtrYM6h4Pdf0zno=
github.com/ethereum-optimism/optimism/op-node v0.10.12/go.mod h1:z+DiFb82Vnn5zM3VEwc2OXK2V/JBg6MLg7ejTbsxye8= github.com/ethereum-optimism/optimism/op-node v0.10.12/go.mod h1:z+DiFb82Vnn5zM3VEwc2OXK2V/JBg6MLg7ejTbsxye8=
github.com/ethereum-optimism/optimism/op-signer v0.1.0 h1:wH44Deai43YQWO0pEd44pDm3BahdAtSmrOHKiPvTB8Y= github.com/ethereum-optimism/optimism/op-signer v0.1.1 h1:Ts6aWd5/nTQ8ZRFZhpOrrcWpnVmdp4fRfff1mvybnGs=
github.com/ethereum-optimism/optimism/op-signer v0.1.0/go.mod h1:u8sN6X/c20pP9F1Ey7jH3fi19D08Y+T9ep3PGJfdyi8= github.com/ethereum-optimism/optimism/op-signer v0.1.1/go.mod h1:HK+9do1IJmDr9aEc44ECweYbCLDfGGYO7PBwTqDNrqI=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ=
github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
......
...@@ -183,7 +183,7 @@ func (b *Server) Start() error { ...@@ -183,7 +183,7 @@ func (b *Server) Start() error {
errCh := make(chan error, 1) errCh := make(chan error, 1)
go func() { go func() {
if b.tls != nil { if b.tls != nil {
if err := b.httpServer.ListenAndServeTLS(b.tls.CLIConfig.TLSCert, b.tls.CLIConfig.TLSKey); err != nil { if err := b.httpServer.ListenAndServeTLS("", ""); err != nil {
errCh <- err errCh <- err
} }
} else { } else {
......
// Package certman provides live reloading of the certificate and key
// files used by the standard library http.Server. It defines a type,
// certMan, with methods watching and getting the files.
// Only valid certificate and key pairs are loaded and an optional
// logger can be passed to certman for logging providing it implements
// the logger interface.
package certman
import (
"crypto/tls"
"fmt"
"path"
"path/filepath"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/fsnotify/fsnotify"
)
// A CertMan represents a certificate manager able to watch certificate
// and key pairs for changes.
type CertMan struct {
mu sync.RWMutex
certFile string
keyFile string
keyPair *tls.Certificate
watcher *fsnotify.Watcher
watching chan bool
log log.Logger
}
// New creates a new certMan. The certFile and the keyFile
// are both paths to the location of the files. Relative and
// absolute paths are accepted.
func New(logger log.Logger, certFile, keyFile string) (*CertMan, error) {
var err error
certFile, err = filepath.Abs(certFile)
if err != nil {
return nil, err
}
keyFile, err = filepath.Abs(keyFile)
if err != nil {
return nil, err
}
cm := &CertMan{
mu: sync.RWMutex{},
certFile: certFile,
keyFile: keyFile,
log: logger,
}
return cm, nil
}
// Watch starts watching for changes to the certificate
// and key files. On any change the certificate and key
// are reloaded. If there is an issue the load will fail
// and the old (if any) certificates and keys will continue
// to be used.
func (cm *CertMan) Watch() error {
var err error
if cm.watcher, err = fsnotify.NewWatcher(); err != nil {
return fmt.Errorf("certman: can't create watcher: %w", err)
}
certPath := path.Dir(cm.certFile)
keyPath := path.Dir(cm.keyFile)
if err = cm.watcher.Add(certPath); err != nil {
return fmt.Errorf("certman: can't watch %s: %w", certPath, err)
}
if keyPath != certPath {
if err = cm.watcher.Add(keyPath); err != nil {
return fmt.Errorf("certman: can't watch %s: %w", certPath, err)
}
}
if err := cm.load(); err != nil {
cm.log.Error("certman: can't load cert or key file", "err", err)
}
cm.log.Info("certman: watching for cert and key change")
cm.watching = make(chan bool)
go cm.run()
return nil
}
func (cm *CertMan) load() error {
keyPair, err := tls.LoadX509KeyPair(cm.certFile, cm.keyFile)
if err == nil {
cm.mu.Lock()
cm.keyPair = &keyPair
cm.mu.Unlock()
cm.log.Info("certman: certificate and key loaded")
}
return err
}
func (cm *CertMan) run() {
cm.log.Info("certman: running")
ticker := time.NewTicker(2 * time.Second)
files := []string{cm.certFile, cm.keyFile}
reload := time.Time{}
loop:
for {
select {
case <-cm.watching:
cm.log.Info("watching triggered; break loop")
break loop
case <-ticker.C:
if !reload.IsZero() && time.Now().After(reload) {
reload = time.Time{}
cm.log.Info("certman: reloading")
if err := cm.load(); err != nil {
cm.log.Error("certman: can't load cert or key file", "err", err)
}
}
case event := <-cm.watcher.Events:
for _, f := range files {
if event.Name == f ||
strings.HasSuffix(event.Name, "/..data") { // kubernetes secrets mount
// we wait a couple seconds in case the cert and key don't update atomically
cm.log.Info(fmt.Sprintf("%s was modified, queue reload", f))
reload = time.Now().Add(2 * time.Second)
}
}
case err := <-cm.watcher.Errors:
cm.log.Error("certman: error watching files", "err", err)
}
}
cm.log.Info("certman: stopped watching")
cm.watcher.Close()
ticker.Stop()
}
// GetCertificate returns the loaded certificate for use by
// the GetCertificate field in tls.Config.
func (cm *CertMan) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cm.mu.RLock()
defer cm.mu.RUnlock()
return cm.keyPair, nil
}
// GetClientCertificate returns the loaded certificate for use by
// the GetClientCertificate field in tls.Config.
func (cm *CertMan) GetClientCertificate(hello *tls.CertificateRequestInfo) (*tls.Certificate, error) {
cm.mu.RLock()
defer cm.mu.RUnlock()
return cm.keyPair, nil
}
// Stop tells certMan to stop watching for changes to the
// certificate and key files.
func (cm *CertMan) Stop() {
cm.watching <- false
}
// Copyright 2017 Dyson Simmons. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
package certman_test
import (
"crypto/tls"
"reflect"
"strings"
"testing"
"github.com/ethereum-optimism/optimism/op-service/tls/certman"
"github.com/ethereum/go-ethereum/log"
)
func TestValidPair(t *testing.T) {
cm, err := certman.New(log.Root(), "./testdata/server1.crt", "./testdata/server1.key")
if err != nil {
t.Errorf("could not create certman: %v", err)
}
if err := cm.Watch(); err != nil {
t.Errorf("could not watch files: %v", err)
}
}
func TestInvalidPair(t *testing.T) {
cm, err := certman.New(log.Root(), "./testdata/server1.crt", "./testdata/server2.key")
if err != nil {
t.Errorf("could not create certman: %v", err)
}
if err := cm.Watch(); err != nil {
t.Errorf("could not watch files: %v", err)
}
}
func TestCertificateNotFound(t *testing.T) {
cm, err := certman.New(log.Root(), "./testdata/nothere.crt", "./testdata/server2.key")
if err != nil {
t.Errorf("could not create certman: %v", err)
}
if err := cm.Watch(); err != nil {
if !strings.HasPrefix(err.Error(), "certman: can't watch cert file: ") {
t.Errorf("unexpected watch error: %v", err)
}
}
}
func TestKeyNotFound(t *testing.T) {
cm, err := certman.New(log.Root(), "./testdata/server1.crt", "./testdata/nothere.key")
if err != nil {
t.Errorf("could not create certman: %v", err)
}
if err := cm.Watch(); err != nil {
if !strings.HasPrefix(err.Error(), "certman: can't watch key file: ") {
t.Errorf("unexpected watch error: %v", err)
}
}
}
func TestGetCertificate(t *testing.T) {
cm, err := certman.New(log.Root(), "./testdata/server1.crt", "./testdata/server1.key")
if err != nil {
t.Errorf("could not create certman: %v", err)
}
if err := cm.Watch(); err != nil {
t.Errorf("could not watch files: %v", err)
}
hello := &tls.ClientHelloInfo{}
cmCert, err := cm.GetCertificate(hello)
if err != nil {
t.Error("could not get certman certificate")
}
expectedCert, _ := tls.LoadX509KeyPair("./testdata/server1.crt", "./testdata/server1.key")
if err != nil {
t.Errorf("could not load certificate and key files to test: %v", err)
}
if !reflect.DeepEqual(cmCert.Certificate, expectedCert.Certificate) {
t.Errorf("certman certificate doesn't match expected certificate")
}
}
Test certificates generated with the following command:
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -sha256 -keyout server.key -out server.crt
\ No newline at end of file
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIJAJiO53P0WQTzMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
Q29tcGFueSBMdGQwHhcNMTcwODA0MTAzMzIyWhcNMTgwODA0MTAzMzIyWjBCMQsw
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
sYk+rsOElxPB5gPH8Vg/RFzdBzDJesI4VCXYcSCl0Ek/lO4AAKjgRssjmZDWWXcZ
3/Z1UlO4cRZy6zaSllFM07WXX3SQ54iBy3lS6NgBcHiWVfKrtXz/dQrswSPBnGEH
PVBmd+xHcEv4wqYnYYVtJuIcGY1P/i14h1ogpEW22cnCVYJwqmjxrD4UQ8vSeTf9
YwVCbKXZ5T+eiNhfQOkCG0rClqvfvZiMgBBlLYvJrc9bELsqIbNGVD4CrkYjZGEc
ToMrb/wZhqLxlcs/iXKI0/579+9vvc44/pi2MFU0nvhoSnEgopKKPy83SwBogpiH
+mDcVgMiEwbXHUyNAFbz/wIDAQABo1MwUTAdBgNVHQ4EFgQULtTUM8bHdg4jhRT3
9piSLGyTSsUwHwYDVR0jBBgwFoAULtTUM8bHdg4jhRT39piSLGyTSsUwDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAchsCFqaRslfgR0r0CoOJsX/H
gEfqYHaL8/yJtUUqCRgyN5VtP1Cxzet8GVsAJaIKimeUCXshhaq94JQRZqxMFxJD
sD9nfWVByorkSO9tmq+1FCWRzfau1AFsJxR6J1hpIbRfcfoi9HG4QnoJwULK/2yh
DEXwnmgsCHTcNnj2U9F2vMLydHEKtmtMNe/S0Z7fHw1qlmqXgXmN5a/KTEPVBQm8
eM6AhNlzrMqUPc8IiiZ32eAdgR2eAuhLnTCdnHwnHafpep72hSjwoUXsDjWNA/xW
/lHx4jVL8muLP5G9YHZWzJwMijLqIlnryyyi+IfKAn4ANW/k44Lxm5hmRXh4UA==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxiT6uw4SXE8Hm
A8fxWD9EXN0HMMl6wjhUJdhxIKXQST+U7gAAqOBGyyOZkNZZdxnf9nVSU7hxFnLr
NpKWUUzTtZdfdJDniIHLeVLo2AFweJZV8qu1fP91CuzBI8GcYQc9UGZ37EdwS/jC
pidhhW0m4hwZjU/+LXiHWiCkRbbZycJVgnCqaPGsPhRDy9J5N/1jBUJspdnlP56I
2F9A6QIbSsKWq9+9mIyAEGUti8mtz1sQuyohs0ZUPgKuRiNkYRxOgytv/BmGovGV
yz+JcojT/nv372+9zjj+mLYwVTSe+GhKcSCikoo/LzdLAGiCmIf6YNxWAyITBtcd
TI0AVvP/AgMBAAECggEATZpnau8N+xfozsliUa24YgKRnv4FZAKXqrisRq71q/kI
sOnj2GX5Oxi6o/q6p3q3Nb2+hNERs2UTsJs3MjuxcG1VEKWcXYi+65lJ03vwDSC4
3jLoObm81IWE/dvKWrfS+Us2rz757y1WPIdyeV9gWfnGPKkXiUyI/ek4kXXjuoiL
WqYeWlhe2mGVxQs5DfxBigvN0th2yfmeQJTLXpoJkCDmSTS3vnvKJfiH/mflLm/B
51lgKEziqvu53JmgYmhswsgRnLtyZ4yjksMroJYPKCTA3RbDud+J6SO01ok+hiNy
QcO/j+1zlFQlWqAE4BSB1IXbW/6ZQAdszPpX9l318QKBgQDaW+nq0FH7djne5838
sqBO7pHGde2j6To8PuGIoskH86p8X4dgutNiingGnYoqK2qTKVTudkdu2ahwGI3P
S3dflwcyJwAvTbjc3ATSoez+P0oUC62DQciMfqaCR+WYAoAR8cdHtSoMyMvLVhSs
1h05AoalTybuT+spscV8hFBdlwKBgQDQI9VxKPt9VwhKf0W6K2IKbNxvHcu0UFvZ
pCZKdxQLr7tP3+oo9fWn6R0sSX/aV+km9YQsn6keX0e4ayJHNfU6UwzNSMKWQZvs
JF7OBuZnpZYX/1lSIUDOw8I29kcVtqCHjpgozbFhJSQzJDDcPvRFmkPBI74sgA1z
xuH7xcc52QKBgDPX7snZfB2ADG1oC/gbUQRskB/Wj/2CuljjdRjDzYcdyzSMWdAV
i2qyBZ1MeilY9YzLG2cingMrmlpC+ihleooviX3W1Kxmf6Wwd1SrLWGQFT59J00q
qTryNwZnm5NjxJR+GxpjYQB4DCrS3UXL8FRAzUcia9PZFbRoiMLvh0UxAoGBAIjZ
prL6cTBeEvN4bw4TDCkynlTo0FDELUASL6LyXFm6t3uzC7DW1ygJm8bMpKWY+5FE
CB2W9IkluHBG8IjFr3Ejvd0To+1LQgundjYcT02CkAdDOyVG++d2yrF8iAx8wVuf
o+fgJmprEzwU5ZNKSS2iWj4ZFCcKIs4my9rQlUcxAoGBAMiqpes+Ka3ofb8sxpzj
xAB2oOub0X93a3xQ4xv5VLDysA7+rv1CDwuYKlOKTjGFwg3CwvbnE2EKkKI9BNgh
UHAdpVXWOck5ubD5EB6vp7xWvzu6AA4KyhyRJaRL1GdSLFl9JDg5YvHskVxumt+L
/GgTbnT/P3+JOhCdc0H0XhGq
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIJALkZ2SqlmTT6MA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
Q29tcGFueSBMdGQwHhcNMTcwODA1MTU1MjIyWhcNMTgwODA1MTU1MjIyWjBCMQsw
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
p3vZAGYjBC4kPf6jqh6NYCWMVe8Db5IOHYECtGxBVOhS5DnuXPm0LUT9UCoUfPeB
mU/1LZk41J70tpwl2IHhSIFqbG+fhNlRlUAA9CIHeRa2RVkFxH7I7xt2FhwU2WsX
vi+GEiOKoZ2mC2cuZ5t8zWKinW+Hd8pL270OboQ980gThfF2ugzoXlyYVa+MrE5z
wjLa3lQoKkqzo54/gKwK+KycXYoljmf2Q0++sSsDAqZlFIYknfct4+ST4TvGRqSw
nPxQcJ33MXDpbnGcTBPXqWxXhvocd+QrOF4Rn7fVRLWZqYoFchSnt4R3qsoPuB8q
WdZn+McT6qqgLUUtRy72PQIDAQABo1MwUTAdBgNVHQ4EFgQU83D1oNnMvjHU5q3E
KIJUtrHMh94wHwYDVR0jBBgwFoAU83D1oNnMvjHU5q3EKIJUtrHMh94wDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEADByFIBVqK7b2ZJVqWzQlY7pC
DbR4bh+z8hwP/VU/JjlR0IdFj1tWLLPq5hmCWScntsmI8AvThCTbZgwogptvbeuA
n2QRuSb3LMZBtq22XdDTPCdM4CQblinbXR3ePmz2ZpF7xxQMz8/IafeoZaaF2w4l
PfHrf4ID89dMOq13MAhiSFmKeyElx2iGGnhsGQzhcoTbhDCW/HK3Zr/BN+uFyBgr
PcV0H+VLOS7/XVnz5wbIiqTfnx0NhzQzw91zY2dXVOvNadD6QpNN5Abwxo9x16TP
SfqKsjPvxk06wvchNAkopBLsqjLMovgKYMMbolVmFeeZVvNEnY19RoBuneJncA==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCne9kAZiMELiQ9
/qOqHo1gJYxV7wNvkg4dgQK0bEFU6FLkOe5c+bQtRP1QKhR894GZT/UtmTjUnvS2
nCXYgeFIgWpsb5+E2VGVQAD0Igd5FrZFWQXEfsjvG3YWHBTZaxe+L4YSI4qhnaYL
Zy5nm3zNYqKdb4d3ykvbvQ5uhD3zSBOF8Xa6DOheXJhVr4ysTnPCMtreVCgqSrOj
nj+ArAr4rJxdiiWOZ/ZDT76xKwMCpmUUhiSd9y3j5JPhO8ZGpLCc/FBwnfcxcOlu
cZxME9epbFeG+hx35Cs4XhGft9VEtZmpigVyFKe3hHeqyg+4HypZ1mf4xxPqqqAt
RS1HLvY9AgMBAAECggEAGE5R7Mvl0wp7OgAFcn/ilox8dFAumHeC0udRJCv9wzvA
I90Aab/XVSaI+KRSutwUk9JVy5tL8xdqfkHlACnBLwuRDVGZvebn/xf9y3BQ01Ln
euLzglPAB2td1NGYeQEgvfoZo/JCgTfmzAraYjDfiNMCtIRmDY1vOuGSAZnxf6e/
C7LB7XdKdGijUgjvHOv82DUbdt5RD/RL9chvk+i9esdjJIxS3YaSOHclV7FSjRqe
f5e9MrPKuSdijYHqHOcxX3hztOrLjL7J4gVPRvjGaWbRmdU79m2oFlwfGlLTU+w6
zlzIu5FqKD375rchkOKw3W8uSyPCRsdnmZxT+CZKIQKBgQDPHiXmGWlTBdcBcgaK
4PIZltAZbA+5MdDDJPDZp9b5gS8/0mXoqOvvC2PeOkrv96l3nrVoTbfOxxutftjO
Xh2OfplpNkxABR8WqY7mKaHR4aphlq1QtxcCcOlmAbmo9Eo03Vhir7q913upgEnY
jzEoX9b41JEJBTR6Mfbfx3b6+QKBgQDPAw2ZuBqiyPmozCDqKnP2yiOJz6wkunqI
rUkZLizN+U9kByiq/NzoDGo6BNchs2XpVhoIt3ixC/xzzhDUkn7V7ZCn8BkEdj/T
jgZyupMJ1wNQK9Du3GaYPRnQ1I3OrfA0oOx1ZVoOOKOEDAEAfhzSW8GWicO3305o
6/4Tq4gCZQKBgQC18vEuS+KX+chgz6/pryVfz3ou6xyA/786v6gKPYUAGTnN4mJ+
Wm8xx5rLLgCJANPSbw1EfQndUFMDPizuVgW3GYZhxD6F+znNadVMYwRyYcGRC5Jk
FwPStCiF4TwdrcXG3TB5OZFelv9e74FwCpMPueobHHnxJ65rLpuHCS5/2QKBgBDF
AYwLUvUO7NKUvrHZgI1kcJ6QWTSceqKpzvsgN3b0FE9ZGR1I4KhXoR9UFw1e2Amf
9PnxyvAktW24KrrdpzKzTP2dwJkQ7zi3D6SpopGwfk83TXScHB+HC5lULqyogIXy
51TXQgVW50AiLM6aaMFNt4/3VwiFKXfsbievxJPVAoGADlJV011pkxlF2wZxpvDT
RGIWCtT3G3GksNPxTkLDsuBnJ38YE0p95MivBP3Q7kdVhfgOzG1AnWMeOUzcdzNM
hXKW9EcyovPYrHpAcdwMXOj+WuU74l2tHLPJQ5+4zTKAc/4ZJ7TKMjZ21qRjqGHQ
dxddYOr2kCOIFNreAaKD9Po=
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIJAJiO53P0WQTzMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
Q29tcGFueSBMdGQwHhcNMTcwODA0MTAzMzIyWhcNMTgwODA0MTAzMzIyWjBCMQsw
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
sYk+rsOElxPB5gPH8Vg/RFzdBzDJesI4VCXYcSCl0Ek/lO4AAKjgRssjmZDWWXcZ
3/Z1UlO4cRZy6zaSllFM07WXX3SQ54iBy3lS6NgBcHiWVfKrtXz/dQrswSPBnGEH
PVBmd+xHcEv4wqYnYYVtJuIcGY1P/i14h1ogpEW22cnCVYJwqmjxrD4UQ8vSeTf9
YwVCbKXZ5T+eiNhfQOkCG0rClqvfvZiMgBBlLYvJrc9bELsqIbNGVD4CrkYjZGEc
ToMrb/wZhqLxlcs/iXKI0/579+9vvc44/pi2MFU0nvhoSnEgopKKPy83SwBogpiH
+mDcVgMiEwbXHUyNAFbz/wIDAQABo1MwUTAdBgNVHQ4EFgQULtTUM8bHdg4jhRT3
9piSLGyTSsUwHwYDVR0jBBgwFoAULtTUM8bHdg4jhRT39piSLGyTSsUwDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAchsCFqaRslfgR0r0CoOJsX/H
gEfqYHaL8/yJtUUqCRgyN5VtP1Cxzet8GVsAJaIKimeUCXshhaq94JQRZqxMFxJD
sD9nfWVByorkSO9tmq+1FCWRzfau1AFsJxR6J1hpIbRfcfoi9HG4QnoJwULK/2yh
DEXwnmgsCHTcNnj2U9F2vMLydHEKtmtMNe/S0Z7fHw1qlmqXgXmN5a/KTEPVBQm8
eM6AhNlzrMqUPc8IiiZ32eAdgR2eAuhLnTCdnHwnHafpep72hSjwoUXsDjWNA/xW
/lHx4jVL8muLP5G9YHZWzJwMijLqIlnryyyi+IfKAn4ANW/k44Lxm5hmRXh4UA==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxiT6uw4SXE8Hm
A8fxWD9EXN0HMMl6wjhUJdhxIKXQST+U7gAAqOBGyyOZkNZZdxnf9nVSU7hxFnLr
NpKWUUzTtZdfdJDniIHLeVLo2AFweJZV8qu1fP91CuzBI8GcYQc9UGZ37EdwS/jC
pidhhW0m4hwZjU/+LXiHWiCkRbbZycJVgnCqaPGsPhRDy9J5N/1jBUJspdnlP56I
2F9A6QIbSsKWq9+9mIyAEGUti8mtz1sQuyohs0ZUPgKuRiNkYRxOgytv/BmGovGV
yz+JcojT/nv372+9zjj+mLYwVTSe+GhKcSCikoo/LzdLAGiCmIf6YNxWAyITBtcd
TI0AVvP/AgMBAAECggEATZpnau8N+xfozsliUa24YgKRnv4FZAKXqrisRq71q/kI
sOnj2GX5Oxi6o/q6p3q3Nb2+hNERs2UTsJs3MjuxcG1VEKWcXYi+65lJ03vwDSC4
3jLoObm81IWE/dvKWrfS+Us2rz757y1WPIdyeV9gWfnGPKkXiUyI/ek4kXXjuoiL
WqYeWlhe2mGVxQs5DfxBigvN0th2yfmeQJTLXpoJkCDmSTS3vnvKJfiH/mflLm/B
51lgKEziqvu53JmgYmhswsgRnLtyZ4yjksMroJYPKCTA3RbDud+J6SO01ok+hiNy
QcO/j+1zlFQlWqAE4BSB1IXbW/6ZQAdszPpX9l318QKBgQDaW+nq0FH7djne5838
sqBO7pHGde2j6To8PuGIoskH86p8X4dgutNiingGnYoqK2qTKVTudkdu2ahwGI3P
S3dflwcyJwAvTbjc3ATSoez+P0oUC62DQciMfqaCR+WYAoAR8cdHtSoMyMvLVhSs
1h05AoalTybuT+spscV8hFBdlwKBgQDQI9VxKPt9VwhKf0W6K2IKbNxvHcu0UFvZ
pCZKdxQLr7tP3+oo9fWn6R0sSX/aV+km9YQsn6keX0e4ayJHNfU6UwzNSMKWQZvs
JF7OBuZnpZYX/1lSIUDOw8I29kcVtqCHjpgozbFhJSQzJDDcPvRFmkPBI74sgA1z
xuH7xcc52QKBgDPX7snZfB2ADG1oC/gbUQRskB/Wj/2CuljjdRjDzYcdyzSMWdAV
i2qyBZ1MeilY9YzLG2cingMrmlpC+ihleooviX3W1Kxmf6Wwd1SrLWGQFT59J00q
qTryNwZnm5NjxJR+GxpjYQB4DCrS3UXL8FRAzUcia9PZFbRoiMLvh0UxAoGBAIjZ
prL6cTBeEvN4bw4TDCkynlTo0FDELUASL6LyXFm6t3uzC7DW1ygJm8bMpKWY+5FE
CB2W9IkluHBG8IjFr3Ejvd0To+1LQgundjYcT02CkAdDOyVG++d2yrF8iAx8wVuf
o+fgJmprEzwU5ZNKSS2iWj4ZFCcKIs4my9rQlUcxAoGBAMiqpes+Ka3ofb8sxpzj
xAB2oOub0X93a3xQ4xv5VLDysA7+rv1CDwuYKlOKTjGFwg3CwvbnE2EKkKI9BNgh
UHAdpVXWOck5ubD5EB6vp7xWvzu6AA4KyhyRJaRL1GdSLFl9JDg5YvHskVxumt+L
/GgTbnT/P3+JOhCdc0H0XhGq
-----END PRIVATE KEY-----
...@@ -10,8 +10,8 @@ import ( ...@@ -10,8 +10,8 @@ import (
"os" "os"
"time" "time"
"github.com/dyson/certman"
optls "github.com/ethereum-optimism/optimism/op-service/tls" optls "github.com/ethereum-optimism/optimism/op-service/tls"
"github.com/ethereum-optimism/optimism/op-service/tls/certman"
"github.com/ethereum/go-ethereum/common/hexutil" "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/log" "github.com/ethereum/go-ethereum/log"
...@@ -24,11 +24,6 @@ type SignerClient struct { ...@@ -24,11 +24,6 @@ type SignerClient struct {
logger log.Logger logger log.Logger
} }
// ethLogger wraps a geth style logger for certman.
type ethLogger struct{ logger log.Logger }
func (l ethLogger) Printf(format string, v ...interface{}) { l.logger.Info(fmt.Sprintf(format, v...)) }
func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConfig) (*SignerClient, error) { func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConfig) (*SignerClient, error) {
caCert, err := os.ReadFile(tlsConfig.TLSCaCert) caCert, err := os.ReadFile(tlsConfig.TLSCaCert)
if err != nil { if err != nil {
...@@ -38,8 +33,7 @@ func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConf ...@@ -38,8 +33,7 @@ func NewSignerClient(logger log.Logger, endpoint string, tlsConfig optls.CLIConf
caCertPool.AppendCertsFromPEM(caCert) caCertPool.AppendCertsFromPEM(caCert)
// certman watches for newer client certifictes and automatically reloads them // certman watches for newer client certifictes and automatically reloads them
cm, err := certman.New(tlsConfig.TLSCert, tlsConfig.TLSKey) cm, err := certman.New(logger, tlsConfig.TLSCert, tlsConfig.TLSKey)
cm.Logger(ethLogger{logger: logger})
if err != nil { if err != nil {
logger.Error("failed to read tls cert or key", "err", err) logger.Error("failed to read tls cert or key", "err", err)
return nil, err return nil, err
......
...@@ -3,8 +3,7 @@ module github.com/ethereum-optimism/optimism/op-signer ...@@ -3,8 +3,7 @@ module github.com/ethereum-optimism/optimism/op-signer
go 1.18 go 1.18
require ( require (
github.com/dyson/certman v0.3.0 github.com/ethereum-optimism/optimism/op-service v0.10.14-0.20230209182028-dceb2b4a70e2
github.com/ethereum-optimism/optimism/op-service v0.10.10
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
) )
...@@ -18,14 +17,12 @@ require ( ...@@ -18,14 +17,12 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.1-0.20211004051800-57c86be7915a // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
) )
......
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/VictoriaMetrics/fastcache v1.9.0 h1:oMwsS6c8abz98B7ytAewQ7M1ZN/Im/iwKoE1euaFvhs= github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY=
github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
...@@ -13,12 +14,10 @@ github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS3 ...@@ -13,12 +14,10 @@ github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS3
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/dyson/certman v0.3.0 h1:S7WCUim5faT/OiBhiY3u5cMaiC9MNKiA+8PJDXLaIYQ= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468 h1:7KgjBYDji5AKi42eRYI+n8Gs+ZJVilSASL3WBu82c3M=
github.com/dyson/certman v0.3.0/go.mod h1:RMWlyA9op6D9SxOBRRX3sxnParehv9gf52WWUJPd1JA= github.com/ethereum-optimism/op-geth v0.0.0-20221216190603-60b51d600468/go.mod h1:p0Yox74PhYlq1HvijrCBCD9A3cI7rXco7hT6KrQr+rY=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be h1:8TdM3M7FjZkrYeGGX9nEVtDDlZ5RiuHtc0mbi5bGKyY= github.com/ethereum-optimism/optimism/op-service v0.10.14-0.20230209182028-dceb2b4a70e2 h1:5TsmOuk87DI8QNzvWvQ/pa5+WqPC46tzE4LFy1ylhP8=
github.com/ethereum-optimism/op-geth v0.0.0-20221104231810-30db39cae2be/go.mod h1:1g9UmZgEINqvYfXmWOUCRJX9fxegeOHudVkLCRAXO5Y= github.com/ethereum-optimism/optimism/op-service v0.10.14-0.20230209182028-dceb2b4a70e2/go.mod h1:+8ZWRn5nW1ROAqOqmVhf+u8g0UKRuegVMSxhRgWJSq4=
github.com/ethereum-optimism/optimism/op-service v0.10.10 h1:B5mGpATX6zPkDABoh6smCjh6Z5mA2KWh71MD1i6T5ww=
github.com/ethereum-optimism/optimism/op-service v0.10.10/go.mod h1:wbtHqi1fv00B3agj7a2zdP3OFanEfGZ23zPgGgFCF/c=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
...@@ -32,12 +31,10 @@ github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gt ...@@ -32,12 +31,10 @@ github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gt
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic=
github.com/rivo/uniseg v0.2.1-0.20211004051800-57c86be7915a h1:s7GrsqeorVkFR1vGmQ6WVL9nup0eyQCC+YVUeSQLH/Q= github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
github.com/rivo/uniseg v0.2.1-0.20211004051800-57c86be7915a/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
...@@ -48,16 +45,18 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs ...@@ -48,16 +45,18 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A=
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4=
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
......
...@@ -395,3 +395,5 @@ SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10545 ...@@ -395,3 +395,5 @@ SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10545
SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10532) SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10532)
SystemConfig_Setters_TestFail:test_setGasLimit_notOwner_reverts() (gas: 10636) SystemConfig_Setters_TestFail:test_setGasLimit_notOwner_reverts() (gas: 10636)
SystemConfig_Setters_TestFail:test_setUnsafeBlockSigner_notOwner_reverts() (gas: 10638) SystemConfig_Setters_TestFail:test_setUnsafeBlockSigner_notOwner_reverts() (gas: 10638)
TransferOnionTest:test_constructor_succeeds() (gas: 564833)
TransferOnionTest:test_unwrap_succeeds() (gas: 724936)
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title TransferOnion
* @notice TransferOnion is a hash onion for distributing tokens. The shell commits
* to an ordered list of the token transfers and can be permissionlessly
* unwrapped in order. The SENDER must `approve` this contract as
* `transferFrom` is used to move the token balances.
*/
contract TransferOnion is ReentrancyGuard {
using SafeERC20 for ERC20;
/**
* @notice Struct representing a layer of the onion.
*/
struct Layer {
address recipient;
uint256 amount;
bytes32 shell;
}
/**
* @notice Address of the token to distribute.
*/
ERC20 public immutable TOKEN;
/**
* @notice Address of the account to distribute tokens from.
*/
address public immutable SENDER;
/**
* @notice Current shell hash.
*/
bytes32 public shell;
/**
* @param _token Address of the token to distribute.
* @param _sender Address of the sender to distribute from.
* @param _shell Initial shell of the onion.
*/
constructor(
ERC20 _token,
address _sender,
bytes32 _shell
) {
TOKEN = _token;
SENDER = _sender;
shell = _shell;
}
/**
* @notice Peels layers from the onion and distributes tokens.
*
* @param _layers Array of onion layers to peel.
*/
function peel(Layer[] memory _layers) public nonReentrant {
bytes32 tempShell = shell;
uint256 length = _layers.length;
for (uint256 i = 0; i < length; ) {
Layer memory layer = _layers[i];
// Confirm that the onion layer is correct.
require(
keccak256(abi.encode(layer.recipient, layer.amount, layer.shell)) == tempShell,
"TransferOnion: what are you doing in my swamp?"
);
// Update the onion layer.
tempShell = layer.shell;
// Transfer the tokens.
TOKEN.safeTransferFrom(SENDER, layer.recipient, layer.amount);
// Unchecked increment to save some gas.
unchecked {
++i;
}
}
shell = tempShell;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { TransferOnion } from "../periphery/TransferOnion.sol";
/**
* @title TransferOnionTest
* @notice Test coverage of TransferOnion
*/
contract TransferOnionTest is Test {
/**
* @notice TransferOnion
*/
TransferOnion internal onion;
/**
* @notice token constructor arg
*/
address internal _token;
/**
* @notice sender constructor arg
*/
address internal _sender;
/**
* @notice Sets up addresses, deploys contracts and funds the owner.
*/
function setUp() public {
ERC20 token = new ERC20("Token", "TKN");
_token = address(token);
_sender = makeAddr("sender");
}
/**
* @notice Deploy the TransferOnion with a dummy shell
*/
function _deploy() public {
_deploy(bytes32(0));
}
/**
* @notice Deploy the TransferOnion with a specific shell
*/
function _deploy(bytes32 _shell) public {
onion = new TransferOnion({ _token: ERC20(_token), _sender: _sender, _shell: _shell });
}
/**
* @notice Build the onion data
*/
function _onionize(TransferOnion.Layer[] memory _layers)
public
pure
returns (bytes32, TransferOnion.Layer[] memory)
{
uint256 length = _layers.length;
bytes32 hash = bytes32(0);
for (uint256 i; i < length; i++) {
TransferOnion.Layer memory layer = _layers[i];
_layers[i].shell = hash;
hash = keccak256(abi.encode(layer.recipient, layer.amount, hash));
}
return (hash, _layers);
}
/**
* @notice The constructor sets the variables as expected
*/
function test_constructor_succeeds() external {
_deploy();
assertEq(address(onion.TOKEN()), _token);
assertEq(onion.SENDER(), _sender);
assertEq(onion.shell(), bytes32(0));
}
/**
* @notice unwrap
*/
function test_unwrap_succeeds() external {
// Commit to transferring tiny amounts of tokens
TransferOnion.Layer[] memory _layers = new TransferOnion.Layer[](2);
_layers[0] = TransferOnion.Layer(address(1), 1, bytes32(0));
_layers[1] = TransferOnion.Layer(address(2), 2, bytes32(0));
// Build the onion shell
(bytes32 shell, TransferOnion.Layer[] memory layers) = _onionize(_layers);
_deploy(shell);
assertEq(onion.shell(), shell);
address token = address(onion.TOKEN());
address sender = onion.SENDER();
// give 3 units of token to sender
deal(token, onion.SENDER(), 3);
vm.prank(sender);
ERC20(token).approve(address(onion), 3);
// To build the inputs, to `peel`, need to reverse the list
TransferOnion.Layer[] memory inputs = new TransferOnion.Layer[](2);
int256 length = int256(layers.length);
for (int256 i = length - 1; i >= 0; i--) {
uint256 ui = uint256(i);
uint256 revidx = uint256(length) - ui - 1;
TransferOnion.Layer memory layer = layers[ui];
inputs[revidx] = layer;
}
// The accounts have no balance
assertEq(ERC20(_token).balanceOf(address(1)), 0);
assertEq(ERC20(_token).balanceOf(address(2)), 0);
onion.peel(inputs);
// Now the accounts have the expected balance
assertEq(ERC20(_token).balanceOf(address(1)), 1);
assertEq(ERC20(_token).balanceOf(address(2)), 2);
}
}
ignores: [ ignores: [
"@babel/eslint-parser", "@babel/eslint-parser",
"@types/level",
"@typescript-eslint/parser", "@typescript-eslint/parser",
"eslint-plugin-import", "eslint-plugin-import",
"eslint-plugin-unicorn", "eslint-plugin-unicorn",
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
"ethers": "^5.7.0", "ethers": "^5.7.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-prom-bundle": "^6.3.6", "express-prom-bundle": "^6.3.6",
"level": "^6.0.1", "level6": "npm:level@^6.0.1",
"levelup": "^4.4.0" "levelup": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {
......
/* Imports: External */ /* Imports: External */
import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts' import { BaseService, LegacyMetrics } from '@eth-optimism/common-ts'
import { LevelUp } from 'levelup' import { LevelUp } from 'levelup'
import level from 'level' import level from 'level6'
import { Counter } from 'prom-client' import { Counter } from 'prom-client'
/* Imports: Internal */ /* Imports: Internal */
......
import { Contract } from 'ethers' import { Contract, BigNumber } from 'ethers'
export interface OutputOracle<TSubmissionEventArgs> {
contract: Contract
filter: any
getTotalElements: () => Promise<BigNumber>
getEventIndex: (args: TSubmissionEventArgs) => BigNumber
}
/** /**
* Partial event interface, meant to reduce the size of the event cache to avoid * Partial event interface, meant to reduce the size of the event cache to avoid
...@@ -41,27 +48,32 @@ const getCache = ( ...@@ -41,27 +48,32 @@ const getCache = (
} }
/** /**
* Updates the event cache for the SCC. * Updates the event cache for a contract and event.
* *
* @param scc The State Commitment Chain contract. * @param contract Contract to update cache for.
* @param filter Event filter to use.
*/ */
export const updateStateBatchEventCache = async ( export const updateOracleCache = async <TSubmissionEventArgs>(
scc: Contract oracle: OutputOracle<TSubmissionEventArgs>
): Promise<void> => { ): Promise<void> => {
const cache = getCache(scc.address) const cache = getCache(oracle.contract.address)
let currentBlock = cache.highestBlock let currentBlock = cache.highestBlock
const endingBlock = await scc.provider.getBlockNumber() const endingBlock = await oracle.contract.provider.getBlockNumber()
let step = endingBlock - currentBlock let step = endingBlock - currentBlock
let failures = 0 let failures = 0
while (currentBlock < endingBlock) { while (currentBlock < endingBlock) {
try { try {
const events = await scc.queryFilter( const events = await oracle.contract.queryFilter(
scc.filters.StateBatchAppended(), oracle.filter,
currentBlock, currentBlock,
currentBlock + step currentBlock + step
) )
// Throw the events into the cache.
for (const event of events) { for (const event of events) {
cache.eventCache[event.args._batchIndex.toNumber()] = { cache.eventCache[
oracle.getEventIndex(event.args as TSubmissionEventArgs).toNumber()
] = {
blockNumber: event.blockNumber, blockNumber: event.blockNumber,
transactionHash: event.transactionHash, transactionHash: event.transactionHash,
args: event.args, args: event.args,
...@@ -97,15 +109,15 @@ export const updateStateBatchEventCache = async ( ...@@ -97,15 +109,15 @@ export const updateStateBatchEventCache = async (
/** /**
* Finds the Event that corresponds to a given state batch by index. * Finds the Event that corresponds to a given state batch by index.
* *
* @param scc StateCommitmentChain contract. * @param oracle Output oracle contract
* @param index State batch index to search for. * @param index State batch index to search for.
* @returns Event corresponding to the batch. * @returns Event corresponding to the batch.
*/ */
export const findEventForStateBatch = async ( export const findEventForStateBatch = async <TSubmissionEventArgs>(
scc: Contract, oracle: OutputOracle<TSubmissionEventArgs>,
index: number index: number
): Promise<PartialEvent> => { ): Promise<PartialEvent> => {
const cache = getCache(scc.address) const cache = getCache(oracle.contract.address)
// Try to find the event in cache first. // Try to find the event in cache first.
if (cache.eventCache[index]) { if (cache.eventCache[index]) {
...@@ -113,7 +125,7 @@ export const findEventForStateBatch = async ( ...@@ -113,7 +125,7 @@ export const findEventForStateBatch = async (
} }
// Update the event cache if we don't have the event. // Update the event cache if we don't have the event.
await updateStateBatchEventCache(scc) await updateOracleCache(oracle)
// Event better be in cache now! // Event better be in cache now!
if (cache.eventCache[index] === undefined) { if (cache.eventCache[index] === undefined) {
...@@ -126,23 +138,23 @@ export const findEventForStateBatch = async ( ...@@ -126,23 +138,23 @@ export const findEventForStateBatch = async (
/** /**
* Finds the first state batch index that has not yet passed the fault proof window. * Finds the first state batch index that has not yet passed the fault proof window.
* *
* @param scc StateCommitmentChain contract. * @param oracle Output oracle contract.
* @returns Starting state root batch index. * @returns Starting state root batch index.
*/ */
export const findFirstUnfinalizedStateBatchIndex = async ( export const findFirstUnfinalizedStateBatchIndex = async <TSubmissionEventArgs>(
scc: Contract oracle: OutputOracle<TSubmissionEventArgs>,
fpw: number
): Promise<number> => { ): Promise<number> => {
const fpw = (await scc.FRAUD_PROOF_WINDOW()).toNumber() const latestBlock = await oracle.contract.provider.getBlock('latest')
const latestBlock = await scc.provider.getBlock('latest') const totalBatches = (await oracle.getTotalElements()).toNumber()
const totalBatches = (await scc.getTotalBatches()).toNumber()
// Perform a binary search to find the next batch that will pass the challenge period. // Perform a binary search to find the next batch that will pass the challenge period.
let lo = 0 let lo = 0
let hi = totalBatches let hi = totalBatches
while (lo !== hi) { while (lo !== hi) {
const mid = Math.floor((lo + hi) / 2) const mid = Math.floor((lo + hi) / 2)
const event = await findEventForStateBatch(scc, mid) const event = await findEventForStateBatch(oracle, mid)
const block = await scc.provider.getBlock(event.blockNumber) const block = await oracle.contract.provider.getBlock(event.blockNumber)
if (block.timestamp + fpw < latestBlock.timestamp) { if (block.timestamp + fpw < latestBlock.timestamp) {
lo = mid + 1 lo = mid + 1
......
This diff is collapsed.
...@@ -12,6 +12,7 @@ import { expect } from './setup' ...@@ -12,6 +12,7 @@ import { expect } from './setup'
import { import {
findEventForStateBatch, findEventForStateBatch,
findFirstUnfinalizedStateBatchIndex, findFirstUnfinalizedStateBatchIndex,
OutputOracle,
} from '../src' } from '../src'
describe('helpers', () => { describe('helpers', () => {
...@@ -28,6 +29,7 @@ describe('helpers', () => { ...@@ -28,6 +29,7 @@ describe('helpers', () => {
let AddressManager: Contract let AddressManager: Contract
let ChainStorageContainer: Contract let ChainStorageContainer: Contract
let StateCommitmentChain: Contract let StateCommitmentChain: Contract
let oracle: OutputOracle<any>
beforeEach(async () => { beforeEach(async () => {
// Set up fakes // Set up fakes
FakeBondManager = await smock.fake(getContractInterface('BondManager')) FakeBondManager = await smock.fake(getContractInterface('BondManager'))
...@@ -67,6 +69,13 @@ describe('helpers', () => { ...@@ -67,6 +69,13 @@ describe('helpers', () => {
// Set up mock returns // Set up mock returns
FakeCanonicalTransactionChain.getTotalElements.returns(1000000000) // just needs to be large FakeCanonicalTransactionChain.getTotalElements.returns(1000000000) // just needs to be large
FakeBondManager.isCollateralized.returns(true) FakeBondManager.isCollateralized.returns(true)
oracle = {
contract: StateCommitmentChain,
filter: StateCommitmentChain.filters.StateBatchAppended(),
getTotalElements: async () => StateCommitmentChain.getTotalBatches(),
getEventIndex: (args: any) => args._batchIndex,
}
}) })
describe('findEventForStateBatch', () => { describe('findEventForStateBatch', () => {
...@@ -79,7 +88,7 @@ describe('helpers', () => { ...@@ -79,7 +88,7 @@ describe('helpers', () => {
}) })
it('should return the event', async () => { it('should return the event', async () => {
const event = await findEventForStateBatch(StateCommitmentChain, 0) const event = await findEventForStateBatch(oracle, 0)
expect(event.args._batchIndex).to.equal(0) expect(event.args._batchIndex).to.equal(0)
}) })
...@@ -88,7 +97,7 @@ describe('helpers', () => { ...@@ -88,7 +97,7 @@ describe('helpers', () => {
describe('when the event does not exist', () => { describe('when the event does not exist', () => {
it('should throw an error', async () => { it('should throw an error', async () => {
await expect( await expect(
findEventForStateBatch(StateCommitmentChain, 0) findEventForStateBatch(oracle, 0)
).to.eventually.be.rejectedWith('unable to find event for batch') ).to.eventually.be.rejectedWith('unable to find event for batch')
}) })
}) })
...@@ -119,7 +128,8 @@ describe('helpers', () => { ...@@ -119,7 +128,8 @@ describe('helpers', () => {
it('should find the first batch older than the FPW', async () => { it('should find the first batch older than the FPW', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain oracle,
challengeWindowSeconds
) )
expect(first).to.equal(1) expect(first).to.equal(1)
...@@ -144,7 +154,8 @@ describe('helpers', () => { ...@@ -144,7 +154,8 @@ describe('helpers', () => {
it('should return zero', async () => { it('should return zero', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain oracle,
challengeWindowSeconds
) )
expect(first).to.equal(0) expect(first).to.equal(0)
...@@ -177,7 +188,8 @@ describe('helpers', () => { ...@@ -177,7 +188,8 @@ describe('helpers', () => {
it('should return undefined', async () => { it('should return undefined', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedStateBatchIndex(
StateCommitmentChain oracle,
challengeWindowSeconds
) )
expect(first).to.equal(undefined) expect(first).to.equal(undefined)
......
...@@ -133,8 +133,8 @@ The Optimism Portal serves as both the entry and exit point to the Optimism L2. ...@@ -133,8 +133,8 @@ The Optimism Portal serves as both the entry and exit point to the Optimism L2.
the [DepositFeed](./deposits.md#deposit-contract) contract, and in addition provides the following interface for the [DepositFeed](./deposits.md#deposit-contract) contract, and in addition provides the following interface for
withdrawals: withdrawals:
- [`WithdrawalTransaction` type](https://github.com/ethereum-optimism/optimism/blob/6c6d142d7bb95faa11066aab5d8aed7187abfe38/packages/contracts-bedrock/contracts/libraries/Types.sol#L76-L83) - [`WithdrawalTransaction` type]
- [`OutputRootProof` type](https://github.com/ethereum-optimism/optimism/blob/6c6d142d7bb95faa11066aab5d8aed7187abfe38/packages/contracts-bedrock/contracts/libraries/Types.sol#L33-L38) - [`OutputRootProof` type]
```js ```js
interface OptimismPortal { interface OptimismPortal {
...@@ -217,3 +217,6 @@ contracts if desired. ...@@ -217,3 +217,6 @@ contracts if desired.
| `FINALIZATION_PERIOD` | `604_800` | seconds | | `FINALIZATION_PERIOD` | `604_800` | seconds |
This `FINALIZATION_PERIOD` value is equivalent to 7 days. This `FINALIZATION_PERIOD` value is equivalent to 7 days.
[`WithdrawalTransaction` type]: https://github.com/ethereum-optimism/optimism/blob/6c6d142d7bb95faa11066aab5d8aed7187abfe38/packages/contracts-bedrock/contracts/libraries/Types.sol#L76-L83
[`OutputRootProof` type]: https://github.com/ethereum-optimism/optimism/blob/6c6d142d7bb95faa11066aab5d8aed7187abfe38/packages/contracts-bedrock/contracts/libraries/Types.sol#L33-L38
...@@ -12090,7 +12090,7 @@ level-ws@^2.0.0: ...@@ -12090,7 +12090,7 @@ level-ws@^2.0.0:
readable-stream "^3.1.0" readable-stream "^3.1.0"
xtend "^4.0.1" xtend "^4.0.1"
level@^6.0.1: "level6@npm:level@^6.0.1":
version "6.0.1" version "6.0.1"
resolved "https://registry.yarnpkg.com/level/-/level-6.0.1.tgz#dc34c5edb81846a6de5079eac15706334b0d7cd6" resolved "https://registry.yarnpkg.com/level/-/level-6.0.1.tgz#dc34c5edb81846a6de5079eac15706334b0d7cd6"
integrity sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw== integrity sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==
......
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