Commit c030d854 authored by protolambda's avatar protolambda

op-wheel: cheat commands logic

parent fe7509da
package cheat
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"math/big"
"path/filepath"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
var HundredETH = big.NewInt(0).Mul(big.NewInt(100), big.NewInt(1000000000000000000))
type Cheater struct {
// The database of the chain with the head block that we patch the state-root of, once the state is updated.
DB ethdb.Database
// Initialized chain, wrapping the database with in-memory presentation of headers and recent changes and such.
Blockchain *core.BlockChain
// The Cheater avoids making writes if this is set to True, and opens the DB as readonly.
ReadOnly bool
}
func OpenGethRawDB(dataDirPath string, readOnly bool) (ethdb.Database, error) {
// don't use readonly mode in actual DB, it doesn't work with Geth.
db, err := rawdb.NewLevelDBDatabaseWithFreezer(dataDirPath, 2048, 500, filepath.Join(dataDirPath, "ancient"), "", readOnly)
if err != nil {
return nil, fmt.Errorf("failed to open leveldb: %w", err)
}
return db, nil
}
// OpenGethDB opens a geth database to apply cheats to.
func OpenGethDB(dataDirPath string, readOnly bool) (*Cheater, error) {
db, err := OpenGethRawDB(dataDirPath, readOnly)
if err != nil {
return nil, err
}
ch, err := core.NewBlockChain(db, nil, nil, nil,
beacon.New(ethash.NewFullFaker()), vm.Config{}, nil, nil)
if err != nil {
_ = db.Close()
return nil, fmt.Errorf("failed to open blockchain around chain db: %w", err)
}
return &Cheater{
DB: db,
Blockchain: ch,
ReadOnly: readOnly,
}, nil
}
func (ch *Cheater) Close() error {
return ch.DB.Close()
}
type HeadFn func(headState *state.StateDB) error
// RunAndClose runs the given function on the head-state, and then persists any changes (if not ReadOnly),
// and updates the blockchain headers indexes to reflect the new state-root, so geth will believe the cheat
// (unless it ever re-applies the block).
func (ch *Cheater) RunAndClose(fn HeadFn) error {
preBlock := ch.Blockchain.CurrentBlock()
if a, b := preBlock.NumberU64(), ch.Blockchain.Genesis().NumberU64(); a <= b {
return fmt.Errorf("cheating at genesis (head block %d <= genesis block %d) is not supported", a, b)
}
state, err := ch.Blockchain.StateAt(preBlock.Root())
if err != nil {
_ = ch.Close()
return fmt.Errorf("failed to look up head state: %w", err)
}
if err := fn(state); err != nil {
_ = ch.Close()
return fmt.Errorf("failed to run state change: %w", err)
}
if ch.ReadOnly {
return ch.Close()
}
// commit the changes, and then update the state-root
stateRoot, err := state.Commit(true)
if err != nil {
_ = ch.Close()
return fmt.Errorf("failed to commit state change: %w", err)
}
header := preBlock.Header()
header.Root = stateRoot
blockHash := header.Hash()
// We have to manually commit the updated state root to the database.
if err := state.Database().TrieDB().Commit(stateRoot, true, nil); err != nil {
return fmt.Errorf("error committing trie db: %w", err)
}
// based on core.BlockChain.writeHeadBlock:
// Add the block to the canonical chain number scheme and mark as the head
batch := ch.DB.NewBatch()
if ch.Blockchain.CurrentFinalizedBlock().Hash() == preBlock.Hash() {
rawdb.WriteFinalizedBlockHash(batch, blockHash)
}
rawdb.DeleteHeaderNumber(batch, preBlock.Hash())
rawdb.WriteHeadHeaderHash(batch, blockHash)
rawdb.WriteHeadFastBlockHash(batch, blockHash)
rawdb.WriteCanonicalHash(batch, blockHash, preBlock.NumberU64())
rawdb.WriteHeaderNumber(batch, blockHash, preBlock.NumberU64())
rawdb.WriteHeader(batch, header)
// not keyed by blockhash, and we didn't remove any txs, so we just leave this one as-is.
// rawdb.WriteTxLookupEntriesByBlock(batch, block)
rawdb.WriteHeadBlockHash(batch, blockHash)
// Geth stores the TD for each block separately from the block itself. We must update this
// manually, otherwise Geth thinks we haven't reached TTD yet and tries to build a block
// using Clique consensus, which causes a panic.
rawdb.WriteTd(batch, blockHash, preBlock.NumberU64(), ch.Blockchain.GetTd(preBlock.Hash(), preBlock.NumberU64()))
// Geth maintains an internal mapping between block bodies and their hashes. None of the database
// accessors above update this mapping, so we need to do it manually.
oldKey := blockBodyKey(preBlock.NumberU64(), preBlock.Hash())
oldBody := rawdb.ReadBodyRLP(ch.DB, preBlock.Hash(), preBlock.NumberU64())
newKey := blockBodyKey(preBlock.NumberU64(), blockHash)
if err := batch.Delete(oldKey); err != nil {
return fmt.Errorf("error deleting old block body key")
}
if err := batch.Put(newKey, oldBody); err != nil {
return fmt.Errorf("error setting new block body key")
}
// Flush the whole batch into the disk, exit the node if failed
if err := batch.Write(); err != nil {
_ = ch.Close()
return fmt.Errorf("failed to update chain indexes and markers: %w", err)
}
// Technically there are more in-memory things to update in real geth,
// to which we don't even have public API access, but that's fine, we're done.
// *And we did update the finalized marker, which is flushed from memory to disk on shutdown in geth.
// bc.hc.SetCurrentHeader(block.Header())
// headFastBlockGauge.Update(int64(block.NumberU64()))
// headBlockGauge.Update(int64(block.NumberU64()))
return ch.Close()
}
// StorageSet modifies the storage of the given address at the given key to the given value.
func StorageSet(address common.Address, key common.Hash, value common.Hash) HeadFn {
return func(headState *state.StateDB) error {
headState.SetState(address, key, value)
return nil
}
}
// StorageGet just reads the storage of the given address at the given key.
func StorageGet(address common.Address, key common.Hash, w io.Writer) HeadFn {
return func(headState *state.StateDB) error {
value := headState.GetState(address, key)
_, err := io.WriteString(w, value.Hex())
return err
}
}
// StorageReadAll reads all values of the given address, and writes it as a (+) diff to the given output writer.
// Simply replace the (+) with (-) if you need to apply the diff as removal of values.
// Combined with StoragePatch this allows for quick surgery of 1 account in one database,
// to another account (maybe even in a different database!).
func StorageReadAll(address common.Address, w io.Writer) HeadFn {
return func(headState *state.StateDB) error {
storage := headState.StorageTrie(address)
iter := trie.NewIterator(storage.NodeIterator(nil))
for iter.Next() {
if _, err := fmt.Fprintf(w, "+ %x = %x\n", iter.Key, dbValueToHash(iter.Value)); err != nil {
return err
}
}
return nil
}
}
func dbValueToHash(enc []byte) common.Hash {
var value common.Hash
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
panic(err)
}
value.SetBytes(content)
}
return value
}
// StorageDiff compares the storage of two different accounts, and writes a patch with differences.
// Each difference is expressed with 1 character + or - to indicate the change from a to b, followed by key = value.
func StorageDiff(out io.Writer, addressA, addressB common.Address) HeadFn {
return func(headState *state.StateDB) error {
aStorage := headState.StorageTrie(addressA)
bStorage := headState.StorageTrie(addressB)
aIter := trie.NewIterator(aStorage.NodeIterator(nil))
bIter := trie.NewIterator(bStorage.NodeIterator(nil))
hasA := aIter.Next()
hasB := bIter.Next()
for {
if !hasA && !hasB {
break
}
if cmp := bytes.Compare(aIter.Key, bIter.Key); cmp < 0 {
// a is smaller, and thus missing in b. Print and move forward a
if _, err := fmt.Fprintf(out, "- %x = %x\n", aIter.Key, dbValueToHash(aIter.Value)); err != nil {
return err
}
hasA = aIter.Next()
} else if cmp > 0 {
// b is smaller, and thus missing in a. Print and move forward b
if _, err := fmt.Fprintf(out, "+ %x = %x\n", bIter.Key, dbValueToHash(bIter.Value)); err != nil {
return err
}
hasB = bIter.Next()
} else if cmp == 0 {
// same key, now check if the values differ
if !bytes.Equal(aIter.Value, bIter.Value) {
if _, err := fmt.Fprintf(out, "- %x = %x\n", aIter.Key, dbValueToHash(aIter.Value)); err != nil {
return err
}
if _, err := fmt.Fprintf(out, "+ %x = %x\n", bIter.Key, dbValueToHash(bIter.Value)); err != nil {
return err
}
}
// move both
hasA = aIter.Next()
hasB = bIter.Next()
}
}
return nil
}
}
// StoragePatch applies a patch of changes to the given state account trie.
// Changes are hex encoded key-value pairs separated by (=).
// Additions are prefixed with (+).
// Deletions are prefixed with (-) and overwrite it to a zero value.
// Comments (#) and empty lines are ignored.
func StoragePatch(patch io.Reader, address common.Address) HeadFn {
return func(headState *state.StateDB) error {
s := bufio.NewScanner(patch)
i := 0
for s.Scan() {
line := s.Text()
if len(line) < 1 || line[0] == '#' { // skip empty lines and comments
continue
}
parts := strings.Split(line[1:], "=")
keyHex := strings.TrimSpace(parts[0])
valueHex := strings.TrimSpace(parts[1])
var key, value common.Hash
if err := key.UnmarshalText([]byte(keyHex)); err != nil {
return fmt.Errorf("key %s is malformatted: %w", keyHex, err)
}
if err := value.UnmarshalText([]byte(valueHex)); err != nil {
return fmt.Errorf("key %s has malformatted value %s: %w", keyHex, valueHex, err)
}
switch line[0] {
case '+':
headState.SetState(address, key, value)
case '-':
headState.SetState(address, key, common.Hash{})
default:
return fmt.Errorf("unrecognized line diff token")
}
i += 1
if i%1000 == 0 { // for every 1000 values, commit to disk
if _, err := headState.Commit(true); err != nil {
return fmt.Errorf("failed to commit state to disk after patching %d entries: %w", i, err)
}
}
}
return nil
}
}
type OvmOwnersConfig struct {
Owner common.Address `json:"owner"`
Sequencer common.Address `json:"sequencer"`
Proposer common.Address `json:"proposer"`
}
func OvmOwners(conf *OvmOwnersConfig) HeadFn {
return func(headState *state.StateDB) error {
// Address manager owner
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.Hash{}, conf.Owner.Hash())
// L1SB proxy owner
headState.SetState(common.HexToAddress("0x636Af16bf2f682dD3109e60102b8E1A089FedAa8"), common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"), conf.Owner.Hash())
// L1XDM owner
headState.SetState(common.HexToAddress("0x5086d1eEF304eb5284A0f6720f79403b4e9bE294"), common.Hash{31: 0x33}, conf.Owner.Hash())
// L1 ERC721 bridge owner
headState.SetState(common.HexToAddress("0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9"), common.HexToHash("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"), conf.Owner.Hash())
// Legacy sequencer/proposer addresses
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.HexToHash("0x2e0dfce60e9e27f035ce28f63c1bdd77cff6b13d8909da4d81d623ff9123fbdc"), conf.Sequencer.Hash())
headState.SetState(common.HexToAddress("0xa6f73589243a6A7a9023b1Fa0651b1d89c177111"), common.HexToHash("0x9776dbdebd0d5eedaea450b21da9901ecd5254e5136a3a9b7b0ecd532734d5b5"), conf.Proposer.Hash())
// Fund sequencer and proposer with 100 ETH
headState.SetBalance(conf.Sequencer, HundredETH)
headState.SetBalance(conf.Proposer, HundredETH)
return nil
}
}
func SetBalance(addr common.Address, amount *big.Int) HeadFn {
return func(headState *state.StateDB) error {
headState.SetBalance(addr, amount)
return nil
}
}
func SetNonce(addr common.Address, nonce uint64) HeadFn {
return func(headState *state.StateDB) error {
headState.SetNonce(addr, nonce)
return nil
}
}
// blockBodyKey returns the database key to use for storing the body of a block.
// This function was copied from Geth's core/rawdb/accessors_chain.go.
func blockBodyKey(number uint64, hash common.Hash) []byte {
return append(append([]byte("b"), encodeBlockNumber(number)...), hash.Bytes()...)
}
// encodeBlockNumber encodes a block number as big endian uint64. This function was
// copied from Geth's core/rawdb/accessors_chain.go file.
func encodeBlockNumber(number uint64) []byte {
enc := make([]byte, 8)
binary.BigEndian.PutUint64(enc, number)
return enc
}
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