Merge branch 'eip-2537-changes' of github.com:s1na/go-ethereum into eip-2537-changes

This commit is contained in:
Sina Mahmoodi 2025-01-16 16:04:30 +01:00
commit 736f77317c
26 changed files with 186 additions and 240 deletions

125
README.md
View file

@ -54,14 +54,14 @@ on how you can run your own `geth` instance.
Minimum: Minimum:
* CPU with 2+ cores * CPU with 4+ cores
* 4GB RAM * 8GB RAM
* 1TB free storage space to sync the Mainnet * 1TB free storage space to sync the Mainnet
* 8 MBit/sec download Internet service * 8 MBit/sec download Internet service
Recommended: Recommended:
* Fast CPU with 4+ cores * Fast CPU with 8+ cores
* 16GB+ RAM * 16GB+ RAM
* High-performance SSD with at least 1TB of free space * High-performance SSD with at least 1TB of free space
* 25+ MBit/sec download Internet service * 25+ MBit/sec download Internet service
@ -138,8 +138,6 @@ export your existing configuration:
$ geth --your-favourite-flags dumpconfig $ geth --your-favourite-flags dumpconfig
``` ```
*Note: This works only with `geth` v1.6.0 and above.*
#### Docker quick start #### Docker quick start
One of the quickest ways to get Ethereum up and running on your machine is by using One of the quickest ways to get Ethereum up and running on your machine is by using
@ -187,7 +185,6 @@ HTTP based JSON-RPC API options:
* `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`) * `--ws.api` API's offered over the WS-RPC interface (default: `eth,net,web3`)
* `--ws.origins` Origins from which to accept WebSocket requests * `--ws.origins` Origins from which to accept WebSocket requests
* `--ipcdisable` Disable the IPC-RPC server * `--ipcdisable` Disable the IPC-RPC server
* `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,txpool,web3`)
* `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it) * `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
You'll need to use your own programming environments' capabilities (libraries, tools, etc) to You'll need to use your own programming environments' capabilities (libraries, tools, etc) to
@ -206,118 +203,14 @@ APIs!**
Maintaining your own private network is more involved as a lot of configurations taken for Maintaining your own private network is more involved as a lot of configurations taken for
granted in the official networks need to be manually set up. granted in the official networks need to be manually set up.
#### Defining the private genesis state Unfortunately since [the Merge](https://ethereum.org/en/roadmap/merge/) it is no longer possible
to easily set up a network of geth nodes without also setting up a corresponding beacon chain.
First, you'll need to create the genesis state of your networks, which all nodes need to be There are three different solutions depending on your use case:
aware of and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`):
```json * If you are looking for a simple way to test smart contracts from go in your CI, you can use the [Simulated Backend](https://geth.ethereum.org/docs/developers/dapp-developer/native-bindings#blockchain-simulator).
{ * If you want a convenient single node environment for testing, you can use our [Dev Mode](https://geth.ethereum.org/docs/developers/dapp-developer/dev-mode).
"config": { * If you are looking for a multiple node test network, you can set one up quite easily with [Kurtosis](https://geth.ethereum.org/docs/fundamentals/kurtosis).
"chainId": <arbitrary positive integer>,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0
},
"alloc": {},
"coinbase": "0x0000000000000000000000000000000000000000",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0x2fefd8",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00"
}
```
The above fields should be fine for most purposes, although we'd recommend changing
the `nonce` to some random value so you prevent unknown remote nodes from being able
to connect to you. If you'd like to pre-fund some accounts for easier testing, create
the accounts and populate the `alloc` field with their addresses.
```json
"alloc": {
"0x0000000000000000000000000000000000000001": {
"balance": "111111111"
},
"0x0000000000000000000000000000000000000002": {
"balance": "222222222"
}
}
```
With the genesis state defined in the above JSON file, you'll need to initialize **every**
`geth` node with it prior to starting it up to ensure all blockchain parameters are correctly
set:
```shell
$ geth init path/to/genesis.json
```
#### Creating the rendezvous point
With all nodes that you want to run initialized to the desired genesis state, you'll need to
start a bootstrap node that others can use to find each other in your network and/or over
the internet. The clean way is to configure and run a dedicated bootnode:
```shell
# Use the devp2p tool to create a node file.
# The devp2p tool is also part of the 'alltools' distribution bundle.
$ devp2p key generate node1.key
# file node1.key is created.
$ devp2p key to-enr -ip 10.2.3.4 -udp 30303 -tcp 30303 node1.key
# Prints the ENR for use in --bootnode flag of other nodes.
# Note this method requires knowing the IP/ports ahead of time.
$ geth --nodekey=node1.key
```
With the bootnode online, it will display an [`enode` URL](https://ethereum.org/en/developers/docs/networking-layer/network-addresses/#enode)
that other nodes can use to connect to it and exchange peer information. Make sure to
replace the displayed IP address information (most probably `[::]`) with your externally
accessible IP to get the actual `enode` URL.
*Note: You could previously use the `bootnode` utility to start a stripped down version of geth. This is not possible anymore.*
#### Starting up your member nodes
With the bootnode operational and externally reachable (you can try
`telnet <ip> <port>` to ensure it's indeed reachable), start every subsequent `geth`
node pointed to the bootnode for peer discovery via the `--bootnodes` flag. It will
probably also be desirable to keep the data directory of your private network separated, so
do also specify a custom `--datadir` flag.
```shell
$ geth --datadir=path/to/custom/data/folder --bootnodes=<bootnode-enode-url-from-above>
```
*Note: Since your network will be completely cut off from the main and test networks, you'll
also need to configure a miner to process transactions and create new blocks for you.*
#### Running a private miner
In a private network setting a single CPU miner instance is more than enough for
practical purposes as it can produce a stable stream of blocks at the correct intervals
without needing heavy resources (consider running on a single thread, no need for multiple
ones either). To start a `geth` instance for mining, run it with all your usual flags, extended
by:
```shell
$ geth <usual-flags> --mine --miner.threads=1 --miner.etherbase=0x0000000000000000000000000000000000000000
```
Which will start mining blocks and transactions on a single CPU thread, crediting all
proceedings to the account specified by `--miner.etherbase`. You can further tune the mining
by changing the default gas limit blocks converge to (`--miner.targetgaslimit`) and the price
transactions are accepted at (`--miner.gasprice`).
## Contribution ## Contribution

View file

@ -7,7 +7,7 @@ It enables usecases like the following:
* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period * I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period
* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei` * I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei`
The two main features that are required for this to work well are; The two main features that are required for this to work well are:
1. Rule Implementation: how to create, manage, and interpret rules in a flexible but secure manner 1. Rule Implementation: how to create, manage, and interpret rules in a flexible but secure manner
2. Credential management and credentials; how to provide auto-unlock without exposing keys unnecessarily. 2. Credential management and credentials; how to provide auto-unlock without exposing keys unnecessarily.
@ -29,10 +29,10 @@ function asBig(str) {
// Approve transactions to a certain contract if the value is below a certain limit // Approve transactions to a certain contract if the value is below a certain limit
function ApproveTx(req) { function ApproveTx(req) {
var limit = big.Newint("0xb1a2bc2ec50000") var limit = new BigNumber("0xb1a2bc2ec50000")
var value = asBig(req.transaction.value); var value = asBig(req.transaction.value);
if (req.transaction.to.toLowerCase() == "0xae967917c465db8578ca9024c205720b1a3651a9") && value.lt(limit)) { if (req.transaction.to.toLowerCase() == "0xae967917c465db8578ca9024c205720b1a3651a9" && value.lt(limit)) {
return "Approve" return "Approve"
} }
// If we return "Reject", it will be rejected. // If we return "Reject", it will be rejected.

View file

@ -17,6 +17,7 @@
package main package main
import ( import (
"cmp"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@ -292,13 +293,7 @@ func sortChanges(changes []types.Change) {
if a.Action == b.Action { if a.Action == b.Action {
return strings.Compare(*a.ResourceRecordSet.Name, *b.ResourceRecordSet.Name) return strings.Compare(*a.ResourceRecordSet.Name, *b.ResourceRecordSet.Name)
} }
if score[string(a.Action)] < score[string(b.Action)] { return cmp.Compare(score[string(a.Action)], score[string(b.Action)])
return -1
}
if score[string(a.Action)] > score[string(b.Action)] {
return 1
}
return 0
}) })
} }

View file

@ -18,6 +18,7 @@ package main
import ( import (
"bytes" "bytes"
"cmp"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@ -104,13 +105,7 @@ func (ns nodeSet) topN(n int) nodeSet {
byscore = append(byscore, v) byscore = append(byscore, v)
} }
slices.SortFunc(byscore, func(a, b nodeJSON) int { slices.SortFunc(byscore, func(a, b nodeJSON) int {
if a.Score > b.Score { return cmp.Compare(b.Score, a.Score)
return -1
}
if a.Score < b.Score {
return 1
}
return 0
}) })
result := make(nodeSet, n) result := make(nodeSet, n)
for _, v := range byscore[:n] { for _, v := range byscore[:n] {

View file

@ -127,8 +127,12 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
} }
// Create an ephemeral in-memory database for computing hash, // Create an ephemeral in-memory database for computing hash,
// all the derived states will be discarded to not pollute disk. // all the derived states will be discarded to not pollute disk.
emptyRoot := types.EmptyRootHash
if isVerkle {
emptyRoot = types.EmptyVerkleHash
}
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb.NewDatabase(db, config), nil)) statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil))
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -148,7 +152,11 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
// flushAlloc is very similar with hash, but the main difference is all the // flushAlloc is very similar with hash, but the main difference is all the
// generated states will be persisted into the given database. // generated states will be persisted into the given database.
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) { func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) {
statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb, nil)) emptyRoot := types.EmptyRootHash
if triedb.IsVerkle() {
emptyRoot = types.EmptyVerkleHash
}
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb, nil))
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }

View file

@ -87,6 +87,10 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui
) )
// Ensure the datadir is not a symbolic link if it exists. // Ensure the datadir is not a symbolic link if it exists.
if info, err := os.Lstat(datadir); !os.IsNotExist(err) { if info, err := os.Lstat(datadir); !os.IsNotExist(err) {
if info == nil {
log.Warn("Could not Lstat the database", "path", datadir)
return nil, errors.New("lstat failed")
}
if info.Mode()&os.ModeSymlink != 0 { if info.Mode()&os.ModeSymlink != 0 {
log.Warn("Symbolic link ancient database is not supported", "path", datadir) log.Warn("Symbolic link ancient database is not supported", "path", datadir)
return nil, errSymlinkDatadir return nil, errSymlinkDatadir

View file

@ -18,6 +18,7 @@ package snapshot
import ( import (
"bytes" "bytes"
"cmp"
"fmt" "fmt"
"slices" "slices"
"sort" "sort"
@ -45,13 +46,7 @@ func (it *weightedIterator) Cmp(other *weightedIterator) int {
return 1 return 1
} }
// Same account/storage-slot in multiple layers, split by priority // Same account/storage-slot in multiple layers, split by priority
if it.priority < other.priority { return cmp.Compare(it.priority, other.priority)
return -1
}
if it.priority > other.priority {
return 1
}
return 0
} }
// fastIterator is a more optimized multi-layer iterator which maintains a // fastIterator is a more optimized multi-layer iterator which maintains a

View file

@ -20,7 +20,6 @@ import (
"maps" "maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
@ -133,8 +132,8 @@ func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common
} }
} }
return &stateUpdate{ return &stateUpdate{
originRoot: types.TrieRootHash(originRoot), originRoot: originRoot,
root: types.TrieRootHash(root), root: root,
accounts: accounts, accounts: accounts,
accountsOrigin: accountsOrigin, accountsOrigin: accountsOrigin,
storages: storages, storages: storages,

View file

@ -19,7 +19,6 @@ package types
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
) )
var ( var (
@ -47,13 +46,3 @@ var (
// EmptyVerkleHash is the known hash of an empty verkle trie. // EmptyVerkleHash is the known hash of an empty verkle trie.
EmptyVerkleHash = common.Hash{} EmptyVerkleHash = common.Hash{}
) )
// TrieRootHash returns the hash itself if it's non-empty or the predefined
// emptyHash one instead.
func TrieRootHash(hash common.Hash) common.Hash {
if hash == (common.Hash{}) {
log.Error("Zero trie root hash!")
return EmptyRootHash
}
return hash
}

View file

@ -111,7 +111,7 @@ func TestFuzzDeriveSha(t *testing.T) {
exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil)))
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) { if !bytes.Equal(got[:], exp[:]) {
printList(newDummy(seed)) printList(t, newDummy(seed))
t.Fatalf("seed %d: got %x exp %x", seed, got, exp) t.Fatalf("seed %d: got %x exp %x", seed, got, exp)
} }
} }
@ -192,15 +192,21 @@ func (d *dummyDerivableList) EncodeIndex(i int, w *bytes.Buffer) {
io.CopyN(w, mrand.New(src), size) io.CopyN(w, mrand.New(src), size)
} }
func printList(l types.DerivableList) { func printList(t *testing.T, l types.DerivableList) {
fmt.Printf("list length: %d\n", l.Len()) var buf bytes.Buffer
fmt.Printf("{\n") _, _ = fmt.Fprintf(&buf, "list length: %d, ", l.Len())
buf.WriteString("list: [")
for i := 0; i < l.Len(); i++ { for i := 0; i < l.Len(); i++ {
var buf bytes.Buffer var itemBuf bytes.Buffer
l.EncodeIndex(i, &buf) l.EncodeIndex(i, &itemBuf)
fmt.Printf("\"%#x\",\n", buf.Bytes()) if i == l.Len()-1 {
_, _ = fmt.Fprintf(&buf, "\"%#x\"", itemBuf.Bytes())
} else {
_, _ = fmt.Fprintf(&buf, "\"%#x\",", itemBuf.Bytes())
}
} }
fmt.Printf("},\n") buf.WriteString("]")
t.Log(buf.String())
} }
type flatList []string type flatList []string

View file

@ -105,8 +105,8 @@ func (e *gfP12) Mul(a, b *gfP12) *gfP12 {
} }
func (e *gfP12) MulScalar(a *gfP12, b *gfP6) *gfP12 { func (e *gfP12) MulScalar(a *gfP12, b *gfP6) *gfP12 {
e.x.Mul(&e.x, b) e.x.Mul(&a.x, b)
e.y.Mul(&e.y, b) e.y.Mul(&a.y, b)
return e return e
} }

View file

@ -125,8 +125,8 @@ func (e *gfP12) Mul(a, b *gfP12, pool *bnPool) *gfP12 {
} }
func (e *gfP12) MulScalar(a *gfP12, b *gfP6, pool *bnPool) *gfP12 { func (e *gfP12) MulScalar(a *gfP12, b *gfP6, pool *bnPool) *gfP12 {
e.x.Mul(e.x, b, pool) e.x.Mul(a.x, b, pool)
e.y.Mul(e.y, b, pool) e.y.Mul(a.y, b, pool)
return e return e
} }

View file

@ -174,6 +174,8 @@ func (p *Peer) dispatchResponse(res *Response, metadata func() interface{}) erro
return <-res.Done // Response delivered, return any errors return <-res.Done // Response delivered, return any errors
case <-res.Req.cancel: case <-res.Req.cancel:
return nil // Request cancelled, silently discard response return nil // Request cancelled, silently discard response
case <-p.term:
return errDisconnected
} }
} }

View file

@ -88,7 +88,7 @@ func testSnapSyncDisabling(t *testing.T, ethVer uint, snapVer uint) {
if err := empty.handler.downloader.BeaconSync(ethconfig.SnapSync, full.chain.CurrentBlock(), nil); err != nil { if err := empty.handler.downloader.BeaconSync(ethconfig.SnapSync, full.chain.CurrentBlock(), nil); err != nil {
t.Fatal("sync failed:", err) t.Fatal("sync failed:", err)
} }
empty.handler.enableSyncedFeatures() time.Sleep(time.Second * 5) // Downloader internally has to wait a timer (3s) to be expired before exiting
if empty.handler.snapSync.Load() { if empty.handler.snapSync.Load() {
t.Fatalf("snap sync not disabled after successful synchronisation") t.Fatalf("snap sync not disabled after successful synchronisation")

View file

@ -217,6 +217,7 @@ type StructLogger struct {
interrupt atomic.Bool // Atomic flag to signal execution interruption interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption reason error // Textual reason for the interruption
skip bool // skip processing hooks.
} }
// NewStreamingStructLogger returns a new streaming logger. // NewStreamingStructLogger returns a new streaming logger.
@ -240,10 +241,12 @@ func NewStructLogger(cfg *Config) *StructLogger {
func (l *StructLogger) Hooks() *tracing.Hooks { func (l *StructLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{ return &tracing.Hooks{
OnTxStart: l.OnTxStart, OnTxStart: l.OnTxStart,
OnTxEnd: l.OnTxEnd, OnTxEnd: l.OnTxEnd,
OnExit: l.OnExit, OnSystemCallStartV2: l.OnSystemCallStart,
OnOpcode: l.OnOpcode, OnSystemCallEnd: l.OnSystemCallEnd,
OnExit: l.OnExit,
OnOpcode: l.OnOpcode,
} }
} }
@ -255,6 +258,10 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
if l.interrupt.Load() { if l.interrupt.Load() {
return return
} }
// Processing a system call.
if l.skip {
return
}
// check if already accumulated the size of the response. // check if already accumulated the size of the response.
if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit { if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit {
return return
@ -320,6 +327,9 @@ func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err erro
if depth != 0 { if depth != 0 {
return return
} }
if l.skip {
return
}
l.output = output l.output = output
l.err = err l.err = err
// TODO @holiman, should we output the per-scope output? // TODO @holiman, should we output the per-scope output?
@ -360,6 +370,13 @@ func (l *StructLogger) Stop(err error) {
func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
l.env = env l.env = env
} }
func (l *StructLogger) OnSystemCallStart(env *tracing.VMContext) {
l.skip = true
}
func (l *StructLogger) OnSystemCallEnd() {
l.skip = false
}
func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) { func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
if err != nil { if err != nil {
@ -389,9 +406,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
} }
type mdLogger struct { type mdLogger struct {
out io.Writer out io.Writer
cfg *Config cfg *Config
env *tracing.VMContext env *tracing.VMContext
skip bool
} }
// NewMarkdownLogger creates a logger which outputs information in a format adapted // NewMarkdownLogger creates a logger which outputs information in a format adapted
@ -406,11 +424,13 @@ func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger {
func (t *mdLogger) Hooks() *tracing.Hooks { func (t *mdLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{ return &tracing.Hooks{
OnTxStart: t.OnTxStart, OnTxStart: t.OnTxStart,
OnEnter: t.OnEnter, OnSystemCallStartV2: t.OnSystemCallStart,
OnExit: t.OnExit, OnSystemCallEnd: t.OnSystemCallEnd,
OnOpcode: t.OnOpcode, OnEnter: t.OnEnter,
OnFault: t.OnFault, OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
} }
} }
@ -418,7 +438,18 @@ func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from
t.env = env t.env = env
} }
func (t *mdLogger) OnSystemCallStart(env *tracing.VMContext) {
t.skip = true
}
func (t *mdLogger) OnSystemCallEnd() {
t.skip = false
}
func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.skip {
return
}
if depth != 0 { if depth != 0 {
return return
} }
@ -446,6 +477,9 @@ func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.A
} }
func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if t.skip {
return
}
if depth == 0 { if depth == 0 {
fmt.Fprintf(t.out, "\nPost-execution info:\n"+ fmt.Fprintf(t.out, "\nPost-execution info:\n"+
" - output: `%#x`\n"+ " - output: `%#x`\n"+
@ -457,6 +491,9 @@ func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, r
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change. // OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
if t.skip {
return
}
stack := scope.StackData() stack := scope.StackData()
fmt.Fprintf(t.out, "| %4d | %10v | %3d |%10v |", pc, vm.OpCode(op).String(), fmt.Fprintf(t.out, "| %4d | %10v | %3d |%10v |", pc, vm.OpCode(op).String(),
cost, t.env.StateDB.GetRefund()) cost, t.env.StateDB.GetRefund())
@ -477,6 +514,9 @@ func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.
} }
func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
if t.skip {
return
}
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
} }

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
@ -36,7 +37,7 @@ func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil
func TestStateOverrideMovePrecompile(t *testing.T) { func TestStateOverrideMovePrecompile(t *testing.T) {
db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) db := state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil)
statedb, err := state.New(common.Hash{}, db) statedb, err := state.New(types.EmptyRootHash, db)
if err != nil { if err != nil {
t.Fatalf("failed to create statedb: %v", err) t.Fatalf("failed to create statedb: %v", err)
} }

View file

@ -45,7 +45,7 @@ const (
maxSimulateBlocks = 256 maxSimulateBlocks = 256
// timestampIncrement is the default increment between block timestamps. // timestampIncrement is the default increment between block timestamps.
timestampIncrement = 1 timestampIncrement = 12
) )
// simBlock is a batch of calls to be simulated sequentially. // simBlock is a batch of calls to be simulated sequentially.

View file

@ -41,19 +41,19 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) {
baseNumber: 10, baseNumber: 10,
baseTimestamp: 50, baseTimestamp: 50,
blocks: []simBlock{{}, {}, {}}, blocks: []simBlock{{}, {}, {}},
expected: []result{{number: 11, timestamp: 51}, {number: 12, timestamp: 52}, {number: 13, timestamp: 53}}, expected: []result{{number: 11, timestamp: 62}, {number: 12, timestamp: 74}, {number: 13, timestamp: 86}},
}, },
{ {
baseNumber: 10, baseNumber: 10,
baseTimestamp: 50, baseTimestamp: 50,
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(70)}}, {}}, blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(80)}}, {}},
expected: []result{{number: 11, timestamp: 51}, {number: 12, timestamp: 52}, {number: 13, timestamp: 70}, {number: 14, timestamp: 71}}, expected: []result{{number: 11, timestamp: 62}, {number: 12, timestamp: 74}, {number: 13, timestamp: 80}, {number: 14, timestamp: 92}},
}, },
{ {
baseNumber: 10, baseNumber: 10,
baseTimestamp: 50, baseTimestamp: 50,
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(11)}}, {BlockOverrides: &override.BlockOverrides{Number: newInt(14)}}, {}}, blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(11)}}, {BlockOverrides: &override.BlockOverrides{Number: newInt(14)}}, {}},
expected: []result{{number: 11, timestamp: 51}, {number: 12, timestamp: 52}, {number: 13, timestamp: 53}, {number: 14, timestamp: 54}, {number: 15, timestamp: 55}}, expected: []result{{number: 11, timestamp: 62}, {number: 12, timestamp: 74}, {number: 13, timestamp: 86}, {number: 14, timestamp: 98}, {number: 15, timestamp: 110}},
}, },
{ {
baseNumber: 10, baseNumber: 10,
@ -64,8 +64,8 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) {
{ {
baseNumber: 10, baseNumber: 10,
baseTimestamp: 50, baseTimestamp: 50,
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(52)}}}, blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(74)}}},
err: "block timestamps must be in order: 52 <= 52", err: "block timestamps must be in order: 74 <= 74",
}, },
{ {
baseNumber: 10, baseNumber: 10,
@ -76,8 +76,8 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) {
{ {
baseNumber: 10, baseNumber: 10,
baseTimestamp: 50, baseTimestamp: 50,
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(11), Time: newUint64(60)}}, {BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(61)}}}, blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(11), Time: newUint64(60)}}, {BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(72)}}},
err: "block timestamps must be in order: 61 <= 61", err: "block timestamps must be in order: 72 <= 72",
}, },
} { } {
sim := &simulator{base: &types.Header{Number: big.NewInt(int64(tc.baseNumber)), Time: tc.baseTimestamp}} sim := &simulator{base: &types.Header{Number: big.NewInt(int64(tc.baseNumber)), Time: tc.baseTimestamp}}

View file

@ -40,7 +40,7 @@ func NewApp(usage string) *cli.App {
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.Version = version.WithCommit(git.Commit, git.Date) app.Version = version.WithCommit(git.Commit, git.Date)
app.Usage = usage app.Usage = usage
app.Copyright = "Copyright 2013-2024 The go-ethereum Authors" app.Copyright = "Copyright 2013-2025 The go-ethereum Authors"
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {
MigrateGlobalFlags(ctx) MigrateGlobalFlags(ctx)
return nil return nil

View file

@ -17,6 +17,7 @@
package p2p package p2p
import ( import (
"cmp"
"fmt" "fmt"
"strings" "strings"
@ -81,13 +82,7 @@ func (cap Cap) String() string {
// Cmp defines the canonical sorting order of capabilities. // Cmp defines the canonical sorting order of capabilities.
func (cap Cap) Cmp(other Cap) int { func (cap Cap) Cmp(other Cap) int {
if cap.Name == other.Name { if cap.Name == other.Name {
if cap.Version < other.Version { return cmp.Compare(cap.Version, other.Version)
return -1
}
if cap.Version > other.Version {
return 1
}
return 0
} }
return strings.Compare(cap.Name, other.Name) return strings.Compare(cap.Name, other.Name)
} }

View file

@ -19,7 +19,6 @@ package trie
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/triedb/database" "github.com/ethereum/go-ethereum/triedb/database"
) )
@ -34,9 +33,6 @@ type trieReader struct {
// newTrieReader initializes the trie reader with the given node reader. // newTrieReader initializes the trie reader with the given node reader.
func newTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*trieReader, error) { func newTrieReader(stateRoot, owner common.Hash, db database.NodeDatabase) (*trieReader, error) {
if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash { if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash {
if stateRoot == (common.Hash{}) {
log.Error("Zero state root hash!")
}
return &trieReader{owner: owner}, nil return &trieReader{owner: owner}, nil
} }
reader, err := db.NodeReader(stateRoot) reader, err := db.NodeReader(stateRoot)

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-verkle"
) )
const ( const (
@ -148,6 +149,29 @@ var Defaults = &Config{
// ReadOnly is the config in order to open database in read only mode. // ReadOnly is the config in order to open database in read only mode.
var ReadOnly = &Config{ReadOnly: true} var ReadOnly = &Config{ReadOnly: true}
// nodeHasher is the function to compute the hash of supplied node blob.
type nodeHasher func([]byte) (common.Hash, error)
// merkleNodeHasher computes the hash of the given merkle node.
func merkleNodeHasher(blob []byte) (common.Hash, error) {
if len(blob) == 0 {
return types.EmptyRootHash, nil
}
return crypto.Keccak256Hash(blob), nil
}
// verkleNodeHasher computes the hash of the given verkle node.
func verkleNodeHasher(blob []byte) (common.Hash, error) {
if len(blob) == 0 {
return types.EmptyVerkleHash, nil
}
n, err := verkle.ParseNode(blob, 0)
if err != nil {
return common.Hash{}, err
}
return n.Commit().Bytes(), nil
}
// Database is a multiple-layered structure for maintaining in-memory states // Database is a multiple-layered structure for maintaining in-memory states
// along with its dirty trie nodes. It consists of one persistent base layer // along with its dirty trie nodes. It consists of one persistent base layer
// backed by a key-value store, on top of which arbitrarily many in-memory diff // backed by a key-value store, on top of which arbitrarily many in-memory diff
@ -164,9 +188,10 @@ type Database struct {
// readOnly is the flag whether the mutation is allowed to be applied. // readOnly is the flag whether the mutation is allowed to be applied.
// It will be set automatically when the database is journaled during // It will be set automatically when the database is journaled during
// the shutdown to reject all following unexpected mutations. // the shutdown to reject all following unexpected mutations.
readOnly bool // Flag if database is opened in read only mode readOnly bool // Flag if database is opened in read only mode
waitSync bool // Flag if database is deactivated due to initial state sync waitSync bool // Flag if database is deactivated due to initial state sync
isVerkle bool // Flag if database is used for verkle tree isVerkle bool // Flag if database is used for verkle tree
hasher nodeHasher // Trie node hasher
config *Config // Configuration for database config *Config // Configuration for database
diskdb ethdb.Database // Persistent storage for matured trie nodes diskdb ethdb.Database // Persistent storage for matured trie nodes
@ -184,19 +209,21 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
} }
config = config.sanitize() config = config.sanitize()
db := &Database{
readOnly: config.ReadOnly,
isVerkle: isVerkle,
config: config,
diskdb: diskdb,
hasher: merkleNodeHasher,
}
// Establish a dedicated database namespace tailored for verkle-specific // Establish a dedicated database namespace tailored for verkle-specific
// data, ensuring the isolation of both verkle and merkle tree data. It's // data, ensuring the isolation of both verkle and merkle tree data. It's
// important to note that the introduction of a prefix won't lead to // important to note that the introduction of a prefix won't lead to
// substantial storage overhead, as the underlying database will efficiently // substantial storage overhead, as the underlying database will efficiently
// compress the shared key prefix. // compress the shared key prefix.
if isVerkle { if isVerkle {
diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix)) db.diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix))
} db.hasher = verkleNodeHasher
db := &Database{
readOnly: config.ReadOnly,
isVerkle: isVerkle,
config: config,
diskdb: diskdb,
} }
// Construct the layer tree by resolving the in-disk singleton state // Construct the layer tree by resolving the in-disk singleton state
// and in-memory layer journal. // and in-memory layer journal.
@ -277,6 +304,8 @@ func (db *Database) repairHistory() error {
// //
// The passed in maps(nodes, states) will be retained to avoid copying everything. // The passed in maps(nodes, states) will be retained to avoid copying everything.
// Therefore, these maps must not be changed afterwards. // Therefore, these maps must not be changed afterwards.
//
// The supplied parentRoot and root must be a valid trie hash value.
func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *StateSetWithOrigin) error { func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *StateSetWithOrigin) error {
// Hold the lock to prevent concurrent mutations. // Hold the lock to prevent concurrent mutations.
db.lock.Lock() db.lock.Lock()
@ -350,10 +379,9 @@ func (db *Database) Enable(root common.Hash) error {
return errDatabaseReadOnly return errDatabaseReadOnly
} }
// Ensure the provided state root matches the stored one. // Ensure the provided state root matches the stored one.
root = types.TrieRootHash(root) stored, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
stored := types.EmptyRootHash if err != nil {
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 { return err
stored = crypto.Keccak256Hash(blob)
} }
if stored != root { if stored != root {
return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root) return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root)
@ -389,6 +417,8 @@ func (db *Database) Enable(root common.Hash) error {
// Recover rollbacks the database to a specified historical point. // Recover rollbacks the database to a specified historical point.
// The state is supported as the rollback destination only if it's // The state is supported as the rollback destination only if it's
// canonical state and the corresponding trie histories are existent. // canonical state and the corresponding trie histories are existent.
//
// The supplied root must be a valid trie hash value.
func (db *Database) Recover(root common.Hash) error { func (db *Database) Recover(root common.Hash) error {
db.lock.Lock() db.lock.Lock()
defer db.lock.Unlock() defer db.lock.Unlock()
@ -401,7 +431,6 @@ func (db *Database) Recover(root common.Hash) error {
return errors.New("state rollback is non-supported") return errors.New("state rollback is non-supported")
} }
// Short circuit if the target state is not recoverable // Short circuit if the target state is not recoverable
root = types.TrieRootHash(root)
if !db.Recoverable(root) { if !db.Recoverable(root) {
return errStateUnrecoverable return errStateUnrecoverable
} }
@ -434,9 +463,10 @@ func (db *Database) Recover(root common.Hash) error {
} }
// Recoverable returns the indicator if the specified state is recoverable. // Recoverable returns the indicator if the specified state is recoverable.
//
// The supplied root must be a valid trie hash value.
func (db *Database) Recoverable(root common.Hash) bool { func (db *Database) Recoverable(root common.Hash) bool {
// Ensure the requested state is a known state. // Ensure the requested state is a known state.
root = types.TrieRootHash(root)
id := rawdb.ReadStateID(db.diskdb, root) id := rawdb.ReadStateID(db.diskdb, root)
if id == nil { if id == nil {
return false return false

View file

@ -222,7 +222,12 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode
dirties = make(map[common.Hash]struct{}) dirties = make(map[common.Hash]struct{})
) )
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
switch rand.Intn(opLen) { // Start with account creation always
op := createAccountOp
if i > 0 {
op = rand.Intn(opLen)
}
switch op {
case createAccountOp: case createAccountOp:
// account creation // account creation
addr := testrand.Address() addr := testrand.Address()
@ -453,8 +458,8 @@ func TestDatabaseRecoverable(t *testing.T) {
// Initial state should be recoverable // Initial state should be recoverable
{types.EmptyRootHash, true}, {types.EmptyRootHash, true},
// Initial state should be recoverable // common.Hash{} is not a valid state root for revert
{common.Hash{}, true}, {common.Hash{}, false},
// Layers below current disk layer are recoverable // Layers below current disk layer are recoverable
{tester.roots[index-1], true}, {tester.roots[index-1], true},

View file

@ -18,6 +18,7 @@ package pathdb
import ( import (
"bytes" "bytes"
"cmp"
"fmt" "fmt"
"slices" "slices"
"sort" "sort"
@ -45,13 +46,7 @@ func (it *weightedIterator) Cmp(other *weightedIterator) int {
return 1 return 1
} }
// Same account/storage-slot in multiple layers, split by priority // Same account/storage-slot in multiple layers, split by priority
if it.priority < other.priority { return cmp.Compare(it.priority, other.priority)
return -1
}
if it.priority > other.priority {
return 1
}
return 0
} }
// fastIterator is a more optimized multi-layer iterator which maintains a // fastIterator is a more optimized multi-layer iterator which maintains a

View file

@ -26,7 +26,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -93,9 +92,9 @@ func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) {
// loadLayers loads a pre-existing state layer backed by a key-value store. // loadLayers loads a pre-existing state layer backed by a key-value store.
func (db *Database) loadLayers() layer { func (db *Database) loadLayers() layer {
// Retrieve the root node of persistent state. // Retrieve the root node of persistent state.
var root = types.EmptyRootHash root, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 { if err != nil {
root = crypto.Keccak256Hash(blob) log.Crit("Failed to compute node hash", "err", err)
} }
// Load the layers by resolving the journal // Load the layers by resolving the journal
head, err := db.loadJournal(root) head, err := db.loadJournal(root)
@ -236,6 +235,8 @@ func (dl *diffLayer) journal(w io.Writer) error {
// This is meant to be used during shutdown to persist the layer without // This is meant to be used during shutdown to persist the layer without
// flattening everything down (bad for reorgs). And this function will mark the // flattening everything down (bad for reorgs). And this function will mark the
// database as read-only to prevent all following mutation to disk. // database as read-only to prevent all following mutation to disk.
//
// The supplied root must be a valid trie hash value.
func (db *Database) Journal(root common.Hash) error { func (db *Database) Journal(root common.Hash) error {
// Retrieve the head layer to journal from. // Retrieve the head layer to journal from.
l := db.tree.get(root) l := db.tree.get(root)
@ -265,9 +266,9 @@ func (db *Database) Journal(root common.Hash) error {
} }
// Secondly write out the state root in disk, ensure all layers // Secondly write out the state root in disk, ensure all layers
// on top are continuous with disk. // on top are continuous with disk.
diskRoot := types.EmptyRootHash diskRoot, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 { if err != nil {
diskRoot = crypto.Keccak256Hash(blob) return err
} }
if err := rlp.Encode(journal, diskRoot); err != nil { if err := rlp.Encode(journal, diskRoot); err != nil {
return err return err

View file

@ -22,7 +22,6 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
) )
@ -62,7 +61,7 @@ func (tree *layerTree) get(root common.Hash) layer {
tree.lock.RLock() tree.lock.RLock()
defer tree.lock.RUnlock() defer tree.lock.RUnlock()
return tree.layers[types.TrieRootHash(root)] return tree.layers[root]
} }
// forEach iterates the stored layers inside and applies the // forEach iterates the stored layers inside and applies the
@ -92,7 +91,6 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
// //
// Although we could silently ignore this internally, it should be the caller's // Although we could silently ignore this internally, it should be the caller's
// responsibility to avoid even attempting to insert such a layer. // responsibility to avoid even attempting to insert such a layer.
root, parentRoot = types.TrieRootHash(root), types.TrieRootHash(parentRoot)
if root == parentRoot { if root == parentRoot {
return errors.New("layer cycle") return errors.New("layer cycle")
} }
@ -112,7 +110,6 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
// are crossed. All diffs beyond the permitted number are flattened downwards. // are crossed. All diffs beyond the permitted number are flattened downwards.
func (tree *layerTree) cap(root common.Hash, layers int) error { func (tree *layerTree) cap(root common.Hash, layers int) error {
// Retrieve the head layer to cap from // Retrieve the head layer to cap from
root = types.TrieRootHash(root)
l := tree.get(root) l := tree.get(root)
if l == nil { if l == nil {
return fmt.Errorf("triedb layer [%#x] missing", root) return fmt.Errorf("triedb layer [%#x] missing", root)