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
771988b6
Unverified
Commit
771988b6
authored
Jan 06, 2024
by
Roberto Bayardo
Committed by
GitHub
Jan 06, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update txmgr to support sending blob txs (#8760)
parent
28c35962
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
526 additions
and
137 deletions
+526
-137
price_bump_test.go
op-service/txmgr/price_bump_test.go
+62
-1
queue_test.go
op-service/txmgr/queue_test.go
+1
-1
txmgr.go
op-service/txmgr/txmgr.go
+228
-70
txmgr_test.go
op-service/txmgr/txmgr_test.go
+235
-65
No files found.
op-service/txmgr/price_bump_test.go
View file @
771988b6
...
@@ -19,13 +19,14 @@ type priceBumpTest struct {
...
@@ -19,13 +19,14 @@ type priceBumpTest struct {
newBasefee
int64
newBasefee
int64
expectedTip
int64
expectedTip
int64
expectedFC
int64
expectedFC
int64
isBlobTx
bool
}
}
func
(
tc
*
priceBumpTest
)
run
(
t
*
testing
.
T
)
{
func
(
tc
*
priceBumpTest
)
run
(
t
*
testing
.
T
)
{
prevFC
:=
calcGasFeeCap
(
big
.
NewInt
(
tc
.
prevBasefee
),
big
.
NewInt
(
tc
.
prevGasTip
))
prevFC
:=
calcGasFeeCap
(
big
.
NewInt
(
tc
.
prevBasefee
),
big
.
NewInt
(
tc
.
prevGasTip
))
lgr
:=
testlog
.
Logger
(
t
,
log
.
LvlCrit
)
lgr
:=
testlog
.
Logger
(
t
,
log
.
LvlCrit
)
tip
,
fc
:=
updateFees
(
big
.
NewInt
(
tc
.
prevGasTip
),
prevFC
,
big
.
NewInt
(
tc
.
newGasTip
),
big
.
NewInt
(
tc
.
newBasefee
),
lgr
)
tip
,
fc
:=
updateFees
(
big
.
NewInt
(
tc
.
prevGasTip
),
prevFC
,
big
.
NewInt
(
tc
.
newGasTip
),
big
.
NewInt
(
tc
.
newBasefee
),
tc
.
isBlobTx
,
lgr
)
require
.
Equal
(
t
,
tc
.
expectedTip
,
tip
.
Int64
(),
"tip must be as expected"
)
require
.
Equal
(
t
,
tc
.
expectedTip
,
tip
.
Int64
(),
"tip must be as expected"
)
require
.
Equal
(
t
,
tc
.
expectedFC
,
fc
.
Int64
(),
"fee cap must be as expected"
)
require
.
Equal
(
t
,
tc
.
expectedFC
,
fc
.
Int64
(),
"fee cap must be as expected"
)
...
@@ -39,51 +40,111 @@ func TestUpdateFees(t *testing.T) {
...
@@ -39,51 +40,111 @@ func TestUpdateFees(t *testing.T) {
newGasTip
:
90
,
newBasefee
:
900
,
newGasTip
:
90
,
newBasefee
:
900
,
expectedTip
:
110
,
expectedFC
:
2310
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
90
,
newBasefee
:
900
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
1000
,
expectedTip
:
110
,
expectedFC
:
2310
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
1000
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
100
,
newBasefee
:
1001
,
newGasTip
:
100
,
newBasefee
:
1001
,
expectedTip
:
110
,
expectedFC
:
2310
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
100
,
newBasefee
:
1001
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
900
,
newGasTip
:
101
,
newBasefee
:
900
,
expectedTip
:
110
,
expectedFC
:
2310
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
900
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
90
,
newBasefee
:
1010
,
newGasTip
:
90
,
newBasefee
:
1010
,
expectedTip
:
110
,
expectedFC
:
2310
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
90
,
newBasefee
:
1010
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
2000
,
newGasTip
:
101
,
newBasefee
:
2000
,
expectedTip
:
110
,
expectedFC
:
4110
,
expectedTip
:
110
,
expectedFC
:
4110
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
3000
,
expectedTip
:
200
,
expectedFC
:
6200
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
900
,
newGasTip
:
120
,
newBasefee
:
900
,
expectedTip
:
120
,
expectedFC
:
2310
,
expectedTip
:
120
,
expectedFC
:
2310
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
900
,
expectedTip
:
220
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
1100
,
newGasTip
:
120
,
newBasefee
:
1100
,
expectedTip
:
120
,
expectedFC
:
2320
,
expectedTip
:
120
,
expectedFC
:
2320
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
2000
,
expectedTip
:
220
,
expectedFC
:
4220
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
1140
,
newGasTip
:
120
,
newBasefee
:
1140
,
expectedTip
:
120
,
expectedFC
:
2400
,
expectedTip
:
120
,
expectedFC
:
2400
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
2040
,
expectedTip
:
220
,
expectedFC
:
4300
,
isBlobTx
:
true
,
},
{
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
1200
,
newGasTip
:
120
,
newBasefee
:
1200
,
expectedTip
:
120
,
expectedFC
:
2520
,
expectedTip
:
120
,
expectedFC
:
2520
,
},
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
2100
,
expectedTip
:
220
,
expectedFC
:
4420
,
isBlobTx
:
true
,
},
}
}
for
i
,
test
:=
range
tests
{
for
i
,
test
:=
range
tests
{
i
:=
i
i
:=
i
...
...
op-service/txmgr/queue_test.go
View file @
771988b6
...
@@ -193,7 +193,7 @@ func TestQueue_Send(t *testing.T) {
...
@@ -193,7 +193,7 @@ func TestQueue_Send(t *testing.T) {
return
core
.
ErrNonceTooLow
return
core
.
ErrNonceTooLow
}
}
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
return
nil
return
nil
}
}
backend
.
setTxSender
(
sendTx
)
backend
.
setTxSender
(
sendTx
)
...
...
op-service/txmgr/txmgr.go
View file @
771988b6
...
@@ -12,10 +12,14 @@ import (
...
@@ -12,10 +12,14 @@ import (
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/retry"
"github.com/ethereum-optimism/optimism/op-service/retry"
...
@@ -23,15 +27,24 @@ import (
...
@@ -23,15 +27,24 @@ import (
)
)
const
(
const
(
//
Geth requires a minimum fee bump of 10% fo
r tx resubmission
//
geth requires a minimum fee bump of 10% for regula
r tx resubmission
priceBump
int64
=
10
priceBump
int64
=
10
// geth requires a minimum fee bump of 100% for blob tx resubmission
blobPriceBump
int64
=
100
)
)
// new = old * (100 + priceBump) / 100
var
(
var
(
priceBumpPercent
=
big
.
NewInt
(
100
+
priceBump
)
priceBumpPercent
=
big
.
NewInt
(
100
+
priceBump
)
oneHundred
=
big
.
NewInt
(
100
)
blobPriceBumpPercent
=
big
.
NewInt
(
100
+
blobPriceBump
)
ninetyNine
=
big
.
NewInt
(
99
)
// geth enforces a 1 gwei minimum for blob tx fee
minBlobTxFee
=
big
.
NewInt
(
params
.
GWei
)
oneHundred
=
big
.
NewInt
(
100
)
ninetyNine
=
big
.
NewInt
(
99
)
two
=
big
.
NewInt
(
2
)
ErrBlobFeeLimit
=
errors
.
New
(
"blob fee limit reached"
)
)
)
// TxManager is an interface that allows callers to reliably publish txs,
// TxManager is an interface that allows callers to reliably publish txs,
...
@@ -149,14 +162,21 @@ func (m *SimpleTxManager) txLogger(tx *types.Transaction, logGas bool) log.Logge
...
@@ -149,14 +162,21 @@ func (m *SimpleTxManager) txLogger(tx *types.Transaction, logGas bool) log.Logge
if
logGas
{
if
logGas
{
fields
=
append
(
fields
,
"gasTipCap"
,
tx
.
GasTipCap
(),
"gasFeeCap"
,
tx
.
GasFeeCap
(),
"gasLimit"
,
tx
.
Gas
())
fields
=
append
(
fields
,
"gasTipCap"
,
tx
.
GasTipCap
(),
"gasFeeCap"
,
tx
.
GasFeeCap
(),
"gasLimit"
,
tx
.
Gas
())
}
}
if
len
(
tx
.
BlobHashes
())
!=
0
{
// log the number of blobs a tx has only if it's a blob tx
fields
=
append
(
fields
,
"blobs"
,
len
(
tx
.
BlobHashes
()))
}
return
m
.
l
.
New
(
fields
...
)
return
m
.
l
.
New
(
fields
...
)
}
}
// TxCandidate is a transaction candidate that can be submitted to ask the
// TxCandidate is a transaction candidate that can be submitted to ask the
// [TxManager] to construct a transaction with gas price bounds.
// [TxManager] to construct a transaction with gas price bounds.
type
TxCandidate
struct
{
type
TxCandidate
struct
{
// TxData is the transaction data to be used in the constructed tx.
// TxData is the transaction
call
data to be used in the constructed tx.
TxData
[]
byte
TxData
[]
byte
// Blobs to send along in the tx (optional). If len(Blobs) > 0 then a blob tx
// will be sent instead of a DynamicFeeTx.
Blobs
[]
*
eth
.
Blob
// To is the recipient of the constructed tx. Nil means contract creation.
// To is the recipient of the constructed tx. Nil means contract creation.
To
*
common
.
Address
To
*
common
.
Address
// GasLimit is the gas limit to be used in the constructed tx.
// GasLimit is the gas limit to be used in the constructed tx.
...
@@ -212,44 +232,96 @@ func (m *SimpleTxManager) send(ctx context.Context, candidate TxCandidate) (*typ
...
@@ -212,44 +232,96 @@ 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
)
{
gasTipCap
,
basefee
,
err
:=
m
.
suggestGasPriceCaps
(
ctx
)
gasTipCap
,
basefee
,
blobBasefee
,
err
:=
m
.
suggestGasPriceCaps
(
ctx
)
if
err
!=
nil
{
if
err
!=
nil
{
m
.
metr
.
RPCError
()
m
.
metr
.
RPCError
()
return
nil
,
fmt
.
Errorf
(
"failed to get gas price info: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"failed to get gas price info: %w"
,
err
)
}
}
gasFeeCap
:=
calcGasFeeCap
(
basefee
,
gasTipCap
)
gasFeeCap
:=
calcGasFeeCap
(
basefee
,
gasTipCap
)
rawTx
:=
&
types
.
DynamicFeeTx
{
gasLimit
:=
candidate
.
GasLimit
ChainID
:
m
.
chainID
,
To
:
candidate
.
To
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
Data
:
candidate
.
TxData
,
Value
:
candidate
.
Value
,
}
m
.
l
.
Info
(
"Creating tx"
,
"to"
,
rawTx
.
To
,
"from"
,
m
.
cfg
.
From
)
// If the gas limit is set, we can use that as the gas
// If the gas limit is set, we can use that as the gas
if
candidate
.
GasLimit
!=
0
{
if
gasLimit
==
0
{
rawTx
.
Gas
=
candidate
.
GasLimit
}
else
{
// Calculate the intrinsic gas for the transaction
// Calculate the intrinsic gas for the transaction
gas
,
err
:=
m
.
backend
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
gas
,
err
:=
m
.
backend
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
From
:
m
.
cfg
.
From
,
From
:
m
.
cfg
.
From
,
To
:
candidate
.
To
,
To
:
candidate
.
To
,
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
Data
:
rawTx
.
Data
,
Data
:
candidate
.
Tx
Data
,
Value
:
rawTx
.
Value
,
Value
:
candidate
.
Value
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to estimate gas: %w"
,
err
)
return
nil
,
fmt
.
Errorf
(
"failed to estimate gas: %w"
,
err
)
}
}
rawTx
.
Gas
=
gas
gasLimit
=
gas
}
var
sidecar
*
types
.
BlobTxSidecar
var
blobHashes
[]
common
.
Hash
if
len
(
candidate
.
Blobs
)
>
0
{
if
candidate
.
To
==
nil
{
return
nil
,
errors
.
New
(
"blob txs cannot deploy contracts"
)
}
if
sidecar
,
blobHashes
,
err
=
makeSidecar
(
candidate
.
Blobs
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to make sidecar: %w"
,
err
)
}
}
}
return
m
.
signWithNextNonce
(
ctx
,
rawTx
)
var
txMessage
types
.
TxData
if
sidecar
!=
nil
{
if
blobBasefee
==
nil
{
return
nil
,
fmt
.
Errorf
(
"expected non-nil blobBasefee"
)
}
blobFeeCap
:=
calcBlobFeeCap
(
blobBasefee
)
message
:=
&
types
.
BlobTx
{
To
:
*
candidate
.
To
,
Data
:
candidate
.
TxData
,
Gas
:
gasLimit
,
BlobHashes
:
blobHashes
,
Sidecar
:
sidecar
,
}
if
err
:=
finishBlobTx
(
message
,
m
.
chainID
,
gasTipCap
,
gasFeeCap
,
blobFeeCap
,
candidate
.
Value
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to create blob transaction: %w"
,
err
)
}
txMessage
=
message
}
else
{
txMessage
=
&
types
.
DynamicFeeTx
{
ChainID
:
m
.
chainID
,
To
:
candidate
.
To
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
Value
:
candidate
.
Value
,
Data
:
candidate
.
TxData
,
Gas
:
gasLimit
,
}
}
return
m
.
signWithNextNonce
(
ctx
,
txMessage
)
// signer sets the nonce field of the tx
}
// makeSidecar builds & returns the BlobTxSidecar and corresponding blob hashes from the raw blob
// data.
func
makeSidecar
(
blobs
[]
*
eth
.
Blob
)
(
*
types
.
BlobTxSidecar
,
[]
common
.
Hash
,
error
)
{
sidecar
:=
&
types
.
BlobTxSidecar
{}
blobHashes
:=
[]
common
.
Hash
{}
for
i
,
blob
:=
range
blobs
{
rawBlob
:=
*
blob
.
KZGBlob
()
sidecar
.
Blobs
=
append
(
sidecar
.
Blobs
,
rawBlob
)
commitment
,
err
:=
kzg4844
.
BlobToCommitment
(
rawBlob
)
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"cannot compute KZG commitment of blob %d in tx candidate: %w"
,
i
,
err
)
}
sidecar
.
Commitments
=
append
(
sidecar
.
Commitments
,
commitment
)
proof
,
err
:=
kzg4844
.
ComputeBlobProof
(
rawBlob
,
commitment
)
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"cannot compute KZG proof for fast commitment verification of blob %d in tx candidate: %w"
,
i
,
err
)
}
sidecar
.
Proofs
=
append
(
sidecar
.
Proofs
,
proof
)
blobHashes
=
append
(
blobHashes
,
eth
.
KZGToVersionedHash
(
commitment
))
}
return
sidecar
,
blobHashes
,
nil
}
}
// signWithNextNonce returns a signed transaction with the next available nonce.
// signWithNextNonce returns a signed transaction with the next available nonce.
...
@@ -257,7 +329,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
...
@@ -257,7 +329,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
// then subsequent calls simply increment this number. If the transaction manager
// then subsequent calls simply increment this number. If the transaction manager
// is reset, it will query the eth_getTransactionCount nonce again. If signing
// is reset, it will query the eth_getTransactionCount nonce again. If signing
// fails, the nonce is not incremented.
// fails, the nonce is not incremented.
func
(
m
*
SimpleTxManager
)
signWithNextNonce
(
ctx
context
.
Context
,
rawTx
*
types
.
DynamicFeeTx
)
(
*
types
.
Transaction
,
error
)
{
func
(
m
*
SimpleTxManager
)
signWithNextNonce
(
ctx
context
.
Context
,
txMessage
types
.
TxData
)
(
*
types
.
Transaction
,
error
)
{
m
.
nonceLock
.
Lock
()
m
.
nonceLock
.
Lock
()
defer
m
.
nonceLock
.
Unlock
()
defer
m
.
nonceLock
.
Unlock
()
...
@@ -275,10 +347,17 @@ func (m *SimpleTxManager) signWithNextNonce(ctx context.Context, rawTx *types.Dy
...
@@ -275,10 +347,17 @@ func (m *SimpleTxManager) signWithNextNonce(ctx context.Context, rawTx *types.Dy
*
m
.
nonce
++
*
m
.
nonce
++
}
}
rawTx
.
Nonce
=
*
m
.
nonce
switch
x
:=
txMessage
.
(
type
)
{
case
*
types
.
DynamicFeeTx
:
x
.
Nonce
=
*
m
.
nonce
case
*
types
.
BlobTx
:
x
.
Nonce
=
*
m
.
nonce
default
:
return
nil
,
fmt
.
Errorf
(
"unrecognized tx type: %T"
,
x
)
}
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
defer
cancel
()
defer
cancel
()
tx
,
err
:=
m
.
cfg
.
Signer
(
ctx
,
m
.
cfg
.
From
,
types
.
NewTx
(
rawTx
))
tx
,
err
:=
m
.
cfg
.
Signer
(
ctx
,
m
.
cfg
.
From
,
types
.
NewTx
(
txMessage
))
if
err
!=
nil
{
if
err
!=
nil
{
// decrement the nonce, so we can retry signing with the same nonce next time
// decrement the nonce, so we can retry signing with the same nonce next time
// signWithNextNonce is called
// signWithNextNonce is called
...
@@ -512,42 +591,31 @@ func (m *SimpleTxManager) queryReceipt(ctx context.Context, txHash common.Hash,
...
@@ -512,42 +591,31 @@ func (m *SimpleTxManager) queryReceipt(ctx context.Context, txHash common.Hash,
return
nil
return
nil
}
}
// increaseGasPrice takes the previous transaction, clones it, and returns it with fee values that
// increaseGasPrice returns a new transaction that is equivalent to the input transaction but with
// are at least `priceBump` percent higher than the previous ones to satisfy Geth's replacement
// higher fees that should satisfy geth's tx replacement rules. It also computes an updated gas
// rules, and no lower than the values returned by the fee suggestion algorithm to ensure it
// limit estimate. To avoid runaway price increases, fees are capped at a `feeLimitMultiplier`
// doesn't linger in the mempool. Finally to avoid runaway price increases, fees are capped at a
// multiple of the suggested values.
// `feeLimitMultiplier` multiple of the suggested values.
func
(
m
*
SimpleTxManager
)
increaseGasPrice
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
(
*
types
.
Transaction
,
error
)
{
func
(
m
*
SimpleTxManager
)
increaseGasPrice
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
(
*
types
.
Transaction
,
error
)
{
m
.
txLogger
(
tx
,
true
)
.
Info
(
"bumping gas price for transaction"
)
m
.
txLogger
(
tx
,
true
)
.
Info
(
"bumping gas price for transaction"
)
tip
,
basefee
,
err
:=
m
.
suggestGasPriceCaps
(
ctx
)
tip
,
basefee
,
blobBasefee
,
err
:=
m
.
suggestGasPriceCaps
(
ctx
)
if
err
!=
nil
{
if
err
!=
nil
{
m
.
txLogger
(
tx
,
false
)
.
Warn
(
"failed to get suggested gas tip and basefee"
,
"err"
,
err
)
m
.
txLogger
(
tx
,
false
)
.
Warn
(
"failed to get suggested gas tip and basefee"
,
"err"
,
err
)
return
nil
,
err
return
nil
,
err
}
}
bumpedTip
,
bumpedFee
:=
updateFees
(
tx
.
GasTipCap
(),
tx
.
GasFeeCap
(),
tip
,
basefee
,
m
.
l
)
bumpedTip
,
bumpedFee
:=
updateFees
(
tx
.
GasTipCap
(),
tx
.
GasFeeCap
(),
tip
,
basefee
,
tx
.
Type
()
==
types
.
BlobTxType
,
m
.
l
)
if
err
:=
m
.
checkLimits
(
tip
,
basefee
,
bumpedTip
,
bumpedFee
);
err
!=
nil
{
if
err
:=
m
.
checkLimits
(
tip
,
basefee
,
bumpedTip
,
bumpedFee
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
rawTx
:=
&
types
.
DynamicFeeTx
{
ChainID
:
tx
.
ChainId
(),
Nonce
:
tx
.
Nonce
(),
GasTipCap
:
bumpedTip
,
GasFeeCap
:
bumpedFee
,
To
:
tx
.
To
(),
Value
:
tx
.
Value
(),
Data
:
tx
.
Data
(),
AccessList
:
tx
.
AccessList
(),
}
// Re-estimate gaslimit in case things have changed or a previous gaslimit estimate was wrong
// Re-estimate gaslimit in case things have changed or a previous gaslimit estimate was wrong
gas
,
err
:=
m
.
backend
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
gas
,
err
:=
m
.
backend
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
From
:
m
.
cfg
.
From
,
From
:
m
.
cfg
.
From
,
To
:
rawTx
.
To
,
To
:
tx
.
To
()
,
GasTipCap
:
bumpedTip
,
GasTipCap
:
bumpedTip
,
GasFeeCap
:
bumpedFee
,
GasFeeCap
:
bumpedFee
,
Data
:
rawTx
.
Data
,
Data
:
tx
.
Data
(),
Value
:
tx
.
Value
(),
})
})
if
err
!=
nil
{
if
err
!=
nil
{
// If this is a transaction resubmission, we sometimes see this outcome because the
// If this is a transaction resubmission, we sometimes see this outcome because the
...
@@ -562,38 +630,75 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa
...
@@ -562,38 +630,75 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa
m
.
l
.
Info
(
"re-estimated gas differs"
,
"tx"
,
tx
.
Hash
(),
"oldgas"
,
tx
.
Gas
(),
"newgas"
,
gas
,
m
.
l
.
Info
(
"re-estimated gas differs"
,
"tx"
,
tx
.
Hash
(),
"oldgas"
,
tx
.
Gas
(),
"newgas"
,
gas
,
"gasFeeCap"
,
bumpedFee
,
"gasTipCap"
,
bumpedTip
)
"gasFeeCap"
,
bumpedFee
,
"gasTipCap"
,
bumpedTip
)
}
}
rawTx
.
Gas
=
gas
var
newTx
*
types
.
Transaction
if
tx
.
Type
()
==
types
.
BlobTxType
{
// Blob transactions have an additional blob gas price we must specify, so we must make sure it is
// getting bumped appropriately.
bumpedBlobFee
:=
calcThresholdValue
(
tx
.
BlobGasFeeCap
(),
true
)
if
bumpedBlobFee
.
Cmp
(
blobBasefee
)
<
0
{
bumpedBlobFee
=
blobBasefee
}
if
err
:=
m
.
checkBlobFeeLimits
(
blobBasefee
,
bumpedBlobFee
);
err
!=
nil
{
return
nil
,
err
}
message
:=
&
types
.
BlobTx
{
Nonce
:
tx
.
Nonce
(),
To
:
*
tx
.
To
(),
Data
:
tx
.
Data
(),
Gas
:
gas
,
BlobHashes
:
tx
.
BlobHashes
(),
Sidecar
:
tx
.
BlobTxSidecar
(),
}
if
err
:=
finishBlobTx
(
message
,
tx
.
ChainId
(),
bumpedTip
,
bumpedFee
,
bumpedBlobFee
,
tx
.
Value
());
err
!=
nil
{
return
nil
,
err
}
newTx
=
types
.
NewTx
(
message
)
}
else
{
newTx
=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
ChainID
:
tx
.
ChainId
(),
Nonce
:
tx
.
Nonce
(),
To
:
tx
.
To
(),
GasTipCap
:
bumpedTip
,
GasFeeCap
:
bumpedFee
,
Value
:
tx
.
Value
(),
Data
:
tx
.
Data
(),
Gas
:
gas
,
})
}
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
defer
cancel
()
defer
cancel
()
newTx
,
err
:=
m
.
cfg
.
Signer
(
ctx
,
m
.
cfg
.
From
,
types
.
NewTx
(
rawTx
)
)
signedTx
,
err
:=
m
.
cfg
.
Signer
(
ctx
,
m
.
cfg
.
From
,
newTx
)
if
err
!=
nil
{
if
err
!=
nil
{
m
.
l
.
Warn
(
"failed to sign new transaction"
,
"err"
,
err
,
"tx"
,
tx
.
Hash
())
m
.
l
.
Warn
(
"failed to sign new transaction"
,
"err"
,
err
,
"tx"
,
tx
.
Hash
())
return
tx
,
nil
return
tx
,
nil
}
}
return
new
Tx
,
nil
return
signed
Tx
,
nil
}
}
// suggestGasPriceCaps suggests what the new tip & new basefee should be based on the current L1 conditions
// suggestGasPriceCaps suggests what the new tip, basefee, and blobfee should be based on the
func
(
m
*
SimpleTxManager
)
suggestGasPriceCaps
(
ctx
context
.
Context
)
(
*
big
.
Int
,
*
big
.
Int
,
error
)
{
// current L1 conditions. blobfee will be nil if 4844 is not yet active.
func
(
m
*
SimpleTxManager
)
suggestGasPriceCaps
(
ctx
context
.
Context
)
(
*
big
.
Int
,
*
big
.
Int
,
*
big
.
Int
,
error
)
{
cCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
cCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
defer
cancel
()
defer
cancel
()
tip
,
err
:=
m
.
backend
.
SuggestGasTipCap
(
cCtx
)
tip
,
err
:=
m
.
backend
.
SuggestGasTipCap
(
cCtx
)
if
err
!=
nil
{
if
err
!=
nil
{
m
.
metr
.
RPCError
()
m
.
metr
.
RPCError
()
return
nil
,
nil
,
fmt
.
Errorf
(
"failed to fetch the suggested gas tip cap: %w"
,
err
)
return
nil
,
nil
,
nil
,
fmt
.
Errorf
(
"failed to fetch the suggested gas tip cap: %w"
,
err
)
}
else
if
tip
==
nil
{
}
else
if
tip
==
nil
{
return
nil
,
nil
,
errors
.
New
(
"the suggested tip was nil"
)
return
nil
,
nil
,
nil
,
errors
.
New
(
"the suggested tip was nil"
)
}
}
cCtx
,
cancel
=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
cCtx
,
cancel
=
context
.
WithTimeout
(
ctx
,
m
.
cfg
.
NetworkTimeout
)
defer
cancel
()
defer
cancel
()
head
,
err
:=
m
.
backend
.
HeaderByNumber
(
cCtx
,
nil
)
head
,
err
:=
m
.
backend
.
HeaderByNumber
(
cCtx
,
nil
)
if
err
!=
nil
{
if
err
!=
nil
{
m
.
metr
.
RPCError
()
m
.
metr
.
RPCError
()
return
nil
,
nil
,
fmt
.
Errorf
(
"failed to fetch the suggested basefee: %w"
,
err
)
return
nil
,
nil
,
nil
,
fmt
.
Errorf
(
"failed to fetch the suggested basefee: %w"
,
err
)
}
else
if
head
.
BaseFee
==
nil
{
}
else
if
head
.
BaseFee
==
nil
{
return
nil
,
nil
,
errors
.
New
(
"txmgr does not support pre-london blocks that do not have a basefee"
)
return
nil
,
nil
,
nil
,
errors
.
New
(
"txmgr does not support pre-london blocks that do not have a basefee"
)
}
}
basefee
:=
head
.
BaseFee
basefee
:=
head
.
BaseFee
m
.
metr
.
RecordBasefee
(
basefee
)
m
.
metr
.
RecordBasefee
(
basefee
)
m
.
metr
.
RecordTipCap
(
tip
)
m
.
metr
.
RecordTipCap
(
tip
)
...
@@ -608,7 +713,11 @@ func (m *SimpleTxManager) suggestGasPriceCaps(ctx context.Context) (*big.Int, *b
...
@@ -608,7 +713,11 @@ func (m *SimpleTxManager) suggestGasPriceCaps(ctx context.Context) (*big.Int, *b
basefee
=
new
(
big
.
Int
)
.
Set
(
m
.
cfg
.
MinBasefee
)
basefee
=
new
(
big
.
Int
)
.
Set
(
m
.
cfg
.
MinBasefee
)
}
}
return
tip
,
basefee
,
nil
var
blobFee
*
big
.
Int
if
head
.
ExcessBlobGas
!=
nil
{
blobFee
=
eip4844
.
CalcBlobFee
(
*
head
.
ExcessBlobGas
)
}
return
tip
,
basefee
,
blobFee
,
nil
}
}
func
(
m
*
SimpleTxManager
)
checkLimits
(
tip
,
basefee
,
bumpedTip
,
bumpedFee
*
big
.
Int
)
error
{
func
(
m
*
SimpleTxManager
)
checkLimits
(
tip
,
basefee
,
bumpedTip
,
bumpedFee
*
big
.
Int
)
error
{
...
@@ -630,28 +739,46 @@ func (m *SimpleTxManager) checkLimits(tip, basefee, bumpedTip, bumpedFee *big.In
...
@@ -630,28 +739,46 @@ func (m *SimpleTxManager) checkLimits(tip, basefee, bumpedTip, bumpedFee *big.In
return
nil
return
nil
}
}
// calcThresholdValue returns ceil(x * priceBumpPercent / 100)
func
(
m
*
SimpleTxManager
)
checkBlobFeeLimits
(
blobBasefee
,
bumpedBlobFee
*
big
.
Int
)
error
{
// If below threshold, don't apply multiplier limit. Note we use same threshold parameter here
// used for non-blob fee limiting.
if
thr
:=
m
.
cfg
.
FeeLimitThreshold
;
thr
!=
nil
&&
thr
.
Cmp
(
bumpedBlobFee
)
==
1
{
return
nil
}
maxBlobFee
:=
new
(
big
.
Int
)
.
Mul
(
calcBlobFeeCap
(
blobBasefee
),
big
.
NewInt
(
int64
(
m
.
cfg
.
FeeLimitMultiplier
)))
if
bumpedBlobFee
.
Cmp
(
maxBlobFee
)
>
0
{
return
fmt
.
Errorf
(
"bumped blob fee %v is over %dx multiple of the suggested value: %w"
,
bumpedBlobFee
,
m
.
cfg
.
FeeLimitMultiplier
,
ErrBlobFeeLimit
)
}
return
nil
}
// calcThresholdValue returns ceil(x * priceBumpPercent / 100) for non-blob txs, or
// ceil(x * blobPriceBumpPercent / 100) for blob txs.
// It guarantees that x is increased by at least 1
// It guarantees that x is increased by at least 1
func
calcThresholdValue
(
x
*
big
.
Int
)
*
big
.
Int
{
func
calcThresholdValue
(
x
*
big
.
Int
,
isBlobTx
bool
)
*
big
.
Int
{
threshold
:=
new
(
big
.
Int
)
.
Mul
(
priceBumpPercent
,
x
)
threshold
:=
new
(
big
.
Int
)
threshold
.
Add
(
threshold
,
ninetyNine
)
if
isBlobTx
{
threshold
.
Div
(
threshold
,
oneHundred
)
threshold
.
Set
(
blobPriceBumpPercent
)
return
threshold
}
else
{
threshold
.
Set
(
priceBumpPercent
)
}
return
threshold
.
Mul
(
threshold
,
x
)
.
Add
(
threshold
,
ninetyNine
)
.
Div
(
threshold
,
oneHundred
)
}
}
// updateFees takes an old transaction's tip & fee cap plus a new tip & basefee, and returns
// updateFees takes an old transaction's tip & fee cap plus a new tip & basefee, and returns
// a suggested tip and fee cap such that:
// a suggested tip and fee cap such that:
//
//
// (a) each satisfies geth's required tx-replacement fee bumps
(we use a 10% increase)
, and
// (a) each satisfies geth's required tx-replacement fee bumps, and
// (b) gasTipCap is no less than new tip, and
// (b) gasTipCap is no less than new tip, and
// (c) gasFeeCap is no less than calcGasFee(newBaseFee, newTip)
// (c) gasFeeCap is no less than calcGasFee(newBaseFee, newTip)
func
updateFees
(
oldTip
,
oldFeeCap
,
newTip
,
newBaseFee
*
big
.
Int
,
lgr
log
.
Logger
)
(
*
big
.
Int
,
*
big
.
Int
)
{
func
updateFees
(
oldTip
,
oldFeeCap
,
newTip
,
newBaseFee
*
big
.
Int
,
isBlobTx
bool
,
lgr
log
.
Logger
)
(
*
big
.
Int
,
*
big
.
Int
)
{
newFeeCap
:=
calcGasFeeCap
(
newBaseFee
,
newTip
)
newFeeCap
:=
calcGasFeeCap
(
newBaseFee
,
newTip
)
lgr
=
lgr
.
New
(
"old_gasTipCap"
,
oldTip
,
"old_gasFeeCap"
,
oldFeeCap
,
lgr
=
lgr
.
New
(
"old_gasTipCap"
,
oldTip
,
"old_gasFeeCap"
,
oldFeeCap
,
"new_gasTipCap"
,
newTip
,
"new_gasFeeCap"
,
newFeeCap
,
"new_gasTipCap"
,
newTip
,
"new_gasFeeCap"
,
newFeeCap
,
"new_basefee"
,
newBaseFee
)
"new_basefee"
,
newBaseFee
)
thresholdTip
:=
calcThresholdValue
(
oldTip
,
isBlobTx
)
thresholdTip
:=
calcThresholdValue
(
oldTip
)
thresholdFeeCap
:=
calcThresholdValue
(
oldFeeCap
,
isBlobTx
)
thresholdFeeCap
:=
calcThresholdValue
(
oldFeeCap
)
if
newTip
.
Cmp
(
thresholdTip
)
>=
0
&&
newFeeCap
.
Cmp
(
thresholdFeeCap
)
>=
0
{
if
newTip
.
Cmp
(
thresholdTip
)
>=
0
&&
newFeeCap
.
Cmp
(
thresholdFeeCap
)
>=
0
{
lgr
.
Debug
(
"Using new tip and feecap"
)
lgr
.
Debug
(
"Using new tip and feecap"
)
return
newTip
,
newFeeCap
return
newTip
,
newFeeCap
...
@@ -680,10 +807,20 @@ func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, lgr log.Logger)
...
@@ -680,10 +807,20 @@ func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, lgr log.Logger)
func
calcGasFeeCap
(
baseFee
,
gasTipCap
*
big
.
Int
)
*
big
.
Int
{
func
calcGasFeeCap
(
baseFee
,
gasTipCap
*
big
.
Int
)
*
big
.
Int
{
return
new
(
big
.
Int
)
.
Add
(
return
new
(
big
.
Int
)
.
Add
(
gasTipCap
,
gasTipCap
,
new
(
big
.
Int
)
.
Mul
(
baseFee
,
big
.
NewInt
(
2
)
),
new
(
big
.
Int
)
.
Mul
(
baseFee
,
two
),
)
)
}
}
// calcBlobFeeCap computes a suggested blob fee cap that is twice the current header's blob basefee
// value, with a minimum value of minBlobTxFee.
func
calcBlobFeeCap
(
blobBasefee
*
big
.
Int
)
*
big
.
Int
{
cap
:=
new
(
big
.
Int
)
.
Mul
(
blobBasefee
,
two
)
if
cap
.
Cmp
(
minBlobTxFee
)
<
0
{
cap
.
Set
(
minBlobTxFee
)
}
return
cap
}
// errStringMatch returns true if err.Error() is a substring in target.Error() or if both are nil.
// errStringMatch returns true if err.Error() is a substring in target.Error() or if both are nil.
// It can accept nil errors without issue.
// It can accept nil errors without issue.
func
errStringMatch
(
err
,
target
error
)
bool
{
func
errStringMatch
(
err
,
target
error
)
bool
{
...
@@ -694,3 +831,24 @@ func errStringMatch(err, target error) bool {
...
@@ -694,3 +831,24 @@ func errStringMatch(err, target error) bool {
}
}
return
strings
.
Contains
(
err
.
Error
(),
target
.
Error
())
return
strings
.
Contains
(
err
.
Error
(),
target
.
Error
())
}
}
// finishBlobTx finishes creating a blob tx message by safely converting bigints to uint256
func
finishBlobTx
(
message
*
types
.
BlobTx
,
chainID
,
tip
,
fee
,
blobFee
,
value
*
big
.
Int
)
error
{
var
o
bool
if
message
.
ChainID
,
o
=
uint256
.
FromBig
(
chainID
);
o
{
return
fmt
.
Errorf
(
"ChainID overflow"
)
}
if
message
.
GasTipCap
,
o
=
uint256
.
FromBig
(
tip
);
o
{
return
fmt
.
Errorf
(
"GasTipCap overflow"
)
}
if
message
.
GasFeeCap
,
o
=
uint256
.
FromBig
(
fee
);
o
{
return
fmt
.
Errorf
(
"GasFeeCap overflow"
)
}
if
message
.
BlobFeeCap
,
o
=
uint256
.
FromBig
(
blobFee
);
o
{
return
fmt
.
Errorf
(
"BlobFeeCap overflow"
)
}
if
message
.
Value
,
o
=
uint256
.
FromBig
(
value
);
o
{
return
fmt
.
Errorf
(
"Value overflow"
)
}
return
nil
}
op-service/txmgr/txmgr_test.go
View file @
771988b6
...
@@ -9,17 +9,30 @@ import (
...
@@ -9,17 +9,30 @@ import (
"testing"
"testing"
"time"
"time"
"github.com/holiman/uint256"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
)
const
(
startingNonce
=
1
// we pick something other than 0 so we can confirm nonces are getting set properly
)
var
(
blobData1
=
eth
.
Data
(
"this is a blob!"
)
blobData2
=
eth
.
Data
(
"amazing, the txmgr can handle more than one blob in a tx!!"
)
)
)
type
sendTransactionFunc
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
type
sendTransactionFunc
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
...
@@ -75,6 +88,21 @@ func (h testHarness) createTxCandidate() TxCandidate {
...
@@ -75,6 +88,21 @@ func (h testHarness) createTxCandidate() TxCandidate {
}
}
}
}
// createBlobTxCandidate creates a mock [TxCandidate] that results in a blob tx
func
(
h
testHarness
)
createBlobTxCandidate
()
TxCandidate
{
inbox
:=
common
.
HexToAddress
(
"0x42000000000000000000000000000000000000ff"
)
var
b1
,
b2
eth
.
Blob
_
=
b1
.
FromData
(
blobData1
)
_
=
b2
.
FromData
(
blobData2
)
return
TxCandidate
{
To
:
&
inbox
,
TxData
:
[]
byte
{
0x00
,
0x01
,
0x02
,
0x03
},
GasLimit
:
uint64
(
1337
),
Blobs
:
[]
*
eth
.
Blob
{
&
b1
,
&
b2
},
}
}
func
configWithNumConfs
(
numConfirmations
uint64
)
Config
{
func
configWithNumConfs
(
numConfirmations
uint64
)
Config
{
return
Config
{
return
Config
{
ResubmissionTimeout
:
time
.
Second
,
ResubmissionTimeout
:
time
.
Second
,
...
@@ -95,6 +123,7 @@ type gasPricer struct {
...
@@ -95,6 +123,7 @@ type gasPricer struct {
mineAtEpoch
int64
mineAtEpoch
int64
baseGasTipFee
*
big
.
Int
baseGasTipFee
*
big
.
Int
baseBaseFee
*
big
.
Int
baseBaseFee
*
big
.
Int
excessBlobGas
uint64
err
error
err
error
mu
sync
.
Mutex
mu
sync
.
Mutex
}
}
...
@@ -104,24 +133,37 @@ func newGasPricer(mineAtEpoch int64) *gasPricer {
...
@@ -104,24 +133,37 @@ func newGasPricer(mineAtEpoch int64) *gasPricer {
mineAtEpoch
:
mineAtEpoch
,
mineAtEpoch
:
mineAtEpoch
,
baseGasTipFee
:
big
.
NewInt
(
5
),
baseGasTipFee
:
big
.
NewInt
(
5
),
baseBaseFee
:
big
.
NewInt
(
7
),
baseBaseFee
:
big
.
NewInt
(
7
),
// Simulate 100 excess blobs, which results in a blobBaseFee of 50 wei. This default means
// blob txs will be subject to the geth minimum blobgas fee of 1 gwei.
excessBlobGas
:
100
*
(
params
.
BlobTxBlobGasPerBlob
),
}
}
}
}
func
(
g
*
gasPricer
)
expGasFeeCap
()
*
big
.
Int
{
func
(
g
*
gasPricer
)
expGasFeeCap
()
*
big
.
Int
{
_
,
gasFeeCap
:=
g
.
feesForEpoch
(
g
.
mineAtEpoch
)
_
,
gasFeeCap
,
_
:=
g
.
feesForEpoch
(
g
.
mineAtEpoch
)
return
gasFeeCap
return
gasFeeCap
}
}
func
(
g
*
gasPricer
)
expBlobFeeCap
()
*
big
.
Int
{
_
,
_
,
excessBlobGas
:=
g
.
feesForEpoch
(
g
.
mineAtEpoch
)
return
eip4844
.
CalcBlobFee
(
excessBlobGas
)
}
func
(
g
*
gasPricer
)
shouldMine
(
gasFeeCap
*
big
.
Int
)
bool
{
func
(
g
*
gasPricer
)
shouldMine
(
gasFeeCap
*
big
.
Int
)
bool
{
return
g
.
expGasFeeCap
()
.
Cmp
(
gasFeeCap
)
=
=
0
return
g
.
expGasFeeCap
()
.
Cmp
(
gasFeeCap
)
<
=
0
}
}
func
(
g
*
gasPricer
)
feesForEpoch
(
epoch
int64
)
(
*
big
.
Int
,
*
big
.
Int
)
{
func
(
g
*
gasPricer
)
shouldMineBlobTx
(
gasFeeCap
,
blobFeeCap
*
big
.
Int
)
bool
{
epochBaseFee
:=
new
(
big
.
Int
)
.
Mul
(
g
.
baseBaseFee
,
big
.
NewInt
(
epoch
))
return
g
.
shouldMine
(
gasFeeCap
)
&&
g
.
expBlobFeeCap
()
.
Cmp
(
blobFeeCap
)
<=
0
epochGasTipCap
:=
new
(
big
.
Int
)
.
Mul
(
g
.
baseGasTipFee
,
big
.
NewInt
(
epoch
))
}
epochGasFeeCap
:=
calcGasFeeCap
(
epochBaseFee
,
epochGasTipCap
)
return
epochGasTipCap
,
epochGasFeeCap
func
(
g
*
gasPricer
)
feesForEpoch
(
epoch
int64
)
(
*
big
.
Int
,
*
big
.
Int
,
uint64
)
{
e
:=
big
.
NewInt
(
epoch
)
epochBaseFee
:=
new
(
big
.
Int
)
.
Mul
(
g
.
baseBaseFee
,
e
)
epochGasTipCap
:=
new
(
big
.
Int
)
.
Mul
(
g
.
baseGasTipFee
,
e
)
epochGasFeeCap
:=
calcGasFeeCap
(
epochBaseFee
,
epochGasTipCap
)
epochExcessBlobGas
:=
g
.
excessBlobGas
*
uint64
(
epoch
)
return
epochGasTipCap
,
epochGasFeeCap
,
epochExcessBlobGas
}
}
func
(
g
*
gasPricer
)
basefee
()
*
big
.
Int
{
func
(
g
*
gasPricer
)
basefee
()
*
big
.
Int
{
...
@@ -130,18 +172,25 @@ func (g *gasPricer) basefee() *big.Int {
...
@@ -130,18 +172,25 @@ func (g *gasPricer) basefee() *big.Int {
return
new
(
big
.
Int
)
.
Mul
(
g
.
baseBaseFee
,
big
.
NewInt
(
g
.
epoch
))
return
new
(
big
.
Int
)
.
Mul
(
g
.
baseBaseFee
,
big
.
NewInt
(
g
.
epoch
))
}
}
func
(
g
*
gasPricer
)
sample
()
(
*
big
.
Int
,
*
big
.
Int
)
{
func
(
g
*
gasPricer
)
excessblobgas
()
uint64
{
g
.
mu
.
Lock
()
defer
g
.
mu
.
Unlock
()
return
g
.
excessBlobGas
*
uint64
(
g
.
epoch
)
}
func
(
g
*
gasPricer
)
sample
()
(
*
big
.
Int
,
*
big
.
Int
,
uint64
)
{
g
.
mu
.
Lock
()
g
.
mu
.
Lock
()
defer
g
.
mu
.
Unlock
()
defer
g
.
mu
.
Unlock
()
g
.
epoch
++
g
.
epoch
++
epochGasTipCap
,
epochGasFeeCap
:=
g
.
feesForEpoch
(
g
.
epoch
)
epochGasTipCap
,
epochGasFeeCap
,
epochExcessBlobGas
:=
g
.
feesForEpoch
(
g
.
epoch
)
return
epochGasTipCap
,
epochGasFeeCap
return
epochGasTipCap
,
epochGasFeeCap
,
epochExcessBlobGas
}
}
type
minedTxInfo
struct
{
type
minedTxInfo
struct
{
gasFeeCap
*
big
.
Int
gasFeeCap
*
big
.
Int
blobFeeCap
*
big
.
Int
blockNumber
uint64
blockNumber
uint64
}
}
...
@@ -176,7 +225,7 @@ func (b *mockBackend) setTxSender(s sendTransactionFunc) {
...
@@ -176,7 +225,7 @@ func (b *mockBackend) setTxSender(s sendTransactionFunc) {
// mine records a (txHash, gasFeeCap) as confirmed. Subsequent calls to
// mine records a (txHash, gasFeeCap) as confirmed. Subsequent calls to
// TransactionReceipt with a matching txHash will result in a non-nil receipt.
// 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.
// If a nil txHash is supplied this has the effect of mining an empty block.
func
(
b
*
mockBackend
)
mine
(
txHash
*
common
.
Hash
,
gasFeeCap
*
big
.
Int
)
{
func
(
b
*
mockBackend
)
mine
(
txHash
*
common
.
Hash
,
gasFeeCap
,
blobFeeCap
*
big
.
Int
)
{
b
.
mu
.
Lock
()
b
.
mu
.
Lock
()
defer
b
.
mu
.
Unlock
()
defer
b
.
mu
.
Unlock
()
...
@@ -184,6 +233,7 @@ func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) {
...
@@ -184,6 +233,7 @@ func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) {
if
txHash
!=
nil
{
if
txHash
!=
nil
{
b
.
minedTxs
[
*
txHash
]
=
minedTxInfo
{
b
.
minedTxs
[
*
txHash
]
=
minedTxInfo
{
gasFeeCap
:
gasFeeCap
,
gasFeeCap
:
gasFeeCap
,
blobFeeCap
:
blobFeeCap
,
blockNumber
:
b
.
blockHeight
,
blockNumber
:
b
.
blockHeight
,
}
}
}
}
...
@@ -210,9 +260,11 @@ func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*typ
...
@@ -210,9 +260,11 @@ func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*typ
if
number
!=
nil
{
if
number
!=
nil
{
num
.
Set
(
number
)
num
.
Set
(
number
)
}
}
bg
:=
b
.
g
.
excessblobgas
()
return
&
types
.
Header
{
return
&
types
.
Header
{
Number
:
num
,
Number
:
num
,
BaseFee
:
b
.
g
.
basefee
(),
BaseFee
:
b
.
g
.
basefee
(),
ExcessBlobGas
:
&
bg
,
},
nil
},
nil
}
}
...
@@ -227,7 +279,7 @@ func (b *mockBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (ui
...
@@ -227,7 +279,7 @@ func (b *mockBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (ui
}
}
func
(
b
*
mockBackend
)
SuggestGasTipCap
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
func
(
b
*
mockBackend
)
SuggestGasTipCap
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
tip
,
_
:=
b
.
g
.
sample
()
tip
,
_
,
_
:=
b
.
g
.
sample
()
return
tip
,
nil
return
tip
,
nil
}
}
...
@@ -239,21 +291,21 @@ func (b *mockBackend) SendTransaction(ctx context.Context, tx *types.Transaction
...
@@ -239,21 +291,21 @@ func (b *mockBackend) SendTransaction(ctx context.Context, tx *types.Transaction
}
}
func
(
b
*
mockBackend
)
NonceAt
(
ctx
context
.
Context
,
account
common
.
Address
,
blockNumber
*
big
.
Int
)
(
uint64
,
error
)
{
func
(
b
*
mockBackend
)
NonceAt
(
ctx
context
.
Context
,
account
common
.
Address
,
blockNumber
*
big
.
Int
)
(
uint64
,
error
)
{
return
0
,
nil
return
startingNonce
,
nil
}
}
func
(
b
*
mockBackend
)
PendingNonceAt
(
ctx
context
.
Context
,
account
common
.
Address
)
(
uint64
,
error
)
{
func
(
b
*
mockBackend
)
PendingNonceAt
(
ctx
context
.
Context
,
account
common
.
Address
)
(
uint64
,
error
)
{
return
0
,
nil
return
startingNonce
,
nil
}
}
func
(
*
mockBackend
)
ChainID
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
func
(
*
mockBackend
)
ChainID
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
return
big
.
NewInt
(
1
),
nil
return
big
.
NewInt
(
1
),
nil
}
}
// TransactionReceipt queries the mockBackend for a mined txHash. If none is
// TransactionReceipt queries the mockBackend for a mined txHash. If none is
found, nil is returned
// fo
und, nil is returned for both return values. Otherwise, it returns a
// fo
r both return values. Otherwise, it returns a receipt containing the txHash, the gasFeeCap
//
receipt containing the txHash and the gasFeeCap used in the GasUsed to make
//
used in GasUsed, and the blobFeeCap in CumuluativeGasUsed to make the values accessible from our
// t
he value accessible from our t
est framework.
// test framework.
func
(
b
*
mockBackend
)
TransactionReceipt
(
ctx
context
.
Context
,
txHash
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
{
func
(
b
*
mockBackend
)
TransactionReceipt
(
ctx
context
.
Context
,
txHash
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
{
b
.
mu
.
RLock
()
b
.
mu
.
RLock
()
defer
b
.
mu
.
RUnlock
()
defer
b
.
mu
.
RUnlock
()
...
@@ -265,10 +317,15 @@ func (b *mockBackend) TransactionReceipt(ctx context.Context, txHash common.Hash
...
@@ -265,10 +317,15 @@ func (b *mockBackend) TransactionReceipt(ctx context.Context, txHash common.Hash
// Return the gas fee cap 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.
// we can assert the proper tx confirmed in our tests.
var
blobFeeCap
uint64
if
txInfo
.
blobFeeCap
!=
nil
{
blobFeeCap
=
txInfo
.
blobFeeCap
.
Uint64
()
}
return
&
types
.
Receipt
{
return
&
types
.
Receipt
{
TxHash
:
txHash
,
TxHash
:
txHash
,
GasUsed
:
txInfo
.
gasFeeCap
.
Uint64
(),
GasUsed
:
txInfo
.
gasFeeCap
.
Uint64
(),
BlockNumber
:
big
.
NewInt
(
int64
(
txInfo
.
blockNumber
)),
CumulativeGasUsed
:
blobFeeCap
,
BlockNumber
:
big
.
NewInt
(
int64
(
txInfo
.
blockNumber
)),
},
nil
},
nil
}
}
...
@@ -284,7 +341,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
...
@@ -284,7 +341,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
gasPricer
:=
newGasPricer
(
1
)
gasPricer
:=
newGasPricer
(
1
)
gasTipCap
,
gasFeeCap
:=
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
...
@@ -293,7 +350,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
...
@@ -293,7 +350,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
if
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
}
}
return
nil
return
nil
}
}
...
@@ -315,7 +372,7 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
...
@@ -315,7 +372,7 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
h
:=
newTestHarness
(
t
)
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
...
@@ -341,7 +398,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
...
@@ -341,7 +398,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
h
:=
newTestHarness
(
t
)
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
...
@@ -349,7 +406,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
...
@@ -349,7 +406,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
if
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
}
}
return
nil
return
nil
}
}
...
@@ -364,6 +421,43 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
...
@@ -364,6 +421,43 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
}
}
// TestTxMgrConfirmsBlobTxAtMaxGasPrice asserts that Send properly returns the max gas price
// receipt if none of the lower gas price txs were mined when attempting to send a blob tx.
func
TestTxMgrConfirmsBlobTxAtHigherGasPrice
(
t
*
testing
.
T
)
{
t
.
Parallel
()
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
,
excessBlobGas
:=
h
.
gasPricer
.
sample
()
blobFeeCap
:=
eip4844
.
CalcBlobFee
(
excessBlobGas
)
t
.
Log
(
"Blob fee cap:"
,
blobFeeCap
,
"gasFeeCap:"
,
gasFeeCap
)
tx
:=
types
.
NewTx
(
&
types
.
BlobTx
{
GasTipCap
:
uint256
.
MustFromBig
(
gasTipCap
),
GasFeeCap
:
uint256
.
MustFromBig
(
gasFeeCap
),
BlobFeeCap
:
uint256
.
MustFromBig
(
blobFeeCap
),
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
h
.
gasPricer
.
shouldMineBlobTx
(
tx
.
GasFeeCap
(),
tx
.
BlobGasFeeCap
())
{
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
(),
tx
.
BlobGasFeeCap
())
}
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
sendTx
(
ctx
,
tx
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
// the fee cap for the blob tx at epoch == 3 should end up higher than the min required gas
// (expFeeCap()) since blob tx fee caps are bumped 100% with each epoch.
require
.
Less
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expBlobFeeCap
()
.
Uint64
(),
receipt
.
CumulativeGasUsed
)
}
// errRpcFailure is a sentinel error used in testing to fail publications.
// errRpcFailure is a sentinel error used in testing to fail publications.
var
errRpcFailure
=
errors
.
New
(
"rpc failure"
)
var
errRpcFailure
=
errors
.
New
(
"rpc failure"
)
...
@@ -375,7 +469,7 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
...
@@ -375,7 +469,7 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
h
:=
newTestHarness
(
t
)
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
...
@@ -401,22 +495,69 @@ func TestTxMgr_CraftTx(t *testing.T) {
...
@@ -401,22 +495,69 @@ func TestTxMgr_CraftTx(t *testing.T) {
candidate
:=
h
.
createTxCandidate
()
candidate
:=
h
.
createTxCandidate
()
// Craft the transaction.
// Craft the transaction.
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
feesForEpoch
(
h
.
gasPricer
.
epoch
+
1
)
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
feesForEpoch
(
h
.
gasPricer
.
epoch
+
1
)
tx
,
err
:=
h
.
mgr
.
craftTx
(
context
.
Background
(),
candidate
)
tx
,
err
:=
h
.
mgr
.
craftTx
(
context
.
Background
(),
candidate
)
require
.
Nil
(
t
,
err
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
tx
)
require
.
NotNil
(
t
,
tx
)
require
.
Equal
(
t
,
byte
(
types
.
DynamicFeeTxType
),
tx
.
Type
())
// Validate the gas tip cap and fee cap.
// Validate the gas tip cap and fee cap.
require
.
Equal
(
t
,
gasTipCap
,
tx
.
GasTipCap
())
require
.
Equal
(
t
,
gasTipCap
,
tx
.
GasTipCap
())
require
.
Equal
(
t
,
gasFeeCap
,
tx
.
GasFeeCap
())
require
.
Equal
(
t
,
gasFeeCap
,
tx
.
GasFeeCap
())
// Validate the nonce was set correctly using the backend.
// Validate the nonce was set correctly using the backend.
require
.
Zero
(
t
,
tx
.
Nonce
())
require
.
Equal
(
t
,
uint64
(
startingNonce
)
,
tx
.
Nonce
())
// Check that the gas was set using the gas limit.
// Check that the gas was set using the gas limit.
require
.
Equal
(
t
,
candidate
.
GasLimit
,
tx
.
Gas
())
require
.
Equal
(
t
,
candidate
.
GasLimit
,
tx
.
Gas
())
}
}
// TestTxMgr_CraftBlobTx ensures that the tx manager will create blob transactions as expected.
func
TestTxMgr_CraftBlobTx
(
t
*
testing
.
T
)
{
t
.
Parallel
()
h
:=
newTestHarness
(
t
)
candidate
:=
h
.
createBlobTxCandidate
()
// Craft the transaction.
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
feesForEpoch
(
h
.
gasPricer
.
epoch
+
1
)
tx
,
err
:=
h
.
mgr
.
craftTx
(
context
.
Background
(),
candidate
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
tx
)
require
.
Equal
(
t
,
byte
(
types
.
BlobTxType
),
tx
.
Type
())
// Validate the gas tip cap and fee cap.
require
.
Equal
(
t
,
gasTipCap
,
tx
.
GasTipCap
())
require
.
Equal
(
t
,
gasFeeCap
,
tx
.
GasFeeCap
())
require
.
Equal
(
t
,
minBlobTxFee
,
tx
.
BlobGasFeeCap
())
// Validate the nonce was set correctly using the backend.
require
.
Equal
(
t
,
uint64
(
startingNonce
),
tx
.
Nonce
())
// Check that the gas was set using the gas limit.
require
.
Equal
(
t
,
candidate
.
GasLimit
,
tx
.
Gas
())
// Check the blob fields
require
.
Equal
(
t
,
2
,
len
(
tx
.
BlobHashes
()))
sidecar
:=
tx
.
BlobTxSidecar
()
require
.
Equal
(
t
,
2
,
len
(
sidecar
.
Blobs
))
require
.
Equal
(
t
,
2
,
len
(
sidecar
.
Commitments
))
require
.
Equal
(
t
,
2
,
len
(
sidecar
.
Proofs
))
// verify the blobs
for
i
:=
range
sidecar
.
Blobs
{
require
.
NoError
(
t
,
kzg4844
.
VerifyBlobProof
(
sidecar
.
Blobs
[
i
],
sidecar
.
Commitments
[
i
],
sidecar
.
Proofs
[
i
]))
}
b1
:=
eth
.
Blob
(
sidecar
.
Blobs
[
0
])
d1
,
err
:=
b1
.
ToData
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
blobData1
,
d1
)
b2
:=
eth
.
Blob
(
sidecar
.
Blobs
[
1
])
d2
,
err
:=
b2
.
ToData
()
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
blobData2
,
d2
)
}
// TestTxMgr_EstimateGas ensures that the tx manager will estimate
// TestTxMgr_EstimateGas ensures that the tx manager will estimate
// the gas when candidate gas limit is zero in [CraftTx].
// the gas when candidate gas limit is zero in [CraftTx].
func
TestTxMgr_EstimateGas
(
t
*
testing
.
T
)
{
func
TestTxMgr_EstimateGas
(
t
*
testing
.
T
)
{
...
@@ -506,7 +647,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
...
@@ -506,7 +647,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
h
:=
newTestHarness
(
t
)
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
...
@@ -519,7 +660,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
...
@@ -519,7 +660,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
}
}
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
return
nil
return
nil
}
}
h
.
backend
.
setTxSender
(
sendTx
)
h
.
backend
.
setTxSender
(
sendTx
)
...
@@ -534,14 +675,14 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
...
@@ -534,14 +675,14 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
}
}
// TestTxMgrConfirmsMinGasPriceAfterBumping delays the mining of the initial tx
// TestTxMgrConfirmsMinGasPriceAfterBumping delays the mining of the initial tx
// with the minimum gas price, and asserts that it
'
s receipt is returned even
// with the minimum gas price, and asserts that its receipt is returned even
// though if the gas price has been bumped in other goroutines.
// though if the gas price has been bumped in other goroutines.
func
TestTxMgrConfirmsMinGasPriceAfterBumping
(
t
*
testing
.
T
)
{
func
TestTxMgrConfirmsMinGasPriceAfterBumping
(
t
*
testing
.
T
)
{
t
.
Parallel
()
t
.
Parallel
()
h
:=
newTestHarness
(
t
)
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
...
@@ -552,7 +693,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
...
@@ -552,7 +693,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
if
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
if
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
time
.
AfterFunc
(
5
*
time
.
Second
,
func
()
{
time
.
AfterFunc
(
5
*
time
.
Second
,
func
()
{
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
})
})
}
}
return
nil
return
nil
...
@@ -573,7 +714,7 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
...
@@ -573,7 +714,7 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
h
:=
newTestHarnessWithConfig
(
t
,
configWithNumConfs
(
2
))
h
:=
newTestHarnessWithConfig
(
t
,
configWithNumConfs
(
2
))
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
GasFeeCap
:
gasFeeCap
,
...
@@ -590,9 +731,9 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
...
@@ -590,9 +731,9 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
// Accept and mine the actual txn we expect to confirm.
// Accept and mine the actual txn we expect to confirm.
case
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
:
case
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
:
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
time
.
AfterFunc
(
5
*
time
.
Second
,
func
()
{
time
.
AfterFunc
(
5
*
time
.
Second
,
func
()
{
h
.
backend
.
mine
(
nil
,
nil
)
h
.
backend
.
mine
(
nil
,
nil
,
nil
)
})
})
return
nil
return
nil
...
@@ -622,7 +763,7 @@ func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) {
...
@@ -622,7 +763,7 @@ func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) {
// Create a tx and mine it immediately using the default backend.
// Create a tx and mine it immediately using the default backend.
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{})
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{})
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
new
(
big
.
Int
))
h
.
backend
.
mine
(
&
txHash
,
new
(
big
.
Int
)
,
nil
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
defer
cancel
()
defer
cancel
()
...
@@ -664,7 +805,7 @@ func TestWaitMinedMultipleConfs(t *testing.T) {
...
@@ -664,7 +805,7 @@ func TestWaitMinedMultipleConfs(t *testing.T) {
// Create an unimined tx.
// Create an unimined tx.
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{})
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{})
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
new
(
big
.
Int
))
h
.
backend
.
mine
(
&
txHash
,
new
(
big
.
Int
)
,
nil
)
receipt
,
err
:=
h
.
mgr
.
waitMined
(
ctx
,
tx
,
NewSendState
(
10
,
time
.
Hour
))
receipt
,
err
:=
h
.
mgr
.
waitMined
(
ctx
,
tx
,
NewSendState
(
10
,
time
.
Hour
))
require
.
Equal
(
t
,
err
,
context
.
DeadlineExceeded
)
require
.
Equal
(
t
,
err
,
context
.
DeadlineExceeded
)
...
@@ -674,7 +815,7 @@ func TestWaitMinedMultipleConfs(t *testing.T) {
...
@@ -674,7 +815,7 @@ func TestWaitMinedMultipleConfs(t *testing.T) {
defer
cancel
()
defer
cancel
()
// Mine an empty block, tx should now be confirmed.
// Mine an empty block, tx should now be confirmed.
h
.
backend
.
mine
(
nil
,
nil
)
h
.
backend
.
mine
(
nil
,
nil
,
nil
)
receipt
,
err
=
h
.
mgr
.
waitMined
(
ctx
,
tx
,
NewSendState
(
10
,
time
.
Hour
))
receipt
,
err
=
h
.
mgr
.
waitMined
(
ctx
,
tx
,
NewSendState
(
10
,
time
.
Hour
))
require
.
Nil
(
t
,
err
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
NotNil
(
t
,
receipt
)
...
@@ -707,6 +848,7 @@ type failingBackend struct {
...
@@ -707,6 +848,7 @@ type failingBackend struct {
returnSuccessHeader
bool
returnSuccessHeader
bool
returnSuccessReceipt
bool
returnSuccessReceipt
bool
baseFee
,
gasTip
*
big
.
Int
baseFee
,
gasTip
*
big
.
Int
excessBlobGas
*
uint64
}
}
// BlockNumber for the failingBackend returns errRpcFailure on the first
// BlockNumber for the failingBackend returns errRpcFailure on the first
...
@@ -743,8 +885,9 @@ func (b *failingBackend) HeaderByNumber(ctx context.Context, _ *big.Int) (*types
...
@@ -743,8 +885,9 @@ func (b *failingBackend) HeaderByNumber(ctx context.Context, _ *big.Int) (*types
}
}
return
&
types
.
Header
{
return
&
types
.
Header
{
Number
:
big
.
NewInt
(
1
),
Number
:
big
.
NewInt
(
1
),
BaseFee
:
b
.
baseFee
,
BaseFee
:
b
.
baseFee
,
ExcessBlobGas
:
b
.
excessBlobGas
,
},
nil
},
nil
}
}
...
@@ -923,15 +1066,17 @@ func TestIncreaseGasPrice(t *testing.T) {
...
@@ -923,15 +1066,17 @@ func TestIncreaseGasPrice(t *testing.T) {
func
TestIncreaseGasPriceLimits
(
t
*
testing
.
T
)
{
func
TestIncreaseGasPriceLimits
(
t
*
testing
.
T
)
{
t
.
Run
(
"no-threshold"
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
"no-threshold"
,
func
(
t
*
testing
.
T
)
{
testIncreaseGasPriceLimit
(
t
,
gasPriceLimitTest
{
testIncreaseGasPriceLimit
(
t
,
gasPriceLimitTest
{
expTipCap
:
46
,
expTipCap
:
46
,
expFeeCap
:
354
,
// just below 5*100
expFeeCap
:
354
,
// just below 5*100
expBlobFeeCap
:
4
*
params
.
GWei
,
})
})
})
})
t
.
Run
(
"with-threshold"
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
"with-threshold"
,
func
(
t
*
testing
.
T
)
{
testIncreaseGasPriceLimit
(
t
,
gasPriceLimitTest
{
testIncreaseGasPriceLimit
(
t
,
gasPriceLimitTest
{
thr
:
big
.
NewInt
(
params
.
GWei
),
thr
:
big
.
NewInt
(
params
.
GWei
*
10
),
expTipCap
:
131
_326_987
,
expTipCap
:
1
_293_535_754
,
expFeeCap
:
933
_286_308
,
// just below 1 gwei
expFeeCap
:
9
_192_620_686
,
// just below 10 gwei
expBlobFeeCap
:
8
*
params
.
GWei
,
})
})
})
})
}
}
...
@@ -939,6 +1084,7 @@ func TestIncreaseGasPriceLimits(t *testing.T) {
...
@@ -939,6 +1084,7 @@ func TestIncreaseGasPriceLimits(t *testing.T) {
type
gasPriceLimitTest
struct
{
type
gasPriceLimitTest
struct
{
thr
*
big
.
Int
thr
*
big
.
Int
expTipCap
,
expFeeCap
int64
expTipCap
,
expFeeCap
int64
expBlobFeeCap
int64
}
}
// testIncreaseGasPriceLimit runs a gas bumping test that increases the gas price until it hits an error.
// testIncreaseGasPriceLimit runs a gas bumping test that increases the gas price until it hits an error.
...
@@ -948,9 +1094,12 @@ func testIncreaseGasPriceLimit(t *testing.T, lt gasPriceLimitTest) {
...
@@ -948,9 +1094,12 @@ func testIncreaseGasPriceLimit(t *testing.T, lt gasPriceLimitTest) {
borkedTip
:=
int64
(
10
)
borkedTip
:=
int64
(
10
)
borkedFee
:=
int64
(
45
)
borkedFee
:=
int64
(
45
)
// simulate 100 excess blobs which yields a 50 wei blob basefee
borkedExcessBlobGas
:=
uint64
(
100
*
params
.
BlobTxBlobGasPerBlob
)
borkedBackend
:=
failingBackend
{
borkedBackend
:=
failingBackend
{
gasTip
:
big
.
NewInt
(
borkedTip
),
gasTip
:
big
.
NewInt
(
borkedTip
),
baseFee
:
big
.
NewInt
(
borkedFee
),
baseFee
:
big
.
NewInt
(
borkedFee
),
excessBlobGas
:
&
borkedExcessBlobGas
,
returnSuccessHeader
:
true
,
returnSuccessHeader
:
true
,
}
}
...
@@ -972,27 +1121,48 @@ func testIncreaseGasPriceLimit(t *testing.T, lt gasPriceLimitTest) {
...
@@ -972,27 +1121,48 @@ func testIncreaseGasPriceLimit(t *testing.T, lt gasPriceLimitTest) {
l
:
testlog
.
Logger
(
t
,
log
.
LvlCrit
),
l
:
testlog
.
Logger
(
t
,
log
.
LvlCrit
),
metr
:
&
metrics
.
NoopTxMetrics
{},
metr
:
&
metrics
.
NoopTxMetrics
{},
}
}
t
x
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
lastGoodT
x
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
big
.
NewInt
(
10
),
GasTipCap
:
big
.
NewInt
(
10
),
GasFeeCap
:
big
.
NewInt
(
100
),
GasFeeCap
:
big
.
NewInt
(
100
),
})
})
// Run IncreaseGasPrice a bunch of times in a row to simulate a very fast resubmit loop.
// Run increaseGasPrice a bunch of times in a row to simulate a very fast resubmit loop to make
// sure it errors out without a runaway fee increase.
ctx
:=
context
.
Background
()
ctx
:=
context
.
Background
()
var
err
error
for
{
for
{
newTx
,
err
:=
mgr
.
increaseGasPrice
(
ctx
,
tx
)
var
tmpTx
*
types
.
Transaction
tmpTx
,
err
=
mgr
.
increaseGasPrice
(
ctx
,
lastGoodTx
)
if
err
!=
nil
{
if
err
!=
nil
{
break
break
}
}
tx
=
new
Tx
lastGoodTx
=
tmp
Tx
}
}
require
.
Error
(
t
,
err
)
lastTip
,
lastFee
:=
tx
.
GasTipCap
(),
tx
.
GasFeeCap
()
// Confirm that fees only rose until expected threshold
// Confirm that fees only rose until expected threshold
require
.
Equal
(
t
,
lt
.
expTipCap
,
lastTip
.
Int64
())
require
.
Equal
(
t
,
lt
.
expTipCap
,
lastGoodTx
.
GasTipCap
()
.
Int64
())
require
.
Equal
(
t
,
lt
.
expFeeCap
,
lastFee
.
Int64
())
require
.
Equal
(
t
,
lt
.
expFeeCap
,
lastGoodTx
.
GasFeeCap
()
.
Int64
())
_
,
err
:=
mgr
.
increaseGasPrice
(
ctx
,
tx
)
require
.
Error
(
t
,
err
)
// Confirm blob txs also don't see runaway fee increase and that blob fee market is also capped
// as expected
blobTx
:=
&
types
.
BlobTx
{}
blobTx
.
GasTipCap
=
uint256
.
NewInt
(
1
)
blobTx
.
GasFeeCap
=
uint256
.
NewInt
(
10
)
// set a large initial blobFeeCap to make sure blob fee cap is hit before regular fee cap
blobTx
.
BlobFeeCap
=
uint256
.
NewInt
(
params
.
GWei
*
2
)
lastGoodTx
=
types
.
NewTx
(
blobTx
)
for
{
var
tmpTx
*
types
.
Transaction
tmpTx
,
err
=
mgr
.
increaseGasPrice
(
ctx
,
lastGoodTx
)
if
err
!=
nil
{
break
}
lastGoodTx
=
tmpTx
}
require
.
ErrorIs
(
t
,
err
,
ErrBlobFeeLimit
)
// Confirm that fees only rose until expected threshold
require
.
Equal
(
t
,
lt
.
expBlobFeeCap
,
lastGoodTx
.
BlobGasFeeCap
()
.
Int64
())
}
}
func
TestErrStringMatch
(
t
*
testing
.
T
)
{
func
TestErrStringMatch
(
t
*
testing
.
T
)
{
...
@@ -1032,7 +1202,7 @@ func TestNonceReset(t *testing.T) {
...
@@ -1032,7 +1202,7 @@ func TestNonceReset(t *testing.T) {
return
core
.
ErrNonceTooLow
return
core
.
ErrNonceTooLow
}
}
txHash
:=
tx
.
Hash
()
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
return
nil
return
nil
}
}
h
.
backend
.
setTxSender
(
sendTx
)
h
.
backend
.
setTxSender
(
sendTx
)
...
@@ -1050,8 +1220,8 @@ func TestNonceReset(t *testing.T) {
...
@@ -1050,8 +1220,8 @@ func TestNonceReset(t *testing.T) {
}
}
}
}
// internal nonce tracking should be reset every 3rd tx
// internal nonce tracking should be reset
to startingNonce value
every 3rd tx
require
.
Equal
(
t
,
[]
uint64
{
0
,
0
,
1
,
2
,
0
,
1
,
2
,
0
},
nonces
)
require
.
Equal
(
t
,
[]
uint64
{
1
,
1
,
2
,
3
,
1
,
2
,
3
,
1
},
nonces
)
}
}
func
TestMinFees
(
t
*
testing
.
T
)
{
func
TestMinFees
(
t
*
testing
.
T
)
{
...
@@ -1103,7 +1273,7 @@ func TestMinFees(t *testing.T) {
...
@@ -1103,7 +1273,7 @@ func TestMinFees(t *testing.T) {
conf
.
MinTipCap
=
tt
.
minTipCap
conf
.
MinTipCap
=
tt
.
minTipCap
h
:=
newTestHarnessWithConfig
(
t
,
conf
)
h
:=
newTestHarnessWithConfig
(
t
,
conf
)
tip
,
basefee
,
err
:=
h
.
mgr
.
suggestGasPriceCaps
(
context
.
TODO
())
tip
,
basefee
,
_
,
err
:=
h
.
mgr
.
suggestGasPriceCaps
(
context
.
TODO
())
require
.
NoError
(
err
)
require
.
NoError
(
err
)
if
tt
.
expectMinBasefee
{
if
tt
.
expectMinBasefee
{
...
...
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