Commit bb26427b authored by Andreas Bigger's avatar Andreas Bigger

Merge branch 'develop' into refcell/batcher/tests

parents f376cc3b 9a644dcf
---
'@eth-optimism/contracts-bedrock': patch
---
Optionally print cast commands during migration
---
'@eth-optimism/atst': minor
---
Update readAttestations and prepareWriteAttestation to handle keys longer than 32 bytes
---
'@eth-optimism/atst': minor
---
Remove broken allowFailures as option
---
'@eth-optimism/common-ts': patch
---
Fix BaseServiceV2 configuration for caseCase options
---
'@eth-optimism/atst': patch
---
Update docs
---
'@eth-optimism/atst': minor
---
Move react api to @eth-optimism/atst/react so react isn't required to run the core sdk
---
'@eth-optimism/sdk': patch
---
Update migrated withdrawal gaslimit calculation
---
'@eth-optimism/atst': minor
---
Fix main and module in atst package.json
---
'@eth-optimism/atst': patch
---
Fixed bug with atst not defaulting to currently connected chain
---
'@eth-optimism/atst': minor
---
Deprecate parseAttestationBytes and createRawKey in favor for createKey, createValue
---
'@eth-optimism/fault-detector': patch
---
Fixes a bug that would cause the fault detector to error out if no outputs had been proposed yet.
---
'@eth-optimism/contracts-bedrock': patch
---
Print tenderly simulation links during deployment
...@@ -30,10 +30,10 @@ ...@@ -30,10 +30,10 @@
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.5.4", "@babel/eslint-parser": "^7.5.4",
"@eth-optimism/contracts": "^0.5.40", "@eth-optimism/contracts": "^0.5.40",
"@eth-optimism/contracts-bedrock": "0.13.0", "@eth-optimism/contracts-bedrock": "0.13.1",
"@eth-optimism/contracts-periphery": "^1.0.7", "@eth-optimism/contracts-periphery": "^1.0.7",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.0.0", "@eth-optimism/sdk": "2.0.1",
"@ethersproject/abstract-provider": "^5.7.0", "@ethersproject/abstract-provider": "^5.7.0",
"@ethersproject/providers": "^5.7.0", "@ethersproject/providers": "^5.7.0",
"@ethersproject/transactions": "^5.7.0", "@ethersproject/transactions": "^5.7.0",
......
package main
import (
"context"
"fmt"
"math/big"
"os"
"strings"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/db"
"github.com/mattn/go-isatty"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-bindings/hardhat"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli"
)
func main() {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(isatty.IsTerminal(os.Stderr.Fd()))))
app := &cli.App{
Name: "check-migration",
Usage: "Run sanity checks on a migrated database",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "l1-rpc-url",
Value: "http://127.0.0.1:8545",
Usage: "RPC URL for an L1 Node",
Required: true,
},
&cli.StringFlag{
Name: "ovm-addresses",
Usage: "Path to ovm-addresses.json",
Required: true,
},
&cli.StringFlag{
Name: "ovm-allowances",
Usage: "Path to ovm-allowances.json",
Required: true,
},
&cli.StringFlag{
Name: "ovm-messages",
Usage: "Path to ovm-messages.json",
Required: true,
},
&cli.StringFlag{
Name: "witness-file",
Usage: "Path to witness file",
Required: true,
},
&cli.StringFlag{
Name: "db-path",
Usage: "Path to database",
Required: true,
},
cli.StringFlag{
Name: "deploy-config",
Usage: "Path to hardhat deploy config file",
Required: true,
},
cli.StringFlag{
Name: "network",
Usage: "Name of hardhat deploy network",
Required: true,
},
cli.StringFlag{
Name: "hardhat-deployments",
Usage: "Comma separated list of hardhat deployment directories",
Required: true,
},
cli.IntFlag{
Name: "db-cache",
Usage: "LevelDB cache size in mb",
Value: 1024,
},
cli.IntFlag{
Name: "db-handles",
Usage: "LevelDB number of handles",
Value: 60,
},
},
Action: func(ctx *cli.Context) error {
deployConfig := ctx.String("deploy-config")
config, err := genesis.NewDeployConfig(deployConfig)
if err != nil {
return err
}
ovmAddresses, err := crossdomain.NewAddresses(ctx.String("ovm-addresses"))
if err != nil {
return err
}
ovmAllowances, err := crossdomain.NewAllowances(ctx.String("ovm-allowances"))
if err != nil {
return err
}
ovmMessages, err := crossdomain.NewSentMessageFromJSON(ctx.String("ovm-messages"))
if err != nil {
return err
}
evmMessages, evmAddresses, err := crossdomain.ReadWitnessData(ctx.String("witness-file"))
if err != nil {
return err
}
log.Info(
"Loaded witness data",
"ovmAddresses", len(ovmAddresses),
"evmAddresses", len(evmAddresses),
"ovmAllowances", len(ovmAllowances),
"ovmMessages", len(ovmMessages),
"evmMessages", len(evmMessages),
)
migrationData := crossdomain.MigrationData{
OvmAddresses: ovmAddresses,
EvmAddresses: evmAddresses,
OvmAllowances: ovmAllowances,
OvmMessages: ovmMessages,
EvmMessages: evmMessages,
}
network := ctx.String("network")
deployments := strings.Split(ctx.String("hardhat-deployments"), ",")
hh, err := hardhat.New(network, []string{}, deployments)
if err != nil {
return err
}
l1RpcURL := ctx.String("l1-rpc-url")
l1Client, err := ethclient.Dial(l1RpcURL)
if err != nil {
return err
}
var block *types.Block
tag := config.L1StartingBlockTag
if tag.BlockNumber != nil {
block, err = l1Client.BlockByNumber(context.Background(), big.NewInt(tag.BlockNumber.Int64()))
} else if tag.BlockHash != nil {
block, err = l1Client.BlockByHash(context.Background(), *tag.BlockHash)
} else {
return fmt.Errorf("invalid l1StartingBlockTag in deploy config: %v", tag)
}
if err != nil {
return err
}
dbCache := ctx.Int("db-cache")
dbHandles := ctx.Int("db-handles")
// Read the required deployment addresses from disk if required
if err := config.GetDeployedAddresses(hh); err != nil {
return err
}
if err := config.Check(); err != nil {
return err
}
postLDB, err := db.Open(ctx.String("db-path"), dbCache, dbHandles)
if err != nil {
return err
}
if err := genesis.PostCheckMigratedDB(
postLDB,
migrationData,
&config.L1CrossDomainMessengerProxy,
config.L1ChainID,
config.FinalSystemOwner,
config.ProxyAdminOwner,
&derive.L1BlockInfo{
Number: block.NumberU64(),
Time: block.Time(),
BaseFee: block.BaseFee(),
BlockHash: block.Hash(),
BatcherAddr: config.BatchSenderAddress,
L1FeeOverhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(config.GasPriceOracleOverhead))),
L1FeeScalar: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(config.GasPriceOracleScalar))),
},
); err != nil {
return err
}
if err := postLDB.Close(); err != nil {
return err
}
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Crit("error in migration", "err", err)
}
}
...@@ -106,6 +106,11 @@ func main() { ...@@ -106,6 +106,11 @@ func main() {
Value: "rollup.json", Value: "rollup.json",
Required: true, Required: true,
}, },
cli.BoolFlag{
Name: "post-check-only",
Usage: "Only perform sanity checks",
Required: false,
},
}, },
Action: func(ctx *cli.Context) error { Action: func(ctx *cli.Context) error {
deployConfig := ctx.String("deploy-config") deployConfig := ctx.String("deploy-config")
......
...@@ -25,8 +25,8 @@ func GetOVMETHTotalSupplySlot() common.Hash { ...@@ -25,8 +25,8 @@ func GetOVMETHTotalSupplySlot() common.Hash {
return getOVMETHTotalSupplySlot() return getOVMETHTotalSupplySlot()
} }
// getOVMETHBalance gets a user's OVM ETH balance from state by querying the // GetOVMETHBalance gets a user's OVM ETH balance from state by querying the
// appropriate storage slot directly. // appropriate storage slot directly.
func getOVMETHBalance(db *state.StateDB, addr common.Address) *big.Int { func GetOVMETHBalance(db *state.StateDB, addr common.Address) *big.Int {
return db.GetState(OVMETHAddress, CalcOVMETHStorageKey(addr)).Big() return db.GetState(OVMETHAddress, CalcOVMETHStorageKey(addr)).Big()
} }
...@@ -29,7 +29,9 @@ var ( ...@@ -29,7 +29,9 @@ var (
} }
) )
func MigrateLegacyETH(db *state.StateDB, addresses []common.Address, chainID int, noCheck bool) error { type FilteredOVMETHAddresses []common.Address
func MigrateLegacyETH(db *state.StateDB, addresses FilteredOVMETHAddresses, chainID int, noCheck bool) error {
// Chain params to use for integrity checking. // Chain params to use for integrity checking.
params := crossdomain.ParamsByChainID[chainID] params := crossdomain.ParamsByChainID[chainID]
if params == nil { if params == nil {
...@@ -39,28 +41,15 @@ func MigrateLegacyETH(db *state.StateDB, addresses []common.Address, chainID int ...@@ -39,28 +41,15 @@ func MigrateLegacyETH(db *state.StateDB, addresses []common.Address, chainID int
// Log the chain params for debugging purposes. // Log the chain params for debugging purposes.
log.Info("Chain params", "chain-id", chainID, "supply-delta", params.ExpectedSupplyDelta) log.Info("Chain params", "chain-id", chainID, "supply-delta", params.ExpectedSupplyDelta)
// Deduplicate the list of addresses by converting to a map.
deduped := make(map[common.Address]bool)
for _, addr := range addresses {
deduped[addr] = true
}
// Migrate the legacy ETH to ETH. // Migrate the legacy ETH to ETH.
log.Info("Migrating legacy ETH to ETH", "num-accounts", len(addresses)) log.Info("Migrating legacy ETH to ETH", "num-accounts", len(addresses))
totalMigrated := new(big.Int) totalMigrated := new(big.Int)
logAccountProgress := util.ProgressLogger(1000, "imported accounts") logAccountProgress := util.ProgressLogger(1000, "imported accounts")
for addr := range deduped { for _, addr := range addresses {
// No accounts should have a balance in state. If they do, bail. // Balances are pre-checked not have any balances in state.
if db.GetBalance(addr).Sign() > 0 {
if noCheck {
log.Error("account has non-zero balance in state - should never happen", "addr", addr)
} else {
log.Crit("account has non-zero balance in state - should never happen", "addr", addr)
}
}
// Pull out the OVM ETH balance. // Pull out the OVM ETH balance.
ovmBalance := getOVMETHBalance(db, addr) ovmBalance := GetOVMETHBalance(db, addr)
// Actually perform the migration by setting the appropriate values in state. // Actually perform the migration by setting the appropriate values in state.
db.SetBalance(addr, ovmBalance) db.SetBalance(addr, ovmBalance)
......
package ether package ether
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
"sync"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain" "github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum-optimism/optimism/op-chain-ops/util" "github.com/ethereum-optimism/optimism/op-chain-ops/util"
...@@ -11,132 +14,295 @@ import ( ...@@ -11,132 +14,295 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
const (
// checkJobs is the number of parallel workers to spawn
// when iterating the storage trie.
checkJobs = 64
)
// maxSlot is the maximum possible storage slot.
var maxSlot = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
// accountData is a wrapper struct that contains the balance and address of an account.
// It gets passed via channel to the collector process.
type accountData struct {
balance *big.Int
address common.Address
}
type DBFactory func() (*state.StateDB, error)
// PreCheckBalances checks that the given list of addresses and allowances represents all storage // PreCheckBalances checks that the given list of addresses and allowances represents all storage
// slots in the LegacyERC20ETH contract. We don't have to filter out extra addresses like we do for // slots in the LegacyERC20ETH contract. We don't have to filter out extra addresses like we do for
// withdrawals because we'll simply carry the balance of a given address to the new system, if the // withdrawals because we'll simply carry the balance of a given address to the new system, if the
// account is extra then it won't have any balance and nothing will happen. // account is extra then it won't have any balance and nothing will happen.
func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common.Address, allowances []*crossdomain.Allowance, chainID int, noCheck bool) ([]common.Address, error) { func PreCheckBalances(dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, chainID int, noCheck bool) (FilteredOVMETHAddresses, error) {
// Chain params to use for integrity checking. // Chain params to use for integrity checking.
params := crossdomain.ParamsByChainID[chainID] params := crossdomain.ParamsByChainID[chainID]
if params == nil { if params == nil {
return nil, fmt.Errorf("no chain params for %d", chainID) return nil, fmt.Errorf("no chain params for %d", chainID)
} }
return doMigration(dbFactory, addresses, allowances, params.ExpectedSupplyDelta, noCheck)
}
func doMigration(dbFactory DBFactory, addresses []common.Address, allowances []*crossdomain.Allowance, expDiff *big.Int, noCheck bool) (FilteredOVMETHAddresses, error) {
// We'll need to maintain a list of all addresses that we've seen along with all of the storage // We'll need to maintain a list of all addresses that we've seen along with all of the storage
// slots based on the witness data. // slots based on the witness data.
addrs := make([]common.Address, 0) addrs := make([]common.Address, 0)
slotsAddrs := make(map[common.Hash]common.Address)
slotsInp := make(map[common.Hash]int) slotsInp := make(map[common.Hash]int)
// For each known address, compute its balance key and add it to the list of addresses. // For each known address, compute its balance key and add it to the list of addresses.
// Mint events are instrumented as regular ETH events in the witness data, so we no longer // Mint events are instrumented as regular ETH events in the witness data, so we no longer
// need to iterate over mint events during the migration. // need to iterate over mint events during the migration.
for _, addr := range addresses { for _, addr := range addresses {
addrs = append(addrs, addr) sk := CalcOVMETHStorageKey(addr)
slotsInp[CalcOVMETHStorageKey(addr)] = 1 slotsAddrs[sk] = addr
slotsInp[sk] = 1
} }
// For each known allowance, compute its storage key and add it to the list of addresses. // For each known allowance, compute its storage key and add it to the list of addresses.
for _, allowance := range allowances { for _, allowance := range allowances {
addrs = append(addrs, allowance.From) sk := CalcAllowanceStorageKey(allowance.From, allowance.To)
slotsInp[CalcAllowanceStorageKey(allowance.From, allowance.To)] = 2 slotsAddrs[sk] = allowance.From
slotsInp[sk] = 2
} }
// Add the old SequencerEntrypoint because someone sent it ETH a long time ago and it has a // Add the old SequencerEntrypoint because someone sent it ETH a long time ago and it has a
// balance but none of our instrumentation could easily find it. Special case. // balance but none of our instrumentation could easily find it. Special case.
sequencerEntrypointAddr := common.HexToAddress("0x4200000000000000000000000000000000000005") sequencerEntrypointAddr := common.HexToAddress("0x4200000000000000000000000000000000000005")
addrs = append(addrs, sequencerEntrypointAddr) entrySK := CalcOVMETHStorageKey(sequencerEntrypointAddr)
slotsInp[CalcOVMETHStorageKey(sequencerEntrypointAddr)] = 1 slotsAddrs[entrySK] = sequencerEntrypointAddr
slotsInp[entrySK] = 1
// Build a mapping of every storage slot in the LegacyERC20ETH contract, except the list of // WaitGroup to wait on each iteration job to finish.
// slots that we know we can ignore (totalSupply, name, symbol). var wg sync.WaitGroup
var count int // Channel to receive storage slot keys and values from each iteration job.
slotsAct := make(map[common.Hash]common.Hash) outCh := make(chan accountData)
progress := util.ProgressLogger(1000, "Read OVM_ETH storage slot") // Channel to receive errors from each iteration job.
err := db.ForEachStorage(predeploys.LegacyERC20ETHAddr, func(key, value common.Hash) bool { errCh := make(chan error, checkJobs)
progress() // Channel to cancel all iteration jobs as well as the collector.
cancelCh := make(chan struct{})
// We can safely ignore specific slots (totalSupply, name, symbol).
if ignoredSlots[key] { // Keep track of the total migrated supply.
return true totalFound := new(big.Int)
// Divide the key space into partitions by dividing the key space by the number
// of jobs. This will leave some slots left over, which we handle below.
partSize := new(big.Int).Div(maxSlot.Big(), big.NewInt(checkJobs))
// Define a worker function to iterate over each partition.
worker := func(start, end common.Hash) {
// Decrement the WaitGroup when the function returns.
defer wg.Done()
db, err := dbFactory()
if err != nil {
log.Crit("cannot get database", "err", err)
} }
// Slot exists, so add it to the map. // Create a new storage trie. Each trie returned by db.StorageTrie
slotsAct[key] = value // is a copy, so this is safe for concurrent use.
count++ st, err := db.StorageTrie(predeploys.LegacyERC20ETHAddr)
return true if err != nil {
}) // Should never happen, so explode if it does.
if err != nil { log.Crit("cannot get storage trie for LegacyERC20ETHAddr", "err", err)
return nil, fmt.Errorf("cannot iterate over LegacyERC20ETHAddr: %w", err) }
} if st == nil {
// Should never happen, so explode if it does.
log.Crit("nil storage trie for LegacyERC20ETHAddr")
}
// Log how many slots were iterated over. it := trie.NewIterator(st.NodeIterator(start.Bytes()))
log.Info("Iterated legacy balances", "count", count)
// Iterate over the list of known slots and check that we have a slot for each one. We'll also // Below code is largely based on db.ForEachStorage. We can't use that
// keep track of the total balance to be migrated and throw if the total supply exceeds the // because it doesn't allow us to specify a start and end key.
// expected supply delta. for it.Next() {
totalFound := new(big.Int) select {
var unknown bool case <-cancelCh:
for slot := range slotsAct { // If one of the workers encounters an error, cancel all of them.
slotType, ok := slotsInp[slot] return
if !ok { default:
if noCheck { break
log.Error("ignoring unknown storage slot in state", "slot", slot.String()) }
} else {
unknown = true // Use the raw (i.e., secure hashed) key to check if we've reached
log.Error("unknown storage slot in state", "slot", slot.String()) // the end of the partition.
if new(big.Int).SetBytes(it.Key).Cmp(end.Big()) >= 0 {
return
}
// Skip if the value is empty.
rawValue := it.Value
if len(rawValue) == 0 {
continue continue
} }
}
// Add balances to the total found. // Get the preimage.
switch slotType { key := common.BytesToHash(st.GetKey(it.Key))
case 1:
// Balance slot. // Parse the raw value.
totalFound.Add(totalFound, slotsAct[slot].Big()) _, content, _, err := rlp.Split(rawValue)
case 2: if err != nil {
// Allowance slot. // Should never happen, so explode if it does.
continue log.Crit("mal-formed data in state: %v", err)
default: }
// Should never happen.
if noCheck { // We can safely ignore specific slots (totalSupply, name, symbol).
log.Error("unknown slot type", "slot", slot, "type", slotType) if ignoredSlots[key] {
} else { continue
log.Crit("unknown slot type: %d", slotType) }
slotType, ok := slotsInp[key]
if !ok {
if noCheck {
log.Error("ignoring unknown storage slot in state", "slot", key.String())
} else {
errCh <- fmt.Errorf("unknown storage slot in state: %s", key.String())
return
}
}
// No accounts should have a balance in state. If they do, bail.
addr, ok := slotsAddrs[key]
if !ok {
log.Crit("could not find address in map - should never happen")
}
bal := db.GetBalance(addr)
if bal.Sign() != 0 {
log.Error(
"account has non-zero balance in state - should never happen",
"addr", addr,
"balance", bal.String(),
)
if !noCheck {
errCh <- fmt.Errorf("account has non-zero balance in state - should never happen: %s", addr.String())
return
}
}
// Add balances to the total found.
switch slotType {
case 1:
// Convert the value to a common.Hash, then send to the channel.
value := common.BytesToHash(content)
outCh <- accountData{
balance: value.Big(),
address: addr,
}
case 2:
// Allowance slot.
continue
default:
// Should never happen.
if noCheck {
log.Error("unknown slot type", "slot", key, "type", slotType)
} else {
log.Crit("unknown slot type %d, should never happen", slotType)
}
} }
} }
} }
if unknown {
return nil, errors.New("unknown storage slots in state (see logs for details)") for i := 0; i < checkJobs; i++ {
wg.Add(1)
// Compute the start and end keys for this partition.
start := common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i)), partSize))
var end common.Hash
if i < checkJobs-1 {
// If this is not the last partition, use the next partition's start key as the end.
end = common.BigToHash(new(big.Int).Mul(big.NewInt(int64(i+1)), partSize))
} else {
// If this is the last partition, use the max slot as the end.
end = maxSlot
}
// Kick off our worker.
go worker(start, end)
}
// Make a channel to make sure that the collector process completes.
collectorCloseCh := make(chan struct{})
// Keep track of the last error seen.
var lastErr error
// There are multiple ways that the cancel channel can be closed:
// - if we receive an error from the errCh
// - if the collector process completes
// To prevent panics, we wrap the close in a sync.Once.
var cancelOnce sync.Once
// Kick off another background process to collect
// values from the channel and add them to the map.
var count int
progress := util.ProgressLogger(1000, "Collected OVM_ETH storage slot")
go func() {
defer func() {
collectorCloseCh <- struct{}{}
}()
for {
select {
case account := <-outCh:
progress()
// Accumulate addresses and total supply.
addrs = append(addrs, account.address)
totalFound = new(big.Int).Add(totalFound, account.balance)
case err := <-errCh:
lastErr = err
cancelOnce.Do(func() {
close(cancelCh)
})
case <-cancelCh:
return
}
}
}()
// Wait for the workers to finish.
wg.Wait()
// Close the cancel channel to signal the collector process to stop.
cancelOnce.Do(func() {
close(cancelCh)
})
// Wait for the collector process to finish.
<-collectorCloseCh
// If we saw an error, return it.
if lastErr != nil {
return nil, lastErr
} }
// Log how many slots were iterated over.
log.Info("Iterated legacy balances", "count", count)
// Verify the supply delta. Recorded total supply in the LegacyERC20ETH contract may be higher // Verify the supply delta. Recorded total supply in the LegacyERC20ETH contract may be higher
// than the actual migrated amount because self-destructs will remove ETH supply in a way that // than the actual migrated amount because self-destructs will remove ETH supply in a way that
// cannot be reflected in the contract. This is fine because self-destructs just mean the L2 is // cannot be reflected in the contract. This is fine because self-destructs just mean the L2 is
// actually *overcollateralized* by some tiny amount. // actually *overcollateralized* by some tiny amount.
db, err := dbFactory()
if err != nil {
log.Crit("cannot get database", "err", err)
}
totalSupply := getOVMETHTotalSupply(db) totalSupply := getOVMETHTotalSupply(db)
delta := new(big.Int).Sub(totalSupply, totalFound) delta := new(big.Int).Sub(totalSupply, totalFound)
if delta.Cmp(params.ExpectedSupplyDelta) != 0 { if delta.Cmp(expDiff) != 0 {
if noCheck { log.Error(
log.Error( "supply mismatch",
"supply mismatch", "migrated", totalFound.String(),
"migrated", totalFound.String(), "supply", totalSupply.String(),
"supply", totalSupply.String(), "delta", delta.String(),
"delta", delta.String(), "exp_delta", expDiff.String(),
"exp_delta", params.ExpectedSupplyDelta.String(), )
) if !noCheck {
} else { return nil, fmt.Errorf("supply mismatch: %s", delta.String())
log.Crit(
"supply mismatch",
"migrated", totalFound.String(),
"supply", totalSupply.String(),
"delta", delta.String(),
"exp_delta", params.ExpectedSupplyDelta.String(),
)
} }
} }
...@@ -146,7 +312,7 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common. ...@@ -146,7 +312,7 @@ func PreCheckBalances(ldb ethdb.Database, db *state.StateDB, addresses []common.
"migrated", totalFound.String(), "migrated", totalFound.String(),
"supply", totalSupply.String(), "supply", totalSupply.String(),
"delta", delta.String(), "delta", delta.String(),
"exp_delta", params.ExpectedSupplyDelta.String(), "exp_delta", expDiff.String(),
) )
// We know we have at least a superset of all addresses here since we know that we have every // We know we have at least a superset of all addresses here since we know that we have every
......
package ether
import (
"bytes"
"math/big"
"math/rand"
"os"
"sort"
"testing"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-chain-ops/crossdomain"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/require"
)
func TestPreCheckBalances(t *testing.T) {
log.Root().SetHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true)))
tests := []struct {
name string
totalSupply *big.Int
expDiff *big.Int
stateBalances map[common.Address]*big.Int
stateAllowances map[common.Address]common.Address
inputAddresses []common.Address
inputAllowances []*crossdomain.Allowance
check func(t *testing.T, addrs FilteredOVMETHAddresses, err error)
}{
{
name: "everything matches",
totalSupply: big.NewInt(3),
expDiff: big.NewInt(0),
stateBalances: map[common.Address]*big.Int{
common.HexToAddress("0x123"): big.NewInt(1),
common.HexToAddress("0x456"): big.NewInt(2),
},
stateAllowances: map[common.Address]common.Address{
common.HexToAddress("0x123"): common.HexToAddress("0x456"),
},
inputAddresses: []common.Address{
common.HexToAddress("0x123"),
common.HexToAddress("0x456"),
},
inputAllowances: []*crossdomain.Allowance{
{
From: common.HexToAddress("0x123"),
To: common.HexToAddress("0x456"),
},
},
check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) {
require.NoError(t, err)
require.EqualValues(t, FilteredOVMETHAddresses{
common.HexToAddress("0x123"),
common.HexToAddress("0x456"),
}, addrs)
},
},
{
name: "extra input addresses",
totalSupply: big.NewInt(1),
expDiff: big.NewInt(0),
stateBalances: map[common.Address]*big.Int{
common.HexToAddress("0x123"): big.NewInt(1),
},
inputAddresses: []common.Address{
common.HexToAddress("0x123"),
common.HexToAddress("0x456"),
},
check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) {
require.NoError(t, err)
require.EqualValues(t, FilteredOVMETHAddresses{
common.HexToAddress("0x123"),
}, addrs)
},
},
{
name: "extra input allowances",
totalSupply: big.NewInt(1),
expDiff: big.NewInt(0),
stateBalances: map[common.Address]*big.Int{
common.HexToAddress("0x123"): big.NewInt(1),
},
stateAllowances: map[common.Address]common.Address{
common.HexToAddress("0x123"): common.HexToAddress("0x456"),
},
inputAddresses: []common.Address{
common.HexToAddress("0x123"),
common.HexToAddress("0x456"),
},
inputAllowances: []*crossdomain.Allowance{
{
From: common.HexToAddress("0x123"),
To: common.HexToAddress("0x456"),
},
{
From: common.HexToAddress("0x123"),
To: common.HexToAddress("0x789"),
},
},
check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) {
require.NoError(t, err)
require.EqualValues(t, FilteredOVMETHAddresses{
common.HexToAddress("0x123"),
}, addrs)
},
},
{
name: "missing input addresses",
totalSupply: big.NewInt(2),
expDiff: big.NewInt(0),
stateBalances: map[common.Address]*big.Int{
common.HexToAddress("0x123"): big.NewInt(1),
common.HexToAddress("0x456"): big.NewInt(1),
},
inputAddresses: []common.Address{
common.HexToAddress("0x123"),
},
check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) {
require.Error(t, err)
require.ErrorContains(t, err, "unknown storage slot")
},
},
{
name: "missing input allowances",
totalSupply: big.NewInt(2),
expDiff: big.NewInt(0),
stateBalances: map[common.Address]*big.Int{
common.HexToAddress("0x123"): big.NewInt(1),
},
stateAllowances: map[common.Address]common.Address{
common.HexToAddress("0x123"): common.HexToAddress("0x456"),
common.HexToAddress("0x123"): common.HexToAddress("0x789"),
},
inputAddresses: []common.Address{
common.HexToAddress("0x123"),
},
inputAllowances: []*crossdomain.Allowance{
{
From: common.HexToAddress("0x123"),
To: common.HexToAddress("0x456"),
},
},
check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) {
require.Error(t, err)
require.ErrorContains(t, err, "unknown storage slot")
},
},
{
name: "bad supply diff",
totalSupply: big.NewInt(4),
expDiff: big.NewInt(0),
stateBalances: map[common.Address]*big.Int{
common.HexToAddress("0x123"): big.NewInt(1),
common.HexToAddress("0x456"): big.NewInt(2),
},
inputAddresses: []common.Address{
common.HexToAddress("0x123"),
common.HexToAddress("0x456"),
},
check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) {
require.Error(t, err)
require.ErrorContains(t, err, "supply mismatch")
},
},
{
name: "good supply diff",
totalSupply: big.NewInt(4),
expDiff: big.NewInt(1),
stateBalances: map[common.Address]*big.Int{
common.HexToAddress("0x123"): big.NewInt(1),
common.HexToAddress("0x456"): big.NewInt(2),
},
inputAddresses: []common.Address{
common.HexToAddress("0x123"),
common.HexToAddress("0x456"),
},
check: func(t *testing.T, addrs FilteredOVMETHAddresses, err error) {
require.NoError(t, err)
require.EqualValues(t, FilteredOVMETHAddresses{
common.HexToAddress("0x123"),
common.HexToAddress("0x456"),
}, addrs)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
db := makeLegacyETH(t, tt.totalSupply, tt.stateBalances, tt.stateAllowances)
factory := func() (*state.StateDB, error) {
return db, nil
}
addrs, err := doMigration(factory, tt.inputAddresses, tt.inputAllowances, tt.expDiff, false)
// Sort the addresses since they come in in a random order.
sort.Slice(addrs, func(i, j int) bool {
return bytes.Compare(addrs[i][:], addrs[j][:]) < 0
})
tt.check(t, addrs, err)
})
}
}
func makeLegacyETH(t *testing.T, totalSupply *big.Int, balances map[common.Address]*big.Int, allowances map[common.Address]common.Address) *state.StateDB {
db, err := state.New(common.Hash{}, state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{
Preimages: true,
Cache: 1024,
}), nil)
require.NoError(t, err)
db.CreateAccount(OVMETHAddress)
db.SetState(OVMETHAddress, getOVMETHTotalSupplySlot(), common.BigToHash(totalSupply))
for slot := range ignoredSlots {
if slot == getOVMETHTotalSupplySlot() {
continue
}
db.SetState(OVMETHAddress, slot, common.Hash{31: 0xff})
}
for addr, balance := range balances {
db.SetState(OVMETHAddress, CalcOVMETHStorageKey(addr), common.BigToHash(balance))
}
for from, to := range allowances {
db.SetState(OVMETHAddress, CalcAllowanceStorageKey(from, to), common.BigToHash(big.NewInt(1)))
}
root, err := db.Commit(false)
require.NoError(t, err)
err = db.Database().TrieDB().Commit(root, true)
require.NoError(t, err)
return db
}
// TestPreCheckBalancesRandom tests that the pre-check balances function works
// with random addresses. This test makes sure that the partition logic doesn't
// miss anything.
func TestPreCheckBalancesRandom(t *testing.T) {
addresses := make([]common.Address, 0)
stateBalances := make(map[common.Address]*big.Int)
allowances := make([]*crossdomain.Allowance, 0)
stateAllowances := make(map[common.Address]common.Address)
totalSupply := big.NewInt(0)
for i := 0; i < 100; i++ {
for i := 0; i < rand.Intn(1000); i++ {
addr := randAddr(t)
addresses = append(addresses, addr)
stateBalances[addr] = big.NewInt(int64(rand.Intn(1_000_000)))
totalSupply = new(big.Int).Add(totalSupply, stateBalances[addr])
}
sort.Slice(addresses, func(i, j int) bool {
return bytes.Compare(addresses[i][:], addresses[j][:]) < 0
})
for i := 0; i < rand.Intn(1000); i++ {
addr := randAddr(t)
to := randAddr(t)
allowances = append(allowances, &crossdomain.Allowance{
From: addr,
To: to,
})
stateAllowances[addr] = to
}
db := makeLegacyETH(t, totalSupply, stateBalances, stateAllowances)
factory := func() (*state.StateDB, error) {
return db, nil
}
outAddrs, err := doMigration(factory, addresses, allowances, big.NewInt(0), false)
require.NoError(t, err)
sort.Slice(outAddrs, func(i, j int) bool {
return bytes.Compare(outAddrs[i][:], outAddrs[j][:]) < 0
})
require.EqualValues(t, addresses, outAddrs)
}
}
func randAddr(t *testing.T) common.Address {
var addr common.Address
_, err := rand.Read(addr[:])
require.NoError(t, err)
return addr
}
...@@ -7,6 +7,8 @@ import ( ...@@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-chain-ops/ether"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
...@@ -251,7 +253,7 @@ func PostCheckPredeploys(prevDB, currDB *state.StateDB) error { ...@@ -251,7 +253,7 @@ func PostCheckPredeploys(prevDB, currDB *state.StateDB) error {
// Balances and nonces should match legacy // Balances and nonces should match legacy
oldNonce := prevDB.GetNonce(addr) oldNonce := prevDB.GetNonce(addr)
oldBalance := prevDB.GetBalance(addr) oldBalance := ether.GetOVMETHBalance(prevDB, addr)
newNonce := currDB.GetNonce(addr) newNonce := currDB.GetNonce(addr)
newBalance := currDB.GetBalance(addr) newBalance := currDB.GetBalance(addr)
if oldNonce != newNonce { if oldNonce != newNonce {
...@@ -543,7 +545,7 @@ func CheckWithdrawalsAfter(db vm.StateDB, data crossdomain.MigrationData, l1Cros ...@@ -543,7 +545,7 @@ func CheckWithdrawalsAfter(db vm.StateDB, data crossdomain.MigrationData, l1Cros
// If the sender is _not_ the L2XDM, the value should not be migrated. // If the sender is _not_ the L2XDM, the value should not be migrated.
wd := wdsByOldSlot[key] wd := wdsByOldSlot[key]
if wd.XDomainSender == predeploys.L2CrossDomainMessengerAddr { if wd.MessageSender == predeploys.L2CrossDomainMessengerAddr {
// Make sure the value is abiTrue if this withdrawal should be migrated. // Make sure the value is abiTrue if this withdrawal should be migrated.
if migratedValue != abiTrue { if migratedValue != abiTrue {
innerErr = fmt.Errorf("expected migrated value to be true, but got %s", migratedValue) innerErr = fmt.Errorf("expected migrated value to be true, but got %s", migratedValue)
...@@ -552,7 +554,7 @@ func CheckWithdrawalsAfter(db vm.StateDB, data crossdomain.MigrationData, l1Cros ...@@ -552,7 +554,7 @@ func CheckWithdrawalsAfter(db vm.StateDB, data crossdomain.MigrationData, l1Cros
} else { } else {
// Otherwise, ensure that withdrawals from senders other than the L2XDM are _not_ migrated. // Otherwise, ensure that withdrawals from senders other than the L2XDM are _not_ migrated.
if migratedValue != abiFalse { if migratedValue != abiFalse {
innerErr = fmt.Errorf("a migration from a sender other than the L2XDM was migrated") innerErr = fmt.Errorf("a migration from a sender other than the L2XDM was migrated. sender: %s, migrated value: %s", wd.MessageSender, migratedValue)
return false return false
} }
} }
......
...@@ -82,16 +82,25 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m ...@@ -82,16 +82,25 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
) )
} }
// Set up the backing store. dbFactory := func() (*state.StateDB, error) {
underlyingDB := state.NewDatabaseWithConfig(ldb, &trie.Config{ // Set up the backing store.
Preimages: true, underlyingDB := state.NewDatabaseWithConfig(ldb, &trie.Config{
Cache: 1024, Preimages: true,
}) Cache: 1024,
})
// Open up the state database.
db, err := state.New(header.Root, underlyingDB, nil) // Open up the state database.
db, err := state.New(header.Root, underlyingDB, nil)
if err != nil {
return nil, fmt.Errorf("cannot open StateDB: %w", err)
}
return db, nil
}
db, err := dbFactory()
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot open StateDB: %w", err) return nil, fmt.Errorf("cannot create StateDB: %w", err)
} }
// Before we do anything else, we need to ensure that all of the input configuration is correct // Before we do anything else, we need to ensure that all of the input configuration is correct
...@@ -139,7 +148,7 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m ...@@ -139,7 +148,7 @@ func MigrateDB(ldb ethdb.Database, config *DeployConfig, l1Block *types.Block, m
// Unlike with withdrawals, we do not need to filter out extra addresses because their balances // Unlike with withdrawals, we do not need to filter out extra addresses because their balances
// would necessarily be zero and therefore not affect the migration. // would necessarily be zero and therefore not affect the migration.
log.Info("Checking addresses...", "no-check", noCheck) log.Info("Checking addresses...", "no-check", noCheck)
addrs, err := ether.PreCheckBalances(ldb, db, migrationData.Addresses(), migrationData.OvmAllowances, int(config.L1ChainID), noCheck) addrs, err := ether.PreCheckBalances(dbFactory, migrationData.Addresses(), migrationData.OvmAllowances, int(config.L1ChainID), noCheck)
if err != nil { if err != nil {
return nil, fmt.Errorf("addresses mismatch: %w", err) return nil, fmt.Errorf("addresses mismatch: %w", err)
} }
......
...@@ -26,6 +26,16 @@ the transaction hash. ...@@ -26,6 +26,16 @@ the transaction hash.
into channels. It then stores the channels with metadata on disk where the file name is the Channel ID. into channels. It then stores the channels with metadata on disk where the file name is the Channel ID.
### Force Close
`batch_decoder force-close` will create a transaction data that can be sent from the batcher address to
the batch inbox address which will force close the given channels. This will allow future channels to
be read without waiting for the channel timeout. It uses uses the results from `batch_decoder fetch` to
create the close transaction because the transaction it creates for a specific channel requires information
about if the channel has been closed or not. If it has been closed already but is missing specific frames
those frames need to be generated differently than simply closing the channel.
## JQ Cheat Sheet ## JQ Cheat Sheet
`jq` is a really useful utility for manipulating JSON files. `jq` is a really useful utility for manipulating JSON files.
...@@ -48,7 +58,6 @@ jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].tran ...@@ -48,7 +58,6 @@ jq "select(.is_ready == false)|[.id, .frames[0].inclusion_block, .frames[0].tran
## Roadmap ## Roadmap
- Parallel transaction fetching (CLI-3563) - Parallel transaction fetching (CLI-3563)
- Create force-close channel tx data from channel ID (CLI-3564)
- Pull the batches out of channels & store that information inside the ChannelWithMetadata (CLI-3565) - Pull the batches out of channels & store that information inside the ChannelWithMetadata (CLI-3565)
- Transaction Bytes used - Transaction Bytes used
- Total uncompressed (different from tx bytes) + compressed bytes - Total uncompressed (different from tx bytes) + compressed bytes
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch" "github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/fetch"
"github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/reassemble" "github.com/ethereum-optimism/optimism/op-node/cmd/batch_decoder/reassemble"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -113,6 +114,46 @@ func main() { ...@@ -113,6 +114,46 @@ func main() {
return nil return nil
}, },
}, },
{
Name: "force-close",
Usage: "Create the tx data which will force close a channel",
Flags: []cli.Flag{
cli.StringFlag{
Name: "id",
Required: true,
Usage: "ID of the channel to close",
},
cli.StringFlag{
Name: "inbox",
Value: "0x0000000000000000000000000000000000000000",
Usage: "(Optional) Batch Inbox Address",
},
cli.StringFlag{
Name: "in",
Value: "/tmp/batch_decoder/transactions_cache",
Usage: "Cache directory for the found transactions",
},
},
Action: func(cliCtx *cli.Context) error {
var id derive.ChannelID
if err := (&id).UnmarshalText([]byte(cliCtx.String("id"))); err != nil {
log.Fatal(err)
}
frames := reassemble.LoadFrames(cliCtx.String("in"), common.HexToAddress(cliCtx.String("inbox")))
var filteredFrames []derive.Frame
for _, frame := range frames {
if frame.Frame.ID == id {
filteredFrames = append(filteredFrames, frame.Frame)
}
}
data, err := derive.ForceCloseTxData(filteredFrames)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%x\n", data)
return nil
},
},
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
......
...@@ -38,14 +38,8 @@ type Config struct { ...@@ -38,14 +38,8 @@ type Config struct {
OutDirectory string OutDirectory string
} }
// Channels loads all transactions from the given input directory that are submitted to the func LoadFrames(directory string, inbox common.Address) []FrameWithMetadata {
// specified batch inbox and then re-assembles all channels & writes the re-assembled channels txns := loadTransactions(directory, inbox)
// to the out directory.
func Channels(config Config) {
if err := os.MkdirAll(config.OutDirectory, 0750); err != nil {
log.Fatal(err)
}
txns := loadTransactions(config.InDirectory, config.BatchInbox)
// Sort first by block number then by transaction index inside the block number range. // Sort first by block number then by transaction index inside the block number range.
// This is to match the order they are processed in derivation. // This is to match the order they are processed in derivation.
sort.Slice(txns, func(i, j int) bool { sort.Slice(txns, func(i, j int) bool {
...@@ -56,7 +50,17 @@ func Channels(config Config) { ...@@ -56,7 +50,17 @@ func Channels(config Config) {
} }
}) })
frames := transactionsToFrames(txns) return transactionsToFrames(txns)
}
// Channels loads all transactions from the given input directory that are submitted to the
// specified batch inbox and then re-assembles all channels & writes the re-assembled channels
// to the out directory.
func Channels(config Config) {
if err := os.MkdirAll(config.OutDirectory, 0750); err != nil {
log.Fatal(err)
}
frames := LoadFrames(config.InDirectory, config.BatchInbox)
framesByChannel := make(map[derive.ChannelID][]FrameWithMetadata) framesByChannel := make(map[derive.ChannelID][]FrameWithMetadata)
for _, frame := range frames { for _, frame := range frames {
framesByChannel[frame.Frame.ID] = append(framesByChannel[frame.Frame.ID], frame) framesByChannel[frame.Frame.ID] = append(framesByChannel[frame.Frame.ID], frame)
...@@ -143,6 +147,7 @@ func transactionsToFrames(txns []fetch.TransactionWithMetadata) []FrameWithMetad ...@@ -143,6 +147,7 @@ func transactionsToFrames(txns []fetch.TransactionWithMetadata) []FrameWithMetad
return out return out
} }
// if inbox is the zero address, it will load all frames
func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithMetadata { func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithMetadata {
files, err := os.ReadDir(dir) files, err := os.ReadDir(dir)
if err != nil { if err != nil {
...@@ -152,7 +157,7 @@ func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithM ...@@ -152,7 +157,7 @@ func loadTransactions(dir string, inbox common.Address) []fetch.TransactionWithM
for _, file := range files { for _, file := range files {
f := path.Join(dir, file.Name()) f := path.Join(dir, file.Name())
txm := loadTransactionsFile(f) txm := loadTransactionsFile(f)
if txm.InboxAddr == inbox && txm.ValidSender { if (inbox == common.Address{} || txm.InboxAddr == inbox) && txm.ValidSender {
out = append(out, txm) out = append(out, txm)
} }
} }
......
...@@ -213,3 +213,58 @@ func BlockToBatch(block *types.Block) (*BatchData, error) { ...@@ -213,3 +213,58 @@ func BlockToBatch(block *types.Block) (*BatchData, error) {
}, },
}, nil }, nil
} }
// ForceCloseTxData generates the transaction data for a transaction which will force close
// a channel. It should be given every frame of that channel which has been submitted on
// chain. The frames should be given in order that they appear on L1.
func ForceCloseTxData(frames []Frame) ([]byte, error) {
if len(frames) == 0 {
return nil, errors.New("must provide at least one frame")
}
frameNumbers := make(map[uint16]struct{})
id := frames[0].ID
closeNumber := uint16(0)
closed := false
for i, frame := range frames {
if !closed && frame.IsLast {
closeNumber = frame.FrameNumber
}
closed = closed || frame.IsLast
frameNumbers[frame.FrameNumber] = struct{}{}
if frame.ID != id {
return nil, fmt.Errorf("invalid ID in list: first ID: %v, %vth ID: %v", id, i, frame.ID)
}
}
var out bytes.Buffer
out.WriteByte(DerivationVersion0)
if !closed {
f := Frame{
ID: id,
FrameNumber: 0,
Data: nil,
IsLast: true,
}
if err := f.MarshalBinary(&out); err != nil {
return nil, err
}
} else {
for i := uint16(0); i <= closeNumber; i++ {
if _, ok := frameNumbers[i]; ok {
continue
}
f := Frame{
ID: id,
FrameNumber: i,
Data: nil,
IsLast: false,
}
if err := f.MarshalBinary(&out); err != nil {
return nil, err
}
}
}
return out.Bytes(), nil
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -49,3 +50,69 @@ func TestRLPByteLimit(t *testing.T) { ...@@ -49,3 +50,69 @@ func TestRLPByteLimit(t *testing.T) {
require.Equal(t, err, rlp.ErrValueTooLarge) require.Equal(t, err, rlp.ErrValueTooLarge)
require.Equal(t, out2, "") require.Equal(t, out2, "")
} }
func TestForceCloseTxData(t *testing.T) {
id := [16]byte{0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef}
tests := []struct {
frames []Frame
errors bool
output string
}{
{
frames: []Frame{},
errors: true,
output: "",
},
{
frames: []Frame{Frame{FrameNumber: 0, IsLast: false}, Frame{ID: id, FrameNumber: 1, IsLast: true}},
errors: true,
output: "",
},
{
frames: []Frame{Frame{ID: id, FrameNumber: 0, IsLast: false}},
errors: false,
output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000001",
},
{
frames: []Frame{Frame{ID: id, FrameNumber: 0, IsLast: true}},
errors: false,
output: "00",
},
{
frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}},
errors: false,
output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000001",
},
{
frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: true}},
errors: false,
output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000",
},
{
frames: []Frame{Frame{ID: id, FrameNumber: 2, IsLast: true}},
errors: false,
output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00010000000000",
},
{
frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}, Frame{ID: id, FrameNumber: 3, IsLast: true}},
errors: false,
output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000",
},
{
frames: []Frame{Frame{ID: id, FrameNumber: 1, IsLast: false}, Frame{ID: id, FrameNumber: 3, IsLast: true}, Frame{ID: id, FrameNumber: 5, IsLast: true}},
errors: false,
output: "00deadbeefdeadbeefdeadbeefdeadbeef00000000000000deadbeefdeadbeefdeadbeefdeadbeef00020000000000",
},
}
for i, test := range tests {
out, err := ForceCloseTxData(test.frames)
if test.errors {
require.NotNil(t, err, "Should error on tc %v", i)
require.Nil(t, out, "Should return no value in tc %v", i)
} else {
require.NoError(t, err, "Should not error on tc %v", i)
require.Equal(t, common.FromHex(test.output), out, "Should match output tc %v", i)
}
}
}
...@@ -157,7 +157,7 @@ func (cfg *Config) CheckL2ChainID(ctx context.Context, client L2Client) error { ...@@ -157,7 +157,7 @@ func (cfg *Config) CheckL2ChainID(ctx context.Context, client L2Client) error {
return err return err
} }
if cfg.L2ChainID.Cmp(id) != 0 { if cfg.L2ChainID.Cmp(id) != 0 {
return fmt.Errorf("incorrect L2 RPC chain id %d, expected %d", cfg.L2ChainID, id) return fmt.Errorf("incorrect L2 RPC chain id, expected from config %d, obtained from client %d", cfg.L2ChainID, id)
} }
return nil return nil
} }
......
# @eth-optimism/actor-tests # @eth-optimism/actor-tests
## 0.0.23
### Patch Changes
- Updated dependencies [22c3885f5]
- Updated dependencies [66cafc00a]
- Updated dependencies [f52c07529]
- @eth-optimism/contracts-bedrock@0.13.1
- @eth-optimism/sdk@2.0.1
## 0.0.22 ## 0.0.22
### Patch Changes ### Patch Changes
......
{ {
"name": "@eth-optimism/actor-tests", "name": "@eth-optimism/actor-tests",
"version": "0.0.22", "version": "0.0.23",
"description": "A library and suite of tests to stress test Optimism Bedrock.", "description": "A library and suite of tests to stress test Optimism Bedrock.",
"license": "MIT", "license": "MIT",
"author": "", "author": "",
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
"test:coverage": "yarn test" "test:coverage": "yarn test"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/contracts-bedrock": "0.13.0", "@eth-optimism/contracts-bedrock": "0.13.1",
"@eth-optimism/core-utils": "^0.12.0", "@eth-optimism/core-utils": "^0.12.0",
"@eth-optimism/sdk": "^2.0.0", "@eth-optimism/sdk": "^2.0.1",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
"@types/chai-as-promised": "^7.1.4", "@types/chai-as-promised": "^7.1.4",
"async-mutex": "^0.3.2", "async-mutex": "^0.3.2",
......
# @eth-optimism/atst # @eth-optimism/atst
## 0.2.0
### Minor Changes
- dcd13eec1: Update readAttestations and prepareWriteAttestation to handle keys longer than 32 bytes
- 9fd5be8e2: Remove broken allowFailures as option
- 3f4a43542: Move react api to @eth-optimism/atst/react so react isn't required to run the core sdk
- 71727eae9: Fix main and module in atst package.json
- 3d5f26c49: Deprecate parseAttestationBytes and createRawKey in favor for createKey, createValue
### Patch Changes
- 68bbe48b6: Update docs
- 6fea2f2db: Fixed bug with atst not defaulting to currently connected chain
## 0.1.0 ## 0.1.0
### Minor Changes ### Minor Changes
......
{ {
"name": "@eth-optimism/atst", "name": "@eth-optimism/atst",
"version": "0.1.0", "version": "0.2.0",
"type": "module", "type": "module",
"main": "dist/index.cjs", "main": "dist/index.cjs",
"types": "src/index.ts", "types": "src/index.ts",
......
# @eth-optimism/drippie-mon # @eth-optimism/drippie-mon
## 0.2.1
### Patch Changes
- Updated dependencies [fecd42d67]
- Updated dependencies [66cafc00a]
- @eth-optimism/common-ts@0.8.1
- @eth-optimism/sdk@2.0.1
## 0.2.0 ## 0.2.0
### Minor Changes ### Minor Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/chain-mon", "name": "@eth-optimism/chain-mon",
"version": "0.2.0", "version": "0.2.1",
"description": "[Optimism] Chain monitoring services", "description": "[Optimism] Chain monitoring services",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -32,10 +32,10 @@ ...@@ -32,10 +32,10 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.8.0", "@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/contracts-periphery": "1.0.7", "@eth-optimism/contracts-periphery": "1.0.7",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.0.0", "@eth-optimism/sdk": "2.0.1",
"ethers": "^5.7.0", "ethers": "^5.7.0",
"@types/dateformat": "^5.0.0", "@types/dateformat": "^5.0.0",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
......
# @eth-optimism/common-ts # @eth-optimism/common-ts
## 0.8.1
### Patch Changes
- fecd42d67: Fix BaseServiceV2 configuration for caseCase options
## 0.8.0 ## 0.8.0
### Minor Changes ### Minor Changes
......
{ {
"name": "@eth-optimism/common-ts", "name": "@eth-optimism/common-ts",
"version": "0.8.0", "version": "0.8.1",
"description": "[Optimism] Advanced typescript tooling used by various services", "description": "[Optimism] Advanced typescript tooling used by various services",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
......
# @eth-optimism/contracts-bedrock # @eth-optimism/contracts-bedrock
## 0.13.1
### Patch Changes
- 22c3885f5: Optionally print cast commands during migration
- f52c07529: Print tenderly simulation links during deployment
## 0.13.0 ## 0.13.0
### Minor Changes ### Minor Changes
......
{ {
"name": "@eth-optimism/contracts-bedrock", "name": "@eth-optimism/contracts-bedrock",
"version": "0.13.0", "version": "0.13.1",
"description": "Contracts for Optimism Specs", "description": "Contracts for Optimism Specs",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"devDependencies": { "devDependencies": {
"@eth-optimism/contracts-bedrock": "0.13.0", "@eth-optimism/contracts-bedrock": "0.13.1",
"@eth-optimism/core-utils": "^0.12.0", "@eth-optimism/core-utils": "^0.12.0",
"@eth-optimism/hardhat-deploy-config": "^0.2.5", "@eth-optimism/hardhat-deploy-config": "^0.2.5",
"@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0",
......
# data transport layer # data transport layer
## 0.5.54
### Patch Changes
- Updated dependencies [fecd42d67]
- @eth-optimism/common-ts@0.8.1
## 0.5.53 ## 0.5.53
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/data-transport-layer", "name": "@eth-optimism/data-transport-layer",
"version": "0.5.53", "version": "0.5.54",
"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",
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.8.0", "@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/contracts": "0.5.40", "@eth-optimism/contracts": "0.5.40",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@ethersproject/providers": "^5.7.0", "@ethersproject/providers": "^5.7.0",
......
# @eth-optimism/fault-detector # @eth-optimism/fault-detector
## 0.6.2
### Patch Changes
- f9b579d55: Fixes a bug that would cause the fault detector to error out if no outputs had been proposed yet.
- Updated dependencies [fecd42d67]
- Updated dependencies [66cafc00a]
- @eth-optimism/common-ts@0.8.1
- @eth-optimism/sdk@2.0.1
## 0.6.1 ## 0.6.1
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/fault-detector", "name": "@eth-optimism/fault-detector",
"version": "0.6.1", "version": "0.6.2",
"description": "[Optimism] Service for detecting faulty L2 output proposals", "description": "[Optimism] Service for detecting faulty L2 output proposals",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
...@@ -47,10 +47,10 @@ ...@@ -47,10 +47,10 @@
"ts-node": "^10.9.1" "ts-node": "^10.9.1"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "^0.8.0", "@eth-optimism/common-ts": "^0.8.1",
"@eth-optimism/contracts": "^0.5.40", "@eth-optimism/contracts": "^0.5.40",
"@eth-optimism/core-utils": "^0.12.0", "@eth-optimism/core-utils": "^0.12.0",
"@eth-optimism/sdk": "^2.0.0", "@eth-optimism/sdk": "^2.0.1",
"@ethersproject/abstract-provider": "^5.7.0" "@ethersproject/abstract-provider": "^5.7.0"
} }
} }
# @eth-optimism/message-relayer # @eth-optimism/message-relayer
## 0.5.32
### Patch Changes
- Updated dependencies [fecd42d67]
- Updated dependencies [66cafc00a]
- @eth-optimism/common-ts@0.8.1
- @eth-optimism/sdk@2.0.1
## 0.5.31 ## 0.5.31
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/message-relayer", "name": "@eth-optimism/message-relayer",
"version": "0.5.31", "version": "0.5.32",
"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",
...@@ -31,9 +31,9 @@ ...@@ -31,9 +31,9 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.8.0", "@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/sdk": "2.0.0", "@eth-optimism/sdk": "2.0.1",
"ethers": "^5.7.0" "ethers": "^5.7.0"
}, },
"devDependencies": { "devDependencies": {
......
# @eth-optimism/replica-healthcheck # @eth-optimism/replica-healthcheck
## 1.2.3
### Patch Changes
- Updated dependencies [fecd42d67]
- @eth-optimism/common-ts@0.8.1
## 1.2.2 ## 1.2.2
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/replica-healthcheck", "name": "@eth-optimism/replica-healthcheck",
"version": "1.2.2", "version": "1.2.3",
"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",
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
"url": "https://github.com/ethereum-optimism/optimism.git" "url": "https://github.com/ethereum-optimism/optimism.git"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/common-ts": "0.8.0", "@eth-optimism/common-ts": "0.8.1",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@ethersproject/abstract-provider": "^5.7.0" "@ethersproject/abstract-provider": "^5.7.0"
}, },
......
# @eth-optimism/sdk # @eth-optimism/sdk
## 2.0.1
### Patch Changes
- 66cafc00a: Update migrated withdrawal gaslimit calculation
- Updated dependencies [22c3885f5]
- Updated dependencies [f52c07529]
- @eth-optimism/contracts-bedrock@0.13.1
## 2.0.0 ## 2.0.0
### Major Changes ### Major Changes
......
{ {
"name": "@eth-optimism/sdk", "name": "@eth-optimism/sdk",
"version": "2.0.0", "version": "2.0.1",
"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",
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
"dependencies": { "dependencies": {
"@eth-optimism/contracts": "0.5.40", "@eth-optimism/contracts": "0.5.40",
"@eth-optimism/core-utils": "0.12.0", "@eth-optimism/core-utils": "0.12.0",
"@eth-optimism/contracts-bedrock": "0.13.0", "@eth-optimism/contracts-bedrock": "0.13.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merkletreejs": "^0.2.27", "merkletreejs": "^0.2.27",
"rlp": "^2.2.7" "rlp": "^2.2.7"
......
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