Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
dc1ed3c9
Unverified
Commit
dc1ed3c9
authored
Jan 30, 2022
by
Matthew Slipper
Committed by
GitHub
Jan 30, 2022
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2086 from cfromknecht/bss-eip-1559
feat: modify txmgr to send EIP-1559 txns
parents
36151fe8
3a7e7098
Changes
16
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
400 additions
and
367 deletions
+400
-367
serious-pets-fly.md
.changeset/serious-pets-fly.md
+5
-0
batch_submitter.go
go/batch-submitter/batch_submitter.go
+0
-4
config.go
go/batch-submitter/config.go
+0
-10
clear_pending_tx.go
go/batch-submitter/drivers/clear_pending_tx.go
+39
-26
clear_pending_tx_test.go
go/batch-submitter/drivers/clear_pending_tx_test.go
+73
-34
interface.go
go/batch-submitter/drivers/interface.go
+7
-7
max_priority_fee_fallback.go
go/batch-submitter/drivers/max_priority_fee_fallback.go
+26
-0
driver.go
go/batch-submitter/drivers/proposer/driver.go
+47
-9
driver.go
go/batch-submitter/drivers/sequencer/driver.go
+41
-7
flags.go
go/batch-submitter/flags/flags.go
+0
-14
l1client.go
go/batch-submitter/mock/l1client.go
+36
-16
service.go
go/batch-submitter/service.go
+5
-11
txmgr.go
go/batch-submitter/txmgr/txmgr.go
+31
-62
txmgr_test.go
go/batch-submitter/txmgr/txmgr_test.go
+90
-137
gas_price.go
go/batch-submitter/utils/gas_price.go
+0
-12
gas_price_test.go
go/batch-submitter/utils/gas_price_test.go
+0
-18
No files found.
.changeset/serious-pets-fly.md
0 → 100644
View file @
dc1ed3c9
---
'
@eth-optimism/batch-submitter-service'
:
patch
---
use EIP-1559 txns for tx/state batches
go/batch-submitter/batch_submitter.go
View file @
dc1ed3c9
...
...
@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/proposer"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/sequencer"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
l2ethclient
"github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
...
...
@@ -159,9 +158,6 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
}
txManagerConfig
:=
txmgr
.
Config
{
MinGasPrice
:
utils
.
GasPriceFromGwei
(
1
),
MaxGasPrice
:
utils
.
GasPriceFromGwei
(
cfg
.
MaxGasPriceInGwei
),
GasRetryIncrement
:
utils
.
GasPriceFromGwei
(
cfg
.
GasRetryIncrement
),
ResubmissionTimeout
:
cfg
.
ResubmissionTimeout
,
ReceiptQueryInterval
:
time
.
Second
,
NumConfirmations
:
cfg
.
NumConfirmations
,
...
...
go/batch-submitter/config.go
View file @
dc1ed3c9
...
...
@@ -133,14 +133,6 @@ type Config struct {
// blocks.
BlockOffset
uint64
// MaxGasPriceInGwei is the maximum gas price in gwei we will allow in order
// to confirm a transaction.
MaxGasPriceInGwei
uint64
// GasRetryIncrement is the step size (in gwei) by which we will ratchet the
// gas price in order to get a transaction confirmed.
GasRetryIncrement
uint64
// SequencerPrivateKey the private key of the wallet used to submit
// transactions to the CTC contract.
SequencerPrivateKey
string
...
...
@@ -199,8 +191,6 @@ func NewConfig(ctx *cli.Context) (Config, error) {
SentryDsn
:
ctx
.
GlobalString
(
flags
.
SentryDsnFlag
.
Name
),
SentryTraceRate
:
ctx
.
GlobalDuration
(
flags
.
SentryTraceRateFlag
.
Name
),
BlockOffset
:
ctx
.
GlobalUint64
(
flags
.
BlockOffsetFlag
.
Name
),
MaxGasPriceInGwei
:
ctx
.
GlobalUint64
(
flags
.
MaxGasPriceInGweiFlag
.
Name
),
GasRetryIncrement
:
ctx
.
GlobalUint64
(
flags
.
GasRetryIncrementFlag
.
Name
),
SequencerPrivateKey
:
ctx
.
GlobalString
(
flags
.
SequencerPrivateKeyFlag
.
Name
),
ProposerPrivateKey
:
ctx
.
GlobalString
(
flags
.
ProposerPrivateKeyFlag
.
Name
),
Mnemonic
:
ctx
.
GlobalString
(
flags
.
MnemonicFlag
.
Name
),
...
...
go/batch-submitter/drivers/clear_pending_tx.go
View file @
dc1ed3c9
...
...
@@ -8,7 +8,6 @@ import (
"strings"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
...
...
@@ -50,20 +49,20 @@ func ClearPendingTx(
// price.
sendTx
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
log
.
Info
(
name
+
" clearing pending tx"
,
"nonce"
,
nonce
,
"gasPrice"
,
gasPrice
)
log
.
Info
(
name
+
" clearing pending tx"
,
"nonce"
,
nonce
)
signedTx
,
err
:=
SignClearingTx
(
ctx
,
walletAddr
,
nonce
,
gasPri
ce
,
l1Client
,
privKey
,
chainID
,
name
,
ctx
,
walletAddr
,
non
ce
,
l1Client
,
privKey
,
chainID
,
)
if
err
!=
nil
{
log
.
Error
(
name
+
" unable to sign clearing tx"
,
"nonce"
,
nonce
,
"
gasPrice"
,
gasPrice
,
"
err"
,
err
)
"err"
,
err
)
return
nil
,
err
}
txHash
:=
signedTx
.
Hash
()
gasTipCap
:=
signedTx
.
GasTipCap
()
gasFeeCap
:=
signedTx
.
GasFeeCap
()
err
=
l1Client
.
SendTransaction
(
ctx
,
signedTx
)
switch
{
...
...
@@ -71,7 +70,8 @@ func ClearPendingTx(
// Clearing transaction successfully confirmed.
case
err
==
nil
:
log
.
Info
(
name
+
" submitted clearing tx"
,
"nonce"
,
nonce
,
"gasPrice"
,
gasPrice
,
"txHash"
,
txHash
)
"gasTipCap"
,
gasTipCap
,
"gasFeeCap"
,
gasFeeCap
,
"txHash"
,
txHash
)
return
signedTx
,
nil
...
...
@@ -91,8 +91,8 @@ func ClearPendingTx(
// transaction, or abort if the old one confirms.
default
:
log
.
Error
(
name
+
" unable to submit clearing tx"
,
"nonce"
,
nonce
,
"gas
Price"
,
gasPrice
,
"txHash"
,
txHash
,
"err"
,
err
)
"nonce"
,
nonce
,
"gas
TipCap"
,
gasTipCap
,
"gasFeeCap"
,
gasFeeCap
,
"
txHash"
,
txHash
,
"
err"
,
err
)
return
nil
,
err
}
}
...
...
@@ -127,26 +127,39 @@ func ClearPendingTx(
// SignClearingTx creates a signed clearing tranaction which sends 0 ETH back to
// the sender's address. EstimateGas is used to set an appropriate gas limit.
func
SignClearingTx
(
name
string
,
ctx
context
.
Context
,
walletAddr
common
.
Address
,
nonce
uint64
,
gasPrice
*
big
.
Int
,
l1Client
L1Client
,
privKey
*
ecdsa
.
PrivateKey
,
chainID
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
gasLimit
,
err
:=
l1Client
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
To
:
&
walletAddr
,
GasPrice
:
gasPrice
,
Value
:
nil
,
Data
:
nil
,
})
gasTipCap
,
err
:=
l1Client
.
SuggestGasTipCap
(
ctx
)
if
err
!=
nil
{
if
!
IsMaxPriorityFeePerGasNotFoundError
(
err
)
{
return
nil
,
err
}
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this
// method, so in the event their API is unreachable we can fallback to a
// degraded mode of operation. This also applies to our test
// environments, as hardhat doesn't support the query either.
log
.
Warn
(
name
+
" eth_maxPriorityFeePerGas is unsupported "
+
"by current backend, using fallback gasTipCap"
)
gasTipCap
=
FallbackGasTipCap
}
head
,
err
:=
l1Client
.
HeaderByNumber
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
err
}
tx
:=
CraftClearingTx
(
walletAddr
,
nonce
,
gasPrice
,
gasLimit
)
gasFeeCap
:=
txmgr
.
CalcGasFeeCap
(
head
.
BaseFee
,
gasTipCap
)
tx
:=
CraftClearingTx
(
walletAddr
,
nonce
,
gasFeeCap
,
gasTipCap
)
return
types
.
SignTx
(
tx
,
types
.
LatestSignerForChainID
(
chainID
),
privKey
,
...
...
@@ -158,16 +171,16 @@ func SignClearingTx(
func
CraftClearingTx
(
walletAddr
common
.
Address
,
nonce
uint64
,
gas
Price
*
big
.
Int
,
gas
Limit
uint64
,
gas
FeeCap
*
big
.
Int
,
gas
TipCap
*
big
.
Int
,
)
*
types
.
Transaction
{
return
types
.
NewTx
(
&
types
.
Legacy
Tx
{
To
:
&
walletAddr
,
Nonce
:
nonce
,
Gas
Price
:
gasPrice
,
Gas
:
gasLimit
,
Value
:
nil
,
Data
:
nil
,
return
types
.
NewTx
(
&
types
.
DynamicFee
Tx
{
To
:
&
walletAddr
,
Nonce
:
nonce
,
Gas
FeeCap
:
gasFeeCap
,
Gas
TipCap
:
gasTipCap
,
Value
:
nil
,
Data
:
nil
,
})
}
go/batch-submitter/drivers/clear_pending_tx_test.go
View file @
dc1ed3c9
...
...
@@ -11,8 +11,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/mock"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
...
...
@@ -27,8 +25,6 @@ func init() {
}
testPrivKey
=
privKey
testWalletAddr
=
crypto
.
PubkeyToAddress
(
privKey
.
PublicKey
)
testChainID
=
new
(
big
.
Int
)
.
SetUint64
(
1
)
testGasPrice
=
new
(
big
.
Int
)
.
SetUint64
(
3
)
}
var
(
...
...
@@ -36,21 +32,22 @@ var (
testWalletAddr
common
.
Address
testChainID
=
big
.
NewInt
(
1
)
testNonce
=
uint64
(
2
)
testGas
Price
=
big
.
NewInt
(
3
)
testGas
Limit
=
uint64
(
4
)
testGas
FeeCap
=
big
.
NewInt
(
3
)
testGas
TipCap
=
big
.
NewInt
(
4
)
testBlockNumber
=
uint64
(
5
)
testBaseFee
=
big
.
NewInt
(
6
)
)
// TestCraftClearingTx asserts that CraftClearingTx produces the expected
// unsigned clearing transaction.
func
TestCraftClearingTx
(
t
*
testing
.
T
)
{
tx
:=
drivers
.
CraftClearingTx
(
testWalletAddr
,
testNonce
,
testGas
Price
,
testGasLimit
,
testWalletAddr
,
testNonce
,
testGas
FeeCap
,
testGasTipCap
,
)
require
.
Equal
(
t
,
&
testWalletAddr
,
tx
.
To
())
require
.
Equal
(
t
,
testNonce
,
tx
.
Nonce
())
require
.
Equal
(
t
,
testGas
Price
,
tx
.
GasPrice
())
require
.
Equal
(
t
,
testGas
Limit
,
tx
.
Gas
())
require
.
Equal
(
t
,
testGas
FeeCap
,
tx
.
GasFeeCap
())
require
.
Equal
(
t
,
testGas
TipCap
,
tx
.
GasTipCap
())
require
.
Equal
(
t
,
new
(
big
.
Int
),
tx
.
Value
())
require
.
Nil
(
t
,
tx
.
Data
())
}
...
...
@@ -59,21 +56,31 @@ func TestCraftClearingTx(t *testing.T) {
// clearing transaction when the call to EstimateGas succeeds.
func
TestSignClearingTxEstimateGasSuccess
(
t
*
testing
.
T
)
{
l1Client
:=
mock
.
NewL1Client
(
mock
.
L1ClientConfig
{
EstimateGas
:
func
(
_
context
.
Context
,
_
ethereum
.
CallMsg
)
(
uint64
,
error
)
{
return
testGasLimit
,
nil
HeaderByNumber
:
func
(
_
context
.
Context
,
_
*
big
.
Int
)
(
*
types
.
Header
,
error
)
{
return
&
types
.
Header
{
BaseFee
:
testBaseFee
,
},
nil
},
SuggestGasTipCap
:
func
(
_
context
.
Context
)
(
*
big
.
Int
,
error
)
{
return
testGasTipCap
,
nil
},
})
expGasFeeCap
:=
new
(
big
.
Int
)
.
Add
(
testGasTipCap
,
new
(
big
.
Int
)
.
Mul
(
testBaseFee
,
big
.
NewInt
(
2
)),
)
tx
,
err
:=
drivers
.
SignClearingTx
(
context
.
Background
(),
testWalletAddr
,
testNonce
,
testGasPri
ce
,
l1Client
,
"TEST"
,
context
.
Background
(),
testWalletAddr
,
testNon
ce
,
l1Client
,
testPrivKey
,
testChainID
,
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
tx
)
require
.
Equal
(
t
,
&
testWalletAddr
,
tx
.
To
())
require
.
Equal
(
t
,
testNonce
,
tx
.
Nonce
())
require
.
Equal
(
t
,
testGasPrice
,
tx
.
GasPrice
())
require
.
Equal
(
t
,
testGas
Limit
,
tx
.
Gas
())
require
.
Equal
(
t
,
expGasFeeCap
,
tx
.
GasFeeCap
())
require
.
Equal
(
t
,
testGas
TipCap
,
tx
.
GasTipCap
())
require
.
Equal
(
t
,
new
(
big
.
Int
),
tx
.
Value
())
require
.
Nil
(
t
,
tx
.
Data
())
...
...
@@ -83,22 +90,44 @@ func TestSignClearingTxEstimateGasSuccess(t *testing.T) {
require
.
Equal
(
t
,
testWalletAddr
,
sender
)
}
// TestSignClearingTx
EstimateGasFail asserts that signing a clearing transaction
//
will fail if the underlying call to EstimateGas
fails.
func
TestSignClearingTx
EstimateGas
Fail
(
t
*
testing
.
T
)
{
err
EstimateGas
:=
errors
.
New
(
"estimate gas
"
)
// TestSignClearingTx
SuggestGasTipCapFail asserts that signing a clearing
//
transaction will fail if the underlying call to SuggestGasTipCap
fails.
func
TestSignClearingTx
SuggestGasTipCap
Fail
(
t
*
testing
.
T
)
{
err
SuggestGasTipCap
:=
errors
.
New
(
"suggest gas tip cap
"
)
l1Client
:=
mock
.
NewL1Client
(
mock
.
L1ClientConfig
{
EstimateGas
:
func
(
_
context
.
Context
,
_
ethereum
.
CallMsg
)
(
uint64
,
error
)
{
return
0
,
errEstimateGas
SuggestGasTipCap
:
func
(
_
context
.
Context
)
(
*
big
.
Int
,
error
)
{
return
nil
,
errSuggestGasTipCap
},
})
tx
,
err
:=
drivers
.
SignClearingTx
(
context
.
Background
(),
testWalletAddr
,
testNonce
,
testGasPri
ce
,
l1Client
,
"TEST"
,
context
.
Background
(),
testWalletAddr
,
testNon
ce
,
l1Client
,
testPrivKey
,
testChainID
,
)
require
.
Equal
(
t
,
errEstimateGas
,
err
)
require
.
Equal
(
t
,
errSuggestGasTipCap
,
err
)
require
.
Nil
(
t
,
tx
)
}
// TestSignClearingTxHeaderByNumberFail asserts that signing a clearing
// transaction will fail if the underlying call to HeaderByNumber fails.
func
TestSignClearingTxHeaderByNumberFail
(
t
*
testing
.
T
)
{
errHeaderByNumber
:=
errors
.
New
(
"header by number"
)
l1Client
:=
mock
.
NewL1Client
(
mock
.
L1ClientConfig
{
HeaderByNumber
:
func
(
_
context
.
Context
,
_
*
big
.
Int
)
(
*
types
.
Header
,
error
)
{
return
nil
,
errHeaderByNumber
},
SuggestGasTipCap
:
func
(
_
context
.
Context
)
(
*
big
.
Int
,
error
)
{
return
testGasTipCap
,
nil
},
})
tx
,
err
:=
drivers
.
SignClearingTx
(
"TEST"
,
context
.
Background
(),
testWalletAddr
,
testNonce
,
l1Client
,
testPrivKey
,
testChainID
,
)
require
.
Equal
(
t
,
errHeaderByNumber
,
err
)
require
.
Nil
(
t
,
tx
)
}
...
...
@@ -117,22 +146,26 @@ func newClearPendingTxHarnessWithNumConfs(
return
testBlockNumber
,
nil
}
}
if
l1ClientConfig
.
HeaderByNumber
==
nil
{
l1ClientConfig
.
HeaderByNumber
=
func
(
_
context
.
Context
,
_
*
big
.
Int
)
(
*
types
.
Header
,
error
)
{
return
&
types
.
Header
{
BaseFee
:
testBaseFee
,
},
nil
}
}
if
l1ClientConfig
.
NonceAt
==
nil
{
l1ClientConfig
.
NonceAt
=
func
(
_
context
.
Context
,
_
common
.
Address
,
_
*
big
.
Int
)
(
uint64
,
error
)
{
return
testNonce
,
nil
}
}
if
l1ClientConfig
.
EstimateGas
==
nil
{
l1ClientConfig
.
EstimateGas
=
func
(
_
context
.
Context
,
_
ethereum
.
CallMsg
)
(
uint64
,
error
)
{
return
testGas
Limit
,
nil
if
l1ClientConfig
.
SuggestGasTipCap
==
nil
{
l1ClientConfig
.
SuggestGasTipCap
=
func
(
_
context
.
Context
)
(
*
big
.
Int
,
error
)
{
return
testGas
TipCap
,
nil
}
}
l1Client
:=
mock
.
NewL1Client
(
l1ClientConfig
)
txMgr
:=
txmgr
.
NewSimpleTxManager
(
"test"
,
txmgr
.
Config
{
MinGasPrice
:
utils
.
GasPriceFromGwei
(
1
),
MaxGasPrice
:
utils
.
GasPriceFromGwei
(
100
),
GasRetryIncrement
:
utils
.
GasPriceFromGwei
(
5
),
ResubmissionTimeout
:
time
.
Second
,
ReceiptQueryInterval
:
50
*
time
.
Millisecond
,
NumConfirmations
:
numConfirmations
,
...
...
@@ -200,11 +233,14 @@ func TestClearPendingTxTimeout(t *testing.T) {
},
})
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
defer
cancel
()
err
:=
drivers
.
ClearPendingTx
(
"test"
,
c
ontext
.
Background
(),
h
.
txMgr
,
h
.
l1Client
,
testWalletAddr
,
test
PrivKey
,
test
ChainID
,
"test"
,
c
tx
,
h
.
txMgr
,
h
.
l1Client
,
testWalletAddr
,
testPrivKey
,
testChainID
,
)
require
.
Equal
(
t
,
txmgr
.
ErrPublishTimeout
,
err
)
require
.
Equal
(
t
,
context
.
DeadlineExceeded
,
err
)
}
// TestClearPendingTxMultipleConfs tests we wait the appropriate number of
...
...
@@ -225,12 +261,15 @@ func TestClearPendingTxMultipleConfs(t *testing.T) {
},
},
numConfs
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
defer
cancel
()
// The txmgr should timeout waiting for the txn to confirm.
err
:=
drivers
.
ClearPendingTx
(
"test"
,
c
ontext
.
Background
(),
h
.
txMgr
,
h
.
l1Client
,
testWalletAddr
,
test
PrivKey
,
test
ChainID
,
"test"
,
c
tx
,
h
.
txMgr
,
h
.
l1Client
,
testWalletAddr
,
testPrivKey
,
testChainID
,
)
require
.
Equal
(
t
,
txmgr
.
ErrPublishTimeout
,
err
)
require
.
Equal
(
t
,
context
.
DeadlineExceeded
,
err
)
// Now set the chain height to the earliest the transaction will be
// considered sufficiently confirmed.
...
...
go/batch-submitter/drivers/interface.go
View file @
dc1ed3c9
...
...
@@ -4,7 +4,6 @@ import (
"context"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
...
...
@@ -12,12 +11,9 @@ import (
// L1Client is an abstraction over an L1 Ethereum client functionality required
// by the batch submitter.
type
L1Client
interface
{
// EstimateGas tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as
// other transactions may be added or removed by miners, but it should
// provide a basis for setting a reasonable default.
EstimateGas
(
context
.
Context
,
ethereum
.
CallMsg
)
(
uint64
,
error
)
// HeaderByNumber returns a block header from the current canonical chain.
// If number is nil, the latest known header is returned.
HeaderByNumber
(
context
.
Context
,
*
big
.
Int
)
(
*
types
.
Header
,
error
)
// NonceAt returns the account nonce of the given account. The block number
// can be nil, in which case the nonce is taken from the latest known block.
...
...
@@ -30,6 +26,10 @@ type L1Client interface {
// method to get the contract address after the transaction has been mined.
SendTransaction
(
context
.
Context
,
*
types
.
Transaction
)
error
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559
// to allow a timely execution of a transaction.
SuggestGasTipCap
(
context
.
Context
)
(
*
big
.
Int
,
error
)
// TransactionReceipt returns the receipt of a transaction by transaction
// hash. Note that the receipt is not available for pending transactions.
TransactionReceipt
(
context
.
Context
,
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
...
...
go/batch-submitter/drivers/max_priority_fee_fallback.go
0 → 100644
View file @
dc1ed3c9
package
drivers
import
(
"errors"
"math/big"
"strings"
)
var
(
errMaxPriorityFeePerGasNotFound
=
errors
.
New
(
"Method eth_maxPriorityFeePerGas not found"
,
)
// FallbackGasTipCap is the default fallback gasTipCap used when we are
// unable to query an L1 backend for a suggested gasTipCap.
FallbackGasTipCap
=
big
.
NewInt
(
1500000000
)
)
// IsMaxPriorityFeePerGasNotFoundError returns true if the provided error
// signals that the backend does not support the eth_maxPrirorityFeePerGas
// method. In this case, the caller should fallback to using the constant above.
func
IsMaxPriorityFeePerGasNotFoundError
(
err
error
)
bool
{
return
strings
.
Contains
(
err
.
Error
(),
errMaxPriorityFeePerGasNotFound
.
Error
(),
)
}
go/batch-submitter/drivers/proposer/driver.go
View file @
dc1ed3c9
...
...
@@ -14,7 +14,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
l2ethclient
"github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum-optimism/optimism/l2geth/params"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
...
...
@@ -197,22 +196,43 @@ func (d *Driver) CraftBatchTx(
}
opts
.
Context
=
ctx
opts
.
Nonce
=
nonce
opts
.
GasPrice
=
big
.
NewInt
(
params
.
GWei
)
// dummy
opts
.
NoSend
=
true
blockOffset
:=
new
(
big
.
Int
)
.
SetUint64
(
d
.
cfg
.
BlockOffset
)
offsetStartsAtIndex
:=
new
(
big
.
Int
)
.
Sub
(
start
,
blockOffset
)
return
d
.
sccContract
.
AppendStateBatch
(
opts
,
stateRoots
,
offsetStartsAtIndex
)
tx
,
err
:=
d
.
sccContract
.
AppendStateBatch
(
opts
,
stateRoots
,
offsetStartsAtIndex
,
)
switch
{
case
err
==
nil
:
return
tx
,
nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this method,
// so in the event their API is unreachable we can fallback to a degraded
// mode of operation. This also applies to our test environments, as hardhat
// doesn't support the query either.
case
drivers
.
IsMaxPriorityFeePerGasNotFoundError
(
err
)
:
log
.
Warn
(
d
.
cfg
.
Name
+
" eth_maxPriorityFeePerGas is unsupported "
+
"by current backend, using fallback gasTipCap"
)
opts
.
GasTipCap
=
drivers
.
FallbackGasTipCap
return
d
.
sccContract
.
AppendStateBatch
(
opts
,
stateRoots
,
offsetStartsAtIndex
,
)
default
:
return
nil
,
err
}
}
// SubmitBatchTx using the passed transaction as a template, signs and
publishes
//
an otherwise identical transaction after setting the provided gas price. The
// final transaction is returned to the caller.
// SubmitBatchTx using the passed transaction as a template, signs and
//
publishes the transaction unmodified apart from sampling the current gas
//
price. The
final transaction is returned to the caller.
func
(
d
*
Driver
)
SubmitBatchTx
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
opts
,
err
:=
bind
.
NewKeyedTransactorWithChainID
(
...
...
@@ -223,7 +243,25 @@ func (d *Driver) SubmitBatchTx(
}
opts
.
Context
=
ctx
opts
.
Nonce
=
new
(
big
.
Int
)
.
SetUint64
(
tx
.
Nonce
())
opts
.
GasPrice
=
gasPrice
return
d
.
rawSccContract
.
RawTransact
(
opts
,
tx
.
Data
())
finalTx
,
err
:=
d
.
rawSccContract
.
RawTransact
(
opts
,
tx
.
Data
())
switch
{
case
err
==
nil
:
return
finalTx
,
nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this method,
// so in the event their API is unreachable we can fallback to a degraded
// mode of operation. This also applies to our test environments, as hardhat
// doesn't support the query either.
case
drivers
.
IsMaxPriorityFeePerGasNotFoundError
(
err
)
:
log
.
Warn
(
d
.
cfg
.
Name
+
" eth_maxPriorityFeePerGas is unsupported "
+
"by current backend, using fallback gasTipCap"
)
opts
.
GasTipCap
=
drivers
.
FallbackGasTipCap
return
d
.
rawSccContract
.
RawTransact
(
opts
,
tx
.
Data
())
default
:
return
nil
,
err
}
}
go/batch-submitter/drivers/sequencer/driver.go
View file @
dc1ed3c9
...
...
@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
l2ethclient
"github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/params"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
...
...
@@ -233,20 +232,37 @@ func (d *Driver) CraftBatchTx(
}
opts
.
Context
=
ctx
opts
.
Nonce
=
nonce
opts
.
GasPrice
=
big
.
NewInt
(
params
.
GWei
)
// dummy
opts
.
NoSend
=
true
return
d
.
rawCtcContract
.
RawTransact
(
opts
,
batchCallData
)
tx
,
err
:=
d
.
rawCtcContract
.
RawTransact
(
opts
,
batchCallData
)
switch
{
case
err
==
nil
:
return
tx
,
nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this
// method, so in the event their API is unreachable we can fallback to a
// degraded mode of operation. This also applies to our test
// environments, as hardhat doesn't support the query either.
case
drivers
.
IsMaxPriorityFeePerGasNotFoundError
(
err
)
:
log
.
Warn
(
d
.
cfg
.
Name
+
" eth_maxPriorityFeePerGas is unsupported "
+
"by current backend, using fallback gasTipCap"
)
opts
.
GasTipCap
=
drivers
.
FallbackGasTipCap
return
d
.
rawCtcContract
.
RawTransact
(
opts
,
batchCallData
)
default
:
return
nil
,
err
}
}
}
// SubmitBatchTx using the passed transaction as a template, signs and publishes
//
an otherwise identical transaction after setting the provided
gas price. The
//
the transaction unmodified apart from sampling the current
gas price. The
// final transaction is returned to the caller.
func
(
d
*
Driver
)
SubmitBatchTx
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
opts
,
err
:=
bind
.
NewKeyedTransactorWithChainID
(
...
...
@@ -257,7 +273,25 @@ func (d *Driver) SubmitBatchTx(
}
opts
.
Context
=
ctx
opts
.
Nonce
=
new
(
big
.
Int
)
.
SetUint64
(
tx
.
Nonce
())
opts
.
GasPrice
=
gasPrice
return
d
.
rawCtcContract
.
RawTransact
(
opts
,
tx
.
Data
())
finalTx
,
err
:=
d
.
rawCtcContract
.
RawTransact
(
opts
,
tx
.
Data
())
switch
{
case
err
==
nil
:
return
finalTx
,
nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this method,
// so in the event their API is unreachable we can fallback to a degraded
// mode of operation. This also applies to our test environments, as hardhat
// doesn't support the query either.
case
drivers
.
IsMaxPriorityFeePerGasNotFoundError
(
err
)
:
log
.
Warn
(
d
.
cfg
.
Name
+
" eth_maxPriorityFeePerGas is unsupported "
+
"by current backend, using fallback gasTipCap"
)
opts
.
GasTipCap
=
drivers
.
FallbackGasTipCap
return
d
.
rawCtcContract
.
RawTransact
(
opts
,
tx
.
Data
())
default
:
return
nil
,
err
}
}
go/batch-submitter/flags/flags.go
View file @
dc1ed3c9
...
...
@@ -151,18 +151,6 @@ var (
Value
:
1
,
EnvVar
:
prefixEnvVar
(
"BLOCK_OFFSET"
),
}
MaxGasPriceInGweiFlag
=
cli
.
Uint64Flag
{
Name
:
"max-gas-price-in-gwei"
,
Usage
:
"Maximum gas price the batch submitter can use for transactions"
,
Value
:
100
,
EnvVar
:
prefixEnvVar
(
"MAX_GAS_PRICE_IN_GWEI"
),
}
GasRetryIncrementFlag
=
cli
.
Uint64Flag
{
Name
:
"gas-retry-increment"
,
Usage
:
"Default step by which to increment gas price bumps"
,
Value
:
5
,
EnvVar
:
prefixEnvVar
(
"GAS_RETRY_INCREMENT_FLAG"
),
}
SequencerPrivateKeyFlag
=
cli
.
StringFlag
{
Name
:
"sequencer-private-key"
,
Usage
:
"The private key to use for sending to the sequencer contract"
,
...
...
@@ -235,8 +223,6 @@ var optionalFlags = []cli.Flag{
SentryDsnFlag
,
SentryTraceRateFlag
,
BlockOffsetFlag
,
MaxGasPriceInGweiFlag
,
GasRetryIncrementFlag
,
SequencerPrivateKeyFlag
,
ProposerPrivateKeyFlag
,
MnemonicFlag
,
...
...
go/batch-submitter/mock/l1client.go
View file @
dc1ed3c9
...
...
@@ -5,7 +5,6 @@ import (
"math/big"
"sync"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
...
...
@@ -16,12 +15,9 @@ type L1ClientConfig struct {
// BlockNumber returns the most recent block number.
BlockNumber
func
(
context
.
Context
)
(
uint64
,
error
)
// EstimateGas tries to estimate the gas needed to execute a specific
// transaction based on the current pending state of the backend blockchain.
// There is no guarantee that this is the true gas limit requirement as
// other transactions may be added or removed by miners, but it should
// provide a basis for setting a reasonable default.
EstimateGas
func
(
context
.
Context
,
ethereum
.
CallMsg
)
(
uint64
,
error
)
// HeaderByNumber returns a block header from the current canonical chain.
// If number is nil, the latest known header is returned.
HeaderByNumber
func
(
context
.
Context
,
*
big
.
Int
)
(
*
types
.
Header
,
error
)
// NonceAt returns the account nonce of the given account. The block number
// can be nil, in which case the nonce is taken from the latest known block.
...
...
@@ -34,6 +30,10 @@ type L1ClientConfig struct {
// method to get the contract address after the transaction has been mined.
SendTransaction
func
(
context
.
Context
,
*
types
.
Transaction
)
error
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559
// to allow a timely execution of a transaction.
SuggestGasTipCap
func
(
context
.
Context
)
(
*
big
.
Int
,
error
)
// TransactionReceipt returns the receipt of a transaction by transaction
// hash. Note that the receipt is not available for pending transactions.
TransactionReceipt
func
(
context
.
Context
,
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
...
...
@@ -61,12 +61,13 @@ func (c *L1Client) BlockNumber(ctx context.Context) (uint64, error) {
return
c
.
cfg
.
BlockNumber
(
ctx
)
}
// EstimateGas executes the mock EstimateGas method.
func
(
c
*
L1Client
)
EstimateGas
(
ctx
context
.
Context
,
call
ethereum
.
CallMsg
)
(
uint64
,
error
)
{
// HeaderByNumber returns a block header from the current canonical chain. If
// number is nil, the latest known header is returned.
func
(
c
*
L1Client
)
HeaderByNumber
(
ctx
context
.
Context
,
blockNumber
*
big
.
Int
)
(
*
types
.
Header
,
error
)
{
c
.
mu
.
RLock
()
defer
c
.
mu
.
RUnlock
()
return
c
.
cfg
.
EstimateGas
(
ctx
,
call
)
return
c
.
cfg
.
HeaderByNumber
(
ctx
,
blockNumber
)
}
// NonceAt executes the mock NonceAt method.
...
...
@@ -85,6 +86,15 @@ func (c *L1Client) SendTransaction(ctx context.Context, tx *types.Transaction) e
return
c
.
cfg
.
SendTransaction
(
ctx
,
tx
)
}
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to
// allow a timely execution of a transaction.
func
(
c
*
L1Client
)
SuggestGasTipCap
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
c
.
mu
.
RLock
()
defer
c
.
mu
.
RUnlock
()
return
c
.
cfg
.
SuggestGasTipCap
(
ctx
)
}
// TransactionReceipt executes the mock TransactionReceipt method.
func
(
c
*
L1Client
)
TransactionReceipt
(
ctx
context
.
Context
,
txHash
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
{
c
.
mu
.
RLock
()
...
...
@@ -103,17 +113,17 @@ func (c *L1Client) SetBlockNumberFunc(
c
.
cfg
.
BlockNumber
=
f
}
// Set
EstimateGasFunc overrwrites the mock EstimateGas
method.
func
(
c
*
L1Client
)
Set
EstimateGas
Func
(
f
func
(
c
ontext
.
Context
,
ethereum
.
CallMsg
)
(
uint64
,
error
))
{
// Set
HeaderByNumberFunc overwrites the mock HeaderByNumber
method.
func
(
c
*
L1Client
)
Set
HeaderByNumber
Func
(
f
func
(
c
tx
context
.
Context
,
blockNumber
*
big
.
Int
)
(
*
types
.
Header
,
error
))
{
c
.
mu
.
Lock
()
defer
c
.
mu
.
Unlock
()
c
.
cfg
.
EstimateGas
=
f
c
.
cfg
.
HeaderByNumber
=
f
}
// SetNonceAtFunc over
r
writes the mock NonceAt method.
// SetNonceAtFunc overwrites the mock NonceAt method.
func
(
c
*
L1Client
)
SetNonceAtFunc
(
f
func
(
context
.
Context
,
common
.
Address
,
*
big
.
Int
)
(
uint64
,
error
))
{
...
...
@@ -123,7 +133,7 @@ func (c *L1Client) SetNonceAtFunc(
c
.
cfg
.
NonceAt
=
f
}
// SetSendTransactionFunc over
r
writes the mock SendTransaction method.
// SetSendTransactionFunc overwrites the mock SendTransaction method.
func
(
c
*
L1Client
)
SetSendTransactionFunc
(
f
func
(
context
.
Context
,
*
types
.
Transaction
)
error
)
{
...
...
@@ -133,6 +143,16 @@ func (c *L1Client) SetSendTransactionFunc(
c
.
cfg
.
SendTransaction
=
f
}
// SetSuggestGasTipCapFunc overwrites themock SuggestGasTipCap method.
func
(
c
*
L1Client
)
SetSuggestGasTipCapFunc
(
f
func
(
context
.
Context
)
(
*
big
.
Int
,
error
))
{
c
.
mu
.
Lock
()
defer
c
.
mu
.
Unlock
()
c
.
cfg
.
SuggestGasTipCap
=
f
}
// SetTransactionReceiptFunc overwrites the mock TransactionReceipt method.
func
(
c
*
L1Client
)
SetTransactionReceiptFunc
(
f
func
(
context
.
Context
,
common
.
Hash
)
(
*
types
.
Receipt
,
error
))
{
...
...
go/batch-submitter/service.go
View file @
dc1ed3c9
...
...
@@ -55,12 +55,11 @@ type Driver interface {
)
(
*
types
.
Transaction
,
error
)
// SubmitBatchTx using the passed transaction as a template, signs and
// publishes
an otherwise identical transaction after setting the provided
//
gas
price. The final transaction is returned to the caller.
// publishes
the transaction unmodified apart from sampling the current gas
// price. The final transaction is returned to the caller.
SubmitBatchTx
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
}
...
...
@@ -194,15 +193,11 @@ func (s *Service) eventLoop() {
// Construct the transaction submission clousure that will attempt
// to send the next transaction at the given nonce and gas price.
sendTx
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
sendTx
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
log
.
Info
(
name
+
" attempting batch tx"
,
"start"
,
start
,
"end"
,
end
,
"nonce"
,
nonce
,
"gasPrice"
,
gasPrice
)
"end"
,
end
,
"nonce"
,
nonce
)
tx
,
err
:=
s
.
cfg
.
Driver
.
SubmitBatchTx
(
ctx
,
tx
,
gasPrice
)
tx
,
err
:=
s
.
cfg
.
Driver
.
SubmitBatchTx
(
ctx
,
tx
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -213,7 +208,6 @@ func (s *Service) eventLoop() {
"end"
,
end
,
"nonce"
,
nonce
,
"tx_hash"
,
tx
.
Hash
(),
"gasPrice"
,
gasPrice
,
)
return
tx
,
nil
...
...
go/batch-submitter/txmgr/txmgr.go
View file @
dc1ed3c9
...
...
@@ -2,46 +2,27 @@ package txmgr
import
(
"context"
"errors"
"math/big"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// ErrPublishTimeout signals that the tx manager did not receive a confirmation
// for a given tx after publishing with the maximum gas price and waiting out a
// resubmission timeout.
var
ErrPublishTimeout
=
errors
.
New
(
"failed to publish tx with max gas price"
)
// SendTxFunc defines a function signature for publishing a desired tx with a
// specific gas price. Implementations of this signature should also return
// promptly when the context is canceled.
type
SendTxFunc
=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
)
(
*
types
.
Transaction
,
error
)
type
SendTxFunc
=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
// Config houses parameters for altering the behavior of a SimpleTxManager.
type
Config
struct
{
// Name the name of the driver to appear in log lines.
Name
string
// MinGasPrice is the minimum gas price (in gwei). This is used as the
// initial publication attempt.
MinGasPrice
*
big
.
Int
// MaxGasPrice is the maximum gas price (in gwei). This is used to clamp
// the upper end of the range that the TxManager will ever publish when
// attempting to confirm a transaction.
MaxGasPrice
*
big
.
Int
// GasRetryIncrement is the additive gas price (in gwei) that will be
// used to bump each successive tx after a ResubmissionTimeout has
// elapsed.
GasRetryIncrement
*
big
.
Int
// ResubmissionTimeout is the interval at which, if no previously
// published transaction has been mined, the new tx with a bumped gas
// price will be published. Only one publication at MaxGasPrice will be
...
...
@@ -135,25 +116,29 @@ func (m *SimpleTxManager) Send(
// background, returning the first successfully mined receipt back to
// the main event loop via receiptChan.
receiptChan
:=
make
(
chan
*
types
.
Receipt
,
1
)
sendTxAsync
:=
func
(
gasPrice
*
big
.
Int
)
{
sendTxAsync
:=
func
()
{
defer
wg
.
Done
()
// Sign and publish transaction with current gas price.
tx
,
err
:=
sendTx
(
ctxc
,
gasPrice
)
tx
,
err
:=
sendTx
(
ctxc
)
if
err
!=
nil
{
if
err
==
context
.
Canceled
||
strings
.
Contains
(
err
.
Error
(),
"context canceled"
)
{
return
}
log
.
Error
(
name
+
" unable to publish transaction"
,
"gas_price"
,
gasPrice
,
"err"
,
err
)
log
.
Error
(
name
+
" unable to publish transaction"
,
"err"
,
err
)
if
shouldAbortImmediately
(
err
)
{
cancel
()
}
// TODO(conner): add retry?
return
}
txHash
:=
tx
.
Hash
()
gasTipCap
:=
tx
.
GasTipCap
()
gasFeeCap
:=
tx
.
GasFeeCap
()
log
.
Info
(
name
+
" transaction published successfully"
,
"hash"
,
txHash
,
"gas
_price"
,
gasPrice
)
"gas
TipCap"
,
gasTipCap
,
"gasFeeCap"
,
gasFeeCap
)
// Wait for the transaction to be mined, reporting the receipt
// back to the main event loop if found.
...
...
@@ -163,7 +148,7 @@ func (m *SimpleTxManager) Send(
)
if
err
!=
nil
{
log
.
Debug
(
name
+
" send tx failed"
,
"hash"
,
txHash
,
"gas
_price"
,
gasPrice
,
"err"
,
err
)
"gas
TipCap"
,
gasTipCap
,
"gasFeeCap"
,
gasFeeCap
,
"err"
,
err
)
}
if
receipt
!=
nil
{
// Use non-blocking select to ensure function can exit
...
...
@@ -171,20 +156,17 @@ func (m *SimpleTxManager) Send(
select
{
case
receiptChan
<-
receipt
:
log
.
Trace
(
name
+
" send tx succeeded"
,
"hash"
,
txHash
,
"gas
_price"
,
gasPrice
)
"gas
TipCap"
,
gasTipCap
,
"gasFeeCap"
,
gasFeeCap
)
default
:
}
}
}
// Initialize our initial gas price to the configured minimum.
curGasPrice
:=
new
(
big
.
Int
)
.
Set
(
m
.
cfg
.
MinGasPrice
)
// Submit and wait for the receipt at our first gas price in the
// background, before entering the event loop and waiting out the
// resubmission timeout.
wg
.
Add
(
1
)
go
sendTxAsync
(
curGasPrice
)
go
sendTxAsync
()
for
{
select
{
...
...
@@ -192,24 +174,9 @@ func (m *SimpleTxManager) Send(
// Whenever a resubmission timeout has elapsed, bump the gas
// price and publish a new transaction.
case
<-
time
.
After
(
m
.
cfg
.
ResubmissionTimeout
)
:
// If our last attempt published at the max gas price,
// return an error as we are unlikely to succeed in
// publishing. This also indicates that the max gas
// price should likely be adjusted higher for the
// daemon.
if
curGasPrice
.
Cmp
(
m
.
cfg
.
MaxGasPrice
)
>=
0
{
return
nil
,
ErrPublishTimeout
}
// Bump the gas price using linear gas price increments.
curGasPrice
=
NextGasPrice
(
curGasPrice
,
m
.
cfg
.
GasRetryIncrement
,
m
.
cfg
.
MaxGasPrice
,
)
// Submit and wait for the bumped traction to confirm.
wg
.
Add
(
1
)
go
sendTxAsync
(
curGasPrice
)
go
sendTxAsync
()
// The passed context has been canceled, i.e. in the event of a
// shutdown.
...
...
@@ -223,6 +190,13 @@ func (m *SimpleTxManager) Send(
}
}
// shouldAbortImmediately returns true if the txmgr should cancel all
// publication attempts and retry. For now, this only includes nonce errors, as
// that error indicates that none of the transactions will ever confirm.
func
shouldAbortImmediately
(
err
error
)
bool
{
return
strings
.
Contains
(
err
.
Error
(),
core
.
ErrNonceTooLow
.
Error
())
}
// WaitMined blocks until the backend indicates confirmation of tx and returns
// the tx receipt. Queries are made every queryInterval, regardless of whether
// the backend returns an error. This method can be canceled using the passed
...
...
@@ -289,17 +263,12 @@ func WaitMined(
}
}
// NextGasPrice bumps the current gas price using an additive gasRetryIncrement,
// clamping the resulting value to maxGasPrice.
//
// NOTE: This method does not mutate curGasPrice, but instead returns a copy.
// This removes the possiblity of races occuring from goroutines sharing access
// to the same underlying big.Int.
func
NextGasPrice
(
curGasPrice
,
gasRetryIncrement
,
maxGasPrice
*
big
.
Int
)
*
big
.
Int
{
nextGasPrice
:=
new
(
big
.
Int
)
.
Set
(
curGasPrice
)
nextGasPrice
.
Add
(
nextGasPrice
,
gasRetryIncrement
)
if
nextGasPrice
.
Cmp
(
maxGasPrice
)
==
1
{
nextGasPrice
.
Set
(
maxGasPrice
)
}
return
nextGasPrice
// CalcGasFeeCap deterministically computes the recommended gas fee cap given
// the base fee and gasTipCap. The resulting gasFeeCap is equal to:
// gasTipCap + 2*baseFee.
func
CalcGasFeeCap
(
baseFee
,
gasTipCap
*
big
.
Int
)
*
big
.
Int
{
return
new
(
big
.
Int
)
.
Add
(
gasTipCap
,
new
(
big
.
Int
)
.
Mul
(
baseFee
,
big
.
NewInt
(
2
)),
)
}
go/batch-submitter/txmgr/txmgr_test.go
View file @
dc1ed3c9
...
...
@@ -14,69 +14,12 @@ import (
"github.com/stretchr/testify/require"
)
// TestNextGasPrice asserts that NextGasPrice properly bumps the passed current
// gas price, and clamps it to the max gas price. It also tests that
// NextGasPrice doesn't mutate the passed curGasPrice argument.
func
TestNextGasPrice
(
t
*
testing
.
T
)
{
t
.
Parallel
()
tests
:=
[]
struct
{
name
string
curGasPrice
*
big
.
Int
gasRetryIncrement
*
big
.
Int
maxGasPrice
*
big
.
Int
expGasPrice
*
big
.
Int
}{
{
name
:
"increment below max"
,
curGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
5
),
gasRetryIncrement
:
new
(
big
.
Int
)
.
SetUint64
(
10
),
maxGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
20
),
expGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
15
),
},
{
name
:
"increment equal max"
,
curGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
5
),
gasRetryIncrement
:
new
(
big
.
Int
)
.
SetUint64
(
10
),
maxGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
15
),
expGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
15
),
},
{
name
:
"increment above max"
,
curGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
5
),
gasRetryIncrement
:
new
(
big
.
Int
)
.
SetUint64
(
10
),
maxGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
12
),
expGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
12
),
},
}
for
_
,
test
:=
range
tests
{
t
.
Run
(
test
.
name
,
func
(
t
*
testing
.
T
)
{
// Copy curGasPrice, as we will later test for mutation.
curGasPrice
:=
new
(
big
.
Int
)
.
Set
(
test
.
curGasPrice
)
nextGasPrice
:=
txmgr
.
NextGasPrice
(
curGasPrice
,
test
.
gasRetryIncrement
,
test
.
maxGasPrice
,
)
require
.
Equal
(
t
,
nextGasPrice
,
test
.
expGasPrice
)
// Ensure curGasPrice hasn't been mutated. This check
// enforces that NextGasPrice creates a copy internally.
// Failure to do so could result in gas price bumps
// being read concurrently from other goroutines, and
// introduce race conditions.
require
.
Equal
(
t
,
curGasPrice
,
test
.
curGasPrice
)
})
}
}
// testHarness houses the necessary resources to test the SimpleTxManager.
type
testHarness
struct
{
cfg
txmgr
.
Config
mgr
txmgr
.
TxManager
backend
*
mockBackend
cfg
txmgr
.
Config
mgr
txmgr
.
TxManager
backend
*
mockBackend
gasPricer
*
gasPricer
}
// newTestHarnessWithConfig initializes a testHarness with a specific
...
...
@@ -86,9 +29,10 @@ func newTestHarnessWithConfig(cfg txmgr.Config) *testHarness {
mgr
:=
txmgr
.
NewSimpleTxManager
(
"TEST"
,
cfg
,
backend
)
return
&
testHarness
{
cfg
:
cfg
,
mgr
:
mgr
,
backend
:
backend
,
cfg
:
cfg
,
mgr
:
mgr
,
backend
:
backend
,
gasPricer
:
newGasPricer
(
3
),
}
}
...
...
@@ -100,17 +44,54 @@ func newTestHarness() *testHarness {
func
configWithNumConfs
(
numConfirmations
uint64
)
txmgr
.
Config
{
return
txmgr
.
Config
{
MinGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
5
),
MaxGasPrice
:
new
(
big
.
Int
)
.
SetUint64
(
50
),
GasRetryIncrement
:
new
(
big
.
Int
)
.
SetUint64
(
5
),
ResubmissionTimeout
:
time
.
Second
,
ReceiptQueryInterval
:
50
*
time
.
Millisecond
,
NumConfirmations
:
numConfirmations
,
}
}
type
gasPricer
struct
{
epoch
int64
mineAtEpoch
int64
baseGasTipFee
*
big
.
Int
baseBaseFee
*
big
.
Int
mu
sync
.
Mutex
}
func
newGasPricer
(
mineAtEpoch
int64
)
*
gasPricer
{
return
&
gasPricer
{
mineAtEpoch
:
mineAtEpoch
,
baseGasTipFee
:
big
.
NewInt
(
5
),
baseBaseFee
:
big
.
NewInt
(
7
),
}
}
func
(
g
*
gasPricer
)
expGasFeeCap
()
*
big
.
Int
{
_
,
gasFeeCap
:=
g
.
feesForEpoch
(
g
.
mineAtEpoch
)
return
gasFeeCap
}
func
(
g
*
gasPricer
)
feesForEpoch
(
epoch
int64
)
(
*
big
.
Int
,
*
big
.
Int
)
{
epochBaseFee
:=
new
(
big
.
Int
)
.
Mul
(
g
.
baseBaseFee
,
big
.
NewInt
(
epoch
))
epochGasTipCap
:=
new
(
big
.
Int
)
.
Mul
(
g
.
baseGasTipFee
,
big
.
NewInt
(
epoch
))
epochGasFeeCap
:=
txmgr
.
CalcGasFeeCap
(
epochBaseFee
,
epochGasTipCap
)
return
epochGasTipCap
,
epochGasFeeCap
}
func
(
g
*
gasPricer
)
sample
()
(
*
big
.
Int
,
*
big
.
Int
,
bool
)
{
g
.
mu
.
Lock
()
defer
g
.
mu
.
Unlock
()
g
.
epoch
++
epochGasTipCap
,
epochGasFeeCap
:=
g
.
feesForEpoch
(
g
.
epoch
)
shouldMine
:=
g
.
epoch
==
g
.
mineAtEpoch
return
epochGasTipCap
,
epochGasFeeCap
,
shouldMine
}
type
minedTxInfo
struct
{
gas
Price
*
big
.
Int
gas
FeeCap
*
big
.
Int
blockNumber
uint64
}
...
...
@@ -133,17 +114,17 @@ func newMockBackend() *mockBackend {
}
}
// mine records a (txHash, gas
Price
) as confirmed. Subsequent calls to
// mine records a (txHash, gas
FeeCap
) as confirmed. Subsequent calls to
// TransactionReceipt with a matching txHash will result in a non-nil receipt.
// If a nil txHash is supplied this has the effect of mining an empty block.
func
(
b
*
mockBackend
)
mine
(
txHash
*
common
.
Hash
,
gas
Price
*
big
.
Int
)
{
func
(
b
*
mockBackend
)
mine
(
txHash
*
common
.
Hash
,
gas
FeeCap
*
big
.
Int
)
{
b
.
mu
.
Lock
()
defer
b
.
mu
.
Unlock
()
b
.
blockHeight
++
if
txHash
!=
nil
{
b
.
minedTxs
[
*
txHash
]
=
minedTxInfo
{
gas
Price
:
gasPrice
,
gas
FeeCap
:
gasFeeCap
,
blockNumber
:
b
.
blockHeight
,
}
}
...
...
@@ -159,7 +140,7 @@ func (b *mockBackend) BlockNumber(ctx context.Context) (uint64, error) {
// TransactionReceipt queries the mockBackend for a mined txHash. If none is
// found, nil is returned for both return values. Otherwise, it retruns a
// receipt containing the txHash and the gas
Price
used in the GasUsed to make
// receipt containing the txHash and the gas
FeeCap
used in the GasUsed to make
// the value accessible from our test framework.
func
(
b
*
mockBackend
)
TransactionReceipt
(
ctx
context
.
Context
,
...
...
@@ -174,11 +155,11 @@ func (b *mockBackend) TransactionReceipt(
return
nil
,
nil
}
// Return the gas
price
for the transaction in the GasUsed field so that
// Return the gas
fee cap
for the transaction in the GasUsed field so that
// we can assert the proper tx confirmed in our tests.
return
&
types
.
Receipt
{
TxHash
:
txHash
,
GasUsed
:
txInfo
.
gas
Price
.
Uint64
(),
GasUsed
:
txInfo
.
gas
FeeCap
.
Uint64
(),
BlockNumber
:
big
.
NewInt
(
int64
(
txInfo
.
blockNumber
)),
},
nil
}
...
...
@@ -189,15 +170,16 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
t
.
Parallel
()
h
:=
newTestHarness
()
gasFeeCap
:=
big
.
NewInt
(
5
)
sendTxFunc
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
tx
:=
types
.
NewTx
(
&
types
.
Legacy
Tx
{
Gas
Price
:
gasPrice
,
tx
:=
types
.
NewTx
(
&
types
.
DynamicFee
Tx
{
Gas
FeeCap
:
gasFeeCap
,
})
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
gas
Price
)
h
.
backend
.
mine
(
&
txHash
,
gas
FeeCap
)
return
tx
,
nil
}
...
...
@@ -205,7 +187,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
sendTxFunc
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
receipt
.
GasUsed
,
h
.
cfg
.
MinGasPrice
.
Uint64
()
)
require
.
Equal
(
t
,
gasFeeCap
.
Uint64
(),
receipt
.
GasUsed
)
}
// TestTxMgrNeverConfirmCancel asserts that a Send can be canceled even if no
...
...
@@ -218,11 +200,10 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
sendTxFunc
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
// Don't publish tx to backend, simulating never being mined.
return
types
.
NewTx
(
&
types
.
Legacy
Tx
{
Gas
Price
:
gasPrice
,
return
types
.
NewTx
(
&
types
.
DynamicFee
Tx
{
Gas
FeeCap
:
big
.
NewInt
(
5
)
,
}),
nil
}
...
...
@@ -236,21 +217,22 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
// TestTxMgrConfirmsAtMaxGasPrice asserts that Send properly returns the max gas
// price receipt if none of the lower gas price txs were mined.
func
TestTxMgrConfirmsAt
Max
GasPrice
(
t
*
testing
.
T
)
{
func
TestTxMgrConfirmsAt
Higher
GasPrice
(
t
*
testing
.
T
)
{
t
.
Parallel
()
h
:=
newTestHarness
()
sendTxFunc
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{
GasPrice
:
gasPrice
,
gasTipCap
,
gasFeeCap
,
shouldMine
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
})
if
gasPrice
.
Cmp
(
h
.
cfg
.
MaxGasPrice
)
==
0
{
if
shouldMine
{
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
gas
Price
)
h
.
backend
.
mine
(
&
txHash
,
gas
FeeCap
)
}
return
tx
,
nil
}
...
...
@@ -259,40 +241,7 @@ func TestTxMgrConfirmsAtMaxGasPrice(t *testing.T) {
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
sendTxFunc
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
receipt
.
GasUsed
,
h
.
cfg
.
MaxGasPrice
.
Uint64
())
}
// TestTxMgrConfirmsAtMaxGasPriceDelayed asserts that after the maximum gas
// price tx has been published, and a resubmission timeout has elapsed, that an
// error is returned signaling that even our max gas price is taking too long.
func
TestTxMgrConfirmsAtMaxGasPriceDelayed
(
t
*
testing
.
T
)
{
t
.
Parallel
()
h
:=
newTestHarness
()
sendTxFunc
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{
GasPrice
:
gasPrice
,
})
// Delay mining of the max gas price tx by more than the
// resubmission timeout. Default config uses 1 second. Send
// should still return an error beforehand.
if
gasPrice
.
Cmp
(
h
.
cfg
.
MaxGasPrice
)
==
0
{
time
.
AfterFunc
(
2
*
time
.
Second
,
func
()
{
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
gasPrice
)
})
}
return
tx
,
nil
}
ctx
:=
context
.
Background
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
sendTxFunc
)
require
.
Equal
(
t
,
err
,
txmgr
.
ErrPublishTimeout
)
require
.
Nil
(
t
,
receipt
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
}
// errRpcFailure is a sentinel error used in testing to fail publications.
...
...
@@ -308,14 +257,15 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
sendTxFunc
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
return
nil
,
errRpcFailure
}
ctx
:=
context
.
Background
()
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
sendTxFunc
)
require
.
Equal
(
t
,
err
,
txmgr
.
ErrPublishTimeout
)
require
.
Equal
(
t
,
err
,
context
.
DeadlineExceeded
)
require
.
Nil
(
t
,
receipt
)
}
...
...
@@ -329,18 +279,20 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
sendTxFunc
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
,
shouldMine
:=
h
.
gasPricer
.
sample
()
// Fail all but the final attempt.
if
gasPrice
.
Cmp
(
h
.
cfg
.
MaxGasPrice
)
!=
0
{
if
!
shouldMine
{
return
nil
,
errRpcFailure
}
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{
GasPrice
:
gasPrice
,
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
})
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
gas
Price
)
h
.
backend
.
mine
(
&
txHash
,
gas
FeeCap
)
return
tx
,
nil
}
...
...
@@ -349,7 +301,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
receipt
.
GasUsed
,
h
.
cfg
.
MaxGasPrice
.
Uint64
()
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
}
// TestTxMgrConfirmsMinGasPriceAfterBumping delays the mining of the initial tx
...
...
@@ -362,16 +314,17 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
sendTxFunc
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{
GasPrice
:
gasPrice
,
gasTipCap
,
gasFeeCap
,
shouldMine
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
})
// Delay mining the tx with the min gas price.
if
gasPrice
.
Cmp
(
h
.
cfg
.
MinGasPrice
)
==
0
{
if
shouldMine
{
time
.
AfterFunc
(
5
*
time
.
Second
,
func
()
{
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
gas
Price
)
h
.
backend
.
mine
(
&
txHash
,
gas
FeeCap
)
})
}
return
tx
,
nil
...
...
@@ -381,7 +334,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
sendTxFunc
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
receipt
.
GasUsed
,
h
.
cfg
.
MinGasPrice
.
Uint64
()
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
}
// TestWaitMinedReturnsReceiptOnFirstSuccess insta-mines a transaction and
...
...
go/batch-submitter/utils/gas_price.go
deleted
100644 → 0
View file @
36151fe8
package
utils
import
(
"math/big"
"github.com/ethereum/go-ethereum/params"
)
// GasPriceFromGwei converts an uint64 gas price in gwei to a big.Int in wei.
func
GasPriceFromGwei
(
gasPriceInGwei
uint64
)
*
big
.
Int
{
return
new
(
big
.
Int
)
.
SetUint64
(
gasPriceInGwei
*
params
.
GWei
)
}
go/batch-submitter/utils/gas_price_test.go
deleted
100644 → 0
View file @
36151fe8
package
utils_test
import
(
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// TestGasPriceFromGwei asserts that the integer value is scaled properly by
// 10^9.
func
TestGasPriceFromGwei
(
t
*
testing
.
T
)
{
require
.
Equal
(
t
,
utils
.
GasPriceFromGwei
(
0
),
new
(
big
.
Int
))
require
.
Equal
(
t
,
utils
.
GasPriceFromGwei
(
1
),
big
.
NewInt
(
params
.
GWei
))
require
.
Equal
(
t
,
utils
.
GasPriceFromGwei
(
100
),
big
.
NewInt
(
100
*
params
.
GWei
))
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment