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
b5a91778
Unverified
Commit
b5a91778
authored
Feb 14, 2023
by
Joshua Gutow
Committed by
GitHub
Feb 14, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4830 from ethereum-optimism/jg/txmgr_cleanup_pt2
txmgr: Simplify resubmit logic and scaffolding
parents
eac9b3ae
3468c6af
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
232 additions
and
155 deletions
+232
-155
driver.go
op-batcher/batcher/driver.go
+2
-0
txmgr.go
op-batcher/batcher/txmgr.go
+1
-31
l2_proposer.go
op-e2e/actions/l2_proposer.go
+5
-1
l2_output_submitter.go
op-proposer/proposer/l2_output_submitter.go
+8
-18
txmgr.go
op-service/txmgr/txmgr.go
+94
-30
txmgr_test.go
op-service/txmgr/txmgr_test.go
+122
-75
No files found.
op-batcher/batcher/driver.go
View file @
b5a91778
...
...
@@ -72,6 +72,8 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitte
ReceiptQueryInterval
:
time
.
Second
,
NumConfirmations
:
cfg
.
NumConfirmations
,
SafeAbortNonceTooLowCount
:
cfg
.
SafeAbortNonceTooLowCount
,
From
:
fromAddress
,
Signer
:
signer
(
rcfg
.
L1ChainID
),
}
batcherCfg
:=
Config
{
...
...
op-batcher/batcher/txmgr.go
View file @
b5a91778
...
...
@@ -54,14 +54,10 @@ func (t *TransactionManager) SendTransaction(ctx context.Context, data []byte) (
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to create tx: %w"
,
err
)
}
// Construct a closure that will update the txn with the current gas prices.
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
return
t
.
UpdateGasPrice
(
ctx
,
tx
)
}
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
100
*
time
.
Second
)
// TODO: Select a timeout that makes sense here.
defer
cancel
()
if
receipt
,
err
:=
t
.
txMgr
.
Send
(
ctx
,
updateGasPrice
,
t
.
l1Client
.
SendTransaction
);
err
!=
nil
{
if
receipt
,
err
:=
t
.
txMgr
.
Send
(
ctx
,
tx
);
err
!=
nil
{
t
.
log
.
Warn
(
"unable to publish tx"
,
"err"
,
err
,
"data_size"
,
len
(
data
))
return
nil
,
err
}
else
{
...
...
@@ -135,29 +131,3 @@ func (t *TransactionManager) CraftTx(ctx context.Context, data []byte) (*types.T
tx
:=
types
.
NewTx
(
rawTx
)
return
t
.
signerFn
(
ctx
,
t
.
senderAddress
,
tx
)
}
// UpdateGasPrice signs an otherwise identical txn to the one provided but with
// updated gas prices sampled from the existing network conditions.
//
// NOTE: This method SHOULD NOT publish the resulting transaction.
func
(
t
*
TransactionManager
)
UpdateGasPrice
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
,
err
:=
t
.
calcGasTipAndFeeCap
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
rawTx
:=
&
types
.
DynamicFeeTx
{
ChainID
:
t
.
chainID
,
Nonce
:
tx
.
Nonce
(),
To
:
tx
.
To
(),
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
Gas
:
tx
.
Gas
(),
Data
:
tx
.
Data
(),
}
// Only log the new tip/fee cap because the updateGasPrice closure reuses the same initial transaction
t
.
log
.
Trace
(
"updating gas price"
,
"tip_cap"
,
gasTipCap
,
"fee_cap"
,
gasFeeCap
)
finalTx
:=
types
.
NewTx
(
rawTx
)
return
t
.
signerFn
(
ctx
,
t
.
senderAddress
,
finalTx
)
}
op-e2e/actions/l2_proposer.go
View file @
b5a91778
...
...
@@ -50,6 +50,8 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl
ReceiptQueryInterval
:
time
.
Second
,
NumConfirmations
:
1
,
SafeAbortNonceTooLowCount
:
4
,
From
:
from
,
// Signer is loaded in `proposer.NewL2OutputSubmitter`
},
L1Client
:
l1
,
RollupClient
:
rollupCl
,
...
...
@@ -85,7 +87,9 @@ func (p *L2Proposer) ActMakeProposalTx(t Testing) {
tx
,
err
:=
p
.
driver
.
CreateProposalTx
(
t
.
Ctx
(),
output
)
require
.
NoError
(
t
,
err
)
err
=
p
.
driver
.
SendTransaction
(
t
.
Ctx
(),
tx
)
// Note: Use L1 instead of the output submitter's transaction manager because
// this is non-blocking while the txmgr is blocking & deadlocks the tests
err
=
p
.
l1
.
SendTransaction
(
t
.
Ctx
(),
tx
)
require
.
NoError
(
t
,
err
)
p
.
lastTx
=
tx
.
Hash
()
...
...
op-proposer/proposer/l2_output_submitter.go
View file @
b5a91778
...
...
@@ -171,6 +171,7 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSu
ReceiptQueryInterval
:
time
.
Second
,
NumConfirmations
:
cfg
.
NumConfirmations
,
SafeAbortNonceTooLowCount
:
cfg
.
SafeAbortNonceTooLowCount
,
From
:
fromAddress
,
}
proposerCfg
:=
Config
{
...
...
@@ -198,6 +199,8 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error)
cancel
()
return
nil
,
err
}
signer
:=
cfg
.
SignerFnFactory
(
chainID
)
cfg
.
TxManagerConfig
.
Signer
=
signer
l2ooContract
,
err
:=
bindings
.
NewL2OutputOracle
(
cfg
.
L2OutputOracleAddr
,
cfg
.
L1Client
)
if
err
!=
nil
{
...
...
@@ -227,7 +230,7 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error)
allowNonFinalized
:
cfg
.
AllowNonFinalized
,
from
:
cfg
.
From
,
signerFn
:
cfg
.
SignerFnFactory
(
chainID
)
,
signerFn
:
signer
,
pollInterval
:
cfg
.
PollInterval
,
},
nil
}
...
...
@@ -261,12 +264,6 @@ func (l *L2OutputSubmitter) UpdateGasPrice(ctx context.Context, tx *types.Transa
return
l
.
rawL2ooContract
.
RawTransact
(
opts
,
tx
.
Data
())
}
// SendTransaction injects a signed transaction into the pending pool for execution.
func
(
l
*
L2OutputSubmitter
)
SendTransaction
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
l
.
log
.
Info
(
"proposer sending transaction"
,
"tx"
,
tx
.
Hash
())
return
l
.
l1Client
.
SendTransaction
(
ctx
,
tx
)
}
// FetchNextOutputInfo gets the block number of the next proposal.
// It returns: the next block number, if the proposal should be made, error
func
(
l
*
L2OutputSubmitter
)
FetchNextOutputInfo
(
ctx
context
.
Context
)
(
*
eth
.
OutputResponse
,
bool
,
error
)
{
...
...
@@ -356,22 +353,15 @@ func (l *L2OutputSubmitter) CreateProposalTx(ctx context.Context, output *eth.Ou
return
tx
,
nil
}
// SendTransaction
Ext
sends a transaction through the transaction manager which handles automatic
// SendTransaction sends a transaction through the transaction manager which handles automatic
// price bumping.
// It also hardcodes a timeout of 100s.
func
(
l
*
L2OutputSubmitter
)
SendTransactionExt
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
// Construct the closure that will update the txn with the current gas prices.
nonce
:=
tx
.
Nonce
()
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
l
.
log
.
Info
(
"proposer updating batch tx gas price"
,
"nonce"
,
nonce
)
return
l
.
UpdateGasPrice
(
ctx
,
tx
)
}
func
(
l
*
L2OutputSubmitter
)
SendTransaction
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
// Wait until one of our submitted transactions confirms. If no
// receipt is received it's likely our gas price was too low.
cCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
100
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
l
.
txMgr
.
Send
(
cCtx
,
updateGasPrice
,
l
.
SendTransaction
)
receipt
,
err
:=
l
.
txMgr
.
Send
(
cCtx
,
tx
)
if
err
!=
nil
{
l
.
log
.
Error
(
"proposer unable to publish tx"
,
"err"
,
err
)
return
err
...
...
@@ -411,7 +401,7 @@ func (l *L2OutputSubmitter) loop() {
cancel
()
break
}
if
err
:=
l
.
SendTransaction
Ext
(
cCtx
,
tx
);
err
!=
nil
{
if
err
:=
l
.
SendTransaction
(
cCtx
,
tx
);
err
!=
nil
{
l
.
log
.
Error
(
"Failed to send proposal transaction"
,
"err"
,
err
)
cancel
()
break
...
...
op-service/txmgr/txmgr.go
View file @
b5a91778
...
...
@@ -2,14 +2,17 @@ package txmgr
import
(
"context"
"errors"
"math/big"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
opcrypto
"github.com/ethereum-optimism/optimism/op-service/crypto"
)
// UpdateGasPriceSendTxFunc defines a function signature for publishing a
...
...
@@ -40,6 +43,10 @@ type Config struct {
// are required to give up on a tx at a particular nonce without receiving
// confirmation.
SafeAbortNonceTooLowCount
uint64
// Signer is used to sign transactions when the gas price is increased.
Signer
opcrypto
.
SignerFn
From
common
.
Address
}
// TxManager is an interface that allows callers to reliably publish txs,
...
...
@@ -50,15 +57,15 @@ type TxManager interface {
// until an invocation of sendTx returns (called with differing gas
// prices). The method may be canceled using the passed context.
//
// The initial transaction MUST be signed & ready to submit.
//
// NOTE: Send should be called by AT MOST one caller at a time.
Send
(
ctx
context
.
Context
,
updateGasPrice
UpdateGasPriceFunc
,
sendTxn
SendTransactionFunc
)
(
*
types
.
Receipt
,
error
)
Send
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
(
*
types
.
Receipt
,
error
)
}
// ReceiptSource is a minimal function signature used to detect the confirmation
// of published txs.
//
// NOTE: This is a subset of bind.DeployBackend.
type
ReceiptSource
interface
{
// ETHBackend is the set of methods that the transaction manager uses to resubmit gas & determine
// when transactions are included on L1.
type
ETHBackend
interface
{
// BlockNumber returns the most recent block number.
BlockNumber
(
ctx
context
.
Context
)
(
uint64
,
error
)
...
...
@@ -66,6 +73,14 @@ type ReceiptSource interface {
// txHash. If lookup does not fail, but the transaction is not found,
// nil should be returned for both values.
TransactionReceipt
(
ctx
context
.
Context
,
txHash
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
// SendTransaction submits a signed transaction to L1.
SendTransaction
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
// These functions are used to estimate what the basefee & priority fee should be set to.
// TODO(CLI-3318): Maybe need a generic interface to support different RPC providers
HeaderByNumber
(
ctx
context
.
Context
,
number
*
big
.
Int
)
(
*
types
.
Header
,
error
)
SuggestGasTipCap
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
}
// SimpleTxManager is a implementation of TxManager that performs linear fee
...
...
@@ -74,12 +89,55 @@ type SimpleTxManager struct {
Config
// embed the config directly
name
string
backend
ReceiptSource
backend
ETHBackend
l
log
.
Logger
}
// IncreaseGasPrice takes the previous transaction & potentially clones then signs it with a higher tip.
// If the basefee + priority fee did not increase by a minimum percent (geth's replacement percent) an
// error will be returned.
// We do not re-estimate the amount of gas used because for some stateful transactions (like output proposals) the
// act of including the transaction renders the repeat of the transaction invalid.
func
(
m
*
SimpleTxManager
)
IncreaseGasPrice
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
(
*
types
.
Transaction
,
error
)
{
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
5
*
time
.
Second
)
defer
cancel
()
var
gasTipCap
,
gasFeeCap
*
big
.
Int
if
tip
,
err
:=
m
.
backend
.
SuggestGasTipCap
(
ctx
);
err
!=
nil
{
return
nil
,
err
}
else
if
tip
==
nil
{
return
nil
,
errors
.
New
(
"the suggested tip was nil"
)
}
else
{
gasTipCap
=
tip
}
if
head
,
err
:=
m
.
backend
.
HeaderByNumber
(
ctx
,
nil
);
err
!=
nil
{
return
nil
,
err
}
else
if
head
.
BaseFee
==
nil
{
return
nil
,
errors
.
New
(
"txmgr does not support pre-london blocks that do not have a basefee"
)
}
else
{
gasFeeCap
=
CalcGasFeeCap
(
head
.
BaseFee
,
gasTipCap
)
}
// TODO (CLI-2630): Check for a large enough price bump
rawTx
:=
&
types
.
DynamicFeeTx
{
ChainID
:
tx
.
ChainId
(),
Nonce
:
tx
.
Nonce
(),
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
Gas
:
tx
.
Gas
(),
To
:
tx
.
To
(),
Value
:
tx
.
Value
(),
Data
:
tx
.
Data
(),
AccessList
:
tx
.
AccessList
(),
}
return
m
.
Signer
(
ctx
,
m
.
From
,
types
.
NewTx
(
rawTx
))
}
// NewSimpleTxManager initializes a new SimpleTxManager with the passed Config.
func
NewSimpleTxManager
(
name
string
,
l
log
.
Logger
,
cfg
Config
,
backend
ReceiptSource
)
*
SimpleTxManager
{
func
NewSimpleTxManager
(
name
string
,
l
log
.
Logger
,
cfg
Config
,
backend
ETHBackend
)
*
SimpleTxManager
{
if
cfg
.
NumConfirmations
==
0
{
panic
(
"txmgr: NumConfirmations cannot be zero"
)
}
...
...
@@ -97,8 +155,12 @@ func NewSimpleTxManager(name string, l log.Logger, cfg Config, backend ReceiptSo
// invocation of sendTx returns (called with differing gas prices). The method
// may be canceled using the passed context.
//
// The initially supplied transaction must be signed, have gas estimation done, and have a reasonable gas fee.
// When the transaction is resubmitted the tx manager will re-sign the transaction at a different gas pricing
// but retain the gas used, the nonce, and the data.
//
// NOTE: Send should be called by AT MOST one caller at a time.
func
(
m
*
SimpleTxManager
)
Send
(
ctx
context
.
Context
,
updateGasPrice
UpdateGasPriceFunc
,
sendTx
SendTransactionFunc
)
(
*
types
.
Receipt
,
error
)
{
func
(
m
*
SimpleTxManager
)
Send
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
(
*
types
.
Receipt
,
error
)
{
// Initialize a wait group to track any spawned goroutines, and ensure
// we properly clean up any dangling resources this method generates.
...
...
@@ -114,22 +176,13 @@ func (m *SimpleTxManager) Send(ctx context.Context, updateGasPrice UpdateGasPric
sendState
:=
NewSendState
(
m
.
SafeAbortNonceTooLowCount
)
// Create a closure that will block on
passed sendTx function
in the
// Create a closure that will block on
submitting the tx
in the
// background, returning the first successfully mined receipt back to
// the main event loop via receiptChan.
receiptChan
:=
make
(
chan
*
types
.
Receipt
,
1
)
sendTxAsync
:=
func
()
{
sendTxAsync
:=
func
(
tx
*
types
.
Transaction
)
{
defer
wg
.
Done
()
tx
,
err
:=
updateGasPrice
(
ctx
)
if
err
!=
nil
{
if
err
==
context
.
Canceled
||
strings
.
Contains
(
err
.
Error
(),
"context canceled"
)
{
return
}
m
.
l
.
Error
(
"unable to update txn gas price"
,
"err"
,
err
)
return
}
txHash
:=
tx
.
Hash
()
nonce
:=
tx
.
Nonce
()
gasTipCap
:=
tx
.
GasTipCap
()
...
...
@@ -137,12 +190,14 @@ func (m *SimpleTxManager) Send(ctx context.Context, updateGasPrice UpdateGasPric
log
:=
m
.
l
.
New
(
"txHash"
,
txHash
,
"nonce"
,
nonce
,
"gasTipCap"
,
gasTipCap
,
"gasFeeCap"
,
gasFeeCap
)
log
.
Info
(
"publishing transaction"
)
// Sign and publish transaction with current gas price.
err
=
sendTx
(
ctx
,
tx
)
err
:=
m
.
backend
.
SendTransaction
(
ctx
,
tx
)
sendState
.
ProcessSendError
(
err
)
if
err
!=
nil
{
if
err
==
context
.
Canceled
||
strings
.
Contains
(
err
.
Error
(),
"context canceled"
)
{
if
errors
.
Is
(
err
,
context
.
Canceled
)
{
return
}
if
errors
.
Is
(
err
,
txpool
.
ErrAlreadyKnown
)
{
log
.
Info
(
"resubmitted already known transaction"
)
return
}
log
.
Error
(
"unable to publish transaction"
,
"err"
,
err
)
...
...
@@ -177,7 +232,7 @@ func (m *SimpleTxManager) Send(ctx context.Context, updateGasPrice UpdateGasPric
// background, before entering the event loop and waiting out the
// resubmission timeout.
wg
.
Add
(
1
)
go
sendTxAsync
()
go
sendTxAsync
(
tx
)
ticker
:=
time
.
NewTicker
(
m
.
ResubmissionTimeout
)
defer
ticker
.
Stop
()
...
...
@@ -196,9 +251,17 @@ func (m *SimpleTxManager) Send(ctx context.Context, updateGasPrice UpdateGasPric
continue
}
// Submit and wait for the bumped traction to confirm.
// Increase the gas price & submit the new transaction
newTx
,
err
:=
m
.
IncreaseGasPrice
(
ctx
,
tx
)
if
err
!=
nil
{
m
.
l
.
Error
(
"Failed to increase the gas price for the tx"
,
"err"
,
err
)
// Don't `continue` here so we resubmit the transaction with the same gas price.
}
else
{
// Save the tx so we know it's gas price.
tx
=
newTx
}
wg
.
Add
(
1
)
go
sendTxAsync
()
go
sendTxAsync
(
tx
)
// The passed context has been canceled, i.e. in the event of a
// shutdown.
...
...
@@ -235,7 +298,7 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction,
break
}
m
.
l
.
Trace
(
"Transaction mined, checking confirmations"
,
"txHash"
,
txHash
,
"txHeight"
,
txHeight
,
m
.
l
.
Debug
(
"Transaction mined, checking confirmations"
,
"txHash"
,
txHash
,
"txHeight"
,
txHeight
,
"tipHeight"
,
tipHeight
,
"numConfirmations"
,
m
.
NumConfirmations
)
// The transaction is considered confirmed when
...
...
@@ -252,7 +315,7 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction,
// Safe to subtract since we know the LHS above is greater.
confsRemaining
:=
(
txHeight
+
m
.
NumConfirmations
)
-
(
tipHeight
+
1
)
m
.
l
.
Info
(
"Transaction not yet confirmed"
,
"txHash"
,
txHash
,
"confsRemaining"
,
confsRemaining
)
m
.
l
.
Debug
(
"Transaction not yet confirmed"
,
"txHash"
,
txHash
,
"confsRemaining"
,
confsRemaining
)
case
err
!=
nil
:
m
.
l
.
Trace
(
"Receipt retrievel failed"
,
"hash"
,
txHash
,
"err"
,
err
)
...
...
@@ -266,6 +329,7 @@ func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction,
select
{
case
<-
ctx
.
Done
()
:
m
.
l
.
Warn
(
"context cancelled in waitMined"
)
return
nil
,
ctx
.
Err
()
case
<-
queryTicker
.
C
:
}
...
...
op-service/txmgr/txmgr_test.go
View file @
b5a91778
...
...
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
...
...
@@ -28,14 +29,15 @@ type testHarness struct {
// newTestHarnessWithConfig initializes a testHarness with a specific
// configuration.
func
newTestHarnessWithConfig
(
t
*
testing
.
T
,
cfg
Config
)
*
testHarness
{
backend
:=
newMockBackend
()
g
:=
newGasPricer
(
3
)
backend
:=
newMockBackend
(
g
)
mgr
:=
NewSimpleTxManager
(
"TEST"
,
testlog
.
Logger
(
t
,
log
.
LvlCrit
),
cfg
,
backend
)
return
&
testHarness
{
cfg
:
cfg
,
mgr
:
mgr
,
backend
:
backend
,
gasPricer
:
newGasPricer
(
3
)
,
gasPricer
:
g
,
}
}
...
...
@@ -51,6 +53,10 @@ func configWithNumConfs(numConfirmations uint64) Config {
ReceiptQueryInterval
:
50
*
time
.
Millisecond
,
NumConfirmations
:
numConfirmations
,
SafeAbortNonceTooLowCount
:
3
,
Signer
:
func
(
ctx
context
.
Context
,
from
common
.
Address
,
tx
*
types
.
Transaction
)
(
*
types
.
Transaction
,
error
)
{
return
tx
,
nil
},
From
:
common
.
Address
{},
}
}
...
...
@@ -87,6 +93,12 @@ func (g *gasPricer) feesForEpoch(epoch int64) (*big.Int, *big.Int) {
return
epochGasTipCap
,
epochGasFeeCap
}
func
(
g
*
gasPricer
)
basefee
()
*
big
.
Int
{
g
.
mu
.
Lock
()
defer
g
.
mu
.
Unlock
()
return
new
(
big
.
Int
)
.
Mul
(
g
.
baseBaseFee
,
big
.
NewInt
(
g
.
epoch
))
}
func
(
g
*
gasPricer
)
sample
()
(
*
big
.
Int
,
*
big
.
Int
)
{
g
.
mu
.
Lock
()
defer
g
.
mu
.
Unlock
()
...
...
@@ -107,6 +119,9 @@ type minedTxInfo struct {
type
mockBackend
struct
{
mu
sync
.
RWMutex
g
*
gasPricer
send
SendTransactionFunc
// blockHeight tracks the current height of the chain.
blockHeight
uint64
...
...
@@ -115,12 +130,18 @@ type mockBackend struct {
}
// newMockBackend initializes a new mockBackend.
func
newMockBackend
()
*
mockBackend
{
func
newMockBackend
(
g
*
gasPricer
)
*
mockBackend
{
return
&
mockBackend
{
g
:
g
,
minedTxs
:
make
(
map
[
common
.
Hash
]
minedTxInfo
),
}
}
// setTxSender sets the implementation for the SendTransactionFunction
func
(
b
*
mockBackend
)
setTxSender
(
s
SendTransactionFunc
)
{
b
.
send
=
s
}
// 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.
...
...
@@ -145,14 +166,30 @@ func (b *mockBackend) BlockNumber(ctx context.Context) (uint64, error) {
return
b
.
blockHeight
,
nil
}
func
(
b
*
mockBackend
)
HeaderByNumber
(
ctx
context
.
Context
,
number
*
big
.
Int
)
(
*
types
.
Header
,
error
)
{
return
&
types
.
Header
{
BaseFee
:
b
.
g
.
basefee
(),
},
nil
}
func
(
b
*
mockBackend
)
SuggestGasTipCap
(
ctx
context
.
Context
)
(
*
big
.
Int
,
error
)
{
tip
,
_
:=
b
.
g
.
sample
()
return
tip
,
nil
}
func
(
b
*
mockBackend
)
SendTransaction
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
b
.
send
==
nil
{
panic
(
"set sender function was not set"
)
}
return
b
.
send
(
ctx
,
tx
)
}
// TransactionReceipt queries the mockBackend for a mined txHash. If none is
// found, nil is returned for both return values. Otherwise, it retruns a
// receipt containing the txHash and the gasFeeCap used in the GasUsed to make
// the value accessible from our 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
()
defer
b
.
mu
.
RUnlock
()
...
...
@@ -180,13 +217,11 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
gasPricer
:=
newGasPricer
(
1
)
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
:=
gasPricer
.
sample
()
return
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
}),
nil
}
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
...
...
@@ -195,9 +230,11 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
}
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
:=
context
.
Background
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
updateGasPrice
,
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
tx
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
...
...
@@ -211,23 +248,21 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
h
:=
newTestHarness
(
t
)
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
return
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
}),
nil
}
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
// Don't publish tx to backend, simulating never being mined.
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
updateGasPrice
,
sendT
x
)
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
t
x
)
require
.
Equal
(
t
,
err
,
context
.
DeadlineExceeded
)
require
.
Nil
(
t
,
receipt
)
}
...
...
@@ -239,14 +274,11 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
h
:=
newTestHarness
(
t
)
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
return
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
}),
nil
}
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
if
h
.
gasPricer
.
shouldMine
(
tx
.
GasFeeCap
())
{
txHash
:=
tx
.
Hash
()
...
...
@@ -254,9 +286,11 @@ func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
}
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
:=
context
.
Background
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
updateGasPrice
,
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
tx
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
...
...
@@ -273,22 +307,21 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
h
:=
newTestHarness
(
t
)
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
return
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
}),
nil
}
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
return
errRpcFailure
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
updateGasPrice
,
sendT
x
)
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
t
x
)
require
.
Equal
(
t
,
err
,
context
.
DeadlineExceeded
)
require
.
Nil
(
t
,
receipt
)
}
...
...
@@ -301,13 +334,11 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
h
:=
newTestHarness
(
t
)
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
return
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
}),
nil
}
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
// Fail all but the final attempt.
...
...
@@ -319,9 +350,11 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
h
.
backend
.
mine
(
&
txHash
,
tx
.
GasFeeCap
())
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
:=
context
.
Background
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
updateGasPrice
,
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
tx
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
...
...
@@ -336,13 +369,11 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
h
:=
newTestHarness
(
t
)
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
return
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
}),
nil
}
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
// Delay mining the tx with the min gas price.
...
...
@@ -354,9 +385,11 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
}
return
nil
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
:=
context
.
Background
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
updateGasPrice
,
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
tx
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
...
...
@@ -368,13 +401,11 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
h
:=
newTestHarnessWithConfig
(
t
,
configWithNumConfs
(
2
))
updateGasPrice
:=
func
(
ctx
context
.
Context
)
(
*
types
.
Transaction
,
error
)
{
gasTipCap
,
gasFeeCap
:=
h
.
gasPricer
.
sample
()
return
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
tx
:=
types
.
NewTx
(
&
types
.
DynamicFeeTx
{
GasTipCap
:
gasTipCap
,
GasFeeCap
:
gasFeeCap
,
}),
nil
}
})
sendTx
:=
func
(
ctx
context
.
Context
,
tx
*
types
.
Transaction
)
error
{
switch
{
...
...
@@ -399,9 +430,11 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
return
core
.
ErrNonceTooLow
}
}
h
.
backend
.
setTxSender
(
sendTx
)
ctx
:=
context
.
Background
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
updateGasPrice
,
sendTx
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
Send
(
ctx
,
tx
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
require
.
Equal
(
t
,
h
.
gasPricer
.
expGasFeeCap
()
.
Uint64
(),
receipt
.
GasUsed
)
...
...
@@ -419,7 +452,8 @@ func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) {
txHash
:=
tx
.
Hash
()
h
.
backend
.
mine
(
&
txHash
,
new
(
big
.
Int
))
ctx
:=
context
.
Background
()
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
h
.
mgr
.
waitMined
(
ctx
,
tx
,
nil
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
...
...
@@ -433,7 +467,7 @@ func TestWaitMinedCanBeCanceled(t *testing.T) {
h
:=
newTestHarness
(
t
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
5
*
time
.
Second
)
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
// Create an unimined tx.
...
...
@@ -524,6 +558,18 @@ func (b *failingBackend) TransactionReceipt(
},
nil
}
func
(
b
*
failingBackend
)
HeaderByNumber
(
_
context
.
Context
,
_
*
big
.
Int
)
(
*
types
.
Header
,
error
)
{
return
nil
,
ethereum
.
NotFound
}
func
(
b
*
failingBackend
)
SendTransaction
(
_
context
.
Context
,
_
*
types
.
Transaction
)
error
{
return
errors
.
New
(
"unimplemented"
)
}
func
(
b
*
failingBackend
)
SuggestGasTipCap
(
_
context
.
Context
)
(
*
big
.
Int
,
error
)
{
return
nil
,
errors
.
New
(
"unimplemented"
)
}
// TestWaitMinedReturnsReceiptAfterFailure asserts that WaitMined is able to
// recover from failed calls to the backend. It uses the failedBackend to
// simulate an rpc call failure, followed by the successful return of a receipt.
...
...
@@ -549,7 +595,8 @@ func TestWaitMinedReturnsReceiptAfterFailure(t *testing.T) {
tx
:=
types
.
NewTx
(
&
types
.
LegacyTx
{})
txHash
:=
tx
.
Hash
()
ctx
:=
context
.
Background
()
ctx
,
cancel
:=
context
.
WithTimeout
(
context
.
Background
(),
10
*
time
.
Second
)
defer
cancel
()
receipt
,
err
:=
mgr
.
waitMined
(
ctx
,
tx
,
nil
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
receipt
)
...
...
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