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
a9870d6e
Unverified
Commit
a9870d6e
authored
Jan 12, 2022
by
Conner Fromknecht
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add shared ClearPendingTx impl
parent
5a09c9a4
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
365 additions
and
0 deletions
+365
-0
clear_pending_tx.go
go/batch-submitter/drivers/clear_pending_tx.go
+173
-0
clear_pending_tx_test.go
go/batch-submitter/drivers/clear_pending_tx_test.go
+192
-0
No files found.
go/batch-submitter/drivers/clear_pending_tx.go
0 → 100644
View file @
a9870d6e
package
drivers
import
(
"context"
"crypto/ecdsa"
"errors"
"math/big"
"strings"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// ErrClearPendingRetry signals that a transaction from a previous running
// instance confirmed rather than our clearing transaction on startup. In this
// case the caller should retry.
var
ErrClearPendingRetry
=
errors
.
New
(
"retry clear pending txn"
)
// ClearPendingTx publishes a NOOP transaction at the wallet's next unused
// nonce. This is used on restarts in order to clear the mempool of any prior
// publications and ensure the batch submitter starts submitting from a clean
// slate.
func
ClearPendingTx
(
name
string
,
ctx
context
.
Context
,
txMgr
txmgr
.
TxManager
,
l1Client
L1Client
,
walletAddr
common
.
Address
,
privKey
*
ecdsa
.
PrivateKey
,
chainID
*
big
.
Int
,
)
error
{
// Query for the submitter's current nonce.
nonce
,
err
:=
l1Client
.
NonceAt
(
ctx
,
walletAddr
,
nil
)
if
err
!=
nil
{
log
.
Error
(
name
+
" unable to get current nonce"
,
"err"
,
err
)
return
err
}
ctx
,
cancel
:=
context
.
WithCancel
(
ctx
)
defer
cancel
()
// Construct the clearing transaction submission clousure that will attempt
// to send the a clearing transaction transaction at the given nonce and gas
// price.
sendTx
:=
func
(
ctx
context
.
Context
,
gasPrice
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
log
.
Info
(
name
+
" clearing pending tx"
,
"nonce"
,
nonce
,
"gasPrice"
,
gasPrice
)
signedTx
,
err
:=
SignClearingTx
(
ctx
,
walletAddr
,
nonce
,
gasPrice
,
l1Client
,
privKey
,
chainID
,
)
if
err
!=
nil
{
log
.
Error
(
name
+
" unable to sign clearing tx"
,
"nonce"
,
nonce
,
"gasPrice"
,
gasPrice
,
"err"
,
err
)
return
nil
,
err
}
txHash
:=
signedTx
.
Hash
()
err
=
l1Client
.
SendTransaction
(
ctx
,
signedTx
)
switch
{
// Clearing transaction successfully confirmed.
case
err
==
nil
:
log
.
Info
(
name
+
" submitted clearing tx"
,
"nonce"
,
nonce
,
"gasPrice"
,
gasPrice
,
"txHash"
,
txHash
)
return
signedTx
,
nil
// Getting a nonce too low error implies that a previous transaction in
// the mempool has confirmed and we should abort trying to publish at
// this nonce.
case
strings
.
Contains
(
err
.
Error
(),
core
.
ErrNonceTooLow
.
Error
())
:
log
.
Info
(
name
+
" transaction from previous restart confirmed, "
+
"aborting mempool clearing"
)
cancel
()
return
nil
,
context
.
Canceled
// An unexpected error occurred. This also handles the case where the
// clearing transaction has not yet bested the gas price a prior
// transaction in the mempool at this nonce. In such a case we will
// continue until our ratchetting strategy overtakes the old
// transaction, or abort if the old one confirms.
default
:
log
.
Error
(
name
+
" unable to submit clearing tx"
,
"nonce"
,
nonce
,
"gasPrice"
,
gasPrice
,
"txHash"
,
txHash
,
"err"
,
err
)
return
nil
,
err
}
}
receipt
,
err
:=
txMgr
.
Send
(
ctx
,
sendTx
)
switch
{
// If the current context is canceled, a prior transaction in the mempool
// confirmed. The caller should retry, which will use the next nonce, before
// proceeding.
case
err
==
context
.
Canceled
:
log
.
Info
(
name
+
" transaction from previous restart confirmed, "
+
"proceeding to startup"
)
return
ErrClearPendingRetry
// Otherwise we were unable to confirm our transaction, this method should
// be retried by the caller.
case
err
!=
nil
:
log
.
Warn
(
name
+
" unable to send clearing tx"
,
"nonce"
,
nonce
,
"err"
,
err
)
return
err
// We succeeded in confirming a clearing transaction. Proceed to startup as
// normal.
default
:
log
.
Info
(
name
+
" cleared pending tx"
,
"nonce"
,
nonce
,
"txHash"
,
receipt
.
TxHash
)
return
nil
}
}
// SignClearingTx creates a signed clearing tranaction which sends 0 ETH back to
// the sender's address. EstimateGas is used to set an appropriate gas limit.
func
SignClearingTx
(
ctx
context
.
Context
,
walletAddr
common
.
Address
,
nonce
uint64
,
gasPrice
*
big
.
Int
,
l1Client
L1Client
,
privKey
*
ecdsa
.
PrivateKey
,
chainID
*
big
.
Int
,
)
(
*
types
.
Transaction
,
error
)
{
gasLimit
,
err
:=
l1Client
.
EstimateGas
(
ctx
,
ethereum
.
CallMsg
{
To
:
&
walletAddr
,
GasPrice
:
gasPrice
,
Value
:
nil
,
Data
:
nil
,
})
if
err
!=
nil
{
return
nil
,
err
}
tx
:=
CraftClearingTx
(
walletAddr
,
nonce
,
gasPrice
,
gasLimit
)
return
types
.
SignTx
(
tx
,
types
.
LatestSignerForChainID
(
chainID
),
privKey
,
)
}
// CraftClearingTx creates an unsigned clearing transaction which sends 0 ETH
// back to the sender's address.
func
CraftClearingTx
(
walletAddr
common
.
Address
,
nonce
uint64
,
gasPrice
*
big
.
Int
,
gasLimit
uint64
,
)
*
types
.
Transaction
{
return
types
.
NewTx
(
&
types
.
LegacyTx
{
To
:
&
walletAddr
,
Nonce
:
nonce
,
GasPrice
:
gasPrice
,
Gas
:
gasLimit
,
Value
:
nil
,
Data
:
nil
,
})
}
go/batch-submitter/drivers/clear_pending_tx_test.go
0 → 100644
View file @
a9870d6e
package
drivers_test
import
(
"context"
"crypto/ecdsa"
"errors"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/mock"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
func
init
()
{
privKey
,
err
:=
crypto
.
GenerateKey
()
if
err
!=
nil
{
panic
(
err
)
}
testPrivKey
=
privKey
testWalletAddr
=
crypto
.
PubkeyToAddress
(
privKey
.
PublicKey
)
testChainID
=
new
(
big
.
Int
)
.
SetUint64
(
1
)
testGasPrice
=
new
(
big
.
Int
)
.
SetUint64
(
3
)
}
var
(
testPrivKey
*
ecdsa
.
PrivateKey
testWalletAddr
common
.
Address
testChainID
*
big
.
Int
// 1
testNonce
=
uint64
(
2
)
testGasPrice
*
big
.
Int
// 3
testGasLimit
=
uint64
(
4
)
)
// TestCraftClearingTx asserts that CraftClearingTx produces the expected
// unsigned clearing transaction.
func
TestCraftClearingTx
(
t
*
testing
.
T
)
{
tx
:=
drivers
.
CraftClearingTx
(
testWalletAddr
,
testNonce
,
testGasPrice
,
testGasLimit
,
)
require
.
Equal
(
t
,
&
testWalletAddr
,
tx
.
To
())
require
.
Equal
(
t
,
testNonce
,
tx
.
Nonce
())
require
.
Equal
(
t
,
testGasPrice
,
tx
.
GasPrice
())
require
.
Equal
(
t
,
testGasLimit
,
tx
.
Gas
())
require
.
Equal
(
t
,
new
(
big
.
Int
),
tx
.
Value
())
require
.
Nil
(
t
,
tx
.
Data
())
}
// TestSignClearingTxSuccess asserts that we will sign a properly formed
// clearing transaction when the call to EstimateGas succeeds.
func
TestSignClearingTxEstimateGasSuccess
(
t
*
testing
.
T
)
{
l1Client
:=
mock
.
NewL1Client
(
mock
.
L1ClientConfig
{
EstimateGas
:
func
(
_
context
.
Context
,
_
ethereum
.
CallMsg
)
(
uint64
,
error
)
{
return
testGasLimit
,
nil
},
})
tx
,
err
:=
drivers
.
SignClearingTx
(
context
.
Background
(),
testWalletAddr
,
testNonce
,
testGasPrice
,
l1Client
,
testPrivKey
,
testChainID
,
)
require
.
Nil
(
t
,
err
)
require
.
NotNil
(
t
,
tx
)
require
.
Equal
(
t
,
&
testWalletAddr
,
tx
.
To
())
require
.
Equal
(
t
,
testNonce
,
tx
.
Nonce
())
require
.
Equal
(
t
,
testGasPrice
,
tx
.
GasPrice
())
require
.
Equal
(
t
,
testGasLimit
,
tx
.
Gas
())
require
.
Equal
(
t
,
new
(
big
.
Int
),
tx
.
Value
())
require
.
Nil
(
t
,
tx
.
Data
())
// Finally, ensure the sender is correct.
sender
,
err
:=
types
.
Sender
(
types
.
LatestSignerForChainID
(
testChainID
),
tx
)
require
.
Nil
(
t
,
err
)
require
.
Equal
(
t
,
testWalletAddr
,
sender
)
}
// TestSignClearingTxEstimateGasFail asserts that signing a clearing transaction
// will fail if the underlying call to EstimateGas fails.
func
TestSignClearingTxEstimateGasFail
(
t
*
testing
.
T
)
{
errEstimateGas
:=
errors
.
New
(
"estimate gas"
)
l1Client
:=
mock
.
NewL1Client
(
mock
.
L1ClientConfig
{
EstimateGas
:
func
(
_
context
.
Context
,
_
ethereum
.
CallMsg
)
(
uint64
,
error
)
{
return
0
,
errEstimateGas
},
})
tx
,
err
:=
drivers
.
SignClearingTx
(
context
.
Background
(),
testWalletAddr
,
testNonce
,
testGasPrice
,
l1Client
,
testPrivKey
,
testChainID
,
)
require
.
Equal
(
t
,
errEstimateGas
,
err
)
require
.
Nil
(
t
,
tx
)
}
type
clearPendingTxHarness
struct
{
l1Client
drivers
.
L1Client
txMgr
txmgr
.
TxManager
}
func
newClearPendingTxHarness
(
l1ClientConfig
mock
.
L1ClientConfig
)
*
clearPendingTxHarness
{
if
l1ClientConfig
.
NonceAt
==
nil
{
l1ClientConfig
.
NonceAt
=
func
(
_
context
.
Context
,
_
common
.
Address
,
_
*
big
.
Int
)
(
uint64
,
error
)
{
return
testNonce
,
nil
}
}
if
l1ClientConfig
.
EstimateGas
==
nil
{
l1ClientConfig
.
EstimateGas
=
func
(
_
context
.
Context
,
_
ethereum
.
CallMsg
)
(
uint64
,
error
)
{
return
testGasLimit
,
nil
}
}
l1Client
:=
mock
.
NewL1Client
(
l1ClientConfig
)
txMgr
:=
txmgr
.
NewSimpleTxManager
(
"test"
,
txmgr
.
Config
{
MinGasPrice
:
utils
.
GasPriceFromGwei
(
1
),
MaxGasPrice
:
utils
.
GasPriceFromGwei
(
100
),
GasRetryIncrement
:
utils
.
GasPriceFromGwei
(
5
),
ResubmissionTimeout
:
time
.
Second
,
ReceiptQueryInterval
:
50
*
time
.
Millisecond
,
},
l1Client
)
return
&
clearPendingTxHarness
{
l1Client
:
l1Client
,
txMgr
:
txMgr
,
}
}
// TestClearPendingTxClearingTxÇonfirms asserts the happy path where our
// clearing transactions confirms unobstructed.
func
TestClearPendingTxClearingTxConfirms
(
t
*
testing
.
T
)
{
h
:=
newClearPendingTxHarness
(
mock
.
L1ClientConfig
{
SendTransaction
:
func
(
_
context
.
Context
,
_
*
types
.
Transaction
)
error
{
return
nil
},
TransactionReceipt
:
func
(
_
context
.
Context
,
txHash
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
{
return
&
types
.
Receipt
{
TxHash
:
txHash
,
},
nil
},
})
err
:=
drivers
.
ClearPendingTx
(
"test"
,
context
.
Background
(),
h
.
txMgr
,
h
.
l1Client
,
testWalletAddr
,
testPrivKey
,
testChainID
,
)
require
.
Nil
(
t
,
err
)
}
// TestClearPendingTx∏reviousTxConfirms asserts that if the mempool starts
// rejecting our transactions because the nonce is too low that ClearPendingTx
// will abort continuing to publish a clearing transaction.
func
TestClearPendingTxPreviousTxConfirms
(
t
*
testing
.
T
)
{
h
:=
newClearPendingTxHarness
(
mock
.
L1ClientConfig
{
SendTransaction
:
func
(
_
context
.
Context
,
_
*
types
.
Transaction
)
error
{
return
core
.
ErrNonceTooLow
},
})
err
:=
drivers
.
ClearPendingTx
(
"test"
,
context
.
Background
(),
h
.
txMgr
,
h
.
l1Client
,
testWalletAddr
,
testPrivKey
,
testChainID
,
)
require
.
Equal
(
t
,
drivers
.
ErrClearPendingRetry
,
err
)
}
// TestClearPendingTxTimeout asserts that ClearPendingTx returns an
// ErrPublishTimeout if the clearing transaction fails to confirm in a timely
// manner and no prior transaction confirms.
func
TestClearPendingTxTimeout
(
t
*
testing
.
T
)
{
h
:=
newClearPendingTxHarness
(
mock
.
L1ClientConfig
{
SendTransaction
:
func
(
_
context
.
Context
,
_
*
types
.
Transaction
)
error
{
return
nil
},
TransactionReceipt
:
func
(
_
context
.
Context
,
txHash
common
.
Hash
)
(
*
types
.
Receipt
,
error
)
{
return
nil
,
nil
},
})
err
:=
drivers
.
ClearPendingTx
(
"test"
,
context
.
Background
(),
h
.
txMgr
,
h
.
l1Client
,
testWalletAddr
,
testPrivKey
,
testChainID
,
)
require
.
Equal
(
t
,
txmgr
.
ErrPublishTimeout
,
err
)
}
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