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 {
newBasefee
int64
expectedTip
int64
expectedFC
int64
isBlobTx
bool
}
func
(
tc
*
priceBumpTest
)
run
(
t
*
testing
.
T
)
{
prevFC
:=
calcGasFeeCap
(
big
.
NewInt
(
tc
.
prevBasefee
),
big
.
NewInt
(
tc
.
prevGasTip
))
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
.
expectedFC
,
fc
.
Int64
(),
"fee cap must be as expected"
)
...
...
@@ -39,51 +40,111 @@ func TestUpdateFees(t *testing.T) {
newGasTip
:
90
,
newBasefee
:
900
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
90
,
newBasefee
:
900
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
1000
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
1000
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
100
,
newBasefee
:
1001
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
100
,
newBasefee
:
1001
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
900
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
900
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
90
,
newBasefee
:
1010
,
expectedTip
:
110
,
expectedFC
:
2310
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
90
,
newBasefee
:
1010
,
expectedTip
:
200
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
2000
,
expectedTip
:
110
,
expectedFC
:
4110
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
101
,
newBasefee
:
3000
,
expectedTip
:
200
,
expectedFC
:
6200
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
900
,
expectedTip
:
120
,
expectedFC
:
2310
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
900
,
expectedTip
:
220
,
expectedFC
:
4200
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
1100
,
expectedTip
:
120
,
expectedFC
:
2320
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
2000
,
expectedTip
:
220
,
expectedFC
:
4220
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
1140
,
expectedTip
:
120
,
expectedFC
:
2400
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
2040
,
expectedTip
:
220
,
expectedFC
:
4300
,
isBlobTx
:
true
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
120
,
newBasefee
:
1200
,
expectedTip
:
120
,
expectedFC
:
2520
,
},
{
prevGasTip
:
100
,
prevBasefee
:
1000
,
newGasTip
:
220
,
newBasefee
:
2100
,
expectedTip
:
220
,
expectedFC
:
4420
,
isBlobTx
:
true
,
},
}
for
i
,
test
:=
range
tests
{
i
:=
i
...
...
op-service/txmgr/queue_test.go
View file @
771988b6
...
...
@@ -193,7 +193,7 @@ func TestQueue_Send(t *testing.T) {
return
core
.
ErrNonceTooLow
}
txHash
:=
tx
.
Hash
()
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
return
nil
}
backend
.
setTxSender
(
sendTx
)
...
...
op-service/txmgr/txmgr.go
View file @
771988b6
...
...
@@ -12,10 +12,14 @@ import (
"github.com/ethereum/go-ethereum"
"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/txpool"
"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/params"
"github.com/holiman/uint256"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/retry"
...
...
@@ -23,15 +27,24 @@ import (
)
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
// geth requires a minimum fee bump of 100% for blob tx resubmission
blobPriceBump
int64
=
100
)
// new = old * (100 + priceBump) / 100
var
(
priceBumpPercent
=
big
.
NewInt
(
100
+
priceBump
)
oneHundred
=
big
.
NewInt
(
100
)
ninetyNine
=
big
.
NewInt
(
99
)
priceBumpPercent
=
big
.
NewInt
(
100
+
priceBump
)
blobPriceBumpPercent
=
big
.
NewInt
(
100
+
blobPriceBump
)
// 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,
...
...
@@ -149,14 +162,21 @@ func (m *SimpleTxManager) txLogger(tx *types.Transaction, logGas bool) log.Logge
if
logGas
{
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
...
)
}
// TxCandidate is a transaction candidate that can be submitted to ask the
// [TxManager] to construct a transaction with gas price bounds.
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
// 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
*
common
.
Address
// 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
// 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.
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
{
m
.
metr
.
RPCError
()
return
nil
,
fmt
.
Errorf
(
"failed to get gas price info: %w"
,
err
)
}
gasFeeCap
:=
calcGasFeeCap
(
basefee
,
gasTipCap
)
rawTx
:=
&
types
.
DynamicFeeTx
{
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
)
gasLimit
:=
candidate
.
GasLimit
// If the gas limit is set, we can use that as the gas
if
candidate
.
GasLimit
!=
0
{
rawTx
.
Gas
=
candidate
.
GasLimit
}
else
{
if
gasLimit
==
0
{
// Calculate the intrinsic gas for the transaction
gas
,
err
:=
m
.
backend
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
From
:
m
.
cfg
.
From
,
To
:
candidate
.
To
,
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
Data
:
rawTx
.
Data
,
Value
:
rawTx
.
Value
,
Data
:
candidate
.
Tx
Data
,
Value
:
candidate
.
Value
,
})
if
err
!=
nil
{
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.
...
...
@@ -257,7 +329,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (*
// then subsequent calls simply increment this number. If the transaction manager
// is reset, it will query the eth_getTransactionCount nonce again. If signing
// 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
()
defer
m
.
nonceLock
.
Unlock
()
...
...
@@ -275,10 +347,17 @@ func (m *SimpleTxManager) signWithNextNonce(ctx context.Context, rawTx *types.Dy
*
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
)
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
{
// decrement the nonce, so we can retry signing with the same nonce next time
// signWithNextNonce is called
...
...
@@ -512,42 +591,31 @@ func (m *SimpleTxManager) queryReceipt(ctx context.Context, txHash common.Hash,
return
nil
}
// increaseGasPrice takes the previous transaction, clones it, and returns it with fee values that
// are at least `priceBump` percent higher than the previous ones to satisfy Geth's replacement
// rules, and no lower than the values returned by the fee suggestion algorithm to ensure it
// doesn't linger in the mempool. Finally to avoid runaway price increases, fees are capped at a
// `feeLimitMultiplier` multiple of the suggested values.
// increaseGasPrice returns a new transaction that is equivalent to the input transaction but with
// higher fees that should satisfy geth's tx replacement rules. It also computes an updated gas
// limit estimate. To avoid runaway price increases, fees are capped at a `feeLimitMultiplier`
// multiple of the suggested values.
func
(
m
*
SimpleTxManager
)
increaseGasPrice
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
(
*
types
.
Transaction
,
error
)
{
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
{
m
.
txLogger
(
tx
,
false
)
.
Warn
(
"failed to get suggested gas tip and basefee"
,
"err"
,
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
{
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
gas
,
err
:=
m
.
backend
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
From
:
m
.
cfg
.
From
,
To
:
rawTx
.
To
,
To
:
tx
.
To
()
,
GasTipCap
:
bumpedTip
,
GasFeeCap
:
bumpedFee
,
Data
:
rawTx
.
Data
,
Data
:
tx
.
Data
(),
Value
:
tx
.
Value
(),
})
if
err
!=
nil
{
// 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
m
.
l
.
Info
(
"re-estimated gas differs"
,
"tx"
,
tx
.
Hash
(),
"oldgas"
,
tx
.
Gas
(),
"newgas"
,
gas
,
"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
)
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
{
m
.
l
.
Warn
(
"failed to sign new transaction"
,
"err"
,
err
,
"tx"
,
tx
.
Hash
())
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
func
(
m
*
SimpleTxManager
)
suggestGasPriceCaps
(
ctx
context
.
Context
)
(
*
big
.
Int
,
*
big
.
Int
,
error
)
{
// suggestGasPriceCaps suggests what the new tip, basefee, and blobfee should be based on the
// 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
)
defer
cancel
()
tip
,
err
:=
m
.
backend
.
SuggestGasTipCap
(
cCtx
)
if
err
!=
nil
{
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
{
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
)
defer
cancel
()
head
,
err
:=
m
.
backend
.
HeaderByNumber
(
cCtx
,
nil
)
if
err
!=
nil
{
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
{
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
m
.
metr
.
RecordBasefee
(
basefee
)
m
.
metr
.
RecordTipCap
(
tip
)
...
...
@@ -608,7 +713,11 @@ func (m *SimpleTxManager) suggestGasPriceCaps(ctx context.Context) (*big.Int, *b
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
{
...
...
@@ -630,28 +739,46 @@ func (m *SimpleTxManager) checkLimits(tip, basefee, bumpedTip, bumpedFee *big.In
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
func
calcThresholdValue
(
x
*
big
.
Int
)
*
big
.
Int
{
threshold
:=
new
(
big
.
Int
)
.
Mul
(
priceBumpPercent
,
x
)
threshold
.
Add
(
threshold
,
ninetyNine
)
threshold
.
Div
(
threshold
,
oneHundred
)
return
threshold
func
calcThresholdValue
(
x
*
big
.
Int
,
isBlobTx
bool
)
*
big
.
Int
{
threshold
:=
new
(
big
.
Int
)
if
isBlobTx
{
threshold
.
Set
(
blobPriceBumpPercent
)
}
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
// 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
// (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
)
lgr
=
lgr
.
New
(
"old_gasTipCap"
,
oldTip
,
"old_gasFeeCap"
,
oldFeeCap
,
"new_gasTipCap"
,
newTip
,
"new_gasFeeCap"
,
newFeeCap
,
"new_basefee"
,
newBaseFee
)
thresholdTip
:=
calcThresholdValue
(
oldTip
)
thresholdFeeCap
:=
calcThresholdValue
(
oldFeeCap
)
"new_gasTipCap"
,
newTip
,
"new_gasFeeCap"
,
newFeeCap
,
"new_basefee"
,
newBaseFee
)
thresholdTip
:=
calcThresholdValue
(
oldTip
,
isBlobTx
)
thresholdFeeCap
:=
calcThresholdValue
(
oldFeeCap
,
isBlobTx
)
if
newTip
.
Cmp
(
thresholdTip
)
>=
0
&&
newFeeCap
.
Cmp
(
thresholdFeeCap
)
>=
0
{
lgr
.
Debug
(
"Using new tip and feecap"
)
return
newTip
,
newFeeCap
...
...
@@ -680,10 +807,20 @@ func updateFees(oldTip, oldFeeCap, newTip, newBaseFee *big.Int, lgr log.Logger)
func
calcGasFeeCap
(
baseFee
,
gasTipCap
*
big
.
Int
)
*
big
.
Int
{
return
new
(
big
.
Int
)
.
Add
(
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.
// It can accept nil errors without issue.
func
errStringMatch
(
err
,
target
error
)
bool
{
...
...
@@ -694,3 +831,24 @@ func errStringMatch(err, target error) bool {
}
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 (
"testing"
"time"
"github.com/holiman/uint256"
"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/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"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/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
...
...
@@ -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
{
return
Config
{
ResubmissionTimeout
:
time
.
Second
,
...
...
@@ -95,6 +123,7 @@ type gasPricer struct {
mineAtEpoch
int64
baseGasTipFee
*
big
.
Int
baseBaseFee
*
big
.
Int
excessBlobGas
uint64
err
error
mu
sync
.
Mutex
}
...
...
@@ -104,24 +133,37 @@ func newGasPricer(mineAtEpoch int64) *gasPricer {
mineAtEpoch
:
mineAtEpoch
,
baseGasTipFee
:
big
.
NewInt
(
5
),
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
{
_
,
gasFeeCap
:=
g
.
feesForEpoch
(
g
.
mineAtEpoch
)
_
,
gasFeeCap
,
_
:=
g
.
feesForEpoch
(
g
.
mineAtEpoch
)
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
{
return
g
.
expGasFeeCap
()
.
Cmp
(
gasFeeCap
)
=
=
0
return
g
.
expGasFeeCap
()
.
Cmp
(
gasFeeCap
)
<
=
0
}
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
:=
calcGasFeeCap
(
epochBaseFee
,
epochGasTipCap
)
func
(
g
*
gasPricer
)
shouldMineBlobTx
(
gasFeeCap
,
blobFeeCap
*
big
.
Int
)
bool
{
return
g
.
shouldMine
(
gasFeeCap
)
&&
g
.
expBlobFeeCap
()
.
Cmp
(
blobFeeCap
)
<=
0
}
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
{
...
...
@@ -130,18 +172,25 @@ func (g *gasPricer) basefee() *big.Int {
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
()
defer
g
.
mu
.
Unlock
()
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
{
gasFeeCap
*
big
.
Int
blobFeeCap
*
big
.
Int
blockNumber
uint64
}
...
...
@@ -176,7 +225,7 @@ func (b *mockBackend) setTxSender(s sendTransactionFunc) {
// mine records a (txHash, gasFeeCap) 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
,
gasFeeCap
*
big
.
Int
)
{
func
(
b
*
mockBackend
)
mine
(
txHash
*
common
.
Hash
,
gasFeeCap
,
blobFeeCap
*
big
.
Int
)
{
b
.
mu
.
Lock
()
defer
b
.
mu
.
Unlock
()
...
...
@@ -184,6 +233,7 @@ func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) {
if
txHash
!=
nil
{
b
.
minedTxs
[
*
txHash
]
=
minedTxInfo
{
gasFeeCap
:
gasFeeCap
,
blobFeeCap
:
blobFeeCap
,
blockNumber
:
b
.
blockHeight
,
}
}
...
...
@@ -210,9 +260,11 @@ func (b *mockBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*typ
if
number
!=
nil
{
num
.
Set
(
number
)
}
bg
:=
b
.
g
.
excessblobgas
()
return
&
types
.
Header
{
Number
:
num
,
BaseFee
:
b
.
g
.
basefee
(),
Number
:
num
,
BaseFee
:
b
.
g
.
basefee
(),
ExcessBlobGas
:
&
bg
,
},
nil
}
...
...
@@ -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
)
{
tip
,
_
:=
b
.
g
.
sample
()
tip
,
_
,
_
:=
b
.
g
.
sample
()
return
tip
,
nil
}
...
...
@@ -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
)
{
return
0
,
nil
return
startingNonce
,
nil
}
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
)
{
return
big
.
NewInt
(
1
),
nil
}
// TransactionReceipt queries the mockBackend for a mined txHash. If none is
// fo
und, nil is returned for both return values. Otherwise, it returns a
//
receipt containing the txHash and the gasFeeCap used in the GasUsed to make
// t
he value accessible from our t
est framework.
// TransactionReceipt queries the mockBackend for a mined txHash. If none is
found, nil is returned
// fo
r both return values. Otherwise, it returns a receipt containing the txHash, the gasFeeCap
//
used in GasUsed, and the blobFeeCap in CumuluativeGasUsed to make the values accessible from our
// test framework.
func
(
b
*
mockBackend
)
TransactionReceipt
(
ctx
context
.
Context
,
txHash
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
{
b
.
mu
.
RLock
()
defer
b
.
mu
.
RUnlock
()
...
...
@@ -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
// we can assert the proper tx confirmed in our tests.
var
blobFeeCap
uint64
if
txInfo
.
blobFeeCap
!=
nil
{
blobFeeCap
=
txInfo
.
blobFeeCap
.
Uint64
()
}
return
&
types
.
Receipt
{
TxHash
:
txHash
,
GasUsed
:
txInfo
.
gasFeeCap
.
Uint64
(),
BlockNumber
:
big
.
NewInt
(
int64
(
txInfo
.
blockNumber
)),
TxHash
:
txHash
,
GasUsed
:
txInfo
.
gasFeeCap
.
Uint64
(),
CumulativeGasUsed
:
blobFeeCap
,
BlockNumber
:
big
.
NewInt
(
int64
(
txInfo
.
blockNumber
)),
},
nil
}
...
...
@@ -284,7 +341,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
gasPricer
:=
newGasPricer
(
1
)
gasTipCap
,
gasFeeCap
:=
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
...
...
@@ -293,7 +350,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
}
return
nil
}
...
...
@@ -315,7 +372,7 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
...
...
@@ -341,7 +398,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
...
...
@@ -349,7 +406,7 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
}
return
nil
}
...
...
@@ -364,6 +421,43 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
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.
var
errRpcFailure
=
errors
.
New
(
"rpc failure"
)
...
...
@@ -375,7 +469,7 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
...
...
@@ -401,22 +495,69 @@ func TestTxMgr_CraftTx(t *testing.T) {
candidate
:=
h
.
createTxCandidate
()
// 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
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
tx
)
require
.
Equal
(
t
,
byte
(
types
.
DynamicFeeTxType
),
tx
.
Type
())
// Validate the gas tip cap and fee cap.
require
.
Equal
(
t
,
gasTipCap
,
tx
.
GasTipCap
())
require
.
Equal
(
t
,
gasFeeCap
,
tx
.
GasFeeCap
())
// 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.
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
// the gas when candidate gas limit is zero in [CraftTx].
func
TestTxMgr_EstimateGas
(
t
*
testing
.
T
)
{
...
...
@@ -506,7 +647,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
...
...
@@ -519,7 +660,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
}
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
...
...
@@ -534,14 +675,14 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
}
// 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.
func
TestTxMgrConfirmsMinGasPriceAfterBumping
(
t
*
testing
.
T
)
{
t
.
Parallel
()
h
:=
newTestHarness
(
t
)
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
...
...
@@ -552,7 +693,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
if
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
time
.
AfterFunc
(
5
*
time
.
Second
,
func
()
{
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
})
}
return
nil
...
...
@@ -573,7 +714,7 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
h
:=
newTestHarnessWithConfig
(
t
,
configWithNumConfs
(
2
))
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
gasTipCap
,
gasFeeCap
,
_
:=
h
.
gasPricer
.
sample
()
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
...
...
@@ -590,9 +731,9 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
// Accept and mine the actual txn we expect to confirm.
case
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
:
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
time
.
AfterFunc
(
5
*
time
.
Second
,
func
()
{
h
.
backend
.
mine
(
nil
,
nil
)
h
.
backend
.
mine
(
nil
,
nil
,
nil
)
})
return
nil
...
...
@@ -622,7 +763,7 @@ func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) {
// Create a tx and mine it immediately using the default backend.
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{})
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
)
defer
cancel
()
...
...
@@ -664,7 +805,7 @@ func TestWaitMinedMultipleConfs(t *testing.T) {
// Create an unimined tx.
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{})
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
))
require
.
Equal
(
t
,
err
,
context
.
DeadlineExceeded
)
...
...
@@ -674,7 +815,7 @@ func TestWaitMinedMultipleConfs(t *testing.T) {
defer
cancel
()
// 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
))
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
...
...
@@ -707,6 +848,7 @@ type failingBackend struct {
returnSuccessHeader
bool
returnSuccessReceipt
bool
baseFee
,
gasTip
*
big
.
Int
excessBlobGas
*
uint64
}
// BlockNumber for the failingBackend returns errRpcFailure on the first
...
...
@@ -743,8 +885,9 @@ func (b *failingBackend) HeaderByNumber(ctx context.Context, _ *big.Int) (*types
}
return
&
types
.
Header
{
Number
:
big
.
NewInt
(
1
),
BaseFee
:
b
.
baseFee
,
Number
:
big
.
NewInt
(
1
),
BaseFee
:
b
.
baseFee
,
ExcessBlobGas
:
b
.
excessBlobGas
,
},
nil
}
...
...
@@ -923,15 +1066,17 @@ func TestIncreaseGasPrice(t *testing.T) {
func
TestIncreaseGasPriceLimits
(
t
*
testing
.
T
)
{
t
.
Run
(
"no-threshold"
,
func
(
t
*
testing
.
T
)
{
testIncreaseGasPriceLimit
(
t
,
gasPriceLimitTest
{
expTipCap
:
46
,
expFeeCap
:
354
,
// just below 5*100
expTipCap
:
46
,
expFeeCap
:
354
,
// just below 5*100
expBlobFeeCap
:
4
*
params
.
GWei
,
})
})
t
.
Run
(
"with-threshold"
,
func
(
t
*
testing
.
T
)
{
testIncreaseGasPriceLimit
(
t
,
gasPriceLimitTest
{
thr
:
big
.
NewInt
(
params
.
GWei
),
expTipCap
:
131
_326_987
,
expFeeCap
:
933
_286_308
,
// just below 1 gwei
thr
:
big
.
NewInt
(
params
.
GWei
*
10
),
expTipCap
:
1
_293_535_754
,
expFeeCap
:
9
_192_620_686
,
// just below 10 gwei
expBlobFeeCap
:
8
*
params
.
GWei
,
})
})
}
...
...
@@ -939,6 +1084,7 @@ func TestIncreaseGasPriceLimits(t *testing.T) {
type
gasPriceLimitTest
struct
{
thr
*
big
.
Int
expTipCap
,
expFeeCap
int64
expBlobFeeCap
int64
}
// 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) {
borkedTip
:=
int64
(
10
)
borkedFee
:=
int64
(
45
)
// simulate 100 excess blobs which yields a 50 wei blob basefee
borkedExcessBlobGas
:=
uint64
(
100
*
params
.
BlobTxBlobGasPerBlob
)
borkedBackend
:=
failingBackend
{
gasTip
:
big
.
NewInt
(
borkedTip
),
baseFee
:
big
.
NewInt
(
borkedFee
),
excessBlobGas
:
&
borkedExcessBlobGas
,
returnSuccessHeader
:
true
,
}
...
...
@@ -972,27 +1121,48 @@ func testIncreaseGasPriceLimit(t *testing.T, lt gasPriceLimitTest) {
l
:
testlog
.
Logger
(
t
,
log
.
LvlCrit
),
metr
:
&
metrics
.
NoopTxMetrics
{},
}
t
x
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
lastGoodT
x
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
big
.
NewInt
(
10
),
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
()
var
err
error
for
{
newTx
,
err
:=
mgr
.
increaseGasPrice
(
ctx
,
tx
)
var
tmpTx
*
types
.
Transaction
tmpTx
,
err
=
mgr
.
increaseGasPrice
(
ctx
,
lastGoodTx
)
if
err
!=
nil
{
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
require
.
Equal
(
t
,
lt
.
expTipCap
,
lastTip
.
Int64
())
require
.
Equal
(
t
,
lt
.
expFeeCap
,
lastFee
.
Int64
())
_
,
err
:=
mgr
.
increaseGasPrice
(
ctx
,
tx
)
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
lt
.
expTipCap
,
lastGoodTx
.
GasTipCap
()
.
Int64
())
require
.
Equal
(
t
,
lt
.
expFeeCap
,
lastGoodTx
.
GasFeeCap
()
.
Int64
())
// 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
)
{
...
...
@@ -1032,7 +1202,7 @@ func TestNonceReset(t *testing.T) {
return
core
.
ErrNonceTooLow
}
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
()
,
nil
)
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
...
...
@@ -1050,8 +1220,8 @@ func TestNonceReset(t *testing.T) {
}
}
// internal nonce tracking should be reset every 3rd tx
require
.
Equal
(
t
,
[]
uint64
{
0
,
0
,
1
,
2
,
0
,
1
,
2
,
0
},
nonces
)
// internal nonce tracking should be reset
to startingNonce value
every 3rd tx
require
.
Equal
(
t
,
[]
uint64
{
1
,
1
,
2
,
3
,
1
,
2
,
3
,
1
},
nonces
)
}
func
TestMinFees
(
t
*
testing
.
T
)
{
...
...
@@ -1103,7 +1273,7 @@ func TestMinFees(t *testing.T) {
conf
.
MinTipCap
=
tt
.
minTipCap
h
:=
newTestHarnessWithConfig
(
t
,
conf
)
tip
,
basefee
,
err
:=
h
.
mgr
.
suggestGasPriceCaps
(
context
.
TODO
())
tip
,
basefee
,
_
,
err
:=
h
.
mgr
.
suggestGasPriceCaps
(
context
.
TODO
())
require
.
NoError
(
err
)
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