clear_pending_tx_test.go 8.65 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
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/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)
}

var (
31 32 33 34
	testPrivKey     *ecdsa.PrivateKey
	testWalletAddr  common.Address
	testChainID     = big.NewInt(1)
	testNonce       = uint64(2)
35 36
	testGasFeeCap   = big.NewInt(3)
	testGasTipCap   = big.NewInt(4)
37
	testBlockNumber = uint64(5)
38
	testBaseFee     = big.NewInt(6)
39 40 41 42 43 44
)

// TestCraftClearingTx asserts that CraftClearingTx produces the expected
// unsigned clearing transaction.
func TestCraftClearingTx(t *testing.T) {
	tx := drivers.CraftClearingTx(
45
		testWalletAddr, testNonce, testGasFeeCap, testGasTipCap,
46 47 48
	)
	require.Equal(t, &testWalletAddr, tx.To())
	require.Equal(t, testNonce, tx.Nonce())
49 50
	require.Equal(t, testGasFeeCap, tx.GasFeeCap())
	require.Equal(t, testGasTipCap, tx.GasTipCap())
51 52 53 54 55 56 57 58
	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{
59 60 61 62 63 64 65
		HeaderByNumber: func(_ context.Context, _ *big.Int) (*types.Header, error) {
			return &types.Header{
				BaseFee: testBaseFee,
			}, nil
		},
		SuggestGasTipCap: func(_ context.Context) (*big.Int, error) {
			return testGasTipCap, nil
66 67 68
		},
	})

69 70 71 72 73
	expGasFeeCap := new(big.Int).Add(
		testGasTipCap,
		new(big.Int).Mul(testBaseFee, big.NewInt(2)),
	)

74
	tx, err := drivers.SignClearingTx(
75 76
		"TEST", context.Background(), testWalletAddr, testNonce, l1Client,
		testPrivKey, testChainID,
77 78 79 80 81
	)
	require.Nil(t, err)
	require.NotNil(t, tx)
	require.Equal(t, &testWalletAddr, tx.To())
	require.Equal(t, testNonce, tx.Nonce())
82 83
	require.Equal(t, expGasFeeCap, tx.GasFeeCap())
	require.Equal(t, testGasTipCap, tx.GasTipCap())
84 85 86 87 88 89 90 91 92
	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)
}

93 94 95 96
// TestSignClearingTxSuggestGasTipCapFail asserts that signing a clearing
// transaction will fail if the underlying call to SuggestGasTipCap fails.
func TestSignClearingTxSuggestGasTipCapFail(t *testing.T) {
	errSuggestGasTipCap := errors.New("suggest gas tip cap")
97 98

	l1Client := mock.NewL1Client(mock.L1ClientConfig{
99 100
		SuggestGasTipCap: func(_ context.Context) (*big.Int, error) {
			return nil, errSuggestGasTipCap
101 102 103 104
		},
	})

	tx, err := drivers.SignClearingTx(
105 106
		"TEST", context.Background(), testWalletAddr, testNonce, l1Client,
		testPrivKey, testChainID,
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
	)
	require.Equal(t, errSuggestGasTipCap, err)
	require.Nil(t, tx)
}

// TestSignClearingTxHeaderByNumberFail asserts that signing a clearing
// transaction will fail if the underlying call to HeaderByNumber fails.
func TestSignClearingTxHeaderByNumberFail(t *testing.T) {
	errHeaderByNumber := errors.New("header by number")

	l1Client := mock.NewL1Client(mock.L1ClientConfig{
		HeaderByNumber: func(_ context.Context, _ *big.Int) (*types.Header, error) {
			return nil, errHeaderByNumber
		},
		SuggestGasTipCap: func(_ context.Context) (*big.Int, error) {
			return testGasTipCap, nil
		},
	})

	tx, err := drivers.SignClearingTx(
127 128
		"TEST", context.Background(), testWalletAddr, testNonce, l1Client,
		testPrivKey, testChainID,
129
	)
130
	require.Equal(t, errHeaderByNumber, err)
131 132 133 134
	require.Nil(t, tx)
}

type clearPendingTxHarness struct {
135
	l1Client *mock.L1Client
136 137 138
	txMgr    txmgr.TxManager
}

139 140 141 142
func newClearPendingTxHarnessWithNumConfs(
	l1ClientConfig mock.L1ClientConfig,
	numConfirmations uint64,
) *clearPendingTxHarness {
143 144 145 146 147 148

	if l1ClientConfig.BlockNumber == nil {
		l1ClientConfig.BlockNumber = func(_ context.Context) (uint64, error) {
			return testBlockNumber, nil
		}
	}
149 150 151 152 153 154 155
	if l1ClientConfig.HeaderByNumber == nil {
		l1ClientConfig.HeaderByNumber = func(_ context.Context, _ *big.Int) (*types.Header, error) {
			return &types.Header{
				BaseFee: testBaseFee,
			}, nil
		}
	}
156 157 158 159 160
	if l1ClientConfig.NonceAt == nil {
		l1ClientConfig.NonceAt = func(_ context.Context, _ common.Address, _ *big.Int) (uint64, error) {
			return testNonce, nil
		}
	}
161 162 163
	if l1ClientConfig.SuggestGasTipCap == nil {
		l1ClientConfig.SuggestGasTipCap = func(_ context.Context) (*big.Int, error) {
			return testGasTipCap, nil
164 165 166 167 168 169 170
		}
	}

	l1Client := mock.NewL1Client(l1ClientConfig)
	txMgr := txmgr.NewSimpleTxManager("test", txmgr.Config{
		ResubmissionTimeout:  time.Second,
		ReceiptQueryInterval: 50 * time.Millisecond,
171
		NumConfirmations:     numConfirmations,
172 173 174 175 176 177 178 179
	}, l1Client)

	return &clearPendingTxHarness{
		l1Client: l1Client,
		txMgr:    txMgr,
	}
}

180 181 182 183
func newClearPendingTxHarness(l1ClientConfig mock.L1ClientConfig) *clearPendingTxHarness {
	return newClearPendingTxHarnessWithNumConfs(l1ClientConfig, 1)
}

184 185 186 187 188 189 190 191 192
// 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{
193 194
				TxHash:      txHash,
				BlockNumber: big.NewInt(int64(testBlockNumber)),
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
			}, 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
		},
	})

236 237 238
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

239
	err := drivers.ClearPendingTx(
240 241
		"test", ctx, h.txMgr, h.l1Client, testWalletAddr, testPrivKey,
		testChainID,
242
	)
243
	require.Equal(t, context.DeadlineExceeded, err)
244
}
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263

// TestClearPendingTxMultipleConfs tests we wait the appropriate number of
// confirmations for the clearing transaction to confirm.
func TestClearPendingTxMultipleConfs(t *testing.T) {
	const numConfs = 2

	// Instantly confirm transaction.
	h := newClearPendingTxHarnessWithNumConfs(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,
				BlockNumber: big.NewInt(int64(testBlockNumber)),
			}, nil
		},
	}, numConfs)

264 265 266
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

267 268
	// The txmgr should timeout waiting for the txn to confirm.
	err := drivers.ClearPendingTx(
269 270
		"test", ctx, h.txMgr, h.l1Client, testWalletAddr, testPrivKey,
		testChainID,
271
	)
272
	require.Equal(t, context.DeadlineExceeded, err)
273 274 275 276 277 278 279 280 281 282 283 284 285 286

	// Now set the chain height to the earliest the transaction will be
	// considered sufficiently confirmed.
	h.l1Client.SetBlockNumberFunc(func(_ context.Context) (uint64, error) {
		return testBlockNumber + numConfs - 1, nil
	})

	// Publishing should succeed.
	err = drivers.ClearPendingTx(
		"test", context.Background(), h.txMgr, h.l1Client, testWalletAddr,
		testPrivKey, testChainID,
	)
	require.Nil(t, err)
}