Commit f7587781 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into clabby/ctb/xdm-min-gas-fix

parents 2bf225ae dd2bef6e
---
'@eth-optimism/sdk': patch
---
Update the migrated withdrawal gas limit for non goerli networks
......@@ -181,6 +181,7 @@ func main() {
migrationData,
&config.L1CrossDomainMessengerProxy,
config.L1ChainID,
config.L2ChainID,
config.FinalSystemOwner,
config.ProxyAdminOwner,
&derive.L1BlockInfo{
......
......@@ -223,6 +223,7 @@ func main() {
migrationData,
&config.L1CrossDomainMessengerProxy,
config.L1ChainID,
config.L2ChainID,
config.FinalSystemOwner,
config.ProxyAdminOwner,
&derive.L1BlockInfo{
......
......@@ -141,6 +141,10 @@ func main() {
if err != nil {
return err
}
l2ChainID, err := clients.L2Client.ChainID(context.Background())
if err != nil {
return err
}
// create the set of withdrawals
wds, err := newWithdrawals(ctx, l1ChainID)
......@@ -212,7 +216,7 @@ func main() {
log.Info("Processing withdrawal", "index", i)
// migrate the withdrawal
withdrawal, err := crossdomain.MigrateWithdrawal(wd, &l1xdmAddr)
withdrawal, err := crossdomain.MigrateWithdrawal(wd, &l1xdmAddr, l2ChainID)
if err != nil {
return err
}
......
......@@ -20,7 +20,13 @@ var (
)
// MigrateWithdrawals will migrate a list of pending withdrawals given a StateDB.
func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1CrossDomainMessenger *common.Address, noCheck bool) error {
func MigrateWithdrawals(
withdrawals SafeFilteredWithdrawals,
db vm.StateDB,
l1CrossDomainMessenger *common.Address,
noCheck bool,
chainID *big.Int,
) error {
for i, legacy := range withdrawals {
legacySlot, err := legacy.StorageSlot()
if err != nil {
......@@ -34,7 +40,7 @@ func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1Cr
}
}
withdrawal, err := MigrateWithdrawal(legacy, l1CrossDomainMessenger)
withdrawal, err := MigrateWithdrawal(legacy, l1CrossDomainMessenger, chainID)
if err != nil {
return err
}
......@@ -52,7 +58,11 @@ func MigrateWithdrawals(withdrawals SafeFilteredWithdrawals, db vm.StateDB, l1Cr
// MigrateWithdrawal will turn a LegacyWithdrawal into a bedrock
// style Withdrawal.
func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *common.Address) (*Withdrawal, error) {
func MigrateWithdrawal(
withdrawal *LegacyWithdrawal,
l1CrossDomainMessenger *common.Address,
chainID *big.Int,
) (*Withdrawal, error) {
// Attempt to parse the value
value, err := withdrawal.Value()
if err != nil {
......@@ -83,7 +93,7 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com
return nil, fmt.Errorf("cannot abi encode relayMessage: %w", err)
}
gasLimit := MigrateWithdrawalGasLimit(data)
gasLimit := MigrateWithdrawalGasLimit(data, chainID)
w := NewWithdrawal(
versionedNonce,
......@@ -97,13 +107,21 @@ func MigrateWithdrawal(withdrawal *LegacyWithdrawal, l1CrossDomainMessenger *com
}
// MigrateWithdrawalGasLimit computes the gas limit for the migrated withdrawal.
func MigrateWithdrawalGasLimit(data []byte) uint64 {
// The chain id is used to determine the overhead.
func MigrateWithdrawalGasLimit(data []byte, chainID *big.Int) uint64 {
// Compute the upper bound on the gas limit. This could be more
// accurate if individual 0 bytes and non zero bytes were accounted
// for.
dataCost := uint64(len(data)) * params.TxDataNonZeroGasEIP2028
// Goerli has a lower gas limit than other chains.
overhead := uint64(200_000)
if chainID.Cmp(big.NewInt(420)) != 0 {
overhead = 1_000_000
}
// Set the outer gas limit. This cannot be zero
gasLimit := dataCost + 200_000
gasLimit := dataCost + overhead
// Cap the gas limit to be 25 million to prevent creating withdrawals
// that go over the block gas limit.
if gasLimit > 25_000_000 {
......
......@@ -12,7 +12,10 @@ import (
"github.com/stretchr/testify/require"
)
var big25Million = big.NewInt(25_000_000)
var (
big25Million = big.NewInt(25_000_000)
bigGoerliChainID = big.NewInt(420)
)
func TestMigrateWithdrawal(t *testing.T) {
withdrawals := make([]*crossdomain.LegacyWithdrawal, 0)
......@@ -27,7 +30,7 @@ func TestMigrateWithdrawal(t *testing.T) {
l1CrossDomainMessenger := common.HexToAddress("0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1")
for i, legacy := range withdrawals {
t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
withdrawal, err := crossdomain.MigrateWithdrawal(legacy, &l1CrossDomainMessenger)
withdrawal, err := crossdomain.MigrateWithdrawal(legacy, &l1CrossDomainMessenger, bigGoerliChainID)
require.Nil(t, err)
require.NotNil(t, withdrawal)
......@@ -50,7 +53,7 @@ func TestMigrateWithdrawalGasLimitMax(t *testing.T) {
data[i] = 0xff
}
result := crossdomain.MigrateWithdrawalGasLimit(data)
result := crossdomain.MigrateWithdrawalGasLimit(data, bigGoerliChainID)
require.Equal(t, result, big25Million.Uint64())
}
......@@ -84,7 +87,7 @@ func TestMigrateWithdrawalGasLimit(t *testing.T) {
}
for _, test := range tests {
result := crossdomain.MigrateWithdrawalGasLimit(test.input)
result := crossdomain.MigrateWithdrawalGasLimit(test.input, bigGoerliChainID)
require.Equal(t, test.output, result)
}
}
......@@ -101,6 +101,7 @@ func PostCheckMigratedDB(
migrationData crossdomain.MigrationData,
l1XDM *common.Address,
l1ChainID uint64,
l2ChainID uint64,
finalSystemOwner common.Address,
proxyAdminOwner common.Address,
info *derive.L1BlockInfo,
......@@ -163,7 +164,7 @@ func PostCheckMigratedDB(
}
log.Info("checked legacy eth")
if err := CheckWithdrawalsAfter(db, migrationData, l1XDM); err != nil {
if err := CheckWithdrawalsAfter(db, migrationData, l1XDM, new(big.Int).SetUint64(l2ChainID)); err != nil {
return err
}
log.Info("checked withdrawals")
......@@ -557,7 +558,7 @@ func PostCheckL1Block(db *state.StateDB, info *derive.L1BlockInfo) error {
return nil
}
func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1CrossDomainMessenger *common.Address) error {
func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1CrossDomainMessenger *common.Address, l2ChainID *big.Int) error {
wds, invalidMessages, err := data.ToWithdrawals()
if err != nil {
return err
......@@ -570,7 +571,7 @@ func CheckWithdrawalsAfter(db *state.StateDB, data crossdomain.MigrationData, l1
wdsByOldSlot := make(map[common.Hash]*crossdomain.LegacyWithdrawal)
invalidMessagesByOldSlot := make(map[common.Hash]crossdomain.InvalidMessage)
for _, wd := range wds {
migrated, err := crossdomain.MigrateWithdrawal(wd, l1CrossDomainMessenger)
migrated, err := crossdomain.MigrateWithdrawal(wd, l1CrossDomainMessenger, l2ChainID)
if err != nil {
return err
}
......
......@@ -186,7 +186,8 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
// the LegacyMessagePasser contract. Here we operate on the list of withdrawals that we
// previously filtered and verified.
log.Info("Starting to migrate withdrawals", "no-check", noCheck)
err = crossdomain.MigrateWithdrawals(filteredWithdrawals, db, &config.L1CrossDomainMessengerProxy, noCheck)
l2ChainID := new(big.Int).SetUint64(config.L2ChainID)
err = crossdomain.MigrateWithdrawals(filteredWithdrawals, db, &config.L1CrossDomainMessengerProxy, noCheck, l2ChainID)
if err != nil {
return nil, fmt.Errorf("cannot migrate withdrawals: %w", err)
}
......
......@@ -84,6 +84,9 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
}
}
localPreimageSource := kvstore.NewLocalPreimageSource(cfg)
splitter := kvstore.NewPreimageSourceSplitter(localPreimageSource.Get, getPreimage)
// Setup pipe for preimage oracle interaction
pClientRW, pHostRW := bidirectionalPipe()
oracleServer := preimage.NewOracleServer(pHostRW)
......@@ -93,7 +96,7 @@ func FaultProofProgram(logger log.Logger, cfg *config.Config) error {
defer pHostRW.Close()
defer hHostRW.Close()
routeHints(logger, hHost, hinter)
launchOracleServer(logger, oracleServer, getPreimage)
launchOracleServer(logger, oracleServer, splitter.Get)
return cl.ClientProgram(
logger,
......
package kvstore
import (
"encoding/binary"
"encoding/json"
"github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum-optimism/optimism/op-program/preimage"
"github.com/ethereum/go-ethereum/common"
)
type LocalPreimageSource struct {
config *config.Config
}
func NewLocalPreimageSource(config *config.Config) *LocalPreimageSource {
return &LocalPreimageSource{config}
}
func localKey(num int64) common.Hash {
return preimage.LocalIndexKey(num).PreimageKey()
}
var (
L1HeadKey = localKey(1)
L2HeadKey = localKey(2)
L2ClaimKey = localKey(3)
L2ClaimBlockNumberKey = localKey(4)
L2ChainConfigKey = localKey(5)
RollupKey = localKey(6)
)
func (s *LocalPreimageSource) Get(key common.Hash) ([]byte, error) {
switch key {
case L1HeadKey:
return s.config.L1Head.Bytes(), nil
case L2HeadKey:
return s.config.L2Head.Bytes(), nil
case L2ClaimKey:
return s.config.L2Claim.Bytes(), nil
case L2ClaimBlockNumberKey:
return binary.BigEndian.AppendUint64(nil, s.config.L2ClaimBlockNumber), nil
case L2ChainConfigKey:
return json.Marshal(s.config.L2ChainConfig)
case RollupKey:
return json.Marshal(s.config.Rollup)
default:
return nil, ErrNotFound
}
}
package kvstore
import (
"encoding/binary"
"encoding/json"
"testing"
"github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum-optimism/optimism/op-program/preimage"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"
)
func TestLocalPreimageSource(t *testing.T) {
cfg := &config.Config{
Rollup: &chaincfg.Goerli,
L1Head: common.HexToHash("0x1111"),
L2Head: common.HexToHash("0x2222"),
L2Claim: common.HexToHash("0x3333"),
L2ClaimBlockNumber: 1234,
L2ChainConfig: params.GoerliChainConfig,
}
source := NewLocalPreimageSource(cfg)
tests := []struct {
name string
key common.Hash
expected []byte
}{
{"L1Head", L1HeadKey, cfg.L1Head.Bytes()},
{"L2Head", L2HeadKey, cfg.L2Head.Bytes()},
{"L2Claim", L2ClaimKey, cfg.L2Claim.Bytes()},
{"L2ClaimBlockNumber", L2ClaimBlockNumberKey, binary.BigEndian.AppendUint64(nil, cfg.L2ClaimBlockNumber)},
{"Rollup", RollupKey, asJson(t, cfg.Rollup)},
{"ChainConfig", L2ChainConfigKey, asJson(t, cfg.L2ChainConfig)},
{"Unknown", preimage.LocalIndexKey(1000).PreimageKey(), nil},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result, err := source.Get(test.key)
if test.expected == nil {
require.ErrorIs(t, err, ErrNotFound)
} else {
require.NoError(t, err)
}
require.Equal(t, test.expected, result)
})
}
}
func asJson(t *testing.T, v any) []byte {
d, err := json.Marshal(v)
require.NoError(t, err)
return d
}
package kvstore
import (
"github.com/ethereum-optimism/optimism/op-program/preimage"
"github.com/ethereum/go-ethereum/common"
)
type PreimageSource func(key common.Hash) ([]byte, error)
type PreimageSourceSplitter struct {
local PreimageSource
global PreimageSource
}
func NewPreimageSourceSplitter(local PreimageSource, global PreimageSource) *PreimageSourceSplitter {
return &PreimageSourceSplitter{
local: local,
global: global,
}
}
func (s *PreimageSourceSplitter) Get(key common.Hash) ([]byte, error) {
if key[0] == byte(preimage.LocalKeyType) {
return s.local(key)
}
return s.global(key)
}
package kvstore
import (
"testing"
"github.com/ethereum-optimism/optimism/op-program/preimage"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestPreimageSourceSplitter(t *testing.T) {
localResult := []byte{1}
globalResult := []byte{2}
local := func(key common.Hash) ([]byte, error) { return localResult, nil }
global := func(key common.Hash) ([]byte, error) { return globalResult, nil }
splitter := NewPreimageSourceSplitter(local, global)
tests := []struct {
name string
keyPrefix byte
expected []byte
}{
{"Local", byte(preimage.LocalKeyType), localResult},
{"Keccak", byte(preimage.Keccak256KeyType), globalResult},
{"Generic", byte(3), globalResult},
{"Reserved", byte(4), globalResult},
{"Application", byte(255), globalResult},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
key := common.Hash{0xff}
key[0] = test.keyPrefix
res, err := splitter.Get(key)
require.NoError(t, err)
require.Equal(t, test.expected, res)
})
}
}
......@@ -33,8 +33,8 @@ const (
_ KeyType = 0
// LocalKeyType is for input-type pre-images, specific to the local program instance.
LocalKeyType KeyType = 1
// Keccak25Key6Type is for keccak256 pre-images, for any global shared pre-images.
Keccak25Key6Type KeyType = 2
// Keccak256KeyType is for keccak256 pre-images, for any global shared pre-images.
Keccak256KeyType KeyType = 2
)
// LocalIndexKey is a key local to the program, indexing a special program input.
......@@ -51,7 +51,7 @@ type Keccak256Key common.Hash
func (k Keccak256Key) PreimageKey() (out common.Hash) {
out = common.Hash(k) // copy the keccak hash
out[0] = byte(Keccak25Key6Type) // apply prefix
out[0] = byte(Keccak256KeyType) // apply prefix
return
}
......
......@@ -26,6 +26,7 @@ import {
BedrockCrossChainMessageProof,
decodeVersionedNonce,
encodeVersionedNonce,
getChainId,
} from '@eth-optimism/core-utils'
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import * as rlp from 'rlp'
......@@ -403,7 +404,8 @@ export class CrossChainMessenger {
let gasLimit: BigNumber
let messageNonce: BigNumber
if (version.eq(0)) {
gasLimit = migratedWithdrawalGasLimit(encoded)
const chainID = await getChainId(this.l2Provider)
gasLimit = migratedWithdrawalGasLimit(encoded, chainID)
messageNonce = resolved.messageNonce
} else {
const receipt = await this.l2Provider.getTransactionReceipt(
......
......@@ -41,10 +41,17 @@ export const hashMessageHash = (messageHash: string): string => {
/**
* Compute the min gas limit for a migrated withdrawal.
*/
export const migratedWithdrawalGasLimit = (data: string): BigNumber => {
export const migratedWithdrawalGasLimit = (
data: string,
chainID: number
): BigNumber => {
// Compute the gas limit and cap at 25 million
const dataCost = BigNumber.from(hexDataLength(data)).mul(16)
let minGasLimit = dataCost.add(200_000)
let overhead = 200_000
if (chainID !== 420) {
overhead = 1_000_000
}
let minGasLimit = dataCost.add(overhead)
if (minGasLimit.gt(25_000_000)) {
minGasLimit = BigNumber.from(25_000_000)
}
......
......@@ -7,11 +7,13 @@ import {
hashMessageHash,
} from '../../src/utils/message-utils'
const goerliChainID = 420
describe('Message Utils', () => {
describe('migratedWithdrawalGasLimit', () => {
it('should have a max of 25 million', () => {
const data = '0x' + 'ff'.repeat(15_000_000)
const result = migratedWithdrawalGasLimit(data)
const result = migratedWithdrawalGasLimit(data, goerliChainID)
expect(result).to.eq(BigNumber.from(25_000_000))
})
......@@ -25,7 +27,7 @@ describe('Message Utils', () => {
]
for (const test of tests) {
const result = migratedWithdrawalGasLimit(test.input)
const result = migratedWithdrawalGasLimit(test.input, goerliChainID)
expect(result).to.eq(test.result)
}
})
......
......@@ -178,17 +178,21 @@ Note that hints may produce multiple pre-images:
e.g. a hint for an ethereum block with transaction list may prepare pre-images for the header,
each of the transactions, and the intermediate merkle-nodes that form the transactions-list Merkle Patricia Trie.
Hinting is implemented with a minimal wire-protocol over a blocking one-way stream:
Hinting is implemented with a request-acknowledgement wire-protocol over a blocking two-way stream:
```text
<request> := <length prefix> <hint bytes> <end>
<request> := <length prefix> <hint bytes>
<repsonse> := <ack>
<length prefix> := big-endian uint32 # length of <hint bytes>
<hint bytes> := byte sequence
<end> := 0 byte
<ack> := 1-byte zero value
```
The `<end>` trailing zero byte allows the server to block the program
(since the communication is blocking) until the hint is processed.
The ack informs the client that the hint has been processed. Servers may respond to hints and pre-image (see below)
requests asynchronously as they are on separate streams. To avoid requesting pre-images that are not yet fetched,
clients should request the pre-image only after it has observed the hint acknowledgement.
### Pre-image communication
......@@ -201,7 +205,6 @@ This protocol can be implemented with blocking read/write syscalls.
<response> := <length prefix> <pre-image bytes>
<length prefix> := big-endian uint64 # length of <pre-image bytes>, note: uint64
<hint bytes> := byte sequence #
```
The `<length prefix>` here may be arbitrarily high:
......
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