Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugins: add plugin system #30824

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions plugins/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package plugins

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
)

// Plugin is an interface that allows 3rd-party developers to build plugins
// for go-ethereum which can be used to add additional functionality to geth
// without modifying the chain logic.
type Plugin interface {
// Setup is called on startup when the Plugin is initialized.
Setup(chain Chain, hooks SetHooks)
// Close is called when the geth node is torn down.
Close()
// NewHead is called when a new head is set.
NewHead()
}

// The Chain interface allows for interacting with the chain from a plugin.
type Chain interface {
// Head returns the number of the current head and finalized block.
Head() (uint64, uint64)
// Header returns a header in the canonical chain.
Header(number uint64) *types.Header
// Block returns a block in the canonical chain.
Block(number uint64) *types.Block
// Receipts returns the receipts of a block in the canonical chain.
Receipts(number uint64) types.Receipts
// State returns the state at a certain root.
State(root common.Hash) State
}

// The State interface allows for interacting with a specific state from a plugin.
// Please note that State might hold internal references which interferes with garbage collection.
// Make sure to not hold references to State for long.
type State interface {
// Account retrieves an account from the state.
Account(addr common.Address) Account
// AccountIterator creates an iterator to iterate over accounts.
AccountIterator(seek common.Hash) snapshot.AccountIterator
// NewAccount interprets an rlp slim account as an Account.
NewAccount(addr common.Address, account []byte) Account
}

// The Account interface allows for interacting with a specific account from a plugin.
// Please note that Account might hold internal references which interferes with garbage collection.
// Make sure to not hold references to Account for long.
type Account interface {
// Balance returns the balance of an account.
Balance() *uint256.Int
// Nonce returns the nonce of an account.
Nonce() uint64
// Code returns the code of an account.
Code() []byte
// Storage returns a storage slot.
Storage(slot common.Hash) common.Hash
// StorageIterator creates an iterator over the storage slots of an account.
StorageIterator(seek common.Hash) snapshot.StorageIterator
}

// SetHooks allows the plugin to install hooks dynamically on the node.
type SetHooks struct {
// SetTracingHooks allows a plugin to set tracing hooks.
SetTracingHooks func(hooks *tracing.Hooks)
// In the future we could add hooks here for plugins to hook deeper into
// block production, adding hooks into the txpool, etc.
}
176 changes: 176 additions & 0 deletions plugins/internal/chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package internal

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/plugins"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)

var _ = plugins.Chain(&Chain{})
var _ = Blockchain(&core.BlockChain{})

type Blockchain interface {
CurrentHeader() *types.Header
GetCanonicalHash(uint64) common.Hash
CurrentFinalBlock() *types.Header
GetHeaderByHash(common.Hash) *types.Header
GetBlockByHash(common.Hash) *types.Block
GetReceiptsByHash(common.Hash) types.Receipts
StateCache() state.Database
}

type Chain struct {
chain Blockchain
}

func (c *Chain) Head() (uint64, uint64) {
return c.chain.CurrentHeader().Number.Uint64(), c.chain.CurrentFinalBlock().Number.Uint64()
}

func (c *Chain) Header(number uint64) *types.Header {
hash := c.chain.GetCanonicalHash(number)
return c.chain.GetHeaderByHash(hash)
}

func (c *Chain) Block(number uint64) *types.Block {
hash := c.chain.GetCanonicalHash(number)
return c.chain.GetBlockByHash(hash)
}

func (c *Chain) Receipts(number uint64) types.Receipts {
hash := c.chain.GetCanonicalHash(number)
return c.chain.GetReceiptsByHash(hash)
}

func (c *Chain) State(root common.Hash) plugins.State {
reader, err := c.chain.StateCache().Reader(root)
if err != nil {
return nil
}

return &State{
root: root,
cache: c.chain.StateCache(),
reader: reader,
}
}

type State struct {
root common.Hash
cache state.Database
reader state.Reader
}

func (s *State) Account(addr common.Address) plugins.Account {
hash := crypto.Keccak256Hash(addr.Bytes())
reader, err := s.cache.Reader(s.root)
if err != nil {
return nil
}
account, err := reader.Account(addr)
if err != nil {
return nil
}
return &Account{
root: s.root,
hash: hash,
account: account,
cache: s.cache,
}
}

func (s *State) AccountIterator(seek common.Hash) snapshot.AccountIterator {
if it, err := s.cache.Snapshot().AccountIterator(s.root, seek); err == nil {
return it
}
return nil
}

func (s *State) NewAccount(addr common.Address, accRLP []byte) plugins.Account {
hash := crypto.Keccak256Hash(addr.Bytes())
var slim *types.SlimAccount
if err := rlp.DecodeBytes(accRLP, &slim); err != nil {
return nil
}
account := &types.StateAccount{
Nonce: slim.Nonce,
Balance: slim.Balance,
CodeHash: slim.CodeHash,
Root: common.BytesToHash(slim.Root),
}
if len(account.CodeHash) == 0 {
account.CodeHash = types.EmptyCodeHash.Bytes()
}
if account.Root == (common.Hash{}) {
account.Root = types.EmptyRootHash
}
return &Account{
root: s.root,
hash: hash,
account: account,
cache: s.cache,
}
}

type Account struct {
addr common.Address
root common.Hash
hash common.Hash
account *types.StateAccount
cache state.Database
}

func (a *Account) Balance() *uint256.Int {
return a.account.Balance
}

func (a *Account) Nonce() uint64 {
return a.account.Nonce
}

func (a *Account) Code() []byte {
if code, err := a.cache.ContractCode(a.addr, a.account.Root); err == nil {
return code
}
return nil
}

func (a *Account) Storage(slot common.Hash) common.Hash {
reader, err := a.cache.Reader(a.root)
if err != nil {
return common.Hash{}
}
if storage, err := reader.Storage(a.addr, slot); err == nil {
return storage
}
return common.Hash{}
}

func (a *Account) StorageIterator(seek common.Hash) snapshot.StorageIterator {
if it, err := a.cache.Snapshot().StorageIterator(a.root, a.hash, seek); err == nil {
return it
}
return nil
}