Commit a3712481 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

Merge pull request #1822 from ethereum-optimism/develop

Develop -> Master PR
parents 6cb01bdf cc02fdf7
---
'@eth-optimism/gas-oracle': patch
---
Fix the gas oracle gas price prometheus metric
---
'@eth-optimism/proxyd': patch
---
Add support for additional SSL certificates in Docker container
---
'@eth-optimism/data-transport-layer': patch
---
Handle unprotected transactions
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/l2geth': patch
---
Allow for unprotected transactions
---
'@eth-optimism/core-utils': patch
---
Clean up the L1 => L2 address aliasing utilities
---
'@eth-optimism/proxyd': minor
---
Updated metrics, support local rate limiter
......@@ -12,11 +12,6 @@ on:
pull_request:
paths:
- 'go/batch-submitter/*'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
workflow_dispatch:
defaults:
......@@ -31,7 +26,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16.x
go-version: 1.16.x
- name: Checkout code
uses: actions/checkout@v2
......@@ -40,4 +35,4 @@ jobs:
run: make
- name: Test
run: make test
run: make test
......@@ -12,11 +12,6 @@ on:
pull_request:
paths:
- 'go/gas-oracle/**'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
workflow_dispatch:
defaults:
......
name: geth unit tests
on:
push:
paths:
......@@ -13,11 +12,6 @@ on:
pull_request:
paths:
- 'l2geth/**'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
workflow_dispatch:
defaults:
......
......@@ -13,11 +13,6 @@ on:
paths:
- 'go/gas-oracle/**'
- 'go/batch-submitter/**'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
jobs:
golangci:
name: lint
......
......@@ -55,7 +55,7 @@ jobs:
if: failure()
uses: jwalton/gh-docker-logs@v1
with:
images: 'ethereumoptimism/hardhat,ops_deployer,ops_dtl,ethereumoptimism/l2geth,ethereumoptimism/message-relayer,ops_batch_submitter,ethereumoptimism/l2geth,ops_integration_tests'
images: 'ethereumoptimism/hardhat,ops_deployer,ops_dtl,ops_l2geth,ethereumoptimism/message-relayer,ops_batch_submitter,ops_replica,ops_integration_tests'
dest: '/home/runner/logs'
- name: Tar logs
......
......@@ -7,6 +7,9 @@ LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)"
CTC_ABI_ARTIFACT := ../../packages/contracts/artifacts/contracts/L1/rollup/CanonicalTransactionChain.sol/CanonicalTransactionChain.json
SCC_ABI_ARTIFACT := ../../packages/contracts/artifacts/contracts/L1/rollup/StateCommitmentChain.sol/StateCommitmentChain.json
batch-submitter:
env GO111MODULE=on go build -v $(LDFLAGS) ./cmd/batch-submitter
......@@ -19,8 +22,45 @@ test:
lint:
golangci-lint run ./...
bindings: bindings-ctc bindings-scc
bindings-ctc:
$(eval temp := $(shell mktemp))
cat $(CTC_ABI_ARTIFACT) \
| jq -r .bytecode > $(temp)
cat $(CTC_ABI_ARTIFACT) \
| jq .abi \
| abigen --pkg ctc \
--abi - \
--out bindings/ctc/canonical_transaction_chain.go \
--type CanonicalTransactionChain \
--bin $(temp)
rm $(temp)
bindings-scc:
$(eval temp := $(shell mktemp))
cat $(SCC_ABI_ARTIFACT) \
| jq -r .bytecode > $(temp)
cat $(SCC_ABI_ARTIFACT) \
| jq .abi \
| abigen --pkg scc \
--abi - \
--out bindings/scc/state_commitment_chain.go \
--type StateCommitmentChain \
--bin $(temp)
rm $(temp)
.PHONY: \
batch-submitter \
bindings \
bindings-ctc \
bindings-scc \
clean \
test \
lint
This diff is collapsed.
This diff is collapsed.
......@@ -81,6 +81,7 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
......
......@@ -19,7 +19,7 @@ import (
var (
txSendCounter = metrics.NewRegisteredCounter("tx/send", ometrics.DefaultRegistry)
txNotSignificantCounter = metrics.NewRegisteredCounter("tx/not-significant", ometrics.DefaultRegistry)
gasPriceGauge = metrics.NewRegisteredGauge("gas-price", ometrics.DefaultRegistry)
gasPriceGauge = metrics.NewRegisteredGauge("gas_price", ometrics.DefaultRegistry)
txConfTimer = metrics.NewRegisteredTimer("tx/confirmed", ometrics.DefaultRegistry)
txSendTimer = metrics.NewRegisteredTimer("tx/send", ometrics.DefaultRegistry)
)
......
......@@ -13,9 +13,17 @@ RUN make proxyd
FROM alpine:3.14.2
COPY ./go/proxyd/entrypoint.sh /bin/entrypoint.sh
RUN apk update && \
apk add ca-certificates && \
chmod +x /bin/entrypoint.sh
EXPOSE 8080
VOLUME /etc/proxyd
COPY --from=builder /app/bin/proxyd /bin/proxyd
ENTRYPOINT ["/bin/entrypoint.sh"]
CMD ["/bin/proxyd", "/etc/proxyd/proxyd.toml"]
......@@ -21,3 +21,6 @@ See `metrics.go` for a list of all available metrics.
The metrics port is configurable via the `metrics.port` and `metrics.host` keys in the config.
## Adding Backend SSL Certificates in Docker
The Docker image runs on Alpine Linux. If you get SSL errors when connecting to a backend within Docker, you may need to add additional certificates to Alpine's certificate store. To do this, bind mount the certificate bundle into a file in `/usr/local/share/ca-certificates`. The `entrypoint.sh` script will then update the store with whatever is in the `ca-certificates` directory prior to starting `proxyd`.
\ No newline at end of file
......@@ -3,6 +3,7 @@ package proxyd
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
......@@ -14,6 +15,7 @@ import (
"math"
"math/rand"
"net/http"
"strconv"
"time"
)
......@@ -24,36 +26,44 @@ const (
var (
ErrInvalidRequest = &RPCErr{
Code: -32601,
Message: "invalid request",
Code: -32601,
Message: "invalid request",
HTTPErrorCode: 400,
}
ErrParseErr = &RPCErr{
Code: -32700,
Message: "parse error",
Code: -32700,
Message: "parse error",
HTTPErrorCode: 400,
}
ErrInternal = &RPCErr{
Code: JSONRPCErrorInternal,
Message: "internal error",
Code: JSONRPCErrorInternal,
Message: "internal error",
HTTPErrorCode: 500,
}
ErrMethodNotWhitelisted = &RPCErr{
Code: JSONRPCErrorInternal - 1,
Message: "rpc method is not whitelisted",
Code: JSONRPCErrorInternal - 1,
Message: "rpc method is not whitelisted",
HTTPErrorCode: 403,
}
ErrBackendOffline = &RPCErr{
Code: JSONRPCErrorInternal - 10,
Message: "backend offline",
Code: JSONRPCErrorInternal - 10,
Message: "backend offline",
HTTPErrorCode: 503,
}
ErrNoBackends = &RPCErr{
Code: JSONRPCErrorInternal - 11,
Message: "no backends available for method",
Code: JSONRPCErrorInternal - 11,
Message: "no backends available for method",
HTTPErrorCode: 503,
}
ErrBackendOverCapacity = &RPCErr{
Code: JSONRPCErrorInternal - 12,
Message: "backend is over capacity",
Code: JSONRPCErrorInternal - 12,
Message: "backend is over capacity",
HTTPErrorCode: 429,
}
ErrBackendBadResponse = &RPCErr{
Code: JSONRPCErrorInternal - 13,
Message: "backend returned an invalid response",
Code: JSONRPCErrorInternal - 13,
Message: "backend returned an invalid response",
HTTPErrorCode: 500,
}
)
......@@ -63,7 +73,7 @@ type Backend struct {
wsURL string
authUsername string
authPassword string
redis Redis
rateLimiter RateLimiter
client *http.Client
dialer *websocket.Dialer
maxRetries int
......@@ -118,18 +128,27 @@ func WithMaxWSConns(maxConns int) BackendOpt {
}
}
func WithTLSConfig(tlsConfig *tls.Config) BackendOpt {
return func(b *Backend) {
if b.client.Transport == nil {
b.client.Transport = &http.Transport{}
}
b.client.Transport.(*http.Transport).TLSClientConfig = tlsConfig
}
}
func NewBackend(
name string,
rpcURL string,
wsURL string,
redis Redis,
rateLimiter RateLimiter,
opts ...BackendOpt,
) *Backend {
backend := &Backend{
Name: name,
rpcURL: rpcURL,
wsURL: wsURL,
redis: redis,
rateLimiter: rateLimiter,
maxResponseSize: math.MaxInt64,
client: &http.Client{
Timeout: 5 * time.Second,
......@@ -160,7 +179,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) {
for i := 0; i <= b.maxRetries; i++ {
RecordRPCForward(ctx, b.Name, req.Method, RPCRequestSourceHTTP)
respTimer := prometheus.NewTimer(rpcBackendRequestDurationSumm.WithLabelValues(b.Name, req.Method))
res, err := b.doForward(req)
res, err := b.doForward(ctx, req)
if err != nil {
lastError = err
log.Warn(
......@@ -179,6 +198,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) {
RecordRPCError(ctx, b.Name, req.Method, res.Error)
log.Info(
"backend responded with RPC error",
"backend", b.Name,
"code", res.Error.Code,
"msg", res.Error.Message,
"req_id", GetReqID(ctx),
......@@ -187,6 +207,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) {
)
} else {
log.Info("forwarded RPC request",
"backend", b.Name,
"method", req.Method,
"auth", GetAuthCtx(ctx),
"req_id", GetReqID(ctx),
......@@ -210,7 +231,7 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet
backendConn, _, err := b.dialer.Dial(b.wsURL, nil)
if err != nil {
b.setOffline()
if err := b.redis.DecBackendWSConns(b.Name); err != nil {
if err := b.rateLimiter.DecBackendWSConns(b.Name); err != nil {
log.Error("error decrementing backend ws conns", "name", b.Name, "err", err)
}
return nil, wrapErr(err, "error dialing backend")
......@@ -221,7 +242,7 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet
}
func (b *Backend) Online() bool {
online, err := b.redis.IsBackendOnline(b.Name)
online, err := b.rateLimiter.IsBackendOnline(b.Name)
if err != nil {
log.Warn(
"error getting backend availability, assuming it is offline",
......@@ -238,7 +259,7 @@ func (b *Backend) IsRateLimited() bool {
return false
}
usedLimit, err := b.redis.IncBackendRPS(b.Name)
usedLimit, err := b.rateLimiter.IncBackendRPS(b.Name)
if err != nil {
log.Error(
"error getting backend used rate limit, assuming limit is exhausted",
......@@ -256,7 +277,7 @@ func (b *Backend) IsWSSaturated() bool {
return false
}
incremented, err := b.redis.IncBackendWSConns(b.Name, b.maxWSConns)
incremented, err := b.rateLimiter.IncBackendWSConns(b.Name, b.maxWSConns)
if err != nil {
log.Error(
"error getting backend used ws conns, assuming limit is exhausted",
......@@ -270,7 +291,7 @@ func (b *Backend) IsWSSaturated() bool {
}
func (b *Backend) setOffline() {
err := b.redis.SetBackendOffline(b.Name, b.outOfServiceInterval)
err := b.rateLimiter.SetBackendOffline(b.Name, b.outOfServiceInterval)
if err != nil {
log.Warn(
"error setting backend offline",
......@@ -280,7 +301,7 @@ func (b *Backend) setOffline() {
}
}
func (b *Backend) doForward(rpcReq *RPCReq) (*RPCRes, error) {
func (b *Backend) doForward(ctx context.Context, rpcReq *RPCReq) (*RPCRes, error) {
body := mustMarshalJSON(rpcReq)
httpReq, err := http.NewRequest("POST", b.rpcURL, bytes.NewReader(body))
......@@ -299,6 +320,13 @@ func (b *Backend) doForward(rpcReq *RPCReq) (*RPCRes, error) {
return nil, wrapErr(err, "error in backend request")
}
rpcBackendHTTPResponseCodesTotal.WithLabelValues(
GetAuthCtx(ctx),
b.Name,
rpcReq.Method,
strconv.Itoa(httpRes.StatusCode),
).Inc()
// Alchemy returns a 400 on bad JSONs, so handle that case
if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 {
return nil, fmt.Errorf("response code %d", httpRes.StatusCode)
......@@ -315,6 +343,12 @@ func (b *Backend) doForward(rpcReq *RPCReq) (*RPCRes, error) {
return nil, ErrBackendBadResponse
}
// capture the HTTP status code in the response. this will only
// ever be 400 given the status check on line 318 above.
if httpRes.StatusCode != 200 {
res.Error.HTTPErrorCode = httpRes.StatusCode
}
return res, nil
}
......@@ -556,7 +590,7 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) {
func (w *WSProxier) close() {
w.clientConn.Close()
w.backendConn.Close()
if err := w.backend.redis.DecBackendWSConns(w.backend.Name); err != nil {
if err := w.backend.rateLimiter.DecBackendWSConns(w.backend.Name); err != nil {
log.Error("error decrementing backend ws conns", "name", w.backend.Name, "err", err)
}
activeBackendWsConnsGauge.WithLabelValues(w.backend.Name).Dec()
......
......@@ -31,13 +31,16 @@ type BackendConfig struct {
RPCURL string `toml:"rpc_url"`
WSURL string `toml:"ws_url"`
MaxRPS int `toml:"max_rps"`
MaxWSConns int `toml:"max_ws_conns"`
MaxWSConns int `toml:"max_ws_conns"`
CAFile string `toml:"ca_file"`
ClientCertFile string `toml:"client_cert_file"`
ClientKeyFile string `toml:"client_key_file"`
}
type BackendsConfig map[string]*BackendConfig
type BackendGroupConfig struct {
Backends []string `toml:"backends"`
Backends []string `toml:"backends"`
}
type BackendGroupsConfig map[string]*BackendGroupConfig
......
#!/bin/sh
echo "Updating CA certificates."
update-ca-certificates
echo "Running CMD."
exec "$@"
\ No newline at end of file
......@@ -52,6 +52,12 @@ username = ""
password = ""
max_rps = 3
max_ws_conns = 1
# Path to a custom root CA.
ca_file = ""
# Path to a custom client cert file.
client_cert_file = ""
# Path to a custom client key file.
client_key_file = ""
[backends.alchemy]
# The URL to contact the backend at.
......
......@@ -38,6 +38,17 @@ var (
"source",
})
rpcBackendHTTPResponseCodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "rpc_backend_http_response_codes_total",
Help: "Count of total backend responses by HTTP status code.",
}, []string{
"auth",
"backend_name",
"method_name",
"status_code",
})
rpcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "rpc_errors_total",
......@@ -101,6 +112,14 @@ var (
Help: "Count of total HTTP requests.",
})
httpResponseCodesTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace,
Name: "http_response_codes_total",
Help: "Count of total HTTP response codes.",
}, []string{
"status_code",
})
httpRequestDurationSumm = promauto.NewSummary(prometheus.SummaryOpts{
Namespace: MetricsNamespace,
Name: "http_request_duration_seconds",
......
package proxyd
import (
"crypto/tls"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/log"
......@@ -29,9 +30,16 @@ func Start(config *Config) error {
}
}
redis, err := NewRedis(config.Redis.URL)
if err != nil {
return err
var lim RateLimiter
var err error
if config.Redis == nil {
log.Warn("redis is not configured, using local rate limiter")
lim = NewLocalRateLimiter()
} else {
lim, err = NewRedisRateLimiter(config.Redis.URL)
if err != nil {
return err
}
}
backendNames := make([]string, 0)
......@@ -68,7 +76,15 @@ func Start(config *Config) error {
if cfg.Password != "" {
opts = append(opts, WithBasicAuth(cfg.Username, cfg.Password))
}
back := NewBackend(name, cfg.RPCURL, cfg.WSURL, redis, opts...)
tlsConfig, err := configureBackendTLS(cfg)
if err != nil {
return err
}
if tlsConfig != nil {
log.Info("using custom TLS config for backend", "name", name)
opts = append(opts, WithTLSConfig(tlsConfig))
}
back := NewBackend(name, cfg.RPCURL, cfg.WSURL, lim, opts...)
backendNames = append(backendNames, name)
backendsByName[name] = back
log.Info("configured backend", "name", name, "rpc_url", cfg.RPCURL, "ws_url", cfg.WSURL)
......@@ -90,17 +106,17 @@ func Start(config *Config) error {
backendGroups[bgName] = group
}
var wsBackendGroup *BackendGroup
if config.WSBackendGroup != "" {
wsBackendGroup = backendGroups[config.WSBackendGroup]
if wsBackendGroup == nil {
return fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup)
}
}
var wsBackendGroup *BackendGroup
if config.WSBackendGroup != "" {
wsBackendGroup = backendGroups[config.WSBackendGroup]
if wsBackendGroup == nil {
return fmt.Errorf("ws backend group %s does not exist", config.WSBackendGroup)
}
}
if wsBackendGroup == nil && config.Server.WSPort != 0 {
return fmt.Errorf("a ws port was defined, but no ws group was defined")
}
if wsBackendGroup == nil && config.Server.WSPort != 0 {
return fmt.Errorf("a ws port was defined, but no ws group was defined")
}
for _, bg := range config.RPCMethodMappings {
if backendGroups[bg] == nil {
......@@ -152,7 +168,7 @@ func Start(config *Config) error {
recvSig := <-sig
log.Info("caught signal, shutting down", "signal", recvSig)
srv.Shutdown()
if err := redis.FlushBackendWSConns(backendNames); err != nil {
if err := lim.FlushBackendWSConns(backendNames); err != nil {
log.Error("error flushing backend ws conns", "err", err)
}
return nil
......@@ -161,3 +177,24 @@ func Start(config *Config) error {
func secondsToDuration(seconds int) time.Duration {
return time.Duration(seconds) * time.Second
}
func configureBackendTLS(cfg *BackendConfig) (*tls.Config, error) {
if cfg.CAFile == "" {
return nil, nil
}
tlsConfig, err := CreateTLSClient(cfg.CAFile)
if err != nil {
return nil, err
}
if cfg.ClientCertFile != "" && cfg.ClientKeyFile != "" {
cert, err := ParseKeyPair(cfg.ClientCertFile, cfg.ClientKeyFile)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
return tlsConfig, nil
}
......@@ -40,7 +40,7 @@ end
return false
`
type Redis interface {
type RateLimiter interface {
IsBackendOnline(name string) (bool, error)
SetBackendOffline(name string, duration time.Duration) error
IncBackendRPS(name string) (int, error)
......@@ -49,14 +49,14 @@ type Redis interface {
FlushBackendWSConns(names []string) error
}
type RedisImpl struct {
type RedisRateLimiter struct {
rdb *redis.Client
randID string
touchKeys map[string]time.Duration
tkMtx sync.Mutex
}
func NewRedis(url string) (Redis, error) {
func NewRedisRateLimiter(url string) (RateLimiter, error) {
opts, err := redis.ParseURL(url)
if err != nil {
return nil, err
......@@ -65,7 +65,7 @@ func NewRedis(url string) (Redis, error) {
if err := rdb.Ping(context.Background()).Err(); err != nil {
return nil, wrapErr(err, "error connecting to redis")
}
out := &RedisImpl{
out := &RedisRateLimiter{
rdb: rdb,
randID: randStr(20),
touchKeys: make(map[string]time.Duration),
......@@ -74,7 +74,7 @@ func NewRedis(url string) (Redis, error) {
return out, nil
}
func (r *RedisImpl) IsBackendOnline(name string) (bool, error) {
func (r *RedisRateLimiter) IsBackendOnline(name string) (bool, error) {
exists, err := r.rdb.Exists(context.Background(), fmt.Sprintf("backend:%s:offline", name)).Result()
if err != nil {
RecordRedisError("IsBackendOnline")
......@@ -84,7 +84,7 @@ func (r *RedisImpl) IsBackendOnline(name string) (bool, error) {
return exists == 0, nil
}
func (r *RedisImpl) SetBackendOffline(name string, duration time.Duration) error {
func (r *RedisRateLimiter) SetBackendOffline(name string, duration time.Duration) error {
err := r.rdb.SetEX(
context.Background(),
fmt.Sprintf("backend:%s:offline", name),
......@@ -98,7 +98,7 @@ func (r *RedisImpl) SetBackendOffline(name string, duration time.Duration) error
return nil
}
func (r *RedisImpl) IncBackendRPS(name string) (int, error) {
func (r *RedisRateLimiter) IncBackendRPS(name string) (int, error) {
cmd := r.rdb.Eval(
context.Background(),
MaxRPSScript,
......@@ -112,7 +112,7 @@ func (r *RedisImpl) IncBackendRPS(name string) (int, error) {
return rps, nil
}
func (r *RedisImpl) IncBackendWSConns(name string, max int) (bool, error) {
func (r *RedisRateLimiter) IncBackendWSConns(name string, max int) (bool, error) {
connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name)
r.tkMtx.Lock()
r.touchKeys[connsKey] = 5 * time.Minute
......@@ -138,7 +138,7 @@ func (r *RedisImpl) IncBackendWSConns(name string, max int) (bool, error) {
return incremented, nil
}
func (r *RedisImpl) DecBackendWSConns(name string) error {
func (r *RedisRateLimiter) DecBackendWSConns(name string) error {
connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name)
err := r.rdb.Decr(context.Background(), connsKey).Err()
if err != nil {
......@@ -148,7 +148,7 @@ func (r *RedisImpl) DecBackendWSConns(name string) error {
return nil
}
func (r *RedisImpl) FlushBackendWSConns(names []string) error {
func (r *RedisRateLimiter) FlushBackendWSConns(names []string) error {
ctx := context.Background()
for _, name := range names {
connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name)
......@@ -168,7 +168,7 @@ func (r *RedisImpl) FlushBackendWSConns(names []string) error {
return nil
}
func (r *RedisImpl) touch() {
func (r *RedisRateLimiter) touch() {
for {
r.tkMtx.Lock()
for key, dur := range r.touchKeys {
......@@ -182,6 +182,76 @@ func (r *RedisImpl) touch() {
}
}
type LocalRateLimiter struct {
deadBackends map[string]time.Time
backendRPS map[string]int
backendWSConns map[string]int
mtx sync.RWMutex
}
func NewLocalRateLimiter() *LocalRateLimiter {
out := &LocalRateLimiter{
deadBackends: make(map[string]time.Time),
backendRPS: make(map[string]int),
backendWSConns: make(map[string]int),
}
go out.clear()
return out
}
func (l *LocalRateLimiter) IsBackendOnline(name string) (bool, error) {
l.mtx.RLock()
defer l.mtx.RUnlock()
return l.deadBackends[name].Before(time.Now()), nil
}
func (l *LocalRateLimiter) SetBackendOffline(name string, duration time.Duration) error {
l.mtx.Lock()
defer l.mtx.Unlock()
l.deadBackends[name] = time.Now().Add(duration)
return nil
}
func (l *LocalRateLimiter) IncBackendRPS(name string) (int, error) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.backendRPS[name] += 1
return l.backendRPS[name], nil
}
func (l *LocalRateLimiter) IncBackendWSConns(name string, max int) (bool, error) {
l.mtx.Lock()
defer l.mtx.Unlock()
if l.backendWSConns[name] == max {
return false, nil
}
l.backendWSConns[name] += 1
return true, nil
}
func (l *LocalRateLimiter) DecBackendWSConns(name string) error {
l.mtx.Lock()
defer l.mtx.Unlock()
if l.backendWSConns[name] == 0 {
return nil
}
l.backendWSConns[name] -= 1
return nil
}
func (l *LocalRateLimiter) FlushBackendWSConns(names []string) error {
return nil
}
func (l *LocalRateLimiter) clear() {
for {
time.Sleep(time.Second)
l.mtx.Lock()
l.backendRPS = make(map[string]int)
l.mtx.Unlock()
}
}
func randStr(l int) string {
b := make([]byte, l)
if _, err := rand.Read(b); err != nil {
......
......@@ -25,8 +25,9 @@ func (r *RPCRes) IsError() bool {
}
type RPCErr struct {
Code int `json:"code"`
Message string `json:"message"`
Code int `json:"code"`
Message string `json:"message"`
HTTPErrorCode int `json:"-"`
}
func (r *RPCErr) Error() string {
......
......@@ -12,6 +12,7 @@ import (
"github.com/rs/cors"
"io"
"net/http"
"strconv"
"time"
)
......@@ -105,7 +106,12 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
return
}
log.Info("received RPC request", "req_id", GetReqID(ctx), "auth", GetAuthCtx(ctx))
log.Info(
"received RPC request",
"req_id", GetReqID(ctx),
"auth", GetAuthCtx(ctx),
"user_agent", r.Header.Get("user-agent"),
)
req, err := ParseRPCReq(io.LimitReader(r.Body, s.maxBodySize))
if err != nil {
......@@ -200,6 +206,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
// but someone sends in an auth key anyway
if authorization != "" {
log.Info("blocked authenticated request against unauthenticated proxy")
httpResponseCodesTotal.WithLabelValues("404").Inc()
w.WriteHeader(404)
return nil
}
......@@ -212,6 +219,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
if authorization == "" || s.authenticatedPaths[authorization] == "" {
log.Info("blocked unauthorized request", "authorization", authorization)
httpResponseCodesTotal.WithLabelValues("401").Inc()
w.WriteHeader(401)
return nil
}
......@@ -225,21 +233,29 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
}
func writeRPCError(w http.ResponseWriter, id *int, err error) {
enc := json.NewEncoder(w)
w.WriteHeader(200)
var body *RPCRes
var res *RPCRes
if r, ok := err.(*RPCErr); ok {
body = NewRPCErrorRes(id, r)
res = NewRPCErrorRes(id, r)
} else {
body = NewRPCErrorRes(id, &RPCErr{
res = NewRPCErrorRes(id, &RPCErr{
Code: JSONRPCErrorInternal,
Message: "internal error",
})
}
if err := enc.Encode(body); err != nil {
log.Error("error writing rpc error", "err", err)
writeRPCRes(w, res)
}
func writeRPCRes(w http.ResponseWriter, res *RPCRes) {
statusCode := 200
if res.IsError() && res.Error.HTTPErrorCode != 0 {
statusCode = res.Error.HTTPErrorCode
}
w.WriteHeader(statusCode)
enc := json.NewEncoder(w)
if err := enc.Encode(res); err != nil {
log.Error("error writing rpc response", "err", err)
}
httpResponseCodesTotal.WithLabelValues(strconv.Itoa(statusCode)).Inc()
}
func instrumentedHdlr(h http.Handler) http.HandlerFunc {
......
package proxyd
import (
"crypto/tls"
"crypto/x509"
"errors"
"io/ioutil"
)
func CreateTLSClient(ca string) (*tls.Config, error) {
pem, err := ioutil.ReadFile(ca)
if err != nil {
return nil, wrapErr(err, "error reading CA")
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(pem)
if !ok {
return nil, errors.New("error parsing TLS client cert")
}
return &tls.Config{
RootCAs: roots,
}, nil
}
func ParseKeyPair(crt, key string) (tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(crt, key)
if err != nil {
return tls.Certificate{}, wrapErr(err, "error loading x509 key pair")
}
return cert, nil
}
\ No newline at end of file
......@@ -31,6 +31,7 @@
"@eth-optimism/contracts": "0.5.4",
"@eth-optimism/core-utils": "0.7.2",
"@eth-optimism/message-relayer": "0.2.4",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
......
import { OptimismEnv } from './shared/env'
import {
defaultTransactionFactory,
gasPriceForL2,
sleep,
isLiveNetwork,
} from './shared/utils'
import { expect } from 'chai'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
describe('Replica Tests', () => {
let env: OptimismEnv
before(async () => {
env = await OptimismEnv.new()
})
describe('Matching blocks', () => {
if (isLiveNetwork()) {
console.log('Skipping replica tests on live network')
return
}
it('should sync a transaction', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2(env)
const result = await env.l2Wallet.sendTransaction(tx)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.replicaProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const replicaBlock = (await env.replicaProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash)
})
it('sync an unprotected tx (eip155)', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.l2Provider.sendTransaction(signed)
let receipt: TransactionReceipt
while (!receipt) {
receipt = await env.replicaProvider.getTransactionReceipt(result.hash)
await sleep(200)
}
const sequencerBlock = (await env.l2Provider.getBlock(
result.blockNumber
)) as any
const replicaBlock = (await env.replicaProvider.getBlock(
result.blockNumber
)) as any
expect(sequencerBlock.stateRoot).to.deep.eq(replicaBlock.stateRoot)
expect(sequencerBlock.hash).to.deep.eq(replicaBlock.hash)
})
})
})
......@@ -84,16 +84,19 @@ describe('Basic RPC tests', () => {
).to.be.rejectedWith('invalid transaction: invalid sender')
})
it('should not accept a transaction without a chain ID', async () => {
it('should accept a transaction without a chain ID', async () => {
const tx = {
...defaultTransactionFactory(),
nonce: await wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await wallet.signTransaction(tx)
const response = await provider.sendTransaction(signed)
await expect(
provider.sendTransaction(await wallet.signTransaction(tx))
).to.be.rejectedWith('Cannot submit unprotected transaction')
expect(response.chainId).to.equal(0)
const v = response.v
expect(v === 27 || v === 28).to.be.true
})
it('should accept a transaction with a value', async () => {
......
......@@ -10,6 +10,7 @@ import {
getAddressManager,
l1Provider,
l2Provider,
replicaProvider,
l1Wallet,
l2Wallet,
fundUser,
......@@ -52,6 +53,7 @@ export class OptimismEnv {
// The providers
l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider
replicaProvider: providers.JsonRpcProvider
constructor(args: any) {
this.addressManager = args.addressManager
......@@ -67,6 +69,7 @@ export class OptimismEnv {
this.l2Wallet = args.l2Wallet
this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider
this.replicaProvider = args.replicaProvider
this.ctc = args.ctc
this.scc = args.scc
}
......@@ -126,6 +129,7 @@ export class OptimismEnv {
l2Wallet,
l1Provider,
l2Provider,
replicaProvider,
})
}
......
......@@ -55,13 +55,19 @@ const env = cleanEnv(process.env, {
export const l1Provider = new providers.JsonRpcProvider(env.L1_URL)
l1Provider.pollingInterval = env.L1_POLLING_INTERVAL
export const l2Provider = new providers.JsonRpcProvider(env.L2_URL)
export const l2Provider = injectL2Context(
new providers.JsonRpcProvider(env.L2_URL)
)
l2Provider.pollingInterval = env.L2_POLLING_INTERVAL
export const verifierProvider = new providers.JsonRpcProvider(env.VERIFIER_URL)
export const verifierProvider = injectL2Context(
new providers.JsonRpcProvider(env.VERIFIER_URL)
)
verifierProvider.pollingInterval = env.VERIFIER_POLLING_INTERVAL
export const replicaProvider = new providers.JsonRpcProvider(env.REPLICA_URL)
export const replicaProvider = injectL2Context(
new providers.JsonRpcProvider(env.REPLICA_URL)
)
replicaProvider.pollingInterval = env.REPLICA_POLLING_INTERVAL
// The sequencer private key which is funded on L1
......
......@@ -1573,9 +1573,6 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
// SubmitTransaction is a helper function that submits tx to txPool and logs a message.
func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
if !tx.Protected() {
return common.Hash{}, errors.New("Cannot submit unprotected transaction")
}
if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
......
......@@ -134,8 +134,8 @@ type RollupClient interface {
// Client is an HTTP based RollupClient
type Client struct {
client *resty.Client
signer *types.EIP155Signer
client *resty.Client
chainID *big.Int
}
// TransactionResponse represents the response from the remote server when
......@@ -166,11 +166,10 @@ func NewClient(url string, chainID *big.Int) *Client {
}
return nil
})
signer := types.NewEIP155Signer(chainID)
return &Client{
client: client,
signer: &signer,
client: client,
chainID: chainID,
}
}
......@@ -322,7 +321,7 @@ func (c *Client) GetLatestTransactionBatchIndex() (*uint64, error) {
// batchedTransactionToTransaction converts a transaction into a
// types.Transaction that can be consumed by the SyncService
func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signer) (*types.Transaction, error) {
func batchedTransactionToTransaction(res *transaction, chainID *big.Int) (*types.Transaction, error) {
// `nil` transactions are not found
if res == nil {
return nil, errElementNotFound
......@@ -373,7 +372,15 @@ func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signe
sig := make([]byte, crypto.SignatureLength)
copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s)
sig[64] = byte(res.Decoded.Signature.V)
var signer types.Signer
if res.Decoded.Signature.V == 27 || res.Decoded.Signature.V == 28 {
signer = types.HomesteadSigner{}
sig[64] = byte(res.Decoded.Signature.V - 27)
} else {
signer = types.NewEIP155Signer(chainID)
sig[64] = byte(res.Decoded.Signature.V)
}
tx, err := tx.WithSignature(signer, sig[:])
if err != nil {
......@@ -431,7 +438,7 @@ func (c *Client) GetTransaction(index uint64, backend Backend) (*types.Transacti
if !ok {
return nil, fmt.Errorf("could not get tx with index %d", index)
}
return batchedTransactionToTransaction(res.Transaction, c.signer)
return batchedTransactionToTransaction(res.Transaction, c.chainID)
}
// GetLatestTransaction will get the latest transaction, meaning the transaction
......@@ -452,7 +459,7 @@ func (c *Client) GetLatestTransaction(backend Backend) (*types.Transaction, erro
return nil, errors.New("Cannot get latest transaction")
}
return batchedTransactionToTransaction(res.Transaction, c.signer)
return batchedTransactionToTransaction(res.Transaction, c.chainID)
}
// GetEthContext will return the EthContext by block number
......@@ -564,7 +571,7 @@ func (c *Client) GetLatestTransactionBatch() (*Batch, []*types.Transaction, erro
if !ok {
return nil, nil, fmt.Errorf("Cannot parse transaction batch response")
}
return parseTransactionBatchResponse(txBatch, c.signer)
return parseTransactionBatchResponse(txBatch, c.chainID)
}
// GetTransactionBatch will return the transaction batch by batch index
......@@ -584,19 +591,19 @@ func (c *Client) GetTransactionBatch(index uint64) (*Batch, []*types.Transaction
if !ok {
return nil, nil, fmt.Errorf("Cannot parse transaction batch response")
}
return parseTransactionBatchResponse(txBatch, c.signer)
return parseTransactionBatchResponse(txBatch, c.chainID)
}
// parseTransactionBatchResponse will turn a TransactionBatchResponse into a
// Batch and its corresponding types.Transactions
func parseTransactionBatchResponse(txBatch *TransactionBatchResponse, signer *types.EIP155Signer) (*Batch, []*types.Transaction, error) {
func parseTransactionBatchResponse(txBatch *TransactionBatchResponse, chainID *big.Int) (*Batch, []*types.Transaction, error) {
if txBatch == nil || txBatch.Batch == nil {
return nil, nil, errElementNotFound
}
batch := txBatch.Batch
txs := make([]*types.Transaction, len(txBatch.Transactions))
for i, tx := range txBatch.Transactions {
transaction, err := batchedTransactionToTransaction(tx, signer)
transaction, err := batchedTransactionToTransaction(tx, chainID)
if err != nil {
return nil, nil, fmt.Errorf("Cannot parse transaction batch: %w", err)
}
......
version: "3"
version: "3.4"
services:
l2geth:
......@@ -51,4 +51,4 @@ volumes:
influxdb_data:
grafana_data:
grafana_dashboards:
prometheus_data:
\ No newline at end of file
prometheus_data:
version: "3"
version: "3.4"
services:
l1_chain:
image: ethereumoptimism/hardhat-node:${DOCKER_TAG:-prerelease-0.5.0-rc-7-ee217ce}
......
version: "3"
version: "3.4"
services:
rpc-proxy:
depends_on:
......
version: "3"
version: "3.4"
services:
# this is a helper service used because there's no official hardhat image
......@@ -165,7 +165,7 @@ services:
depends_on:
- dtl
deploy:
replicas: 0
replicas: 1
build:
context: ..
dockerfile: ./ops/docker/Dockerfile.geth
......@@ -181,8 +181,8 @@ services:
ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60
ports:
- ${L2GETH_HTTP_PORT:-8549}:8545
- ${L2GETH_WS_PORT:-8550}:8546
- ${REPLICA_HTTP_PORT:-8549}:8545
- ${REPLICA_WS_PORT:-8550}:8546
integration_tests:
deploy:
......@@ -195,6 +195,8 @@ services:
environment:
L1_URL: http://l1_chain:8545
L2_URL: http://l2geth:8545
REPLICA_URL: http://replica:8545
VERIFIER_URL: http://verifier:8545
URL: http://deployer:8081/addresses.json
ENABLE_GAS_REPORT: 1
NO_NETWORK: 1
......
......@@ -73,8 +73,8 @@
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "4.3.2",
"@openzeppelin/contracts-upgradeable": "4.3.2",
"@typechain/ethers-v5": "^7.0.1",
"@typechain/hardhat": "^2.3.0",
"@typechain/ethers-v5": "^8.0.2",
"@typechain/hardhat": "^3.0.0",
"@types/chai": "^4.2.18",
"@types/lodash": "^4.14.168",
"@types/mkdirp": "^1.0.1",
......@@ -117,7 +117,7 @@
"solidity-coverage": "^0.7.17",
"ts-generator": "0.0.8",
"ts-node": "^10.0.0",
"typechain": "^5.1.0",
"typechain": "^6.0.2",
"typescript": "^4.3.5",
"yargs": "^16.2.0"
},
......
import { ethers } from 'ethers'
import { bnToAddress } from './bn'
// Constant representing the alias to apply to the msg.sender when a contract sends an L1 => L2
// message. We need this aliasing scheme because a contract can be deployed to the same address
// on both L1 and L2 but with different bytecode (address is not dependent on bytecode when using
// the standard CREATE opcode). We want to treat L1 contracts as having a different address while
// still making it possible for L2 contracts to easily reverse the aliasing scheme and figure out
// the real address of the contract that sent the L1 => L2 message.
export const L1_TO_L2_ALIAS_OFFSET =
'0x1111000000000000000000000000000000001111'
export const bnToAddress = (bn: ethers.BigNumber | number): string => {
bn = ethers.BigNumber.from(bn)
if (bn.isNegative()) {
bn = ethers.BigNumber.from('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
.add(bn)
.add(1)
}
const addr = bn.toHexString().slice(2).padStart(40, '0')
return ethers.utils.getAddress(
'0x' + addr.slice(addr.length - 40, addr.length)
)
}
/**
* Applies the L1 => L2 aliasing scheme to an address.
*
* @param address Address to apply the scheme to.
* @returns Address with the scheme applied.
*/
export const applyL1ToL2Alias = (address: string): string => {
if (!ethers.utils.isAddress(address)) {
throw new Error(`not a valid address: ${address}`)
......@@ -25,6 +24,12 @@ export const applyL1ToL2Alias = (address: string): string => {
return bnToAddress(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET))
}
/**
* Reverses the L1 => L2 aliasing scheme from an address.
*
* @param address Address to reverse the scheme from.
* @returns Alias with the scheme reversed.
*/
export const undoL1ToL2Alias = (address: string): string => {
if (!ethers.utils.isAddress(address)) {
throw new Error(`not a valid address: ${address}`)
......
import { ethers } from 'ethers'
import { remove0x, add0x } from './common/hex-strings'
/**
* Converts an ethers BigNumber into an equivalent Ethereum address representation.
*
* @param bn BigNumber to convert to an address.
* @return BigNumber converted to an address, represented as a hex string.
*/
export const bnToAddress = (bn: ethers.BigNumber | number): string => {
// Coerce numbers into a BigNumber.
bn = ethers.BigNumber.from(bn)
// Negative numbers are converted to addresses by adding MAX_ADDRESS + 1.
// TODO: Explain this in more detail, it's basically just matching the behavior of doing
// addr(uint256(addr) - some_number) in Solidity where some_number > uint256(addr).
if (bn.isNegative()) {
bn = ethers.BigNumber.from('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
.add(bn)
.add(1)
}
// Convert to a hex string
let addr = bn.toHexString()
// Remove leading 0x so we can mutate the address a bit
addr = remove0x(addr)
// Make sure it's 40 characters (= 20 bytes)
addr = addr.padStart(40, '0')
// Only take the last 40 characters (= 20 bytes)
addr = addr.slice(addr.length - 40, addr.length)
// Add 0x again
addr = add0x(addr)
// Convert into a checksummed address
addr = ethers.utils.getAddress(addr)
return addr
}
import { ethers } from 'ethers'
export interface EventArgsAddressSet {
_name: string
_newAddress: string
_oldAddress: string
}
export interface EventArgsTransactionEnqueued {
_l1TxOrigin: string
_target: string
_gasLimit: ethers.BigNumber
_data: string
_queueIndex: ethers.BigNumber
_timestamp: ethers.BigNumber
}
export interface EventArgsTransactionBatchAppended {
_batchIndex: ethers.BigNumber
_batchRoot: string
_batchSize: ethers.BigNumber
_prevTotalElements: ethers.BigNumber
_extraData: string
}
export interface EventArgsStateBatchAppended {
_batchIndex: ethers.BigNumber
_batchRoot: string
_batchSize: ethers.BigNumber
_prevTotalElements: ethers.BigNumber
_extraData: string
}
export interface EventArgsSequencerBatchAppended {
_startingQueueIndex: ethers.BigNumber
_numQueueElements: ethers.BigNumber
_totalElements: ethers.BigNumber
}
......@@ -2,10 +2,10 @@ export * from './coders'
export * from './common'
export * from './watcher'
export * from './l2context'
export * from './events'
export * from './batches'
export * from './bcfg'
export * from './fees'
export * from './provider'
export * from './alias'
export * from './types'
export * from './bn'
......@@ -5,8 +5,8 @@ import {
fromHexString,
toHexString,
toRpcHexString,
EventArgsSequencerBatchAppended,
} from '@eth-optimism/core-utils'
import { SequencerBatchAppendedEvent } from '@eth-optimism/contracts/dist/types/CanonicalTransactionChain'
/* Imports: Internal */
import {
......@@ -21,7 +21,7 @@ import { SEQUENCER_GAS_LIMIT, parseSignatureVParam } from '../../../utils'
import { MissingElementError } from './errors'
export const handleEventsSequencerBatchAppended: EventHandlerSet<
EventArgsSequencerBatchAppended,
SequencerBatchAppendedEvent,
SequencerBatchAppendedExtraData,
SequencerBatchAppendedParsedEvent
> = {
......
/* Imports: External */
import { StateBatchAppendedEvent } from '@eth-optimism/contracts/dist/types/StateCommitmentChain'
import { getContractFactory } from '@eth-optimism/contracts'
import { EventArgsStateBatchAppended } from '@eth-optimism/core-utils'
import { BigNumber } from 'ethers'
/* Imports: Internal */
......@@ -14,7 +14,7 @@ import {
import { MissingElementError } from './errors'
export const handleEventsStateBatchAppended: EventHandlerSet<
EventArgsStateBatchAppended,
StateBatchAppendedEvent,
StateBatchAppendedExtraData,
StateBatchAppendedParsedEvent
> = {
......
import { EventArgsTransactionEnqueued } from '@eth-optimism/core-utils'
/* Imports: External */
import { BigNumber } from 'ethers'
import { TransactionEnqueuedEvent } from '@eth-optimism/contracts/dist/types/CanonicalTransactionChain'
/* Imports: Internal */
import { BigNumber } from 'ethers'
import { EnqueueEntry, EventHandlerSet } from '../../../types'
import { MissingElementError } from './errors'
export const handleEventsTransactionEnqueued: EventHandlerSet<
EventArgsTransactionEnqueued,
TransactionEnqueuedEvent,
null,
EnqueueEntry
> = {
......
/* Imports: External */
import { fromHexString, FallbackProvider } from '@eth-optimism/core-utils'
import { BaseService, Metrics } from '@eth-optimism/common-ts'
import { TypedEvent } from '@eth-optimism/contracts/dist/types/common'
import { BaseProvider } from '@ethersproject/providers'
import { LevelUp } from 'levelup'
import { constants } from 'ethers'
......@@ -15,7 +16,7 @@ import {
loadContract,
validators,
} from '../../utils'
import { TypedEthersEvent, EventHandlerSet } from '../../types'
import { EventHandlerSet } from '../../types'
import { handleEventsTransactionEnqueued } from './handlers/transaction-enqueued'
import { handleEventsSequencerBatchAppended } from './handlers/sequencer-batch-appended'
import { handleEventsStateBatchAppended } from './handlers/state-batch-appended'
......@@ -389,9 +390,7 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> {
for (const eventRange of eventRanges) {
// Find all relevant events within the range.
const events: TypedEthersEvent<any>[] = await this.state.contracts[
contractName
]
const events: TypedEvent[] = await this.state.contracts[contractName]
.attach(eventRange.address)
.queryFilter(
this.state.contracts[contractName].filters[eventName](),
......
import { BaseProvider } from '@ethersproject/providers'
import { BigNumber, Event } from 'ethers'
import { BigNumber } from 'ethers'
import { TypedEvent } from '@eth-optimism/contracts/dist/types/common'
import { TransportDB } from '../db/transport-db'
import {
......@@ -9,29 +10,29 @@ import {
StateRootEntry,
} from './database-types'
export type TypedEthersEvent<T> = Event & {
args: T
}
export type GetExtraDataHandler<TEventArgs, TExtraData> = (
event?: TypedEthersEvent<TEventArgs>,
export type GetExtraDataHandler<TEvent extends TypedEvent, TExtraData> = (
event?: TEvent,
l1RpcProvider?: BaseProvider
) => Promise<TExtraData>
export type ParseEventHandler<TEventArgs, TExtraData, TParsedEvent> = (
event: TypedEthersEvent<TEventArgs>,
extraData: TExtraData,
l2ChainId: number
) => TParsedEvent
export type ParseEventHandler<
TEvent extends TypedEvent,
TExtraData,
TParsedEvent
> = (event: TEvent, extraData: TExtraData, l2ChainId: number) => TParsedEvent
export type StoreEventHandler<TParsedEvent> = (
parsedEvent: TParsedEvent,
db: TransportDB
) => Promise<void>
export interface EventHandlerSet<TEventArgs, TExtraData, TParsedEvent> {
getExtraData: GetExtraDataHandler<TEventArgs, TExtraData>
parseEvent: ParseEventHandler<TEventArgs, TExtraData, TParsedEvent>
export interface EventHandlerSet<
TEvent extends TypedEvent,
TExtraData,
TParsedEvent
> {
getExtraData: GetExtraDataHandler<TEvent, TExtraData>
parseEvent: ParseEventHandler<TEvent, TExtraData, TParsedEvent>
storeEvent: StoreEventHandler<TParsedEvent>
}
......
......@@ -2,8 +2,14 @@
import { ethers } from 'ethers'
export const parseSignatureVParam = (
v: number | ethers.BigNumber,
v: number | ethers.BigNumber | string,
chainId: number
): number => {
return ethers.BigNumber.from(v).toNumber() - 2 * chainId - 35
v = ethers.BigNumber.from(v).toNumber()
// Handle unprotected transactions
if (v === 27 || v === 28) {
return v
}
// Handle EIP155 transactions
return v - 2 * chainId - 35
}
......@@ -2686,15 +2686,18 @@
dependencies:
ethers "^5.0.2"
"@typechain/ethers-v5@^7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-7.0.1.tgz#f9ae60ae5bd9e8ea8a996f66244147e8e74034ae"
integrity sha512-mXEJ7LG0pOYO+MRPkHtbf30Ey9X2KAsU0wkeoVvjQIn7iAY6tB3k3s+82bbmJAUMyENbQ04RDOZit36CgSG6Gg==
"@typechain/ethers-v5@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-8.0.2.tgz#0f2cc0db1513cb02e3e78929139216b153c9d2c0"
integrity sha512-oRMA3X5UWrsUiNb/lFTusa8xBpw6CckOHAk7sZBHeDQh4tAp+ZU24wdwdURcOtPnagzdCv5Dvl1qlD038brf1A==
dependencies:
lodash "^4.17.15"
ts-essentials "^7.0.1"
"@typechain/hardhat@^2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-2.3.0.tgz#dc7f29281637b38b77c7c046ae82700703395d0f"
integrity sha512-zERrtNol86L4DX60ktnXxP7Cq8rSZHPaQvsChyiQQVuvVs2FTLm24Yi+MYnfsIdbUBIXZG7SxDWhtCF5I0tJNQ==
"@typechain/hardhat@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-3.0.0.tgz#a94396855506d42b98d6e7c09f87ca14180d6c64"
integrity sha512-FpnIIXkDXm54XCHI/Z2iOet7h1MrFSvZfuljX9Uzc6FEjEfb01Tuzu8ywe2iquD3g5JXqovgdv+M54L/2Z6jkg==
dependencies:
fs-extra "^9.1.0"
......@@ -15170,10 +15173,10 @@ typechain@^3.0.0:
ts-essentials "^6.0.3"
ts-generator "^0.1.1"
typechain@^5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/typechain/-/typechain-5.1.2.tgz#c8784d6155a8e69397ca47f438a3b4fb2aa939da"
integrity sha512-FuaCxJd7BD3ZAjVJoO+D6TnqKey3pQdsqOBsC83RKYWKli5BDhdf0TPkwfyjt20TUlZvOzJifz+lDwXsRkiSKA==
typechain@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/typechain/-/typechain-6.0.2.tgz#0a1d6328aa934dfd66c8941cd94c1d3491cc34f0"
integrity sha512-fphYQ2+r5z38eq02qr4KjgU9/Xdda8Cj+eV2QHSRXhDPyhEVv+ln9123iGjjDLEhGyROb3DQkbvjm3Dl0GtUeQ==
dependencies:
"@types/prettier" "^2.1.1"
command-line-args "^4.0.7"
......
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