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

Merge branch 'develop' into cleanup/recent-merges

parents b09479fe 04bd8cab
---
'@eth-optimism/fee-estimation': patch
---
Updated docs
# @eth-optimism/fee-estimation # @eth-optimism/fee-estimation
Tools for estimating gas on OP chains
- **Tip** the [specs file](./src/estimateFees.spec.ts) has usage examples of every method in this library.
## Overview ## Overview
This package is designed to provide an easy way to estimate gas on OP chains. This package is designed to provide an easy way to estimate gas on OP chains.
...@@ -30,26 +34,33 @@ npm install @eth-optimism/fee-estimation ...@@ -30,26 +34,33 @@ npm install @eth-optimism/fee-estimation
yarn add @eth-optimism/fee-estimation yarn add @eth-optimism/fee-estimation
``` ```
## Usage ### Basic Usage
### Import the package
```ts ```ts
import { estimateFees } from '@eth-optimism/fee-estimation' import {
``` estimateFees,
} from '@eth-optimism/fee-estimation'
import {optimistABI} from '@eth-optimism/contracts-ts'
import {viemClient} from './viem-client'
### Basic Usage const optimistOwnerAddress =
'0x77194aa25a06f932c10c0f25090f3046af2c85a6' as const
const tokenId = BigInt(optimistOwnerAddress)
```ts
const fees = await estimateFees({ const fees = await estimateFees({
client: viemClient,
// If not using in viem can pass in this instead
/*
client: { client: {
chainId: 10, chainId: 10,
rpcUrl: 'https://mainnet.optimism.io', rpcUrl: 'https://mainnet.optimism.io',
}, },
blockNumber: BigInt(106889079), */
account: '0xe371815c5f8a4f9acd1576879de288acd81269f1', functionName: 'burn',
to: '0xe35f24470730f5a488da9721548c1ab0b65b53d5', abi: optimistABI,
data: '0x5c19a95c00000000000000000000000046abfe1c972fca43766d6ad70e1c1df72f4bb4d1', args: [tokenid],
account: optimistOwnerAddress,
to: '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5',
}) })
``` ```
...@@ -58,51 +69,38 @@ const fees = await estimateFees({ ...@@ -58,51 +69,38 @@ const fees = await estimateFees({
### `estimateFees` function ### `estimateFees` function
```ts ```ts
estimateFees(params: EstimateFeeParams): Promise<bigint> estimateFees(options: OracleTransactionParameters<TAbi, TFunctionName> & GasPriceOracleOptions & Omit<EstimateGasParameters, 'data'>): Promise<bigint>
``` ```
#### Parameters #### Parameters
`params`: An object with the following fields: `options`: An object with the following fields:
- `client`: An object with `rpcUrl` and `chainId` fields, or an instance of a Viem `PublicClient`.
- `blockNumber`: A BigInt representing the block number at which you want to estimate the fees. - `abi`: A JSON object ABI of contract.
- `blockTag`: A string representing the block tag to query from. - `account`: A hex address of the account making the transaction.
- `account`: A string representing the account making the transaction. - `args`: Array of arguments to contract function. The types of this will be inferred from the ABI
- `to`: A string representing the recipient of the transaction. - `blockNumber`(optional): A BigInt representing the block number at which you want to estimate the fees.
- `data`: A string representing the data being sent with the transaction. This should be a 0x-prefixed hex string. - `chainId`: An integer chain id.
#### Returns - `client`: An object with rpcUrl field, or an instance of a Viem PublicClient.
A Promise that resolves to a BigInt representing the estimated fee in wei. - `functionName`: A string representing the function name for the transaction call data.
## FAQ - `maxFeePerGas`(optional): A BigInt representing the maximum fee per gas that the user is willing to pay.
### How to encode function data? - `maxPriorityFeePerGas`(optional): A BigInt representing the maximum priority fee per gas that the user is willing to pay.
You can use our package to encode the function data. Here is an example: - `to`: A hex address of the recipient of the transaction.
```ts - `value`(optional): A BigInt representing the value in wei sent along with the transaction.
import { encodeFunctionData } from '@eth-optimism/fee-estimation'
import { optimistABI } from '@eth-optimism/contracts-ts'
const data = encodeFunctionData({ #### Returns
functionName: 'burn',
abi: optimistABI,
args: [BigInt('0x77194aa25a06f932c10c0f25090f3046af2c85a6')],
})
```
This will return a 0x-prefixed hex string that represents the encoded function data.
## Testing
The package provides a set of tests that you can run to verify its operation. The tests are a great resource for examples. You can find them at `./src/estimateFees.spec.ts`. A Promise that resolves to a BigInt representing the estimated fee in wei.
## Other methods ## Other methods
...@@ -361,63 +359,3 @@ const libraryVersion = await version(paramsWithClient) ...@@ -361,63 +359,3 @@ const libraryVersion = await version(paramsWithClient)
``` ```
--- ---
### encodeFunctionData()
Encodes function data based on a given function name and arguments.
```ts
encodeFunctionData({ functionName, abi, args }: EncodeFunctionDataParams): string;
```
#### Parameters
- `{ functionName, abi, args }: EncodeFunctionDataParams` - An object containing the function name, ABI (Application Binary Interface), and arguments.
#### Returns
- `string` - Returns the encoded function data as a string.
#### Example
```ts
const encodedData = encodeFunctionData({
functionName: 'burn',
abi: optimistABI,
args: [BigInt(optimistOwnerAddress)],
})
```
---
### estimateFees()
Estimates the fee for a transaction given the input data and the address of the sender and recipient.
```ts
estimateFees({ client, data, account, to, blockNumber }: EstimateFeesParams): Promise<bigint>;
```
#### Parameters
- `{ client, data, account, to, blockNumber }: EstimateFeesParams` - An object containing the client, transaction data, sender's address, recipient's address, and block number.
#### Returns
- `Promise<bigint>` - Returns a promise that resolves to the estimated fee for the given transaction.
#### Example
```ts
const estimateFeesParams = {
data: '0xd1e16f0a603acf1f8150e020434b096e408bafa429a7134fbdad2ae82a9b2b882bfcf5fe174162cf4b3d5f2ab46ff6433792fc99885d55ce0972d982583cc1e11b64b1d8d50121c0497642000000000000000000000000000000000000060a2c8052ed420000000000000000000000000000000000004234002c8052edba12222222228d8ba445958a75a0704d566bf2c84200000000000000000000000000000000000006420000000000000000000000000000000000004239965c9dab5448482cf7e002f583c812ceb53046000100000000000000000003',
account: '0xe371815c5f8a4f9acd1576879de288acd81269f1',
to: '0xe35f24470730f5a488da9721548c1ab0b65b53d5',
}
const estimatedFees = await estimateFees({
...paramsWithClient,
...estimateFeesParams,
})
```
I hope this information is helpful!
...@@ -854,9 +854,12 @@ func calcBackoff(i int) time.Duration { ...@@ -854,9 +854,12 @@ func calcBackoff(i int) time.Duration {
type WSProxier struct { type WSProxier struct {
backend *Backend backend *Backend
clientConn *websocket.Conn clientConn *websocket.Conn
clientConnMu sync.Mutex
backendConn *websocket.Conn backendConn *websocket.Conn
backendConnMu sync.Mutex
methodWhitelist *StringSet methodWhitelist *StringSet
clientConnMu sync.Mutex readTimeout time.Duration
writeTimeout time.Duration
} }
func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier { func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier {
...@@ -865,6 +868,8 @@ func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, met ...@@ -865,6 +868,8 @@ func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, met
clientConn: clientConn, clientConn: clientConn,
backendConn: backendConn, backendConn: backendConn,
methodWhitelist: methodWhitelist, methodWhitelist: methodWhitelist,
readTimeout: defaultWSReadTimeout,
writeTimeout: defaultWSWriteTimeout,
} }
} }
...@@ -882,11 +887,11 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { ...@@ -882,11 +887,11 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) {
// Block until we get a message. // Block until we get a message.
msgType, msg, err := w.clientConn.ReadMessage() msgType, msg, err := w.clientConn.ReadMessage()
if err != nil { if err != nil {
errC <- err if err := w.writeBackendConn(websocket.CloseMessage, formatWSError(err)); err != nil {
if err := w.backendConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil {
log.Error("error writing backendConn message", "err", err) log.Error("error writing backendConn message", "err", err)
errC <- err
return
} }
return
} }
RecordWSMessage(ctx, w.backend.Name, SourceClient) RecordWSMessage(ctx, w.backend.Name, SourceClient)
...@@ -894,7 +899,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { ...@@ -894,7 +899,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) {
// Route control messages to the backend. These don't // Route control messages to the backend. These don't
// count towards the total RPC requests count. // count towards the total RPC requests count.
if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage { if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage {
err := w.backendConn.WriteMessage(msgType, msg) err := w.writeBackendConn(msgType, msg)
if err != nil { if err != nil {
errC <- err errC <- err
return return
...@@ -952,7 +957,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) { ...@@ -952,7 +957,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) {
"req_id", GetReqID(ctx), "req_id", GetReqID(ctx),
) )
err = w.backendConn.WriteMessage(msgType, msg) err = w.writeBackendConn(msgType, msg)
if err != nil { if err != nil {
errC <- err errC <- err
return return
...@@ -965,11 +970,11 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) { ...@@ -965,11 +970,11 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) {
// Block until we get a message. // Block until we get a message.
msgType, msg, err := w.backendConn.ReadMessage() msgType, msg, err := w.backendConn.ReadMessage()
if err != nil { if err != nil {
errC <- err
if err := w.writeClientConn(websocket.CloseMessage, formatWSError(err)); err != nil { if err := w.writeClientConn(websocket.CloseMessage, formatWSError(err)); err != nil {
log.Error("error writing clientConn message", "err", err) log.Error("error writing clientConn message", "err", err)
errC <- err
return
} }
return
} }
RecordWSMessage(ctx, w.backend.Name, SourceBackend) RecordWSMessage(ctx, w.backend.Name, SourceBackend)
...@@ -1050,8 +1055,23 @@ func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) { ...@@ -1050,8 +1055,23 @@ func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) {
func (w *WSProxier) writeClientConn(msgType int, msg []byte) error { func (w *WSProxier) writeClientConn(msgType int, msg []byte) error {
w.clientConnMu.Lock() w.clientConnMu.Lock()
defer w.clientConnMu.Unlock()
if err := w.clientConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil {
log.Error("ws client write timeout", "err", err)
return err
}
err := w.clientConn.WriteMessage(msgType, msg) err := w.clientConn.WriteMessage(msgType, msg)
w.clientConnMu.Unlock() return err
}
func (w *WSProxier) writeBackendConn(msgType int, msg []byte) error {
w.backendConnMu.Lock()
defer w.backendConnMu.Unlock()
if err := w.backendConn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil {
log.Error("ws backend write timeout", "err", err)
return err
}
err := w.backendConn.WriteMessage(msgType, msg)
return err return err
} }
......
...@@ -2,14 +2,14 @@ package integration_tests ...@@ -2,14 +2,14 @@ package integration_tests
import ( import (
"os" "os"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/proxyd" "github.com/ethereum-optimism/optimism/proxyd"
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -201,7 +201,7 @@ func TestWS(t *testing.T) { ...@@ -201,7 +201,7 @@ func TestWS(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
timeout := time.NewTicker(30 * time.Second) timeout := time.NewTicker(10 * time.Second)
doneCh := make(chan struct{}, 1) doneCh := make(chan struct{}, 1)
backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) { backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) {
require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(tt.backendRes))) require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(tt.backendRes)))
...@@ -270,3 +270,48 @@ func TestWSClientClosure(t *testing.T) { ...@@ -270,3 +270,48 @@ func TestWSClientClosure(t *testing.T) {
}) })
} }
} }
func TestWSClientExceedReadLimit(t *testing.T) {
backendHdlr := new(backendHandler)
clientHdlr := new(clientHandler)
backend := NewMockWSBackend(nil, func(conn *websocket.Conn, msgType int, data []byte) {
backendHdlr.MsgCB(conn, msgType, data)
}, func(conn *websocket.Conn, err error) {
backendHdlr.CloseCB(conn, err)
})
defer backend.Close()
require.NoError(t, os.Setenv("GOOD_BACKEND_RPC_URL", backend.URL()))
config := ReadConfig("ws")
_, shutdown, err := proxyd.Start(config)
require.NoError(t, err)
defer shutdown()
client, err := NewProxydWSClient("ws://127.0.0.1:8546", func(msgType int, data []byte) {
clientHdlr.MsgCB(msgType, data)
}, nil)
require.NoError(t, err)
closed := false
originalHandler := client.conn.CloseHandler()
client.conn.SetCloseHandler(func(code int, text string) error {
closed = true
return originalHandler(code, text)
})
backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) {
t.Fatalf("backend should not get the large message")
})
payload := strings.Repeat("barf", 1024*1024)
clientReq := "{\"id\": 1, \"method\": \"eth_subscribe\", \"params\": [\"" + payload + "\"]}"
err = client.WriteMessage(
websocket.TextMessage,
[]byte(clientReq),
)
require.Error(t, err)
require.True(t, closed)
}
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/rs/cors" "github.com/rs/cors"
"github.com/syndtr/goleveldb/leveldb/opt"
) )
const ( const (
...@@ -35,7 +36,11 @@ const ( ...@@ -35,7 +36,11 @@ const (
ContextKeyXForwardedFor = "x_forwarded_for" ContextKeyXForwardedFor = "x_forwarded_for"
MaxBatchRPCCallsHardLimit = 100 MaxBatchRPCCallsHardLimit = 100
cacheStatusHdr = "X-Proxyd-Cache-Status" cacheStatusHdr = "X-Proxyd-Cache-Status"
defaultServerTimeout = time.Second * 10 defaultRPCTimeout = 10 * time.Second
defaultBodySizeLimit = 256 * opt.KiB
defaultWSHandshakeTimeout = 10 * time.Second
defaultWSReadTimeout = 2 * time.Minute
defaultWSWriteTimeout = 10 * time.Second
maxRequestBodyLogLen = 2000 maxRequestBodyLogLen = 2000
defaultMaxUpstreamBatchSize = 10 defaultMaxUpstreamBatchSize = 10
) )
...@@ -92,11 +97,11 @@ func NewServer( ...@@ -92,11 +97,11 @@ func NewServer(
} }
if maxBodySize == 0 { if maxBodySize == 0 {
maxBodySize = math.MaxInt64 maxBodySize = defaultBodySizeLimit
} }
if timeout == 0 { if timeout == 0 {
timeout = defaultServerTimeout timeout = defaultRPCTimeout
} }
if maxUpstreamBatchSize == 0 { if maxUpstreamBatchSize == 0 {
...@@ -170,7 +175,7 @@ func NewServer( ...@@ -170,7 +175,7 @@ func NewServer(
maxRequestBodyLogLen: maxRequestBodyLogLen, maxRequestBodyLogLen: maxRequestBodyLogLen,
maxBatchSize: maxBatchSize, maxBatchSize: maxBatchSize,
upgrader: &websocket.Upgrader{ upgrader: &websocket.Upgrader{
HandshakeTimeout: 5 * time.Second, HandshakeTimeout: defaultWSHandshakeTimeout,
}, },
mainLim: mainLim, mainLim: mainLim,
overrideLims: overrideLims, overrideLims: overrideLims,
...@@ -547,6 +552,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { ...@@ -547,6 +552,7 @@ func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
log.Error("error upgrading client conn", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err) log.Error("error upgrading client conn", "auth", GetAuthCtx(ctx), "req_id", GetReqID(ctx), "err", err)
return return
} }
clientConn.SetReadLimit(s.maxBodySize)
proxier, err := s.wsBackendGroup.ProxyWS(ctx, clientConn, s.wsMethodWhitelist) proxier, err := s.wsBackendGroup.ProxyWS(ctx, clientConn, s.wsMethodWhitelist)
if err != nil { if err != nil {
......
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