Commit 4bc2c019 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #2338 from cfromknecht/bss-core-txmgr-error-revert

feat: txmgr return ErrReverted if confirmed tx reverts
parents e8aa162c 6856b215
---
'@eth-optimism/batch-submitter-service': patch
'@eth-optimism/teleportr': patch
---
Count reverted transactions in failed_submissions
...@@ -231,6 +231,7 @@ func TestClearPendingTxClearingTxConfirms(t *testing.T) { ...@@ -231,6 +231,7 @@ func TestClearPendingTxClearingTxConfirms(t *testing.T) {
return &types.Receipt{ return &types.Receipt{
TxHash: txHash, TxHash: txHash,
BlockNumber: big.NewInt(int64(testBlockNumber)), BlockNumber: big.NewInt(int64(testBlockNumber)),
Status: types.ReceiptStatusSuccessful,
}, nil }, nil
}, },
}) })
...@@ -296,6 +297,7 @@ func TestClearPendingTxMultipleConfs(t *testing.T) { ...@@ -296,6 +297,7 @@ func TestClearPendingTxMultipleConfs(t *testing.T) {
return &types.Receipt{ return &types.Receipt{
TxHash: txHash, TxHash: txHash,
BlockNumber: big.NewInt(int64(testBlockNumber)), BlockNumber: big.NewInt(int64(testBlockNumber)),
Status: types.ReceiptStatusSuccessful,
}, nil }, nil
}, },
}, numConfs) }, numConfs)
......
...@@ -215,6 +215,18 @@ func (s *Service) eventLoop() { ...@@ -215,6 +215,18 @@ func (s *Service) eventLoop() {
receipt, err := s.txMgr.Send( receipt, err := s.txMgr.Send(
s.ctx, updateGasPrice, s.cfg.Driver.SendTransaction, s.ctx, updateGasPrice, s.cfg.Driver.SendTransaction,
) )
// Record the confirmation time and gas used if we receive a
// receipt, as this indicates the transaction confirmed. We record
// these metrics here as the transaction may have reverted, and will
// abort below.
if receipt != nil {
batchConfirmationTime := time.Since(batchConfirmationStart) /
time.Millisecond
s.metrics.BatchConfirmationTimeMs().Set(float64(batchConfirmationTime))
s.metrics.SubmissionGasUsedWei().Set(float64(receipt.GasUsed))
}
if err != nil { if err != nil {
log.Error(name+" unable to publish batch tx", log.Error(name+" unable to publish batch tx",
"err", err) "err", err)
...@@ -225,11 +237,7 @@ func (s *Service) eventLoop() { ...@@ -225,11 +237,7 @@ func (s *Service) eventLoop() {
// The transaction was successfully submitted. // The transaction was successfully submitted.
log.Info(name+" batch tx successfully published", log.Info(name+" batch tx successfully published",
"tx_hash", receipt.TxHash) "tx_hash", receipt.TxHash)
batchConfirmationTime := time.Since(batchConfirmationStart) /
time.Millisecond
s.metrics.BatchConfirmationTimeMs().Set(float64(batchConfirmationTime))
s.metrics.BatchesSubmitted().Inc() s.metrics.BatchesSubmitted().Inc()
s.metrics.SubmissionGasUsedWei().Set(float64(receipt.GasUsed))
s.metrics.SubmissionTimestamp().Set(float64(time.Now().UnixNano() / 1e6)) s.metrics.SubmissionTimestamp().Set(float64(time.Now().UnixNano() / 1e6))
case err := <-s.ctx.Done(): case err := <-s.ctx.Done():
......
...@@ -2,6 +2,7 @@ package txmgr ...@@ -2,6 +2,7 @@ package txmgr
import ( import (
"context" "context"
"errors"
"math/big" "math/big"
"strings" "strings"
"sync" "sync"
...@@ -12,6 +13,9 @@ import ( ...@@ -12,6 +13,9 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// ErrReverted signals that a mined transaction reverted.
var ErrReverted = errors.New("transaction reverted")
// UpdateGasPriceSendTxFunc defines a function signature for publishing a // UpdateGasPriceSendTxFunc defines a function signature for publishing a
// desired tx with a specific gas price. Implementations of this signature // desired tx with a specific gas price. Implementations of this signature
// should also return promptly when the context is canceled. // should also return promptly when the context is canceled.
...@@ -225,6 +229,9 @@ func (m *SimpleTxManager) Send( ...@@ -225,6 +229,9 @@ func (m *SimpleTxManager) Send(
// The transaction has confirmed. // The transaction has confirmed.
case receipt := <-receiptChan: case receipt := <-receiptChan:
if receipt.Status == types.ReceiptStatusFailed {
return receipt, ErrReverted
}
return receipt, nil return receipt, nil
} }
} }
...@@ -288,7 +295,10 @@ func waitMined( ...@@ -288,7 +295,10 @@ func waitMined(
// 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+numConfirmations <= tipHeight+1 {
log.Info("Transaction confirmed", "txHash", txHash) reverted := receipt.Status == types.ReceiptStatusFailed
log.Info("Transaction confirmed",
"txHash", txHash,
"reverted", reverted)
return receipt, nil return receipt, nil
} }
......
...@@ -98,6 +98,7 @@ func (g *gasPricer) sample() (*big.Int, *big.Int) { ...@@ -98,6 +98,7 @@ func (g *gasPricer) sample() (*big.Int, *big.Int) {
type minedTxInfo struct { type minedTxInfo struct {
gasFeeCap *big.Int gasFeeCap *big.Int
blockNumber uint64 blockNumber uint64
reverted bool
} }
// mockBackend implements txmgr.ReceiptSource that tracks mined transactions // mockBackend implements txmgr.ReceiptSource that tracks mined transactions
...@@ -123,6 +124,20 @@ func newMockBackend() *mockBackend { ...@@ -123,6 +124,20 @@ func newMockBackend() *mockBackend {
// TransactionReceipt with a matching txHash will result in a non-nil receipt. // TransactionReceipt with a matching txHash will result in a non-nil receipt.
// If a nil txHash is supplied this has the effect of mining an empty block. // If a nil txHash is supplied this has the effect of mining an empty block.
func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) { func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) {
b.mineWithStatus(txHash, gasFeeCap, false)
}
// mineWithStatus records a (txHash, gasFeeCap) pair as confirmed, but also
// includes the option to specify whether or not the transaction reverted.
// 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) mineWithStatus(
txHash *common.Hash,
gasFeeCap *big.Int,
revert bool,
) {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
...@@ -131,6 +146,7 @@ func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) { ...@@ -131,6 +146,7 @@ func (b *mockBackend) mine(txHash *common.Hash, gasFeeCap *big.Int) {
b.minedTxs[*txHash] = minedTxInfo{ b.minedTxs[*txHash] = minedTxInfo{
gasFeeCap: gasFeeCap, gasFeeCap: gasFeeCap,
blockNumber: b.blockHeight, blockNumber: b.blockHeight,
reverted: revert,
} }
} }
} }
...@@ -160,12 +176,18 @@ func (b *mockBackend) TransactionReceipt( ...@@ -160,12 +176,18 @@ func (b *mockBackend) TransactionReceipt(
return nil, nil return nil, nil
} }
var status = types.ReceiptStatusSuccessful
if txInfo.reverted {
status = types.ReceiptStatusFailed
}
// Return the gas fee cap for the transaction in the GasUsed field so that // Return the gas fee cap for the transaction in the GasUsed field so that
// we can assert the proper tx confirmed in our tests. // we can assert the proper tx confirmed in our tests.
return &types.Receipt{ return &types.Receipt{
TxHash: txHash, TxHash: txHash,
GasUsed: txInfo.gasFeeCap.Uint64(), GasUsed: txInfo.gasFeeCap.Uint64(),
BlockNumber: big.NewInt(int64(txInfo.blockNumber)), BlockNumber: big.NewInt(int64(txInfo.blockNumber)),
Status: status,
}, nil }, nil
} }
...@@ -201,6 +223,39 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) { ...@@ -201,6 +223,39 @@ func TestTxMgrConfirmAtMinGasPrice(t *testing.T) {
require.Equal(t, gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed) require.Equal(t, gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed)
} }
// TestTxMgrFailsForRevertedTxn asserts that Send returns ErrReverted if the
// confirmed transaction reverts during execution, and returns the resulting
// receipt.
func TestTxMgrFailsForRevertedTxn(t *testing.T) {
t.Parallel()
h := newTestHarness()
gasPricer := newGasPricer(1)
updateGasPrice := func(ctx context.Context) (*types.Transaction, error) {
gasTipCap, gasFeeCap := gasPricer.sample()
return types.NewTx(&types.DynamicFeeTx{
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
}), nil
}
sendTx := func(ctx context.Context, tx *types.Transaction) error {
if gasPricer.shouldMine(tx.GasFeeCap()) {
txHash := tx.Hash()
h.backend.mineWithStatus(&txHash, tx.GasFeeCap(), true)
}
return nil
}
ctx := context.Background()
receipt, err := h.mgr.Send(ctx, updateGasPrice, sendTx)
require.Equal(t, txmgr.ErrReverted, err)
require.NotNil(t, receipt)
require.Equal(t, gasPricer.expGasFeeCap().Uint64(), receipt.GasUsed)
}
// TestTxMgrNeverConfirmCancel asserts that a Send can be canceled even if no // TestTxMgrNeverConfirmCancel asserts that a Send can be canceled even if no
// transaction is mined. This is done to ensure the the tx mgr can properly // transaction is mined. This is done to ensure the the tx mgr can properly
// abort on shutdown, even if a txn is in the process of being published. // abort on shutdown, even if a txn is in the process of being published.
...@@ -519,6 +574,7 @@ func (b *failingBackend) TransactionReceipt( ...@@ -519,6 +574,7 @@ func (b *failingBackend) TransactionReceipt(
return &types.Receipt{ return &types.Receipt{
TxHash: txHash, TxHash: txHash,
BlockNumber: big.NewInt(1), BlockNumber: big.NewInt(1),
Status: types.ReceiptStatusSuccessful,
}, nil }, nil
} }
......
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