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:
* CPU with 2+ cores
* 4GB RAM
* CPU with 4+ cores
* 8GB RAM
* 1TB free storage space to sync the Mainnet
* 8 MBit/sec download Internet service
Recommended:
* Fast CPU with 4+ cores
* Fast CPU with 8+ cores
* 16GB+ RAM
* High-performance SSD with at least 1TB of free space
* 25+ MBit/sec download Internet service
@ -138,8 +138,6 @@ export your existing configuration:
$ geth --your-favourite-flags dumpconfig
```
*Note: This works only with `geth` v1.6.0 and above.*
#### Docker quick start
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.origins` Origins from which to accept WebSocket requests
* `--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)
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
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
aware of and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`):
There are three different solutions depending on your use case:
```json
{
"config": {
"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`).
* 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).
* 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).
## 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 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
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
function ApproveTx(req) {
var limit = big.Newint("0xb1a2bc2ec50000")
var limit = new BigNumber("0xb1a2bc2ec50000")
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"
}
// If we return "Reject", it will be rejected.

View file

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

View file

@ -18,6 +18,7 @@ package main
import (
"bytes"
"cmp"
"encoding/json"
"fmt"
"os"
@ -104,13 +105,7 @@ func (ns nodeSet) topN(n int) nodeSet {
byscore = append(byscore, v)
}
slices.SortFunc(byscore, func(a, b nodeJSON) int {
if a.Score > b.Score {
return -1
}
if a.Score < b.Score {
return 1
}
return 0
return cmp.Compare(b.Score, a.Score)
})
result := make(nodeSet, 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,
// all the derived states will be discarded to not pollute disk.
emptyRoot := types.EmptyRootHash
if isVerkle {
emptyRoot = types.EmptyVerkleHash
}
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 {
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
// generated states will be persisted into the given database.
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 {
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.
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 {
log.Warn("Symbolic link ancient database is not supported", "path", datadir)
return nil, errSymlinkDatadir

View file

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

View file

@ -20,7 +20,6 @@ import (
"maps"
"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/triedb"
)
@ -133,8 +132,8 @@ func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common
}
}
return &stateUpdate{
originRoot: types.TrieRootHash(originRoot),
root: types.TrieRootHash(root),
originRoot: originRoot,
root: root,
accounts: accounts,
accountsOrigin: accountsOrigin,
storages: storages,

View file

@ -19,7 +19,6 @@ package types
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)
var (
@ -47,13 +46,3 @@ var (
// EmptyVerkleHash is the known hash of an empty verkle trie.
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)))
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) {
printList(newDummy(seed))
printList(t, newDummy(seed))
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)
}
func printList(l types.DerivableList) {
fmt.Printf("list length: %d\n", l.Len())
fmt.Printf("{\n")
func printList(t *testing.T, l types.DerivableList) {
var buf bytes.Buffer
_, _ = fmt.Fprintf(&buf, "list length: %d, ", l.Len())
buf.WriteString("list: [")
for i := 0; i < l.Len(); i++ {
var buf bytes.Buffer
l.EncodeIndex(i, &buf)
fmt.Printf("\"%#x\",\n", buf.Bytes())
var itemBuf bytes.Buffer
l.EncodeIndex(i, &itemBuf)
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

View file

@ -105,8 +105,8 @@ func (e *gfP12) Mul(a, b *gfP12) *gfP12 {
}
func (e *gfP12) MulScalar(a *gfP12, b *gfP6) *gfP12 {
e.x.Mul(&e.x, b)
e.y.Mul(&e.y, b)
e.x.Mul(&a.x, b)
e.y.Mul(&a.y, b)
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 {
e.x.Mul(e.x, b, pool)
e.y.Mul(e.y, b, pool)
e.x.Mul(a.x, b, pool)
e.y.Mul(a.y, b, pool)
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
case <-res.Req.cancel:
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 {
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() {
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
reason error // Textual reason for the interruption
skip bool // skip processing hooks.
}
// NewStreamingStructLogger returns a new streaming logger.
@ -240,10 +241,12 @@ func NewStructLogger(cfg *Config) *StructLogger {
func (l *StructLogger) Hooks() *tracing.Hooks {
return &tracing.Hooks{
OnTxStart: l.OnTxStart,
OnTxEnd: l.OnTxEnd,
OnExit: l.OnExit,
OnOpcode: l.OnOpcode,
OnTxStart: l.OnTxStart,
OnTxEnd: l.OnTxEnd,
OnSystemCallStartV2: l.OnSystemCallStart,
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() {
return
}
// Processing a system call.
if l.skip {
return
}
// check if already accumulated the size of the response.
if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit {
return
@ -320,6 +327,9 @@ func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err erro
if depth != 0 {
return
}
if l.skip {
return
}
l.output = output
l.err = err
// 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) {
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) {
if err != nil {
@ -389,9 +406,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
}
type mdLogger struct {
out io.Writer
cfg *Config
env *tracing.VMContext
out io.Writer
cfg *Config
env *tracing.VMContext
skip bool
}
// 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 {
return &tracing.Hooks{
OnTxStart: t.OnTxStart,
OnEnter: t.OnEnter,
OnExit: t.OnExit,
OnOpcode: t.OnOpcode,
OnFault: t.OnFault,
OnTxStart: t.OnTxStart,
OnSystemCallStartV2: t.OnSystemCallStart,
OnSystemCallEnd: t.OnSystemCallEnd,
OnEnter: t.OnEnter,
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
}
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) {
if t.skip {
return
}
if depth != 0 {
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) {
if t.skip {
return
}
if depth == 0 {
fmt.Fprintf(t.out, "\nPost-execution info:\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.
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()
fmt.Fprintf(t.out, "| %4d | %10v | %3d |%10v |", pc, vm.OpCode(op).String(),
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) {
if t.skip {
return
}
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/core/rawdb"
"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/triedb"
)
@ -36,7 +37,7 @@ func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil
func TestStateOverrideMovePrecompile(t *testing.T) {
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 {
t.Fatalf("failed to create statedb: %v", err)
}

View file

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

View file

@ -41,19 +41,19 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) {
baseNumber: 10,
baseTimestamp: 50,
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,
baseTimestamp: 50,
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(70)}}, {}},
expected: []result{{number: 11, timestamp: 51}, {number: 12, timestamp: 52}, {number: 13, timestamp: 70}, {number: 14, timestamp: 71}},
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(80)}}, {}},
expected: []result{{number: 11, timestamp: 62}, {number: 12, timestamp: 74}, {number: 13, timestamp: 80}, {number: 14, timestamp: 92}},
},
{
baseNumber: 10,
baseTimestamp: 50,
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,
@ -64,8 +64,8 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) {
{
baseNumber: 10,
baseTimestamp: 50,
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(52)}}},
err: "block timestamps must be in order: 52 <= 52",
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(74)}}},
err: "block timestamps must be in order: 74 <= 74",
},
{
baseNumber: 10,
@ -76,8 +76,8 @@ func TestSimulateSanitizeBlockOrder(t *testing.T) {
{
baseNumber: 10,
baseTimestamp: 50,
blocks: []simBlock{{BlockOverrides: &override.BlockOverrides{Number: newInt(11), Time: newUint64(60)}}, {BlockOverrides: &override.BlockOverrides{Number: newInt(13), Time: newUint64(61)}}},
err: "block timestamps must be in order: 61 <= 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: 72 <= 72",
},
} {
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.Version = version.WithCommit(git.Commit, git.Date)
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 {
MigrateGlobalFlags(ctx)
return nil

View file

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

View file

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

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-verkle"
)
const (
@ -148,6 +149,29 @@ var Defaults = &Config{
// ReadOnly is the config in order to open database in read only mode.
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
// 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
@ -164,9 +188,10 @@ type Database struct {
// readOnly is the flag whether the mutation is allowed to be applied.
// It will be set automatically when the database is journaled during
// the shutdown to reject all following unexpected mutations.
readOnly bool // Flag if database is opened in read only mode
waitSync bool // Flag if database is deactivated due to initial state sync
isVerkle bool // Flag if database is used for verkle tree
readOnly bool // Flag if database is opened in read only mode
waitSync bool // Flag if database is deactivated due to initial state sync
isVerkle bool // Flag if database is used for verkle tree
hasher nodeHasher // Trie node hasher
config *Config // Configuration for database
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()
db := &Database{
readOnly: config.ReadOnly,
isVerkle: isVerkle,
config: config,
diskdb: diskdb,
hasher: merkleNodeHasher,
}
// Establish a dedicated database namespace tailored for verkle-specific
// 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
// substantial storage overhead, as the underlying database will efficiently
// compress the shared key prefix.
if isVerkle {
diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix))
}
db := &Database{
readOnly: config.ReadOnly,
isVerkle: isVerkle,
config: config,
diskdb: diskdb,
db.diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix))
db.hasher = verkleNodeHasher
}
// Construct the layer tree by resolving the in-disk singleton state
// 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.
// 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 {
// Hold the lock to prevent concurrent mutations.
db.lock.Lock()
@ -350,10 +379,9 @@ func (db *Database) Enable(root common.Hash) error {
return errDatabaseReadOnly
}
// Ensure the provided state root matches the stored one.
root = types.TrieRootHash(root)
stored := types.EmptyRootHash
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
stored = crypto.Keccak256Hash(blob)
stored, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
if err != nil {
return err
}
if 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.
// The state is supported as the rollback destination only if it's
// 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 {
db.lock.Lock()
defer db.lock.Unlock()
@ -401,7 +431,6 @@ func (db *Database) Recover(root common.Hash) error {
return errors.New("state rollback is non-supported")
}
// Short circuit if the target state is not recoverable
root = types.TrieRootHash(root)
if !db.Recoverable(root) {
return errStateUnrecoverable
}
@ -434,9 +463,10 @@ func (db *Database) Recover(root common.Hash) error {
}
// 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 {
// Ensure the requested state is a known state.
root = types.TrieRootHash(root)
id := rawdb.ReadStateID(db.diskdb, root)
if id == nil {
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{})
)
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:
// account creation
addr := testrand.Address()
@ -453,8 +458,8 @@ func TestDatabaseRecoverable(t *testing.T) {
// Initial state should be recoverable
{types.EmptyRootHash, true},
// Initial state should be recoverable
{common.Hash{}, true},
// common.Hash{} is not a valid state root for revert
{common.Hash{}, false},
// Layers below current disk layer are recoverable
{tester.roots[index-1], true},

View file

@ -18,6 +18,7 @@ package pathdb
import (
"bytes"
"cmp"
"fmt"
"slices"
"sort"
@ -45,13 +46,7 @@ func (it *weightedIterator) Cmp(other *weightedIterator) int {
return 1
}
// Same account/storage-slot in multiple layers, split by priority
if it.priority < other.priority {
return -1
}
if it.priority > other.priority {
return 1
}
return 0
return cmp.Compare(it.priority, other.priority)
}
// 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/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"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.
func (db *Database) loadLayers() layer {
// Retrieve the root node of persistent state.
var root = types.EmptyRootHash
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
root = crypto.Keccak256Hash(blob)
root, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
if err != nil {
log.Crit("Failed to compute node hash", "err", err)
}
// Load the layers by resolving the journal
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
// flattening everything down (bad for reorgs). And this function will mark the
// 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 {
// Retrieve the head layer to journal from.
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
// on top are continuous with disk.
diskRoot := types.EmptyRootHash
if blob := rawdb.ReadAccountTrieNode(db.diskdb, nil); len(blob) > 0 {
diskRoot = crypto.Keccak256Hash(blob)
diskRoot, err := db.hasher(rawdb.ReadAccountTrieNode(db.diskdb, nil))
if err != nil {
return err
}
if err := rlp.Encode(journal, diskRoot); err != nil {
return err

View file

@ -22,7 +22,6 @@ import (
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie/trienode"
)
@ -62,7 +61,7 @@ func (tree *layerTree) get(root common.Hash) layer {
tree.lock.RLock()
defer tree.lock.RUnlock()
return tree.layers[types.TrieRootHash(root)]
return tree.layers[root]
}
// 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
// responsibility to avoid even attempting to insert such a layer.
root, parentRoot = types.TrieRootHash(root), types.TrieRootHash(parentRoot)
if root == parentRoot {
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.
func (tree *layerTree) cap(root common.Hash, layers int) error {
// Retrieve the head layer to cap from
root = types.TrieRootHash(root)
l := tree.get(root)
if l == nil {
return fmt.Errorf("triedb layer [%#x] missing", root)