Commit d0c22cf5 authored by Joshua Gutow's avatar Joshua Gutow

txmgr: Flatten + simplify

This flattens out function definitions & removes some unused functions.
This also simplifies the configuration and emphasizes the SimpleTxManager.
parent ae9f35d9
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -72,8 +72,6 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitte ...@@ -72,8 +72,6 @@ func NewBatchSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*BatchSubmitte
} }
txManagerConfig := txmgr.Config{ txManagerConfig := txmgr.Config{
Log: l,
Name: "Batch Submitter",
ResubmissionTimeout: cfg.ResubmissionTimeout, ResubmissionTimeout: cfg.ResubmissionTimeout,
ReceiptQueryInterval: time.Second, ReceiptQueryInterval: time.Second,
NumConfirmations: cfg.NumConfirmations, NumConfirmations: cfg.NumConfirmations,
......
...@@ -37,7 +37,7 @@ func NewTransactionManager(log log.Logger, txMgrConfg txmgr.Config, batchInboxAd ...@@ -37,7 +37,7 @@ func NewTransactionManager(log log.Logger, txMgrConfg txmgr.Config, batchInboxAd
batchInboxAddress: batchInboxAddress, batchInboxAddress: batchInboxAddress,
senderAddress: senderAddress, senderAddress: senderAddress,
chainID: chainID, chainID: chainID,
txMgr: txmgr.NewSimpleTxManager("batcher", txMgrConfg, l1Client), txMgr: txmgr.NewSimpleTxManager("batcher", log, txMgrConfg, l1Client),
l1Client: l1Client, l1Client: l1Client,
signerFn: signerFn, signerFn: signerFn,
log: log, log: log,
......
...@@ -46,8 +46,6 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl ...@@ -46,8 +46,6 @@ func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Cl
L2OutputOracleAddr: cfg.OutputOracleAddr, L2OutputOracleAddr: cfg.OutputOracleAddr,
PollInterval: time.Second, PollInterval: time.Second,
TxManagerConfig: txmgr.Config{ TxManagerConfig: txmgr.Config{
Log: log,
Name: "action-proposer",
ResubmissionTimeout: 5 * time.Second, ResubmissionTimeout: 5 * time.Second,
ReceiptQueryInterval: time.Second, ReceiptQueryInterval: time.Second,
NumConfirmations: 1, NumConfirmations: 1,
......
...@@ -167,8 +167,6 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSu ...@@ -167,8 +167,6 @@ func NewL2OutputSubmitterFromCLIConfig(cfg CLIConfig, l log.Logger) (*L2OutputSu
} }
txMgrConfg := txmgr.Config{ txMgrConfg := txmgr.Config{
Log: l,
Name: "L2Output Submitter",
ResubmissionTimeout: cfg.ResubmissionTimeout, ResubmissionTimeout: cfg.ResubmissionTimeout,
ReceiptQueryInterval: time.Second, ReceiptQueryInterval: time.Second,
NumConfirmations: cfg.NumConfirmations, NumConfirmations: cfg.NumConfirmations,
...@@ -215,7 +213,7 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error) ...@@ -215,7 +213,7 @@ func NewL2OutputSubmitter(cfg Config, l log.Logger) (*L2OutputSubmitter, error)
rawL2ooContract := bind.NewBoundContract(cfg.L2OutputOracleAddr, parsed, cfg.L1Client, cfg.L1Client, cfg.L1Client) rawL2ooContract := bind.NewBoundContract(cfg.L2OutputOracleAddr, parsed, cfg.L1Client, cfg.L1Client, cfg.L1Client)
return &L2OutputSubmitter{ return &L2OutputSubmitter{
txMgr: txmgr.NewSimpleTxManager("proposer", cfg.TxManagerConfig, cfg.L1Client), txMgr: txmgr.NewSimpleTxManager("proposer", l, cfg.TxManagerConfig, cfg.L1Client),
done: make(chan struct{}), done: make(chan struct{}),
log: l, log: l,
ctx: ctx, ctx: ctx,
......
...@@ -4,16 +4,17 @@ go 1.18 ...@@ -4,16 +4,17 @@ go 1.18
require ( require (
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3
github.com/ethereum-optimism/optimism/op-node v0.10.12
github.com/ethereum-optimism/optimism/op-signer v0.1.0 github.com/ethereum-optimism/optimism/op-signer v0.1.0
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_golang v1.13.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
) )
require ( require (
github.com/VictoriaMetrics/fastcache v1.9.0 // indirect github.com/VictoriaMetrics/fastcache v1.10.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.23.3 // indirect github.com/btcsuite/btcd v0.23.3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
...@@ -25,14 +26,13 @@ require ( ...@@ -25,14 +26,13 @@ require (
github.com/deckarep/golang-set v1.8.0 // indirect github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dyson/certman v0.3.0 // indirect github.com/dyson/certman v0.3.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/fjl/memsize v0.0.1 // indirect github.com/fjl/memsize v0.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-kit/kit v0.10.0 // indirect github.com/go-kit/kit v0.10.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect github.com/go-stack/stack v1.8.1 // indirect
github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
...@@ -46,7 +46,7 @@ require ( ...@@ -46,7 +46,7 @@ require (
github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/kr/pretty v0.3.0 // indirect github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
...@@ -58,7 +58,7 @@ require ( ...@@ -58,7 +58,7 @@ require (
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/tsdb v0.10.0 // indirect github.com/prometheus/tsdb v0.10.0 // indirect
github.com/rivo/uniseg v0.2.1-0.20211004051800-57c86be7915a // indirect github.com/rivo/uniseg v0.3.4 // indirect
github.com/rjeczalik/notify v0.9.2 // indirect github.com/rjeczalik/notify v0.9.2 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/rs/cors v1.8.2 // indirect github.com/rs/cors v1.8.2 // indirect
...@@ -67,13 +67,13 @@ require ( ...@@ -67,13 +67,13 @@ require (
github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d // indirect github.com/status-im/keycard-go v0.0.0-20211109104530-b0e0482ba91d // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
......
This diff is collapsed.
...@@ -21,12 +21,6 @@ type SendTransactionFunc = func(ctx context.Context, tx *types.Transaction) erro ...@@ -21,12 +21,6 @@ type SendTransactionFunc = func(ctx context.Context, tx *types.Transaction) erro
// Config houses parameters for altering the behavior of a SimpleTxManager. // Config houses parameters for altering the behavior of a SimpleTxManager.
type Config struct { type Config struct {
// Log is a local logging instance.
Log log.Logger
// Name the name of the driver to appear in log lines.
Name string
// ResubmissionTimeout is the interval at which, if no previously // ResubmissionTimeout is the interval at which, if no previously
// published transaction has been mined, the new tx with a bumped gas // published transaction has been mined, the new tx with a bumped gas
// price will be published. Only one publication at MaxGasPrice will be // price will be published. Only one publication at MaxGasPrice will be
...@@ -57,11 +51,7 @@ type TxManager interface { ...@@ -57,11 +51,7 @@ type TxManager interface {
// prices). The method may be canceled using the passed context. // prices). The method may be canceled using the passed context.
// //
// NOTE: Send should be called by AT MOST one caller at a time. // NOTE: Send should be called by AT MOST one caller at a time.
Send( Send(ctx context.Context, updateGasPrice UpdateGasPriceFunc, sendTxn SendTransactionFunc) (*types.Receipt, error)
ctx context.Context,
updateGasPrice UpdateGasPriceFunc,
sendTxn SendTransactionFunc,
) (*types.Receipt, error)
} }
// ReceiptSource is a minimal function signature used to detect the confirmation // ReceiptSource is a minimal function signature used to detect the confirmation
...@@ -75,32 +65,30 @@ type ReceiptSource interface { ...@@ -75,32 +65,30 @@ type ReceiptSource interface {
// TransactionReceipt queries the backend for a receipt associated with // TransactionReceipt queries the backend for a receipt associated with
// txHash. If lookup does not fail, but the transaction is not found, // txHash. If lookup does not fail, but the transaction is not found,
// nil should be returned for both values. // nil should be returned for both values.
TransactionReceipt( TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
ctx context.Context, txHash common.Hash) (*types.Receipt, error)
} }
// SimpleTxManager is a implementation of TxManager that performs linear fee // SimpleTxManager is a implementation of TxManager that performs linear fee
// bumping of a tx until it confirms. // bumping of a tx until it confirms.
type SimpleTxManager struct { type SimpleTxManager struct {
Config // embed the config directly
name string name string
cfg Config
backend ReceiptSource backend ReceiptSource
l log.Logger l log.Logger
} }
// NewSimpleTxManager initializes a new SimpleTxManager with the passed Config. // NewSimpleTxManager initializes a new SimpleTxManager with the passed Config.
func NewSimpleTxManager( func NewSimpleTxManager(name string, l log.Logger, cfg Config, backend ReceiptSource) *SimpleTxManager {
name string, cfg Config, backend ReceiptSource) *SimpleTxManager {
if cfg.NumConfirmations == 0 { if cfg.NumConfirmations == 0 {
panic("txmgr: NumConfirmations cannot be zero") panic("txmgr: NumConfirmations cannot be zero")
} }
return &SimpleTxManager{ return &SimpleTxManager{
name: name, name: name,
cfg: cfg, Config: cfg,
backend: backend, backend: backend,
l: cfg.Log, l: l.New("service", name),
} }
} }
...@@ -110,13 +98,7 @@ func NewSimpleTxManager( ...@@ -110,13 +98,7 @@ func NewSimpleTxManager(
// may be canceled using the passed context. // may be canceled using the passed context.
// //
// NOTE: Send should be called by AT MOST one caller at a time. // NOTE: Send should be called by AT MOST one caller at a time.
func (m *SimpleTxManager) Send( func (m *SimpleTxManager) Send(ctx context.Context, updateGasPrice UpdateGasPriceFunc, sendTx SendTransactionFunc) (*types.Receipt, error) {
ctx context.Context,
updateGasPrice UpdateGasPriceFunc,
sendTx SendTransactionFunc,
) (*types.Receipt, error) {
name := m.name
// Initialize a wait group to track any spawned goroutines, and ensure // Initialize a wait group to track any spawned goroutines, and ensure
// we properly clean up any dangling resources this method generates. // we properly clean up any dangling resources this method generates.
...@@ -127,10 +109,10 @@ func (m *SimpleTxManager) Send( ...@@ -127,10 +109,10 @@ func (m *SimpleTxManager) Send(
// Initialize a subcontext for the goroutines spawned in this process. // Initialize a subcontext for the goroutines spawned in this process.
// The defer to cancel is done here (in reverse order of Wait) so that // The defer to cancel is done here (in reverse order of Wait) so that
// the goroutines can exit before blocking on the wait group. // the goroutines can exit before blocking on the wait group.
ctxc, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
sendState := NewSendState(m.cfg.SafeAbortNonceTooLowCount) sendState := NewSendState(m.SafeAbortNonceTooLowCount)
// Create a closure that will block on passed sendTx function in the // Create a closure that will block on passed sendTx function in the
// background, returning the first successfully mined receipt back to // background, returning the first successfully mined receipt back to
...@@ -139,13 +121,12 @@ func (m *SimpleTxManager) Send( ...@@ -139,13 +121,12 @@ func (m *SimpleTxManager) Send(
sendTxAsync := func() { sendTxAsync := func() {
defer wg.Done() defer wg.Done()
tx, err := updateGasPrice(ctxc) tx, err := updateGasPrice(ctx)
if err != nil { if err != nil {
if err == context.Canceled || if err == context.Canceled || strings.Contains(err.Error(), "context canceled") {
strings.Contains(err.Error(), "context canceled") {
return return
} }
m.l.Error(name+" unable to update txn gas price", "err", err) m.l.Error("unable to update txn gas price", "err", err)
return return
} }
...@@ -153,47 +134,40 @@ func (m *SimpleTxManager) Send( ...@@ -153,47 +134,40 @@ func (m *SimpleTxManager) Send(
nonce := tx.Nonce() nonce := tx.Nonce()
gasTipCap := tx.GasTipCap() gasTipCap := tx.GasTipCap()
gasFeeCap := tx.GasFeeCap() gasFeeCap := tx.GasFeeCap()
m.l.Info(name+" publishing transaction", "txHash", txHash, log := m.l.New("txHash", txHash, "nonce", nonce, "gasTipCap", gasTipCap, "gasFeeCap", gasFeeCap)
"nonce", nonce, "gasTipCap", gasTipCap, "gasFeeCap", gasFeeCap) log.Info("publishing transaction")
// Sign and publish transaction with current gas price. // Sign and publish transaction with current gas price.
err = sendTx(ctxc, tx) err = sendTx(ctx, tx)
sendState.ProcessSendError(err) sendState.ProcessSendError(err)
if err != nil { if err != nil {
if err == context.Canceled || if err == context.Canceled ||
strings.Contains(err.Error(), "context canceled") { strings.Contains(err.Error(), "context canceled") {
return return
} }
m.l.Error(name+" unable to publish transaction", "err", err) log.Error("unable to publish transaction", "err", err)
if sendState.ShouldAbortImmediately() { if sendState.ShouldAbortImmediately() {
log.Warn("Aborting transaction submission")
cancel() cancel()
} }
// TODO(conner): add retry? // TODO(conner): add retry?
return return
} }
m.l.Info(name+" transaction published successfully", "hash", txHash, log.Info("transaction published successfully")
"nonce", nonce, "gasTipCap", gasTipCap, "gasFeeCap", gasFeeCap)
// Wait for the transaction to be mined, reporting the receipt // Wait for the transaction to be mined, reporting the receipt
// back to the main event loop if found. // back to the main event loop if found.
receipt, err := waitMined( receipt, err := m.waitMined(ctx, tx, sendState)
m.l, ctxc, m.backend, tx, m.cfg.ReceiptQueryInterval,
m.cfg.NumConfirmations, sendState,
)
if err != nil { if err != nil {
m.l.Debug(name+" send tx failed", "hash", txHash, log.Debug("send tx failed", "err", err)
"nonce", nonce, "gasTipCap", gasTipCap, "gasFeeCap", gasFeeCap,
"err", err)
} }
if receipt != nil { if receipt != nil {
// Use non-blocking select to ensure function can exit // Use non-blocking select to ensure function can exit
// if more than one receipt is discovered. // if more than one receipt is discovered.
select { select {
case receiptChan <- receipt: case receiptChan <- receipt:
m.l.Trace(name+" send tx succeeded", "hash", txHash, log.Trace("send tx succeeded")
"nonce", nonce, "gasTipCap", gasTipCap,
"gasFeeCap", gasFeeCap)
default: default:
} }
} }
...@@ -205,7 +179,7 @@ func (m *SimpleTxManager) Send( ...@@ -205,7 +179,7 @@ func (m *SimpleTxManager) Send(
wg.Add(1) wg.Add(1)
go sendTxAsync() go sendTxAsync()
ticker := time.NewTicker(m.cfg.ResubmissionTimeout) ticker := time.NewTicker(m.ResubmissionTimeout)
defer ticker.Stop() defer ticker.Stop()
for { for {
...@@ -228,8 +202,8 @@ func (m *SimpleTxManager) Send( ...@@ -228,8 +202,8 @@ func (m *SimpleTxManager) Send(
// The passed context has been canceled, i.e. in the event of a // The passed context has been canceled, i.e. in the event of a
// shutdown. // shutdown.
case <-ctxc.Done(): case <-ctx.Done():
return nil, ctxc.Err() return nil, ctx.Err()
// The transaction has confirmed. // The transaction has confirmed.
case receipt := <-receiptChan: case receipt := <-receiptChan:
...@@ -238,39 +212,16 @@ func (m *SimpleTxManager) Send( ...@@ -238,39 +212,16 @@ func (m *SimpleTxManager) Send(
} }
} }
// WaitMined blocks until the backend indicates confirmation of tx and returns
// the tx receipt. Queries are made every queryInterval, regardless of whether
// the backend returns an error. This method can be canceled using the passed
// context.
func WaitMined(
ctx context.Context,
backend ReceiptSource,
tx *types.Transaction,
queryInterval time.Duration,
numConfirmations uint64,
) (*types.Receipt, error) {
return waitMined(log.New(), ctx, backend, tx, queryInterval, numConfirmations, nil)
}
// waitMined implements the core functionality of WaitMined, with the option to // waitMined implements the core functionality of WaitMined, with the option to
// pass in a SendState to record whether or not the transaction is mined. // pass in a SendState to record whether or not the transaction is mined.
func waitMined( func (m *SimpleTxManager) waitMined(ctx context.Context, tx *types.Transaction, sendState *SendState) (*types.Receipt, error) {
l log.Logger, queryTicker := time.NewTicker(m.ReceiptQueryInterval)
ctx context.Context,
backend ReceiptSource,
tx *types.Transaction,
queryInterval time.Duration,
numConfirmations uint64,
sendState *SendState,
) (*types.Receipt, error) {
queryTicker := time.NewTicker(queryInterval)
defer queryTicker.Stop() defer queryTicker.Stop()
txHash := tx.Hash() txHash := tx.Hash()
for { for {
receipt, err := backend.TransactionReceipt(ctx, txHash) receipt, err := m.backend.TransactionReceipt(ctx, txHash)
switch { switch {
case receipt != nil: case receipt != nil:
if sendState != nil { if sendState != nil {
...@@ -278,16 +229,14 @@ func waitMined( ...@@ -278,16 +229,14 @@ func waitMined(
} }
txHeight := receipt.BlockNumber.Uint64() txHeight := receipt.BlockNumber.Uint64()
tipHeight, err := backend.BlockNumber(ctx) tipHeight, err := m.backend.BlockNumber(ctx)
if err != nil { if err != nil {
l.Error("Unable to fetch block number", "err", err) m.l.Error("Unable to fetch block number", "err", err)
break break
} }
l.Trace("Transaction mined, checking confirmations", m.l.Trace("Transaction mined, checking confirmations", "txHash", txHash, "txHeight", txHeight,
"txHash", txHash, "txHeight", txHeight, "tipHeight", tipHeight, "numConfirmations", m.NumConfirmations)
"tipHeight", tipHeight,
"numConfirmations", numConfirmations)
// The transaction is considered confirmed when // The transaction is considered confirmed when
// txHeight+numConfirmations-1 <= tipHeight. Note that the -1 is // txHeight+numConfirmations-1 <= tipHeight. Note that the -1 is
...@@ -296,25 +245,23 @@ func waitMined( ...@@ -296,25 +245,23 @@ func waitMined(
// transaction should be confirmed when txHeight is equal to // transaction should be confirmed when txHeight is equal to
// tipHeight. The equation is rewritten in this form to avoid // tipHeight. The equation is rewritten in this form to avoid
// underflows. // underflows.
if txHeight+numConfirmations <= tipHeight+1 { if txHeight+m.NumConfirmations <= tipHeight+1 {
l.Info("Transaction confirmed", "txHash", txHash) m.l.Info("Transaction confirmed", "txHash", txHash)
return receipt, nil return receipt, nil
} }
// Safe to subtract since we know the LHS above is greater. // Safe to subtract since we know the LHS above is greater.
confsRemaining := (txHeight + numConfirmations) - (tipHeight + 1) confsRemaining := (txHeight + m.NumConfirmations) - (tipHeight + 1)
l.Info("Transaction not yet confirmed", "txHash", txHash, m.l.Info("Transaction not yet confirmed", "txHash", txHash, "confsRemaining", confsRemaining)
"confsRemaining", confsRemaining)
case err != nil: case err != nil:
l.Trace("Receipt retrievel failed", "hash", txHash, m.l.Trace("Receipt retrievel failed", "hash", txHash, "err", err)
"err", err)
default: default:
if sendState != nil { if sendState != nil {
sendState.TxNotMined(txHash) sendState.TxNotMined(txHash)
} }
l.Trace("Transaction not yet mined", "hash", txHash) m.l.Trace("Transaction not yet mined", "hash", txHash)
} }
select { select {
......
package txmgr_test package txmgr
import ( import (
"context" "context"
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"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"
...@@ -19,17 +19,17 @@ import ( ...@@ -19,17 +19,17 @@ import (
// testHarness houses the necessary resources to test the SimpleTxManager. // testHarness houses the necessary resources to test the SimpleTxManager.
type testHarness struct { type testHarness struct {
cfg txmgr.Config cfg Config
mgr txmgr.TxManager mgr *SimpleTxManager
backend *mockBackend backend *mockBackend
gasPricer *gasPricer gasPricer *gasPricer
} }
// newTestHarnessWithConfig initializes a testHarness with a specific // newTestHarnessWithConfig initializes a testHarness with a specific
// configuration. // configuration.
func newTestHarnessWithConfig(cfg txmgr.Config) *testHarness { func newTestHarnessWithConfig(t *testing.T, cfg Config) *testHarness {
backend := newMockBackend() backend := newMockBackend()
mgr := txmgr.NewSimpleTxManager("TEST", cfg, backend) mgr := NewSimpleTxManager("TEST", testlog.Logger(t, log.LvlCrit), cfg, backend)
return &testHarness{ return &testHarness{
cfg: cfg, cfg: cfg,
...@@ -41,13 +41,12 @@ func newTestHarnessWithConfig(cfg txmgr.Config) *testHarness { ...@@ -41,13 +41,12 @@ func newTestHarnessWithConfig(cfg txmgr.Config) *testHarness {
// newTestHarness initializes a testHarness with a default configuration that is // newTestHarness initializes a testHarness with a default configuration that is
// suitable for most tests. // suitable for most tests.
func newTestHarness() *testHarness { func newTestHarness(t *testing.T) *testHarness {
return newTestHarnessWithConfig(configWithNumConfs(1)) return newTestHarnessWithConfig(t, configWithNumConfs(1))
} }
func configWithNumConfs(numConfirmations uint64) txmgr.Config { func configWithNumConfs(numConfirmations uint64) Config {
return txmgr.Config{ return Config{
Log: log.New(),
ResubmissionTimeout: time.Second, ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: numConfirmations, NumConfirmations: numConfirmations,
...@@ -83,7 +82,7 @@ func (g *gasPricer) shouldMine(gasFeeCap *big.Int) bool { ...@@ -83,7 +82,7 @@ func (g *gasPricer) shouldMine(gasFeeCap *big.Int) bool {
func (g *gasPricer) feesForEpoch(epoch int64) (*big.Int, *big.Int) { func (g *gasPricer) feesForEpoch(epoch int64) (*big.Int, *big.Int) {
epochBaseFee := new(big.Int).Mul(g.baseBaseFee, big.NewInt(epoch)) epochBaseFee := new(big.Int).Mul(g.baseBaseFee, big.NewInt(epoch))
epochGasTipCap := new(big.Int).Mul(g.baseGasTipFee, big.NewInt(epoch)) epochGasTipCap := new(big.Int).Mul(g.baseGasTipFee, big.NewInt(epoch))
epochGasFeeCap := txmgr.CalcGasFeeCap(epochBaseFee, epochGasTipCap) epochGasFeeCap := CalcGasFeeCap(epochBaseFee, epochGasTipCap)
return epochGasTipCap, epochGasFeeCap return epochGasTipCap, epochGasFeeCap
} }
...@@ -103,7 +102,7 @@ type minedTxInfo struct { ...@@ -103,7 +102,7 @@ type minedTxInfo struct {
blockNumber uint64 blockNumber uint64
} }
// mockBackend implements txmgr.ReceiptSource that tracks mined transactions // mockBackend implements ReceiptSource that tracks mined transactions
// along with the gas price used. // along with the gas price used.
type mockBackend struct { type mockBackend struct {
mu sync.RWMutex mu sync.RWMutex
...@@ -177,7 +176,7 @@ func (b *mockBackend) TransactionReceipt( ...@@ -177,7 +176,7 @@ func (b *mockBackend) TransactionReceipt(
func TestTxMgrConfirmAtMinGasPrice(t *testing.T) { func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarness(t)
gasPricer := newGasPricer(1) gasPricer := newGasPricer(1)
...@@ -210,7 +209,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) { ...@@ -210,7 +209,7 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
func TestTxMgrNeverConfirmCancel(t *testing.T) { func TestTxMgrNeverConfirmCancel(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarness(t)
updateGasPrice := func(ctx context.Context) (*types.Transaction, error) { updateGasPrice := func(ctx context.Context) (*types.Transaction, error) {
gasTipCap, gasFeeCap := h.gasPricer.sample() gasTipCap, gasFeeCap := h.gasPricer.sample()
...@@ -238,7 +237,7 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) { ...@@ -238,7 +237,7 @@ func TestTxMgrNeverConfirmCancel(t *testing.T) {
func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) { func TestTxMgrConfirmsAtHigherGasPrice(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarness(t)
updateGasPrice := func(ctx context.Context) (*types.Transaction, error) { updateGasPrice := func(ctx context.Context) (*types.Transaction, error) {
gasTipCap, gasFeeCap := h.gasPricer.sample() gasTipCap, gasFeeCap := h.gasPricer.sample()
...@@ -268,11 +267,11 @@ var errRpcFailure = errors.New("rpc failure") ...@@ -268,11 +267,11 @@ var errRpcFailure = errors.New("rpc failure")
// TestTxMgrBlocksOnFailingRpcCalls asserts that if all of the publication // TestTxMgrBlocksOnFailingRpcCalls asserts that if all of the publication
// attempts fail due to rpc failures, that the tx manager will return // attempts fail due to rpc failures, that the tx manager will return
// txmgr.ErrPublishTimeout. // ErrPublishTimeout.
func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) { func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarness(t)
updateGasPrice := func(ctx context.Context) (*types.Transaction, error) { updateGasPrice := func(ctx context.Context) (*types.Transaction, error) {
gasTipCap, gasFeeCap := h.gasPricer.sample() gasTipCap, gasFeeCap := h.gasPricer.sample()
...@@ -300,7 +299,7 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) { ...@@ -300,7 +299,7 @@ func TestTxMgrBlocksOnFailingRpcCalls(t *testing.T) {
func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) { func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarness(t)
updateGasPrice := func(ctx context.Context) (*types.Transaction, error) { updateGasPrice := func(ctx context.Context) (*types.Transaction, error) {
gasTipCap, gasFeeCap := h.gasPricer.sample() gasTipCap, gasFeeCap := h.gasPricer.sample()
...@@ -335,7 +334,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) { ...@@ -335,7 +334,7 @@ func TestTxMgrOnlyOnePublicationSucceeds(t *testing.T) {
func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) { func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarness(t)
updateGasPrice := func(ctx context.Context) (*types.Transaction, error) { updateGasPrice := func(ctx context.Context) (*types.Transaction, error) {
gasTipCap, gasFeeCap := h.gasPricer.sample() gasTipCap, gasFeeCap := h.gasPricer.sample()
...@@ -367,7 +366,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) { ...@@ -367,7 +366,7 @@ func TestTxMgrConfirmsMinGasPriceAfterBumping(t *testing.T) {
func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) { func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarnessWithConfig(configWithNumConfs(2)) h := newTestHarnessWithConfig(t, configWithNumConfs(2))
updateGasPrice := func(ctx context.Context) (*types.Transaction, error) { updateGasPrice := func(ctx context.Context) (*types.Transaction, error) {
gasTipCap, gasFeeCap := h.gasPricer.sample() gasTipCap, gasFeeCap := h.gasPricer.sample()
...@@ -409,11 +408,11 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) { ...@@ -409,11 +408,11 @@ func TestTxMgrDoesntAbortNonceTooLowAfterMiningTx(t *testing.T) {
} }
// TestWaitMinedReturnsReceiptOnFirstSuccess insta-mines a transaction and // TestWaitMinedReturnsReceiptOnFirstSuccess insta-mines a transaction and
// asserts that WaitMined returns the appropriate receipt. // asserts that waitMined returns the appropriate receipt.
func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) { func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarnessWithConfig(t, configWithNumConfs(1))
// 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{})
...@@ -421,18 +420,18 @@ func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) { ...@@ -421,18 +420,18 @@ func TestWaitMinedReturnsReceiptOnFirstSuccess(t *testing.T) {
h.backend.mine(&txHash, new(big.Int)) h.backend.mine(&txHash, new(big.Int))
ctx := context.Background() ctx := context.Background()
receipt, err := txmgr.WaitMined(ctx, h.backend, tx, 50*time.Millisecond, 1) receipt, err := h.mgr.waitMined(ctx, tx, nil)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
require.Equal(t, receipt.TxHash, txHash) require.Equal(t, receipt.TxHash, txHash)
} }
// TestWaitMinedCanBeCanceled ensures that WaitMined exits of the passed context // TestWaitMinedCanBeCanceled ensures that waitMined exits of the passed context
// is canceled before a receipt is found. // is canceled before a receipt is found.
func TestWaitMinedCanBeCanceled(t *testing.T) { func TestWaitMinedCanBeCanceled(t *testing.T) {
t.Parallel() t.Parallel()
h := newTestHarness() h := newTestHarness(t)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
...@@ -440,20 +439,20 @@ func TestWaitMinedCanBeCanceled(t *testing.T) { ...@@ -440,20 +439,20 @@ func TestWaitMinedCanBeCanceled(t *testing.T) {
// Create an unimined tx. // Create an unimined tx.
tx := types.NewTx(&types.LegacyTx{}) tx := types.NewTx(&types.LegacyTx{})
receipt, err := txmgr.WaitMined(ctx, h.backend, tx, 50*time.Millisecond, 1) receipt, err := h.mgr.waitMined(ctx, tx, nil)
require.Equal(t, err, context.DeadlineExceeded) require.Equal(t, err, context.DeadlineExceeded)
require.Nil(t, receipt) require.Nil(t, receipt)
} }
// TestWaitMinedMultipleConfs asserts that WaitMiend will properly wait for more // TestWaitMinedMultipleConfs asserts that waitMined will properly wait for more
// than one confirmation. // than one confirmation.
func TestWaitMinedMultipleConfs(t *testing.T) { func TestWaitMinedMultipleConfs(t *testing.T) {
t.Parallel() t.Parallel()
const numConfs = 2 const numConfs = 2
h := newTestHarnessWithConfig(configWithNumConfs(numConfs)) h := newTestHarnessWithConfig(t, configWithNumConfs(numConfs))
ctxt, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
// Create an unimined tx. // Create an unimined tx.
...@@ -461,16 +460,16 @@ func TestWaitMinedMultipleConfs(t *testing.T) { ...@@ -461,16 +460,16 @@ func TestWaitMinedMultipleConfs(t *testing.T) {
txHash := tx.Hash() txHash := tx.Hash()
h.backend.mine(&txHash, new(big.Int)) h.backend.mine(&txHash, new(big.Int))
receipt, err := txmgr.WaitMined(ctxt, h.backend, tx, 50*time.Millisecond, numConfs) receipt, err := h.mgr.waitMined(ctx, tx, nil)
require.Equal(t, err, context.DeadlineExceeded) require.Equal(t, err, context.DeadlineExceeded)
require.Nil(t, receipt) require.Nil(t, receipt)
ctxt, cancel = context.WithTimeout(context.Background(), time.Second) ctx, cancel = context.WithTimeout(context.Background(), time.Second)
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)
receipt, err = txmgr.WaitMined(ctxt, h.backend, tx, 50*time.Millisecond, numConfs) receipt, err = h.mgr.waitMined(ctx, tx, nil)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
require.Equal(t, txHash, receipt.TxHash) require.Equal(t, txHash, receipt.TxHash)
...@@ -487,10 +486,10 @@ func TestManagerPanicOnZeroConfs(t *testing.T) { ...@@ -487,10 +486,10 @@ func TestManagerPanicOnZeroConfs(t *testing.T) {
} }
}() }()
_ = newTestHarnessWithConfig(configWithNumConfs(0)) _ = newTestHarnessWithConfig(t, configWithNumConfs(0))
} }
// failingBackend implements txmgr.ReceiptSource, returning a failure on the // failingBackend implements ReceiptSource, returning a failure on the
// first call but a success on the second call. This allows us to test that the // first call but a success on the second call. This allows us to test that the
// inner loop of WaitMined properly handles this case. // inner loop of WaitMined properly handles this case.
type failingBackend struct { type failingBackend struct {
...@@ -533,13 +532,25 @@ func TestWaitMinedReturnsReceiptAfterFailure(t *testing.T) { ...@@ -533,13 +532,25 @@ func TestWaitMinedReturnsReceiptAfterFailure(t *testing.T) {
var borkedBackend failingBackend var borkedBackend failingBackend
mgr := &SimpleTxManager{
Config: Config{
ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3,
},
name: "TEST",
backend: &borkedBackend,
l: testlog.Logger(t, log.LvlCrit),
}
// Don't mine the tx with the default backend. The failingBackend will // Don't mine the tx with the default backend. The failingBackend will
// return the txHash on the second call. // return the txHash on the second call.
tx := types.NewTx(&types.LegacyTx{}) tx := types.NewTx(&types.LegacyTx{})
txHash := tx.Hash() txHash := tx.Hash()
ctx := context.Background() ctx := context.Background()
receipt, err := txmgr.WaitMined(ctx, &borkedBackend, tx, 50*time.Millisecond, 1) receipt, err := mgr.waitMined(ctx, tx, nil)
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, receipt) require.NotNil(t, receipt)
require.Equal(t, receipt.TxHash, txHash) require.Equal(t, receipt.TxHash, txHash)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment