Commit f0c3f937 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge branch 'develop' into fix/go-bss-go-version

parents 5905f3dc dc1ed3c9
---
'@eth-optimism/integration-tests': patch
---
Add in berlin hardfork tests
---
'@eth-optimism/l2geth': patch
---
Implement berlin hardfork
---
'@eth-optimism/batch-submitter-service': patch
---
use EIP-1559 txns for tx/state batches
---
'@eth-optimism/contracts': patch
---
Add berlin hardfork config to genesis creation
...@@ -65,7 +65,7 @@ jobs: ...@@ -65,7 +65,7 @@ jobs:
run: yarn changeset version --snapshot run: yarn changeset version --snapshot
- name: Publish To NPM - name: Publish To NPM
uses: changesets/action@master uses: changesets/action@v1
id: changesets id: changesets
with: with:
publish: yarn changeset publish --tag canary publish: yarn changeset publish --tag canary
......
...@@ -55,7 +55,7 @@ jobs: ...@@ -55,7 +55,7 @@ jobs:
run: yarn run: yarn
- name: Publish To NPM or Create Release Pull Request - name: Publish To NPM or Create Release Pull Request
uses: changesets/action@master uses: changesets/action@v1
id: changesets id: changesets
with: with:
publish: yarn release publish: yarn release
...@@ -101,14 +101,6 @@ jobs: ...@@ -101,14 +101,6 @@ jobs:
push: true push: true
tags: ethereumoptimism/l2geth:${{ needs.release.outputs.l2geth }},ethereumoptimism/l2geth:latest tags: ethereumoptimism/l2geth:${{ needs.release.outputs.l2geth }},ethereumoptimism/l2geth:latest
- name: Publish rpc-proxy
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.rpc-proxy
push: true
tags: ethereumoptimism/rpc-proxy:${{ needs.release.outputs.l2geth }},ethereumoptimism/rpc-proxy:latest
gas-oracle: gas-oracle:
name: Publish Gas Oracle Version ${{ needs.release.outputs.gas-oracle }} name: Publish Gas Oracle Version ${{ needs.release.outputs.gas-oracle }}
needs: release needs: release
......
...@@ -15,7 +15,6 @@ import ( ...@@ -15,7 +15,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/proposer" "github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/proposer"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/sequencer" "github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/sequencer"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
"github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
l2rpc "github.com/ethereum-optimism/optimism/l2geth/rpc" l2rpc "github.com/ethereum-optimism/optimism/l2geth/rpc"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -163,9 +162,6 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) { ...@@ -163,9 +162,6 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
} }
txManagerConfig := txmgr.Config{ txManagerConfig := txmgr.Config{
MinGasPrice: utils.GasPriceFromGwei(1),
MaxGasPrice: utils.GasPriceFromGwei(cfg.MaxGasPriceInGwei),
GasRetryIncrement: utils.GasPriceFromGwei(cfg.GasRetryIncrement),
ResubmissionTimeout: cfg.ResubmissionTimeout, ResubmissionTimeout: cfg.ResubmissionTimeout,
ReceiptQueryInterval: time.Second, ReceiptQueryInterval: time.Second,
NumConfirmations: cfg.NumConfirmations, NumConfirmations: cfg.NumConfirmations,
......
...@@ -133,14 +133,6 @@ type Config struct { ...@@ -133,14 +133,6 @@ type Config struct {
// blocks. // blocks.
BlockOffset uint64 BlockOffset uint64
// MaxGasPriceInGwei is the maximum gas price in gwei we will allow in order
// to confirm a transaction.
MaxGasPriceInGwei uint64
// GasRetryIncrement is the step size (in gwei) by which we will ratchet the
// gas price in order to get a transaction confirmed.
GasRetryIncrement uint64
// SequencerPrivateKey the private key of the wallet used to submit // SequencerPrivateKey the private key of the wallet used to submit
// transactions to the CTC contract. // transactions to the CTC contract.
SequencerPrivateKey string SequencerPrivateKey string
...@@ -202,8 +194,6 @@ func NewConfig(ctx *cli.Context) (Config, error) { ...@@ -202,8 +194,6 @@ func NewConfig(ctx *cli.Context) (Config, error) {
SentryDsn: ctx.GlobalString(flags.SentryDsnFlag.Name), SentryDsn: ctx.GlobalString(flags.SentryDsnFlag.Name),
SentryTraceRate: ctx.GlobalDuration(flags.SentryTraceRateFlag.Name), SentryTraceRate: ctx.GlobalDuration(flags.SentryTraceRateFlag.Name),
BlockOffset: ctx.GlobalUint64(flags.BlockOffsetFlag.Name), BlockOffset: ctx.GlobalUint64(flags.BlockOffsetFlag.Name),
MaxGasPriceInGwei: ctx.GlobalUint64(flags.MaxGasPriceInGweiFlag.Name),
GasRetryIncrement: ctx.GlobalUint64(flags.GasRetryIncrementFlag.Name),
SequencerPrivateKey: ctx.GlobalString(flags.SequencerPrivateKeyFlag.Name), SequencerPrivateKey: ctx.GlobalString(flags.SequencerPrivateKeyFlag.Name),
ProposerPrivateKey: ctx.GlobalString(flags.ProposerPrivateKeyFlag.Name), ProposerPrivateKey: ctx.GlobalString(flags.ProposerPrivateKeyFlag.Name),
Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name), Mnemonic: ctx.GlobalString(flags.MnemonicFlag.Name),
......
...@@ -8,7 +8,6 @@ import ( ...@@ -8,7 +8,6 @@ import (
"strings" "strings"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "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/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"
...@@ -50,20 +49,20 @@ func ClearPendingTx( ...@@ -50,20 +49,20 @@ func ClearPendingTx(
// price. // price.
sendTx := func( sendTx := func(
ctx context.Context, ctx context.Context,
gasPrice *big.Int,
) (*types.Transaction, error) { ) (*types.Transaction, error) {
log.Info(name+" clearing pending tx", "nonce", nonce, log.Info(name+" clearing pending tx", "nonce", nonce)
"gasPrice", gasPrice)
signedTx, err := SignClearingTx( signedTx, err := SignClearingTx(
ctx, walletAddr, nonce, gasPrice, l1Client, privKey, chainID, name, ctx, walletAddr, nonce, l1Client, privKey, chainID,
) )
if err != nil { if err != nil {
log.Error(name+" unable to sign clearing tx", "nonce", nonce, log.Error(name+" unable to sign clearing tx", "nonce", nonce,
"gasPrice", gasPrice, "err", err) "err", err)
return nil, err return nil, err
} }
txHash := signedTx.Hash() txHash := signedTx.Hash()
gasTipCap := signedTx.GasTipCap()
gasFeeCap := signedTx.GasFeeCap()
err = l1Client.SendTransaction(ctx, signedTx) err = l1Client.SendTransaction(ctx, signedTx)
switch { switch {
...@@ -71,7 +70,8 @@ func ClearPendingTx( ...@@ -71,7 +70,8 @@ func ClearPendingTx(
// Clearing transaction successfully confirmed. // Clearing transaction successfully confirmed.
case err == nil: case err == nil:
log.Info(name+" submitted clearing tx", "nonce", nonce, log.Info(name+" submitted clearing tx", "nonce", nonce,
"gasPrice", gasPrice, "txHash", txHash) "gasTipCap", gasTipCap, "gasFeeCap", gasFeeCap,
"txHash", txHash)
return signedTx, nil return signedTx, nil
...@@ -91,8 +91,8 @@ func ClearPendingTx( ...@@ -91,8 +91,8 @@ func ClearPendingTx(
// transaction, or abort if the old one confirms. // transaction, or abort if the old one confirms.
default: default:
log.Error(name+" unable to submit clearing tx", log.Error(name+" unable to submit clearing tx",
"nonce", nonce, "gasPrice", gasPrice, "txHash", txHash, "nonce", nonce, "gasTipCap", gasTipCap, "gasFeeCap", gasFeeCap,
"err", err) "txHash", txHash, "err", err)
return nil, err return nil, err
} }
} }
...@@ -127,26 +127,39 @@ func ClearPendingTx( ...@@ -127,26 +127,39 @@ func ClearPendingTx(
// SignClearingTx creates a signed clearing tranaction which sends 0 ETH back to // 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. // the sender's address. EstimateGas is used to set an appropriate gas limit.
func SignClearingTx( func SignClearingTx(
name string,
ctx context.Context, ctx context.Context,
walletAddr common.Address, walletAddr common.Address,
nonce uint64, nonce uint64,
gasPrice *big.Int,
l1Client L1Client, l1Client L1Client,
privKey *ecdsa.PrivateKey, privKey *ecdsa.PrivateKey,
chainID *big.Int, chainID *big.Int,
) (*types.Transaction, error) { ) (*types.Transaction, error) {
gasLimit, err := l1Client.EstimateGas(ctx, ethereum.CallMsg{ gasTipCap, err := l1Client.SuggestGasTipCap(ctx)
To: &walletAddr, if err != nil {
GasPrice: gasPrice, if !IsMaxPriorityFeePerGasNotFoundError(err) {
Value: nil, return nil, err
Data: nil, }
})
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this
// method, so in the event their API is unreachable we can fallback to a
// degraded mode of operation. This also applies to our test
// environments, as hardhat doesn't support the query either.
log.Warn(name + " eth_maxPriorityFeePerGas is unsupported " +
"by current backend, using fallback gasTipCap")
gasTipCap = FallbackGasTipCap
}
head, err := l1Client.HeaderByNumber(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tx := CraftClearingTx(walletAddr, nonce, gasPrice, gasLimit) gasFeeCap := txmgr.CalcGasFeeCap(head.BaseFee, gasTipCap)
tx := CraftClearingTx(walletAddr, nonce, gasFeeCap, gasTipCap)
return types.SignTx( return types.SignTx(
tx, types.LatestSignerForChainID(chainID), privKey, tx, types.LatestSignerForChainID(chainID), privKey,
...@@ -158,16 +171,16 @@ func SignClearingTx( ...@@ -158,16 +171,16 @@ func SignClearingTx(
func CraftClearingTx( func CraftClearingTx(
walletAddr common.Address, walletAddr common.Address,
nonce uint64, nonce uint64,
gasPrice *big.Int, gasFeeCap *big.Int,
gasLimit uint64, gasTipCap *big.Int,
) *types.Transaction { ) *types.Transaction {
return types.NewTx(&types.LegacyTx{ return types.NewTx(&types.DynamicFeeTx{
To: &walletAddr, To: &walletAddr,
Nonce: nonce, Nonce: nonce,
GasPrice: gasPrice, GasFeeCap: gasFeeCap,
Gas: gasLimit, GasTipCap: gasTipCap,
Value: nil, Value: nil,
Data: nil, Data: nil,
}) })
} }
...@@ -11,8 +11,6 @@ import ( ...@@ -11,8 +11,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers" "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/mock"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "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/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"
...@@ -27,8 +25,6 @@ func init() { ...@@ -27,8 +25,6 @@ func init() {
} }
testPrivKey = privKey testPrivKey = privKey
testWalletAddr = crypto.PubkeyToAddress(privKey.PublicKey) testWalletAddr = crypto.PubkeyToAddress(privKey.PublicKey)
testChainID = new(big.Int).SetUint64(1)
testGasPrice = new(big.Int).SetUint64(3)
} }
var ( var (
...@@ -36,21 +32,22 @@ var ( ...@@ -36,21 +32,22 @@ var (
testWalletAddr common.Address testWalletAddr common.Address
testChainID = big.NewInt(1) testChainID = big.NewInt(1)
testNonce = uint64(2) testNonce = uint64(2)
testGasPrice = big.NewInt(3) testGasFeeCap = big.NewInt(3)
testGasLimit = uint64(4) testGasTipCap = big.NewInt(4)
testBlockNumber = uint64(5) testBlockNumber = uint64(5)
testBaseFee = big.NewInt(6)
) )
// TestCraftClearingTx asserts that CraftClearingTx produces the expected // TestCraftClearingTx asserts that CraftClearingTx produces the expected
// unsigned clearing transaction. // unsigned clearing transaction.
func TestCraftClearingTx(t *testing.T) { func TestCraftClearingTx(t *testing.T) {
tx := drivers.CraftClearingTx( tx := drivers.CraftClearingTx(
testWalletAddr, testNonce, testGasPrice, testGasLimit, testWalletAddr, testNonce, testGasFeeCap, testGasTipCap,
) )
require.Equal(t, &testWalletAddr, tx.To()) require.Equal(t, &testWalletAddr, tx.To())
require.Equal(t, testNonce, tx.Nonce()) require.Equal(t, testNonce, tx.Nonce())
require.Equal(t, testGasPrice, tx.GasPrice()) require.Equal(t, testGasFeeCap, tx.GasFeeCap())
require.Equal(t, testGasLimit, tx.Gas()) require.Equal(t, testGasTipCap, tx.GasTipCap())
require.Equal(t, new(big.Int), tx.Value()) require.Equal(t, new(big.Int), tx.Value())
require.Nil(t, tx.Data()) require.Nil(t, tx.Data())
} }
...@@ -59,21 +56,31 @@ func TestCraftClearingTx(t *testing.T) { ...@@ -59,21 +56,31 @@ func TestCraftClearingTx(t *testing.T) {
// clearing transaction when the call to EstimateGas succeeds. // clearing transaction when the call to EstimateGas succeeds.
func TestSignClearingTxEstimateGasSuccess(t *testing.T) { func TestSignClearingTxEstimateGasSuccess(t *testing.T) {
l1Client := mock.NewL1Client(mock.L1ClientConfig{ l1Client := mock.NewL1Client(mock.L1ClientConfig{
EstimateGas: func(_ context.Context, _ ethereum.CallMsg) (uint64, error) { HeaderByNumber: func(_ context.Context, _ *big.Int) (*types.Header, error) {
return testGasLimit, nil return &types.Header{
BaseFee: testBaseFee,
}, nil
},
SuggestGasTipCap: func(_ context.Context) (*big.Int, error) {
return testGasTipCap, nil
}, },
}) })
expGasFeeCap := new(big.Int).Add(
testGasTipCap,
new(big.Int).Mul(testBaseFee, big.NewInt(2)),
)
tx, err := drivers.SignClearingTx( tx, err := drivers.SignClearingTx(
context.Background(), testWalletAddr, testNonce, testGasPrice, l1Client, "TEST", context.Background(), testWalletAddr, testNonce, l1Client,
testPrivKey, testChainID, testPrivKey, testChainID,
) )
require.Nil(t, err) require.Nil(t, err)
require.NotNil(t, tx) require.NotNil(t, tx)
require.Equal(t, &testWalletAddr, tx.To()) require.Equal(t, &testWalletAddr, tx.To())
require.Equal(t, testNonce, tx.Nonce()) require.Equal(t, testNonce, tx.Nonce())
require.Equal(t, testGasPrice, tx.GasPrice()) require.Equal(t, expGasFeeCap, tx.GasFeeCap())
require.Equal(t, testGasLimit, tx.Gas()) require.Equal(t, testGasTipCap, tx.GasTipCap())
require.Equal(t, new(big.Int), tx.Value()) require.Equal(t, new(big.Int), tx.Value())
require.Nil(t, tx.Data()) require.Nil(t, tx.Data())
...@@ -83,22 +90,44 @@ func TestSignClearingTxEstimateGasSuccess(t *testing.T) { ...@@ -83,22 +90,44 @@ func TestSignClearingTxEstimateGasSuccess(t *testing.T) {
require.Equal(t, testWalletAddr, sender) require.Equal(t, testWalletAddr, sender)
} }
// TestSignClearingTxEstimateGasFail asserts that signing a clearing transaction // TestSignClearingTxSuggestGasTipCapFail asserts that signing a clearing
// will fail if the underlying call to EstimateGas fails. // transaction will fail if the underlying call to SuggestGasTipCap fails.
func TestSignClearingTxEstimateGasFail(t *testing.T) { func TestSignClearingTxSuggestGasTipCapFail(t *testing.T) {
errEstimateGas := errors.New("estimate gas") errSuggestGasTipCap := errors.New("suggest gas tip cap")
l1Client := mock.NewL1Client(mock.L1ClientConfig{ l1Client := mock.NewL1Client(mock.L1ClientConfig{
EstimateGas: func(_ context.Context, _ ethereum.CallMsg) (uint64, error) { SuggestGasTipCap: func(_ context.Context) (*big.Int, error) {
return 0, errEstimateGas return nil, errSuggestGasTipCap
}, },
}) })
tx, err := drivers.SignClearingTx( tx, err := drivers.SignClearingTx(
context.Background(), testWalletAddr, testNonce, testGasPrice, l1Client, "TEST", context.Background(), testWalletAddr, testNonce, l1Client,
testPrivKey, testChainID, testPrivKey, testChainID,
) )
require.Equal(t, errEstimateGas, err) 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(
"TEST", context.Background(), testWalletAddr, testNonce, l1Client,
testPrivKey, testChainID,
)
require.Equal(t, errHeaderByNumber, err)
require.Nil(t, tx) require.Nil(t, tx)
} }
...@@ -117,22 +146,26 @@ func newClearPendingTxHarnessWithNumConfs( ...@@ -117,22 +146,26 @@ func newClearPendingTxHarnessWithNumConfs(
return testBlockNumber, nil return testBlockNumber, nil
} }
} }
if l1ClientConfig.HeaderByNumber == nil {
l1ClientConfig.HeaderByNumber = func(_ context.Context, _ *big.Int) (*types.Header, error) {
return &types.Header{
BaseFee: testBaseFee,
}, nil
}
}
if l1ClientConfig.NonceAt == nil { if l1ClientConfig.NonceAt == nil {
l1ClientConfig.NonceAt = func(_ context.Context, _ common.Address, _ *big.Int) (uint64, error) { l1ClientConfig.NonceAt = func(_ context.Context, _ common.Address, _ *big.Int) (uint64, error) {
return testNonce, nil return testNonce, nil
} }
} }
if l1ClientConfig.EstimateGas == nil { if l1ClientConfig.SuggestGasTipCap == nil {
l1ClientConfig.EstimateGas = func(_ context.Context, _ ethereum.CallMsg) (uint64, error) { l1ClientConfig.SuggestGasTipCap = func(_ context.Context) (*big.Int, error) {
return testGasLimit, nil return testGasTipCap, nil
} }
} }
l1Client := mock.NewL1Client(l1ClientConfig) l1Client := mock.NewL1Client(l1ClientConfig)
txMgr := txmgr.NewSimpleTxManager("test", txmgr.Config{ txMgr := txmgr.NewSimpleTxManager("test", txmgr.Config{
MinGasPrice: utils.GasPriceFromGwei(1),
MaxGasPrice: utils.GasPriceFromGwei(100),
GasRetryIncrement: utils.GasPriceFromGwei(5),
ResubmissionTimeout: time.Second, ResubmissionTimeout: time.Second,
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: numConfirmations, NumConfirmations: numConfirmations,
...@@ -200,11 +233,14 @@ func TestClearPendingTxTimeout(t *testing.T) { ...@@ -200,11 +233,14 @@ func TestClearPendingTxTimeout(t *testing.T) {
}, },
}) })
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := drivers.ClearPendingTx( err := drivers.ClearPendingTx(
"test", context.Background(), h.txMgr, h.l1Client, testWalletAddr, "test", ctx, h.txMgr, h.l1Client, testWalletAddr, testPrivKey,
testPrivKey, testChainID, testChainID,
) )
require.Equal(t, txmgr.ErrPublishTimeout, err) require.Equal(t, context.DeadlineExceeded, err)
} }
// TestClearPendingTxMultipleConfs tests we wait the appropriate number of // TestClearPendingTxMultipleConfs tests we wait the appropriate number of
...@@ -225,12 +261,15 @@ func TestClearPendingTxMultipleConfs(t *testing.T) { ...@@ -225,12 +261,15 @@ func TestClearPendingTxMultipleConfs(t *testing.T) {
}, },
}, numConfs) }, numConfs)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// The txmgr should timeout waiting for the txn to confirm. // The txmgr should timeout waiting for the txn to confirm.
err := drivers.ClearPendingTx( err := drivers.ClearPendingTx(
"test", context.Background(), h.txMgr, h.l1Client, testWalletAddr, "test", ctx, h.txMgr, h.l1Client, testWalletAddr, testPrivKey,
testPrivKey, testChainID, testChainID,
) )
require.Equal(t, txmgr.ErrPublishTimeout, err) require.Equal(t, context.DeadlineExceeded, err)
// Now set the chain height to the earliest the transaction will be // Now set the chain height to the earliest the transaction will be
// considered sufficiently confirmed. // considered sufficiently confirmed.
......
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"context" "context"
"math/big" "math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
...@@ -12,12 +11,9 @@ import ( ...@@ -12,12 +11,9 @@ import (
// L1Client is an abstraction over an L1 Ethereum client functionality required // L1Client is an abstraction over an L1 Ethereum client functionality required
// by the batch submitter. // by the batch submitter.
type L1Client interface { type L1Client interface {
// EstimateGas tries to estimate the gas needed to execute a specific // HeaderByNumber returns a block header from the current canonical chain.
// transaction based on the current pending state of the backend blockchain. // If number is nil, the latest known header is returned.
// There is no guarantee that this is the true gas limit requirement as HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
// other transactions may be added or removed by miners, but it should
// provide a basis for setting a reasonable default.
EstimateGas(context.Context, ethereum.CallMsg) (uint64, error)
// NonceAt returns the account nonce of the given account. The block number // NonceAt returns the account nonce of the given account. The block number
// can be nil, in which case the nonce is taken from the latest known block. // can be nil, in which case the nonce is taken from the latest known block.
...@@ -30,6 +26,10 @@ type L1Client interface { ...@@ -30,6 +26,10 @@ type L1Client interface {
// method to get the contract address after the transaction has been mined. // method to get the contract address after the transaction has been mined.
SendTransaction(context.Context, *types.Transaction) error SendTransaction(context.Context, *types.Transaction) error
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559
// to allow a timely execution of a transaction.
SuggestGasTipCap(context.Context) (*big.Int, error)
// TransactionReceipt returns the receipt of a transaction by transaction // TransactionReceipt returns the receipt of a transaction by transaction
// hash. Note that the receipt is not available for pending transactions. // hash. Note that the receipt is not available for pending transactions.
TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error) TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error)
......
package drivers
import (
"errors"
"math/big"
"strings"
)
var (
errMaxPriorityFeePerGasNotFound = errors.New(
"Method eth_maxPriorityFeePerGas not found",
)
// FallbackGasTipCap is the default fallback gasTipCap used when we are
// unable to query an L1 backend for a suggested gasTipCap.
FallbackGasTipCap = big.NewInt(1500000000)
)
// IsMaxPriorityFeePerGasNotFoundError returns true if the provided error
// signals that the backend does not support the eth_maxPrirorityFeePerGas
// method. In this case, the caller should fallback to using the constant above.
func IsMaxPriorityFeePerGasNotFoundError(err error) bool {
return strings.Contains(
err.Error(), errMaxPriorityFeePerGasNotFound.Error(),
)
}
...@@ -14,7 +14,6 @@ import ( ...@@ -14,7 +14,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/log" "github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum-optimism/optimism/l2geth/params"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -197,22 +196,43 @@ func (d *Driver) CraftBatchTx( ...@@ -197,22 +196,43 @@ func (d *Driver) CraftBatchTx(
} }
opts.Context = ctx opts.Context = ctx
opts.Nonce = nonce opts.Nonce = nonce
opts.GasPrice = big.NewInt(params.GWei) // dummy
opts.NoSend = true opts.NoSend = true
blockOffset := new(big.Int).SetUint64(d.cfg.BlockOffset) blockOffset := new(big.Int).SetUint64(d.cfg.BlockOffset)
offsetStartsAtIndex := new(big.Int).Sub(start, blockOffset) offsetStartsAtIndex := new(big.Int).Sub(start, blockOffset)
return d.sccContract.AppendStateBatch(opts, stateRoots, offsetStartsAtIndex) tx, err := d.sccContract.AppendStateBatch(
opts, stateRoots, offsetStartsAtIndex,
)
switch {
case err == nil:
return tx, nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this method,
// so in the event their API is unreachable we can fallback to a degraded
// mode of operation. This also applies to our test environments, as hardhat
// doesn't support the query either.
case drivers.IsMaxPriorityFeePerGasNotFoundError(err):
log.Warn(d.cfg.Name + " eth_maxPriorityFeePerGas is unsupported " +
"by current backend, using fallback gasTipCap")
opts.GasTipCap = drivers.FallbackGasTipCap
return d.sccContract.AppendStateBatch(
opts, stateRoots, offsetStartsAtIndex,
)
default:
return nil, err
}
} }
// SubmitBatchTx using the passed transaction as a template, signs and publishes // SubmitBatchTx using the passed transaction as a template, signs and
// an otherwise identical transaction after setting the provided gas price. The // publishes the transaction unmodified apart from sampling the current gas
// final transaction is returned to the caller. // price. The final transaction is returned to the caller.
func (d *Driver) SubmitBatchTx( func (d *Driver) SubmitBatchTx(
ctx context.Context, ctx context.Context,
tx *types.Transaction, tx *types.Transaction,
gasPrice *big.Int,
) (*types.Transaction, error) { ) (*types.Transaction, error) {
opts, err := bind.NewKeyedTransactorWithChainID( opts, err := bind.NewKeyedTransactorWithChainID(
...@@ -223,7 +243,25 @@ func (d *Driver) SubmitBatchTx( ...@@ -223,7 +243,25 @@ func (d *Driver) SubmitBatchTx(
} }
opts.Context = ctx opts.Context = ctx
opts.Nonce = new(big.Int).SetUint64(tx.Nonce()) opts.Nonce = new(big.Int).SetUint64(tx.Nonce())
opts.GasPrice = gasPrice
return d.rawSccContract.RawTransact(opts, tx.Data()) finalTx, err := d.rawSccContract.RawTransact(opts, tx.Data())
switch {
case err == nil:
return finalTx, nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this method,
// so in the event their API is unreachable we can fallback to a degraded
// mode of operation. This also applies to our test environments, as hardhat
// doesn't support the query either.
case drivers.IsMaxPriorityFeePerGasNotFoundError(err):
log.Warn(d.cfg.Name + " eth_maxPriorityFeePerGas is unsupported " +
"by current backend, using fallback gasTipCap")
opts.GasTipCap = drivers.FallbackGasTipCap
return d.rawSccContract.RawTransact(opts, tx.Data())
default:
return nil, err
}
} }
...@@ -12,7 +12,6 @@ import ( ...@@ -12,7 +12,6 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics" "github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr" "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/params"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -233,20 +232,37 @@ func (d *Driver) CraftBatchTx( ...@@ -233,20 +232,37 @@ func (d *Driver) CraftBatchTx(
} }
opts.Context = ctx opts.Context = ctx
opts.Nonce = nonce opts.Nonce = nonce
opts.GasPrice = big.NewInt(params.GWei) // dummy
opts.NoSend = true opts.NoSend = true
return d.rawCtcContract.RawTransact(opts, batchCallData) tx, err := d.rawCtcContract.RawTransact(opts, batchCallData)
switch {
case err == nil:
return tx, nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this
// method, so in the event their API is unreachable we can fallback to a
// degraded mode of operation. This also applies to our test
// environments, as hardhat doesn't support the query either.
case drivers.IsMaxPriorityFeePerGasNotFoundError(err):
log.Warn(d.cfg.Name + " eth_maxPriorityFeePerGas is unsupported " +
"by current backend, using fallback gasTipCap")
opts.GasTipCap = drivers.FallbackGasTipCap
return d.rawCtcContract.RawTransact(opts, batchCallData)
default:
return nil, err
}
} }
} }
// SubmitBatchTx using the passed transaction as a template, signs and publishes // SubmitBatchTx using the passed transaction as a template, signs and publishes
// an otherwise identical transaction after setting the provided gas price. The // the transaction unmodified apart from sampling the current gas price. The
// final transaction is returned to the caller. // final transaction is returned to the caller.
func (d *Driver) SubmitBatchTx( func (d *Driver) SubmitBatchTx(
ctx context.Context, ctx context.Context,
tx *types.Transaction, tx *types.Transaction,
gasPrice *big.Int,
) (*types.Transaction, error) { ) (*types.Transaction, error) {
opts, err := bind.NewKeyedTransactorWithChainID( opts, err := bind.NewKeyedTransactorWithChainID(
...@@ -257,7 +273,25 @@ func (d *Driver) SubmitBatchTx( ...@@ -257,7 +273,25 @@ func (d *Driver) SubmitBatchTx(
} }
opts.Context = ctx opts.Context = ctx
opts.Nonce = new(big.Int).SetUint64(tx.Nonce()) opts.Nonce = new(big.Int).SetUint64(tx.Nonce())
opts.GasPrice = gasPrice
return d.rawCtcContract.RawTransact(opts, tx.Data()) finalTx, err := d.rawCtcContract.RawTransact(opts, tx.Data())
switch {
case err == nil:
return finalTx, nil
// If the transaction failed because the backend does not support
// eth_maxPriorityFeePerGas, fallback to using the default constant.
// Currently Alchemy is the only backend provider that exposes this method,
// so in the event their API is unreachable we can fallback to a degraded
// mode of operation. This also applies to our test environments, as hardhat
// doesn't support the query either.
case drivers.IsMaxPriorityFeePerGasNotFoundError(err):
log.Warn(d.cfg.Name + " eth_maxPriorityFeePerGas is unsupported " +
"by current backend, using fallback gasTipCap")
opts.GasTipCap = drivers.FallbackGasTipCap
return d.rawCtcContract.RawTransact(opts, tx.Data())
default:
return nil, err
}
} }
...@@ -151,18 +151,6 @@ var ( ...@@ -151,18 +151,6 @@ var (
Value: 1, Value: 1,
EnvVar: prefixEnvVar("BLOCK_OFFSET"), EnvVar: prefixEnvVar("BLOCK_OFFSET"),
} }
MaxGasPriceInGweiFlag = cli.Uint64Flag{
Name: "max-gas-price-in-gwei",
Usage: "Maximum gas price the batch submitter can use for transactions",
Value: 100,
EnvVar: prefixEnvVar("MAX_GAS_PRICE_IN_GWEI"),
}
GasRetryIncrementFlag = cli.Uint64Flag{
Name: "gas-retry-increment",
Usage: "Default step by which to increment gas price bumps",
Value: 5,
EnvVar: prefixEnvVar("GAS_RETRY_INCREMENT_FLAG"),
}
SequencerPrivateKeyFlag = cli.StringFlag{ SequencerPrivateKeyFlag = cli.StringFlag{
Name: "sequencer-private-key", Name: "sequencer-private-key",
Usage: "The private key to use for sending to the sequencer contract", Usage: "The private key to use for sending to the sequencer contract",
...@@ -240,8 +228,6 @@ var optionalFlags = []cli.Flag{ ...@@ -240,8 +228,6 @@ var optionalFlags = []cli.Flag{
SentryDsnFlag, SentryDsnFlag,
SentryTraceRateFlag, SentryTraceRateFlag,
BlockOffsetFlag, BlockOffsetFlag,
MaxGasPriceInGweiFlag,
GasRetryIncrementFlag,
SequencerPrivateKeyFlag, SequencerPrivateKeyFlag,
ProposerPrivateKeyFlag, ProposerPrivateKeyFlag,
MnemonicFlag, MnemonicFlag,
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"math/big" "math/big"
"sync" "sync"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
...@@ -16,12 +15,9 @@ type L1ClientConfig struct { ...@@ -16,12 +15,9 @@ type L1ClientConfig struct {
// BlockNumber returns the most recent block number. // BlockNumber returns the most recent block number.
BlockNumber func(context.Context) (uint64, error) BlockNumber func(context.Context) (uint64, error)
// EstimateGas tries to estimate the gas needed to execute a specific // HeaderByNumber returns a block header from the current canonical chain.
// transaction based on the current pending state of the backend blockchain. // If number is nil, the latest known header is returned.
// There is no guarantee that this is the true gas limit requirement as HeaderByNumber func(context.Context, *big.Int) (*types.Header, error)
// other transactions may be added or removed by miners, but it should
// provide a basis for setting a reasonable default.
EstimateGas func(context.Context, ethereum.CallMsg) (uint64, error)
// NonceAt returns the account nonce of the given account. The block number // NonceAt returns the account nonce of the given account. The block number
// can be nil, in which case the nonce is taken from the latest known block. // can be nil, in which case the nonce is taken from the latest known block.
...@@ -34,6 +30,10 @@ type L1ClientConfig struct { ...@@ -34,6 +30,10 @@ type L1ClientConfig struct {
// method to get the contract address after the transaction has been mined. // method to get the contract address after the transaction has been mined.
SendTransaction func(context.Context, *types.Transaction) error SendTransaction func(context.Context, *types.Transaction) error
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559
// to allow a timely execution of a transaction.
SuggestGasTipCap func(context.Context) (*big.Int, error)
// TransactionReceipt returns the receipt of a transaction by transaction // TransactionReceipt returns the receipt of a transaction by transaction
// hash. Note that the receipt is not available for pending transactions. // hash. Note that the receipt is not available for pending transactions.
TransactionReceipt func(context.Context, common.Hash) (*types.Receipt, error) TransactionReceipt func(context.Context, common.Hash) (*types.Receipt, error)
...@@ -61,12 +61,13 @@ func (c *L1Client) BlockNumber(ctx context.Context) (uint64, error) { ...@@ -61,12 +61,13 @@ func (c *L1Client) BlockNumber(ctx context.Context) (uint64, error) {
return c.cfg.BlockNumber(ctx) return c.cfg.BlockNumber(ctx)
} }
// EstimateGas executes the mock EstimateGas method. // HeaderByNumber returns a block header from the current canonical chain. If
func (c *L1Client) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { // number is nil, the latest known header is returned.
func (c *L1Client) HeaderByNumber(ctx context.Context, blockNumber *big.Int) (*types.Header, error) {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
return c.cfg.EstimateGas(ctx, call) return c.cfg.HeaderByNumber(ctx, blockNumber)
} }
// NonceAt executes the mock NonceAt method. // NonceAt executes the mock NonceAt method.
...@@ -85,6 +86,15 @@ func (c *L1Client) SendTransaction(ctx context.Context, tx *types.Transaction) e ...@@ -85,6 +86,15 @@ func (c *L1Client) SendTransaction(ctx context.Context, tx *types.Transaction) e
return c.cfg.SendTransaction(ctx, tx) return c.cfg.SendTransaction(ctx, tx)
} }
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to
// allow a timely execution of a transaction.
func (c *L1Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
c.mu.RLock()
defer c.mu.RUnlock()
return c.cfg.SuggestGasTipCap(ctx)
}
// TransactionReceipt executes the mock TransactionReceipt method. // TransactionReceipt executes the mock TransactionReceipt method.
func (c *L1Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { func (c *L1Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
c.mu.RLock() c.mu.RLock()
...@@ -103,17 +113,17 @@ func (c *L1Client) SetBlockNumberFunc( ...@@ -103,17 +113,17 @@ func (c *L1Client) SetBlockNumberFunc(
c.cfg.BlockNumber = f c.cfg.BlockNumber = f
} }
// SetEstimateGasFunc overrwrites the mock EstimateGas method. // SetHeaderByNumberFunc overwrites the mock HeaderByNumber method.
func (c *L1Client) SetEstimateGasFunc( func (c *L1Client) SetHeaderByNumberFunc(
f func(context.Context, ethereum.CallMsg) (uint64, error)) { f func(ctx context.Context, blockNumber *big.Int) (*types.Header, error)) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
c.cfg.EstimateGas = f c.cfg.HeaderByNumber = f
} }
// SetNonceAtFunc overrwrites the mock NonceAt method. // SetNonceAtFunc overwrites the mock NonceAt method.
func (c *L1Client) SetNonceAtFunc( func (c *L1Client) SetNonceAtFunc(
f func(context.Context, common.Address, *big.Int) (uint64, error)) { f func(context.Context, common.Address, *big.Int) (uint64, error)) {
...@@ -123,7 +133,7 @@ func (c *L1Client) SetNonceAtFunc( ...@@ -123,7 +133,7 @@ func (c *L1Client) SetNonceAtFunc(
c.cfg.NonceAt = f c.cfg.NonceAt = f
} }
// SetSendTransactionFunc overrwrites the mock SendTransaction method. // SetSendTransactionFunc overwrites the mock SendTransaction method.
func (c *L1Client) SetSendTransactionFunc( func (c *L1Client) SetSendTransactionFunc(
f func(context.Context, *types.Transaction) error) { f func(context.Context, *types.Transaction) error) {
...@@ -133,6 +143,16 @@ func (c *L1Client) SetSendTransactionFunc( ...@@ -133,6 +143,16 @@ func (c *L1Client) SetSendTransactionFunc(
c.cfg.SendTransaction = f c.cfg.SendTransaction = f
} }
// SetSuggestGasTipCapFunc overwrites themock SuggestGasTipCap method.
func (c *L1Client) SetSuggestGasTipCapFunc(
f func(context.Context) (*big.Int, error)) {
c.mu.Lock()
defer c.mu.Unlock()
c.cfg.SuggestGasTipCap = f
}
// SetTransactionReceiptFunc overwrites the mock TransactionReceipt method. // SetTransactionReceiptFunc overwrites the mock TransactionReceipt method.
func (c *L1Client) SetTransactionReceiptFunc( func (c *L1Client) SetTransactionReceiptFunc(
f func(context.Context, common.Hash) (*types.Receipt, error)) { f func(context.Context, common.Hash) (*types.Receipt, error)) {
......
...@@ -55,12 +55,11 @@ type Driver interface { ...@@ -55,12 +55,11 @@ type Driver interface {
) (*types.Transaction, error) ) (*types.Transaction, error)
// SubmitBatchTx using the passed transaction as a template, signs and // SubmitBatchTx using the passed transaction as a template, signs and
// publishes an otherwise identical transaction after setting the provided // publishes the transaction unmodified apart from sampling the current gas
// gas price. The final transaction is returned to the caller. // price. The final transaction is returned to the caller.
SubmitBatchTx( SubmitBatchTx(
ctx context.Context, ctx context.Context,
tx *types.Transaction, tx *types.Transaction,
gasPrice *big.Int,
) (*types.Transaction, error) ) (*types.Transaction, error)
} }
...@@ -194,15 +193,11 @@ func (s *Service) eventLoop() { ...@@ -194,15 +193,11 @@ func (s *Service) eventLoop() {
// Construct the transaction submission clousure that will attempt // Construct the transaction submission clousure that will attempt
// to send the next transaction at the given nonce and gas price. // to send the next transaction at the given nonce and gas price.
sendTx := func( sendTx := func(ctx context.Context) (*types.Transaction, error) {
ctx context.Context,
gasPrice *big.Int,
) (*types.Transaction, error) {
log.Info(name+" attempting batch tx", "start", start, log.Info(name+" attempting batch tx", "start", start,
"end", end, "nonce", nonce, "end", end, "nonce", nonce)
"gasPrice", gasPrice)
tx, err := s.cfg.Driver.SubmitBatchTx(ctx, tx, gasPrice) tx, err := s.cfg.Driver.SubmitBatchTx(ctx, tx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -213,7 +208,6 @@ func (s *Service) eventLoop() { ...@@ -213,7 +208,6 @@ func (s *Service) eventLoop() {
"end", end, "end", end,
"nonce", nonce, "nonce", nonce,
"tx_hash", tx.Hash(), "tx_hash", tx.Hash(),
"gasPrice", gasPrice,
) )
return tx, nil return tx, nil
......
...@@ -2,46 +2,27 @@ package txmgr ...@@ -2,46 +2,27 @@ package txmgr
import ( import (
"context" "context"
"errors"
"math/big" "math/big"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// ErrPublishTimeout signals that the tx manager did not receive a confirmation
// for a given tx after publishing with the maximum gas price and waiting out a
// resubmission timeout.
var ErrPublishTimeout = errors.New("failed to publish tx with max gas price")
// SendTxFunc defines a function signature for publishing a desired tx with a // SendTxFunc defines a function signature for publishing a desired tx with a
// specific gas price. Implementations of this signature should also return // specific gas price. Implementations of this signature should also return
// promptly when the context is canceled. // promptly when the context is canceled.
type SendTxFunc = func( type SendTxFunc = func(ctx context.Context) (*types.Transaction, error)
ctx context.Context, gasPrice *big.Int) (*types.Transaction, error)
// 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 {
// Name the name of the driver to appear in log lines.
Name string Name string
// MinGasPrice is the minimum gas price (in gwei). This is used as the
// initial publication attempt.
MinGasPrice *big.Int
// MaxGasPrice is the maximum gas price (in gwei). This is used to clamp
// the upper end of the range that the TxManager will ever publish when
// attempting to confirm a transaction.
MaxGasPrice *big.Int
// GasRetryIncrement is the additive gas price (in gwei) that will be
// used to bump each successive tx after a ResubmissionTimeout has
// elapsed.
GasRetryIncrement *big.Int
// 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
...@@ -135,25 +116,29 @@ func (m *SimpleTxManager) Send( ...@@ -135,25 +116,29 @@ func (m *SimpleTxManager) Send(
// background, returning the first successfully mined receipt back to // background, returning the first successfully mined receipt back to
// the main event loop via receiptChan. // the main event loop via receiptChan.
receiptChan := make(chan *types.Receipt, 1) receiptChan := make(chan *types.Receipt, 1)
sendTxAsync := func(gasPrice *big.Int) { sendTxAsync := func() {
defer wg.Done() defer wg.Done()
// Sign and publish transaction with current gas price. // Sign and publish transaction with current gas price.
tx, err := sendTx(ctxc, gasPrice) tx, err := sendTx(ctxc)
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
} }
log.Error(name+" unable to publish transaction", log.Error(name+" unable to publish transaction", "err", err)
"gas_price", gasPrice, "err", err) if shouldAbortImmediately(err) {
cancel()
}
// TODO(conner): add retry? // TODO(conner): add retry?
return return
} }
txHash := tx.Hash() txHash := tx.Hash()
gasTipCap := tx.GasTipCap()
gasFeeCap := tx.GasFeeCap()
log.Info(name+" transaction published successfully", "hash", txHash, log.Info(name+" transaction published successfully", "hash", txHash,
"gas_price", gasPrice) "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.
...@@ -163,7 +148,7 @@ func (m *SimpleTxManager) Send( ...@@ -163,7 +148,7 @@ func (m *SimpleTxManager) Send(
) )
if err != nil { if err != nil {
log.Debug(name+" send tx failed", "hash", txHash, log.Debug(name+" send tx failed", "hash", txHash,
"gas_price", gasPrice, "err", err) "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
...@@ -171,20 +156,17 @@ func (m *SimpleTxManager) Send( ...@@ -171,20 +156,17 @@ func (m *SimpleTxManager) Send(
select { select {
case receiptChan <- receipt: case receiptChan <- receipt:
log.Trace(name+" send tx succeeded", "hash", txHash, log.Trace(name+" send tx succeeded", "hash", txHash,
"gas_price", gasPrice) "gasTipCap", gasTipCap, "gasFeeCap", gasFeeCap)
default: default:
} }
} }
} }
// Initialize our initial gas price to the configured minimum.
curGasPrice := new(big.Int).Set(m.cfg.MinGasPrice)
// Submit and wait for the receipt at our first gas price in the // Submit and wait for the receipt at our first gas price in the
// background, before entering the event loop and waiting out the // background, before entering the event loop and waiting out the
// resubmission timeout. // resubmission timeout.
wg.Add(1) wg.Add(1)
go sendTxAsync(curGasPrice) go sendTxAsync()
for { for {
select { select {
...@@ -192,24 +174,9 @@ func (m *SimpleTxManager) Send( ...@@ -192,24 +174,9 @@ func (m *SimpleTxManager) Send(
// Whenever a resubmission timeout has elapsed, bump the gas // Whenever a resubmission timeout has elapsed, bump the gas
// price and publish a new transaction. // price and publish a new transaction.
case <-time.After(m.cfg.ResubmissionTimeout): case <-time.After(m.cfg.ResubmissionTimeout):
// If our last attempt published at the max gas price,
// return an error as we are unlikely to succeed in
// publishing. This also indicates that the max gas
// price should likely be adjusted higher for the
// daemon.
if curGasPrice.Cmp(m.cfg.MaxGasPrice) >= 0 {
return nil, ErrPublishTimeout
}
// Bump the gas price using linear gas price increments.
curGasPrice = NextGasPrice(
curGasPrice, m.cfg.GasRetryIncrement,
m.cfg.MaxGasPrice,
)
// Submit and wait for the bumped traction to confirm. // Submit and wait for the bumped traction to confirm.
wg.Add(1) wg.Add(1)
go sendTxAsync(curGasPrice) go sendTxAsync()
// 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.
...@@ -223,6 +190,13 @@ func (m *SimpleTxManager) Send( ...@@ -223,6 +190,13 @@ func (m *SimpleTxManager) Send(
} }
} }
// shouldAbortImmediately returns true if the txmgr should cancel all
// publication attempts and retry. For now, this only includes nonce errors, as
// that error indicates that none of the transactions will ever confirm.
func shouldAbortImmediately(err error) bool {
return strings.Contains(err.Error(), core.ErrNonceTooLow.Error())
}
// WaitMined blocks until the backend indicates confirmation of tx and returns // WaitMined blocks until the backend indicates confirmation of tx and returns
// the tx receipt. Queries are made every queryInterval, regardless of whether // the tx receipt. Queries are made every queryInterval, regardless of whether
// the backend returns an error. This method can be canceled using the passed // the backend returns an error. This method can be canceled using the passed
...@@ -289,17 +263,12 @@ func WaitMined( ...@@ -289,17 +263,12 @@ func WaitMined(
} }
} }
// NextGasPrice bumps the current gas price using an additive gasRetryIncrement, // CalcGasFeeCap deterministically computes the recommended gas fee cap given
// clamping the resulting value to maxGasPrice. // the base fee and gasTipCap. The resulting gasFeeCap is equal to:
// // gasTipCap + 2*baseFee.
// NOTE: This method does not mutate curGasPrice, but instead returns a copy. func CalcGasFeeCap(baseFee, gasTipCap *big.Int) *big.Int {
// This removes the possiblity of races occuring from goroutines sharing access return new(big.Int).Add(
// to the same underlying big.Int. gasTipCap,
func NextGasPrice(curGasPrice, gasRetryIncrement, maxGasPrice *big.Int) *big.Int { new(big.Int).Mul(baseFee, big.NewInt(2)),
nextGasPrice := new(big.Int).Set(curGasPrice) )
nextGasPrice.Add(nextGasPrice, gasRetryIncrement)
if nextGasPrice.Cmp(maxGasPrice) == 1 {
nextGasPrice.Set(maxGasPrice)
}
return nextGasPrice
} }
This diff is collapsed.
package utils
import (
"math/big"
"github.com/ethereum/go-ethereum/params"
)
// GasPriceFromGwei converts an uint64 gas price in gwei to a big.Int in wei.
func GasPriceFromGwei(gasPriceInGwei uint64) *big.Int {
return new(big.Int).SetUint64(gasPriceInGwei * params.GWei)
}
package utils_test
import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
// TestGasPriceFromGwei asserts that the integer value is scaled properly by
// 10^9.
func TestGasPriceFromGwei(t *testing.T) {
require.Equal(t, utils.GasPriceFromGwei(0), new(big.Int))
require.Equal(t, utils.GasPriceFromGwei(1), big.NewInt(params.GWei))
require.Equal(t, utils.GasPriceFromGwei(100), big.NewInt(100*params.GWei))
}
# @eth-optimism/proxyd # @eth-optimism/proxyd
## 3.7.0
### Minor Changes
- 3c2926b1: Add debug cache status header to proxyd responses
## 3.6.0 ## 3.6.0
### Minor Changes ### Minor Changes
......
{ {
"name": "@eth-optimism/proxyd", "name": "@eth-optimism/proxyd",
"version": "3.6.0", "version": "3.7.0",
"private": true, "private": true,
"dependencies": {} "dependencies": {}
} }
...@@ -25,6 +25,7 @@ const ( ...@@ -25,6 +25,7 @@ const (
ContextKeyReqID = "req_id" ContextKeyReqID = "req_id"
ContextKeyXForwardedFor = "x_forwarded_for" ContextKeyXForwardedFor = "x_forwarded_for"
MaxBatchRPCCalls = 100 MaxBatchRPCCalls = 100
cacheStatusHdr = "X-Proxyd-Cache-Status"
) )
type Server struct { type Server struct {
...@@ -159,6 +160,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { ...@@ -159,6 +160,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
} }
batchRes := make([]*RPCRes, len(reqs), len(reqs)) batchRes := make([]*RPCRes, len(reqs), len(reqs))
var batchContainsCached bool
for i := 0; i < len(reqs); i++ { for i := 0; i < len(reqs); i++ {
req, err := ParseRPCReq(reqs[i]) req, err := ParseRPCReq(reqs[i])
if err != nil { if err != nil {
...@@ -167,9 +169,14 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { ...@@ -167,9 +169,14 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
continue continue
} }
batchRes[i] = s.handleSingleRPC(ctx, req) var cached bool
batchRes[i], cached = s.handleSingleRPC(ctx, req)
if cached {
batchContainsCached = true
}
} }
setCacheHeader(w, batchContainsCached)
writeBatchRPCRes(ctx, w, batchRes) writeBatchRPCRes(ctx, w, batchRes)
return return
} }
...@@ -181,14 +188,15 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { ...@@ -181,14 +188,15 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
return return
} }
backendRes := s.handleSingleRPC(ctx, req) backendRes, cached := s.handleSingleRPC(ctx, req)
setCacheHeader(w, cached)
writeRPCRes(ctx, w, backendRes) writeRPCRes(ctx, w, backendRes)
} }
func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes { func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) (*RPCRes, bool) {
if err := ValidateRPCReq(req); err != nil { if err := ValidateRPCReq(req); err != nil {
RecordRPCError(ctx, BackendProxyd, MethodUnknown, err) RecordRPCError(ctx, BackendProxyd, MethodUnknown, err)
return NewRPCErrorRes(nil, err) return NewRPCErrorRes(nil, err), false
} }
group := s.rpcMethodMappings[req.Method] group := s.rpcMethodMappings[req.Method]
...@@ -202,7 +210,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes { ...@@ -202,7 +210,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
"method", req.Method, "method", req.Method,
) )
RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrMethodNotWhitelisted) RecordRPCError(ctx, BackendProxyd, MethodUnknown, ErrMethodNotWhitelisted)
return NewRPCErrorRes(req.ID, ErrMethodNotWhitelisted) return NewRPCErrorRes(req.ID, ErrMethodNotWhitelisted), false
} }
var backendRes *RPCRes var backendRes *RPCRes
...@@ -215,7 +223,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes { ...@@ -215,7 +223,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
) )
} }
if backendRes != nil { if backendRes != nil {
return backendRes return backendRes, true
} }
backendRes, err = s.backendGroups[group].Forward(ctx, req) backendRes, err = s.backendGroups[group].Forward(ctx, req)
...@@ -226,7 +234,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes { ...@@ -226,7 +234,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
"req_id", GetReqID(ctx), "req_id", GetReqID(ctx),
"err", err, "err", err,
) )
return NewRPCErrorRes(req.ID, err) return NewRPCErrorRes(req.ID, err), false
} }
if backendRes.Error == nil { if backendRes.Error == nil {
...@@ -239,7 +247,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes { ...@@ -239,7 +247,7 @@ func (s *Server) handleSingleRPC(ctx context.Context, req *RPCReq) *RPCRes {
} }
} }
return backendRes return backendRes, false
} }
func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) { func (s *Server) HandleWS(w http.ResponseWriter, r *http.Request) {
...@@ -322,6 +330,14 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context ...@@ -322,6 +330,14 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
) )
} }
func setCacheHeader(w http.ResponseWriter, cached bool) {
if cached {
w.Header().Set(cacheStatusHdr, "HIT")
} else {
w.Header().Set(cacheStatusHdr, "MISS")
}
}
func writeRPCError(ctx context.Context, w http.ResponseWriter, id json.RawMessage, err error) { func writeRPCError(ctx context.Context, w http.ResponseWriter, id json.RawMessage, err error) {
var res *RPCRes var res *RPCRes
if r, ok := err.(*RPCErr); ok { if r, ok := err.(*RPCErr); ok {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract Precompiles {
function expmod(uint256 base, uint256 e, uint256 m) public returns (uint256 o) {
assembly {
// define pointer
let p := mload(0x40)
// store data assembly-favouring ways
mstore(p, 0x20) // Length of Base
mstore(add(p, 0x20), 0x20) // Length of Exponent
mstore(add(p, 0x40), 0x20) // Length of Modulus
mstore(add(p, 0x60), base) // Base
mstore(add(p, 0x80), e) // Exponent
mstore(add(p, 0xa0), m) // Modulus
if iszero(staticcall(sub(gas(), 2000), 0x05, p, 0xc0, p, 0x20)) {
revert(0, 0)
}
// data
o := mload(p)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract SelfDestruction {
bytes32 public data = 0x0000000000000000000000000000000000000000000000000000000061626364;
function setData(bytes32 _data) public {
data = _data;
}
function destruct() public {
address payable self = payable(address(this));
selfdestruct(self);
}
}
...@@ -4,6 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types' ...@@ -4,6 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle' import '@nomiclabs/hardhat-waffle'
import 'hardhat-gas-reporter' import 'hardhat-gas-reporter'
import './tasks/check-block-hashes'
import { envConfig } from './test/shared/utils' import { envConfig } from './test/shared/utils'
const enableGasReport = !!process.env.ENABLE_GAS_REPORT const enableGasReport = !!process.env.ENABLE_GAS_REPORT
......
...@@ -28,9 +28,9 @@ ...@@ -28,9 +28,9 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/contracts": "0.5.9", "@eth-optimism/contracts": "0.5.10",
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"@eth-optimism/message-relayer": "0.2.13", "@eth-optimism/message-relayer": "0.2.14",
"@ethersproject/abstract-provider": "^5.5.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0", "@ethersproject/transactions": "^5.4.0",
......
import { task } from 'hardhat/config'
import { providers } from 'ethers'
import { die, logStderr } from '../test/shared/utils'
task(
'check-block-hashes',
'Compares the block hashes of two different replicas.'
)
.addPositionalParam('replicaA', 'The first replica')
.addPositionalParam('replicaB', 'The second replica')
.setAction(async ({ replicaA, replicaB }) => {
const providerA = new providers.JsonRpcProvider(replicaA)
const providerB = new providers.JsonRpcProvider(replicaB)
let netA
let netB
try {
netA = await providerA.getNetwork()
} catch (e) {
console.error(`Error getting network from ${replicaA}:`)
die(e)
}
try {
netB = await providerA.getNetwork()
} catch (e) {
console.error(`Error getting network from ${replicaB}:`)
die(e)
}
if (netA.chainId !== netB.chainId) {
die('Chain IDs do not match')
return
}
logStderr('Getting block height.')
const heightA = await providerA.getBlockNumber()
const heightB = await providerB.getBlockNumber()
const endHeight = Math.min(heightA, heightB)
logStderr(`Chose block height: ${endHeight}`)
for (let n = endHeight; n >= 1; n--) {
const blocks = await Promise.all([
providerA.getBlock(n),
providerB.getBlock(n),
])
const hashA = blocks[0].hash
const hashB = blocks[1].hash
if (hashA !== hashB) {
console.log(`HASH MISMATCH! block=${n} a=${hashA} b=${hashB}`)
continue
}
console.log(`HASHES OK! block=${n} hash=${hashA}`)
return
}
})
import { Contract } from 'ethers'
import { ethers } from 'hardhat'
import { OptimismEnv } from '../shared/env'
import { expect } from '../shared/setup'
import { traceToGasByOpcode } from '../hardfork.spec'
import { envConfig } from '../shared/utils'
describe('Nightly', () => {
before(async function () {
if (!envConfig.RUN_NIGHTLY_TESTS) {
this.skip()
}
})
describe('Berlin Hardfork', () => {
let env: OptimismEnv
let SimpleStorage: Contract
let Precompiles: Contract
before(async () => {
env = await OptimismEnv.new()
SimpleStorage = await ethers.getContractAt(
'SimpleStorage',
'0xE08fFE40748367ddc29B5A154331C73B7FCC13bD',
env.l2Wallet
)
Precompiles = await ethers.getContractAt(
'Precompiles',
'0x32E8Fbfd0C0bd1117112b249e997C27b0EC7cba2',
env.l2Wallet
)
})
describe('EIP-2929', () => {
it('should update the gas schedule', async () => {
const tx = await SimpleStorage.setValueNotXDomain(
`0x${'77'.repeat(32)}`
)
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
['0x2bb346f53544c5711502fbcbd1d78dc4fb61ca5f9390b9d6d67f1a3a77de7c39']
)
const berlinSstoreCosts = traceToGasByOpcode(
berlinTrace.structLogs,
'SSTORE'
)
const preBerlinSstoreCosts = traceToGasByOpcode(
preBerlinTrace.structLogs,
'SSTORE'
)
expect(preBerlinSstoreCosts).to.eq(80000)
expect(berlinSstoreCosts).to.eq(5300)
})
})
describe('EIP-2565', () => {
it('should become cheaper', async () => {
const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
['0x7ba7d273449b0062448fe5e7426bb169a032ce189d0e3781eb21079e85c2d7d5']
)
expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas)
})
})
describe('Berlin Additional (L1 London)', () => {
describe('EIP-3529', () => {
it('should remove the refund for selfdestruct', async () => {
const Factory__SelfDestruction = await ethers.getContractFactory(
'SelfDestruction',
env.l2Wallet
)
const SelfDestruction = await Factory__SelfDestruction.deploy()
const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[
'0x948667349f00e996d9267e5c30d72fe7202a0ecdb88bab191e9a022bba6e4cb3',
]
)
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
})
})
})
})
})
import { Contract, BigNumber } from 'ethers'
import { ethers } from 'hardhat'
import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
export const traceToGasByOpcode = (structLogs, opcode) => {
let gas = 0
const opcodes = []
for (const log of structLogs) {
if (log.op === opcode) {
opcodes.push(opcode)
gas += log.gasCost
}
}
return gas
}
describe('Hard forks', () => {
let env: OptimismEnv
let SimpleStorage: Contract
let SelfDestruction: Contract
let Precompiles: Contract
before(async () => {
env = await OptimismEnv.new()
const Factory__SimpleStorage = await ethers.getContractFactory(
'SimpleStorage',
env.l2Wallet
)
SimpleStorage = await Factory__SimpleStorage.deploy()
const Factory__SelfDestruction = await ethers.getContractFactory(
'SelfDestruction',
env.l2Wallet
)
SelfDestruction = await Factory__SelfDestruction.deploy()
const Factory__Precompiles = await ethers.getContractFactory(
'Precompiles',
env.l2Wallet
)
Precompiles = await Factory__Precompiles.deploy()
})
describe('Berlin', () => {
// https://eips.ethereum.org/EIPS/eip-2929
describe('EIP-2929', () => {
it('should update the gas schedule', async () => {
// Get the tip height
const tip = await env.l2Provider.getBlock('latest')
// send a transaction to be able to trace
const tx = await SimpleStorage.setValueNotXDomain(
`0x${'77'.repeat(32)}`
)
await tx.wait()
// Collect the traces
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
expect(berlinTrace.gas).to.not.eq(preBerlinTrace.gas)
const berlinSstoreCosts = traceToGasByOpcode(
berlinTrace.structLogs,
'SSTORE'
)
const preBerlinSstoreCosts = traceToGasByOpcode(
preBerlinTrace.structLogs,
'SSTORE'
)
expect(berlinSstoreCosts).to.not.eq(preBerlinSstoreCosts)
})
})
// https://eips.ethereum.org/EIPS/eip-2565
describe('EIP-2565', async () => {
it('should become cheaper', async () => {
const tip = await env.l2Provider.getBlock('latest')
const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas)
})
})
})
// Optimism includes EIP-3529 as part of its Berlin hardfork. It is part
// of the London hardfork on L1. Since it is coupled to the Berlin
// hardfork, some of its functionality cannot be directly tests via
// integration tests since we can currently only turn on all of the Berlin
// EIPs or none of the Berlin EIPs
describe('Berlin Additional (L1 London)', () => {
// https://eips.ethereum.org/EIPS/eip-3529
describe('EIP-3529', async () => {
const bytes32Zero = '0x' + '00'.repeat(32)
const bytes32NonZero = '0x' + 'ff'.repeat(32)
it('should lower the refund for storage clear', async () => {
const tip = await env.l2Provider.getBlock('latest')
const value = await SelfDestruction.callStatic.data()
// It should be non zero
expect(BigNumber.from(value).toNumber()).to.not.eq(0)
{
// Set the value to another non zero value
// Going from non zero to non zero
const tx = await SelfDestruction.setData(bytes32NonZero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// Updating a non zero value to another non zero value should not change
expect(berlinTrace.gas).to.deep.eq(preBerlinTrace.gas)
}
{
// Set the value to the zero value
// Going from non zero to zero
const tx = await SelfDestruction.setData(bytes32Zero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// Updating to a zero value from a non zero value should becomes
// more expensive due to this change being coupled with EIP-2929
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
}
{
// Set the value to a non zero value
// Going from zero to non zero
const tx = await SelfDestruction.setData(bytes32NonZero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// Updating to a zero value from a non zero value should becomes
// more expensive due to this change being coupled with EIP-2929
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
}
})
it('should remove the refund for selfdestruct', async () => {
const tip = await env.l2Provider.getBlock('latest')
// Send transaction with a large gas limit
const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
)
// The berlin execution should use more gas than the pre Berlin
// execution because there is no longer a selfdestruct gas
// refund
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
})
})
})
})
...@@ -28,6 +28,7 @@ describe('Basic RPC tests', () => { ...@@ -28,6 +28,7 @@ describe('Basic RPC tests', () => {
const provider = injectL2Context(l2Provider) const provider = injectL2Context(l2Provider)
let Reverter: Contract let Reverter: Contract
let ValueContext: Contract
let revertMessage: string let revertMessage: string
let revertingTx: TransactionRequest let revertingTx: TransactionRequest
let revertingDeployTx: TransactionRequest let revertingDeployTx: TransactionRequest
...@@ -53,6 +54,12 @@ describe('Basic RPC tests', () => { ...@@ -53,6 +54,12 @@ describe('Basic RPC tests', () => {
revertingDeployTx = { revertingDeployTx = {
data: Factory__ConstructorReverter.bytecode, data: Factory__ConstructorReverter.bytecode,
} }
// Deploy a contract to check msg.value of the call
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
ValueContext = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
}) })
describe('eth_sendRawTransaction', () => { describe('eth_sendRawTransaction', () => {
...@@ -209,12 +216,6 @@ describe('Basic RPC tests', () => { ...@@ -209,12 +216,6 @@ describe('Basic RPC tests', () => {
}) })
it('should allow eth_calls with nonzero value', async () => { it('should allow eth_calls with nonzero value', async () => {
// Deploy a contract to check msg.value of the call
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
const ValueContext: Contract = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
// Fund account to call from // Fund account to call from
const from = wallet.address const from = wallet.address
const value = 15 const value = 15
...@@ -234,12 +235,6 @@ describe('Basic RPC tests', () => { ...@@ -234,12 +235,6 @@ describe('Basic RPC tests', () => {
// https://github.com/ethereum-optimism/optimism/issues/1998 // https://github.com/ethereum-optimism/optimism/issues/1998
it('should use address(0) as the default "from" value', async () => { it('should use address(0) as the default "from" value', async () => {
// Deploy a contract to check msg.caller
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
const ValueContext: Contract = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
// Do the call and check msg.sender // Do the call and check msg.sender
const data = ValueContext.interface.encodeFunctionData('getCaller') const data = ValueContext.interface.encodeFunctionData('getCaller')
const res = await provider.call({ const res = await provider.call({
...@@ -256,12 +251,6 @@ describe('Basic RPC tests', () => { ...@@ -256,12 +251,6 @@ describe('Basic RPC tests', () => {
}) })
it('should correctly use the "from" value', async () => { it('should correctly use the "from" value', async () => {
// Deploy a contract to check msg.caller
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
const ValueContext: Contract = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
const from = wallet.address const from = wallet.address
// Do the call and check msg.sender // Do the call and check msg.sender
...@@ -278,6 +267,15 @@ describe('Basic RPC tests', () => { ...@@ -278,6 +267,15 @@ describe('Basic RPC tests', () => {
) )
expect(paddedRes).to.eq(from) expect(paddedRes).to.eq(from)
}) })
it('should be deterministic', async () => {
let res = await ValueContext.callStatic.getSelfBalance()
for (let i = 0; i < 10; i++) {
const next = await ValueContext.callStatic.getSelfBalance()
expect(res.toNumber()).to.deep.eq(next.toNumber())
res = next
}
})
}) })
describe('eth_getTransactionReceipt', () => { describe('eth_getTransactionReceipt', () => {
...@@ -450,7 +448,7 @@ describe('Basic RPC tests', () => { ...@@ -450,7 +448,7 @@ describe('Basic RPC tests', () => {
}) })
describe('eth_estimateGas', () => { describe('eth_estimateGas', () => {
it('gas estimation is deterministic', async () => { it('simple send gas estimation is deterministic', async () => {
let lastEstimate: BigNumber let lastEstimate: BigNumber
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
...@@ -466,6 +464,15 @@ describe('Basic RPC tests', () => { ...@@ -466,6 +464,15 @@ describe('Basic RPC tests', () => {
} }
}) })
it('deterministic gas estimation for evm execution', async () => {
let res = await ValueContext.estimateGas.getSelfBalance()
for (let i = 0; i < 10; i++) {
const next = await ValueContext.estimateGas.getSelfBalance()
expect(res.toNumber()).to.deep.eq(next.toNumber())
res = next
}
})
it('should return a gas estimate for txs with empty data', async () => { it('should return a gas estimate for txs with empty data', async () => {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
to: defaultTransactionFactory().to, to: defaultTransactionFactory().to,
......
...@@ -93,6 +93,9 @@ const procEnv = cleanEnv(process.env, { ...@@ -93,6 +93,9 @@ const procEnv = cleanEnv(process.env, {
RUN_STRESS_TESTS: bool({ RUN_STRESS_TESTS: bool({
default: true, default: true,
}), }),
RUN_NIGHTLY_TESTS: bool({
default: false,
}),
MOCHA_TIMEOUT: num({ MOCHA_TIMEOUT: num({
default: 120_000, default: 120_000,
...@@ -264,3 +267,12 @@ export const isHardhat = async () => { ...@@ -264,3 +267,12 @@ export const isHardhat = async () => {
const chainId = await l1Wallet.getChainId() const chainId = await l1Wallet.getChainId()
return chainId === HARDHAT_CHAIN_ID return chainId === HARDHAT_CHAIN_ID
} }
export const die = (...args) => {
console.log(...args)
process.exit(1)
}
export const logStderr = (msg: string) => {
process.stderr.write(`${msg}\n`)
}
...@@ -592,14 +592,15 @@ type callmsg struct { ...@@ -592,14 +592,15 @@ type callmsg struct {
ethereum.CallMsg ethereum.CallMsg
} }
func (m callmsg) From() common.Address { return m.CallMsg.From } func (m callmsg) From() common.Address { return m.CallMsg.From }
func (m callmsg) Nonce() uint64 { return 0 } func (m callmsg) Nonce() uint64 { return 0 }
func (m callmsg) CheckNonce() bool { return false } func (m callmsg) CheckNonce() bool { return false }
func (m callmsg) To() *common.Address { return m.CallMsg.To } func (m callmsg) To() *common.Address { return m.CallMsg.To }
func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.CallMsg.Value } func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.CallMsg.Data } func (m callmsg) Data() []byte { return m.CallMsg.Data }
func (m callmsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
// UsingOVM // UsingOVM
// These getters return OVM specific fields // These getters return OVM specific fields
......
...@@ -48,7 +48,6 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author ...@@ -48,7 +48,6 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
} }
if rcfg.UsingOVM { if rcfg.UsingOVM {
// When using the OVM, we must: // When using the OVM, we must:
// - Set the BlockNumber to be the msg.L1BlockNumber
// - Set the Time to be the msg.L1Timestamp // - Set the Time to be the msg.L1Timestamp
return vm.Context{ return vm.Context{
CanTransfer: CanTransfer, CanTransfer: CanTransfer,
......
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
import (
"github.com/ethereum-optimism/optimism/l2geth/common"
)
type accessList struct {
addresses map[common.Address]int
slots []map[common.Hash]struct{}
}
// ContainsAddress returns true if the address is in the access list.
func (al *accessList) ContainsAddress(address common.Address) bool {
_, ok := al.addresses[address]
return ok
}
// Contains checks if a slot within an account is present in the access list, returning
// separate flags for the presence of the account and the slot respectively.
func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
idx, ok := al.addresses[address]
if !ok {
// no such address (and hence zero slots)
return false, false
}
if idx == -1 {
// address yes, but no slots
return true, false
}
_, slotPresent = al.slots[idx][slot]
return true, slotPresent
}
// newAccessList creates a new accessList.
func newAccessList() *accessList {
return &accessList{
addresses: make(map[common.Address]int),
}
}
// Copy creates an independent copy of an accessList.
func (a *accessList) Copy() *accessList {
cp := newAccessList()
for k, v := range a.addresses {
cp.addresses[k] = v
}
cp.slots = make([]map[common.Hash]struct{}, len(a.slots))
for i, slotMap := range a.slots {
newSlotmap := make(map[common.Hash]struct{}, len(slotMap))
for k := range slotMap {
newSlotmap[k] = struct{}{}
}
cp.slots[i] = newSlotmap
}
return cp
}
// AddAddress adds an address to the access list, and returns 'true' if the operation
// caused a change (addr was not previously in the list).
func (al *accessList) AddAddress(address common.Address) bool {
if _, present := al.addresses[address]; present {
return false
}
al.addresses[address] = -1
return true
}
// AddSlot adds the specified (addr, slot) combo to the access list.
// Return values are:
// - address added
// - slot added
// For any 'true' value returned, a corresponding journal entry must be made.
func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) {
idx, addrPresent := al.addresses[address]
if !addrPresent || idx == -1 {
// Address not present, or addr present but no slots there
al.addresses[address] = len(al.slots)
slotmap := map[common.Hash]struct{}{slot: {}}
al.slots = append(al.slots, slotmap)
return !addrPresent, true
}
// There is already an (address,slot) mapping
slotmap := al.slots[idx]
if _, ok := slotmap[slot]; !ok {
slotmap[slot] = struct{}{}
// Journal add slot change
return false, true
}
// No changes required
return false, false
}
// DeleteSlot removes an (address, slot)-tuple from the access list.
// This operation needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of
// operations.
func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
idx, addrOk := al.addresses[address]
// There are two ways this can fail
if !addrOk {
panic("reverting slot change, address not present in list")
}
slotmap := al.slots[idx]
delete(slotmap, slot)
// If that was the last (first) slot, remove it
// Since additions and rollbacks are always performed in order,
// we can delete the item without worrying about screwing up later indices
if len(slotmap) == 0 {
al.slots = al.slots[:idx]
al.addresses[address] = -1
}
}
// DeleteAddress removes an address from the access list. This operation
// needs to be performed in the same order as the addition happened.
// This method is meant to be used by the journal, which maintains ordering of
// operations.
func (al *accessList) DeleteAddress(address common.Address) {
delete(al.addresses, address)
}
...@@ -129,6 +129,15 @@ type ( ...@@ -129,6 +129,15 @@ type (
touchChange struct { touchChange struct {
account *common.Address account *common.Address
} }
// Changes to the access list
accessListAddAccountChange struct {
address *common.Address
}
accessListAddSlotChange struct {
address *common.Address
slot *common.Hash
}
) )
func (ch createObjectChange) revert(s *StateDB) { func (ch createObjectChange) revert(s *StateDB) {
...@@ -230,3 +239,28 @@ func (ch addPreimageChange) revert(s *StateDB) { ...@@ -230,3 +239,28 @@ func (ch addPreimageChange) revert(s *StateDB) {
func (ch addPreimageChange) dirtied() *common.Address { func (ch addPreimageChange) dirtied() *common.Address {
return nil return nil
} }
func (ch accessListAddAccountChange) revert(s *StateDB) {
/*
One important invariant here, is that whenever a (addr, slot) is added, if the
addr is not already present, the add causes two journal entries:
- one for the address,
- one for the (address,slot)
Therefore, when unrolling the change, we can always blindly delete the
(addr) at this point, since no storage adds can remain when come upon
a single (addr) change.
*/
s.accessList.DeleteAddress(*ch.address)
}
func (ch accessListAddAccountChange) dirtied() *common.Address {
return nil
}
func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot)
}
func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil
}
...@@ -100,6 +100,9 @@ type StateDB struct { ...@@ -100,6 +100,9 @@ type StateDB struct {
preimages map[common.Hash][]byte preimages map[common.Hash][]byte
// Per-transaction access list
accessList *accessList
// Journal of state modifications. This is the backbone of // Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot. // Snapshot and RevertToSnapshot.
journal *journal journal *journal
...@@ -132,6 +135,7 @@ func New(root common.Hash, db Database) (*StateDB, error) { ...@@ -132,6 +135,7 @@ func New(root common.Hash, db Database) (*StateDB, error) {
logs: make(map[common.Hash][]*types.Log), logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte), preimages: make(map[common.Hash][]byte),
journal: newJournal(), journal: newJournal(),
accessList: newAccessList(),
}, nil }, nil
} }
...@@ -163,6 +167,7 @@ func (s *StateDB) Reset(root common.Hash) error { ...@@ -163,6 +167,7 @@ func (s *StateDB) Reset(root common.Hash) error {
s.logs = make(map[common.Hash][]*types.Log) s.logs = make(map[common.Hash][]*types.Log)
s.logSize = 0 s.logSize = 0
s.preimages = make(map[common.Hash][]byte) s.preimages = make(map[common.Hash][]byte)
s.accessList = newAccessList()
s.clearJournalAndRefund() s.clearJournalAndRefund()
return nil return nil
} }
...@@ -673,6 +678,13 @@ func (s *StateDB) Copy() *StateDB { ...@@ -673,6 +678,13 @@ func (s *StateDB) Copy() *StateDB {
for hash, preimage := range s.preimages { for hash, preimage := range s.preimages {
state.preimages[hash] = preimage state.preimages[hash] = preimage
} }
// Do we need to copy the access list? In practice: No. At the start of a
// transaction, the access list is empty. In practice, we only ever copy state
// _between_ transactions/blocks, never in the middle of a transaction.
// However, it doesn't cost us much to copy an empty list, so we do it anyway
// to not blow up if we ever decide copy it in the middle of a transaction
state.accessList = s.accessList.Copy()
return state return state
} }
...@@ -764,6 +776,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) { ...@@ -764,6 +776,7 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) {
s.thash = thash s.thash = thash
s.bhash = bhash s.bhash = bhash
s.txIndex = ti s.txIndex = ti
s.accessList = newAccessList()
} }
func (s *StateDB) clearJournalAndRefund() { func (s *StateDB) clearJournalAndRefund() {
...@@ -815,3 +828,63 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { ...@@ -815,3 +828,63 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
return nil return nil
}) })
} }
// PrepareAccessList handles the preparatory steps for executing a state transition with
// regards to both EIP-2929 and EIP-2930:
//
// - Add sender to access list (2929)
// - Add destination to access list (2929)
// - Add precompiles to access list (2929)
// - Add the contents of the optional tx access list (2930)
//
// This method should only be called if Berlin/2929+2930 is applicable at the current number.
func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
s.AddAddressToAccessList(sender)
if dst != nil {
s.AddAddressToAccessList(*dst)
}
for _, addr := range precompiles {
s.AddAddressToAccessList(addr)
}
for _, el := range list {
s.AddAddressToAccessList(el.Address)
for _, key := range el.StorageKeys {
s.AddSlotToAccessList(el.Address, key)
}
}
}
// AddAddressToAccessList adds the given address to the access list
func (s *StateDB) AddAddressToAccessList(addr common.Address) {
if s.accessList.AddAddress(addr) {
s.journal.append(accessListAddAccountChange{&addr})
}
}
// AddSlotToAccessList adds the given (address, slot)-tuple to the access list
func (s *StateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
addrMod, slotMod := s.accessList.AddSlot(addr, slot)
if addrMod {
// In practice, this should not happen, since there is no way to enter the
// scope of 'address' without having the 'address' become already added
// to the access list (via call-variant, create, etc).
// Better safe than sorry, though
s.journal.append(accessListAddAccountChange{&addr})
}
if slotMod {
s.journal.append(accessListAddSlotChange{
address: &addr,
slot: &slot,
})
}
}
// AddressInAccessList returns true if the given address is in the access list.
func (s *StateDB) AddressInAccessList(addr common.Address) bool {
return s.accessList.ContainsAddress(addr)
}
// SlotInAccessList returns true if the given (address, slot)-tuple is in the access list.
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
return s.accessList.Contains(addr, slot)
}
...@@ -680,3 +680,177 @@ func TestDeleteCreateRevert(t *testing.T) { ...@@ -680,3 +680,177 @@ func TestDeleteCreateRevert(t *testing.T) {
t.Fatalf("self-destructed contract came alive") t.Fatalf("self-destructed contract came alive")
} }
} }
func TestStateDBAccessList(t *testing.T) {
// Some helpers
addr := func(a string) common.Address {
return common.HexToAddress(a)
}
slot := func(a string) common.Hash {
return common.HexToHash(a)
}
memDb := rawdb.NewMemoryDatabase()
db := NewDatabase(memDb)
state, _ := New(common.Hash{}, db)
state.accessList = newAccessList()
verifyAddrs := func(astrings ...string) {
t.Helper()
// convert to common.Address form
var addresses []common.Address
var addressMap = make(map[common.Address]struct{})
for _, astring := range astrings {
address := addr(astring)
addresses = append(addresses, address)
addressMap[address] = struct{}{}
}
// Check that the given addresses are in the access list
for _, address := range addresses {
if !state.AddressInAccessList(address) {
t.Fatalf("expected %x to be in access list", address)
}
}
// Check that only the expected addresses are present in the acesslist
for address := range state.accessList.addresses {
if _, exist := addressMap[address]; !exist {
t.Fatalf("extra address %x in access list", address)
}
}
}
verifySlots := func(addrString string, slotStrings ...string) {
if !state.AddressInAccessList(addr(addrString)) {
t.Fatalf("scope missing address/slots %v", addrString)
}
var address = addr(addrString)
// convert to common.Hash form
var slots []common.Hash
var slotMap = make(map[common.Hash]struct{})
for _, slotString := range slotStrings {
s := slot(slotString)
slots = append(slots, s)
slotMap[s] = struct{}{}
}
// Check that the expected items are in the access list
for i, s := range slots {
if _, slotPresent := state.SlotInAccessList(address, s); !slotPresent {
t.Fatalf("input %d: scope missing slot %v (address %v)", i, s, addrString)
}
}
// Check that no extra elements are in the access list
index := state.accessList.addresses[address]
if index >= 0 {
stateSlots := state.accessList.slots[index]
for s := range stateSlots {
if _, slotPresent := slotMap[s]; !slotPresent {
t.Fatalf("scope has extra slot %v (address %v)", s, addrString)
}
}
}
}
state.AddAddressToAccessList(addr("aa")) // 1
state.AddSlotToAccessList(addr("bb"), slot("01")) // 2,3
state.AddSlotToAccessList(addr("bb"), slot("02")) // 4
verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02")
// Make a copy
stateCopy1 := state.Copy()
if exp, got := 4, state.journal.length(); exp != got {
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
}
// same again, should cause no journal entries
state.AddSlotToAccessList(addr("bb"), slot("01"))
state.AddSlotToAccessList(addr("bb"), slot("02"))
state.AddAddressToAccessList(addr("aa"))
if exp, got := 4, state.journal.length(); exp != got {
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
}
// some new ones
state.AddSlotToAccessList(addr("bb"), slot("03")) // 5
state.AddSlotToAccessList(addr("aa"), slot("01")) // 6
state.AddSlotToAccessList(addr("cc"), slot("01")) // 7,8
state.AddAddressToAccessList(addr("cc"))
if exp, got := 8, state.journal.length(); exp != got {
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
}
verifyAddrs("aa", "bb", "cc")
verifySlots("aa", "01")
verifySlots("bb", "01", "02", "03")
verifySlots("cc", "01")
// now start rolling back changes
state.journal.revert(state, 7)
if _, ok := state.SlotInAccessList(addr("cc"), slot("01")); ok {
t.Fatalf("slot present, expected missing")
}
verifyAddrs("aa", "bb", "cc")
verifySlots("aa", "01")
verifySlots("bb", "01", "02", "03")
state.journal.revert(state, 6)
if state.AddressInAccessList(addr("cc")) {
t.Fatalf("addr present, expected missing")
}
verifyAddrs("aa", "bb")
verifySlots("aa", "01")
verifySlots("bb", "01", "02", "03")
state.journal.revert(state, 5)
if _, ok := state.SlotInAccessList(addr("aa"), slot("01")); ok {
t.Fatalf("slot present, expected missing")
}
verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02", "03")
state.journal.revert(state, 4)
if _, ok := state.SlotInAccessList(addr("bb"), slot("03")); ok {
t.Fatalf("slot present, expected missing")
}
verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02")
state.journal.revert(state, 3)
if _, ok := state.SlotInAccessList(addr("bb"), slot("02")); ok {
t.Fatalf("slot present, expected missing")
}
verifyAddrs("aa", "bb")
verifySlots("bb", "01")
state.journal.revert(state, 2)
if _, ok := state.SlotInAccessList(addr("bb"), slot("01")); ok {
t.Fatalf("slot present, expected missing")
}
verifyAddrs("aa", "bb")
state.journal.revert(state, 1)
if state.AddressInAccessList(addr("bb")) {
t.Fatalf("addr present, expected missing")
}
verifyAddrs("aa")
state.journal.revert(state, 0)
if state.AddressInAccessList(addr("aa")) {
t.Fatalf("addr present, expected missing")
}
if got, exp := len(state.accessList.addresses), 0; got != exp {
t.Fatalf("expected empty, got %d", got)
}
if got, exp := len(state.accessList.slots), 0; got != exp {
t.Fatalf("expected empty, got %d", got)
}
// Check the copy
// Make a copy
state = stateCopy1
verifyAddrs("aa", "bb")
verifySlots("bb", "01", "02")
if got, exp := len(state.accessList.addresses), 2; got != exp {
t.Fatalf("expected empty, got %d", got)
}
if got, exp := len(state.accessList.slots), 1; got != exp {
t.Fatalf("expected empty, got %d", got)
}
}
...@@ -79,6 +79,7 @@ type Message interface { ...@@ -79,6 +79,7 @@ type Message interface {
Nonce() uint64 Nonce() uint64
CheckNonce() bool CheckNonce() bool
Data() []byte Data() []byte
AccessList() types.AccessList
L1Timestamp() uint64 L1Timestamp() uint64
L1BlockNumber() *big.Int L1BlockNumber() *big.Int
...@@ -253,6 +254,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo ...@@ -253,6 +254,11 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
vmerr error vmerr error
) )
// The access list gets created here
if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsBerlin {
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
}
if contractCreation { if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
} else { } else {
......
// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package types
import (
"github.com/ethereum-optimism/optimism/l2geth/common"
)
//go:generate gencodec -type AccessTuple -out gen_access_tuple.go
// AccessList is an EIP-2930 access list.
type AccessList []AccessTuple
// AccessTuple is the element type of an access list.
type AccessTuple struct {
Address common.Address `json:"address" gencodec:"required"`
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"`
}
// StorageKeys returns the total number of storage keys in the access list.
func (al AccessList) StorageKeys() int {
sum := 0
for _, tuple := range al {
sum += len(tuple.StorageKeys)
}
return sum
}
...@@ -479,6 +479,7 @@ type Message struct { ...@@ -479,6 +479,7 @@ type Message struct {
gasPrice *big.Int gasPrice *big.Int
data []byte data []byte
checkNonce bool checkNonce bool
accessList AccessList
l1Timestamp uint64 l1Timestamp uint64
l1BlockNumber *big.Int l1BlockNumber *big.Int
...@@ -495,6 +496,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b ...@@ -495,6 +496,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
gasPrice: gasPrice, gasPrice: gasPrice,
data: data, data: data,
checkNonce: checkNonce, checkNonce: checkNonce,
accessList: AccessList{},
l1Timestamp: l1Timestamp, l1Timestamp: l1Timestamp,
l1BlockNumber: l1BlockNumber, l1BlockNumber: l1BlockNumber,
...@@ -502,14 +504,15 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b ...@@ -502,14 +504,15 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
} }
} }
func (m Message) From() common.Address { return m.from } func (m Message) From() common.Address { return m.from }
func (m Message) To() *common.Address { return m.to } func (m Message) To() *common.Address { return m.to }
func (m Message) GasPrice() *big.Int { return m.gasPrice } func (m Message) GasPrice() *big.Int { return m.gasPrice }
func (m Message) Value() *big.Int { return m.amount } func (m Message) Value() *big.Int { return m.amount }
func (m Message) Gas() uint64 { return m.gasLimit } func (m Message) Gas() uint64 { return m.gasLimit }
func (m Message) Nonce() uint64 { return m.nonce } func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data } func (m Message) Data() []byte { return m.data }
func (m Message) CheckNonce() bool { return m.checkNonce } func (m Message) CheckNonce() bool { return m.checkNonce }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) L1Timestamp() uint64 { return m.l1Timestamp } func (m Message) L1Timestamp() uint64 { return m.l1Timestamp }
func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber } func (m Message) L1BlockNumber() *big.Int { return m.l1BlockNumber }
......
...@@ -77,6 +77,55 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ ...@@ -77,6 +77,55 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{9}): &blake2F{}, common.BytesToAddress([]byte{9}): &blake2F{},
} }
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
// contracts used in the Berlin release.
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
}
var (
PrecompiledAddressesBerlin []common.Address
PrecompiledAddressesIstanbul []common.Address
PrecompiledAddressesByzantium []common.Address
PrecompiledAddressesHomestead []common.Address
)
func init() {
for k := range PrecompiledContractsHomestead {
PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k)
}
for k := range PrecompiledContractsByzantium {
PrecompiledAddressesByzantium = append(PrecompiledAddressesByzantium, k)
}
for k := range PrecompiledContractsIstanbul {
PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k)
}
for k := range PrecompiledContractsBerlin {
PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k)
}
}
func ActivePrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsBerlin:
return PrecompiledAddressesBerlin
case rules.IsIstanbul:
return PrecompiledAddressesIstanbul
case rules.IsByzantium:
return PrecompiledAddressesByzantium
default:
return PrecompiledAddressesHomestead
}
}
// RunPrecompiledContract runs and evaluates the output of a precompiled contract. // RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(input) gas := p.RequiredGas(input)
...@@ -170,13 +219,18 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { ...@@ -170,13 +219,18 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
} }
// bigModExp implements a native big integer exponential modular operation. // bigModExp implements a native big integer exponential modular operation.
type bigModExp struct{} type bigModExp struct {
eip2565 bool
}
var ( var (
big1 = big.NewInt(1) big1 = big.NewInt(1)
big3 = big.NewInt(3)
big4 = big.NewInt(4) big4 = big.NewInt(4)
big7 = big.NewInt(7)
big8 = big.NewInt(8) big8 = big.NewInt(8)
big16 = big.NewInt(16) big16 = big.NewInt(16)
big20 = big.NewInt(20)
big32 = big.NewInt(32) big32 = big.NewInt(32)
big64 = big.NewInt(64) big64 = big.NewInt(64)
big96 = big.NewInt(96) big96 = big.NewInt(96)
...@@ -186,6 +240,34 @@ var ( ...@@ -186,6 +240,34 @@ var (
big199680 = big.NewInt(199680) big199680 = big.NewInt(199680)
) )
// modexpMultComplexity implements bigModexp multComplexity formula, as defined in EIP-198
//
// def mult_complexity(x):
// if x <= 64: return x ** 2
// elif x <= 1024: return x ** 2 // 4 + 96 * x - 3072
// else: return x ** 2 // 16 + 480 * x - 199680
//
// where is x is max(length_of_MODULUS, length_of_BASE)
func modexpMultComplexity(x *big.Int) *big.Int {
switch {
case x.Cmp(big64) <= 0:
x.Mul(x, x) // x ** 2
case x.Cmp(big1024) <= 0:
// (x ** 2 // 4 ) + ( 96 * x - 3072)
x = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(x, x), big4),
new(big.Int).Sub(new(big.Int).Mul(big96, x), big3072),
)
default:
// (x ** 2 // 16) + (480 * x - 199680)
x = new(big.Int).Add(
new(big.Int).Div(new(big.Int).Mul(x, x), big16),
new(big.Int).Sub(new(big.Int).Mul(big480, x), big199680),
)
}
return x
}
// RequiredGas returns the gas required to execute the pre-compiled contract. // RequiredGas returns the gas required to execute the pre-compiled contract.
func (c *bigModExp) RequiredGas(input []byte) uint64 { func (c *bigModExp) RequiredGas(input []byte) uint64 {
var ( var (
...@@ -220,25 +302,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { ...@@ -220,25 +302,36 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 {
adjExpLen.Mul(big8, adjExpLen) adjExpLen.Mul(big8, adjExpLen)
} }
adjExpLen.Add(adjExpLen, big.NewInt(int64(msb))) adjExpLen.Add(adjExpLen, big.NewInt(int64(msb)))
// Calculate the gas cost of the operation // Calculate the gas cost of the operation
gas := new(big.Int).Set(math.BigMax(modLen, baseLen)) gas := new(big.Int).Set(math.BigMax(modLen, baseLen))
switch { if c.eip2565 {
case gas.Cmp(big64) <= 0: // EIP-2565 has three changes
// 1. Different multComplexity (inlined here)
// in EIP-2565 (https://eips.ethereum.org/EIPS/eip-2565):
//
// def mult_complexity(x):
// ceiling(x/8)^2
//
//where is x is max(length_of_MODULUS, length_of_BASE)
gas = gas.Add(gas, big7)
gas = gas.Div(gas, big8)
gas.Mul(gas, gas) gas.Mul(gas, gas)
case gas.Cmp(big1024) <= 0:
gas = new(big.Int).Add( gas.Mul(gas, math.BigMax(adjExpLen, big1))
new(big.Int).Div(new(big.Int).Mul(gas, gas), big4), // 2. Different divisor (`GQUADDIVISOR`) (3)
new(big.Int).Sub(new(big.Int).Mul(big96, gas), big3072), gas.Div(gas, big3)
) if gas.BitLen() > 64 {
default: return math.MaxUint64
gas = new(big.Int).Add( }
new(big.Int).Div(new(big.Int).Mul(gas, gas), big16), // 3. Minimum price of 200 gas
new(big.Int).Sub(new(big.Int).Mul(big480, gas), big199680), if gas.Uint64() < 200 {
) return 200
}
return gas.Uint64()
} }
gas = modexpMultComplexity(gas)
gas.Mul(gas, math.BigMax(adjExpLen, big1)) gas.Mul(gas, math.BigMax(adjExpLen, big1))
gas.Div(gas, new(big.Int).SetUint64(params.ModExpQuadCoeffDiv)) gas.Div(gas, big20)
if gas.BitLen() > 64 { if gas.BitLen() > 64 {
return math.MaxUint64 return math.MaxUint64
......
...@@ -91,7 +91,11 @@ func enable2200(jt *JumpTable) { ...@@ -91,7 +91,11 @@ func enable2200(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP2200 jt[SSTORE].dynamicGas = gasSStoreEIP2200
} }
func enableMinimal2929(jt *JumpTable) { // enable2929 enables "EIP-2929: Gas cost increases for state access opcodes"
// https://eips.ethereum.org/EIPS/eip-2929
func enable2929(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP2929
jt[SLOAD].constantGas = 0 jt[SLOAD].constantGas = 0
jt[SLOAD].dynamicGas = gasSLoadEIP2929 jt[SLOAD].dynamicGas = gasSLoadEIP2929
...@@ -124,3 +128,48 @@ func enableMinimal2929(jt *JumpTable) { ...@@ -124,3 +128,48 @@ func enableMinimal2929(jt *JumpTable) {
jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150 jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929
} }
// enable3529 enabled "EIP-3529: Reduction in refunds":
// - Removes refunds for selfdestructs
// - Reduces refunds for SSTORE
// - Reduces max refunds to 20% gas
func enable3529(jt *JumpTable) {
jt[SSTORE].dynamicGas = gasSStoreEIP3529
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP3529
}
// UsingOVM
// Optimism specific changes
func enableMinimal2929(jt *JumpTable) {
jt[SLOAD].constantGas = 0
jt[SLOAD].dynamicGas = gasSLoadEIP2929Optimism
jt[EXTCODECOPY].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929Optimism
jt[EXTCODESIZE].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheckOptimism
jt[EXTCODEHASH].constantGas = params.WarmStorageReadCostEIP2929
jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheckOptimism
jt[BALANCE].constantGas = params.WarmStorageReadCostEIP2929
jt[BALANCE].dynamicGas = gasEip2929AccountCheckOptimism
jt[CALL].constantGas = params.WarmStorageReadCostEIP2929
jt[CALL].dynamicGas = gasCallEIP2929Optimism
jt[CALLCODE].constantGas = params.WarmStorageReadCostEIP2929
jt[CALLCODE].dynamicGas = gasCallCodeEIP2929Optimism
jt[STATICCALL].constantGas = params.WarmStorageReadCostEIP2929
jt[STATICCALL].dynamicGas = gasStaticCallEIP2929Optimism
jt[DELEGATECALL].constantGas = params.WarmStorageReadCostEIP2929
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929Optimism
// This was previously part of the dynamic cost, but we're using it as a constantGas
// factor here
jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929Optimism
}
...@@ -27,4 +27,5 @@ var ( ...@@ -27,4 +27,5 @@ var (
ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") ErrNoCompatibleInterpreter = errors.New("no compatible interpreter")
ErrGasUintOverflow = errors.New("gas uint64 overflow")
) )
...@@ -55,6 +55,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err ...@@ -55,6 +55,9 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
if evm.chainRules.IsIstanbul { if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul precompiles = PrecompiledContractsIstanbul
} }
if evm.chainRules.IsBerlin {
precompiles = PrecompiledContractsBerlin
}
if p := precompiles[*contract.CodeAddr]; p != nil { if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract) return RunPrecompiledContract(p, input, contract)
} }
...@@ -220,6 +223,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ...@@ -220,6 +223,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if evm.chainRules.IsIstanbul { if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul precompiles = PrecompiledContractsIstanbul
} }
if evm.chainRules.IsBerlin {
precompiles = PrecompiledContractsBerlin
}
if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 { if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
...@@ -413,7 +419,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ...@@ -413,7 +419,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
nonce := evm.StateDB.GetNonce(caller.Address()) nonce := evm.StateDB.GetNonce(caller.Address())
evm.StateDB.SetNonce(caller.Address(), nonce+1) evm.StateDB.SetNonce(caller.Address(), nonce+1)
// We add this to the access list _before_ taking a snapshot. Even if the creation fails,
// the access-list change should not be rolled back
if evm.chainRules.IsBerlin {
evm.StateDB.AddAddressToAccessList(address)
}
// Ensure there's no existing contract already at the designated address // Ensure there's no existing contract already at the designated address
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
......
...@@ -57,6 +57,16 @@ type StateDB interface { ...@@ -57,6 +57,16 @@ type StateDB interface {
// is defined according to EIP161 (balance = nonce = code = 0). // is defined according to EIP161 (balance = nonce = code = 0).
Empty(common.Address) bool Empty(common.Address) bool
PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
AddressInAccessList(addr common.Address) bool
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
AddAddressToAccessList(addr common.Address)
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
AddSlotToAccessList(addr common.Address, slot common.Hash)
RevertToSnapshot(int) RevertToSnapshot(int)
Snapshot() int Snapshot() int
......
...@@ -94,8 +94,13 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { ...@@ -94,8 +94,13 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
if !cfg.JumpTable[STOP].valid { if !cfg.JumpTable[STOP].valid {
var jt JumpTable var jt JumpTable
switch { switch {
case evm.chainRules.IsBerlin:
jt = berlinInstructionSet
case evm.chainRules.IsIstanbul: case evm.chainRules.IsIstanbul:
jt = istanbulInstructionSet jt = istanbulInstructionSet
if rcfg.UsingOVM {
enableMinimal2929(&jt)
}
case evm.chainRules.IsConstantinople: case evm.chainRules.IsConstantinople:
jt = constantinopleInstructionSet jt = constantinopleInstructionSet
case evm.chainRules.IsByzantium: case evm.chainRules.IsByzantium:
...@@ -116,10 +121,6 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { ...@@ -116,10 +121,6 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
log.Error("EIP activation failed", "eip", eip, "error", err) log.Error("EIP activation failed", "eip", eip, "error", err)
} }
} }
// Enable minimal eip 2929
if rcfg.UsingOVM {
enableMinimal2929(&jt)
}
cfg.JumpTable = jt cfg.JumpTable = jt
} }
......
...@@ -61,11 +61,21 @@ var ( ...@@ -61,11 +61,21 @@ var (
byzantiumInstructionSet = newByzantiumInstructionSet() byzantiumInstructionSet = newByzantiumInstructionSet()
constantinopleInstructionSet = newConstantinopleInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet()
istanbulInstructionSet = newIstanbulInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet()
berlinInstructionSet = newBerlinInstructionSet()
) )
// JumpTable contains the EVM opcodes supported at a given fork. // JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]operation type JumpTable [256]operation
// newBerlinInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg and berlin instructions.
func newBerlinInstructionSet() JumpTable {
instructionSet := newIstanbulInstructionSet()
enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
return instructionSet
}
// newIstanbulInstructionSet returns the frontier, homestead // newIstanbulInstructionSet returns the frontier, homestead
// byzantium, contantinople and petersburg instructions. // byzantium, contantinople and petersburg instructions.
func newIstanbulInstructionSet() JumpTable { func newIstanbulInstructionSet() JumpTable {
......
...@@ -21,12 +21,14 @@ import ( ...@@ -21,12 +21,14 @@ import (
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"strings"
"time" "time"
"github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum-optimism/optimism/l2geth/common"
"github.com/ethereum-optimism/optimism/l2geth/common/hexutil" "github.com/ethereum-optimism/optimism/l2geth/common/hexutil"
"github.com/ethereum-optimism/optimism/l2geth/common/math" "github.com/ethereum-optimism/optimism/l2geth/common/math"
"github.com/ethereum-optimism/optimism/l2geth/core/types" "github.com/ethereum-optimism/optimism/l2geth/core/types"
"github.com/ethereum-optimism/optimism/l2geth/params"
) )
// Storage represents a contract's storage. // Storage represents a contract's storage.
...@@ -49,6 +51,8 @@ type LogConfig struct { ...@@ -49,6 +51,8 @@ type LogConfig struct {
DisableStorage bool // disable storage capture DisableStorage bool // disable storage capture
Debug bool // print output during capture end Debug bool // print output during capture end
Limit int // maximum length of output, but zero means unlimited Limit int // maximum length of output, but zero means unlimited
// Chain overrides, can be used to execute a trace using future fork rules
Overrides *params.ChainConfig `json:"overrides,omitempty"`
} }
//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
...@@ -254,3 +258,74 @@ func WriteLogs(writer io.Writer, logs []*types.Log) { ...@@ -254,3 +258,74 @@ func WriteLogs(writer io.Writer, logs []*types.Log) {
fmt.Fprintln(writer) fmt.Fprintln(writer)
} }
} }
type mdLogger struct {
out io.Writer
cfg *LogConfig
}
// NewMarkdownLogger creates a logger which outputs information in a format adapted
// for human readability, and is also a valid markdown table
func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
l := &mdLogger{writer, cfg}
if l.cfg == nil {
l.cfg = &LogConfig{}
}
return l
}
func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
if !create {
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(),
input, gas, value)
} else {
fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
from.String(), to.String(),
input, gas, value)
}
fmt.Fprintf(t.out, `
| Pc | Op | Cost | Stack | RStack | Refund |
|-------|-------------|------|-----------|-----------|---------|
`)
return nil
}
func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
if !t.cfg.DisableStack {
// format stack
var a []string
for _, elem := range stack.data {
a = append(a, fmt.Sprintf("%v", elem.String()))
}
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
fmt.Fprintf(t.out, "%10v |", b)
// format return stack
a = a[:0]
b = fmt.Sprintf("[%v]", strings.Join(a, ","))
fmt.Fprintf(t.out, "%10v |", b)
}
fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund())
fmt.Fprintln(t.out, "")
if err != nil {
fmt.Fprintf(t.out, "Error: %v\n", err)
}
return nil
}
func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
return nil
}
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error {
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err)
return nil
}
This diff is collapsed.
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"errors"
"github.com/ethereum-optimism/optimism/l2geth/common"
"github.com/ethereum-optimism/optimism/l2geth/common/math"
"github.com/ethereum-optimism/optimism/l2geth/params"
)
// These functions are modified to work without the access list logic.
// Access lists will be added in the future and these functions can
// be reverted to their upstream implementations.
// Modified dynamic gas cost to always return the cold cost
func gasSLoadEIP2929Optimism(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return params.ColdSloadCostEIP2929, nil
}
func gasExtCodeCopyEIP2929Optimism(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
// memory expansion first (dynamic part of pre-2929 implementation)
gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
if err != nil {
return 0, err
}
var overflow bool
if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCostEIP2929-params.WarmStorageReadCostEIP2929); overflow {
return 0, errors.New("gas uint64 overflow")
}
return gas, nil
}
func gasEip2929AccountCheckOptimism(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
return params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929, nil
}
func makeCallVariantGasCallEIP2929Optimism(oldCalculator gasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
// the cost to charge for cold access, if any, is Cold - Warm
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
if !contract.UseGas(coldCost) {
return 0, ErrOutOfGas
}
// Now call the old calculator, which takes into account
// - create new account
// - transfer value
// - memory expansion
// - 63/64ths rule
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
if err != nil {
return gas, err
}
// In case of a cold access, we temporarily add the cold charge back, and also
// add it to the returned gas. By adding it to the return, it will be charged
// outside of this function, as part of the dynamic gas, and that will make it
// also become correctly reported to tracers.
contract.Gas += coldCost
return gas + coldCost, nil
}
}
var (
gasCallEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasCall)
gasDelegateCallEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasDelegateCall)
gasStaticCallEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasStaticCall)
gasCallCodeEIP2929Optimism = makeCallVariantGasCallEIP2929Optimism(gasCallCode)
gasSelfdestructEIP2929Optimism = makeSelfdestructGasFnOptimism(true)
)
// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539
func makeSelfdestructGasFnOptimism(refundsEnabled bool) gasFunc {
gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
address := common.BigToAddress(stack.peek())
gas := params.ColdAccountAccessCostEIP2929
// if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas
}
if refundsEnabled && !evm.StateDB.HasSuicided(contract.Address()) {
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
}
return gas, nil
}
return gasFunc
}
...@@ -106,6 +106,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { ...@@ -106,6 +106,9 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
vmenv = NewEnv(cfg) vmenv = NewEnv(cfg)
sender = vm.AccountRef(cfg.Origin) sender = vm.AccountRef(cfg.Origin)
) )
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
cfg.State.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
}
cfg.State.CreateAccount(address) cfg.State.CreateAccount(address)
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code) cfg.State.SetCode(address, code)
...@@ -135,7 +138,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { ...@@ -135,7 +138,9 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
vmenv = NewEnv(cfg) vmenv = NewEnv(cfg)
sender = vm.AccountRef(cfg.Origin) sender = vm.AccountRef(cfg.Origin)
) )
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
cfg.State.PrepareAccessList(cfg.Origin, nil, vm.ActivePrecompiles(rules), nil)
}
// Call the code with the given configuration. // Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create( code, address, leftOverGas, err := vmenv.Create(
sender, sender,
...@@ -157,6 +162,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er ...@@ -157,6 +162,11 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
vmenv := NewEnv(cfg) vmenv := NewEnv(cfg)
sender := cfg.State.GetOrNewStateObject(cfg.Origin) sender := cfg.State.GetOrNewStateObject(cfg.Origin)
statedb := cfg.State
if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin {
statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules), nil)
}
// Call the code with the given configuration. // Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call( ret, leftOverGas, err := vmenv.Call(
sender, sender,
......
...@@ -17,12 +17,15 @@ ...@@ -17,12 +17,15 @@
package runtime package runtime
import ( import (
"fmt"
"math/big" "math/big"
"os"
"strings" "strings"
"testing" "testing"
"github.com/ethereum-optimism/optimism/l2geth/accounts/abi" "github.com/ethereum-optimism/optimism/l2geth/accounts/abi"
"github.com/ethereum-optimism/optimism/l2geth/common" "github.com/ethereum-optimism/optimism/l2geth/common"
"github.com/ethereum-optimism/optimism/l2geth/core/asm"
"github.com/ethereum-optimism/optimism/l2geth/core/rawdb" "github.com/ethereum-optimism/optimism/l2geth/core/rawdb"
"github.com/ethereum-optimism/optimism/l2geth/core/state" "github.com/ethereum-optimism/optimism/l2geth/core/state"
"github.com/ethereum-optimism/optimism/l2geth/core/vm" "github.com/ethereum-optimism/optimism/l2geth/core/vm"
...@@ -203,3 +206,115 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) { ...@@ -203,3 +206,115 @@ func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents // initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056") benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
} }
// TestEip2929Cases contains various testcases that are used for
// EIP-2929 about gas repricings
func TestEip2929Cases(t *testing.T) {
id := 1
prettyPrint := func(comment string, code []byte) {
instrs := make([]string, 0)
it := asm.NewInstructionIterator(code)
for it.Next() {
if it.Arg() != nil && 0 < len(it.Arg()) {
instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg()))
} else {
instrs = append(instrs, fmt.Sprintf("%v", it.Op()))
}
}
ops := strings.Join(instrs, ", ")
fmt.Printf("### Case %d\n\n", id)
id++
fmt.Printf("%v\n\nBytecode: \n```\n0x%x\n```\nOperations: \n```\n%v\n```\n\n",
comment,
code, ops)
Execute(code, nil, &Config{
EVMConfig: vm.Config{
Debug: true,
Tracer: vm.NewMarkdownLogger(nil, os.Stdout),
ExtraEips: []int{2929},
},
})
}
{ // First eip testcase
code := []byte{
// Three checks against a precompile
byte(vm.PUSH1), 1, byte(vm.EXTCODEHASH), byte(vm.POP),
byte(vm.PUSH1), 2, byte(vm.EXTCODESIZE), byte(vm.POP),
byte(vm.PUSH1), 3, byte(vm.BALANCE), byte(vm.POP),
// Three checks against a non-precompile
byte(vm.PUSH1), 0xf1, byte(vm.EXTCODEHASH), byte(vm.POP),
byte(vm.PUSH1), 0xf2, byte(vm.EXTCODESIZE), byte(vm.POP),
byte(vm.PUSH1), 0xf3, byte(vm.BALANCE), byte(vm.POP),
// Same three checks (should be cheaper)
byte(vm.PUSH1), 0xf2, byte(vm.EXTCODEHASH), byte(vm.POP),
byte(vm.PUSH1), 0xf3, byte(vm.EXTCODESIZE), byte(vm.POP),
byte(vm.PUSH1), 0xf1, byte(vm.BALANCE), byte(vm.POP),
// Check the origin, and the 'this'
byte(vm.ORIGIN), byte(vm.BALANCE), byte(vm.POP),
byte(vm.ADDRESS), byte(vm.BALANCE), byte(vm.POP),
byte(vm.STOP),
}
prettyPrint("This checks `EXT`(codehash,codesize,balance) of precompiles, which should be `100`, "+
"and later checks the same operations twice against some non-precompiles. "+
"Those are cheaper second time they are accessed. Lastly, it checks the `BALANCE` of `origin` and `this`.", code)
}
{ // EXTCODECOPY
code := []byte{
// extcodecopy( 0xff,0,0,0,0)
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY),
// extcodecopy( 0xff,0,0,0,0)
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY),
// extcodecopy( this,0,0,0,0)
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
byte(vm.ADDRESS), byte(vm.EXTCODECOPY),
byte(vm.STOP),
}
prettyPrint("This checks `extcodecopy( 0xff,0,0,0,0)` twice, (should be expensive first time), "+
"and then does `extcodecopy( this,0,0,0,0)`.", code)
}
{ // SLOAD + SSTORE
code := []byte{
// Add slot `0x1` to access list
byte(vm.PUSH1), 0x01, byte(vm.SLOAD), byte(vm.POP), // SLOAD( 0x1) (add to access list)
// Write to `0x1` which is already in access list
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x01, byte(vm.SSTORE), // SSTORE( loc: 0x01, val: 0x11)
// Write to `0x2` which is not in access list
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11)
// Write again to `0x2`
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11)
// Read slot in access list (0x2)
byte(vm.PUSH1), 0x02, byte(vm.SLOAD), // SLOAD( 0x2)
// Read slot in access list (0x1)
byte(vm.PUSH1), 0x01, byte(vm.SLOAD), // SLOAD( 0x1)
}
prettyPrint("This checks `sload( 0x1)` followed by `sstore(loc: 0x01, val:0x11)`, then 'naked' sstore:"+
"`sstore(loc: 0x02, val:0x11)` twice, and `sload(0x2)`, `sload(0x1)`. ", code)
}
{ // Call variants
code := []byte{
// identity precompile
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0x04, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP),
// random account - call 1
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP),
// random account - call 2
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.STATICCALL), byte(vm.POP),
}
prettyPrint("This calls the `identity`-precompile (cheap), then calls an account (expensive) and `staticcall`s the same"+
"account (cheap)", code)
}
}
...@@ -141,13 +141,7 @@ func (b *EthAPIBackend) SequencerClientHttp() string { ...@@ -141,13 +141,7 @@ func (b *EthAPIBackend) SequencerClientHttp() string {
} }
func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) {
// Pending block is only known by the miner if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber {
if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock()
return block.Header(), nil
}
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock().Header(), nil return b.eth.blockchain.CurrentBlock().Header(), nil
} }
return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil
...@@ -175,13 +169,7 @@ func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*ty ...@@ -175,13 +169,7 @@ func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*ty
} }
func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
// Pending block is only known by the miner if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber {
if number == rpc.PendingBlockNumber {
block := b.eth.miner.PendingBlock()
return block, nil
}
// Otherwise resolve and return the block
if number == rpc.LatestBlockNumber {
return b.eth.blockchain.CurrentBlock(), nil return b.eth.blockchain.CurrentBlock(), nil
} }
return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil return b.eth.blockchain.GetBlockByNumber(uint64(number)), nil
...@@ -213,12 +201,6 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r ...@@ -213,12 +201,6 @@ func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash r
} }
func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
// Pending state is only known by the miner
if number == rpc.PendingBlockNumber {
block, state := b.eth.miner.Pending()
return state, block.Header(), nil
}
// Otherwise resolve the block number and return its state
header, err := b.HeaderByNumber(ctx, number) header, err := b.HeaderByNumber(ctx, number)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
...@@ -271,7 +253,7 @@ func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { ...@@ -271,7 +253,7 @@ func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(blockHash) return b.eth.blockchain.GetTdByHash(blockHash)
} }
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg *vm.Config) (*vm.EVM, func() error, error) {
// This was removed upstream: // This was removed upstream:
// https://github.com/ethereum-optimism/optimism/l2geth/commit/39f502329fac4640cfb71959c3496f19ea88bc85#diff-9886da3412b43831145f62cec6e895eb3613a175b945e5b026543b7463454603 // https://github.com/ethereum-optimism/optimism/l2geth/commit/39f502329fac4640cfb71959c3496f19ea88bc85#diff-9886da3412b43831145f62cec6e895eb3613a175b945e5b026543b7463454603
// We're throwing this behind a UsingOVM flag for now as to not break // We're throwing this behind a UsingOVM flag for now as to not break
...@@ -280,9 +262,11 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *sta ...@@ -280,9 +262,11 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *sta
state.SetBalance(msg.From(), math.MaxBig256) state.SetBalance(msg.From(), math.MaxBig256)
} }
vmError := func() error { return nil } vmError := func() error { return nil }
if vmCfg == nil {
vmCfg = b.eth.blockchain.GetVMConfig()
}
context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil)
return vm.NewEVM(context, state, b.eth.blockchain.Config(), *b.eth.blockchain.GetVMConfig()), vmError, nil return vm.NewEVM(context, state, b.eth.blockchain.Config(), *vmCfg), vmError, nil
} }
func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
......
...@@ -38,6 +38,7 @@ import ( ...@@ -38,6 +38,7 @@ import (
"github.com/ethereum-optimism/optimism/l2geth/eth/tracers" "github.com/ethereum-optimism/optimism/l2geth/eth/tracers"
"github.com/ethereum-optimism/optimism/l2geth/internal/ethapi" "github.com/ethereum-optimism/optimism/l2geth/internal/ethapi"
"github.com/ethereum-optimism/optimism/l2geth/log" "github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum-optimism/optimism/l2geth/params"
"github.com/ethereum-optimism/optimism/l2geth/rlp" "github.com/ethereum-optimism/optimism/l2geth/rlp"
"github.com/ethereum-optimism/optimism/l2geth/rpc" "github.com/ethereum-optimism/optimism/l2geth/rpc"
"github.com/ethereum-optimism/optimism/l2geth/trie" "github.com/ethereum-optimism/optimism/l2geth/trie"
...@@ -106,17 +107,15 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block ...@@ -106,17 +107,15 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block
var from, to *types.Block var from, to *types.Block
switch start { switch start {
case rpc.PendingBlockNumber:
from = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber: case rpc.LatestBlockNumber:
case rpc.PendingBlockNumber:
from = api.eth.blockchain.CurrentBlock() from = api.eth.blockchain.CurrentBlock()
default: default:
from = api.eth.blockchain.GetBlockByNumber(uint64(start)) from = api.eth.blockchain.GetBlockByNumber(uint64(start))
} }
switch end { switch end {
case rpc.PendingBlockNumber:
to = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber: case rpc.LatestBlockNumber:
case rpc.PendingBlockNumber:
to = api.eth.blockchain.CurrentBlock() to = api.eth.blockchain.CurrentBlock()
default: default:
to = api.eth.blockchain.GetBlockByNumber(uint64(end)) to = api.eth.blockchain.GetBlockByNumber(uint64(end))
...@@ -357,9 +356,8 @@ func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.B ...@@ -357,9 +356,8 @@ func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.B
var block *types.Block var block *types.Block
switch number { switch number {
case rpc.PendingBlockNumber:
block = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber: case rpc.LatestBlockNumber:
case rpc.PendingBlockNumber:
block = api.eth.blockchain.CurrentBlock() block = api.eth.blockchain.CurrentBlock()
default: default:
block = api.eth.blockchain.GetBlockByNumber(uint64(number)) block = api.eth.blockchain.GetBlockByNumber(uint64(number))
...@@ -561,9 +559,30 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block ...@@ -561,9 +559,30 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
// Execute transaction, either tracing all or just the requested one // Execute transaction, either tracing all or just the requested one
var ( var (
signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number()) dumps []string
dumps []string signer = types.MakeSigner(api.eth.blockchain.Config(), block.Number())
chainConfig = api.eth.blockchain.Config()
canon = true
) )
// Check if there are any overrides: the caller may wish to enable a future
// fork when executing this block. Note, such overrides are only applicable to the
// actual specified block, not any preceding blocks that we have to go through
// in order to obtain the state.
// Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork`
if config != nil && config.Overrides != nil {
// Copy the config, to not screw up the main config
// Note: the Clique-part is _not_ deep copied
chainConfigCopy := new(params.ChainConfig)
*chainConfigCopy = *chainConfig
chainConfig = chainConfigCopy
if berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil {
chainConfig.BerlinBlock = berlin
canon = false
}
}
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
// Prepare the trasaction for un-traced execution // Prepare the trasaction for un-traced execution
var ( var (
...@@ -579,7 +598,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block ...@@ -579,7 +598,9 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
if tx.Hash() == txHash || txHash == (common.Hash{}) { if tx.Hash() == txHash || txHash == (common.Hash{}) {
// Generate a unique temporary file to dump it into // Generate a unique temporary file to dump it into
prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4])
if !canon {
prefix = fmt.Sprintf("%valt-", prefix)
}
dump, err = ioutil.TempFile(os.TempDir(), prefix) dump, err = ioutil.TempFile(os.TempDir(), prefix)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -595,7 +616,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block ...@@ -595,7 +616,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
} }
} }
// Execute the transaction and flush any traces to disk // Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf) vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vmConf)
_, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
if writer != nil { if writer != nil {
writer.Flush() writer.Flush()
...@@ -755,8 +776,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v ...@@ -755,8 +776,19 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
default: default:
tracer = vm.NewStructLogger(config.LogConfig) tracer = vm.NewStructLogger(config.LogConfig)
} }
chainConfig := api.eth.blockchain.Config()
if config != nil && config.LogConfig != nil && config.LogConfig.Overrides != nil {
chainConfigCopy := new(params.ChainConfig)
*chainConfigCopy = *chainConfig
chainConfig = chainConfigCopy
if berlin := config.LogConfig.Overrides.BerlinBlock; berlin != nil {
chainConfig.BerlinBlock = berlin
}
}
// Run the transaction with tracing enabled. // Run the transaction with tracing enabled.
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) vmenv := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{Debug: true, Tracer: tracer})
ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
if err != nil { if err != nil {
......
...@@ -776,7 +776,7 @@ func (b *Block) Call(ctx context.Context, args struct { ...@@ -776,7 +776,7 @@ func (b *Block) Call(ctx context.Context, args struct {
return nil, err return nil, err
} }
} }
result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, &vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
status := hexutil.Uint64(1) status := hexutil.Uint64(1)
if failed { if failed {
status = 0 status = 0
...@@ -842,7 +842,7 @@ func (p *Pending) Call(ctx context.Context, args struct { ...@@ -842,7 +842,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.CallArgs Data ethapi.CallArgs
}) (*CallResult, error) { }) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, &vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
status := hexutil.Uint64(1) status := hexutil.Uint64(1)
if failed { if failed {
status = 0 status = 0
......
...@@ -120,6 +120,8 @@ type CallMsg struct { ...@@ -120,6 +120,8 @@ type CallMsg struct {
Value *big.Int // amount of wei sent along with the call Value *big.Int // amount of wei sent along with the call
Data []byte // input data, usually an ABI-encoded contract method invocation Data []byte // input data, usually an ABI-encoded contract method invocation
AccessList types.AccessList // EIP-2930 access list.
L1Timestamp uint64 L1Timestamp uint64
L1BlockNumber *big.Int L1BlockNumber *big.Int
QueueOrigin types.QueueOrigin QueueOrigin types.QueueOrigin
......
...@@ -799,7 +799,7 @@ type account struct { ...@@ -799,7 +799,7 @@ type account struct {
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
} }
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) { func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg *vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
...@@ -910,7 +910,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo ...@@ -910,7 +910,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
defer cancel() defer cancel()
// Get a new instance of the EVM. // Get a new instance of the EVM.
evm, vmError, err := b.GetEVM(ctx, msg, state, header) evm, vmError, err := b.GetEVM(ctx, msg, state, header, vmCfg)
if err != nil { if err != nil {
return nil, 0, false, err return nil, 0, false, err
} }
...@@ -946,7 +946,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr ...@@ -946,7 +946,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
if overrides != nil { if overrides != nil {
accounts = *overrides accounts = *overrides
} }
result, _, failed, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) result, _, failed, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, &vm.Config{}, 5*time.Second, s.b.RPCGasCap())
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -989,11 +989,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash ...@@ -989,11 +989,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
} }
cap = hi cap = hi
// Set sender address or use a default if none specified if !rcfg.UsingOVM {
if args.From == nil { // Set sender address or use a default if none specified
if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { if args.From == nil {
if accounts := wallets[0].Accounts(); len(accounts) > 0 { if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
args.From = &accounts[0].Address if accounts := wallets[0].Accounts(); len(accounts) > 0 {
args.From = &accounts[0].Address
}
} }
} }
} }
...@@ -1005,7 +1007,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash ...@@ -1005,7 +1007,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
executable := func(gas uint64) (bool, []byte) { executable := func(gas uint64) (bool, []byte) {
args.Gas = (*hexutil.Uint64)(&gas) args.Gas = (*hexutil.Uint64)(&gas)
res, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) res, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, &vm.Config{}, 0, gasCap)
if err != nil || failed { if err != nil || failed {
return false, res return false, res
} }
......
...@@ -59,7 +59,7 @@ type Backend interface { ...@@ -59,7 +59,7 @@ type Backend interface {
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
GetTd(hash common.Hash) *big.Int GetTd(hash common.Hash) *big.Int
GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg *vm.Config) (*vm.EVM, func() error, error)
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
......
...@@ -199,7 +199,7 @@ func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int { ...@@ -199,7 +199,7 @@ func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int {
return b.eth.blockchain.GetTdByHash(hash) return b.eth.blockchain.GetTdByHash(hash)
} }
func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg *vm.Config) (*vm.EVM, func() error, error) {
state.SetBalance(msg.From(), math.MaxBig256) state.SetBalance(msg.From(), math.MaxBig256)
context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) context := core.NewEVMContext(msg, header, b.eth.blockchain, nil)
return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil
......
...@@ -215,16 +215,16 @@ var ( ...@@ -215,16 +215,16 @@ var (
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // adding flags to the config to also have to set these fields.
AllEthashProtocolChanges = &ChainConfig{big.NewInt(108), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} AllEthashProtocolChanges = &ChainConfig{big.NewInt(108), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus. // and accepted by the Ethereum core developers into the Clique consensus.
// //
// This configuration is intentionally not using keyed fields to force anyone // This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields. // adding flags to the config to also have to set these fields.
AllCliqueProtocolChanges = &ChainConfig{big.NewInt(420), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} AllCliqueProtocolChanges = &ChainConfig{big.NewInt(420), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}}
TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil} TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil}
TestRules = TestChainConfig.Rules(new(big.Int)) TestRules = TestChainConfig.Rules(new(big.Int))
) )
...@@ -295,7 +295,9 @@ type ChainConfig struct { ...@@ -295,7 +295,9 @@ type ChainConfig struct {
PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople) PetersburgBlock *big.Int `json:"petersburgBlock,omitempty"` // Petersburg switch block (nil = same as Constantinople)
IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul)
MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated)
EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) BerlinBlock *big.Int `json:"berlinBlock,omitempty"` // Berlin switch block (nil = no fork, 0 = already on berlin)
EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated)
// Various consensus engines // Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"` Ethash *EthashConfig `json:"ethash,omitempty"`
...@@ -332,7 +334,7 @@ func (c *ChainConfig) String() string { ...@@ -332,7 +334,7 @@ func (c *ChainConfig) String() string {
default: default:
engine = "unknown" engine = "unknown"
} }
return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Engine: %v}", return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, Engine: %v}",
c.ChainID, c.ChainID,
c.HomesteadBlock, c.HomesteadBlock,
c.DAOForkBlock, c.DAOForkBlock,
...@@ -345,6 +347,7 @@ func (c *ChainConfig) String() string { ...@@ -345,6 +347,7 @@ func (c *ChainConfig) String() string {
c.PetersburgBlock, c.PetersburgBlock,
c.IstanbulBlock, c.IstanbulBlock,
c.MuirGlacierBlock, c.MuirGlacierBlock,
c.BerlinBlock,
engine, engine,
) )
} }
...@@ -401,6 +404,11 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool { ...@@ -401,6 +404,11 @@ func (c *ChainConfig) IsIstanbul(num *big.Int) bool {
return isForked(c.IstanbulBlock, num) return isForked(c.IstanbulBlock, num)
} }
// IsBerlin returns whether num is either equal to the Berlin fork block or greater.
func (c *ChainConfig) IsBerlin(num *big.Int) bool {
return isForked(c.BerlinBlock, num)
}
// IsEWASM returns whether num represents a block number after the EWASM fork // IsEWASM returns whether num represents a block number after the EWASM fork
func (c *ChainConfig) IsEWASM(num *big.Int) bool { func (c *ChainConfig) IsEWASM(num *big.Int) bool {
return isForked(c.EWASMBlock, num) return isForked(c.EWASMBlock, num)
...@@ -442,6 +450,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { ...@@ -442,6 +450,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{"petersburgBlock", c.PetersburgBlock}, {"petersburgBlock", c.PetersburgBlock},
{"istanbulBlock", c.IstanbulBlock}, {"istanbulBlock", c.IstanbulBlock},
{"muirGlacierBlock", c.MuirGlacierBlock}, {"muirGlacierBlock", c.MuirGlacierBlock},
{name: "berlinBlock", block: c.BerlinBlock},
} { } {
if lastFork.name != "" { if lastFork.name != "" {
// Next one must be higher number // Next one must be higher number
...@@ -498,6 +507,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi ...@@ -498,6 +507,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) {
return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock)
} }
if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) {
return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock)
}
if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) { if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock) return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
} }
...@@ -568,6 +580,7 @@ type Rules struct { ...@@ -568,6 +580,7 @@ type Rules struct {
ChainID *big.Int ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin bool
} }
// Rules ensures c's ChainID is not nil. // Rules ensures c's ChainID is not nil.
...@@ -586,5 +599,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { ...@@ -586,5 +599,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
IsConstantinople: c.IsConstantinople(num), IsConstantinople: c.IsConstantinople(num),
IsPetersburg: c.IsPetersburg(num), IsPetersburg: c.IsPetersburg(num),
IsIstanbul: c.IsIstanbul(num), IsIstanbul: c.IsIstanbul(num),
IsBerlin: c.IsBerlin(num),
} }
} }
...@@ -52,7 +52,11 @@ const ( ...@@ -52,7 +52,11 @@ const (
NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value
NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value
SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed SstoreSentryGasEIP2200 uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed
SstoreSetGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero
SstoreResetGasEIP2200 uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else
SstoreClearsScheduleRefundEIP2200 uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot
SstoreNoopGasEIP2200 uint64 = 800 // Once per SSTORE operation if the value doesn't change. SstoreNoopGasEIP2200 uint64 = 800 // Once per SSTORE operation if the value doesn't change.
SstoreDirtyGasEIP2200 uint64 = 800 // Once per SSTORE operation if a dirty value is changed. SstoreDirtyGasEIP2200 uint64 = 800 // Once per SSTORE operation if a dirty value is changed.
SstoreInitGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero SstoreInitGasEIP2200 uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero
...@@ -65,23 +69,31 @@ const ( ...@@ -65,23 +69,31 @@ const (
ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST
WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST
// In EIP-2200: SstoreResetGas was 5000.
// In EIP-2929: SstoreResetGas was changed to '5000 - COLD_SLOAD_COST'.
// In EIP-3529: SSTORE_CLEARS_SCHEDULE is defined as SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST
// Which becomes: 5000 - 2100 + 1900 = 4800
SstoreClearsScheduleRefundEIP3529 uint64 = SstoreResetGasEIP2200 - ColdSloadCostEIP2929 + TxAccessListStorageKeyGas
JumpdestGas uint64 = 1 // Once per JUMPDEST operation. JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
CreateDataGas uint64 = 200 // CreateDataGas uint64 = 200 //
CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack. CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack.
ExpGas uint64 = 10 // Once per EXP instruction ExpGas uint64 = 10 // Once per EXP instruction
LogGas uint64 = 375 // Per LOG* operation. LogGas uint64 = 375 // Per LOG* operation.
CopyGas uint64 = 3 // CopyGas uint64 = 3 //
StackLimit uint64 = 1024 // Maximum size of VM stack allowed. StackLimit uint64 = 1024 // Maximum size of VM stack allowed.
TierStepGas uint64 = 0 // Once per operation, for a selection of them. TierStepGas uint64 = 0 // Once per operation, for a selection of them.
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
Create2Gas uint64 = 32000 // Once per CREATE2 operation Create2Gas uint64 = 32000 // Once per CREATE2 operation
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions. TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions.
TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul) TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
// These have been changed during the course of the chain // These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
......
#!/bin/bash
# script to help simplify l2geth initialization
# it needs a path on the filesystem to the state
# dump
set -eou pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
REPO=$DIR/..
STATE_DUMP=${STATE_DUMP:-$REPO/../packages/contracts/dist/dumps/state-dump.latest.json}
DATADIR=${DATADIR:-$HOME/.ethereum}
# These are the initial key and address that must be used for the clique
# signer on the optimism network. All nodes must be initialized with this
# key before they are able to join the network and sync correctly.
# The signer address needs to be in the genesis block's extradata.
SIGNER_KEY=6587ae678cf4fc9a33000cdbf9f35226b71dcc6a4684a31203241f9bcfd55d27
SIGNER=0x00000398232e2064f896018496b4b44b3d62751f
mkdir -p $DATADIR
if [[ ! -f $STATE_DUMP ]]; then
echo "Cannot find $STATE_DUMP"
exit 1
fi
# Add funds to the signer account so that it can be used to send transactions
if [[ ! -z "$DEVELOPMENT" ]]; then
echo "Setting up development genesis"
echo "Assuming that the initial clique signer is $SIGNER, this is configured in genesis extradata"
DUMP=$(cat $STATE_DUMP | jq '.alloc += {"0x00000398232e2064f896018496b4b44b3d62751f": {balance: "10000000000000000000"}}')
TEMP=$(mktemp)
echo "$DUMP" | jq . > $TEMP
STATE_DUMP=$TEMP
fi
geth="$REPO/build/bin/geth"
USING_OVM=true $geth init --datadir $DATADIR $STATE_DUMP
echo "6587ae678cf4fc9a33000cdbf9f35226b71dcc6a4684a31203241f9bcfd55d27" \
> $DATADIR/keyfile
echo "password" > $DATADIR/password
USING_OVM=true $geth account import \
--datadir $DATADIR --password $DATADIR/password $DATADIR/keyfile
...@@ -6,16 +6,18 @@ REPO=$DIR/.. ...@@ -6,16 +6,18 @@ REPO=$DIR/..
IS_VERIFIER= IS_VERIFIER=
ROLLUP_SYNC_SERVICE_ENABLE=true ROLLUP_SYNC_SERVICE_ENABLE=true
DATADIR=$HOME/.ethereum DATADIR=$HOME/.ethereum
TARGET_GAS_LIMIT=11000000 TARGET_GAS_LIMIT=15000000
ETH1_CTC_DEPLOYMENT_HEIGHT=12686738 ETH1_CTC_DEPLOYMENT_HEIGHT=12686738
ROLLUP_CLIENT_HTTP=http://localhost:7878 ROLLUP_CLIENT_HTTP=http://localhost:7878
ROLLUP_POLL_INTERVAL=15s ROLLUP_POLL_INTERVAL=15s
ROLLUP_TIMESTAMP_REFRESH=3m ROLLUP_TIMESTAMP_REFRESH=15s
CACHE=1024 CACHE=1024
RPC_PORT=8545 RPC_PORT=8545
WS_PORT=8546 WS_PORT=8546
VERBOSITY=3 VERBOSITY=3
ROLLUP_BACKEND=l2 ROLLUP_BACKEND=l2
CHAIN_ID=69
BLOCK_SIGNER_ADDRESS=0x00000398232E2064F896018496b4b44b3D62751F
USAGE=" USAGE="
Start the Sequencer or Verifier with most configuration pre-set. Start the Sequencer or Verifier with most configuration pre-set.
...@@ -174,15 +176,22 @@ cmd="$cmd --ws" ...@@ -174,15 +176,22 @@ cmd="$cmd --ws"
cmd="$cmd --wsaddr 0.0.0.0" cmd="$cmd --wsaddr 0.0.0.0"
cmd="$cmd --wsport $WS_PORT" cmd="$cmd --wsport $WS_PORT"
cmd="$cmd --wsorigins '*'" cmd="$cmd --wsorigins '*'"
cmd="$cmd --rpcapi 'eth,net,rollup,web3,debug'" cmd="$cmd --rpcapi eth,net,rollup,web3,debug,personal"
cmd="$cmd --gasprice 0" cmd="$cmd --gasprice 0"
cmd="$cmd --nousb" cmd="$cmd --nousb"
cmd="$cmd --gcmode=archive" cmd="$cmd --gcmode=archive"
cmd="$cmd --ipcdisable" cmd="$cmd --nodiscover"
cmd="$cmd --mine"
cmd="$cmd --password=$DATADIR/password"
cmd="$cmd --allow-insecure-unlock"
cmd="$cmd --unlock=$BLOCK_SIGNER_ADDRESS"
cmd="$cmd --miner.etherbase=$BLOCK_SIGNER_ADDRESS"
cmd="$cmd --txpool.pricelimit 0"
if [[ ! -z "$IS_VERIFIER" ]]; then if [[ ! -z "$IS_VERIFIER" ]]; then
cmd="$cmd --rollup.verifier" cmd="$cmd --rollup.verifier"
fi fi
cmd="$cmd --verbosity=$VERBOSITY" cmd="$cmd --verbosity=$VERBOSITY"
echo -e "Running:\nTARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd" echo -e "Running:\nTARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd"
eval env TARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd TARGET_GAS_LIMIT=$TARGET_GAS_LIMIT USING_OVM=true $cmd
...@@ -181,6 +181,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config) (*stat ...@@ -181,6 +181,16 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config) (*stat
context.GetHash = vmTestBlockHash context.GetHash = vmTestBlockHash
evm := vm.NewEVM(context, statedb, config, vmconfig) evm := vm.NewEVM(context, statedb, config, vmconfig)
if config.IsBerlin(context.BlockNumber) {
statedb.AddAddressToAccessList(msg.From())
if dst := msg.To(); dst != nil {
statedb.AddAddressToAccessList(*dst)
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range vm.ActivePrecompiles(config.Rules(context.BlockNumber)) {
statedb.AddAddressToAccessList(addr)
}
}
gaspool := new(core.GasPool) gaspool := new(core.GasPool)
gaspool.AddGas(block.GasLimit()) gaspool.AddGas(block.GasLimit())
snapshot := statedb.Snapshot() snapshot := statedb.Snapshot()
......
# Changelog # Changelog
## 0.4.15
### Patch Changes
- ae4a90d9: Adds a fix for the BSS to account for new timestamp logic in L2Geth
- ca547c4e: Import performance to not couple batch submitter to version of nodejs that has performance as a builtin
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
- @eth-optimism/contracts@0.5.10
## 0.4.14 ## 0.4.14
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/batch-submitter", "name": "@eth-optimism/batch-submitter",
"version": "0.4.14", "version": "0.4.15",
"description": "[Optimism] Service for submitting transactions and transaction results", "description": "[Optimism] Service for submitting transactions and transaction results",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -34,8 +34,8 @@ ...@@ -34,8 +34,8 @@
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.2.1", "@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.9", "@eth-optimism/contracts": "0.5.10",
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"@eth-optimism/ynatm": "^0.2.2", "@eth-optimism/ynatm": "^0.2.2",
"@ethersproject/abstract-provider": "^5.4.1", "@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.4.5",
......
/* External Imports */ /* External Imports */
import { performance } from 'perf_hooks'
import { Promise as bPromise } from 'bluebird' import { Promise as bPromise } from 'bluebird'
import { Contract, Signer, providers } from 'ethers' import { Contract, Signer, providers } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider' import { TransactionReceipt } from '@ethersproject/abstract-provider'
......
/* External Imports */ /* External Imports */
import { performance } from 'perf_hooks'
import { Promise as bPromise } from 'bluebird' import { Promise as bPromise } from 'bluebird'
import { Signer, ethers, Contract, providers } from 'ethers' import { Signer, ethers, Contract, providers } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider' import { TransactionReceipt } from '@ethersproject/abstract-provider'
...@@ -683,10 +685,18 @@ export class TransactionBatchSubmitter extends BatchSubmitter { ...@@ -683,10 +685,18 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
queued: BatchElement[] queued: BatchElement[]
}> = [] }> = []
for (const block of blocks) { for (const block of blocks) {
// Create a new context in certain situations
if ( if (
(lastBlockIsSequencerTx === false && block.isSequencerTx === true) || // If there are no contexts yet, create a new context.
groupedBlocks.length === 0 || groupedBlocks.length === 0 ||
(block.timestamp !== lastTimestamp && block.isSequencerTx === true) || // If the last block was an L1 to L2 transaction, but the next block is a Sequencer
// transaction, create a new context.
(lastBlockIsSequencerTx === false && block.isSequencerTx === true) ||
// If the timestamp of the last block differs from the timestamp of the current block,
// create a new context. Applies to both L1 to L2 transactions and Sequencer transactions.
block.timestamp !== lastTimestamp ||
// If the block number of the last block differs from the block number of the current block,
// create a new context. ONLY applies to Sequencer transactions.
(block.blockNumber !== lastBlockNumber && block.isSequencerTx === true) (block.blockNumber !== lastBlockNumber && block.isSequencerTx === true)
) { ) {
groupedBlocks.push({ groupedBlocks.push({
...@@ -694,6 +704,7 @@ export class TransactionBatchSubmitter extends BatchSubmitter { ...@@ -694,6 +704,7 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
queued: [], queued: [],
}) })
} }
const cur = groupedBlocks.length - 1 const cur = groupedBlocks.length - 1
block.isSequencerTx block.isSequencerTx
? groupedBlocks[cur].sequenced.push(block) ? groupedBlocks[cur].sequenced.push(block)
......
# Changelog # Changelog
## 0.5.10
### Patch Changes
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
## 0.5.9 ## 0.5.9
### Patch Changes ### Patch Changes
......
...@@ -9,8 +9,6 @@ Within each contract file you'll find a comment that lists: ...@@ -9,8 +9,6 @@ Within each contract file you'll find a comment that lists:
1. The compiler with which a contract is intended to be compiled, `solc` or `optimistic-solc`. 1. The compiler with which a contract is intended to be compiled, `solc` or `optimistic-solc`.
2. The network upon to which the contract will be deployed, `OVM` or `EVM`. 2. The network upon to which the contract will be deployed, `OVM` or `EVM`.
A more detailed overview of these contracts can be found on the [community hub](http://community.optimism.io/docs/protocol/protocol.html#system-overview).
<!-- TODO: Add link to final contract docs here when finished. --> <!-- TODO: Add link to final contract docs here when finished. -->
## Usage (npm) ## Usage (npm)
......
...@@ -65,6 +65,8 @@ import { makeL2GenesisFile } from '../src/make-genesis' ...@@ -65,6 +65,8 @@ import { makeL2GenesisFile } from '../src/make-genesis'
const l1FeeWalletAddress = env.L1_FEE_WALLET_ADDRESS const l1FeeWalletAddress = env.L1_FEE_WALLET_ADDRESS
// The L1 cross domain messenger address, used for cross domain messaging // The L1 cross domain messenger address, used for cross domain messaging
const l1CrossDomainMessengerAddress = env.L1_CROSS_DOMAIN_MESSENGER_ADDRESS const l1CrossDomainMessengerAddress = env.L1_CROSS_DOMAIN_MESSENGER_ADDRESS
// The block height at which the berlin hardfork activates
const berlinBlock = parseInt(env.BERLIN_BLOCK, 10) || 0
ensure(whitelistOwner, 'WHITELIST_OWNER') ensure(whitelistOwner, 'WHITELIST_OWNER')
ensure(gasPriceOracleOwner, 'GAS_PRICE_ORACLE_OWNER') ensure(gasPriceOracleOwner, 'GAS_PRICE_ORACLE_OWNER')
...@@ -74,6 +76,7 @@ import { makeL2GenesisFile } from '../src/make-genesis' ...@@ -74,6 +76,7 @@ import { makeL2GenesisFile } from '../src/make-genesis'
ensure(l1StandardBridgeAddress, 'L1_STANDARD_BRIDGE_ADDRESS') ensure(l1StandardBridgeAddress, 'L1_STANDARD_BRIDGE_ADDRESS')
ensure(l1FeeWalletAddress, 'L1_FEE_WALLET_ADDRESS') ensure(l1FeeWalletAddress, 'L1_FEE_WALLET_ADDRESS')
ensure(l1CrossDomainMessengerAddress, 'L1_CROSS_DOMAIN_MESSENGER_ADDRESS') ensure(l1CrossDomainMessengerAddress, 'L1_CROSS_DOMAIN_MESSENGER_ADDRESS')
ensure(berlinBlock, 'BERLIN_BLOCK')
// Basic warning so users know that the whitelist will be disabled if the owner is the zero address. // Basic warning so users know that the whitelist will be disabled if the owner is the zero address.
if (env.WHITELIST_OWNER === '0x' + '00'.repeat(20)) { if (env.WHITELIST_OWNER === '0x' + '00'.repeat(20)) {
...@@ -96,6 +99,7 @@ import { makeL2GenesisFile } from '../src/make-genesis' ...@@ -96,6 +99,7 @@ import { makeL2GenesisFile } from '../src/make-genesis'
l1StandardBridgeAddress, l1StandardBridgeAddress,
l1FeeWalletAddress, l1FeeWalletAddress,
l1CrossDomainMessengerAddress, l1CrossDomainMessengerAddress,
berlinBlock,
}) })
fs.writeFileSync(outfile, JSON.stringify(genesis, null, 4)) fs.writeFileSync(outfile, JSON.stringify(genesis, null, 4))
......
{ {
"name": "@eth-optimism/contracts", "name": "@eth-optimism/contracts",
"version": "0.5.9", "version": "0.5.10",
"description": "[Optimism] L1 and L2 smart contracts for Optimism", "description": "[Optimism] L1 and L2 smart contracts for Optimism",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.4.1", "@ethersproject/abstract-provider": "^5.4.1",
"@ethersproject/abstract-signer": "^5.4.1", "@ethersproject/abstract-signer": "^5.4.1",
"@ethersproject/hardware-wallets": "^5.4.0" "@ethersproject/hardware-wallets": "^5.4.0"
......
...@@ -40,6 +40,8 @@ export interface RollupDeployConfig { ...@@ -40,6 +40,8 @@ export interface RollupDeployConfig {
l1FeeWalletAddress: string l1FeeWalletAddress: string
// Address of the L1CrossDomainMessenger contract. // Address of the L1CrossDomainMessenger contract.
l1CrossDomainMessengerAddress: string l1CrossDomainMessengerAddress: string
// Block height to activate berlin hardfork
berlinBlock: number
} }
/** /**
...@@ -150,6 +152,7 @@ export const makeL2GenesisFile = async ( ...@@ -150,6 +152,7 @@ export const makeL2GenesisFile = async (
petersburgBlock: 0, petersburgBlock: 0,
istanbulBlock: 0, istanbulBlock: 0,
muirGlacierBlock: 0, muirGlacierBlock: 0,
berlinBlock: cfg.berlinBlock,
clique: { clique: {
period: 0, period: 0,
epoch: 30000, epoch: 30000,
......
# @eth-optimism/core-utils # @eth-optimism/core-utils
## 0.7.5
### Patch Changes
- ad94b9d1: test/docs: Improve docstrings and tests for utils inside of hex-strings.ts
## 0.7.4 ## 0.7.4
### Patch Changes ### Patch Changes
......
{ {
"name": "@eth-optimism/core-utils", "name": "@eth-optimism/core-utils",
"version": "0.7.4", "version": "0.7.5",
"description": "[Optimism] Core typescript utilities", "description": "[Optimism] Core typescript utilities",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
......
...@@ -56,6 +56,12 @@ export const toHexString = (inp: Buffer | string | number | null): string => { ...@@ -56,6 +56,12 @@ export const toHexString = (inp: Buffer | string | number | null): string => {
} }
} }
/**
* Casts a number to a hex string without zero padding.
*
* @param n Number to cast to a hex string.
* @return Number cast as a hex string.
*/
export const toRpcHexString = (n: number | BigNumber): string => { export const toRpcHexString = (n: number | BigNumber): string => {
let num let num
if (typeof n === 'number') { if (typeof n === 'number') {
...@@ -67,10 +73,18 @@ export const toRpcHexString = (n: number | BigNumber): string => { ...@@ -67,10 +73,18 @@ export const toRpcHexString = (n: number | BigNumber): string => {
if (num === '0x0') { if (num === '0x0') {
return num return num
} else { } else {
// BigNumber pads a single 0 to keep hex length even
return num.replace(/^0x0/, '0x') return num.replace(/^0x0/, '0x')
} }
} }
/**
* Zero pads a hex string if str.length !== 2 + length * 2. Pads to length * 2.
*
* @param str Hex string to pad
* @param length Half the length of the desired padded hex string
* @return Hex string with length of 2 + length * 2
*/
export const padHexString = (str: string, length: number): string => { export const padHexString = (str: string, length: number): string => {
if (str.length === 2 + length * 2) { if (str.length === 2 + length * 2) {
return str return str
...@@ -79,9 +93,25 @@ export const padHexString = (str: string, length: number): string => { ...@@ -79,9 +93,25 @@ export const padHexString = (str: string, length: number): string => {
} }
} }
export const encodeHex = (val: any, len: number) => /**
* Casts an input to hex string without '0x' prefix with conditional padding.
* Hex string will always start with a 0.
*
* @param val Input to cast to a hex string.
* @param len Desired length to pad hex string. Ignored if less than hex string length.
* @return Hex string with '0' prefix
*/
export const encodeHex = (val: any, len: number): string =>
remove0x(BigNumber.from(val).toHexString()).padStart(len, '0') remove0x(BigNumber.from(val).toHexString()).padStart(len, '0')
/**
* Case insensitive hex string equality check
*
* @param stringA Hex string A
* @param stringB Hex string B
* @throws {Error} Inputs must be valid hex strings
* @return True if equal
*/
export const hexStringEquals = (stringA: string, stringB: string): boolean => { export const hexStringEquals = (stringA: string, stringB: string): boolean => {
if (!ethers.utils.isHexString(stringA)) { if (!ethers.utils.isHexString(stringA)) {
throw new Error(`input is not a hex string: ${stringA}`) throw new Error(`input is not a hex string: ${stringA}`)
...@@ -94,6 +124,12 @@ export const hexStringEquals = (stringA: string, stringB: string): boolean => { ...@@ -94,6 +124,12 @@ export const hexStringEquals = (stringA: string, stringB: string): boolean => {
return stringA.toLowerCase() === stringB.toLowerCase() return stringA.toLowerCase() === stringB.toLowerCase()
} }
/**
* Casts a number to a 32-byte, zero padded hex string.
*
* @param value Number to cast to a hex string.
* @return Number cast as a hex string.
*/
export const bytes32ify = (value: number | BigNumber): string => { export const bytes32ify = (value: number | BigNumber): string => {
return hexZeroPad(BigNumber.from(value).toHexString(), 32) return hexZeroPad(BigNumber.from(value).toHexString(), 32)
} }
...@@ -18,7 +18,7 @@ describe('address aliasing utils', () => { ...@@ -18,7 +18,7 @@ describe('address aliasing utils', () => {
it('should throw if the input is not a valid address', () => { it('should throw if the input is not a valid address', () => {
expect(() => { expect(() => {
applyL1ToL2Alias('0x1234') applyL1ToL2Alias('0x1234')
}).to.throw }).to.throw('not a valid address: 0x1234')
}) })
}) })
...@@ -38,7 +38,7 @@ describe('address aliasing utils', () => { ...@@ -38,7 +38,7 @@ describe('address aliasing utils', () => {
it('should throw if the input is not a valid address', () => { it('should throw if the input is not a valid address', () => {
expect(() => { expect(() => {
undoL1ToL2Alias('0x1234') undoL1ToL2Alias('0x1234')
}).to.throw }).to.throw('not a valid address: 0x1234')
}) })
}) })
}) })
...@@ -9,6 +9,9 @@ import { ...@@ -9,6 +9,9 @@ import {
fromHexString, fromHexString,
toHexString, toHexString,
padHexString, padHexString,
encodeHex,
hexStringEquals,
bytes32ify,
} from '../src' } from '../src'
describe('remove0x', () => { describe('remove0x', () => {
...@@ -52,13 +55,17 @@ describe('add0x', () => { ...@@ -52,13 +55,17 @@ describe('add0x', () => {
}) })
describe('toHexString', () => { describe('toHexString', () => {
it('should return undefined', () => { it('should throw an error when input is null', () => {
expect(add0x(undefined)).to.deep.equal(undefined) expect(() => {
toHexString(null)
}).to.throw(
'The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received null'
)
}) })
it('should return with a hex string', () => { it('should return with a hex string', () => {
const cases = [ const cases = [
{ input: 0, output: '0x00' }, { input: 0, output: '0x00' },
{ input: 48, output: '0x30' },
{ {
input: '0', input: '0',
output: '0x30', output: '0x30',
...@@ -122,3 +129,184 @@ describe('toRpcHexString', () => { ...@@ -122,3 +129,184 @@ describe('toRpcHexString', () => {
} }
}) })
}) })
describe('encodeHex', () => {
it('should throw an error when val is invalid', () => {
expect(() => {
encodeHex(null, 0)
}).to.throw('invalid BigNumber value')
expect(() => {
encodeHex(10.5, 0)
}).to.throw('fault="underflow", operation="BigNumber.from", value=10.5')
expect(() => {
encodeHex('10.5', 0)
}).to.throw('invalid BigNumber string')
})
it('should return a hex string of val with length len', () => {
const cases = [
{
input: {
val: 0,
len: 0,
},
output: '00',
},
{
input: {
val: 0,
len: 4,
},
output: '0000',
},
{
input: {
val: 1,
len: 0,
},
output: '01',
},
{
input: {
val: 1,
len: 10,
},
output: '0000000001',
},
{
input: {
val: 100,
len: 4,
},
output: '0064',
},
{
input: {
val: '100',
len: 0,
},
output: '64',
},
]
for (const test of cases) {
expect(encodeHex(test.input.val, test.input.len)).to.deep.equal(
test.output
)
}
})
})
describe('hexStringEquals', () => {
it('should throw an error when input is not a hex string', () => {
expect(() => {
hexStringEquals('', '')
}).to.throw('input is not a hex string: ')
expect(() => {
hexStringEquals('0xx', '0x1')
}).to.throw('input is not a hex string: 0xx')
expect(() => {
hexStringEquals('0x1', '2')
}).to.throw('input is not a hex string: 2')
expect(() => {
hexStringEquals('-0x1', '0x1')
}).to.throw('input is not a hex string: -0x1')
})
it('should return the hex strings equality', () => {
const cases = [
{
input: {
stringA: '0x',
stringB: '0x',
},
output: true,
},
{
input: {
stringA: '0x1',
stringB: '0x1',
},
output: true,
},
{
input: {
stringA: '0x064',
stringB: '0x064',
},
output: true,
},
{
input: {
stringA: '0x',
stringB: '0x0',
},
output: false,
},
{
input: {
stringA: '0x0',
stringB: '0x1',
},
output: false,
},
{
input: {
stringA: '0x64',
stringB: '0x064',
},
output: false,
},
]
for (const test of cases) {
expect(
hexStringEquals(test.input.stringA, test.input.stringB)
).to.deep.equal(test.output)
}
})
})
describe('bytes32ify', () => {
it('should throw an error when input is invalid', () => {
expect(() => {
bytes32ify(-1)
}).to.throw('invalid hex string')
})
it('should return a zero padded, 32 bytes hex string', () => {
const cases = [
{
input: 0,
output:
'0x0000000000000000000000000000000000000000000000000000000000000000',
},
{
input: BigNumber.from(0),
output:
'0x0000000000000000000000000000000000000000000000000000000000000000',
},
{
input: 2,
output:
'0x0000000000000000000000000000000000000000000000000000000000000002',
},
{
input: BigNumber.from(2),
output:
'0x0000000000000000000000000000000000000000000000000000000000000002',
},
{
input: 100,
output:
'0x0000000000000000000000000000000000000000000000000000000000000064',
},
]
for (const test of cases) {
expect(bytes32ify(test.input)).to.deep.equal(test.output)
}
})
})
# data transport layer # data transport layer
## 0.5.13
### Patch Changes
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
- @eth-optimism/contracts@0.5.10
## 0.5.12 ## 0.5.12
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/data-transport-layer", "name": "@eth-optimism/data-transport-layer",
"version": "0.5.12", "version": "0.5.13",
"description": "[Optimism] Service for shuttling data from L1 into L2", "description": "[Optimism] Service for shuttling data from L1 into L2",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.2.1", "@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.9", "@eth-optimism/contracts": "0.5.10",
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0", "@ethersproject/transactions": "^5.4.0",
"@sentry/node": "^6.3.1", "@sentry/node": "^6.3.1",
......
# @eth-optimism/message-relayer # @eth-optimism/message-relayer
## 0.2.14
### Patch Changes
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
- @eth-optimism/contracts@0.5.10
## 0.2.13 ## 0.2.13
### Patch Changes ### Patch Changes
......
{ {
"name": "@eth-optimism/message-relayer", "name": "@eth-optimism/message-relayer",
"version": "0.2.13", "version": "0.2.14",
"description": "[Optimism] Service for automatically relaying L2 to L1 transactions", "description": "[Optimism] Service for automatically relaying L2 to L1 transactions",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.2.1", "@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/contracts": "0.5.9", "@eth-optimism/contracts": "0.5.10",
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"@sentry/node": "^6.3.1", "@sentry/node": "^6.3.1",
"bcfg": "^0.1.6", "bcfg": "^0.1.6",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
}, },
"devDependencies": { "devDependencies": {
"@discoveryjs/json-ext": "^0.5.3", "@discoveryjs/json-ext": "^0.5.3",
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.5.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abi": "^5.5.0", "@ethersproject/abi": "^5.5.0",
"@ethersproject/bignumber": "^5.5.0", "@ethersproject/bignumber": "^5.5.0",
......
# @eth-optimism/replica-healthcheck # @eth-optimism/replica-healthcheck
## 0.3.5
### Patch Changes
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
## 0.3.4 ## 0.3.4
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/replica-healthcheck", "name": "@eth-optimism/replica-healthcheck",
"version": "0.3.4", "version": "0.3.5",
"description": "[Optimism] Service for monitoring the health of replica nodes", "description": "[Optimism] Service for monitoring the health of replica nodes",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.2.1", "@eth-optimism/common-ts": "0.2.1",
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"ethers": "^5.4.5", "ethers": "^5.4.5",
"express": "^4.17.1", "express": "^4.17.1",
......
# @eth-optimism/sdk # @eth-optimism/sdk
## 0.0.6
### Patch Changes
- Updated dependencies [ad94b9d1]
- @eth-optimism/core-utils@0.7.5
- @eth-optimism/contracts@0.5.10
## 0.0.5 ## 0.0.5
### Patch Changes ### Patch Changes
......
{ {
"name": "@eth-optimism/sdk", "name": "@eth-optimism/sdk",
"version": "0.0.5", "version": "0.0.6",
"description": "[Optimism] Tools for working with Optimism", "description": "[Optimism] Tools for working with Optimism",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -59,8 +59,8 @@ ...@@ -59,8 +59,8 @@
"typescript": "^4.3.5" "typescript": "^4.3.5"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/contracts": "0.5.9", "@eth-optimism/contracts": "0.5.10",
"@eth-optimism/core-utils": "0.7.4", "@eth-optimism/core-utils": "0.7.5",
"@ethersproject/abstract-provider": "^5.5.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/abstract-signer": "^5.5.0", "@ethersproject/abstract-signer": "^5.5.0",
"ethers": "^5.5.2" "ethers": "^5.5.2"
......
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Overrides, Signer, BigNumber } from 'ethers'
import {
TransactionRequest,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { predeploys } from '@eth-optimism/contracts'
import {
CrossChainMessageRequest,
ICrossChainMessenger,
ICrossChainProvider,
MessageLike,
NumberLike,
MessageDirection,
} from './interfaces'
import { omit } from './utils'
export class CrossChainMessenger implements ICrossChainMessenger {
provider: ICrossChainProvider
l1Signer: Signer
l2Signer: Signer
/**
* Creates a new CrossChainMessenger instance.
*
* @param opts Options for the messenger.
* @param opts.provider CrossChainProvider to use to send messages.
* @param opts.l1Signer Signer to use to send messages on L1.
* @param opts.l2Signer Signer to use to send messages on L2.
*/
constructor(opts: {
provider: ICrossChainProvider
l1Signer: Signer
l2Signer: Signer
}) {
this.provider = opts.provider
this.l1Signer = opts.l1Signer
this.l2Signer = opts.l2Signer
}
public async sendMessage(
message: CrossChainMessageRequest,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionResponse> {
const tx = await this.populateTransaction.sendMessage(message, opts)
if (message.direction === MessageDirection.L1_TO_L2) {
return this.l1Signer.sendTransaction(tx)
} else {
return this.l2Signer.sendTransaction(tx)
}
}
public async resendMessage(
message: MessageLike,
messageGasLimit: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionResponse> {
return this.l1Signer.sendTransaction(
await this.populateTransaction.resendMessage(
message,
messageGasLimit,
opts
)
)
}
public async finalizeMessage(
message: MessageLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionResponse> {
throw new Error('Not implemented')
}
public async depositETH(
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionResponse> {
return this.l1Signer.sendTransaction(
await this.populateTransaction.depositETH(amount, opts)
)
}
public async withdrawETH(
amount: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionResponse> {
return this.l2Signer.sendTransaction(
await this.populateTransaction.withdrawETH(amount, opts)
)
}
populateTransaction = {
sendMessage: async (
message: CrossChainMessageRequest,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionRequest> => {
if (message.direction === MessageDirection.L1_TO_L2) {
return this.provider.contracts.l1.L1CrossDomainMessenger.connect(
this.l1Signer
).populateTransaction.sendMessage(
message.target,
message.message,
opts?.l2GasLimit ||
(await this.provider.estimateL2MessageGasLimit(message)),
omit(opts?.overrides || {}, 'l2GasLimit')
)
} else {
return this.provider.contracts.l2.L2CrossDomainMessenger.connect(
this.l2Signer
).populateTransaction.sendMessage(
message.target,
message.message,
0, // Gas limit goes unused when sending from L2 to L1
omit(opts?.overrides || {}, 'l2GasLimit')
)
}
},
resendMessage: async (
message: MessageLike,
messageGasLimit: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionRequest> => {
const resolved = await this.provider.toCrossChainMessage(message)
if (resolved.direction === MessageDirection.L2_TO_L1) {
throw new Error(`cannot resend L2 to L1 message`)
}
return this.provider.contracts.l1.L1CrossDomainMessenger.connect(
this.l1Signer
).populateTransaction.replayMessage(
resolved.target,
resolved.sender,
resolved.message,
resolved.messageNonce,
resolved.gasLimit,
messageGasLimit,
opts?.overrides || {}
)
},
finalizeMessage: async (
message: MessageLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionRequest> => {
throw new Error('Not implemented')
},
depositETH: async (
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<TransactionRequest> => {
return this.provider.contracts.l1.L1StandardBridge.populateTransaction.depositETH(
opts?.l2GasLimit || 200000, // 200k gas is fine as a default
'0x', // No data
{
...omit(opts?.overrides || {}, 'l2GasLimit', 'value'),
value: amount,
}
)
},
withdrawETH: async (
amount: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<TransactionRequest> => {
return this.provider.contracts.l2.L2StandardBridge.populateTransaction.withdraw(
predeploys.OVM_ETH,
amount,
0, // No need to supply gas here
'0x', // No data,
opts?.overrides || {}
)
},
}
estimateGas = {
sendMessage: async (
message: CrossChainMessageRequest,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<BigNumber> => {
const tx = await this.populateTransaction.sendMessage(message, opts)
if (message.direction === MessageDirection.L1_TO_L2) {
return this.provider.l1Provider.estimateGas(tx)
} else {
return this.provider.l2Provider.estimateGas(tx)
}
},
resendMessage: async (
message: MessageLike,
messageGasLimit: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<BigNumber> => {
const tx = await this.populateTransaction.resendMessage(
message,
messageGasLimit,
opts
)
return this.provider.l1Provider.estimateGas(tx)
},
finalizeMessage: async (
message: MessageLike,
opts?: {
overrides?: Overrides
}
): Promise<BigNumber> => {
throw new Error('Not implemented')
},
depositETH: async (
amount: NumberLike,
opts?: {
l2GasLimit?: NumberLike
overrides?: Overrides
}
): Promise<BigNumber> => {
const tx = await this.populateTransaction.depositETH(amount, opts)
return this.provider.l1Provider.estimateGas(tx)
},
withdrawETH: async (
amount: NumberLike,
opts?: {
overrides?: Overrides
}
): Promise<BigNumber> => {
const tx = await this.populateTransaction.withdrawETH(amount, opts)
return this.provider.l2Provider.estimateGas(tx)
},
}
}
This diff is collapsed.
export * from './interfaces' export * from './interfaces'
export * from './utils' export * from './utils'
export * from './cross-chain-provider' export * from './cross-chain-provider'
export * from './cross-chain-messenger'
...@@ -3,6 +3,7 @@ import { Provider, BlockTag } from '@ethersproject/abstract-provider' ...@@ -3,6 +3,7 @@ import { Provider, BlockTag } from '@ethersproject/abstract-provider'
import { import {
MessageLike, MessageLike,
MessageRequestLike,
TransactionLike, TransactionLike,
AddressLike, AddressLike,
NumberLike, NumberLike,
...@@ -205,12 +206,14 @@ export interface ICrossChainProvider { ...@@ -205,12 +206,14 @@ export interface ICrossChainProvider {
* @param message Message get a gas estimate for. * @param message Message get a gas estimate for.
* @param opts Options object. * @param opts Options object.
* @param opts.bufferPercent Percentage of gas to add to the estimate. Defaults to 20. * @param opts.bufferPercent Percentage of gas to add to the estimate. Defaults to 20.
* @param opts.from Address to use as the sender.
* @returns Estimates L2 gas limit. * @returns Estimates L2 gas limit.
*/ */
estimateL2MessageGasLimit( estimateL2MessageGasLimit(
message: MessageLike, message: MessageRequestLike,
opts?: { opts?: {
bufferPercent?: number bufferPercent?: number
from?: string
} }
): Promise<BigNumber> ): Promise<BigNumber>
...@@ -225,17 +228,6 @@ export interface ICrossChainProvider { ...@@ -225,17 +228,6 @@ export interface ICrossChainProvider {
*/ */
estimateMessageWaitTimeSeconds(message: MessageLike): Promise<number> estimateMessageWaitTimeSeconds(message: MessageLike): Promise<number>
/**
* Returns the estimated amount of time before the message can be executed (in L1 blocks).
* When this is a message being sent to L1, this will return the estimated time until the message
* will complete its challenge period. When this is a message being sent to L2, this will return
* the estimated amount of time until the message will be picked up and executed on L2.
*
* @param message Message to estimate the time remaining for.
* @returns Estimated amount of time remaining (in blocks) before the message can be executed.
*/
estimateMessageWaitTimeBlocks(message: MessageLike): Promise<number>
/** /**
* Queries the current challenge period in seconds from the StateCommitmentChain. * Queries the current challenge period in seconds from the StateCommitmentChain.
* *
...@@ -243,14 +235,6 @@ export interface ICrossChainProvider { ...@@ -243,14 +235,6 @@ export interface ICrossChainProvider {
*/ */
getChallengePeriodSeconds(): Promise<number> getChallengePeriodSeconds(): Promise<number>
/**
* Queries the current challenge period in blocks from the StateCommitmentChain. Estimation is
* based on the challenge period in seconds divided by the L1 block time.
*
* @returns Current challenge period in blocks.
*/
getChallengePeriodBlocks(): Promise<number>
/** /**
* Returns the state root that corresponds to a given message. This is the state root for the * Returns the state root that corresponds to a given message. This is the state root for the
* block in which the transaction was included, as published to the StateCommitmentChain. If the * block in which the transaction was included, as published to the StateCommitmentChain. If the
......
This diff is collapsed.
...@@ -3,6 +3,13 @@ pragma solidity ^0.8.9; ...@@ -3,6 +3,13 @@ pragma solidity ^0.8.9;
import { MockMessenger } from "./MockMessenger.sol"; import { MockMessenger } from "./MockMessenger.sol";
contract MockBridge { contract MockBridge {
event ETHDepositInitiated(
address indexed _from,
address indexed _to,
uint256 _amount,
bytes _data
);
event ERC20DepositInitiated( event ERC20DepositInitiated(
address indexed _l1Token, address indexed _l1Token,
address indexed _l2Token, address indexed _l2Token,
...@@ -110,4 +117,38 @@ contract MockBridge { ...@@ -110,4 +117,38 @@ contract MockBridge {
) public { ) public {
emit DepositFailed(_params.l1Token, _params.l2Token, _params.from, _params.to, _params.amount, _params.data); emit DepositFailed(_params.l1Token, _params.l2Token, _params.from, _params.to, _params.amount, _params.data);
} }
function depositETH(
uint32 _l2GasLimit,
bytes memory _data
)
public
payable
{
emit ETHDepositInitiated(
msg.sender,
msg.sender,
msg.value,
_data
);
}
function withdraw(
address _l2Token,
uint256 _amount,
uint32 _l1Gas,
bytes calldata _data
)
public
payable
{
emit WithdrawalInitiated(
address(0),
_l2Token,
msg.sender,
msg.sender,
_amount,
_data
);
}
} }
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