Commit 6a3b1ec1 authored by protolambda's avatar protolambda Committed by GitHub

op-signer: update client to support Blob tx signing (#9185)

* op-signer: update client to support Blob tx signing

* op-service: update maxFeePerBlobGas JSON field to match internal geth TransactionArgs
parent 17676bbb
...@@ -219,7 +219,7 @@ require ( ...@@ -219,7 +219,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect rsc.io/tmplfunc v0.0.3 // indirect
) )
replace github.com/ethereum/go-ethereum v1.13.5 => github.com/ethereum-optimism/op-geth v1.101305.2-rc.2.0.20240123201117-bfa8ffc685e0 replace github.com/ethereum/go-ethereum v1.13.5 => github.com/ethereum-optimism/op-geth v1.101305.3-rc.1.0.20240124221225-5c6f10d449ab
//replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain //replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain
//replace github.com/ethereum/go-ethereum v1.13.5 => ../go-ethereum //replace github.com/ethereum/go-ethereum v1.13.5 => ../go-ethereum
...@@ -170,8 +170,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/ ...@@ -170,8 +170,8 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/
github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101305.2-rc.2.0.20240123201117-bfa8ffc685e0 h1:Cu3e2Pgn0rBTzr7FjLbUBsJRq2RcVWDPf9tTp6dBZtY= github.com/ethereum-optimism/op-geth v1.101305.3-rc.1.0.20240124221225-5c6f10d449ab h1:SpoQiEG5mteZnaPCo5Pm5QrberdODUefwLjdAtL9LAE=
github.com/ethereum-optimism/op-geth v1.101305.2-rc.2.0.20240123201117-bfa8ffc685e0/go.mod h1:/0cPafbmmt3JR0yiuoBJWTRCx8briXUd/mtXr+GJ3Zw= github.com/ethereum-optimism/op-geth v1.101305.3-rc.1.0.20240124221225-5c6f10d449ab/go.mod h1:/0cPafbmmt3JR0yiuoBJWTRCx8briXUd/mtXr+GJ3Zw=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240123193359-a5fc767e225a h1:mWIRpGyrAlWHUznUHKgAJUafkNGfO7VmeLjilhVhB80= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240123193359-a5fc767e225a h1:mWIRpGyrAlWHUznUHKgAJUafkNGfO7VmeLjilhVhB80=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240123193359-a5fc767e225a/go.mod h1:/70H/KqrtKcvWvNGVj6S3rAcLC+kUPr3t2aDmYIS+Xk= github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240123193359-a5fc767e225a/go.mod h1:/70H/KqrtKcvWvNGVj6S3rAcLC+kUPr3t2aDmYIS+Xk=
github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY=
......
...@@ -389,6 +389,7 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txDat ...@@ -389,6 +389,7 @@ func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txDat
} }
func (l *BatchSubmitter) blobTxCandidate(data []byte) (*txmgr.TxCandidate, error) { func (l *BatchSubmitter) blobTxCandidate(data []byte) (*txmgr.TxCandidate, error) {
l.Log.Info("building Blob transaction candidate", "size", len(data))
var b eth.Blob var b eth.Blob
if err := b.FromData(data); err != nil { if err := b.FromData(data); err != nil {
return nil, fmt.Errorf("data could not be converted to blob: %w", err) return nil, fmt.Errorf("data could not be converted to blob: %w", err)
...@@ -400,6 +401,7 @@ func (l *BatchSubmitter) blobTxCandidate(data []byte) (*txmgr.TxCandidate, error ...@@ -400,6 +401,7 @@ func (l *BatchSubmitter) blobTxCandidate(data []byte) (*txmgr.TxCandidate, error
} }
func (l *BatchSubmitter) calldataTxCandidate(data []byte) *txmgr.TxCandidate { func (l *BatchSubmitter) calldataTxCandidate(data []byte) *txmgr.TxCandidate {
l.Log.Info("building Calldata transaction candidate", "size", len(data))
return &txmgr.TxCandidate{ return &txmgr.TxCandidate{
To: &l.RollupConfig.BatchInboxAddress, To: &l.RollupConfig.BatchInboxAddress,
TxData: data, TxData: data,
......
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
"github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-batcher/metrics"
"github.com/ethereum-optimism/optimism/op-batcher/rpc" "github.com/ethereum-optimism/optimism/op-batcher/rpc"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/cliapp" "github.com/ethereum-optimism/optimism/op-service/cliapp"
"github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/dial"
...@@ -172,6 +173,7 @@ func (bs *BatcherService) initRollupConfig(ctx context.Context) error { ...@@ -172,6 +173,7 @@ func (bs *BatcherService) initRollupConfig(ctx context.Context) error {
if err := bs.RollupConfig.Check(); err != nil { if err := bs.RollupConfig.Check(); err != nil {
return fmt.Errorf("invalid rollup config: %w", err) return fmt.Errorf("invalid rollup config: %w", err)
} }
bs.RollupConfig.LogDescription(bs.Log, chaincfg.L2ChainIDToNetworkDisplayName)
return nil return nil
} }
...@@ -197,9 +199,23 @@ func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error { ...@@ -197,9 +199,23 @@ func (bs *BatcherService) initChannelConfig(cfg *CLIConfig) error {
} }
bs.ChannelConfig.MaxFrameSize-- // subtract 1 byte for version bs.ChannelConfig.MaxFrameSize-- // subtract 1 byte for version
if bs.UseBlobs && !bs.RollupConfig.IsEcotone(uint64(time.Now().Unix())) {
bs.Log.Error("Cannot use Blob data before Ecotone!") // log only, the batcher may not be actively running.
}
if !bs.UseBlobs && bs.RollupConfig.IsEcotone(uint64(time.Now().Unix())) {
bs.Log.Warn("Ecotone upgrade is active, but batcher is not configured to use Blobs!")
}
if err := bs.ChannelConfig.Check(); err != nil { if err := bs.ChannelConfig.Check(); err != nil {
return fmt.Errorf("invalid channel configuration: %w", err) return fmt.Errorf("invalid channel configuration: %w", err)
} }
bs.Log.Info("Initialized channel-config",
"use_blobs", bs.UseBlobs,
"max_frame_size", bs.ChannelConfig.MaxFrameSize,
"max_channel_duration", bs.ChannelConfig.MaxChannelDuration,
"channel_timeout", bs.ChannelConfig.ChannelTimeout,
"batch_type", bs.ChannelConfig.BatchType,
"sub_safety_margin", bs.ChannelConfig.SubSafetyMargin)
return nil return nil
} }
......
...@@ -93,17 +93,23 @@ func (s *SignerClient) pingVersion() (string, error) { ...@@ -93,17 +93,23 @@ func (s *SignerClient) pingVersion() (string, error) {
} }
func (s *SignerClient) SignTransaction(ctx context.Context, chainId *big.Int, from common.Address, tx *types.Transaction) (*types.Transaction, error) { func (s *SignerClient) SignTransaction(ctx context.Context, chainId *big.Int, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
args := NewTransactionArgsFromTransaction(chainId, from, tx) sidecar := tx.BlobTxSidecar()
args := NewTransactionArgsFromTransaction(chainId, &from, tx.WithoutBlobTxSidecar())
var result hexutil.Bytes var result hexutil.Bytes
if err := s.client.CallContext(ctx, &result, "eth_signTransaction", args); err != nil { if err := s.client.CallContext(ctx, &result, "eth_signTransaction", args); err != nil {
return nil, fmt.Errorf("eth_signTransaction failed: %w", err) return nil, fmt.Errorf("eth_signTransaction failed: %w", err)
} }
signed := &types.Transaction{} var signed types.Transaction
if err := signed.UnmarshalBinary(result); err != nil { if err := signed.UnmarshalBinary(result); err != nil {
return nil, err return nil, err
} }
if sidecar != nil {
if err := signed.SetBlobTxSidecar(sidecar); err != nil {
return nil, fmt.Errorf("failed to attach sidecar to signed blob tx: %w", err)
}
}
return signed, nil return &signed, nil
} }
package signer package signer
import ( import (
"bytes"
"errors"
"fmt"
"math/big" "math/big"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -10,6 +15,8 @@ import ( ...@@ -10,6 +15,8 @@ import (
// TransactionArgs represents the arguments to construct a new transaction // TransactionArgs represents the arguments to construct a new transaction
// or a message call. // or a message call.
// Geth has an internal version of this, but this is not exported, and only supported in v1.13.11 and forward.
// This signing API format is based on the legacy personal-account signing RPC of ethereum.
type TransactionArgs struct { type TransactionArgs struct {
From *common.Address `json:"from"` From *common.Address `json:"from"`
To *common.Address `json:"to"` To *common.Address `json:"to"`
...@@ -28,16 +35,20 @@ type TransactionArgs struct { ...@@ -28,16 +35,20 @@ type TransactionArgs struct {
AccessList *types.AccessList `json:"accessList,omitempty"` AccessList *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"`
// Custom extension for EIP-4844 support
BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
} }
// NewTransactionArgsFromTransaction creates a TransactionArgs struct from an EIP-1559 transaction // NewTransactionArgsFromTransaction creates a TransactionArgs struct from an EIP-1559 or EIP-4844 transaction
func NewTransactionArgsFromTransaction(chainId *big.Int, from common.Address, tx *types.Transaction) *TransactionArgs { func NewTransactionArgsFromTransaction(chainId *big.Int, from *common.Address, tx *types.Transaction) *TransactionArgs {
data := hexutil.Bytes(tx.Data()) data := hexutil.Bytes(tx.Data())
nonce := hexutil.Uint64(tx.Nonce()) nonce := hexutil.Uint64(tx.Nonce())
gas := hexutil.Uint64(tx.Gas()) gas := hexutil.Uint64(tx.Gas())
accesses := tx.AccessList() accesses := tx.AccessList()
args := &TransactionArgs{ args := &TransactionArgs{
From: &from, From: from,
Input: &data, Input: &data,
Nonce: &nonce, Nonce: &nonce,
Value: (*hexutil.Big)(tx.Value()), Value: (*hexutil.Big)(tx.Value()),
...@@ -47,6 +58,8 @@ func NewTransactionArgsFromTransaction(chainId *big.Int, from common.Address, tx ...@@ -47,6 +58,8 @@ func NewTransactionArgsFromTransaction(chainId *big.Int, from common.Address, tx
MaxFeePerGas: (*hexutil.Big)(tx.GasFeeCap()), MaxFeePerGas: (*hexutil.Big)(tx.GasFeeCap()),
MaxPriorityFeePerGas: (*hexutil.Big)(tx.GasTipCap()), MaxPriorityFeePerGas: (*hexutil.Big)(tx.GasTipCap()),
AccessList: &accesses, AccessList: &accesses,
BlobVersionedHashes: tx.BlobHashes(),
BlobFeeCap: (*hexutil.Big)(tx.BlobGasFeeCap()),
} }
return args return args
} }
...@@ -62,23 +75,106 @@ func (args *TransactionArgs) data() []byte { ...@@ -62,23 +75,106 @@ func (args *TransactionArgs) data() []byte {
return nil return nil
} }
// ToTransaction converts the arguments to a transaction. func (args *TransactionArgs) Check() error {
func (args *TransactionArgs) ToTransaction() *types.Transaction { if args.Gas == nil {
return errors.New("gas not specified")
}
if args.GasPrice != nil {
return errors.New("only accepts maxFeePerGas/maxPriorityFeePerGas params")
}
if args.MaxFeePerGas == nil || args.MaxPriorityFeePerGas == nil {
return errors.New("missing maxFeePerGas or maxPriorityFeePerGas")
}
// Both EIP-1559 fee parameters are now set; sanity check them.
if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
}
if args.Nonce == nil {
return errors.New("nonce not specified")
}
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
}
if args.To == nil && len(args.data()) == 0 {
return errors.New("contract creation without any data provided")
}
if args.ChainID == nil {
return errors.New("chain id not specified")
}
if args.Value == nil {
args.Value = new(hexutil.Big)
}
if args.AccessList == nil {
args.AccessList = &types.AccessList{}
}
if args.BlobVersionedHashes != nil {
if len(args.BlobVersionedHashes) == 0 {
return errors.New("non-null blob versioned hashes should not be empty")
}
if args.BlobFeeCap == nil {
return errors.New("when including blobs a blob-fee-cap is required")
}
} else {
if args.BlobFeeCap != nil {
return errors.New("unexpected blob-fee-cap, transaction does not include blobs")
}
}
return nil
}
// ToTransactionData converts the arguments to transaction content-data. Warning: this excludes blob data.
func (args *TransactionArgs) ToTransactionData() (types.TxData, error) {
var data types.TxData var data types.TxData
al := types.AccessList{} al := types.AccessList{}
if args.AccessList != nil { if args.AccessList != nil {
al = *args.AccessList al = *args.AccessList
} }
if len(args.BlobVersionedHashes) > 0 {
chainID, overflow := uint256.FromBig((*big.Int)(args.ChainID))
if overflow {
return nil, fmt.Errorf("chainID %s too large for blob tx", args.ChainID)
}
maxFeePerGas, overflow := uint256.FromBig((*big.Int)(args.MaxFeePerGas))
if overflow {
return nil, fmt.Errorf("maxFeePerGas %s too large for blob tx", args.MaxFeePerGas)
}
maxPriorityFeePerGas, overflow := uint256.FromBig((*big.Int)(args.MaxPriorityFeePerGas))
if overflow {
return nil, fmt.Errorf("maxPriorityFeePerGas %s too large for blob tx", args.MaxPriorityFeePerGas)
}
value, overflow := uint256.FromBig((*big.Int)(args.Value))
if overflow {
return nil, fmt.Errorf("value %s too large for blob tx", args.Value)
}
blobFeeCap, overflow := uint256.FromBig((*big.Int)(args.BlobFeeCap))
if overflow {
return nil, fmt.Errorf("blobFeeCap %s too large for blob tx", args.BlobFeeCap)
}
data = &types.BlobTx{
ChainID: chainID,
Nonce: uint64(*args.Nonce),
GasTipCap: maxPriorityFeePerGas,
GasFeeCap: maxFeePerGas,
Gas: uint64(*args.Gas),
To: *args.To,
Value: value,
Data: args.data(),
AccessList: al,
BlobFeeCap: blobFeeCap,
BlobHashes: args.BlobVersionedHashes,
}
} else {
data = &types.DynamicFeeTx{ data = &types.DynamicFeeTx{
To: args.To,
ChainID: (*big.Int)(args.ChainID), ChainID: (*big.Int)(args.ChainID),
Nonce: uint64(*args.Nonce), Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasFeeCap: (*big.Int)(args.MaxFeePerGas),
GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
GasFeeCap: (*big.Int)(args.MaxFeePerGas),
Gas: uint64(*args.Gas),
To: args.To,
Value: (*big.Int)(args.Value), Value: (*big.Int)(args.Value),
Data: args.data(), Data: args.data(),
AccessList: al, AccessList: al,
} }
return types.NewTx(data) }
return data, nil
} }
...@@ -245,6 +245,7 @@ func (m *SimpleTxManager) send(ctx context.Context, candidate TxCandidate) (*typ ...@@ -245,6 +245,7 @@ func (m *SimpleTxManager) send(ctx context.Context, candidate TxCandidate) (*typ
// NOTE: If the [TxCandidate.GasLimit] is non-zero, it will be used as the transaction's gas. // NOTE: If the [TxCandidate.GasLimit] is non-zero, it will be used as the transaction's gas.
// NOTE: Otherwise, the [SimpleTxManager] will query the specified backend for an estimate. // NOTE: Otherwise, the [SimpleTxManager] will query the specified backend for an estimate.
func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*types.Transaction, error) { func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*types.Transaction, error) {
m.l.Debug("crafting Transaction", "blobs", len(candidate.Blobs), "calldata_size", len(candidate.TxData))
gasTipCap, baseFee, blobBaseFee, err := m.suggestGasPriceCaps(ctx) gasTipCap, baseFee, blobBaseFee, err := m.suggestGasPriceCaps(ctx)
if err != nil { if err != nil {
m.metr.RPCError() m.metr.RPCError()
......
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