diff --git a/plugins/interface.go b/plugins/interface.go
new file mode 100644
index 0000000000..52e6fd6bec
--- /dev/null
+++ b/plugins/interface.go
@@ -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 .
+
+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.
+}
diff --git a/plugins/internal/chain.go b/plugins/internal/chain.go
new file mode 100644
index 0000000000..bcd180b885
--- /dev/null
+++ b/plugins/internal/chain.go
@@ -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 .
+
+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
+}