Commit 29c7b445 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

op-chain-ops: delete memdb (#10404)

* op-chain-ops: delete memdb

The simple in memory statedb implementation that was
backed by a genesis has served us well but is no longer
required since we build the genesis files using foundry.
This was originally used to enable simple execution to
deploy contracts and then dump into a `genesis.json`.
Keeping this memdb around will only bloat PRs that update
the geth version as they may change the interface to the
statedb. Instead of using the memdb interface, the genesis
is modified directly.

* lint: fix

* style: way better
Co-authored-by: default avatarprotolambda <proto@protolambda.com>

* build: fix

* build: fix

---------
Co-authored-by: default avatarprotolambda <proto@protolambda.com>
parent 7534ac50
......@@ -4,16 +4,12 @@ import (
"fmt"
"math/big"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
)
// PrecompileCount represents the number of precompile addresses
......@@ -61,43 +57,43 @@ func BuildL1DeveloperGenesis(config *DeployConfig, dump *ForgeAllocs, l1Deployme
}
// copy, for safety when the dump is reused (like in e2e testing)
genesis.Alloc = dump.Copy().Accounts
memDB := state.NewMemoryStateDB(genesis)
FundDevAccounts(memDB)
SetPrecompileBalances(memDB)
FundDevAccounts(genesis)
SetPrecompileBalances(genesis)
l1Deployments.ForEach(func(name string, addr common.Address) {
acc := memDB.GetAccount(addr)
if acc != nil {
acc, ok := genesis.Alloc[addr]
if ok {
log.Info("Included L1 deployment", "name", name, "address", addr, "balance", acc.Balance, "storage", len(acc.Storage), "nonce", acc.Nonce)
} else {
log.Info("Excluded L1 deployment", "name", name, "address", addr)
}
})
return memDB.Genesis(), nil
}
// CreateAccountNotExists creates the account in the `vm.StateDB` if it doesn't exist.
func CreateAccountNotExists(db vm.StateDB, account common.Address) {
if !db.Exist(account) {
db.CreateAccount(account)
}
return genesis, nil
}
// FundDevAccounts will fund each of the development accounts.
func FundDevAccounts(db vm.StateDB) {
func FundDevAccounts(gen *core.Genesis) {
for _, account := range DevAccounts {
CreateAccountNotExists(db, account)
db.AddBalance(account, uint256.MustFromBig(devBalance))
acc := gen.Alloc[account]
if acc.Balance == nil {
acc.Balance = new(big.Int)
}
acc.Balance = acc.Balance.Add(acc.Balance, devBalance)
gen.Alloc[account] = acc
}
}
// SetPrecompileBalances will set a single wei at each precompile address.
// This is an optimization to make calling them cheaper.
func SetPrecompileBalances(db vm.StateDB) {
func SetPrecompileBalances(gen *core.Genesis) {
for i := 0; i < PrecompileCount; i++ {
addr := common.BytesToAddress([]byte{byte(i)})
CreateAccountNotExists(db, addr)
db.AddBalance(addr, uint256.NewInt(1))
acc := gen.Alloc[addr]
if acc.Balance == nil {
acc.Balance = new(big.Int)
}
acc.Balance = acc.Balance.Add(acc.Balance, big.NewInt(1))
gen.Alloc[addr] = acc
}
}
package genesis
import (
"math/big"
"testing"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
)
// TestFundDevAccounts ensures that the developer accounts are
// added to the genesis state correctly.
func TestFundDevAccounts(t *testing.T) {
gen := core.Genesis{
Alloc: make(types.GenesisAlloc),
}
FundDevAccounts(&gen)
require.Equal(t, len(gen.Alloc), len(DevAccounts))
for _, account := range gen.Alloc {
require.Equal(t, devBalance, account.Balance)
}
}
// TestSetPrecompileBalances ensures that the precompiles are
// initialized with a balance of 1.
func TestSetPrecompileBalances(t *testing.T) {
gen := core.Genesis{
Alloc: make(types.GenesisAlloc),
}
SetPrecompileBalances(&gen)
require.Equal(t, len(gen.Alloc), PrecompileCount)
for _, account := range gen.Alloc {
require.Equal(t, big.NewInt(1), account.Balance)
}
}
package state
import (
"bytes"
"fmt"
"math/big"
"sync"
"github.com/holiman/uint256"
"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/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
var _ vm.StateDB = (*MemoryStateDB)(nil)
var (
emptyCodeHash = crypto.Keccak256(nil)
zeroAddr = common.Address{}
)
// MemoryStateDB implements geth's StateDB interface
// but operates on a core.Genesis so that a genesis.json
// can easily be created.
type MemoryStateDB struct {
rw sync.RWMutex
genesis *core.Genesis
}
func NewMemoryStateDB(genesis *core.Genesis) *MemoryStateDB {
if genesis == nil {
genesis = core.DeveloperGenesisBlock(15_000_000, &zeroAddr)
}
return &MemoryStateDB{
genesis: genesis,
rw: sync.RWMutex{},
}
}
// Genesis is a getter for the underlying core.Genesis
func (db *MemoryStateDB) Genesis() *core.Genesis {
return db.genesis
}
// GetAccount is a getter for a types.Account found in
// the core.Genesis
func (db *MemoryStateDB) GetAccount(addr common.Address) *types.Account {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return nil
}
return &account
}
// StateDB interface implemented below
func (db *MemoryStateDB) CreateAccount(addr common.Address) {
db.rw.Lock()
defer db.rw.Unlock()
db.createAccount(addr)
}
func (db *MemoryStateDB) createAccount(addr common.Address) {
if _, ok := db.genesis.Alloc[addr]; !ok {
db.genesis.Alloc[addr] = types.Account{
Code: []byte{},
Storage: make(map[common.Hash]common.Hash),
Balance: big.NewInt(0),
Nonce: 0,
}
}
}
func (db *MemoryStateDB) SubBalance(addr common.Address, amount *uint256.Int) {
db.rw.Lock()
defer db.rw.Unlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
panic(fmt.Sprintf("%s not in state", addr))
}
if account.Balance.Sign() == 0 {
return
}
account.Balance.Sub(account.Balance, amount.ToBig())
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) AddBalance(addr common.Address, amount *uint256.Int) {
db.rw.Lock()
defer db.rw.Unlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
panic(fmt.Sprintf("%s not in state", addr))
}
account.Balance.Add(account.Balance, amount.ToBig())
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) GetBalance(addr common.Address) *uint256.Int {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return common.U2560
}
return uint256.MustFromBig(account.Balance)
}
func (db *MemoryStateDB) GetNonce(addr common.Address) uint64 {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return 0
}
return account.Nonce
}
func (db *MemoryStateDB) SetNonce(addr common.Address, value uint64) {
db.rw.Lock()
defer db.rw.Unlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return
}
account.Nonce = value
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) GetCodeHash(addr common.Address) common.Hash {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return common.Hash{}
}
if len(account.Code) == 0 {
return common.BytesToHash(emptyCodeHash)
}
return common.BytesToHash(crypto.Keccak256(account.Code))
}
func (db *MemoryStateDB) GetCode(addr common.Address) []byte {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return nil
}
if bytes.Equal(crypto.Keccak256(account.Code), emptyCodeHash) {
return nil
}
return account.Code
}
func (db *MemoryStateDB) SetCode(addr common.Address, code []byte) {
db.rw.Lock()
defer db.rw.Unlock()
db.createAccount(addr)
account, ok := db.genesis.Alloc[addr]
if !ok {
return
}
account.Code = code
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) GetCodeSize(addr common.Address) int {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return 0
}
if bytes.Equal(crypto.Keccak256(account.Code), emptyCodeHash) {
return 0
}
return len(account.Code)
}
func (db *MemoryStateDB) AddRefund(uint64) {
panic("AddRefund unimplemented")
}
func (db *MemoryStateDB) SubRefund(uint64) {
panic("SubRefund unimplemented")
}
func (db *MemoryStateDB) GetRefund() uint64 {
panic("GetRefund unimplemented")
}
func (db *MemoryStateDB) GetCommittedState(common.Address, common.Hash) common.Hash {
panic("GetCommittedState unimplemented")
}
func (db *MemoryStateDB) GetState(addr common.Address, key common.Hash) common.Hash {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return common.Hash{}
}
return account.Storage[key]
}
func (db *MemoryStateDB) SetState(addr common.Address, key, value common.Hash) {
db.rw.Lock()
defer db.rw.Unlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
panic(fmt.Sprintf("%s not in state", addr))
}
account.Storage[key] = value
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) DeleteState(addr common.Address, key common.Hash) {
db.rw.Lock()
defer db.rw.Unlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
panic(fmt.Sprintf("%s not in state", addr))
}
delete(account.Storage, key)
db.genesis.Alloc[addr] = account
}
func (db *MemoryStateDB) SelfDestruct(common.Address) {
panic("SelfDestruct unimplemented")
}
func (db *MemoryStateDB) HasSelfDestructed(common.Address) bool {
panic("HasSelfDestructed unimplemented")
}
func (db *MemoryStateDB) Selfdestruct6780(common.Address) {
panic("Selfdestruct6780 unimplemented")
}
// Exist reports whether the given account exists in state.
// Notably this should also return true for suicided accounts.
func (db *MemoryStateDB) Exist(addr common.Address) bool {
db.rw.RLock()
defer db.rw.RUnlock()
_, ok := db.genesis.Alloc[addr]
return ok
}
// Empty returns whether the given account is empty. Empty
// is defined according to EIP161 (balance = nonce = code = 0).
func (db *MemoryStateDB) Empty(addr common.Address) bool {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
isZeroNonce := account.Nonce == 0
isZeroValue := account.Balance.Sign() == 0
isEmptyCode := bytes.Equal(crypto.Keccak256(account.Code), emptyCodeHash)
return ok || (isZeroNonce && isZeroValue && isEmptyCode)
}
func (db *MemoryStateDB) PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
panic("PrepareAccessList unimplemented")
}
func (db *MemoryStateDB) AddressInAccessList(addr common.Address) bool {
panic("AddressInAccessList unimplemented")
}
func (db *MemoryStateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) {
panic("SlotInAccessList unimplemented")
}
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
func (db *MemoryStateDB) AddAddressToAccessList(addr common.Address) {
panic("AddAddressToAccessList unimplemented")
}
// 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
func (db *MemoryStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) {
panic("AddSlotToAccessList unimplemented")
}
func (db *MemoryStateDB) RevertToSnapshot(int) {
panic("RevertToSnapshot unimplemented")
}
func (db *MemoryStateDB) Snapshot() int {
panic("Snapshot unimplemented")
}
func (db *MemoryStateDB) AddLog(*types.Log) {
panic("AddLog unimplemented")
}
func (db *MemoryStateDB) AddPreimage(common.Hash, []byte) {
panic("AddPreimage unimplemented")
}
func (db *MemoryStateDB) ForEachStorage(addr common.Address, cb func(common.Hash, common.Hash) bool) error {
db.rw.RLock()
defer db.rw.RUnlock()
account, ok := db.genesis.Alloc[addr]
if !ok {
return nil
}
for key, value := range account.Storage {
if !cb(key, value) {
return nil
}
}
return nil
}
func (db *MemoryStateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
panic("transient state is unsupported")
}
func (db *MemoryStateDB) SetTransientState(addr common.Address, key, value common.Hash) {
panic("transient state is unsupported")
}
func (db *MemoryStateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
// no-op, no transient state to prepare, nor any access-list to set/prepare
}
package state_test
import (
crand "crypto/rand"
"math/big"
"math/rand"
"testing"
"time"
"github.com/holiman/uint256"
"github.com/ethereum-optimism/optimism/op-chain-ops/state"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
)
func TestAddBalance(t *testing.T) {
t.Parallel()
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
db := state.NewMemoryStateDB(nil)
for i := 0; i < 100; i++ {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
value := uint256.NewInt(uint64(rng.Intn(1000)))
db.CreateAccount(addr)
db.AddBalance(addr, value)
account := db.GetAccount(addr)
require.NotNil(t, account)
require.Equal(t, uint256.MustFromBig(account.Balance), value)
}
}
func TestCode(t *testing.T) {
t.Parallel()
db := state.NewMemoryStateDB(nil)
for i := 0; i < 100; i++ {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
db.CreateAccount(addr)
pre := db.GetCode(addr)
require.Nil(t, pre)
code := make([]byte, rand.Intn(1024))
_, err := crand.Read(code)
require.NoError(t, err)
db.SetCode(addr, code)
post := db.GetCode(addr)
if len(code) == 0 {
require.Nil(t, post)
} else {
require.Equal(t, post, code)
}
size := db.GetCodeSize(addr)
require.Equal(t, size, len(code))
codeHash := db.GetCodeHash(addr)
require.Equal(t, codeHash, common.BytesToHash(crypto.Keccak256(code)))
}
}
func BigEqual(a, b *big.Int) bool {
if a == nil || b == nil {
return a == b
} else {
return a.Cmp(b) == 0
}
}
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