Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/xplugethhooks-core-state…
Browse files Browse the repository at this point in the history
…' into xdevelop
  • Loading branch information
philip-morlier committed Sep 11, 2024
2 parents 39388c7 + ada583d commit 6ef08a0
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 10 deletions.
32 changes: 22 additions & 10 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,11 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
nodes = trienode.NewMergedNodeSet()
codeWriter = s.db.DiskDB().NewBatch()
)

// begin PluGeth injection
codeUpdates := make(map[common.Hash][]byte)
// end PluGeth injection

// Handle all state deletions first
incomplete, err := s.handleDestruction(nodes)
if err != nil {
Expand All @@ -1657,6 +1662,9 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
// Write any contract code associated with the state object
if obj.code != nil && obj.dirtyCode {
rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
// begin PluGeth injection
codeUpdates[common.BytesToHash(obj.CodeHash())] = obj.code
// end PluGeth injection
obj.dirtyCode = false
}
// Write any storage changes in the state object to its storage trie
Expand Down Expand Up @@ -1720,18 +1728,22 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
start := time.Now()
// Only update if there's a state transition (skip empty Clique blocks)
if parent := s.snap.Root(); parent != root {
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
}
// Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := s.snaps.Cap(root, 128); err != nil {
log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err)
//begin PluGeth code injection
pluginStateUpdate(root, parent, s.snap, s.trie, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages, codeUpdates)
if _, ok := s.snap.(*pluginSnapshot); !ok && s.snaps != nil { // This if statement (but not its content) was added by PluGeth
//end PluGeth injection
if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
}
// Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := s.snaps.Cap(root, 128); err != nil {
log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err)
}
}
}

if metrics.EnabledExpensive {
s.SnapshotCommits += time.Since(start)
}
Expand Down
195 changes: 195 additions & 0 deletions core/state/xplugeth_hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package state

import (
"fmt"
"bytes"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/openrelayxyz/xplugeth"
)

var (
acctCheckTimer = metrics.NewRegisteredTimer("plugeth/statedb/accounts/checks", nil)
)

type pluginSnapshot struct {
root common.Hash
}

func (s *pluginSnapshot) Root() common.Hash {
return s.root
}

func (s *pluginSnapshot) Account(hash common.Hash) (*types.SlimAccount, error) {
return nil, fmt.Errorf("not implemented")
}

func (s *pluginSnapshot) AccountRLP(hash common.Hash) ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}

func (s *pluginSnapshot) Storage(accountHash, storageHash common.Hash) ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}

type acctChecker struct {
snap snapshot.Snapshot
trie Trie
}

type acctByHasher interface {
GetAccountByHash(common.Hash) (*types.StateAccount, error)
}

type stateUpdatePlugin interface {
StateUpdate(blockRoot, parentRoot common.Hash, snap snapshot.Snapshot, trie Trie, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte, codeUpdates map[common.Hash][]byte)
}

func init() {
xplugeth.RegisterHook[stateUpdatePlugin]()

}

func (ac *acctChecker) exists(k common.Hash) bool {
if hadStorage, ok := ac.snapExists(k); ok {
return hadStorage
}
return ac.trieExists(k)
}

func (ac *acctChecker) snapExists(k common.Hash) (bool, bool) {
acct, err := ac.snap.AccountRLP(k)
if err != nil {
return false, false
}
return len(acct) != 0, true
}

func (ac *acctChecker) trieExists(k common.Hash) bool {
trie, ok := ac.trie.(acctByHasher)
if !ok {
log.Warn("Couldn't check trie existence, wrong trie type")
return true
}
acct, _ := trie.GetAccountByHash(k)
return acct != nil
}

func (ac *acctChecker) updated(k common.Hash, v []byte) bool {
if updated, ok := ac.snapUpdated(k, v); ok {
return updated
}
return ac.trieUpdated(k, v)
}

func pluginStateUpdate(blockRoot, parentRoot common.Hash, snap snapshot.Snapshot, trie Trie, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte, codeUpdates map[common.Hash][]byte) {
checker := &acctChecker{snap, trie}

filteredDestructs := make(map[common.Hash]struct{})
for k, v := range destructs {
if _, ok := accounts[k]; ok {
if !checker.hadStorage(k) {
// If an account is in both destructs and accounts, that means it was
// "destroyed" and recreated in the same block. Especially post-cancun,
// that generally means that an account that had ETH but no code got
// replaced by an account that had code.
//
// If there's data in the accounts map, we only need to process this
// account if there are storage slots we need to clear out, so we
// check the account storage for the empty root. If it's empty, we can
// skip this destruct. We need this check to normalize parallel blocks
// with serial blocks, because they report destructs differently.
continue
}
} else {
if !checker.exists(k) {
// If we have a destruct for an account that didn't exist at the previous
// block, we don't need to destruct it. This normalizes the use case of
// CREATE and CREATE2 deployments to empty addresses that revert.
continue
}
}
filteredDestructs[k] = v
}

filteredAccounts := make(map[common.Hash][]byte)
start := time.Now()
for k, v := range accounts {
if checker.updated(k, v) {
filteredAccounts[k] = v
}
}
acctCheckTimer.UpdateSince(start)

for _, m := range xplugeth.GetModules[stateUpdatePlugin]() {
m.StateUpdate(blockRoot, parentRoot, snap, trie, filteredDestructs, filteredAccounts, storage, codeUpdates)
}
}

func (ac *acctChecker) hadStorage(k common.Hash) bool {
if hadStorage, ok := ac.snapHadStorage(k); ok {
return hadStorage
}
return ac.trieHadStorage(k)
}

func (ac *acctChecker) trieHadStorage(k common.Hash) bool {
trie, ok := ac.trie.(acctByHasher)
if !ok {
log.Warn("Couldn't check trie updates, wrong trie type")
return true
}
acct, err := trie.GetAccountByHash(k)
if err != nil {
return true
}
if acct == nil {
// No account existed at this hash, so it wouldn't have had storage
return false
}
return acct.Root != types.EmptyRootHash
}

func (ac *acctChecker) snapHadStorage(k common.Hash) (bool, bool) {
acct, err := ac.snap.Account(k)
if err != nil {
return false, false
}
if acct == nil {
// No account existed at this hash, so it wouldn't have had storage
return false, true
}
if len(acct.Root) > 0 && !bytes.Equal(acct.Root, types.EmptyRootHash.Bytes()) {
return true, true
}
return false, true
}

func (ac *acctChecker) snapUpdated(k common.Hash, v []byte) (bool, bool) {
acct, err := ac.snap.AccountRLP(k)
if err != nil {
return false, false
}
if len(acct) == 0 {
return false, false
}
return !bytes.Equal(acct, v), true
}

func (ac *acctChecker) trieUpdated(k common.Hash, v []byte) bool {
trie, ok := ac.trie.(acctByHasher)
if !ok {
log.Warn("Couldn't check trie updates, wrong trie type")
return true
}
oAcct, err := trie.GetAccountByHash(k)
if err != nil {
return true
}
return !bytes.Equal(v, types.SlimAccountRLP(*oAcct))
}

0 comments on commit 6ef08a0

Please sign in to comment.