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: ...@@ -12,11 +12,6 @@ on:
pull_request: pull_request:
paths: paths:
- 'go/batch-submitter/*' - 'go/batch-submitter/*'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
workflow_dispatch: workflow_dispatch:
defaults: defaults:
......
...@@ -12,11 +12,6 @@ on: ...@@ -12,11 +12,6 @@ on:
pull_request: pull_request:
paths: paths:
- 'go/gas-oracle/**' - 'go/gas-oracle/**'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
workflow_dispatch: workflow_dispatch:
defaults: defaults:
......
name: geth unit tests name: geth unit tests
on: on:
push: push:
paths: paths:
...@@ -13,11 +12,6 @@ on: ...@@ -13,11 +12,6 @@ on:
pull_request: pull_request:
paths: paths:
- 'l2geth/**' - 'l2geth/**'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
workflow_dispatch: workflow_dispatch:
defaults: defaults:
......
...@@ -13,11 +13,6 @@ on: ...@@ -13,11 +13,6 @@ on:
paths: paths:
- 'go/gas-oracle/**' - 'go/gas-oracle/**'
- 'go/batch-submitter/**' - 'go/batch-submitter/**'
branches:
- 'master'
- 'develop'
- '*rc'
- 'regenesis/*'
jobs: jobs:
golangci: golangci:
name: lint name: lint
......
...@@ -55,7 +55,7 @@ jobs: ...@@ -55,7 +55,7 @@ jobs:
if: failure() if: failure()
uses: jwalton/gh-docker-logs@v1 uses: jwalton/gh-docker-logs@v1
with: 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' dest: '/home/runner/logs'
- name: Tar logs - name: Tar logs
......
...@@ -7,6 +7,9 @@ LDFLAGSSTRING +=-X main.GitDate=$(GITDATE) ...@@ -7,6 +7,9 @@ LDFLAGSSTRING +=-X main.GitDate=$(GITDATE)
LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION) LDFLAGSSTRING +=-X main.GitVersion=$(GITVERSION)
LDFLAGS := -ldflags "$(LDFLAGSSTRING)" 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: batch-submitter:
env GO111MODULE=on go build -v $(LDFLAGS) ./cmd/batch-submitter env GO111MODULE=on go build -v $(LDFLAGS) ./cmd/batch-submitter
...@@ -19,8 +22,45 @@ test: ...@@ -19,8 +22,45 @@ test:
lint: lint:
golangci-lint run ./... 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: \ .PHONY: \
batch-submitter \ batch-submitter \
bindings \
bindings-ctc \
bindings-scc \
clean \ clean \
test \ test \
lint 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 ...@@ -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/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/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/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/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 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
......
...@@ -19,7 +19,7 @@ import ( ...@@ -19,7 +19,7 @@ import (
var ( var (
txSendCounter = metrics.NewRegisteredCounter("tx/send", ometrics.DefaultRegistry) txSendCounter = metrics.NewRegisteredCounter("tx/send", ometrics.DefaultRegistry)
txNotSignificantCounter = metrics.NewRegisteredCounter("tx/not-significant", 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) txConfTimer = metrics.NewRegisteredTimer("tx/confirmed", ometrics.DefaultRegistry)
txSendTimer = metrics.NewRegisteredTimer("tx/send", ometrics.DefaultRegistry) txSendTimer = metrics.NewRegisteredTimer("tx/send", ometrics.DefaultRegistry)
) )
......
...@@ -13,9 +13,17 @@ RUN make proxyd ...@@ -13,9 +13,17 @@ RUN make proxyd
FROM alpine:3.14.2 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 EXPOSE 8080
VOLUME /etc/proxyd VOLUME /etc/proxyd
COPY --from=builder /app/bin/proxyd /bin/proxyd COPY --from=builder /app/bin/proxyd /bin/proxyd
ENTRYPOINT ["/bin/entrypoint.sh"]
CMD ["/bin/proxyd", "/etc/proxyd/proxyd.toml"] CMD ["/bin/proxyd", "/etc/proxyd/proxyd.toml"]
...@@ -21,3 +21,6 @@ See `metrics.go` for a list of all available metrics. ...@@ -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. 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 ...@@ -3,6 +3,7 @@ package proxyd
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
...@@ -14,6 +15,7 @@ import ( ...@@ -14,6 +15,7 @@ import (
"math" "math"
"math/rand" "math/rand"
"net/http" "net/http"
"strconv"
"time" "time"
) )
...@@ -26,34 +28,42 @@ var ( ...@@ -26,34 +28,42 @@ var (
ErrInvalidRequest = &RPCErr{ ErrInvalidRequest = &RPCErr{
Code: -32601, Code: -32601,
Message: "invalid request", Message: "invalid request",
HTTPErrorCode: 400,
} }
ErrParseErr = &RPCErr{ ErrParseErr = &RPCErr{
Code: -32700, Code: -32700,
Message: "parse error", Message: "parse error",
HTTPErrorCode: 400,
} }
ErrInternal = &RPCErr{ ErrInternal = &RPCErr{
Code: JSONRPCErrorInternal, Code: JSONRPCErrorInternal,
Message: "internal error", Message: "internal error",
HTTPErrorCode: 500,
} }
ErrMethodNotWhitelisted = &RPCErr{ ErrMethodNotWhitelisted = &RPCErr{
Code: JSONRPCErrorInternal - 1, Code: JSONRPCErrorInternal - 1,
Message: "rpc method is not whitelisted", Message: "rpc method is not whitelisted",
HTTPErrorCode: 403,
} }
ErrBackendOffline = &RPCErr{ ErrBackendOffline = &RPCErr{
Code: JSONRPCErrorInternal - 10, Code: JSONRPCErrorInternal - 10,
Message: "backend offline", Message: "backend offline",
HTTPErrorCode: 503,
} }
ErrNoBackends = &RPCErr{ ErrNoBackends = &RPCErr{
Code: JSONRPCErrorInternal - 11, Code: JSONRPCErrorInternal - 11,
Message: "no backends available for method", Message: "no backends available for method",
HTTPErrorCode: 503,
} }
ErrBackendOverCapacity = &RPCErr{ ErrBackendOverCapacity = &RPCErr{
Code: JSONRPCErrorInternal - 12, Code: JSONRPCErrorInternal - 12,
Message: "backend is over capacity", Message: "backend is over capacity",
HTTPErrorCode: 429,
} }
ErrBackendBadResponse = &RPCErr{ ErrBackendBadResponse = &RPCErr{
Code: JSONRPCErrorInternal - 13, Code: JSONRPCErrorInternal - 13,
Message: "backend returned an invalid response", Message: "backend returned an invalid response",
HTTPErrorCode: 500,
} }
) )
...@@ -63,7 +73,7 @@ type Backend struct { ...@@ -63,7 +73,7 @@ type Backend struct {
wsURL string wsURL string
authUsername string authUsername string
authPassword string authPassword string
redis Redis rateLimiter RateLimiter
client *http.Client client *http.Client
dialer *websocket.Dialer dialer *websocket.Dialer
maxRetries int maxRetries int
...@@ -118,18 +128,27 @@ func WithMaxWSConns(maxConns int) BackendOpt { ...@@ -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( func NewBackend(
name string, name string,
rpcURL string, rpcURL string,
wsURL string, wsURL string,
redis Redis, rateLimiter RateLimiter,
opts ...BackendOpt, opts ...BackendOpt,
) *Backend { ) *Backend {
backend := &Backend{ backend := &Backend{
Name: name, Name: name,
rpcURL: rpcURL, rpcURL: rpcURL,
wsURL: wsURL, wsURL: wsURL,
redis: redis, rateLimiter: rateLimiter,
maxResponseSize: math.MaxInt64, maxResponseSize: math.MaxInt64,
client: &http.Client{ client: &http.Client{
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
...@@ -160,7 +179,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) { ...@@ -160,7 +179,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) {
for i := 0; i <= b.maxRetries; i++ { for i := 0; i <= b.maxRetries; i++ {
RecordRPCForward(ctx, b.Name, req.Method, RPCRequestSourceHTTP) RecordRPCForward(ctx, b.Name, req.Method, RPCRequestSourceHTTP)
respTimer := prometheus.NewTimer(rpcBackendRequestDurationSumm.WithLabelValues(b.Name, req.Method)) respTimer := prometheus.NewTimer(rpcBackendRequestDurationSumm.WithLabelValues(b.Name, req.Method))
res, err := b.doForward(req) res, err := b.doForward(ctx, req)
if err != nil { if err != nil {
lastError = err lastError = err
log.Warn( log.Warn(
...@@ -179,6 +198,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) { ...@@ -179,6 +198,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) {
RecordRPCError(ctx, b.Name, req.Method, res.Error) RecordRPCError(ctx, b.Name, req.Method, res.Error)
log.Info( log.Info(
"backend responded with RPC error", "backend responded with RPC error",
"backend", b.Name,
"code", res.Error.Code, "code", res.Error.Code,
"msg", res.Error.Message, "msg", res.Error.Message,
"req_id", GetReqID(ctx), "req_id", GetReqID(ctx),
...@@ -187,6 +207,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) { ...@@ -187,6 +207,7 @@ func (b *Backend) Forward(ctx context.Context, req *RPCReq) (*RPCRes, error) {
) )
} else { } else {
log.Info("forwarded RPC request", log.Info("forwarded RPC request",
"backend", b.Name,
"method", req.Method, "method", req.Method,
"auth", GetAuthCtx(ctx), "auth", GetAuthCtx(ctx),
"req_id", GetReqID(ctx), "req_id", GetReqID(ctx),
...@@ -210,7 +231,7 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet ...@@ -210,7 +231,7 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet
backendConn, _, err := b.dialer.Dial(b.wsURL, nil) backendConn, _, err := b.dialer.Dial(b.wsURL, nil)
if err != nil { if err != nil {
b.setOffline() 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) log.Error("error decrementing backend ws conns", "name", b.Name, "err", err)
} }
return nil, wrapErr(err, "error dialing backend") return nil, wrapErr(err, "error dialing backend")
...@@ -221,7 +242,7 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet ...@@ -221,7 +242,7 @@ func (b *Backend) ProxyWS(clientConn *websocket.Conn, methodWhitelist *StringSet
} }
func (b *Backend) Online() bool { func (b *Backend) Online() bool {
online, err := b.redis.IsBackendOnline(b.Name) online, err := b.rateLimiter.IsBackendOnline(b.Name)
if err != nil { if err != nil {
log.Warn( log.Warn(
"error getting backend availability, assuming it is offline", "error getting backend availability, assuming it is offline",
...@@ -238,7 +259,7 @@ func (b *Backend) IsRateLimited() bool { ...@@ -238,7 +259,7 @@ func (b *Backend) IsRateLimited() bool {
return false return false
} }
usedLimit, err := b.redis.IncBackendRPS(b.Name) usedLimit, err := b.rateLimiter.IncBackendRPS(b.Name)
if err != nil { if err != nil {
log.Error( log.Error(
"error getting backend used rate limit, assuming limit is exhausted", "error getting backend used rate limit, assuming limit is exhausted",
...@@ -256,7 +277,7 @@ func (b *Backend) IsWSSaturated() bool { ...@@ -256,7 +277,7 @@ func (b *Backend) IsWSSaturated() bool {
return false return false
} }
incremented, err := b.redis.IncBackendWSConns(b.Name, b.maxWSConns) incremented, err := b.rateLimiter.IncBackendWSConns(b.Name, b.maxWSConns)
if err != nil { if err != nil {
log.Error( log.Error(
"error getting backend used ws conns, assuming limit is exhausted", "error getting backend used ws conns, assuming limit is exhausted",
...@@ -270,7 +291,7 @@ func (b *Backend) IsWSSaturated() bool { ...@@ -270,7 +291,7 @@ func (b *Backend) IsWSSaturated() bool {
} }
func (b *Backend) setOffline() { func (b *Backend) setOffline() {
err := b.redis.SetBackendOffline(b.Name, b.outOfServiceInterval) err := b.rateLimiter.SetBackendOffline(b.Name, b.outOfServiceInterval)
if err != nil { if err != nil {
log.Warn( log.Warn(
"error setting backend offline", "error setting backend offline",
...@@ -280,7 +301,7 @@ func (b *Backend) setOffline() { ...@@ -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) body := mustMarshalJSON(rpcReq)
httpReq, err := http.NewRequest("POST", b.rpcURL, bytes.NewReader(body)) httpReq, err := http.NewRequest("POST", b.rpcURL, bytes.NewReader(body))
...@@ -299,6 +320,13 @@ func (b *Backend) doForward(rpcReq *RPCReq) (*RPCRes, error) { ...@@ -299,6 +320,13 @@ func (b *Backend) doForward(rpcReq *RPCReq) (*RPCRes, error) {
return nil, wrapErr(err, "error in backend request") 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 // Alchemy returns a 400 on bad JSONs, so handle that case
if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 { if httpRes.StatusCode != 200 && httpRes.StatusCode != 400 {
return nil, fmt.Errorf("response code %d", httpRes.StatusCode) return nil, fmt.Errorf("response code %d", httpRes.StatusCode)
...@@ -315,6 +343,12 @@ func (b *Backend) doForward(rpcReq *RPCReq) (*RPCRes, error) { ...@@ -315,6 +343,12 @@ func (b *Backend) doForward(rpcReq *RPCReq) (*RPCRes, error) {
return nil, ErrBackendBadResponse 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 return res, nil
} }
...@@ -556,7 +590,7 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { ...@@ -556,7 +590,7 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) {
func (w *WSProxier) close() { func (w *WSProxier) close() {
w.clientConn.Close() w.clientConn.Close()
w.backendConn.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) log.Error("error decrementing backend ws conns", "name", w.backend.Name, "err", err)
} }
activeBackendWsConnsGauge.WithLabelValues(w.backend.Name).Dec() activeBackendWsConnsGauge.WithLabelValues(w.backend.Name).Dec()
......
...@@ -32,6 +32,9 @@ type BackendConfig struct { ...@@ -32,6 +32,9 @@ type BackendConfig struct {
WSURL string `toml:"ws_url"` WSURL string `toml:"ws_url"`
MaxRPS int `toml:"max_rps"` 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 BackendsConfig map[string]*BackendConfig
......
#!/bin/sh
echo "Updating CA certificates."
update-ca-certificates
echo "Running CMD."
exec "$@"
\ No newline at end of file
...@@ -52,6 +52,12 @@ username = "" ...@@ -52,6 +52,12 @@ username = ""
password = "" password = ""
max_rps = 3 max_rps = 3
max_ws_conns = 1 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] [backends.alchemy]
# The URL to contact the backend at. # The URL to contact the backend at.
......
...@@ -38,6 +38,17 @@ var ( ...@@ -38,6 +38,17 @@ var (
"source", "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{ rpcErrorsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: MetricsNamespace, Namespace: MetricsNamespace,
Name: "rpc_errors_total", Name: "rpc_errors_total",
...@@ -101,6 +112,14 @@ var ( ...@@ -101,6 +112,14 @@ var (
Help: "Count of total HTTP requests.", 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{ httpRequestDurationSumm = promauto.NewSummary(prometheus.SummaryOpts{
Namespace: MetricsNamespace, Namespace: MetricsNamespace,
Name: "http_request_duration_seconds", Name: "http_request_duration_seconds",
......
package proxyd package proxyd
import ( import (
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -29,10 +30,17 @@ func Start(config *Config) error { ...@@ -29,10 +30,17 @@ func Start(config *Config) error {
} }
} }
redis, err := NewRedis(config.Redis.URL) 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 { if err != nil {
return err return err
} }
}
backendNames := make([]string, 0) backendNames := make([]string, 0)
backendsByName := make(map[string]*Backend) backendsByName := make(map[string]*Backend)
...@@ -68,7 +76,15 @@ func Start(config *Config) error { ...@@ -68,7 +76,15 @@ func Start(config *Config) error {
if cfg.Password != "" { if cfg.Password != "" {
opts = append(opts, WithBasicAuth(cfg.Username, 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) backendNames = append(backendNames, name)
backendsByName[name] = back backendsByName[name] = back
log.Info("configured backend", "name", name, "rpc_url", cfg.RPCURL, "ws_url", cfg.WSURL) log.Info("configured backend", "name", name, "rpc_url", cfg.RPCURL, "ws_url", cfg.WSURL)
...@@ -152,7 +168,7 @@ func Start(config *Config) error { ...@@ -152,7 +168,7 @@ func Start(config *Config) error {
recvSig := <-sig recvSig := <-sig
log.Info("caught signal, shutting down", "signal", recvSig) log.Info("caught signal, shutting down", "signal", recvSig)
srv.Shutdown() 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) log.Error("error flushing backend ws conns", "err", err)
} }
return nil return nil
...@@ -161,3 +177,24 @@ func Start(config *Config) error { ...@@ -161,3 +177,24 @@ func Start(config *Config) error {
func secondsToDuration(seconds int) time.Duration { func secondsToDuration(seconds int) time.Duration {
return time.Duration(seconds) * time.Second 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 ...@@ -40,7 +40,7 @@ end
return false return false
` `
type Redis interface { type RateLimiter interface {
IsBackendOnline(name string) (bool, error) IsBackendOnline(name string) (bool, error)
SetBackendOffline(name string, duration time.Duration) error SetBackendOffline(name string, duration time.Duration) error
IncBackendRPS(name string) (int, error) IncBackendRPS(name string) (int, error)
...@@ -49,14 +49,14 @@ type Redis interface { ...@@ -49,14 +49,14 @@ type Redis interface {
FlushBackendWSConns(names []string) error FlushBackendWSConns(names []string) error
} }
type RedisImpl struct { type RedisRateLimiter struct {
rdb *redis.Client rdb *redis.Client
randID string randID string
touchKeys map[string]time.Duration touchKeys map[string]time.Duration
tkMtx sync.Mutex tkMtx sync.Mutex
} }
func NewRedis(url string) (Redis, error) { func NewRedisRateLimiter(url string) (RateLimiter, error) {
opts, err := redis.ParseURL(url) opts, err := redis.ParseURL(url)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -65,7 +65,7 @@ func NewRedis(url string) (Redis, error) { ...@@ -65,7 +65,7 @@ func NewRedis(url string) (Redis, error) {
if err := rdb.Ping(context.Background()).Err(); err != nil { if err := rdb.Ping(context.Background()).Err(); err != nil {
return nil, wrapErr(err, "error connecting to redis") return nil, wrapErr(err, "error connecting to redis")
} }
out := &RedisImpl{ out := &RedisRateLimiter{
rdb: rdb, rdb: rdb,
randID: randStr(20), randID: randStr(20),
touchKeys: make(map[string]time.Duration), touchKeys: make(map[string]time.Duration),
...@@ -74,7 +74,7 @@ func NewRedis(url string) (Redis, error) { ...@@ -74,7 +74,7 @@ func NewRedis(url string) (Redis, error) {
return out, nil 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() exists, err := r.rdb.Exists(context.Background(), fmt.Sprintf("backend:%s:offline", name)).Result()
if err != nil { if err != nil {
RecordRedisError("IsBackendOnline") RecordRedisError("IsBackendOnline")
...@@ -84,7 +84,7 @@ func (r *RedisImpl) IsBackendOnline(name string) (bool, error) { ...@@ -84,7 +84,7 @@ func (r *RedisImpl) IsBackendOnline(name string) (bool, error) {
return exists == 0, nil 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( err := r.rdb.SetEX(
context.Background(), context.Background(),
fmt.Sprintf("backend:%s:offline", name), fmt.Sprintf("backend:%s:offline", name),
...@@ -98,7 +98,7 @@ func (r *RedisImpl) SetBackendOffline(name string, duration time.Duration) error ...@@ -98,7 +98,7 @@ func (r *RedisImpl) SetBackendOffline(name string, duration time.Duration) error
return nil return nil
} }
func (r *RedisImpl) IncBackendRPS(name string) (int, error) { func (r *RedisRateLimiter) IncBackendRPS(name string) (int, error) {
cmd := r.rdb.Eval( cmd := r.rdb.Eval(
context.Background(), context.Background(),
MaxRPSScript, MaxRPSScript,
...@@ -112,7 +112,7 @@ func (r *RedisImpl) IncBackendRPS(name string) (int, error) { ...@@ -112,7 +112,7 @@ func (r *RedisImpl) IncBackendRPS(name string) (int, error) {
return rps, nil 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) connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name)
r.tkMtx.Lock() r.tkMtx.Lock()
r.touchKeys[connsKey] = 5 * time.Minute r.touchKeys[connsKey] = 5 * time.Minute
...@@ -138,7 +138,7 @@ func (r *RedisImpl) IncBackendWSConns(name string, max int) (bool, error) { ...@@ -138,7 +138,7 @@ func (r *RedisImpl) IncBackendWSConns(name string, max int) (bool, error) {
return incremented, nil 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) connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name)
err := r.rdb.Decr(context.Background(), connsKey).Err() err := r.rdb.Decr(context.Background(), connsKey).Err()
if err != nil { if err != nil {
...@@ -148,7 +148,7 @@ func (r *RedisImpl) DecBackendWSConns(name string) error { ...@@ -148,7 +148,7 @@ func (r *RedisImpl) DecBackendWSConns(name string) error {
return nil return nil
} }
func (r *RedisImpl) FlushBackendWSConns(names []string) error { func (r *RedisRateLimiter) FlushBackendWSConns(names []string) error {
ctx := context.Background() ctx := context.Background()
for _, name := range names { for _, name := range names {
connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name) connsKey := fmt.Sprintf("proxy:%s:wsconns:%s", r.randID, name)
...@@ -168,7 +168,7 @@ func (r *RedisImpl) FlushBackendWSConns(names []string) error { ...@@ -168,7 +168,7 @@ func (r *RedisImpl) FlushBackendWSConns(names []string) error {
return nil return nil
} }
func (r *RedisImpl) touch() { func (r *RedisRateLimiter) touch() {
for { for {
r.tkMtx.Lock() r.tkMtx.Lock()
for key, dur := range r.touchKeys { for key, dur := range r.touchKeys {
...@@ -182,6 +182,76 @@ func (r *RedisImpl) touch() { ...@@ -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 { func randStr(l int) string {
b := make([]byte, l) b := make([]byte, l)
if _, err := rand.Read(b); err != nil { if _, err := rand.Read(b); err != nil {
......
...@@ -27,6 +27,7 @@ func (r *RPCRes) IsError() bool { ...@@ -27,6 +27,7 @@ func (r *RPCRes) IsError() bool {
type RPCErr struct { type RPCErr struct {
Code int `json:"code"` Code int `json:"code"`
Message string `json:"message"` Message string `json:"message"`
HTTPErrorCode int `json:"-"`
} }
func (r *RPCErr) Error() string { func (r *RPCErr) Error() string {
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/rs/cors" "github.com/rs/cors"
"io" "io"
"net/http" "net/http"
"strconv"
"time" "time"
) )
...@@ -105,7 +106,12 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { ...@@ -105,7 +106,12 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
return 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)) req, err := ParseRPCReq(io.LimitReader(r.Body, s.maxBodySize))
if err != nil { if err != nil {
...@@ -200,6 +206,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ...@@ -200,6 +206,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
// but someone sends in an auth key anyway // but someone sends in an auth key anyway
if authorization != "" { if authorization != "" {
log.Info("blocked authenticated request against unauthenticated proxy") log.Info("blocked authenticated request against unauthenticated proxy")
httpResponseCodesTotal.WithLabelValues("404").Inc()
w.WriteHeader(404) w.WriteHeader(404)
return nil return nil
} }
...@@ -212,6 +219,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ...@@ -212,6 +219,7 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
if authorization == "" || s.authenticatedPaths[authorization] == "" { if authorization == "" || s.authenticatedPaths[authorization] == "" {
log.Info("blocked unauthorized request", "authorization", authorization) log.Info("blocked unauthorized request", "authorization", authorization)
httpResponseCodesTotal.WithLabelValues("401").Inc()
w.WriteHeader(401) w.WriteHeader(401)
return nil return nil
} }
...@@ -225,21 +233,29 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ...@@ -225,21 +233,29 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
} }
func writeRPCError(w http.ResponseWriter, id *int, err error) { func writeRPCError(w http.ResponseWriter, id *int, err error) {
enc := json.NewEncoder(w) var res *RPCRes
w.WriteHeader(200)
var body *RPCRes
if r, ok := err.(*RPCErr); ok { if r, ok := err.(*RPCErr); ok {
body = NewRPCErrorRes(id, r) res = NewRPCErrorRes(id, r)
} else { } else {
body = NewRPCErrorRes(id, &RPCErr{ res = NewRPCErrorRes(id, &RPCErr{
Code: JSONRPCErrorInternal, Code: JSONRPCErrorInternal,
Message: "internal error", Message: "internal error",
}) })
} }
if err := enc.Encode(body); err != nil { writeRPCRes(w, res)
log.Error("error writing rpc error", "err", err) }
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 { 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 @@ ...@@ -31,6 +31,7 @@
"@eth-optimism/contracts": "0.5.4", "@eth-optimism/contracts": "0.5.4",
"@eth-optimism/core-utils": "0.7.2", "@eth-optimism/core-utils": "0.7.2",
"@eth-optimism/message-relayer": "0.2.4", "@eth-optimism/message-relayer": "0.2.4",
"@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0", "@ethersproject/transactions": "^5.4.0",
"@nomiclabs/hardhat-ethers": "^2.0.2", "@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', () => { ...@@ -84,16 +84,19 @@ describe('Basic RPC tests', () => {
).to.be.rejectedWith('invalid transaction: invalid sender') ).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 = { const tx = {
...defaultTransactionFactory(), ...defaultTransactionFactory(),
nonce: await wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env), gasPrice: await gasPriceForL2(env),
chainId: null, // Disables EIP155 transaction signing. chainId: null, // Disables EIP155 transaction signing.
} }
const signed = await wallet.signTransaction(tx)
const response = await provider.sendTransaction(signed)
await expect( expect(response.chainId).to.equal(0)
provider.sendTransaction(await wallet.signTransaction(tx)) const v = response.v
).to.be.rejectedWith('Cannot submit unprotected transaction') expect(v === 27 || v === 28).to.be.true
}) })
it('should accept a transaction with a value', async () => { it('should accept a transaction with a value', async () => {
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
getAddressManager, getAddressManager,
l1Provider, l1Provider,
l2Provider, l2Provider,
replicaProvider,
l1Wallet, l1Wallet,
l2Wallet, l2Wallet,
fundUser, fundUser,
...@@ -52,6 +53,7 @@ export class OptimismEnv { ...@@ -52,6 +53,7 @@ export class OptimismEnv {
// The providers // The providers
l1Provider: providers.JsonRpcProvider l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider l2Provider: providers.JsonRpcProvider
replicaProvider: providers.JsonRpcProvider
constructor(args: any) { constructor(args: any) {
this.addressManager = args.addressManager this.addressManager = args.addressManager
...@@ -67,6 +69,7 @@ export class OptimismEnv { ...@@ -67,6 +69,7 @@ export class OptimismEnv {
this.l2Wallet = args.l2Wallet this.l2Wallet = args.l2Wallet
this.l1Provider = args.l1Provider this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider this.l2Provider = args.l2Provider
this.replicaProvider = args.replicaProvider
this.ctc = args.ctc this.ctc = args.ctc
this.scc = args.scc this.scc = args.scc
} }
...@@ -126,6 +129,7 @@ export class OptimismEnv { ...@@ -126,6 +129,7 @@ export class OptimismEnv {
l2Wallet, l2Wallet,
l1Provider, l1Provider,
l2Provider, l2Provider,
replicaProvider,
}) })
} }
......
...@@ -55,13 +55,19 @@ const env = cleanEnv(process.env, { ...@@ -55,13 +55,19 @@ const env = cleanEnv(process.env, {
export const l1Provider = new providers.JsonRpcProvider(env.L1_URL) export const l1Provider = new providers.JsonRpcProvider(env.L1_URL)
l1Provider.pollingInterval = env.L1_POLLING_INTERVAL 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 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 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 replicaProvider.pollingInterval = env.REPLICA_POLLING_INTERVAL
// The sequencer private key which is funded on L1 // The sequencer private key which is funded on L1
......
...@@ -1573,9 +1573,6 @@ func (args *SendTxArgs) toTransaction() *types.Transaction { ...@@ -1573,9 +1573,6 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
// SubmitTransaction is a helper function that submits tx to txPool and logs a message. // 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) { 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 { if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
......
...@@ -135,7 +135,7 @@ type RollupClient interface { ...@@ -135,7 +135,7 @@ type RollupClient interface {
// Client is an HTTP based RollupClient // Client is an HTTP based RollupClient
type Client struct { type Client struct {
client *resty.Client client *resty.Client
signer *types.EIP155Signer chainID *big.Int
} }
// TransactionResponse represents the response from the remote server when // TransactionResponse represents the response from the remote server when
...@@ -166,11 +166,10 @@ func NewClient(url string, chainID *big.Int) *Client { ...@@ -166,11 +166,10 @@ func NewClient(url string, chainID *big.Int) *Client {
} }
return nil return nil
}) })
signer := types.NewEIP155Signer(chainID)
return &Client{ return &Client{
client: client, client: client,
signer: &signer, chainID: chainID,
} }
} }
...@@ -322,7 +321,7 @@ func (c *Client) GetLatestTransactionBatchIndex() (*uint64, error) { ...@@ -322,7 +321,7 @@ func (c *Client) GetLatestTransactionBatchIndex() (*uint64, error) {
// batchedTransactionToTransaction converts a transaction into a // batchedTransactionToTransaction converts a transaction into a
// types.Transaction that can be consumed by the SyncService // 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 // `nil` transactions are not found
if res == nil { if res == nil {
return nil, errElementNotFound return nil, errElementNotFound
...@@ -373,7 +372,15 @@ func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signe ...@@ -373,7 +372,15 @@ func batchedTransactionToTransaction(res *transaction, signer *types.EIP155Signe
sig := make([]byte, crypto.SignatureLength) sig := make([]byte, crypto.SignatureLength)
copy(sig[32-len(r):32], r) copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s) copy(sig[64-len(s):64], s)
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) sig[64] = byte(res.Decoded.Signature.V)
}
tx, err := tx.WithSignature(signer, sig[:]) tx, err := tx.WithSignature(signer, sig[:])
if err != nil { if err != nil {
...@@ -431,7 +438,7 @@ func (c *Client) GetTransaction(index uint64, backend Backend) (*types.Transacti ...@@ -431,7 +438,7 @@ func (c *Client) GetTransaction(index uint64, backend Backend) (*types.Transacti
if !ok { if !ok {
return nil, fmt.Errorf("could not get tx with index %d", index) 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 // GetLatestTransaction will get the latest transaction, meaning the transaction
...@@ -452,7 +459,7 @@ func (c *Client) GetLatestTransaction(backend Backend) (*types.Transaction, erro ...@@ -452,7 +459,7 @@ func (c *Client) GetLatestTransaction(backend Backend) (*types.Transaction, erro
return nil, errors.New("Cannot get latest transaction") 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 // GetEthContext will return the EthContext by block number
...@@ -564,7 +571,7 @@ func (c *Client) GetLatestTransactionBatch() (*Batch, []*types.Transaction, erro ...@@ -564,7 +571,7 @@ func (c *Client) GetLatestTransactionBatch() (*Batch, []*types.Transaction, erro
if !ok { if !ok {
return nil, nil, fmt.Errorf("Cannot parse transaction batch response") 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 // GetTransactionBatch will return the transaction batch by batch index
...@@ -584,19 +591,19 @@ func (c *Client) GetTransactionBatch(index uint64) (*Batch, []*types.Transaction ...@@ -584,19 +591,19 @@ func (c *Client) GetTransactionBatch(index uint64) (*Batch, []*types.Transaction
if !ok { if !ok {
return nil, nil, fmt.Errorf("Cannot parse transaction batch response") 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 // parseTransactionBatchResponse will turn a TransactionBatchResponse into a
// Batch and its corresponding types.Transactions // 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 { if txBatch == nil || txBatch.Batch == nil {
return nil, nil, errElementNotFound return nil, nil, errElementNotFound
} }
batch := txBatch.Batch batch := txBatch.Batch
txs := make([]*types.Transaction, len(txBatch.Transactions)) txs := make([]*types.Transaction, len(txBatch.Transactions))
for i, tx := range txBatch.Transactions { for i, tx := range txBatch.Transactions {
transaction, err := batchedTransactionToTransaction(tx, signer) transaction, err := batchedTransactionToTransaction(tx, chainID)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("Cannot parse transaction batch: %w", err) return nil, nil, fmt.Errorf("Cannot parse transaction batch: %w", err)
} }
......
version: "3" version: "3.4"
services: services:
l2geth: l2geth:
......
version: "3" version: "3.4"
services: services:
l1_chain: l1_chain:
image: ethereumoptimism/hardhat-node:${DOCKER_TAG:-prerelease-0.5.0-rc-7-ee217ce} image: ethereumoptimism/hardhat-node:${DOCKER_TAG:-prerelease-0.5.0-rc-7-ee217ce}
......
version: "3" version: "3.4"
services: services:
rpc-proxy: rpc-proxy:
depends_on: depends_on:
......
version: "3" version: "3.4"
services: services:
# this is a helper service used because there's no official hardhat image # this is a helper service used because there's no official hardhat image
...@@ -165,7 +165,7 @@ services: ...@@ -165,7 +165,7 @@ services:
depends_on: depends_on:
- dtl - dtl
deploy: deploy:
replicas: 0 replicas: 1
build: build:
context: .. context: ..
dockerfile: ./ops/docker/Dockerfile.geth dockerfile: ./ops/docker/Dockerfile.geth
...@@ -181,8 +181,8 @@ services: ...@@ -181,8 +181,8 @@ services:
ETH1_CTC_DEPLOYMENT_HEIGHT: 8 ETH1_CTC_DEPLOYMENT_HEIGHT: 8
RETRIES: 60 RETRIES: 60
ports: ports:
- ${L2GETH_HTTP_PORT:-8549}:8545 - ${REPLICA_HTTP_PORT:-8549}:8545
- ${L2GETH_WS_PORT:-8550}:8546 - ${REPLICA_WS_PORT:-8550}:8546
integration_tests: integration_tests:
deploy: deploy:
...@@ -195,6 +195,8 @@ services: ...@@ -195,6 +195,8 @@ services:
environment: environment:
L1_URL: http://l1_chain:8545 L1_URL: http://l1_chain:8545
L2_URL: http://l2geth:8545 L2_URL: http://l2geth:8545
REPLICA_URL: http://replica:8545
VERIFIER_URL: http://verifier:8545
URL: http://deployer:8081/addresses.json URL: http://deployer:8081/addresses.json
ENABLE_GAS_REPORT: 1 ENABLE_GAS_REPORT: 1
NO_NETWORK: 1 NO_NETWORK: 1
......
...@@ -73,8 +73,8 @@ ...@@ -73,8 +73,8 @@
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "4.3.2", "@openzeppelin/contracts": "4.3.2",
"@openzeppelin/contracts-upgradeable": "4.3.2", "@openzeppelin/contracts-upgradeable": "4.3.2",
"@typechain/ethers-v5": "^7.0.1", "@typechain/ethers-v5": "^8.0.2",
"@typechain/hardhat": "^2.3.0", "@typechain/hardhat": "^3.0.0",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
"@types/lodash": "^4.14.168", "@types/lodash": "^4.14.168",
"@types/mkdirp": "^1.0.1", "@types/mkdirp": "^1.0.1",
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
"solidity-coverage": "^0.7.17", "solidity-coverage": "^0.7.17",
"ts-generator": "0.0.8", "ts-generator": "0.0.8",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typechain": "^5.1.0", "typechain": "^6.0.2",
"typescript": "^4.3.5", "typescript": "^4.3.5",
"yargs": "^16.2.0" "yargs": "^16.2.0"
}, },
......
import { ethers } from 'ethers' 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 = export const L1_TO_L2_ALIAS_OFFSET =
'0x1111000000000000000000000000000000001111' '0x1111000000000000000000000000000000001111'
export const bnToAddress = (bn: ethers.BigNumber | number): string => { /**
bn = ethers.BigNumber.from(bn) * Applies the L1 => L2 aliasing scheme to an address.
if (bn.isNegative()) { *
bn = ethers.BigNumber.from('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF') * @param address Address to apply the scheme to.
.add(bn) * @returns Address with the scheme applied.
.add(1) */
}
const addr = bn.toHexString().slice(2).padStart(40, '0')
return ethers.utils.getAddress(
'0x' + addr.slice(addr.length - 40, addr.length)
)
}
export const applyL1ToL2Alias = (address: string): string => { export const applyL1ToL2Alias = (address: string): string => {
if (!ethers.utils.isAddress(address)) { if (!ethers.utils.isAddress(address)) {
throw new Error(`not a valid address: ${address}`) throw new Error(`not a valid address: ${address}`)
...@@ -25,6 +24,12 @@ export const applyL1ToL2Alias = (address: string): string => { ...@@ -25,6 +24,12 @@ export const applyL1ToL2Alias = (address: string): string => {
return bnToAddress(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET)) 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 => { export const undoL1ToL2Alias = (address: string): string => {
if (!ethers.utils.isAddress(address)) { if (!ethers.utils.isAddress(address)) {
throw new Error(`not a valid address: ${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' ...@@ -2,10 +2,10 @@ export * from './coders'
export * from './common' export * from './common'
export * from './watcher' export * from './watcher'
export * from './l2context' export * from './l2context'
export * from './events'
export * from './batches' export * from './batches'
export * from './bcfg' export * from './bcfg'
export * from './fees' export * from './fees'
export * from './provider' export * from './provider'
export * from './alias' export * from './alias'
export * from './types' export * from './types'
export * from './bn'
...@@ -5,8 +5,8 @@ import { ...@@ -5,8 +5,8 @@ import {
fromHexString, fromHexString,
toHexString, toHexString,
toRpcHexString, toRpcHexString,
EventArgsSequencerBatchAppended,
} from '@eth-optimism/core-utils' } from '@eth-optimism/core-utils'
import { SequencerBatchAppendedEvent } from '@eth-optimism/contracts/dist/types/CanonicalTransactionChain'
/* Imports: Internal */ /* Imports: Internal */
import { import {
...@@ -21,7 +21,7 @@ import { SEQUENCER_GAS_LIMIT, parseSignatureVParam } from '../../../utils' ...@@ -21,7 +21,7 @@ import { SEQUENCER_GAS_LIMIT, parseSignatureVParam } from '../../../utils'
import { MissingElementError } from './errors' import { MissingElementError } from './errors'
export const handleEventsSequencerBatchAppended: EventHandlerSet< export const handleEventsSequencerBatchAppended: EventHandlerSet<
EventArgsSequencerBatchAppended, SequencerBatchAppendedEvent,
SequencerBatchAppendedExtraData, SequencerBatchAppendedExtraData,
SequencerBatchAppendedParsedEvent SequencerBatchAppendedParsedEvent
> = { > = {
......
/* Imports: External */ /* Imports: External */
import { StateBatchAppendedEvent } from '@eth-optimism/contracts/dist/types/StateCommitmentChain'
import { getContractFactory } from '@eth-optimism/contracts' import { getContractFactory } from '@eth-optimism/contracts'
import { EventArgsStateBatchAppended } from '@eth-optimism/core-utils'
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
/* Imports: Internal */ /* Imports: Internal */
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
import { MissingElementError } from './errors' import { MissingElementError } from './errors'
export const handleEventsStateBatchAppended: EventHandlerSet< export const handleEventsStateBatchAppended: EventHandlerSet<
EventArgsStateBatchAppended, StateBatchAppendedEvent,
StateBatchAppendedExtraData, StateBatchAppendedExtraData,
StateBatchAppendedParsedEvent 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 */ /* Imports: Internal */
import { BigNumber } from 'ethers'
import { EnqueueEntry, EventHandlerSet } from '../../../types' import { EnqueueEntry, EventHandlerSet } from '../../../types'
import { MissingElementError } from './errors' import { MissingElementError } from './errors'
export const handleEventsTransactionEnqueued: EventHandlerSet< export const handleEventsTransactionEnqueued: EventHandlerSet<
EventArgsTransactionEnqueued, TransactionEnqueuedEvent,
null, null,
EnqueueEntry EnqueueEntry
> = { > = {
......
/* Imports: External */ /* Imports: External */
import { fromHexString, FallbackProvider } from '@eth-optimism/core-utils' import { fromHexString, FallbackProvider } from '@eth-optimism/core-utils'
import { BaseService, Metrics } from '@eth-optimism/common-ts' import { BaseService, Metrics } from '@eth-optimism/common-ts'
import { TypedEvent } from '@eth-optimism/contracts/dist/types/common'
import { BaseProvider } from '@ethersproject/providers' import { BaseProvider } from '@ethersproject/providers'
import { LevelUp } from 'levelup' import { LevelUp } from 'levelup'
import { constants } from 'ethers' import { constants } from 'ethers'
...@@ -15,7 +16,7 @@ import { ...@@ -15,7 +16,7 @@ import {
loadContract, loadContract,
validators, validators,
} from '../../utils' } from '../../utils'
import { TypedEthersEvent, EventHandlerSet } from '../../types' import { EventHandlerSet } from '../../types'
import { handleEventsTransactionEnqueued } from './handlers/transaction-enqueued' import { handleEventsTransactionEnqueued } from './handlers/transaction-enqueued'
import { handleEventsSequencerBatchAppended } from './handlers/sequencer-batch-appended' import { handleEventsSequencerBatchAppended } from './handlers/sequencer-batch-appended'
import { handleEventsStateBatchAppended } from './handlers/state-batch-appended' import { handleEventsStateBatchAppended } from './handlers/state-batch-appended'
...@@ -389,9 +390,7 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> { ...@@ -389,9 +390,7 @@ export class L1IngestionService extends BaseService<L1IngestionServiceOptions> {
for (const eventRange of eventRanges) { for (const eventRange of eventRanges) {
// Find all relevant events within the range. // Find all relevant events within the range.
const events: TypedEthersEvent<any>[] = await this.state.contracts[ const events: TypedEvent[] = await this.state.contracts[contractName]
contractName
]
.attach(eventRange.address) .attach(eventRange.address)
.queryFilter( .queryFilter(
this.state.contracts[contractName].filters[eventName](), this.state.contracts[contractName].filters[eventName](),
......
import { BaseProvider } from '@ethersproject/providers' 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 { TransportDB } from '../db/transport-db'
import { import {
...@@ -9,29 +10,29 @@ import { ...@@ -9,29 +10,29 @@ import {
StateRootEntry, StateRootEntry,
} from './database-types' } from './database-types'
export type TypedEthersEvent<T> = Event & { export type GetExtraDataHandler<TEvent extends TypedEvent, TExtraData> = (
args: T event?: TEvent,
}
export type GetExtraDataHandler<TEventArgs, TExtraData> = (
event?: TypedEthersEvent<TEventArgs>,
l1RpcProvider?: BaseProvider l1RpcProvider?: BaseProvider
) => Promise<TExtraData> ) => Promise<TExtraData>
export type ParseEventHandler<TEventArgs, TExtraData, TParsedEvent> = ( export type ParseEventHandler<
event: TypedEthersEvent<TEventArgs>, TEvent extends TypedEvent,
extraData: TExtraData, TExtraData,
l2ChainId: number TParsedEvent
) => TParsedEvent > = (event: TEvent, extraData: TExtraData, l2ChainId: number) => TParsedEvent
export type StoreEventHandler<TParsedEvent> = ( export type StoreEventHandler<TParsedEvent> = (
parsedEvent: TParsedEvent, parsedEvent: TParsedEvent,
db: TransportDB db: TransportDB
) => Promise<void> ) => Promise<void>
export interface EventHandlerSet<TEventArgs, TExtraData, TParsedEvent> { export interface EventHandlerSet<
getExtraData: GetExtraDataHandler<TEventArgs, TExtraData> TEvent extends TypedEvent,
parseEvent: ParseEventHandler<TEventArgs, TExtraData, TParsedEvent> TExtraData,
TParsedEvent
> {
getExtraData: GetExtraDataHandler<TEvent, TExtraData>
parseEvent: ParseEventHandler<TEvent, TExtraData, TParsedEvent>
storeEvent: StoreEventHandler<TParsedEvent> storeEvent: StoreEventHandler<TParsedEvent>
} }
......
...@@ -2,8 +2,14 @@ ...@@ -2,8 +2,14 @@
import { ethers } from 'ethers' import { ethers } from 'ethers'
export const parseSignatureVParam = ( export const parseSignatureVParam = (
v: number | ethers.BigNumber, v: number | ethers.BigNumber | string,
chainId: number chainId: number
): 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 @@ ...@@ -2686,15 +2686,18 @@
dependencies: dependencies:
ethers "^5.0.2" ethers "^5.0.2"
"@typechain/ethers-v5@^7.0.1": "@typechain/ethers-v5@^8.0.2":
version "7.0.1" version "8.0.2"
resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-7.0.1.tgz#f9ae60ae5bd9e8ea8a996f66244147e8e74034ae" resolved "https://registry.yarnpkg.com/@typechain/ethers-v5/-/ethers-v5-8.0.2.tgz#0f2cc0db1513cb02e3e78929139216b153c9d2c0"
integrity sha512-mXEJ7LG0pOYO+MRPkHtbf30Ey9X2KAsU0wkeoVvjQIn7iAY6tB3k3s+82bbmJAUMyENbQ04RDOZit36CgSG6Gg== integrity sha512-oRMA3X5UWrsUiNb/lFTusa8xBpw6CckOHAk7sZBHeDQh4tAp+ZU24wdwdURcOtPnagzdCv5Dvl1qlD038brf1A==
dependencies:
lodash "^4.17.15"
ts-essentials "^7.0.1"
"@typechain/hardhat@^2.3.0": "@typechain/hardhat@^3.0.0":
version "2.3.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-2.3.0.tgz#dc7f29281637b38b77c7c046ae82700703395d0f" resolved "https://registry.yarnpkg.com/@typechain/hardhat/-/hardhat-3.0.0.tgz#a94396855506d42b98d6e7c09f87ca14180d6c64"
integrity sha512-zERrtNol86L4DX60ktnXxP7Cq8rSZHPaQvsChyiQQVuvVs2FTLm24Yi+MYnfsIdbUBIXZG7SxDWhtCF5I0tJNQ== integrity sha512-FpnIIXkDXm54XCHI/Z2iOet7h1MrFSvZfuljX9Uzc6FEjEfb01Tuzu8ywe2iquD3g5JXqovgdv+M54L/2Z6jkg==
dependencies: dependencies:
fs-extra "^9.1.0" fs-extra "^9.1.0"
...@@ -15170,10 +15173,10 @@ typechain@^3.0.0: ...@@ -15170,10 +15173,10 @@ typechain@^3.0.0:
ts-essentials "^6.0.3" ts-essentials "^6.0.3"
ts-generator "^0.1.1" ts-generator "^0.1.1"
typechain@^5.1.0: typechain@^6.0.2:
version "5.1.2" version "6.0.2"
resolved "https://registry.yarnpkg.com/typechain/-/typechain-5.1.2.tgz#c8784d6155a8e69397ca47f438a3b4fb2aa939da" resolved "https://registry.yarnpkg.com/typechain/-/typechain-6.0.2.tgz#0a1d6328aa934dfd66c8941cd94c1d3491cc34f0"
integrity sha512-FuaCxJd7BD3ZAjVJoO+D6TnqKey3pQdsqOBsC83RKYWKli5BDhdf0TPkwfyjt20TUlZvOzJifz+lDwXsRkiSKA== integrity sha512-fphYQ2+r5z38eq02qr4KjgU9/Xdda8Cj+eV2QHSRXhDPyhEVv+ln9123iGjjDLEhGyROb3DQkbvjm3Dl0GtUeQ==
dependencies: dependencies:
"@types/prettier" "^2.1.1" "@types/prettier" "^2.1.1"
command-line-args "^4.0.7" 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