Skip to content

Commit

Permalink
Move estimateGasAndNonce from wallet to txMgr, and add gasLimit 1.1x …
Browse files Browse the repository at this point in the history
…bump by default (#167)

* moved estimateGas from wallet to txMgr, and bumped gasLimit by 1.1 (default value)

* lower fallbackGasTipCap from 15 to 5 gwei

* update our gasFeeCap formula to 2*basefee+gastip

* bump gasLimitMultiplier to 1.20
  • Loading branch information
samlaf authored Mar 29, 2024
1 parent 7b41459 commit d5c0a15
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 67 deletions.
58 changes: 0 additions & 58 deletions chainio/clients/wallet/privatekey_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@ import (
"github.com/Layr-Labs/eigensdk-go/logging"
"github.com/Layr-Labs/eigensdk-go/signerv2"
sdktypes "github.com/Layr-Labs/eigensdk-go/types"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)

var (
FallbackGasTipCap = big.NewInt(15_000_000_000)
)

var _ Wallet = (*privateKeyWallet)(nil)

type privateKeyWallet struct {
Expand All @@ -43,14 +38,6 @@ func NewPrivateKeyWallet(ethClient eth.Client, signer signerv2.SignerFn, signerA
}

func (t *privateKeyWallet) SendTransaction(ctx context.Context, tx *types.Transaction) (TxID, error) {
// Estimate gas and nonce
// can't print tx hash in logs because the tx changes below when we complete and sign it
// so the txHash is meaningless at this point
t.logger.Debug("Estimating gas and nonce")
tx, err := t.estimateGasAndNonce(ctx, tx)
if err != nil {
return "", err
}

t.logger.Debug("Getting signer for tx")
signer, err := t.signerFn(ctx, t.address)
Expand Down Expand Up @@ -95,48 +82,3 @@ func (t *privateKeyWallet) GetTransactionReceipt(ctx context.Context, txID TxID)
func (t *privateKeyWallet) SenderAddress(ctx context.Context) (common.Address, error) {
return t.address, nil
}

// estimateGasAndNonce we are explicitly implementing this because
// * We want to support legacy transactions (i.e. not dynamic fee)
// * We want to support gas management, i.e. add buffer to gas limit
func (t *privateKeyWallet) estimateGasAndNonce(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
gasTipCap, err := t.ethClient.SuggestGasTipCap(ctx)
if err != nil {
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
t.logger.Info("eth_maxPriorityFeePerGas is unsupported by current backend, using fallback gasTipCap")
gasTipCap = FallbackGasTipCap
}

header, err := t.ethClient.HeaderByNumber(ctx, nil)
if err != nil {
return nil, err
}

gasFeeCap := new(big.Int).Add(header.BaseFee, gasTipCap)

gasLimit, err := t.ethClient.EstimateGas(ctx, ethereum.CallMsg{
From: t.address,
To: tx.To(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Value: tx.Value(),
Data: tx.Data(),
})
if err != nil {
return nil, err
}

rawTx := &types.DynamicFeeTx{
ChainID: tx.ChainId(),
To: tx.To(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: tx.Data(),
Value: tx.Value(),
Gas: gasLimit, // TODO(add buffer)
Nonce: tx.Nonce(), // We are not doing any nonce management for now but we probably should later for more robustness
}

return types.NewTx(rawTx), nil
}
105 changes: 96 additions & 9 deletions chainio/txmgr/txmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package txmgr
import (
"context"
"errors"
"math/big"
"time"

"github.com/Layr-Labs/eigensdk-go/chainio/clients/eth"
Expand All @@ -15,6 +16,13 @@ import (
"github.com/ethereum/go-ethereum/log"
)

var (
// 5 gwei in case the backend does not support eth_maxPriorityFeePerGas (no idea if/when this ever happens..)
FallbackGasTipCap = big.NewInt(5_000_000_000)
// 1.20x gas limit multiplier. This is arbitrary but should be safe for most cases
FallbackGasLimitMultiplier = 1.20
)

// We are taking inspiration from the optimism TxManager interface
// https://github.com/ethereum-optimism/optimism/blob/develop/op-service/txmgr/txmgr.go

Expand All @@ -30,10 +38,11 @@ type TxManager interface {
}

type SimpleTxManager struct {
wallet wallet.Wallet
client eth.Client
log logging.Logger
sender common.Address
wallet wallet.Wallet
client eth.Client
log logging.Logger
sender common.Address
gasLimitMultiplier float64
}

var _ TxManager = (*SimpleTxManager)(nil)
Expand All @@ -47,21 +56,44 @@ func NewSimpleTxManager(
sender common.Address,
) *SimpleTxManager {
return &SimpleTxManager{
wallet: wallet,
client: client,
log: log,
sender: sender,
wallet: wallet,
client: client,
log: log,
sender: sender,
gasLimitMultiplier: FallbackGasLimitMultiplier,
}
}

func (m *SimpleTxManager) WithGasLimitMultiplier(multiplier float64) *SimpleTxManager {
m.gasLimitMultiplier = multiplier
return m
}

// Send is used to send a transaction to the Ethereum node. It takes an unsigned/signed transaction
// and then sends it to the Ethereum node.
// It also takes care of gas estimation and adds a buffer to the gas limit
// If you pass in a signed transaction it will ignore the signature
// and resign the transaction after adding the nonce and gas limit.
// To check out the whole flow on how this works, check out the README.md in this folder
func (m *SimpleTxManager) Send(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) {
txID, err := m.wallet.SendTransaction(ctx, tx)
// Estimate gas and nonce
// can't print tx hash in logs because the tx changes below when we complete and sign it
// so the txHash is meaningless at this point
m.log.Debug("Estimating gas and nonce")
tx, err := m.estimateGasAndNonce(ctx, tx)
if err != nil {
return nil, err
}
bumpedGasTx := &types.DynamicFeeTx{
To: tx.To(),
Nonce: tx.Nonce(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
Gas: uint64(float64(tx.Gas()) * m.gasLimitMultiplier),
Value: tx.Value(),
Data: tx.Data(),
}
txID, err := m.wallet.SendTransaction(ctx, types.NewTx(bumpedGasTx))
if err != nil {
return nil, errors.Join(errors.New("send: failed to estimate gas and nonce"), err)
}
Expand Down Expand Up @@ -120,3 +152,58 @@ func (m *SimpleTxManager) queryReceipt(ctx context.Context, txID wallet.TxID) *t

return receipt
}

// estimateGasAndNonce we are explicitly implementing this because
// * We want to support legacy transactions (i.e. not dynamic fee)
// * We want to support gas management, i.e. add buffer to gas limit
func (m *SimpleTxManager) estimateGasAndNonce(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
gasTipCap, err := m.client.SuggestGasTipCap(ctx)
if err != nil {
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
m.log.Info("eth_maxPriorityFeePerGas is unsupported by current backend, using fallback gasTipCap")
gasTipCap = FallbackGasTipCap
}

header, err := m.client.HeaderByNumber(ctx, nil)
if err != nil {
return nil, err
}

// 2*baseFee + gasTipCap makes sure that the tx remains includeable for 6 consecutive 100% full blocks.
// see https://www.blocknative.com/blog/eip-1559-fees
gasFeeCap := new(big.Int).Add(header.BaseFee.Mul(header.BaseFee, big.NewInt(2)), gasTipCap)

gasLimit := tx.Gas()
// we only estimate if gasLimit is not already set
if gasLimit == 0 {
from, err := m.wallet.SenderAddress(ctx)
if err != nil {
return nil, errors.Join(errors.New("send: failed to get sender address"), err)
}
gasLimit, err = m.client.EstimateGas(ctx, ethereum.CallMsg{
From: from,
To: tx.To(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Value: tx.Value(),
Data: tx.Data(),
})
if err != nil {
return nil, errors.Join(errors.New("send: failed to estimate gas"), err)
}
}

rawTx := &types.DynamicFeeTx{
ChainID: tx.ChainId(),
To: tx.To(),
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Data: tx.Data(),
Value: tx.Value(),
Gas: uint64(float64(gasLimit) * m.gasLimitMultiplier),
Nonce: tx.Nonce(), // We are not doing any nonce management for now but we probably should later for more robustness
}

return types.NewTx(rawTx), nil
}

0 comments on commit d5c0a15

Please sign in to comment.