Merge branch 'ethereum:master' into santa

This commit is contained in:
web3santa 2024-04-14 23:13:40 +09:00 committed by GitHub
commit b25a6ba82c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 965 additions and 302 deletions

View file

@ -205,7 +205,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
to = &t to = &t
} }
args := &apitypes.SendTxArgs{ args := &apitypes.SendTxArgs{
Data: &data, Input: &data,
Nonce: hexutil.Uint64(tx.Nonce()), Nonce: hexutil.Uint64(tx.Nonce()),
Value: hexutil.Big(*tx.Value()), Value: hexutil.Big(*tx.Value()),
Gas: hexutil.Uint64(tx.Gas()), Gas: hexutil.Uint64(tx.Gas()),
@ -215,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
switch tx.Type() { switch tx.Type() {
case types.LegacyTxType, types.AccessListTxType: case types.LegacyTxType, types.AccessListTxType:
args.GasPrice = (*hexutil.Big)(tx.GasPrice()) args.GasPrice = (*hexutil.Big)(tx.GasPrice())
case types.DynamicFeeTxType: case types.DynamicFeeTxType, types.BlobTxType:
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
default: default:
@ -235,6 +235,17 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
accessList := tx.AccessList() accessList := tx.AccessList()
args.AccessList = &accessList args.AccessList = &accessList
} }
if tx.Type() == types.BlobTxType {
args.BlobHashes = tx.BlobHashes()
sidecar := tx.BlobTxSidecar()
if sidecar == nil {
return nil, errors.New("blobs must be present for signing")
}
args.Blobs = sidecar.Blobs
args.Commitments = sidecar.Commitments
args.Proofs = sidecar.Proofs
}
var res signTransactionResult var res signTransactionResult
if err := api.client.Call(&res, "account_signTransaction", args); err != nil { if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
return nil, err return nil, err

View file

@ -19,6 +19,7 @@ package engine
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"slices"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -132,12 +133,7 @@ func (b PayloadID) Version() PayloadVersion {
// Is returns whether the identifier matches any of provided payload versions. // Is returns whether the identifier matches any of provided payload versions.
func (b PayloadID) Is(versions ...PayloadVersion) bool { func (b PayloadID) Is(versions ...PayloadVersion) bool {
for _, v := range versions { return slices.Contains(versions, b.Version())
if v == b.Version() {
return true
}
}
return false
} }
func (b PayloadID) String() string { func (b PayloadID) String() string {

View file

@ -19,7 +19,9 @@ package types
import ( import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"math"
"os" "os"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -27,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/beacon/merkle" "github.com/ethereum/go-ethereum/beacon/merkle"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -34,6 +37,8 @@ import (
// across signing different data structures. // across signing different data structures.
const syncCommitteeDomain = 7 const syncCommitteeDomain = 7
var knownForks = []string{"GENESIS", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB"}
// Fork describes a single beacon chain fork and also stores the calculated // Fork describes a single beacon chain fork and also stores the calculated
// signature domain used after this fork. // signature domain used after this fork.
type Fork struct { type Fork struct {
@ -46,6 +51,9 @@ type Fork struct {
// Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types // Fork version, see https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#custom-types
Version []byte Version []byte
// index in list of known forks or MaxInt if unknown
knownIndex int
// calculated by computeDomain, based on fork version and genesis validators root // calculated by computeDomain, based on fork version and genesis validators root
domain merkle.Value domain merkle.Value
} }
@ -101,7 +109,12 @@ func (f Forks) SigningRoot(header Header) (common.Hash, error) {
func (f Forks) Len() int { return len(f) } func (f Forks) Len() int { return len(f) }
func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] } func (f Forks) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
func (f Forks) Less(i, j int) bool { return f[i].Epoch < f[j].Epoch } func (f Forks) Less(i, j int) bool {
if f[i].Epoch != f[j].Epoch {
return f[i].Epoch < f[j].Epoch
}
return f[i].knownIndex < f[j].knownIndex
}
// ChainConfig contains the beacon chain configuration. // ChainConfig contains the beacon chain configuration.
type ChainConfig struct { type ChainConfig struct {
@ -122,16 +135,22 @@ func (c *ChainConfig) ForkAtEpoch(epoch uint64) Fork {
// AddFork adds a new item to the list of forks. // AddFork adds a new item to the list of forks.
func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig { func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainConfig {
knownIndex := slices.Index(knownForks, name)
if knownIndex == -1 {
knownIndex = math.MaxInt // assume that the unknown fork happens after the known ones
if epoch != math.MaxUint64 {
log.Warn("Unknown fork in config.yaml", "fork name", name, "known forks", knownForks)
}
}
fork := &Fork{ fork := &Fork{
Name: name, Name: name,
Epoch: epoch, Epoch: epoch,
Version: version, Version: version,
knownIndex: knownIndex,
} }
fork.computeDomain(c.GenesisValidatorsRoot) fork.computeDomain(c.GenesisValidatorsRoot)
c.Forks = append(c.Forks, fork) c.Forks = append(c.Forks, fork)
sort.Sort(c.Forks) sort.Sort(c.Forks)
return c return c
} }
@ -181,6 +200,5 @@ func (c *ChainConfig) LoadForks(path string) error {
for name := range versions { for name := range versions {
return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name) return fmt.Errorf("epoch number missing for fork %q in beacon chain config file", name)
} }
sort.Sort(c.Forks)
return nil return nil
} }

View file

@ -5,22 +5,22 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/ # https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# version:golang 1.22.1 # version:golang 1.22.2
# https://go.dev/dl/ # https://go.dev/dl/
79c9b91d7f109515a25fc3ecdaad125d67e6bdb54f6d4d98580f46799caea321 go1.22.1.src.tar.gz 374ea82b289ec738e968267cac59c7d5ff180f9492250254784b2044e90df5a9 go1.22.2.src.tar.gz
3bc971772f4712fec0364f4bc3de06af22a00a12daab10b6f717fdcd13156cc0 go1.22.1.darwin-amd64.tar.gz 33e7f63077b1c5bce4f1ecadd4d990cf229667c40bfb00686990c950911b7ab7 go1.22.2.darwin-amd64.tar.gz
f6a9cec6b8a002fcc9c0ee24ec04d67f430a52abc3cfd613836986bcc00d8383 go1.22.1.darwin-arm64.tar.gz 660298be38648723e783ba0398e90431de1cb288c637880cdb124f39bd977f0d go1.22.2.darwin-arm64.tar.gz
99f81c10d5a3f8a886faf8fa86aaa2aaf929fbed54a972ae5eec3c5e0bdb961a go1.22.1.freebsd-386.tar.gz efc7162b0cad2f918ac566a923d4701feb29dc9c0ab625157d49b1cbcbba39da go1.22.2.freebsd-386.tar.gz
51c614ddd92ee4a9913a14c39bf80508d9cfba08561f24d2f075fd00f3cfb067 go1.22.1.freebsd-amd64.tar.gz d753428296e6709527e291fd204700a587ffef2c0a472b21aebea11618245929 go1.22.2.freebsd-amd64.tar.gz
8484df36d3d40139eaf0fe5e647b006435d826cc12f9ae72973bf7ec265e0ae4 go1.22.1.linux-386.tar.gz 586d9eb7fe0489ab297ad80dd06414997df487c5cf536c490ffeaa8d8f1807a7 go1.22.2.linux-386.tar.gz
aab8e15785c997ae20f9c88422ee35d962c4562212bb0f879d052a35c8307c7f go1.22.1.linux-amd64.tar.gz 5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471216a17 go1.22.2.linux-amd64.tar.gz
e56685a245b6a0c592fc4a55f0b7803af5b3f827aaa29feab1f40e491acf35b8 go1.22.1.linux-arm64.tar.gz 36e720b2d564980c162a48c7e97da2e407dfcc4239e1e58d98082dfa2486a0c1 go1.22.2.linux-arm64.tar.gz
8cb7a90e48c20daed39a6ac8b8a40760030ba5e93c12274c42191d868687c281 go1.22.1.linux-armv6l.tar.gz 9243dfafde06e1efe24d59df6701818e6786b4adfdf1191098050d6d023c5369 go1.22.2.linux-armv6l.tar.gz
ac775e19d93cc1668999b77cfe8c8964abfbc658718feccfe6e0eb87663cd668 go1.22.1.linux-ppc64le.tar.gz 251a8886c5113be6490bdbb955ddee98763b49c9b1bf4c8364c02d3b482dab00 go1.22.2.linux-ppc64le.tar.gz
7bb7dd8e10f95c9a4cc4f6bef44c816a6e7c9e03f56ac6af6efbb082b19b379f go1.22.1.linux-s390x.tar.gz 2b39019481c28c560d65e9811a478ae10e3ef765e0f59af362031d386a71bfef go1.22.2.linux-s390x.tar.gz
0c5ebb7eb39b7884ec99f92b425d4c03a96a72443562aafbf6e7d15c42a3108a go1.22.1.windows-386.zip 651753c06df037020ef4d162c5b273452e9ba976ed17ae39e66ef7ee89d8147e go1.22.2.windows-386.zip
cf9c66a208a106402a527f5b956269ca506cfe535fc388e828d249ea88ed28ba go1.22.1.windows-amd64.zip 8e581cf330f49d3266e936521a2d8263679ef7e2fc2cbbceb85659122d883596 go1.22.2.windows-amd64.zip
85b8511b298c9f4199ecae26afafcc3d46155bac934d43f2357b9224bcaa310f go1.22.1.windows-arm64.zip ddfca5beb9a0c62254266c3090c2555d899bf3e7aa26243e7de3621108f06875 go1.22.2.windows-arm64.zip
# version:golangci 1.55.2 # version:golangci 1.55.2
# https://github.com/golangci/golangci-lint/releases/ # https://github.com/golangci/golangci-lint/releases/

View file

@ -9,14 +9,14 @@ It enables usecases like the following:
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 managements 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.
The section below deals with both of them The section below deals with both of them
## Rule Implementation ## Rule Implementation
A ruleset file is implemented as a `js` file. Under the hood, the ruleset-engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods A ruleset file is implemented as a `js` file. Under the hood, the ruleset engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods
defined in the UI protocol. Example: defined in the UI protocol. Example:
```js ```js
@ -27,7 +27,7 @@ function asBig(str) {
return new BigNumber(str) return new BigNumber(str)
} }
// Approve transactions to a certain contract if 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 = big.Newint("0xb1a2bc2ec50000")
var value = asBig(req.transaction.value); var value = asBig(req.transaction.value);
@ -70,7 +70,7 @@ The Otto vm has a few [caveats](https://github.com/robertkrimen/otto):
Additionally, a few more have been added Additionally, a few more have been added
* The rule execution cannot load external javascript files. * The rule execution cannot load external javascript files.
* The only preloaded library is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the github repository. * The only preloaded library is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the GitHub repository.
* Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data. * Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data.
* Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes. * Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes.
* The JS engine has access to `storage` and `console`. * The JS engine has access to `storage` and `console`.
@ -88,8 +88,8 @@ Some security precautions can be made, such as:
##### Security of implementation ##### Security of implementation
The drawbacks of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement, since it's already The drawback of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement since it's already
implemented for `geth`. There are no known security vulnerabilities in, nor have we had any security-problems with it so far. implemented for `geth`. There are no known security vulnerabilities in it, nor have we had any security problems with it so far.
The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered
an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit
@ -105,7 +105,7 @@ It's unclear whether any other DSL could be more secure; since there's always th
## Credential management ## Credential management
The ability to auto-approve transaction means that the signer needs to have necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass). The ability to auto-approve transactions means that the signer needs to have the necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass).
### Example implementation ### Example implementation
@ -127,8 +127,8 @@ The `vault.dat` would be an encrypted container storing the following informatio
### Security considerations ### Security considerations
This would leave it up to the user to ensure that the `path/to/masterseed` is handled in a secure way. It's difficult to get around this, although one could This would leave it up to the user to ensure that the `path/to/masterseed` is handled securely. It's difficult to get around this, although one could
imagine leveraging OS-level keychains where supported. The setup is however in general similar to how ssh-keys are stored in `.ssh/`. imagine leveraging OS-level keychains where supported. The setup is however, in general, similar to how ssh-keys are stored in `.ssh/`.
# Implementation status # Implementation status
@ -163,7 +163,7 @@ function isLimitOk(transaction) {
if (stored != "") { if (stored != "") {
txs = JSON.parse(stored) txs = JSON.parse(stored)
} }
// First, remove all that have passed out of the time-window // First, remove all that has passed out of the time window
var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
console.log(txs, newtxs.length); console.log(txs, newtxs.length);
@ -174,7 +174,7 @@ function isLimitOk(transaction) {
console.log("ApproveTx > Sum so far", sum); console.log("ApproveTx > Sum so far", sum);
console.log("ApproveTx > Requested", value.toNumber()); console.log("ApproveTx > Requested", value.toNumber());
// Would we exceed weekly limit ? // Would we exceed the weekly limit ?
return sum.plus(value).lt(limit) return sum.plus(value).lt(limit)
} }

View file

@ -102,7 +102,7 @@ func (s *Suite) sendTxs(t *utesting.T, txs []*types.Transaction) error {
} }
} }
return fmt.Errorf("timed out waiting for txs") return errors.New("timed out waiting for txs")
} }
func (s *Suite) sendInvalidTxs(t *utesting.T, txs []*types.Transaction) error { func (s *Suite) sendInvalidTxs(t *utesting.T, txs []*types.Transaction) error {

View file

@ -19,6 +19,7 @@ package v5test
import ( import (
"bytes" "bytes"
"net" "net"
"slices"
"sync" "sync"
"time" "time"
@ -266,7 +267,7 @@ func (s *Suite) TestFindnodeResults(t *utesting.T) {
n := bn.conn.localNode.Node() n := bn.conn.localNode.Node()
expect[n.ID()] = n expect[n.ID()] = n
d := uint(enode.LogDist(n.ID(), s.Dest.ID())) d := uint(enode.LogDist(n.ID(), s.Dest.ID()))
if !containsUint(dists, d) { if !slices.Contains(dists, d) {
dists = append(dists, d) dists = append(dists, d)
} }
} }

View file

@ -252,12 +252,3 @@ func checkRecords(records []*enr.Record) ([]*enode.Node, error) {
} }
return nodes, nil return nodes, nil
} }
func containsUint(ints []uint, x uint) bool {
for i := range ints {
if ints[i] == x {
return true
}
}
return false
}

View file

@ -50,4 +50,4 @@ contains the password.
## JSON ## JSON
In case you need to output the result in a JSON format, you shall by using the `--json` flag. In case you need to output the result in a JSON format, you shall use the `--json` flag.

View file

@ -22,7 +22,7 @@ import (
"container/heap" "container/heap"
) )
// Priority queue data structure. // Prque is a priority queue data structure.
type Prque[P cmp.Ordered, V any] struct { type Prque[P cmp.Ordered, V any] struct {
cont *sstack[P, V] cont *sstack[P, V]
} }
@ -32,7 +32,7 @@ func New[P cmp.Ordered, V any](setIndex SetIndexCallback[V]) *Prque[P, V] {
return &Prque[P, V]{newSstack[P, V](setIndex)} return &Prque[P, V]{newSstack[P, V](setIndex)}
} }
// Pushes a value with a given priority into the queue, expanding if necessary. // Push a value with a given priority into the queue, expanding if necessary.
func (p *Prque[P, V]) Push(data V, priority P) { func (p *Prque[P, V]) Push(data V, priority P) {
heap.Push(p.cont, &item[P, V]{data, priority}) heap.Push(p.cont, &item[P, V]{data, priority})
} }
@ -43,14 +43,14 @@ func (p *Prque[P, V]) Peek() (V, P) {
return item.value, item.priority return item.value, item.priority
} }
// Pops the value with the greatest priority off the stack and returns it. // Pop the value with the greatest priority off the stack and returns it.
// Currently no shrinking is done. // Currently no shrinking is done.
func (p *Prque[P, V]) Pop() (V, P) { func (p *Prque[P, V]) Pop() (V, P) {
item := heap.Pop(p.cont).(*item[P, V]) item := heap.Pop(p.cont).(*item[P, V])
return item.value, item.priority return item.value, item.priority
} }
// Pops only the item from the queue, dropping the associated priority value. // PopItem pops only the item from the queue, dropping the associated priority value.
func (p *Prque[P, V]) PopItem() V { func (p *Prque[P, V]) PopItem() V {
return heap.Pop(p.cont).(*item[P, V]).value return heap.Pop(p.cont).(*item[P, V]).value
} }
@ -60,17 +60,17 @@ func (p *Prque[P, V]) Remove(i int) V {
return heap.Remove(p.cont, i).(*item[P, V]).value return heap.Remove(p.cont, i).(*item[P, V]).value
} }
// Checks whether the priority queue is empty. // Empty checks whether the priority queue is empty.
func (p *Prque[P, V]) Empty() bool { func (p *Prque[P, V]) Empty() bool {
return p.cont.Len() == 0 return p.cont.Len() == 0
} }
// Returns the number of element in the priority queue. // Size returns the number of element in the priority queue.
func (p *Prque[P, V]) Size() int { func (p *Prque[P, V]) Size() int {
return p.cont.Len() return p.cont.Len()
} }
// Clears the contents of the priority queue. // Reset clears the contents of the priority queue.
func (p *Prque[P, V]) Reset() { func (p *Prque[P, V]) Reset() {
*p = *New[P, V](p.cont.setIndex) *p = *New[P, V](p.cont.setIndex)
} }

View file

@ -49,7 +49,7 @@ func newSstack[P cmp.Ordered, V any](setIndex SetIndexCallback[V]) *sstack[P, V]
return result return result
} }
// Pushes a value onto the stack, expanding it if necessary. Required by // Push a value onto the stack, expanding it if necessary. Required by
// heap.Interface. // heap.Interface.
func (s *sstack[P, V]) Push(data any) { func (s *sstack[P, V]) Push(data any) {
if s.size == s.capacity { if s.size == s.capacity {
@ -69,7 +69,7 @@ func (s *sstack[P, V]) Push(data any) {
s.size++ s.size++
} }
// Pops a value off the stack and returns it. Currently no shrinking is done. // Pop a value off the stack and returns it. Currently no shrinking is done.
// Required by heap.Interface. // Required by heap.Interface.
func (s *sstack[P, V]) Pop() (res any) { func (s *sstack[P, V]) Pop() (res any) {
s.size-- s.size--
@ -85,18 +85,18 @@ func (s *sstack[P, V]) Pop() (res any) {
return return
} }
// Returns the length of the stack. Required by sort.Interface. // Len returns the length of the stack. Required by sort.Interface.
func (s *sstack[P, V]) Len() int { func (s *sstack[P, V]) Len() int {
return s.size return s.size
} }
// Compares the priority of two elements of the stack (higher is first). // Less compares the priority of two elements of the stack (higher is first).
// Required by sort.Interface. // Required by sort.Interface.
func (s *sstack[P, V]) Less(i, j int) bool { func (s *sstack[P, V]) Less(i, j int) bool {
return s.blocks[i/blockSize][i%blockSize].priority > s.blocks[j/blockSize][j%blockSize].priority return s.blocks[i/blockSize][i%blockSize].priority > s.blocks[j/blockSize][j%blockSize].priority
} }
// Swaps two elements in the stack. Required by sort.Interface. // Swap two elements in the stack. Required by sort.Interface.
func (s *sstack[P, V]) Swap(i, j int) { func (s *sstack[P, V]) Swap(i, j int) {
ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize ib, io, jb, jo := i/blockSize, i%blockSize, j/blockSize, j%blockSize
a, b := s.blocks[jb][jo], s.blocks[ib][io] a, b := s.blocks[jb][jo], s.blocks[ib][io]
@ -107,7 +107,7 @@ func (s *sstack[P, V]) Swap(i, j int) {
s.blocks[ib][io], s.blocks[jb][jo] = a, b s.blocks[ib][io], s.blocks[jb][jo] = a, b
} }
// Resets the stack, effectively clearing its contents. // Reset the stack, effectively clearing its contents.
func (s *sstack[P, V]) Reset() { func (s *sstack[P, V]) Reset() {
*s = *newSstack[P, V](s.setIndex) *s = *newSstack[P, V](s.setIndex)
} }

View file

@ -132,7 +132,7 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
if rbloom != header.Bloom { if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
} }
// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) // The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash { if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)

View file

@ -244,6 +244,8 @@ type BlockChain struct {
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
receiptsCache *lru.Cache[common.Hash, []*types.Receipt] receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
blockCache *lru.Cache[common.Hash, *types.Block] blockCache *lru.Cache[common.Hash, *types.Block]
txLookupLock sync.RWMutex
txLookupCache *lru.Cache[common.Hash, txLookup] txLookupCache *lru.Cache[common.Hash, txLookup]
wg sync.WaitGroup wg sync.WaitGroup
@ -439,7 +441,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
} }
if alloc == nil { if alloc == nil {
return nil, fmt.Errorf("live blockchain tracer requires genesis alloc to be set") return nil, errors.New("live blockchain tracer requires genesis alloc to be set")
} }
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc) bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
@ -2290,14 +2292,14 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
// rewind the canonical chain to a lower point. // rewind the canonical chain to a lower point.
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain)) log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain))
} }
// Reset the tx lookup cache in case to clear stale txlookups. // Acquire the tx-lookup lock before mutation. This step is essential
// This is done before writing any new chain data to avoid the // as the txlookups should be changed atomically, and all subsequent
// weird scenario that canonical chain is changed while the // reads should be blocked until the mutation is complete.
// stale lookups are still cached. bc.txLookupLock.Lock()
bc.txLookupCache.Purge()
// Insert the new chain(except the head block(reverse order)), // Insert the new chain segment in incremental order, from the old
// taking care of the proper incremental order. // to the new. The new chain head (newChain[0]) is not inserted here,
// as it will be handled separately outside of this function
for i := len(newChain) - 1; i >= 1; i-- { for i := len(newChain) - 1; i >= 1; i-- {
// Insert the block in the canonical way, re-writing history // Insert the block in the canonical way, re-writing history
bc.writeHeadBlock(newChain[i]) bc.writeHeadBlock(newChain[i])
@ -2334,6 +2336,11 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
if err := indexesBatch.Write(); err != nil { if err := indexesBatch.Write(); err != nil {
log.Crit("Failed to delete useless indexes", "err", err) log.Crit("Failed to delete useless indexes", "err", err)
} }
// Reset the tx lookup cache to clear stale txlookup cache.
bc.txLookupCache.Purge()
// Release the tx-lookup lock after mutation.
bc.txLookupLock.Unlock()
// Send out events for logs from the old canon chain, and 'reborn' // Send out events for logs from the old canon chain, and 'reborn'
// logs from the new canon chain. The number of logs can be very // logs from the new canon chain. The number of logs can be very

View file

@ -266,6 +266,9 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
// transaction indexing is already finished. The transaction is not existent // transaction indexing is already finished. The transaction is not existent
// from the node's perspective. // from the node's perspective.
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) { func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) {
bc.txLookupLock.RLock()
defer bc.txLookupLock.RUnlock()
// Short circuit if the txlookup already in the cache, retrieve otherwise // Short circuit if the txlookup already in the cache, retrieve otherwise
if item, exist := bc.txLookupCache.Get(hash); exist { if item, exist := bc.txLookupCache.Get(hash); exist {
return item.lookup, item.transaction, nil return item.lookup, item.transaction, nil

View file

@ -22,7 +22,7 @@ package core
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"path" "path/filepath"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -1966,7 +1966,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
// Create a temporary persistent database // Create a temporary persistent database
datadir := t.TempDir() datadir := t.TempDir()
ancient := path.Join(datadir, "ancient") ancient := filepath.Join(datadir, "ancient")
db, err := rawdb.Open(rawdb.OpenOptions{ db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir, Directory: datadir,

View file

@ -596,6 +596,9 @@ func (s *MatcherSession) deliverSections(bit uint, sections []uint64, bitsets []
// of the session, any request in-flight need to be responded to! Empty responses // of the session, any request in-flight need to be responded to! Empty responses
// are fine though in that case. // are fine though in that case.
func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) { func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan *Retrieval) {
waitTimer := time.NewTimer(wait)
defer waitTimer.Stop()
for { for {
// Allocate a new bloom bit index to retrieve data for, stopping when done // Allocate a new bloom bit index to retrieve data for, stopping when done
bit, ok := s.allocateRetrieval() bit, ok := s.allocateRetrieval()
@ -604,6 +607,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
} }
// Bit allocated, throttle a bit if we're below our batch limit // Bit allocated, throttle a bit if we're below our batch limit
if s.pendingSections(bit) < batch { if s.pendingSections(bit) < batch {
waitTimer.Reset(wait)
select { select {
case <-s.quit: case <-s.quit:
// Session terminating, we can't meaningfully service, abort // Session terminating, we can't meaningfully service, abort
@ -611,7 +615,7 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan
s.deliverSections(bit, []uint64{}, [][]byte{}) s.deliverSections(bit, []uint64{}, [][]byte{})
return return
case <-time.After(wait): case <-waitTimer.C:
// Throttling up, fetch whatever is available // Throttling up, fetch whatever is available
} }
} }

View file

@ -59,7 +59,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.ExcessBlobGas != nil { if header.ExcessBlobGas != nil {
blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas)
} }
if header.Difficulty.Cmp(common.Big0) == 0 { if header.Difficulty.Sign() == 0 {
random = &header.MixDigest random = &header.MixDigest
} }
return vm.BlockContext{ return vm.BlockContext{

View file

@ -582,7 +582,7 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
Config: &config, Config: &config,
GasLimit: gasLimit, GasLimit: gasLimit,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: big.NewInt(1), Difficulty: big.NewInt(0),
Alloc: map[common.Address]types.Account{ Alloc: map[common.Address]types.Account{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256

View file

@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -172,7 +171,7 @@ func resolveChainFreezerDir(ancient string) string {
// sub folder, if not then two possibilities: // sub folder, if not then two possibilities:
// - chain freezer is not initialized // - chain freezer is not initialized
// - chain freezer exists in legacy location (root ancient folder) // - chain freezer exists in legacy location (root ancient folder)
freezer := path.Join(ancient, ChainFreezerName) freezer := filepath.Join(ancient, ChainFreezerName)
if !common.FileExist(freezer) { if !common.FileExist(freezer) {
if !common.FileExist(ancient) { if !common.FileExist(ancient) {
// The entire ancient store is not initialized, still use the sub // The entire ancient store is not initialized, still use the sub

View file

@ -23,7 +23,6 @@ import (
"math/big" "math/big"
"math/rand" "math/rand"
"os" "os"
"path"
"path/filepath" "path/filepath"
"sync" "sync"
"testing" "testing"
@ -398,11 +397,11 @@ func TestRenameWindows(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f2, err := os.Create(path.Join(dir1, fname2)) f2, err := os.Create(filepath.Join(dir1, fname2))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
f3, err := os.Create(path.Join(dir2, fname2)) f3, err := os.Create(filepath.Join(dir2, fname2))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -424,15 +423,15 @@ func TestRenameWindows(t *testing.T) {
if err := f3.Close(); err != nil { if err := f3.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := os.Rename(f.Name(), path.Join(dir2, fname)); err != nil { if err := os.Rename(f.Name(), filepath.Join(dir2, fname)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := os.Rename(f2.Name(), path.Join(dir2, fname2)); err != nil { if err := os.Rename(f2.Name(), filepath.Join(dir2, fname2)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Check file contents // Check file contents
f, err = os.Open(path.Join(dir2, fname)) f, err = os.Open(filepath.Join(dir2, fname))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -446,7 +445,7 @@ func TestRenameWindows(t *testing.T) {
t.Errorf("unexpected file contents. Got %v\n", buf) t.Errorf("unexpected file contents. Got %v\n", buf)
} }
f, err = os.Open(path.Join(dir2, fname2)) f, err = os.Open(filepath.Join(dir2, fname2))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

69
core/tracing/CHANGELOG.md Normal file
View file

@ -0,0 +1,69 @@
# Changelog
All notable changes to the tracing interface will be documented in this file.
## [Unreleased]
There has been a major breaking change in the tracing interface for custom native tracers. JS and built-in tracers are not affected by this change and tracing API methods may be used as before. This overhaul has been done as part of the new live tracing feature ([#29189](https://github.com/ethereum/go-ethereum/pull/29189)). To learn more about live tracing please refer to the [docs](https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing).
**The `EVMLogger` interface which the tracers implemented has been removed.** It has been replaced by a new struct `tracing.Hooks`. `Hooks` keeps pointers to event listening functions. Internally the EVM will use these function pointers to emit events and can skip an event if the tracer has opted not to implement it. In fact this is the main reason for this change of approach. Another benefit is the ease of adding new hooks in future, and dynamically assigning event receivers.
The consequence of this change can be seen in the constructor of a tracer. Let's take the 4byte tracer as an example. Previously the constructor return an instance which satisfied the interface. Now it should return a pointer to `tracers.Tracer` (which is now also a struct as opposed to an interface) and explicitly assign the event listeners. As a side-benefit the tracers will not have to provide empty implementation of methods just to satisfy the interface:
```go
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
t := &fourByteTracer{
ids: make(map[string]int),
}
return t, nil
}
```
And now:
```go
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) {
t := &fourByteTracer{
ids: make(map[string]int),
}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnTxStart: t.onTxStart,
OnEnter: t.onEnter,
},
GetResult: t.getResult,
Stop: t.stop,
}, nil
}
```
### Event listeners
If you have sharp eyes you might have noticed the new names for `OnTxStart` and `OnEnter`, previously called `CaptureTxStart` and `CaptureEnter`. Indeed there have been various modifications to the signatures of the event listeners. All method names now follow the `On*` pattern instead of `Capture*`. However the modifications are not limited to the names.
#### New methods
The live tracing feature was half about adding more observability into the state of the blockchain. As such there have been a host of method additions. Please consult the [Hooks](./hooks.go) struct for the full list of methods. Custom tracers which are invoked through the API (as opposed to "live" tracers) can benefit from the following new methods:
- `OnGasChange(old, new uint64, reason GasChangeReason)`: This hook tracks the lifetime of gas within a transaction and its subcalls. It will first track the initial purchase of gas with ether, then the following consumptions and refunds of gas until at the end the rest is returned.
- `OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason)`: This hook tracks the balance changes of accounts. Where possible a reason is provided for the change (e.g. a transfer, gas purchase, withdrawal deposit etc).
- `OnNonceChange(addr common.Address, prev, new uint64)`: This hook tracks the nonce changes of accounts.
- `OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)`: This hook tracks the code changes of accounts.
- `OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash)`: This hook tracks the storage changes of accounts.
- `OnLogChange(log *types.Log)`: This hook tracks the logs emitted by the EVM.
#### Removed methods
The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signaled the top-level call frame of a transaction. The relevant info will be now emitted by `OnEnter` and `OnExit` which are emitted for every call frame. They now contain a `depth` parameter which can be used to distinguish the top-level call frame when necessary. The `create bool` parameter to `CaptureStart` can now be inferred from `typ byte` in `OnEnter`, i.e. `vm.OpCode(typ) == vm.CREATE`.
#### Modified methods
- `CaptureTxStart` -> `OnTxStart(vm *VMContext, tx *types.Transaction, from common.Address)`. It now emits the full transaction object as well as `from` which should be used to get the sender address. The `*VMContext` is a replacement for the `*vm.EVM` object previously passed to `CaptureStart`.
- `CaptureTxEnd` -> `OnTxEnd(receipt *types.Receipt, err error)`. It now returns the full receipt object.
- `CaptureEnter` -> `OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)`. The new `depth int` parameter indicates the call stack depth. It is 0 for the top-level call. Furthermore, the location where `OnEnter` is called in the EVM is now made a soon as a call is started. This means some specific error cases that were not before calling `OnEnter/OnExit` will now do so, leading some transaction to have an extra call traced.
- `CaptureExit` -> `OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool)`. It has the new `depth` parameter, same as `OnEnter`. The new `reverted` parameter indicates whether the call frame was reverted.
- `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract.
- `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above.
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.13.14...master

View file

@ -60,7 +60,7 @@ func newLimbo(datadir string) (*limbo, error) {
fails = append(fails, id) fails = append(fails, id)
} }
} }
store, err := billy.Open(billy.Options{Path: datadir}, newSlotter(), index) store, err := billy.Open(billy.Options{Path: datadir, Repair: true}, newSlotter(), index)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -197,8 +197,8 @@ type Block struct {
withdrawals Withdrawals withdrawals Withdrawals
// caches // caches
hash atomic.Value hash atomic.Pointer[common.Hash]
size atomic.Value size atomic.Uint64
// These fields are used by package eth to track // These fields are used by package eth to track
// inter-peer block relay. // inter-peer block relay.
@ -406,8 +406,8 @@ func (b *Block) BlobGasUsed() *uint64 {
// Size returns the true RLP encoded storage size of the block, either by encoding // Size returns the true RLP encoded storage size of the block, either by encoding
// and returning it, or returning a previously cached value. // and returning it, or returning a previously cached value.
func (b *Block) Size() uint64 { func (b *Block) Size() uint64 {
if size := b.size.Load(); size != nil { if size := b.size.Load(); size > 0 {
return size.(uint64) return size
} }
c := writeCounter(0) c := writeCounter(0)
rlp.Encode(&c, b) rlp.Encode(&c, b)
@ -486,11 +486,11 @@ func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
// The hash is computed on the first call and cached thereafter. // The hash is computed on the first call and cached thereafter.
func (b *Block) Hash() common.Hash { func (b *Block) Hash() common.Hash {
if hash := b.hash.Load(); hash != nil { if hash := b.hash.Load(); hash != nil {
return hash.(common.Hash) return *hash
} }
v := b.header.Hash() h := b.header.Hash()
b.hash.Store(v) b.hash.Store(&h)
return v return h
} }
type Blocks []*Block type Blocks []*Block

View file

@ -57,9 +57,9 @@ type Transaction struct {
time time.Time // Time first seen locally (spam avoidance) time time.Time // Time first seen locally (spam avoidance)
// caches // caches
hash atomic.Value hash atomic.Pointer[common.Hash]
size atomic.Value size atomic.Uint64
from atomic.Value from atomic.Pointer[sigCache]
} }
// NewTx creates a new transaction. // NewTx creates a new transaction.
@ -446,6 +446,26 @@ func (tx *Transaction) WithoutBlobTxSidecar() *Transaction {
return cpy return cpy
} }
// WithBlobTxSidecar returns a copy of tx with the blob sidecar added.
func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction {
blobtx, ok := tx.inner.(*BlobTx)
if !ok {
return tx
}
cpy := &Transaction{
inner: blobtx.withSidecar(sideCar),
time: tx.time,
}
// Note: tx.size cache not carried over because the sidecar is included in size!
if h := tx.hash.Load(); h != nil {
cpy.hash.Store(h)
}
if f := tx.from.Load(); f != nil {
cpy.from.Store(f)
}
return cpy
}
// SetTime sets the decoding time of a transaction. This is used by tests to set // SetTime sets the decoding time of a transaction. This is used by tests to set
// arbitrary times and by persistent transaction pools when loading old txs from // arbitrary times and by persistent transaction pools when loading old txs from
// disk. // disk.
@ -462,7 +482,7 @@ func (tx *Transaction) Time() time.Time {
// Hash returns the transaction hash. // Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash { func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil { if hash := tx.hash.Load(); hash != nil {
return hash.(common.Hash) return *hash
} }
var h common.Hash var h common.Hash
@ -471,15 +491,15 @@ func (tx *Transaction) Hash() common.Hash {
} else { } else {
h = prefixedRlpHash(tx.Type(), tx.inner) h = prefixedRlpHash(tx.Type(), tx.inner)
} }
tx.hash.Store(h) tx.hash.Store(&h)
return h return h
} }
// Size returns the true encoded storage size of the transaction, either by encoding // Size returns the true encoded storage size of the transaction, either by encoding
// and returning it, or returning a previously cached value. // and returning it, or returning a previously cached value.
func (tx *Transaction) Size() uint64 { func (tx *Transaction) Size() uint64 {
if size := tx.size.Load(); size != nil { if size := tx.size.Load(); size > 0 {
return size.(uint64) return size
} }
// Cache miss, encode and cache. // Cache miss, encode and cache.

View file

@ -128,8 +128,7 @@ func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction
// signing method. The cache is invalidated if the cached signer does // signing method. The cache is invalidated if the cached signer does
// not match the signer used in the current call. // not match the signer used in the current call.
func Sender(signer Signer, tx *Transaction) (common.Address, error) { func Sender(signer Signer, tx *Transaction) (common.Address, error) {
if sc := tx.from.Load(); sc != nil { if sigCache := tx.from.Load(); sigCache != nil {
sigCache := sc.(sigCache)
// If the signer used to derive from in a previous // If the signer used to derive from in a previous
// call is not the same as used current, invalidate // call is not the same as used current, invalidate
// the cache. // the cache.
@ -142,7 +141,7 @@ func Sender(signer Signer, tx *Transaction) (common.Address, error) {
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
tx.from.Store(sigCache{signer: signer, from: addr}) tx.from.Store(&sigCache{signer: signer, from: addr})
return addr, nil return addr, nil
} }

View file

@ -191,6 +191,12 @@ func (tx *BlobTx) withoutSidecar() *BlobTx {
return &cpy return &cpy
} }
func (tx *BlobTx) withSidecar(sideCar *BlobTxSidecar) *BlobTx {
cpy := *tx
cpy.Sidecar = sideCar
return &cpy
}
func (tx *BlobTx) encode(b *bytes.Buffer) error { func (tx *BlobTx) encode(b *bytes.Buffer) error {
if tx.Sidecar == nil { if tx.Sidecar == nil {
return rlp.Encode(b, tx) return rlp.Encode(b, tx)

View file

@ -111,15 +111,15 @@ var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
// PrecompiledContractsBLS contains the set of pre-compiled Ethereum // PrecompiledContractsBLS contains the set of pre-compiled Ethereum
// contracts specified in EIP-2537. These are exported for testing purposes. // contracts specified in EIP-2537. These are exported for testing purposes.
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{10}): &bls12381G1Add{}, common.BytesToAddress([]byte{11}): &bls12381G1Add{},
common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, common.BytesToAddress([]byte{12}): &bls12381G1Mul{},
common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, common.BytesToAddress([]byte{13}): &bls12381G1MultiExp{},
common.BytesToAddress([]byte{13}): &bls12381G2Add{}, common.BytesToAddress([]byte{14}): &bls12381G2Add{},
common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, common.BytesToAddress([]byte{15}): &bls12381G2Mul{},
common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, common.BytesToAddress([]byte{16}): &bls12381G2MultiExp{},
common.BytesToAddress([]byte{16}): &bls12381Pairing{}, common.BytesToAddress([]byte{17}): &bls12381Pairing{},
common.BytesToAddress([]byte{17}): &bls12381MapG1{}, common.BytesToAddress([]byte{18}): &bls12381MapG1{},
common.BytesToAddress([]byte{18}): &bls12381MapG2{}, common.BytesToAddress([]byte{19}): &bls12381MapG2{},
} }
var ( var (

View file

@ -31,6 +31,7 @@ var (
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrExecutionReverted = errors.New("execution reverted") ErrExecutionReverted = errors.New("execution reverted")
ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") ErrMaxCodeSizeExceeded = errors.New("max code size exceeded")
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
ErrInvalidJump = errors.New("invalid jump destination") ErrInvalidJump = errors.New("invalid jump destination")
ErrWriteProtection = errors.New("write protection") ErrWriteProtection = errors.New("write protection")
ErrReturnDataOutOfBounds = errors.New("return data out of bounds") ErrReturnDataOutOfBounds = errors.New("return data out of bounds")

View file

@ -439,13 +439,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if evm.chainRules.IsBerlin { if evm.chainRules.IsBerlin {
evm.StateDB.AddAddressToAccessList(address) evm.StateDB.AddAddressToAccessList(address)
} }
// Ensure there's no existing contract already at the designated address // Ensure there's no existing contract already at the designated address.
// Account is regarded as existent if any of these three conditions is met:
// - the nonce is nonzero
// - the code is non-empty
// - the storage is non-empty
contractHash := evm.StateDB.GetCodeHash(address) contractHash := evm.StateDB.GetCodeHash(address)
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { storageRoot := evm.StateDB.GetStorageRoot(address)
if evm.StateDB.GetNonce(address) != 0 ||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
} }
return nil, common.Address{}, 0, ErrContractAddressCollision return nil, common.Address{}, 0, ErrContractAddressCollision
} }
// Create a new account on the state // Create a new account on the state

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"errors" "errors"
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -310,9 +311,12 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
return 0, err return 0, err
} }
size, overflow := stack.Back(2).Uint64WithOverflow() size, overflow := stack.Back(2).Uint64WithOverflow()
if overflow || size > params.MaxInitCodeSize { if overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if size > params.MaxInitCodeSize {
return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size)
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
moreGas := params.InitCodeWordGas * ((size + 31) / 32) moreGas := params.InitCodeWordGas * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow { if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
@ -326,9 +330,12 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
return 0, err return 0, err
} }
size, overflow := stack.Back(2).Uint64WithOverflow() size, overflow := stack.Back(2).Uint64WithOverflow()
if overflow || size > params.MaxInitCodeSize { if overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if size > params.MaxInitCodeSize {
return 0, fmt.Errorf("%w: size %d", ErrMaxInitCodeSizeExceeded, size)
}
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32)
if gas, overflow = math.SafeAdd(gas, moreGas); overflow { if gas, overflow = math.SafeAdd(gas, moreGas); overflow {

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"bytes" "bytes"
"errors"
"math" "math"
"math/big" "math/big"
"sort" "sort"
@ -98,7 +99,7 @@ func TestEIP2200(t *testing.T) {
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(uint256.Int)) _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(uint256.Int))
if err != tt.failure { if !errors.Is(err, tt.failure) {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
} }
if used := tt.gaspool - gas; used != tt.used { if used := tt.gaspool - gas; used != tt.used {

View file

@ -49,6 +49,7 @@ type StateDB interface {
GetCommittedState(common.Address, common.Hash) common.Hash GetCommittedState(common.Address, common.Hash) common.Hash
GetState(common.Address, common.Hash) common.Hash GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash) SetState(common.Address, common.Hash, common.Hash)
GetStorageRoot(addr common.Address) common.Hash
GetTransientState(addr common.Address, key common.Hash) common.Hash GetTransientState(addr common.Address, key common.Hash) common.Hash
SetTransientState(addr common.Address, key, value common.Hash) SetTransientState(addr common.Address, key, value common.Hash)

View file

@ -17,6 +17,8 @@
package vm package vm
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
@ -255,7 +257,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
var dynamicCost uint64 var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing cost += dynamicCost // for tracing
if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
}
if !contract.UseGas(dynamicCost, in.evm.Config.Tracer, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }

View file

@ -134,6 +134,7 @@ func createKeyPair() (string, string) {
defer os.Remove(tmpKey.Name()) defer os.Remove(tmpKey.Name())
defer os.Remove(tmpKey.Name() + ".pub") defer os.Remove(tmpKey.Name() + ".pub")
defer os.Remove(tmpKey.Name() + ".sec") defer os.Remove(tmpKey.Name() + ".sec")
defer tmpKey.Close()
cmd := exec.Command("signify", "-G", "-n", "-p", tmpKey.Name()+".pub", "-s", tmpKey.Name()+".sec") cmd := exec.Command("signify", "-G", "-n", "-p", tmpKey.Name()+".pub", "-s", tmpKey.Name()+".sec")
if output, err := cmd.CombinedOutput(); err != nil { if output, err := cmd.CombinedOutput(); err != nil {
panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output)) panic(fmt.Sprintf("could not verify the file: %v, output: \n%s", err, output))

View file

@ -111,7 +111,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if !config.SyncMode.IsValid() { if !config.SyncMode.IsValid() {
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode) return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
} }
if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 { if config.Miner.GasPrice == nil || config.Miner.GasPrice.Sign() <= 0 {
log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice) log.Warn("Sanitizing invalid miner gas price", "provided", config.Miner.GasPrice, "updated", ethconfig.Defaults.Miner.GasPrice)
config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice) config.Miner.GasPrice = new(big.Int).Set(ethconfig.Defaults.Miner.GasPrice)
} }

View file

@ -74,7 +74,7 @@ func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis) (*node.
// send enough transactions to fill multiple blocks // send enough transactions to fill multiple blocks
func TestSimulatedBeaconSendWithdrawals(t *testing.T) { func TestSimulatedBeaconSendWithdrawals(t *testing.T) {
var withdrawals []types.Withdrawal var withdrawals []types.Withdrawal
txs := make(map[common.Hash]types.Transaction) txs := make(map[common.Hash]*types.Transaction)
var ( var (
// testKey is a private key to use for funding a tester account. // testKey is a private key to use for funding a tester account.
@ -110,7 +110,7 @@ func TestSimulatedBeaconSendWithdrawals(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error signing transaction, err=%v", err) t.Fatalf("error signing transaction, err=%v", err)
} }
txs[tx.Hash()] = *tx txs[tx.Hash()] = tx
if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil { if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil {
t.Fatal("SendTx failed", err) t.Fatal("SendTx failed", err)

View file

@ -289,6 +289,9 @@ func (d *Downloader) fetchBeaconHeaders(from uint64) error {
localHeaders = d.readHeaderRange(tail, int(count)) localHeaders = d.readHeaderRange(tail, int(count))
log.Warn("Retrieved beacon headers from local", "from", from, "count", count) log.Warn("Retrieved beacon headers from local", "from", from, "count", count)
} }
fsHeaderContCheckTimer := time.NewTimer(fsHeaderContCheck)
defer fsHeaderContCheckTimer.Stop()
for { for {
// Some beacon headers might have appeared since the last cycle, make // Some beacon headers might have appeared since the last cycle, make
// sure we're always syncing to all available ones // sure we're always syncing to all available ones
@ -381,8 +384,9 @@ func (d *Downloader) fetchBeaconHeaders(from uint64) error {
} }
// State sync still going, wait a bit for new headers and retry // State sync still going, wait a bit for new headers and retry
log.Trace("Pivot not yet committed, waiting...") log.Trace("Pivot not yet committed, waiting...")
fsHeaderContCheckTimer.Reset(fsHeaderContCheck)
select { select {
case <-time.After(fsHeaderContCheck): case <-fsHeaderContCheckTimer.C:
case <-d.cancelCh: case <-d.cancelCh:
return errCanceled return errCanceled
} }

View file

@ -1276,7 +1276,10 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
var ( var (
mode = d.getMode() mode = d.getMode()
gotHeaders = false // Wait for batches of headers to process gotHeaders = false // Wait for batches of headers to process
timer = time.NewTimer(time.Second)
) )
defer timer.Stop()
for { for {
select { select {
case <-d.cancelCh: case <-d.cancelCh:
@ -1397,10 +1400,11 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode
if mode == FullSync || mode == SnapSync { if mode == FullSync || mode == SnapSync {
// If we've reached the allowed number of pending headers, stall a bit // If we've reached the allowed number of pending headers, stall a bit
for d.queue.PendingBodies() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders { for d.queue.PendingBodies() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders {
timer.Reset(time.Second)
select { select {
case <-d.cancelCh: case <-d.cancelCh:
return errCanceled return errCanceled
case <-time.After(time.Second): case <-timer.C:
} }
} }
// Otherwise insert the headers for content retrieval // Otherwise insert the headers for content retrieval
@ -1567,7 +1571,10 @@ func (d *Downloader) processSnapSyncContent() error {
var ( var (
oldPivot *fetchResult // Locked in pivot block, might change eventually oldPivot *fetchResult // Locked in pivot block, might change eventually
oldTail []*fetchResult // Downloaded content after the pivot oldTail []*fetchResult // Downloaded content after the pivot
timer = time.NewTimer(time.Second)
) )
defer timer.Stop()
for { for {
// Wait for the next batch of downloaded data to be available. If we have // Wait for the next batch of downloaded data to be available. If we have
// not yet reached the pivot point, wait blockingly as there's no need to // not yet reached the pivot point, wait blockingly as there's no need to
@ -1650,6 +1657,7 @@ func (d *Downloader) processSnapSyncContent() error {
oldPivot = P oldPivot = P
} }
// Wait for completion, occasionally checking for pivot staleness // Wait for completion, occasionally checking for pivot staleness
timer.Reset(time.Second)
select { select {
case <-sync.done: case <-sync.done:
if sync.err != nil { if sync.err != nil {
@ -1660,7 +1668,7 @@ func (d *Downloader) processSnapSyncContent() error {
} }
oldPivot = nil oldPivot = nil
case <-time.After(time.Second): case <-timer.C:
oldTail = afterP oldTail = afterP
continue continue
} }

View file

@ -20,6 +20,7 @@ import (
"context" "context"
"errors" "errors"
"math/big" "math/big"
"slices"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/bloombits"
@ -347,16 +348,6 @@ func (f *Filter) pendingLogs() []*types.Log {
return nil return nil
} }
// includes returns true if the element is present in the list.
func includes[T comparable](things []T, element T) bool {
for _, thing := range things {
if thing == element {
return true
}
}
return false
}
// filterLogs creates a slice of logs matching the given criteria. // filterLogs creates a slice of logs matching the given criteria.
func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log { func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log {
var check = func(log *types.Log) bool { var check = func(log *types.Log) bool {
@ -366,7 +357,7 @@ func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []comm
if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
return false return false
} }
if len(addresses) > 0 && !includes(addresses, log.Address) { if len(addresses) > 0 && !slices.Contains(addresses, log.Address) {
return false return false
} }
// If the to filtered topics is greater than the amount of topics in logs, skip. // If the to filtered topics is greater than the amount of topics in logs, skip.
@ -377,7 +368,7 @@ func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []comm
if len(sub) == 0 { if len(sub) == 0 {
continue // empty rule set == wildcard continue // empty rule set == wildcard
} }
if !includes(sub, log.Topics[i]) { if !slices.Contains(sub, log.Topics[i]) {
return false return false
} }
} }

View file

@ -54,4 +54,9 @@ var (
// skipStorageHealingGauge is the metric to track how many storages are retrieved // skipStorageHealingGauge is the metric to track how many storages are retrieved
// in multiple requests but healing is not necessary. // in multiple requests but healing is not necessary.
skipStorageHealingGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/noheal", nil) skipStorageHealingGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/noheal", nil)
// largeStorageDiscardGauge is the metric to track how many chunked storages are
// discarded during the snap sync.
largeStorageDiscardGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/chunk/discard", nil)
largeStorageResumedGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/chunk/resume", nil)
) )

View file

@ -0,0 +1,154 @@
// 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 snap
import (
"encoding/json"
"testing"
"github.com/ethereum/go-ethereum/common"
)
// Legacy sync progress definitions
type legacyStorageTask struct {
Next common.Hash // Next account to sync in this interval
Last common.Hash // Last account to sync in this interval
}
type legacyAccountTask struct {
Next common.Hash // Next account to sync in this interval
Last common.Hash // Last account to sync in this interval
SubTasks map[common.Hash][]*legacyStorageTask // Storage intervals needing fetching for large contracts
}
type legacyProgress struct {
Tasks []*legacyAccountTask // The suspended account tasks (contract tasks within)
}
func compareProgress(a legacyProgress, b SyncProgress) bool {
if len(a.Tasks) != len(b.Tasks) {
return false
}
for i := 0; i < len(a.Tasks); i++ {
if a.Tasks[i].Next != b.Tasks[i].Next {
return false
}
if a.Tasks[i].Last != b.Tasks[i].Last {
return false
}
// new fields are not checked here
if len(a.Tasks[i].SubTasks) != len(b.Tasks[i].SubTasks) {
return false
}
for addrHash, subTasksA := range a.Tasks[i].SubTasks {
subTasksB, ok := b.Tasks[i].SubTasks[addrHash]
if !ok || len(subTasksB) != len(subTasksA) {
return false
}
for j := 0; j < len(subTasksA); j++ {
if subTasksA[j].Next != subTasksB[j].Next {
return false
}
if subTasksA[j].Last != subTasksB[j].Last {
return false
}
}
}
}
return true
}
func makeLegacyProgress() legacyProgress {
return legacyProgress{
Tasks: []*legacyAccountTask{
{
Next: common.Hash{},
Last: common.Hash{0x77},
SubTasks: map[common.Hash][]*legacyStorageTask{
common.Hash{0x1}: {
{
Next: common.Hash{},
Last: common.Hash{0xff},
},
},
},
},
{
Next: common.Hash{0x88},
Last: common.Hash{0xff},
},
},
}
}
func convertLegacy(legacy legacyProgress) SyncProgress {
var progress SyncProgress
for i, task := range legacy.Tasks {
subTasks := make(map[common.Hash][]*storageTask)
for owner, list := range task.SubTasks {
var cpy []*storageTask
for i := 0; i < len(list); i++ {
cpy = append(cpy, &storageTask{
Next: list[i].Next,
Last: list[i].Last,
})
}
subTasks[owner] = cpy
}
accountTask := &accountTask{
Next: task.Next,
Last: task.Last,
SubTasks: subTasks,
}
if i == 0 {
accountTask.StorageCompleted = []common.Hash{{0xaa}, {0xbb}} // fulfill new fields
}
progress.Tasks = append(progress.Tasks, accountTask)
}
return progress
}
func TestSyncProgressCompatibility(t *testing.T) {
// Decode serialized bytes of legacy progress, backward compatibility
legacy := makeLegacyProgress()
blob, err := json.Marshal(legacy)
if err != nil {
t.Fatalf("Failed to marshal progress %v", err)
}
var dec SyncProgress
if err := json.Unmarshal(blob, &dec); err != nil {
t.Fatalf("Failed to unmarshal progress %v", err)
}
if !compareProgress(legacy, dec) {
t.Fatal("sync progress is not backward compatible")
}
// Decode serialized bytes of new format progress
progress := convertLegacy(legacy)
blob, err = json.Marshal(progress)
if err != nil {
t.Fatalf("Failed to marshal progress %v", err)
}
var legacyDec legacyProgress
if err := json.Unmarshal(blob, &legacyDec); err != nil {
t.Fatalf("Failed to unmarshal progress %v", err)
}
if !compareProgress(legacyDec, progress) {
t.Fatal("sync progress is not forward compatible")
}
}

View file

@ -295,11 +295,19 @@ type bytecodeHealResponse struct {
// accountTask represents the sync task for a chunk of the account snapshot. // accountTask represents the sync task for a chunk of the account snapshot.
type accountTask struct { type accountTask struct {
// These fields get serialized to leveldb on shutdown // These fields get serialized to key-value store on shutdown
Next common.Hash // Next account to sync in this interval Next common.Hash // Next account to sync in this interval
Last common.Hash // Last account to sync in this interval Last common.Hash // Last account to sync in this interval
SubTasks map[common.Hash][]*storageTask // Storage intervals needing fetching for large contracts SubTasks map[common.Hash][]*storageTask // Storage intervals needing fetching for large contracts
// This is a list of account hashes whose storage are already completed
// in this cycle. This field is newly introduced in v1.14 and will be
// empty if the task is resolved from legacy progress data. Furthermore,
// this additional field will be ignored by legacy Geth. The only side
// effect is that these contracts might be resynced in the new cycle,
// retaining the legacy behavior.
StorageCompleted []common.Hash `json:",omitempty"`
// These fields are internals used during runtime // These fields are internals used during runtime
req *accountRequest // Pending request to fill this task req *accountRequest // Pending request to fill this task
res *accountResponse // Validate response filling this task res *accountResponse // Validate response filling this task
@ -311,6 +319,7 @@ type accountTask struct {
codeTasks map[common.Hash]struct{} // Code hashes that need retrieval codeTasks map[common.Hash]struct{} // Code hashes that need retrieval
stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval stateTasks map[common.Hash]common.Hash // Account hashes->roots that need full state retrieval
stateCompleted map[common.Hash]struct{} // Account hashes whose storage have been completed
genBatch ethdb.Batch // Batch used by the node generator genBatch ethdb.Batch // Batch used by the node generator
genTrie *trie.StackTrie // Node generator from storage slots genTrie *trie.StackTrie // Node generator from storage slots
@ -318,6 +327,30 @@ type accountTask struct {
done bool // Flag whether the task can be removed done bool // Flag whether the task can be removed
} }
// activeSubTasks returns the set of storage tasks covered by the current account
// range. Normally this would be the entire subTask set, but on a sync interrupt
// and later resume it can happen that a shorter account range is retrieved. This
// method ensures that we only start up the subtasks covered by the latest account
// response.
//
// Nil is returned if the account range is empty.
func (task *accountTask) activeSubTasks() map[common.Hash][]*storageTask {
if len(task.res.hashes) == 0 {
return nil
}
var (
tasks = make(map[common.Hash][]*storageTask)
last = task.res.hashes[len(task.res.hashes)-1]
)
for hash, subTasks := range task.SubTasks {
subTasks := subTasks // closure
if hash.Cmp(last) <= 0 {
tasks[hash] = subTasks
}
}
return tasks
}
// storageTask represents the sync task for a chunk of the storage snapshot. // storageTask represents the sync task for a chunk of the storage snapshot.
type storageTask struct { type storageTask struct {
Next common.Hash // Next account to sync in this interval Next common.Hash // Next account to sync in this interval
@ -745,6 +778,14 @@ func (s *Syncer) loadSyncStatus() {
for _, task := range s.tasks { for _, task := range s.tasks {
task := task // closure for task.genBatch in the stacktrie writer callback task := task // closure for task.genBatch in the stacktrie writer callback
// Restore the completed storages
task.stateCompleted = make(map[common.Hash]struct{})
for _, hash := range task.StorageCompleted {
task.stateCompleted[hash] = struct{}{}
}
task.StorageCompleted = nil
// Allocate batch for account trie generation
task.genBatch = ethdb.HookedBatch{ task.genBatch = ethdb.HookedBatch{
Batch: s.db.NewBatch(), Batch: s.db.NewBatch(),
OnPut: func(key []byte, value []byte) { OnPut: func(key []byte, value []byte) {
@ -767,6 +808,8 @@ func (s *Syncer) loadSyncStatus() {
options = options.WithSkipBoundary(task.Next != (common.Hash{}), task.Last != common.MaxHash, boundaryAccountNodesGauge) options = options.WithSkipBoundary(task.Next != (common.Hash{}), task.Last != common.MaxHash, boundaryAccountNodesGauge)
} }
task.genTrie = trie.NewStackTrie(options) task.genTrie = trie.NewStackTrie(options)
// Restore leftover storage tasks
for accountHash, subtasks := range task.SubTasks { for accountHash, subtasks := range task.SubTasks {
for _, subtask := range subtasks { for _, subtask := range subtasks {
subtask := subtask // closure for subtask.genBatch in the stacktrie writer callback subtask := subtask // closure for subtask.genBatch in the stacktrie writer callback
@ -865,6 +908,7 @@ func (s *Syncer) loadSyncStatus() {
Last: last, Last: last,
SubTasks: make(map[common.Hash][]*storageTask), SubTasks: make(map[common.Hash][]*storageTask),
genBatch: batch, genBatch: batch,
stateCompleted: make(map[common.Hash]struct{}),
genTrie: trie.NewStackTrie(options), genTrie: trie.NewStackTrie(options),
}) })
log.Debug("Created account sync task", "from", next, "last", last) log.Debug("Created account sync task", "from", next, "last", last)
@ -886,6 +930,14 @@ func (s *Syncer) saveSyncStatus() {
} }
} }
} }
// Save the account hashes of completed storage.
task.StorageCompleted = make([]common.Hash, 0, len(task.stateCompleted))
for hash := range task.stateCompleted {
task.StorageCompleted = append(task.StorageCompleted, hash)
}
if len(task.StorageCompleted) > 0 {
log.Debug("Leftover completed storages", "number", len(task.StorageCompleted), "next", task.Next, "last", task.Last)
}
} }
// Store the actual progress markers // Store the actual progress markers
progress := &SyncProgress{ progress := &SyncProgress{
@ -970,6 +1022,10 @@ func (s *Syncer) cleanStorageTasks() {
delete(task.SubTasks, account) delete(task.SubTasks, account)
task.pend-- task.pend--
// Mark the state as complete to prevent resyncing, regardless
// if state healing is necessary.
task.stateCompleted[account] = struct{}{}
// If this was the last pending task, forward the account task // If this was the last pending task, forward the account task
if task.pend == 0 { if task.pend == 0 {
s.forwardAccountTask(task) s.forwardAccountTask(task)
@ -1209,7 +1265,8 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st
continue continue
} }
// Skip tasks that are already retrieving (or done with) all small states // Skip tasks that are already retrieving (or done with) all small states
if len(task.SubTasks) == 0 && len(task.stateTasks) == 0 { storageTasks := task.activeSubTasks()
if len(storageTasks) == 0 && len(task.stateTasks) == 0 {
continue continue
} }
// Task pending retrieval, try to find an idle peer. If no such peer // Task pending retrieval, try to find an idle peer. If no such peer
@ -1253,7 +1310,7 @@ func (s *Syncer) assignStorageTasks(success chan *storageResponse, fail chan *st
roots = make([]common.Hash, 0, storageSets) roots = make([]common.Hash, 0, storageSets)
subtask *storageTask subtask *storageTask
) )
for account, subtasks := range task.SubTasks { for account, subtasks := range storageTasks {
for _, st := range subtasks { for _, st := range subtasks {
// Skip any subtasks already filling // Skip any subtasks already filling
if st.req != nil { if st.req != nil {
@ -1850,11 +1907,11 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
res.task.res = res res.task.res = res
// Ensure that the response doesn't overflow into the subsequent task // Ensure that the response doesn't overflow into the subsequent task
last := res.task.Last.Big() lastBig := res.task.Last.Big()
for i, hash := range res.hashes { for i, hash := range res.hashes {
// Mark the range complete if the last is already included. // Mark the range complete if the last is already included.
// Keep iteration to delete the extra states if exists. // Keep iteration to delete the extra states if exists.
cmp := hash.Big().Cmp(last) cmp := hash.Big().Cmp(lastBig)
if cmp == 0 { if cmp == 0 {
res.cont = false res.cont = false
continue continue
@ -1890,7 +1947,21 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
} }
// Check if the account is a contract with an unknown storage trie // Check if the account is a contract with an unknown storage trie
if account.Root != types.EmptyRootHash { if account.Root != types.EmptyRootHash {
// If the storage was already retrieved in the last cycle, there's no need
// to resync it again, regardless of whether the storage root is consistent
// or not.
if _, exist := res.task.stateCompleted[res.hashes[i]]; exist {
// The leftover storage tasks are not expected, unless system is
// very wrong.
if _, ok := res.task.SubTasks[res.hashes[i]]; ok {
panic(fmt.Errorf("unexpected leftover storage tasks, owner: %x", res.hashes[i]))
}
// Mark the healing tag if storage root node is inconsistent, or
// it's non-existent due to storage chunking.
if !rawdb.HasTrieNode(s.db, res.hashes[i], nil, account.Root, s.scheme) { if !rawdb.HasTrieNode(s.db, res.hashes[i], nil, account.Root, s.scheme) {
res.task.needHeal[i] = true
}
} else {
// If there was a previous large state retrieval in progress, // If there was a previous large state retrieval in progress,
// don't restart it from scratch. This happens if a sync cycle // don't restart it from scratch. This happens if a sync cycle
// is interrupted and resumed later. However, *do* update the // is interrupted and resumed later. However, *do* update the
@ -1902,7 +1973,12 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
} }
res.task.needHeal[i] = true res.task.needHeal[i] = true
resumed[res.hashes[i]] = struct{}{} resumed[res.hashes[i]] = struct{}{}
largeStorageResumedGauge.Inc(1)
} else { } else {
// It's possible that in the hash scheme, the storage, along
// with the trie nodes of the given root, is already present
// in the database. Schedule the storage task anyway to simplify
// the logic here.
res.task.stateTasks[res.hashes[i]] = account.Root res.task.stateTasks[res.hashes[i]] = account.Root
} }
res.task.needState[i] = true res.task.needState[i] = true
@ -1910,13 +1986,29 @@ func (s *Syncer) processAccountResponse(res *accountResponse) {
} }
} }
} }
// Delete any subtasks that have been aborted but not resumed. This may undo // Delete any subtasks that have been aborted but not resumed. It's essential
// some progress if a new peer gives us less accounts than an old one, but for // as the corresponding contract might be self-destructed in this cycle(it's
// now we have to live with that. // no longer possible in ethereum as self-destruction is disabled in Cancun
// Fork, but the condition is still necessary for other networks).
//
// Keep the leftover storage tasks if they are not covered by the responded
// account range which should be picked up in next account wave.
if len(res.hashes) > 0 {
// The hash of last delivered account in the response
last := res.hashes[len(res.hashes)-1]
for hash := range res.task.SubTasks { for hash := range res.task.SubTasks {
// TODO(rjl493456442) degrade the log level before merging.
if hash.Cmp(last) > 0 {
log.Info("Keeping suspended storage retrieval", "account", hash)
continue
}
// TODO(rjl493456442) degrade the log level before merging.
// It should never happen in ethereum.
if _, ok := resumed[hash]; !ok { if _, ok := resumed[hash]; !ok {
log.Debug("Aborting suspended storage retrieval", "account", hash) log.Error("Aborting suspended storage retrieval", "account", hash)
delete(res.task.SubTasks, hash) delete(res.task.SubTasks, hash)
largeStorageDiscardGauge.Inc(1)
}
} }
} }
// If the account range contained no contracts, or all have been fully filled // If the account range contained no contracts, or all have been fully filled
@ -2014,6 +2106,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) {
if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) {
res.mainTask.needState[j] = false res.mainTask.needState[j] = false
res.mainTask.pend-- res.mainTask.pend--
res.mainTask.stateCompleted[account] = struct{}{} // mark it as completed
smallStorageGauge.Inc(1) smallStorageGauge.Inc(1)
} }
// If the last contract was chunked, mark it as needing healing // If the last contract was chunked, mark it as needing healing
@ -2409,10 +2502,19 @@ func (s *Syncer) forwardAccountTask(task *accountTask) {
return return
} }
task.Next = incHash(hash) task.Next = incHash(hash)
// Remove the completion flag once the account range is pushed
// forward. The leftover accounts will be skipped in the next
// cycle.
delete(task.stateCompleted, hash)
} }
// All accounts marked as complete, track if the entire task is done // All accounts marked as complete, track if the entire task is done
task.done = !res.cont task.done = !res.cont
// Error out if there is any leftover completion flag.
if task.done && len(task.stateCompleted) != 0 {
panic(fmt.Errorf("storage completion flags should be emptied, %d left", len(task.stateCompleted)))
}
// Stack trie could have generated trie nodes, push them to disk (we need to // Stack trie could have generated trie nodes, push them to disk (we need to
// flush after finalizing task.done. It's fine even if we crash and lose this // flush after finalizing task.done. It's fine even if we crash and lose this
// write as it will only cause more data to be downloaded during heal. // write as it will only cause more data to be downloaded during heal.

View file

@ -57,7 +57,7 @@
"gas": "0x1a034", "gas": "0x1a034",
"init": "0x36600060003760406103e8366000600060095af26001556103e8516002556104085160035500" "init": "0x36600060003760406103e8366000600060095af26001556103e8516002556104085160035500"
}, },
"error": "out of gas", "error": "out of gas: not enough gas for reentrancy sentry",
"traceAddress": [], "traceAddress": [],
"subtraces": 1, "subtraces": 1,
"transactionPosition": 117, "transactionPosition": 117,

View file

@ -57,7 +57,7 @@
"gas": "0x19ee4", "gas": "0x19ee4",
"init": "0x5a600055600060006000f0505a60015500" "init": "0x5a600055600060006000f0505a60015500"
}, },
"error": "out of gas", "error": "out of gas: not enough gas for reentrancy sentry",
"traceAddress": [], "traceAddress": [],
"subtraces": 1, "subtraces": 1,
"transactionPosition": 63, "transactionPosition": 63,

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"slices"
"github.com/dop251/goja" "github.com/dop251/goja"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
@ -529,13 +530,7 @@ func (t *jsTracer) setBuiltinFunctions() {
vm.Interrupt(err) vm.Interrupt(err)
return false return false
} }
addr := common.BytesToAddress(a) return slices.Contains(t.activePrecompiles, common.BytesToAddress(a))
for _, p := range t.activePrecompiles {
if p == addr {
return true
}
}
return false
}) })
vm.Set("slice", func(slice goja.Value, start, end int64) goja.Value { vm.Set("slice", func(slice goja.Value, start, end int64) goja.Value {
b, err := t.fromBuf(vm, slice, false) b, err := t.fromBuf(vm, slice, false)

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"slices"
"strings" "strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -228,12 +229,7 @@ func (t *flatCallTracer) Stop(err error) {
// isPrecompiled returns whether the addr is a precompile. // isPrecompiled returns whether the addr is a precompile.
func (t *flatCallTracer) isPrecompiled(addr common.Address) bool { func (t *flatCallTracer) isPrecompiled(addr common.Address) bool {
for _, p := range t.activePrecompiles { return slices.Contains(t.activePrecompiles, addr)
if p == addr {
return true
}
}
return false
} }
func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) { func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) {

View file

@ -299,7 +299,7 @@ func testGetProofNonExistent(t *testing.T, client *rpc.Client) {
t.Fatalf("invalid nonce, want: %v got: %v", 0, result.Nonce) t.Fatalf("invalid nonce, want: %v got: %v", 0, result.Nonce)
} }
// test balance // test balance
if result.Balance.Cmp(big.NewInt(0)) != 0 { if result.Balance.Sign() != 0 {
t.Fatalf("invalid balance, want: %v got: %v", 0, result.Balance) t.Fatalf("invalid balance, want: %v got: %v", 0, result.Balance)
} }
// test storage // test storage

View file

@ -46,7 +46,7 @@ func WithCallGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethc
// 0 is not possible as a live Geth node would reject that due to DoS protection, // 0 is not possible as a live Geth node would reject that due to DoS protection,
// so the simulated backend will replicate that behavior for consistency. // so the simulated backend will replicate that behavior for consistency.
func WithMinerMinTip(tip *big.Int) func(nodeConf *node.Config, ethConf *ethconfig.Config) { func WithMinerMinTip(tip *big.Int) func(nodeConf *node.Config, ethConf *ethconfig.Config) {
if tip == nil || tip.Cmp(new(big.Int)) <= 0 { if tip == nil || tip.Sign() <= 0 {
panic("invalid miner minimum tip") panic("invalid miner minimum tip")
} }
return func(nodeConf *node.Config, ethConf *ethconfig.Config) { return func(nodeConf *node.Config, ethConf *ethconfig.Config) {

View file

@ -544,10 +544,13 @@ func (s *Service) reportLatency(conn *connWrapper) error {
return err return err
} }
// Wait for the pong request to arrive back // Wait for the pong request to arrive back
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
select { select {
case <-s.pongCh: case <-s.pongCh:
// Pong delivered, report the latency // Pong delivered, report the latency
case <-time.After(5 * time.Second): case <-timer.C:
// Ping timeout, abort // Ping timeout, abort
return errors.New("ping timed out") return errors.New("ping timed out")
} }

View file

@ -27,7 +27,6 @@ import (
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -112,7 +111,7 @@ func RunGit(args ...string) string {
// readGitFile returns content of file in .git directory. // readGitFile returns content of file in .git directory.
func readGitFile(file string) string { func readGitFile(file string) string {
content, err := os.ReadFile(path.Join(".git", file)) content, err := os.ReadFile(filepath.Join(".git", file))
if err != nil { if err != nil {
return "" return ""
} }
@ -175,7 +174,7 @@ func UploadSFTP(identityFile, host, dir string, files []string) error {
} }
in := io.MultiWriter(stdin, os.Stdout) in := io.MultiWriter(stdin, os.Stdout)
for _, f := range files { for _, f := range files {
fmt.Fprintln(in, "put", f, path.Join(dir, filepath.Base(f))) fmt.Fprintln(in, "put", f, filepath.Join(dir, filepath.Base(f)))
} }
fmt.Fprintln(in, "exit") fmt.Fprintln(in, "exit")
// Some issue with the PPA sftp server makes it so the server does not // Some issue with the PPA sftp server makes it so the server does not

View file

@ -24,7 +24,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"log/slog"
"os" "os"
"os/user" "os/user"
"path/filepath" "path/filepath"
@ -57,7 +56,7 @@ type HandlerT struct {
// Verbosity sets the log verbosity ceiling. The verbosity of individual packages // Verbosity sets the log verbosity ceiling. The verbosity of individual packages
// and source files can be raised using Vmodule. // and source files can be raised using Vmodule.
func (*HandlerT) Verbosity(level int) { func (*HandlerT) Verbosity(level int) {
glogger.Verbosity(slog.Level(level)) glogger.Verbosity(log.FromLegacyLevel(level))
} }
// Vmodule sets the log verbosity pattern. See package log for details on the // Vmodule sets the log verbosity pattern. See package log for details on the

View file

@ -231,9 +231,9 @@ func Setup(ctx *cli.Context) error {
case ctx.Bool(logjsonFlag.Name): case ctx.Bool(logjsonFlag.Name):
// Retain backwards compatibility with `--log.json` flag if `--log.format` not set // Retain backwards compatibility with `--log.json` flag if `--log.format` not set
defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead") defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead")
handler = log.JSONHandler(output) handler = log.JSONHandlerWithLevel(output, log.LevelInfo)
case logFmtFlag == "json": case logFmtFlag == "json":
handler = log.JSONHandler(output) handler = log.JSONHandlerWithLevel(output, log.LevelInfo)
case logFmtFlag == "logfmt": case logFmtFlag == "logfmt":
handler = log.LogfmtHandler(output) handler = log.LogfmtHandler(output)
case logFmtFlag == "", logFmtFlag == "terminal": case logFmtFlag == "", logFmtFlag == "terminal":

View file

@ -18,7 +18,6 @@ package era
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"math/big" "math/big"
@ -80,7 +79,7 @@ func (it *Iterator) Block() (*types.Block, error) {
// Receipts returns the receipts for the iterator's current position. // Receipts returns the receipts for the iterator's current position.
func (it *Iterator) Receipts() (types.Receipts, error) { func (it *Iterator) Receipts() (types.Receipts, error) {
if it.inner.Receipts == nil { if it.inner.Receipts == nil {
return nil, fmt.Errorf("receipts must be non-nil") return nil, errors.New("receipts must be non-nil")
} }
var receipts types.Receipts var receipts types.Receipts
err := rlp.Decode(it.inner.Receipts, &receipts) err := rlp.Decode(it.inner.Receipts, &receipts)

View file

@ -1497,7 +1497,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
} else { } else {
to = crypto.CreateAddress(args.from(), uint64(*args.Nonce)) to = crypto.CreateAddress(args.from(), uint64(*args.Nonce))
} }
isPostMerge := header.Difficulty.Cmp(common.Big0) == 0 isPostMerge := header.Difficulty.Sign() == 0
// Retrieve the precompiles since they don't need to be added to the access list // Retrieve the precompiles since they don't need to be added to the access list
precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time))
@ -1865,15 +1865,14 @@ type SignTransactionResult struct {
// The node needs to have the private key of the account corresponding with // The node needs to have the private key of the account corresponding with
// the given from address and it needs to be unlocked. // the given from address and it needs to be unlocked.
func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) { func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
args.blobSidecarAllowed = true
if args.Gas == nil { if args.Gas == nil {
return nil, errors.New("gas not specified") return nil, errors.New("gas not specified")
} }
if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) { if args.GasPrice == nil && (args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil) {
return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas") return nil, errors.New("missing gasPrice or maxFeePerGas/maxPriorityFeePerGas")
} }
if args.IsEIP4844() {
return nil, errBlobTxNotSupported
}
if args.Nonce == nil { if args.Nonce == nil {
return nil, errors.New("nonce not specified") return nil, errors.New("nonce not specified")
} }
@ -1889,6 +1888,16 @@ func (s *TransactionAPI) SignTransaction(ctx context.Context, args TransactionAr
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If the transaction-to-sign was a blob transaction, then the signed one
// no longer retains the blobs, only the blob hashes. In this step, we need
// to put back the blob(s).
if args.IsEIP4844() {
signed = signed.WithBlobTxSidecar(&types.BlobTxSidecar{
Blobs: args.Blobs,
Commitments: args.Commitments,
Proofs: args.Proofs,
})
}
data, err := signed.MarshalBinary() data, err := signed.MarshalBinary()
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -1037,11 +1037,8 @@ func TestSignBlobTransaction(t *testing.T) {
} }
_, err = api.SignTransaction(context.Background(), argsFromTransaction(res.Tx, b.acc.Address)) _, err = api.SignTransaction(context.Background(), argsFromTransaction(res.Tx, b.acc.Address))
if err == nil { if err != nil {
t.Fatalf("should fail on blob transaction") t.Fatalf("should not fail on blob transaction")
}
if !errors.Is(err, errBlobTxNotSupported) {
t.Errorf("error mismatch. Have: %v, want: %v", err, errBlobTxNotSupported)
} }
} }

View file

@ -97,7 +97,7 @@ func (args *TransactionArgs) data() []byte {
// setDefaults fills in default values for unspecified tx fields. // setDefaults fills in default values for unspecified tx fields.
func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGasEstimation bool) error { func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGasEstimation bool) error {
if err := args.setBlobTxSidecar(ctx, b); err != nil { if err := args.setBlobTxSidecar(ctx); err != nil {
return err return err
} }
if err := args.setFeeDefaults(ctx, b); err != nil { if err := args.setFeeDefaults(ctx, b); err != nil {
@ -290,7 +290,7 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ
} }
// setBlobTxSidecar adds the blob tx // setBlobTxSidecar adds the blob tx
func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context, b Backend) error { func (args *TransactionArgs) setBlobTxSidecar(ctx context.Context) error {
// No blobs, we're done. // No blobs, we're done.
if args.Blobs == nil { if args.Blobs == nil {
return nil return nil

View file

@ -18,7 +18,7 @@ package jsre
import ( import (
"os" "os"
"path" "path/filepath"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -42,7 +42,7 @@ func (no *testNativeObjectBinding) TestMethod(call goja.FunctionCall) goja.Value
func newWithTestJS(t *testing.T, testjs string) *JSRE { func newWithTestJS(t *testing.T, testjs string) *JSRE {
dir := t.TempDir() dir := t.TempDir()
if testjs != "" { if testjs != "" {
if err := os.WriteFile(path.Join(dir, "test.js"), []byte(testjs), os.ModePerm); err != nil { if err := os.WriteFile(filepath.Join(dir, "test.js"), []byte(testjs), os.ModePerm); err != nil {
t.Fatal("cannot create test.js:", err) t.Fatal("cannot create test.js:", err)
} }
} }

View file

@ -115,8 +115,15 @@ func (l *leveler) Level() slog.Level {
// JSONHandler returns a handler which prints records in JSON format. // JSONHandler returns a handler which prints records in JSON format.
func JSONHandler(wr io.Writer) slog.Handler { func JSONHandler(wr io.Writer) slog.Handler {
return JSONHandlerWithLevel(wr, levelMaxVerbosity)
}
// JSONHandlerWithLevel returns a handler which prints records in JSON format that are less than or equal to
// the specified verbosity level.
func JSONHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler {
return slog.NewJSONHandler(wr, &slog.HandlerOptions{ return slog.NewJSONHandler(wr, &slog.HandlerOptions{
ReplaceAttr: builtinReplaceJSON, ReplaceAttr: builtinReplaceJSON,
Level: &leveler{level},
}) })
} }

View file

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"maps"
"regexp" "regexp"
"runtime" "runtime"
"strconv" "strconv"
@ -145,10 +146,7 @@ func (h *GlogHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
h.lock.RLock() h.lock.RLock()
siteCache := make(map[uintptr]slog.Level) siteCache := maps.Clone(h.siteCache)
for k, v := range h.siteCache {
siteCache[k] = v
}
h.lock.RUnlock() h.lock.RUnlock()
patterns := []pattern{} patterns := []pattern{}

View file

@ -50,6 +50,25 @@ func TestTerminalHandlerWithAttrs(t *testing.T) {
} }
} }
// Make sure the default json handler outputs debug log lines
func TestJSONHandler(t *testing.T) {
out := new(bytes.Buffer)
handler := JSONHandler(out)
logger := slog.New(handler)
logger.Debug("hi there")
if len(out.String()) == 0 {
t.Error("expected non-empty debug log output from default JSON Handler")
}
out.Reset()
handler = JSONHandlerWithLevel(out, slog.LevelInfo)
logger = slog.New(handler)
logger.Debug("hi there")
if len(out.String()) != 0 {
t.Errorf("expected empty debug log output, but got: %v", out.String())
}
}
func BenchmarkTraceLogging(b *testing.B) { func BenchmarkTraceLogging(b *testing.B) {
SetDefault(NewLogger(NewTerminalHandler(os.Stderr, true))) SetDefault(NewLogger(NewTerminalHandler(os.Stderr, true)))
b.ResetTimer() b.ResetTimer()

View file

@ -75,7 +75,7 @@ type newPayloadResult struct {
receipts []*types.Receipt // Receipts collected during construction receipts []*types.Receipt // Receipts collected during construction
} }
// generateParams wraps various of settings for generating sealing task. // generateParams wraps various settings for generating sealing task.
type generateParams struct { type generateParams struct {
timestamp uint64 // The timestamp for sealing task timestamp uint64 // The timestamp for sealing task
forceTime bool // Flag whether the given timestamp is immutable or not forceTime bool // Flag whether the given timestamp is immutable or not
@ -131,7 +131,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
if genParams.parentHash != (common.Hash{}) { if genParams.parentHash != (common.Hash{}) {
block := miner.chain.GetBlockByHash(genParams.parentHash) block := miner.chain.GetBlockByHash(genParams.parentHash)
if block == nil { if block == nil {
return nil, fmt.Errorf("missing parent") return nil, errors.New("missing parent")
} }
parent = block.Header() parent = block.Header()
} }

View file

@ -25,6 +25,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"slices"
"strings" "strings"
"sync" "sync"
@ -278,16 +279,6 @@ func (n *Node) openEndpoints() error {
return err return err
} }
// containsLifecycle checks if 'lfs' contains 'l'.
func containsLifecycle(lfs []Lifecycle, l Lifecycle) bool {
for _, obj := range lfs {
if obj == l {
return true
}
}
return false
}
// stopServices terminates running services, RPC and p2p networking. // stopServices terminates running services, RPC and p2p networking.
// It is the inverse of Start. // It is the inverse of Start.
func (n *Node) stopServices(running []Lifecycle) error { func (n *Node) stopServices(running []Lifecycle) error {
@ -571,7 +562,7 @@ func (n *Node) RegisterLifecycle(lifecycle Lifecycle) {
if n.state != initializingState { if n.state != initializingState {
panic("can't register lifecycle on running/stopped node") panic("can't register lifecycle on running/stopped node")
} }
if containsLifecycle(n.lifecycles, lifecycle) { if slices.Contains(n.lifecycles, lifecycle) {
panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle)) panic(fmt.Sprintf("attempt to register lifecycle %T more than once", lifecycle))
} }
n.lifecycles = append(n.lifecycles, lifecycle) n.lifecycles = append(n.lifecycles, lifecycle)

View file

@ -22,7 +22,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"path" "path/filepath"
"testing" "testing"
"time" "time"
@ -98,7 +98,7 @@ func TestAuthEndpoints(t *testing.T) {
t.Fatalf("failed to create jwt secret: %v", err) t.Fatalf("failed to create jwt secret: %v", err)
} }
// Geth must read it from a file, and does not support in-memory JWT secrets, so we create a temporary file. // Geth must read it from a file, and does not support in-memory JWT secrets, so we create a temporary file.
jwtPath := path.Join(t.TempDir(), "jwt_secret") jwtPath := filepath.Join(t.TempDir(), "jwt_secret")
if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(secret[:])), 0600); err != nil { if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(secret[:])), 0600); err != nil {
t.Fatalf("failed to prepare jwt secret file: %v", err) t.Fatalf("failed to prepare jwt secret file: %v", err)
} }

View file

@ -23,6 +23,7 @@ import (
"net" "net"
"net/http" "net/http"
"reflect" "reflect"
"slices"
"strings" "strings"
"testing" "testing"
@ -116,7 +117,7 @@ func TestLifecycleRegistry_Successful(t *testing.T) {
noop := NewNoop() noop := NewNoop()
stack.RegisterLifecycle(noop) stack.RegisterLifecycle(noop)
if !containsLifecycle(stack.lifecycles, noop) { if !slices.Contains(stack.lifecycles, Lifecycle(noop)) {
t.Fatalf("lifecycle was not properly registered on the node, %v", err) t.Fatalf("lifecycle was not properly registered on the node, %v", err)
} }
} }

View file

@ -285,7 +285,7 @@ func (tn *preminedTestnet) neighborsAtDistances(base *enode.Node, distances []ui
for i := range lookupTestnet.dists[d] { for i := range lookupTestnet.dists[d] {
n := lookupTestnet.node(d, i) n := lookupTestnet.node(d, i)
d := enode.LogDist(base.ID(), n.ID()) d := enode.LogDist(base.ID(), n.ID())
if containsUint(uint(d), distances) { if slices.Contains(distances, uint(d)) {
result = append(result, n) result = append(result, n)
if len(result) >= elems { if len(result) >= elems {
return result return result

View file

@ -25,6 +25,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"slices"
"sync" "sync"
"time" "time"
@ -437,7 +438,7 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
} }
if distances != nil { if distances != nil {
nd := enode.LogDist(c.id, node.ID()) nd := enode.LogDist(c.id, node.ID())
if !containsUint(uint(nd), distances) { if !slices.Contains(distances, uint(nd)) {
return nil, errors.New("does not match any requested distance") return nil, errors.New("does not match any requested distance")
} }
} }
@ -448,15 +449,6 @@ func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, s
return node, nil return node, nil
} }
func containsUint(x uint, xs []uint) bool {
for _, v := range xs {
if x == v {
return true
}
}
return false
}
// callToNode sends the given call and sets up a handler for response packets (of message // callToNode sends the given call and sets up a handler for response packets (of message
// type responseType). Responses are dispatched to the call's response channel. // type responseType). Responses are dispatched to the call's response channel.
func (t *UDPv5) callToNode(n *enode.Node, responseType byte, req v5wire.Packet) *callV5 { func (t *UDPv5) callToNode(n *enode.Node, responseType byte, req v5wire.Packet) *callV5 {

View file

@ -215,7 +215,7 @@ func TestIteratorNodeUpdates(t *testing.T) {
// Ensure RandomNode returns the new nodes after the tree is updated. // Ensure RandomNode returns the new nodes after the tree is updated.
updateSomeNodes(keys, nodes) updateSomeNodes(keys, nodes)
tree2, _ := makeTestTree("n", nodes, nil) tree2, _ := makeTestTree("n", nodes, nil)
resolver.clear() clear(resolver)
resolver.add(tree2.ToTXT("n")) resolver.add(tree2.ToTXT("n"))
t.Log("tree updated") t.Log("tree updated")
@ -256,7 +256,7 @@ func TestIteratorRootRecheckOnFail(t *testing.T) {
// Ensure RandomNode returns the new nodes after the tree is updated. // Ensure RandomNode returns the new nodes after the tree is updated.
updateSomeNodes(keys, nodes) updateSomeNodes(keys, nodes)
tree2, _ := makeTestTree("n", nodes, nil) tree2, _ := makeTestTree("n", nodes, nil)
resolver.clear() clear(resolver)
resolver.add(tree2.ToTXT("n")) resolver.add(tree2.ToTXT("n"))
t.Log("tree updated") t.Log("tree updated")
@ -447,12 +447,6 @@ func newMapResolver(maps ...map[string]string) mapResolver {
return mr return mr
} }
func (mr mapResolver) clear() {
for k := range mr {
delete(mr, k)
}
}
func (mr mapResolver) add(m map[string]string) { func (mr mapResolver) add(m map[string]string) {
maps.Copy(mr, m) maps.Copy(mr, m)
} }

View file

@ -303,10 +303,13 @@ func (n *ExecNode) Stop() error {
go func() { go func() {
waitErr <- n.Cmd.Wait() waitErr <- n.Cmd.Wait()
}() }()
timer := time.NewTimer(5 * time.Second)
defer timer.Stop()
select { select {
case err := <-waitErr: case err := <-waitErr:
return err return err
case <-time.After(5 * time.Second): case <-timer.C:
return n.Cmd.Process.Kill() return n.Cmd.Process.Kill()
} }
} }

View file

@ -65,8 +65,13 @@ func startStop(net *Network, quit chan struct{}, nodeCount int) {
if err != nil { if err != nil {
panic("Could not startup node network for mocker") panic("Could not startup node network for mocker")
} }
tick := time.NewTicker(10 * time.Second) var (
tick = time.NewTicker(10 * time.Second)
timer = time.NewTimer(3 * time.Second)
)
defer tick.Stop() defer tick.Stop()
defer timer.Stop()
for { for {
select { select {
case <-quit: case <-quit:
@ -80,11 +85,12 @@ func startStop(net *Network, quit chan struct{}, nodeCount int) {
return return
} }
timer.Reset(3 * time.Second)
select { select {
case <-quit: case <-quit:
log.Info("Terminating simulation loop") log.Info("Terminating simulation loop")
return return
case <-time.After(3 * time.Second): case <-timer.C:
} }
log.Debug("starting node", "id", id) log.Debug("starting node", "id", id)

View file

@ -1028,11 +1028,14 @@ func (net *Network) Load(snap *Snapshot) error {
} }
} }
timeout := time.NewTimer(snapshotLoadTimeout)
defer timeout.Stop()
select { select {
// Wait until all connections from the snapshot are established. // Wait until all connections from the snapshot are established.
case <-allConnected: case <-allConnected:
// Make sure that we do not wait forever. // Make sure that we do not wait forever.
case <-time.After(snapshotLoadTimeout): case <-timeout.C:
return errors.New("snapshot connections not established") return errors.New("snapshot connections not established")
} }
return nil return nil

View file

@ -158,17 +158,17 @@ func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error)
switch { switch {
case typ == rawValueType: case typ == rawValueType:
return decodeRawValue, nil return decodeRawValue, nil
case typ.AssignableTo(reflect.PtrTo(bigInt)): case typ.AssignableTo(reflect.PointerTo(bigInt)):
return decodeBigInt, nil return decodeBigInt, nil
case typ.AssignableTo(bigInt): case typ.AssignableTo(bigInt):
return decodeBigIntNoPtr, nil return decodeBigIntNoPtr, nil
case typ == reflect.PtrTo(u256Int): case typ == reflect.PointerTo(u256Int):
return decodeU256, nil return decodeU256, nil
case typ == u256Int: case typ == u256Int:
return decodeU256NoPtr, nil return decodeU256NoPtr, nil
case kind == reflect.Ptr: case kind == reflect.Ptr:
return makePtrDecoder(typ, tags) return makePtrDecoder(typ, tags)
case reflect.PtrTo(typ).Implements(decoderInterface): case reflect.PointerTo(typ).Implements(decoderInterface):
return decodeDecoder, nil return decodeDecoder, nil
case isUint(kind): case isUint(kind):
return decodeUint, nil return decodeUint, nil
@ -262,7 +262,7 @@ func decodeU256(s *Stream, val reflect.Value) error {
func makeListDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) { func makeListDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) {
etype := typ.Elem() etype := typ.Elem()
if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) { if etype.Kind() == reflect.Uint8 && !reflect.PointerTo(etype).Implements(decoderInterface) {
if typ.Kind() == reflect.Array { if typ.Kind() == reflect.Array {
return decodeByteArray, nil return decodeByteArray, nil
} }
@ -474,7 +474,7 @@ func makeSimplePtrDecoder(etype reflect.Type, etypeinfo *typeinfo) decoder {
// //
// This decoder is used for pointer-typed struct fields with struct tag "nil". // This decoder is used for pointer-typed struct fields with struct tag "nil".
func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, ts rlpstruct.Tags) decoder { func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, ts rlpstruct.Tags) decoder {
typ := reflect.PtrTo(etype) typ := reflect.PointerTo(etype)
nilPtr := reflect.Zero(typ) nilPtr := reflect.Zero(typ)
// Determine the value kind that results in nil pointer. // Determine the value kind that results in nil pointer.

View file

@ -141,17 +141,17 @@ func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) {
switch { switch {
case typ == rawValueType: case typ == rawValueType:
return writeRawValue, nil return writeRawValue, nil
case typ.AssignableTo(reflect.PtrTo(bigInt)): case typ.AssignableTo(reflect.PointerTo(bigInt)):
return writeBigIntPtr, nil return writeBigIntPtr, nil
case typ.AssignableTo(bigInt): case typ.AssignableTo(bigInt):
return writeBigIntNoPtr, nil return writeBigIntNoPtr, nil
case typ == reflect.PtrTo(u256Int): case typ == reflect.PointerTo(u256Int):
return writeU256IntPtr, nil return writeU256IntPtr, nil
case typ == u256Int: case typ == u256Int:
return writeU256IntNoPtr, nil return writeU256IntNoPtr, nil
case kind == reflect.Ptr: case kind == reflect.Ptr:
return makePtrWriter(typ, ts) return makePtrWriter(typ, ts)
case reflect.PtrTo(typ).Implements(encoderInterface): case reflect.PointerTo(typ).Implements(encoderInterface):
return makeEncoderWriter(typ), nil return makeEncoderWriter(typ), nil
case isUint(kind): case isUint(kind):
return writeUint, nil return writeUint, nil

View file

@ -227,7 +227,7 @@ func isSubscriptionType(t reflect.Type) bool {
return t == subscriptionType return t == subscriptionType
} }
// isPubSub tests whether the given method has as as first argument a context.Context and // isPubSub tests whether the given method's first argument is a context.Context and
// returns the pair (Subscription, error). // returns the pair (Subscription, error).
func isPubSub(methodType reflect.Type) bool { func isPubSub(methodType reflect.Type) bool {
// numIn(0) is the receiver type // numIn(0) is the receiver type

View file

@ -590,7 +590,10 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA
return nil, err return nil, err
} }
// Convert fields into a real transaction // Convert fields into a real transaction
var unsignedTx = result.Transaction.ToTransaction() unsignedTx, err := result.Transaction.ToTransaction()
if err != nil {
return nil, err
}
// Get the password for the transaction // Get the password for the transaction
pw, err := api.lookupOrQueryPassword(acc.Address, "Account password", pw, err := api.lookupOrQueryPassword(acc.Address, "Account password",
fmt.Sprintf("Please enter the password for account %s", acc.Address.String())) fmt.Sprintf("Please enter the password for account %s", acc.Address.String()))

View file

@ -18,12 +18,14 @@ package apitypes
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"reflect" "reflect"
"regexp" "regexp"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -34,6 +36,8 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"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/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"
) )
var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Za-z](\w*)(\[\])?$`) var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Za-z](\w*)(\[\])?$`)
@ -92,12 +96,21 @@ type SendTxArgs struct {
// We accept "data" and "input" for backwards-compatibility reasons. // We accept "data" and "input" for backwards-compatibility reasons.
// "input" is the newer name and should be preferred by clients. // "input" is the newer name and should be preferred by clients.
// Issue detail: https://github.com/ethereum/go-ethereum/issues/15628 // Issue detail: https://github.com/ethereum/go-ethereum/issues/15628
Data *hexutil.Bytes `json:"data"` Data *hexutil.Bytes `json:"data,omitempty"`
Input *hexutil.Bytes `json:"input,omitempty"` Input *hexutil.Bytes `json:"input,omitempty"`
// For non-legacy transactions // For non-legacy transactions
AccessList *types.AccessList `json:"accessList,omitempty"` AccessList *types.AccessList `json:"accessList,omitempty"`
ChainID *hexutil.Big `json:"chainId,omitempty"` ChainID *hexutil.Big `json:"chainId,omitempty"`
// For BlobTxType
BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas,omitempty"`
BlobHashes []common.Hash `json:"blobVersionedHashes,omitempty"`
// For BlobTxType transactions with blob sidecar
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
Commitments []kzg4844.Commitment `json:"commitments,omitempty"`
Proofs []kzg4844.Proof `json:"proofs,omitempty"`
} }
func (args SendTxArgs) String() string { func (args SendTxArgs) String() string {
@ -108,24 +121,56 @@ func (args SendTxArgs) String() string {
return err.Error() return err.Error()
} }
// data retrieves the transaction calldata. Input field is preferred.
func (args *SendTxArgs) data() []byte {
if args.Input != nil {
return *args.Input
}
if args.Data != nil {
return *args.Data
}
return nil
}
// ToTransaction converts the arguments to a transaction. // ToTransaction converts the arguments to a transaction.
func (args *SendTxArgs) ToTransaction() *types.Transaction { func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) {
// Add the To-field, if specified // Add the To-field, if specified
var to *common.Address var to *common.Address
if args.To != nil { if args.To != nil {
dstAddr := args.To.Address() dstAddr := args.To.Address()
to = &dstAddr to = &dstAddr
} }
if err := args.validateTxSidecar(); err != nil {
var input []byte return nil, err
if args.Input != nil {
input = *args.Input
} else if args.Data != nil {
input = *args.Data
} }
var data types.TxData var data types.TxData
switch { switch {
case args.BlobHashes != nil:
al := types.AccessList{}
if args.AccessList != nil {
al = *args.AccessList
}
data = &types.BlobTx{
To: *to,
ChainID: uint256.MustFromBig((*big.Int)(args.ChainID)),
Nonce: uint64(args.Nonce),
Gas: uint64(args.Gas),
GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)),
GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)),
Value: uint256.MustFromBig((*big.Int)(&args.Value)),
Data: args.data(),
AccessList: al,
BlobHashes: args.BlobHashes,
BlobFeeCap: uint256.MustFromBig((*big.Int)(args.BlobFeeCap)),
}
if args.Blobs != nil {
data.(*types.BlobTx).Sidecar = &types.BlobTxSidecar{
Blobs: args.Blobs,
Commitments: args.Commitments,
Proofs: args.Proofs,
}
}
case args.MaxFeePerGas != nil: case args.MaxFeePerGas != nil:
al := types.AccessList{} al := types.AccessList{}
if args.AccessList != nil { if args.AccessList != nil {
@ -139,7 +184,7 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
GasFeeCap: (*big.Int)(args.MaxFeePerGas), GasFeeCap: (*big.Int)(args.MaxFeePerGas),
GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
Value: (*big.Int)(&args.Value), Value: (*big.Int)(&args.Value),
Data: input, Data: args.data(),
AccessList: al, AccessList: al,
} }
case args.AccessList != nil: case args.AccessList != nil:
@ -150,7 +195,7 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
Gas: uint64(args.Gas), Gas: uint64(args.Gas),
GasPrice: (*big.Int)(args.GasPrice), GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(&args.Value), Value: (*big.Int)(&args.Value),
Data: input, Data: args.data(),
AccessList: *args.AccessList, AccessList: *args.AccessList,
} }
default: default:
@ -160,10 +205,81 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
Gas: uint64(args.Gas), Gas: uint64(args.Gas),
GasPrice: (*big.Int)(args.GasPrice), GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(&args.Value), Value: (*big.Int)(&args.Value),
Data: input, Data: args.data(),
} }
} }
return types.NewTx(data)
return types.NewTx(data), nil
}
// validateTxSidecar validates blob data, if present
func (args *SendTxArgs) validateTxSidecar() error {
// No blobs, we're done.
if args.Blobs == nil {
return nil
}
n := len(args.Blobs)
// Assume user provides either only blobs (w/o hashes), or
// blobs together with commitments and proofs.
if args.Commitments == nil && args.Proofs != nil {
return errors.New(`blob proofs provided while commitments were not`)
} else if args.Commitments != nil && args.Proofs == nil {
return errors.New(`blob commitments provided while proofs were not`)
}
// len(blobs) == len(commitments) == len(proofs) == len(hashes)
if args.Commitments != nil && len(args.Commitments) != n {
return fmt.Errorf("number of blobs and commitments mismatch (have=%d, want=%d)", len(args.Commitments), n)
}
if args.Proofs != nil && len(args.Proofs) != n {
return fmt.Errorf("number of blobs and proofs mismatch (have=%d, want=%d)", len(args.Proofs), n)
}
if args.BlobHashes != nil && len(args.BlobHashes) != n {
return fmt.Errorf("number of blobs and hashes mismatch (have=%d, want=%d)", len(args.BlobHashes), n)
}
if args.Commitments == nil {
// Generate commitment and proof.
commitments := make([]kzg4844.Commitment, n)
proofs := make([]kzg4844.Proof, n)
for i, b := range args.Blobs {
c, err := kzg4844.BlobToCommitment(&b)
if err != nil {
return fmt.Errorf("blobs[%d]: error computing commitment: %v", i, err)
}
commitments[i] = c
p, err := kzg4844.ComputeBlobProof(&b, c)
if err != nil {
return fmt.Errorf("blobs[%d]: error computing proof: %v", i, err)
}
proofs[i] = p
}
args.Commitments = commitments
args.Proofs = proofs
} else {
for i, b := range args.Blobs {
if err := kzg4844.VerifyBlobProof(&b, args.Commitments[i], args.Proofs[i]); err != nil {
return fmt.Errorf("failed to verify blob proof: %v", err)
}
}
}
hashes := make([]common.Hash, n)
hasher := sha256.New()
for i, c := range args.Commitments {
hashes[i] = kzg4844.CalcBlobHashV1(hasher, &c)
}
if args.BlobHashes != nil {
for i, h := range hashes {
if h != args.BlobHashes[i] {
return fmt.Errorf("blob hash verification failed (have=%s, want=%s)", args.BlobHashes[i], h)
}
}
} else {
args.BlobHashes = hashes
}
return nil
} }
type SigFormat struct { type SigFormat struct {
@ -271,16 +387,8 @@ func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage
// Dependencies returns an array of custom types ordered by their hierarchical reference tree // Dependencies returns an array of custom types ordered by their hierarchical reference tree
func (typedData *TypedData) Dependencies(primaryType string, found []string) []string { func (typedData *TypedData) Dependencies(primaryType string, found []string) []string {
primaryType = strings.TrimSuffix(primaryType, "[]") primaryType = strings.TrimSuffix(primaryType, "[]")
includes := func(arr []string, str string) bool {
for _, obj := range arr {
if obj == str {
return true
}
}
return false
}
if includes(found, primaryType) { if slices.Contains(found, primaryType) {
return found return found
} }
if typedData.Types[primaryType] == nil { if typedData.Types[primaryType] == nil {
@ -289,7 +397,7 @@ func (typedData *TypedData) Dependencies(primaryType string, found []string) []s
found = append(found, primaryType) found = append(found, primaryType)
for _, field := range typedData.Types[primaryType] { for _, field := range typedData.Types[primaryType] {
for _, dep := range typedData.Dependencies(field.Type, found) { for _, dep := range typedData.Dependencies(field.Type, found) {
if !includes(found, dep) { if !slices.Contains(found, dep) {
found = append(found, dep) found = append(found, dep)
} }
} }

View file

@ -16,7 +16,16 @@
package apitypes package apitypes
import "testing" import (
"crypto/sha256"
"encoding/json"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"
)
func TestIsPrimitive(t *testing.T) { func TestIsPrimitive(t *testing.T) {
t.Parallel() t.Parallel()
@ -39,3 +48,96 @@ func TestIsPrimitive(t *testing.T) {
} }
} }
} }
func TestTxArgs(t *testing.T) {
for i, tc := range []struct {
data []byte
want common.Hash
wantType uint8
}{
{
data: []byte(`{"from":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","accessList":[],"blobVersionedHashes":["0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014"],"chainId":"0x7","gas":"0x124f8","gasPrice":"0x693d4ca8","input":"0x","maxFeePerBlobGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x0","r":"0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1","s":"0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14","to":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","type":"0x1","v":"0x0","value":"0x0","yParity":"0x0"}`),
want: common.HexToHash("0x7d53234acc11ac5b5948632c901a944694e228795782f511887d36fd76ff15c4"),
wantType: types.BlobTxType,
},
{
// on input, we don't read the type, but infer the type from the arguments present
data: []byte(`{"from":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","accessList":[],"chainId":"0x7","gas":"0x124f8","gasPrice":"0x693d4ca8","input":"0x","maxFeePerBlobGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x0","r":"0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1","s":"0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14","to":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","type":"0x12","v":"0x0","value":"0x0","yParity":"0x0"}`),
want: common.HexToHash("0x7919e2b0b9b543cb87a137b6ff66491ec7ae937cb88d3c29db4d9b28073dce53"),
wantType: types.DynamicFeeTxType,
},
} {
var txArgs SendTxArgs
if err := json.Unmarshal(tc.data, &txArgs); err != nil {
t.Fatal(err)
}
tx, err := txArgs.ToTransaction()
if err != nil {
t.Fatal(err)
}
if have := tx.Type(); have != tc.wantType {
t.Errorf("test %d, have type %d, want type %d", i, have, tc.wantType)
}
if have := tx.Hash(); have != tc.want {
t.Errorf("test %d: have %v, want %v", i, have, tc.want)
}
d2, err := json.Marshal(txArgs)
if err != nil {
t.Fatal(err)
}
var txArgs2 SendTxArgs
if err := json.Unmarshal(d2, &txArgs2); err != nil {
t.Fatal(err)
}
tx1, _ := txArgs.ToTransaction()
tx2, _ := txArgs2.ToTransaction()
if have, want := tx1.Hash(), tx2.Hash(); have != want {
t.Errorf("test %d: have %v, want %v", i, have, want)
}
}
/*
End to end testing:
$ go run ./cmd/clef --advanced --suppress-bootwarn
$ go run ./cmd/geth --nodiscover --maxpeers 0 --signer /home/user/.clef/clef.ipc console
> tx={"from":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","to":"0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425","gas":"0x124f8","maxFeePerGas":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","nonce":"0x0","input":"0x","accessList":[],"maxFeePerBlobGas":"0x3b9aca00","blobVersionedHashes":["0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014"]}
> eth.signTransaction(tx)
*/
}
func TestBlobTxs(t *testing.T) {
blob := kzg4844.Blob{0x1}
commitment, err := kzg4844.BlobToCommitment(&blob)
if err != nil {
t.Fatal(err)
}
proof, err := kzg4844.ComputeBlobProof(&blob, commitment)
if err != nil {
t.Fatal(err)
}
hash := kzg4844.CalcBlobHashV1(sha256.New(), &commitment)
b := &types.BlobTx{
ChainID: uint256.NewInt(6),
Nonce: 8,
GasTipCap: uint256.NewInt(500),
GasFeeCap: uint256.NewInt(600),
Gas: 21000,
BlobFeeCap: uint256.NewInt(700),
BlobHashes: []common.Hash{hash},
Value: uint256.NewInt(100),
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{blob},
Commitments: []kzg4844.Commitment{commitment},
Proofs: []kzg4844.Proof{proof},
},
}
tx := types.NewTx(b)
data, err := json.Marshal(tx)
if err != nil {
t.Fatal(err)
}
t.Logf("tx %v", string(data))
}

View file

@ -128,7 +128,7 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
fmt.Printf("chainid: %v\n", chainId) fmt.Printf("chainid: %v\n", chainId)
} }
if list := request.Transaction.AccessList; list != nil { if list := request.Transaction.AccessList; list != nil {
fmt.Printf("Accesslist\n") fmt.Printf("Accesslist:\n")
for i, el := range *list { for i, el := range *list {
fmt.Printf(" %d. %v\n", i, el.Address) fmt.Printf(" %d. %v\n", i, el.Address)
for j, slot := range el.StorageKeys { for j, slot := range el.StorageKeys {
@ -136,6 +136,12 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
} }
} }
} }
if len(request.Transaction.BlobHashes) > 0 {
fmt.Printf("Blob hashes:\n")
for _, bh := range request.Transaction.BlobHashes {
fmt.Printf(" %v\n", bh)
}
}
if request.Transaction.Data != nil { if request.Transaction.Data != nil {
d := *request.Transaction.Data d := *request.Transaction.Data
if len(d) > 0 { if len(d) > 0 {

View file

@ -23,7 +23,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
"path" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -385,7 +385,7 @@ func TestJsonFiles(t *testing.T) {
continue continue
} }
expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail") expectedFailure := strings.HasPrefix(fInfo.Name(), "expfail")
data, err := os.ReadFile(path.Join("testdata", fInfo.Name())) data, err := os.ReadFile(filepath.Join("testdata", fInfo.Name()))
if err != nil { if err != nil {
t.Errorf("Failed to read file %v: %v", fInfo.Name(), err) t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
continue continue
@ -411,14 +411,14 @@ func TestJsonFiles(t *testing.T) {
// crashes or hangs. // crashes or hangs.
func TestFuzzerFiles(t *testing.T) { func TestFuzzerFiles(t *testing.T) {
t.Parallel() t.Parallel()
corpusdir := path.Join("testdata", "fuzzing") corpusdir := filepath.Join("testdata", "fuzzing")
testfiles, err := os.ReadDir(corpusdir) testfiles, err := os.ReadDir(corpusdir)
if err != nil { if err != nil {
t.Fatalf("failed reading files: %v", err) t.Fatalf("failed reading files: %v", err)
} }
verbose := false verbose := false
for i, fInfo := range testfiles { for i, fInfo := range testfiles {
data, err := os.ReadFile(path.Join(corpusdir, fInfo.Name())) data, err := os.ReadFile(filepath.Join(corpusdir, fInfo.Name()))
if err != nil { if err != nil {
t.Errorf("Failed to read file %v: %v", fInfo.Name(), err) t.Errorf("Failed to read file %v: %v", fInfo.Name(), err)
continue continue

View file

@ -20,7 +20,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/ethereum/go-ethereum/signer/core/apitypes"
@ -36,6 +35,11 @@ func (db *Database) ValidateTransaction(selector *string, tx *apitypes.SendTxArg
if tx.Data != nil && tx.Input != nil && !bytes.Equal(*tx.Data, *tx.Input) { if tx.Data != nil && tx.Input != nil && !bytes.Equal(*tx.Data, *tx.Input) {
return nil, errors.New(`ambiguous request: both "data" and "input" are set and are not identical`) return nil, errors.New(`ambiguous request: both "data" and "input" are set and are not identical`)
} }
// ToTransaction validates, among other things, that blob hashes match with blobs, and also
// populates the hashes if they were previously unset.
if _, err := tx.ToTransaction(); err != nil {
return nil, err
}
// Place data on 'data', and nil 'input' // Place data on 'data', and nil 'input'
var data []byte var data []byte
if tx.Input != nil { if tx.Input != nil {
@ -52,7 +56,7 @@ func (db *Database) ValidateTransaction(selector *string, tx *apitypes.SendTxArg
// e.g. https://github.com/ethereum/go-ethereum/issues/16106. // e.g. https://github.com/ethereum/go-ethereum/issues/16106.
if len(data) == 0 { if len(data) == 0 {
// Prevent sending ether into black hole (show stopper) // Prevent sending ether into black hole (show stopper)
if tx.Value.ToInt().Cmp(big.NewInt(0)) > 0 { if tx.Value.ToInt().Sign() > 0 {
return nil, errors.New("transaction will create a contract with value but empty code") return nil, errors.New("transaction will create a contract with value but empty code")
} }
// No value submitted at least, critically Warn, but don't blow up // No value submitted at least, critically Warn, but don't blow up

View file

@ -64,6 +64,14 @@ func TestExecutionSpecBlocktests(t *testing.T) {
} }
bt := new(testMatcher) bt := new(testMatcher)
// These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we
// no longer delete "leftover storage" when deploying a contract.
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode_create_tx.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/self_destructing_initcode.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/recreate_self_destructed_contract_different_txs.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/delegatecall_from_new_contract_to_pre_existing_contract.json`)
bt.skipLoad(`^cancun/eip6780_selfdestruct/selfdestruct/create_selfdestruct_same_tx.json`)
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) { bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
execBlockTest(t, bt, test) execBlockTest(t, bt, test)
}) })

View file

@ -25,15 +25,15 @@ import (
) )
const ( const (
blsG1Add = byte(10) blsG1Add = byte(11)
blsG1Mul = byte(11) blsG1Mul = byte(12)
blsG1MultiExp = byte(12) blsG1MultiExp = byte(13)
blsG2Add = byte(13) blsG2Add = byte(14)
blsG2Mul = byte(14) blsG2Mul = byte(15)
blsG2MultiExp = byte(15) blsG2MultiExp = byte(16)
blsPairing = byte(16) blsPairing = byte(17)
blsMapG1 = byte(17) blsMapG1 = byte(18)
blsMapG2 = byte(18) blsMapG2 = byte(19)
) )
func checkInput(id byte, inputLen int) bool { func checkInput(id byte, inputLen int) bool {

View file

@ -54,9 +54,22 @@ func initMatcher(st *testMatcher) {
// Uses 1GB RAM per tested fork // Uses 1GB RAM per tested fork
st.skipLoad(`^stStaticCall/static_Call1MB`) st.skipLoad(`^stStaticCall/static_Call1MB`)
// These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we
// no longer delete "leftover storage" when deploying a contract.
st.skipLoad(`^stSStoreTest/InitCollision\.json`)
st.skipLoad(`^stRevertTest/RevertInCreateInInit\.json`)
st.skipLoad(`^stExtCodeHash/dynamicAccountOverwriteEmpty\.json`)
st.skipLoad(`^stCreate2/create2collisionStorage\.json`)
st.skipLoad(`^stCreate2/RevertInCreateInInitCreate2\.json`)
// Broken tests: // Broken tests:
// EOF is not part of cancun // EOF is not part of cancun
st.skipLoad(`^stEOF/`) st.skipLoad(`^stEOF/`)
// The tests under Pyspecs are the ones that are published as execution-spec tests.
// We run these tests separately, no need to _also_ run them as part of the
// reference tests.
st.skipLoad(`^Pyspecs/`)
} }
func TestState(t *testing.T) { func TestState(t *testing.T) {

View file

@ -207,8 +207,7 @@ func CodeSizeKey(address []byte) []byte {
func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) { func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) {
var ( var (
chunkOffset = new(uint256.Int).Add(codeOffset, chunk) chunkOffset = new(uint256.Int).Add(codeOffset, chunk)
treeIndex = new(uint256.Int).Div(chunkOffset, verkleNodeWidth) treeIndex, subIndexMod = new(uint256.Int).DivMod(chunkOffset, verkleNodeWidth, new(uint256.Int))
subIndexMod = new(uint256.Int).Mod(chunkOffset, verkleNodeWidth)
) )
var subIndex byte var subIndex byte
if len(subIndexMod) != 0 { if len(subIndexMod) != 0 {