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
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
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
yarn add @eth-optimism/fee-estimation
```
## Usage
### Import the package
### Basic Usage
```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({
client: viemClient,
// If not using in viem can pass in this instead
/*
client: {
chainId: 10,
rpcUrl: 'https://mainnet.optimism.io',
},
blockNumber: BigInt(106889079),
account: '0xe371815c5f8a4f9acd1576879de288acd81269f1',
to: '0xe35f24470730f5a488da9721548c1ab0b65b53d5',
data: '0x5c19a95c00000000000000000000000046abfe1c972fca43766d6ad70e1c1df72f4bb4d1',
*/
functionName: 'burn',
abi: optimistABI,
args: [tokenid],
account: optimistOwnerAddress,
to: '0x2335022c740d17c2837f9C884Bfe4fFdbf0A95D5',
})
```
......@@ -58,51 +69,38 @@ const fees = await estimateFees({
### `estimateFees` function
```ts
estimateFees(params: EstimateFeeParams): Promise<bigint>
estimateFees(options: OracleTransactionParameters<TAbi, TFunctionName> & GasPriceOracleOptions & Omit<EstimateGasParameters, 'data'>): Promise<bigint>
```
#### Parameters
`params`: An object with the following fields:
- `client`: An object with `rpcUrl` and `chainId` fields, or an instance of a Viem `PublicClient`.
`options`: An object with the following fields:
- `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
import { encodeFunctionData } from '@eth-optimism/fee-estimation'
import { optimistABI } from '@eth-optimism/contracts-ts'
- `value`(optional): A BigInt representing the value in wei sent along with the transaction.
const data = encodeFunctionData({
functionName: 'burn',
abi: optimistABI,
args: [BigInt('0x77194aa25a06f932c10c0f25090f3046af2c85a6')],
})
```
This will return a 0x-prefixed hex string that represents the encoded function data.
## Testing
#### Returns
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
......@@ -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 {
type WSProxier struct {
backend *Backend
clientConn *websocket.Conn
clientConnMu sync.Mutex
backendConn *websocket.Conn
backendConnMu sync.Mutex
methodWhitelist *StringSet
clientConnMu sync.Mutex
readTimeout time.Duration
writeTimeout time.Duration
}
func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, methodWhitelist *StringSet) *WSProxier {
......@@ -865,6 +868,8 @@ func NewWSProxier(backend *Backend, clientConn, backendConn *websocket.Conn, met
clientConn: clientConn,
backendConn: backendConn,
methodWhitelist: methodWhitelist,
readTimeout: defaultWSReadTimeout,
writeTimeout: defaultWSWriteTimeout,
}
}
......@@ -882,11 +887,11 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) {
// Block until we get a message.
msgType, msg, err := w.clientConn.ReadMessage()
if err != nil {
errC <- err
if err := w.backendConn.WriteMessage(websocket.CloseMessage, formatWSError(err)); err != nil {
if err := w.writeBackendConn(websocket.CloseMessage, formatWSError(err)); err != nil {
log.Error("error writing backendConn message", "err", err)
errC <- err
return
}
return
}
RecordWSMessage(ctx, w.backend.Name, SourceClient)
......@@ -894,7 +899,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) {
// Route control messages to the backend. These don't
// count towards the total RPC requests count.
if msgType != websocket.TextMessage && msgType != websocket.BinaryMessage {
err := w.backendConn.WriteMessage(msgType, msg)
err := w.writeBackendConn(msgType, msg)
if err != nil {
errC <- err
return
......@@ -952,7 +957,7 @@ func (w *WSProxier) clientPump(ctx context.Context, errC chan error) {
"req_id", GetReqID(ctx),
)
err = w.backendConn.WriteMessage(msgType, msg)
err = w.writeBackendConn(msgType, msg)
if err != nil {
errC <- err
return
......@@ -965,11 +970,11 @@ func (w *WSProxier) backendPump(ctx context.Context, errC chan error) {
// Block until we get a message.
msgType, msg, err := w.backendConn.ReadMessage()
if err != nil {
errC <- err
if err := w.writeClientConn(websocket.CloseMessage, formatWSError(err)); err != nil {
log.Error("error writing clientConn message", "err", err)
errC <- err
return
}
return
}
RecordWSMessage(ctx, w.backend.Name, SourceBackend)
......@@ -1050,8 +1055,23 @@ func (w *WSProxier) parseBackendMsg(msg []byte) (*RPCRes, error) {
func (w *WSProxier) writeClientConn(msgType int, msg []byte) error {
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)
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
}
......
......@@ -2,14 +2,14 @@ package integration_tests
import (
"os"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/proxyd"
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"
)
......@@ -201,7 +201,7 @@ func TestWS(t *testing.T) {
}
for _, tt := range tests {
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)
backendHdlr.SetMsgCB(func(conn *websocket.Conn, msgType int, data []byte) {
require.NoError(t, conn.WriteMessage(websocket.TextMessage, []byte(tt.backendRes)))
......@@ -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 (
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/cors"
"github.com/syndtr/goleveldb/leveldb/opt"
)
const (
......@@ -35,7 +36,11 @@ const (
ContextKeyXForwardedFor = "x_forwarded_for"
MaxBatchRPCCallsHardLimit = 100
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
defaultMaxUpstreamBatchSize = 10
)
......@@ -92,11 +97,11 @@ func NewServer(
}
if maxBodySize == 0 {
maxBodySize = math.MaxInt64
maxBodySize = defaultBodySizeLimit
}
if timeout == 0 {
timeout = defaultServerTimeout
timeout = defaultRPCTimeout
}
if maxUpstreamBatchSize == 0 {
......@@ -170,7 +175,7 @@ func NewServer(
maxRequestBodyLogLen: maxRequestBodyLogLen,
maxBatchSize: maxBatchSize,
upgrader: &websocket.Upgrader{
HandshakeTimeout: 5 * time.Second,
HandshakeTimeout: defaultWSHandshakeTimeout,
},
mainLim: mainLim,
overrideLims: overrideLims,
......@@ -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)
return
}
clientConn.SetReadLimit(s.maxBodySize)
proxier, err := s.wsBackendGroup.ProxyWS(ctx, clientConn, s.wsMethodWhitelist)
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