From cf500264664c42c259be955961d54b385ffd5c02 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 5 Aug 2025 03:34:12 +0200 Subject: [PATCH 01/14] core/state: introduce the TransitionState object (verkle transition part 1) (#31634) This is the first part of #31532 It maintains a series of conversion maker which are to be updated by the conversion code (in a follow-up PR, this is a breakdown of a larger PR to make things easier to review). They can be used in this way: - During the conversion, by storing the conversion markers when the block has been processed. This is meant to be written in a function that isn't currently present, hence [this TODO](https://github.com/ethereum/go-ethereum/pull/31634/files#diff-89272f61e115723833d498a0acbe59fa2286e3dc7276a676a7f7816f21e248b7R384). Part of https://github.com/ethereum/go-ethereum/issues/31583 --------- Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Co-authored-by: Gary Rong --- core/chain_makers.go | 4 +- core/overlay/state_transition.go | 105 +++++++++++++++++++++++++++++++ core/rawdb/accessors_overlay.go | 30 +++++++++ core/rawdb/database.go | 2 +- core/rawdb/schema.go | 8 +++ core/state/database.go | 28 ++++++--- core/state/reader.go | 1 + core/verkle_witness_test.go | 6 +- 8 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 core/overlay/state_transition.go create mode 100644 core/rawdb/accessors_overlay.go diff --git a/core/chain_makers.go b/core/chain_makers.go index b2559495a1..af55716cca 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -540,8 +540,10 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine return block, b.receipts } + sdb := state.NewDatabase(trdb, nil) + for i := 0; i < n; i++ { - statedb, err := state.New(parent.Root(), state.NewDatabase(trdb, nil)) + statedb, err := state.New(parent.Root(), sdb) if err != nil { panic(err) } diff --git a/core/overlay/state_transition.go b/core/overlay/state_transition.go new file mode 100644 index 0000000000..90b5c9431a --- /dev/null +++ b/core/overlay/state_transition.go @@ -0,0 +1,105 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package overlay + +import ( + "bytes" + "encoding/gob" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// TransitionState is a structure that holds the progress markers of the +// translation process. +type TransitionState struct { + CurrentAccountAddress *common.Address // addresss of the last translated account + CurrentSlotHash common.Hash // hash of the last translated storage slot + CurrentPreimageOffset int64 // next byte to read from the preimage file + Started, Ended bool + + // Mark whether the storage for an account has been processed. This is useful if the + // maximum number of leaves of the conversion is reached before the whole storage is + // processed. + StorageProcessed bool + + BaseRoot common.Hash // hash of the last read-only MPT base tree +} + +// InTransition returns true if the translation process is in progress. +func (ts *TransitionState) InTransition() bool { + return ts != nil && ts.Started && !ts.Ended +} + +// Transitioned returns true if the translation process has been completed. +func (ts *TransitionState) Transitioned() bool { + return ts != nil && ts.Ended +} + +// Copy returns a deep copy of the TransitionState object. +func (ts *TransitionState) Copy() *TransitionState { + ret := &TransitionState{ + Started: ts.Started, + Ended: ts.Ended, + CurrentSlotHash: ts.CurrentSlotHash, + CurrentPreimageOffset: ts.CurrentPreimageOffset, + StorageProcessed: ts.StorageProcessed, + } + if ts.CurrentAccountAddress != nil { + addr := *ts.CurrentAccountAddress + ret.CurrentAccountAddress = &addr + } + return ret +} + +// LoadTransitionState retrieves the Verkle transition state associated with +// the given state root hash from the database. +func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle bool) *TransitionState { + var ts *TransitionState + + data, _ := rawdb.ReadVerkleTransitionState(db, root) + + // if a state could be read from the db, attempt to decode it + if len(data) > 0 { + var ( + newts TransitionState + buf = bytes.NewBuffer(data[:]) + dec = gob.NewDecoder(buf) + ) + // Decode transition state + err := dec.Decode(&newts) + if err != nil { + log.Error("failed to decode transition state", "err", err) + return nil + } + ts = &newts + } + + // Fallback that should only happen before the transition + if ts == nil { + // Initialize the first transition state, with the "ended" + // field set to true if the database was created + // as a verkle database. + log.Debug("no transition state found, starting fresh", "is verkle", db) + + // Start with a fresh state + ts = &TransitionState{Ended: isVerkle} + } + return ts +} diff --git a/core/rawdb/accessors_overlay.go b/core/rawdb/accessors_overlay.go new file mode 100644 index 0000000000..364cc889d1 --- /dev/null +++ b/core/rawdb/accessors_overlay.go @@ -0,0 +1,30 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +func ReadVerkleTransitionState(db ethdb.KeyValueReader, hash common.Hash) ([]byte, error) { + return db.Get(transitionStateKey(hash)) +} + +func WriteVerkleTransitionState(db ethdb.KeyValueWriter, hash common.Hash, state []byte) error { + return db.Put(transitionStateKey(hash), state) +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 2ebdf360b5..25cd20d164 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -604,7 +604,7 @@ var knownMetadataKeys = [][]byte{ snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey, persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey, - filterMapsRangeKey, headStateHistoryIndexKey, + filterMapsRangeKey, headStateHistoryIndexKey, VerkleTransitionStatePrefix, } // printChainMetadata prints out chain metadata to stderr. diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 388a08f243..72f9bd34ec 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -158,6 +158,9 @@ var ( preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil) + + // Verkle transition information + VerkleTransitionStatePrefix = []byte("verkle-transition-state-") ) // LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary @@ -397,3 +400,8 @@ func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Has binary.BigEndian.PutUint32(buf[:], blockID) return append(append(append(StateHistoryStorageBlockPrefix, addressHash.Bytes()...), storageHash.Bytes()...), buf[:]...) } + +// transitionStateKey = transitionStatusKey + hash +func transitionStateKey(hash common.Hash) []byte { + return append(VerkleTransitionStatePrefix, hash.Bytes()...) +} diff --git a/core/state/database.go b/core/state/database.go index 5fb198a629..b46e5d500d 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" @@ -151,17 +152,21 @@ type CachingDB struct { codeCache *lru.SizeConstrainedCache[common.Hash, []byte] codeSizeCache *lru.Cache[common.Hash, int] pointCache *utils.PointCache + + // Transition-specific fields + TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState] } // NewDatabase creates a state database with the provided data sources. func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB { return &CachingDB{ - disk: triedb.Disk(), - triedb: triedb, - snap: snap, - codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), - pointCache: utils.NewPointCache(pointCacheSize), + disk: triedb.Disk(), + triedb: triedb, + snap: snap, + codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), + codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), + pointCache: utils.NewPointCache(pointCacheSize), + TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000), } } @@ -224,7 +229,13 @@ func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithSta // OpenTrie opens the main account trie at a specific root hash. func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { - return trie.NewVerkleTrie(root, db.triedb, db.pointCache) + ts := overlay.LoadTransitionState(db.TrieDB().Disk(), root, db.triedb.IsVerkle()) + if ts.InTransition() { + panic("transition isn't supported yet") + } + if ts.Transitioned() { + return trie.NewVerkleTrie(root, db.triedb, db.pointCache) + } } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { @@ -235,9 +246,6 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { // OpenStorageTrie opens the storage trie of an account. func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { - // In the verkle case, there is only one tree. But the two-tree structure - // is hardcoded in the codebase. So we need to return the same trie in this - // case. if db.triedb.IsVerkle() { return self, nil } diff --git a/core/state/reader.go b/core/state/reader.go index 4628f4d5db..f56a1bfae1 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -241,6 +241,7 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach if !db.IsVerkle() { tr, err = trie.NewStateTrie(trie.StateTrieID(root), db) } else { + // TODO @gballet determine the trie type (verkle or overlay) by transition state tr, err = trie.NewVerkleTrie(root, db, cache) } if err != nil { diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go index a89672e6e5..e200bf7f50 100644 --- a/core/verkle_witness_test.go +++ b/core/verkle_witness_test.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "fmt" "math/big" "slices" "testing" @@ -202,12 +203,15 @@ func TestProcessVerkle(t *testing.T) { t.Log("verified verkle proof, inserting blocks into the chain") + for i, b := range chain { + fmt.Printf("%d %x\n", i, b.Root()) + } endnum, err := blockchain.InsertChain(chain) if err != nil { t.Fatalf("block %d imported with error: %v", endnum, err) } - for i := 0; i < 2; i++ { + for i := range 2 { b := blockchain.GetBlockByNumber(uint64(i) + 1) if b == nil { t.Fatalf("expected block %d to be present in chain", i+1) From e9dca3b1810054052a7186a4d3e9a3f52d25c3fe Mon Sep 17 00:00:00 2001 From: maskpp Date: Tue, 5 Aug 2025 13:07:45 +0800 Subject: [PATCH 02/14] eth/catalyst: avoid load the same blob tx multi times (#32190) - If all the `vhashes` are in the same `sidecar`, then it will load the same blob tx many times. This PR aims to upgrade this. --------- Co-authored-by: Gary Rong --- core/txpool/blobpool/blobpool.go | 103 ++++++++--- core/txpool/blobpool/blobpool_test.go | 242 ++++++++++++++++++++++---- eth/catalyst/api.go | 85 +++------ 3 files changed, 306 insertions(+), 124 deletions(-) diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 078af34864..948ecd14c3 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -1299,32 +1300,86 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata { // GetBlobs returns a number of blobs and proofs for the given versioned hashes. // This is a utility method for the engine API, enabling consensus clients to // retrieve blobs from the pools directly instead of the network. -func (p *BlobPool) GetBlobs(vhashes []common.Hash) []*types.BlobTxSidecar { - sidecars := make([]*types.BlobTxSidecar, len(vhashes)) - for idx, vhash := range vhashes { - // Retrieve the datastore item (in a short lock) - p.lock.RLock() - id, exists := p.lookup.storeidOfBlob(vhash) - if !exists { - p.lock.RUnlock() - continue - } - data, err := p.store.Get(id) - p.lock.RUnlock() +func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) { + var ( + blobs = make([]*kzg4844.Blob, len(vhashes)) + commitments = make([]kzg4844.Commitment, len(vhashes)) + proofs = make([][]kzg4844.Proof, len(vhashes)) - // After releasing the lock, try to fill any blobs requested - if err != nil { - log.Error("Tracked blob transaction missing from store", "id", id, "err", err) - continue - } - item := new(types.Transaction) - if err = rlp.DecodeBytes(data, item); err != nil { - log.Error("Blobs corrupted for traced transaction", "id", id, "err", err) - continue - } - sidecars[idx] = item.BlobTxSidecar() + indices = make(map[common.Hash][]int) + filled = make(map[common.Hash]struct{}) + ) + for i, h := range vhashes { + indices[h] = append(indices[h], i) } - return sidecars + for _, vhash := range vhashes { + // Skip duplicate vhash that was already resolved in a previous iteration + if _, ok := filled[vhash]; ok { + continue + } + // Retrieve the corresponding blob tx with the vhash + p.lock.RLock() + txID, exists := p.lookup.storeidOfBlob(vhash) + p.lock.RUnlock() + if !exists { + return nil, nil, nil, fmt.Errorf("blob with vhash %x is not found", vhash) + } + data, err := p.store.Get(txID) + if err != nil { + return nil, nil, nil, err + } + + // Decode the blob transaction + tx := new(types.Transaction) + if err := rlp.DecodeBytes(data, tx); err != nil { + return nil, nil, nil, err + } + sidecar := tx.BlobTxSidecar() + if sidecar == nil { + return nil, nil, nil, fmt.Errorf("blob tx without sidecar %x", tx.Hash()) + } + // Traverse the blobs in the transaction + for i, hash := range tx.BlobHashes() { + list, ok := indices[hash] + if !ok { + continue // non-interesting blob + } + var pf []kzg4844.Proof + switch version { + case types.BlobSidecarVersion0: + if sidecar.Version == types.BlobSidecarVersion0 { + pf = []kzg4844.Proof{sidecar.Proofs[i]} + } else { + proof, err := kzg4844.ComputeBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i]) + if err != nil { + return nil, nil, nil, err + } + pf = []kzg4844.Proof{proof} + } + case types.BlobSidecarVersion1: + if sidecar.Version == types.BlobSidecarVersion0 { + cellProofs, err := kzg4844.ComputeCellProofs(&sidecar.Blobs[i]) + if err != nil { + return nil, nil, nil, err + } + pf = cellProofs + } else { + cellProofs, err := sidecar.CellProofsAt(i) + if err != nil { + return nil, nil, nil, err + } + pf = cellProofs + } + } + for _, index := range list { + blobs[index] = &sidecar.Blobs[i] + commitments[index] = sidecar.Commitments[i] + proofs[index] = pf + } + filled[hash] = struct{}{} + } + } + return blobs, commitments, proofs, nil } // AvailableBlobs returns the number of blobs that are available in the subpool. diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 422c35f6d2..55eed86cff 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -26,6 +26,7 @@ import ( "math/big" "os" "path/filepath" + "reflect" "sync" "testing" @@ -50,6 +51,7 @@ var ( testBlobCommits []kzg4844.Commitment testBlobProofs []kzg4844.Proof testBlobVHashes [][32]byte + testBlobIndices = make(map[[32]byte]int) ) const testMaxBlobsPerBlock = 6 @@ -66,6 +68,7 @@ func init() { testBlobProofs = append(testBlobProofs, testBlobProof) testBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &testBlobCommit) + testBlobIndices[testBlobVHash] = len(testBlobVHashes) testBlobVHashes = append(testBlobVHashes, testBlobVHash) } } @@ -216,7 +219,7 @@ func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, // makeMultiBlobTx is a utility method to construct a ramdom blob tx with // certain number of blobs in its sidecar. -func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, key *ecdsa.PrivateKey) *types.Transaction { +func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, blobOffset int, key *ecdsa.PrivateKey, version byte) *types.Transaction { var ( blobs []kzg4844.Blob blobHashes []common.Hash @@ -224,10 +227,15 @@ func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCa proofs []kzg4844.Proof ) for i := 0; i < blobCount; i++ { - blobs = append(blobs, *testBlobs[i]) - commitments = append(commitments, testBlobCommits[i]) - proofs = append(proofs, testBlobProofs[i]) - blobHashes = append(blobHashes, testBlobVHashes[i]) + blobs = append(blobs, *testBlobs[blobOffset+i]) + commitments = append(commitments, testBlobCommits[blobOffset+i]) + if version == types.BlobSidecarVersion0 { + proofs = append(proofs, testBlobProofs[blobOffset+i]) + } else { + cellProofs, _ := kzg4844.ComputeCellProofs(testBlobs[blobOffset+i]) + proofs = append(proofs, cellProofs...) + } + blobHashes = append(blobHashes, testBlobVHashes[blobOffset+i]) } blobtx := &types.BlobTx{ ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID), @@ -238,7 +246,7 @@ func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCa BlobFeeCap: uint256.NewInt(blobFeeCap), BlobHashes: blobHashes, Value: uint256.NewInt(100), - Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion0, blobs, commitments, proofs), + Sidecar: types.NewBlobTxSidecar(version, blobs, commitments, proofs), } return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) } @@ -396,35 +404,21 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) { // whatever is in the pool, it can be retrieved correctly. func verifyBlobRetrievals(t *testing.T, pool *BlobPool) { // Collect all the blobs tracked by the pool - known := make(map[common.Hash]struct{}) + var ( + hashes []common.Hash + known = make(map[common.Hash]struct{}) + ) for _, txs := range pool.index { for _, tx := range txs { for _, vhash := range tx.vhashes { known[vhash] = struct{}{} } + hashes = append(hashes, tx.vhashes...) } } - // Attempt to retrieve all test blobs - hashes := make([]common.Hash, len(testBlobVHashes)) - for i := range testBlobVHashes { - copy(hashes[i][:], testBlobVHashes[i][:]) - } - sidecars := pool.GetBlobs(hashes) - var blobs []*kzg4844.Blob - var proofs []*kzg4844.Proof - for idx, sidecar := range sidecars { - if sidecar == nil { - blobs = append(blobs, nil) - proofs = append(proofs, nil) - continue - } - blobHashes := sidecar.BlobHashes() - for i, hash := range blobHashes { - if hash == hashes[idx] { - blobs = append(blobs, &sidecar.Blobs[i]) - proofs = append(proofs, &sidecar.Proofs[i]) - } - } + blobs, _, proofs, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0) + if err != nil { + t.Fatal(err) } // Cross validate what we received vs what we wanted if len(blobs) != len(hashes) || len(proofs) != len(hashes) { @@ -434,13 +428,12 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) { for i, hash := range hashes { // If an item is missing, but shouldn't, error if blobs[i] == nil || proofs[i] == nil { - if _, ok := known[hash]; ok { - t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) - } + t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) continue } // Item retrieved, make sure it matches the expectation - if *blobs[i] != *testBlobs[i] || *proofs[i] != testBlobProofs[i] { + index := testBlobIndices[hash] + if *blobs[i] != *testBlobs[index] || proofs[i][0] != testBlobProofs[index] { t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash) continue } @@ -1071,9 +1064,9 @@ func TestChangingSlotterSize(t *testing.T) { addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr3 = crypto.PubkeyToAddress(key3.PublicKey) - tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, key1) - tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, key2) - tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, key3) + tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0) + tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 0, key2, types.BlobSidecarVersion0) + tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, 0, key3, types.BlobSidecarVersion0) blob1, _ = rlp.EncodeToBytes(tx1) blob2, _ = rlp.EncodeToBytes(tx2) @@ -1191,8 +1184,8 @@ func TestBlobCountLimit(t *testing.T) { // Attempt to add transactions. var ( - tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, key1) - tx2 = makeMultiBlobTx(0, 1, 800, 70, 7, key2) + tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0) + tx2 = makeMultiBlobTx(0, 1, 800, 70, 7, 0, key2, types.BlobSidecarVersion0) ) errs := pool.Add([]*types.Transaction{tx1, tx2}, true) @@ -1675,6 +1668,181 @@ func TestAdd(t *testing.T) { } } +func TestGetBlobs(t *testing.T) { + //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + + // Create a temporary folder for the persistent backend + storage := t.TempDir() + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(params.BlobTxMaxBlobs), nil) + + // Create transactions from a few accounts. + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + key3, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + + tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0) // [0, 6) + tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion1) // [6, 12) + tx3 = makeMultiBlobTx(0, 1, 800, 110, 6, 12, key3, types.BlobSidecarVersion0) // [12, 18) + + blob1, _ = rlp.EncodeToBytes(tx1) + blob2, _ = rlp.EncodeToBytes(tx2) + blob3, _ = rlp.EncodeToBytes(tx3) + ) + + // Write the two safely sized txs to store. note: although the store is + // configured for a blob count of 6, it can also support around ~1mb of call + // data - all this to say that we aren't using the the absolute largest shelf + // available. + store.Put(blob1) + store.Put(blob2) + store.Put(blob3) + store.Close() + + // Mimic a blobpool with max blob count of 6 upgrading to a max blob count of 24. + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true, false) + + // Make custom chain config where the max blob count changes based on the loop variable. + cancunTime := uint64(0) + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + LondonBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CancunTime: &cancunTime, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: ¶ms.BlobConfig{ + Target: 12, + Max: 24, + UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction, + }, + }, + } + chain := &testBlockChain{ + config: config, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain, nil) + if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + + // Verify the regular three txs are always available. + if got := pool.Get(tx1.Hash()); got == nil { + t.Errorf("expected tx %s from %s in pool", tx1.Hash(), addr1) + } + if got := pool.Get(tx2.Hash()); got == nil { + t.Errorf("expected tx %s from %s in pool", tx2.Hash(), addr2) + } + if got := pool.Get(tx3.Hash()); got == nil { + t.Errorf("expected tx %s from %s in pool", tx3.Hash(), addr3) + } + + cases := []struct { + start int + limit int + version byte + expErr bool + }{ + { + start: 0, limit: 6, + version: types.BlobSidecarVersion0, + }, + { + start: 0, limit: 6, + version: types.BlobSidecarVersion1, + }, + { + start: 3, limit: 9, + version: types.BlobSidecarVersion0, + }, + { + start: 3, limit: 9, + version: types.BlobSidecarVersion1, + }, + { + start: 3, limit: 15, + version: types.BlobSidecarVersion0, + }, + { + start: 3, limit: 15, + version: types.BlobSidecarVersion1, + }, + { + start: 0, limit: 18, + version: types.BlobSidecarVersion0, + }, + { + start: 0, limit: 18, + version: types.BlobSidecarVersion1, + }, + { + start: 18, limit: 20, + version: types.BlobSidecarVersion0, + expErr: true, + }, + } + for i, c := range cases { + var vhashes []common.Hash + for j := c.start; j < c.limit; j++ { + vhashes = append(vhashes, testBlobVHashes[j]) + } + blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version) + + if c.expErr { + if err == nil { + t.Errorf("Unexpected return, want error for case %d", i) + } + } else { + if err != nil { + t.Errorf("Unexpected error for case %d, %v", i, err) + } + // Cross validate what we received vs what we wanted + length := c.limit - c.start + if len(blobs) != length || len(proofs) != length { + t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), length) + continue + } + for j := 0; j < len(blobs); j++ { + // If an item is missing, but shouldn't, error + if blobs[j] == nil || proofs[j] == nil { + t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j]) + continue + } + // Item retrieved, make sure the blob matches the expectation + if *blobs[j] != *testBlobs[c.start+j] { + t.Errorf("retrieved blob mismatch: item %d, hash %x", j, vhashes[j]) + continue + } + // Item retrieved, make sure the proof matches the expectation + if c.version == types.BlobSidecarVersion0 { + if proofs[j][0] != testBlobProofs[c.start+j] { + t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j]) + } + } else { + want, _ := kzg4844.ComputeCellProofs(blobs[j]) + if !reflect.DeepEqual(want, proofs[j]) { + t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j]) + } + } + } + } + } + + pool.Close() +} + // fakeBilly is a billy.Database implementation which just drops data on the floor. type fakeBilly struct { billy.Database diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index f91896cc6e..038328d9ba 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -18,7 +18,6 @@ package catalyst import ( - "crypto/sha256" "errors" "fmt" "strconv" @@ -31,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/version" @@ -120,10 +118,13 @@ var caps = []string{ var ( // Number of blobs requested via getBlobsV2 getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil) + // Number of blobs requested via getBlobsV2 that are present in the blobpool getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil) + // Number of times getBlobsV2 responded with “hit” getBlobsV2RequestHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil) + // Number of times getBlobsV2 responded with “miss” getBlobsV2RequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil) ) @@ -494,29 +495,15 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo if len(hashes) > 128 { return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes))) } - var ( - res = make([]*engine.BlobAndProofV1, len(hashes)) - hasher = sha256.New() - index = make(map[common.Hash]int) - sidecars = api.eth.BlobTxPool().GetBlobs(hashes) - ) - - for i, hash := range hashes { - index[hash] = i + blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion0) + if err != nil { + return nil, engine.InvalidParams.With(err) } - for i, sidecar := range sidecars { - if res[i] != nil || sidecar == nil { - // already filled - continue - } - for cIdx, commitment := range sidecar.Commitments { - computed := kzg4844.CalcBlobHashV1(hasher, &commitment) - if idx, ok := index[computed]; ok { - res[idx] = &engine.BlobAndProofV1{ - Blob: sidecar.Blobs[cIdx][:], - Proof: sidecar.Proofs[cIdx][:], - } - } + res := make([]*engine.BlobAndProofV1, len(hashes)) + for i := 0; i < len(blobs); i++ { + res[i] = &engine.BlobAndProofV1{ + Blob: blobs[i][:], + Proof: proofs[i][0][:], } } return res, nil @@ -538,47 +525,19 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo } getBlobsV2RequestHit.Inc(1) - // pull up the blob hashes - var ( - res = make([]*engine.BlobAndProofV2, len(hashes)) - index = make(map[common.Hash][]int) - sidecars = api.eth.BlobTxPool().GetBlobs(hashes) - ) - - for i, hash := range hashes { - index[hash] = append(index[hash], i) + blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1) + if err != nil { + return nil, engine.InvalidParams.With(err) } - for i, sidecar := range sidecars { - if res[i] != nil { - // already filled - continue + res := make([]*engine.BlobAndProofV2, len(hashes)) + for i := 0; i < len(blobs); i++ { + var cellProofs []hexutil.Bytes + for _, proof := range proofs[i] { + cellProofs = append(cellProofs, proof[:]) } - if sidecar == nil { - // not found, return empty response - return nil, nil - } - if sidecar.Version != types.BlobSidecarVersion1 { - log.Info("GetBlobs queried V0 transaction: index %v, blobhashes %v", index, sidecar.BlobHashes()) - return nil, nil - } - blobHashes := sidecar.BlobHashes() - for bIdx, hash := range blobHashes { - if idxes, ok := index[hash]; ok { - proofs, err := sidecar.CellProofsAt(bIdx) - if err != nil { - return nil, engine.InvalidParams.With(err) - } - var cellProofs []hexutil.Bytes - for _, proof := range proofs { - cellProofs = append(cellProofs, proof[:]) - } - for _, idx := range idxes { - res[idx] = &engine.BlobAndProofV2{ - Blob: sidecar.Blobs[bIdx][:], - CellProofs: cellProofs, - } - } - } + res[i] = &engine.BlobAndProofV2{ + Blob: blobs[i][:], + CellProofs: cellProofs, } } return res, nil From 59405c40d394552f1915caedd5f43d971c7ba1fd Mon Sep 17 00:00:00 2001 From: Minhyuk Kim Date: Tue, 5 Aug 2025 20:51:16 +0900 Subject: [PATCH 03/14] eth/gasestimator: check ErrGasLimitTooHigh conditions (#32348) This PR makes 2 changes to how [EIP-7825](https://github.com/ethereum/go-ethereum/pull/31824) behaves. When `eth_estimateGas` or `eth_createAccessList` is called without any gas limit in the payload, geth will choose the block's gas limit or the `RPCGasCap`, which can be larger than the `maxTxGas`. When this happens for `estimateGas`, the gas estimation just errors out and ends, when it should continue doing binary search to find the lowest possible gas limit. This PR will: - Add a check to see if `hi` is larger than `maxTxGas` and cap it to `maxTxGas` if it's larger. And add a special case handling for gas estimation execute when it errs with `ErrGasLimitTooHigh` --------- Co-authored-by: Gary Rong --- eth/gasestimator/gasestimator.go | 20 ++++++++++++++++++++ internal/ethapi/override/override_test.go | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index 7e9d8125de..6e79fbd62b 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -62,6 +62,23 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin if call.GasLimit >= params.TxGas { hi = call.GasLimit } + + // Cap the maximum gas allowance according to EIP-7825 if the estimation targets Osaka + if hi > params.MaxTxGas { + blockNumber, blockTime := opts.Header.Number, opts.Header.Time + if opts.BlockOverrides != nil { + if opts.BlockOverrides.Number != nil { + blockNumber = opts.BlockOverrides.Number.ToInt() + } + if opts.BlockOverrides.Time != nil { + blockTime = uint64(*opts.BlockOverrides.Time) + } + } + if opts.Config.IsOsaka(blockNumber, blockTime) { + hi = params.MaxTxGas + } + } + // Normalize the max fee per gas the call is willing to spend. var feeCap *big.Int if call.GasFeeCap != nil { @@ -209,6 +226,9 @@ func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit ui if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit } + if errors.Is(err, core.ErrGasLimitTooHigh) { + return true, nil, nil // Special case, lower gas limit + } return true, nil, err // Bail out } return result.Failed(), result, nil diff --git a/internal/ethapi/override/override_test.go b/internal/ethapi/override/override_test.go index 02a17c1331..41b4f2c253 100644 --- a/internal/ethapi/override/override_test.go +++ b/internal/ethapi/override/override_test.go @@ -31,6 +31,10 @@ import ( type precompileContract struct{} +func (p *precompileContract) Name() string { + panic("implement me") +} + func (p *precompileContract) RequiredGas(input []byte) uint64 { return 0 } func (p *precompileContract) Run(input []byte) ([]byte, error) { return nil, nil } From 792de5d2e3b40837aadb65993e387bf5aa5bce9f Mon Sep 17 00:00:00 2001 From: cui Date: Wed, 6 Aug 2025 18:57:43 +0800 Subject: [PATCH 04/14] core/filtermaps: remove unnecessary nil check and add cv2 lock (#32309) Co-authored-by: zsfelfoldi --- core/filtermaps/chain_view.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/core/filtermaps/chain_view.go b/core/filtermaps/chain_view.go index 7c48048ad9..35c5ed22a5 100644 --- a/core/filtermaps/chain_view.go +++ b/core/filtermaps/chain_view.go @@ -124,19 +124,12 @@ func (cv *ChainView) RawReceipts(number uint64) types.Receipts { // SharedRange returns the block range shared by two chain views. func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] { - cv.lock.Lock() - defer cv.lock.Unlock() - - if cv == nil || cv2 == nil || !cv.extendNonCanonical() || !cv2.extendNonCanonical() { + if cv == nil || cv2 == nil { return common.Range[uint64]{} } - var sharedLen uint64 - for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber; n++ { - h1, h2 := cv.blockHash(n), cv2.blockHash(n) - if h1 != h2 || h1 == (common.Hash{}) { - break - } - sharedLen = n + 1 + sharedLen := min(cv.headNumber, cv2.headNumber) + 1 + for sharedLen > 0 && cv.BlockId(sharedLen-1) != cv2.BlockId(sharedLen-1) { + sharedLen-- } return common.NewRange(0, sharedLen) } From e7189b59871931cb15747db376ce7dcc595f7f9a Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 16:42:22 +0800 Subject: [PATCH 05/14] go.mod: upgraded github.com/golang-jwt/jwt/v4 v4.5.1 => v4.5.2 (#32356) https://pkg.go.dev/vuln/GO-2025-3553 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6b63450a91..363d7d3dfb 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/fsnotify/fsnotify v1.6.0 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/gofrs/flock v0.12.1 - github.com/golang-jwt/jwt/v4 v4.5.1 + github.com/golang-jwt/jwt/v4 v4.5.2 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/google/gofuzz v1.2.0 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index db59c74229..099d432ba4 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= From f49f3ec78086903b278559f29bf38124c02d825f Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 20:15:54 +0800 Subject: [PATCH 06/14] rpc: use reflect.TypeFor (#32316) --- rpc/service.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rpc/service.go b/rpc/service.go index d50090e9fb..0f62d7eb7c 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -29,10 +29,10 @@ import ( ) var ( - contextType = reflect.TypeOf((*context.Context)(nil)).Elem() - errorType = reflect.TypeOf((*error)(nil)).Elem() - subscriptionType = reflect.TypeOf(Subscription{}) - stringType = reflect.TypeOf("") + contextType = reflect.TypeFor[context.Context]() + errorType = reflect.TypeFor[error]() + subscriptionType = reflect.TypeFor[Subscription]() + stringType = reflect.TypeFor[string]() ) type serviceRegistry struct { From dfde155541f82f4b8f3bf72ee507a12e716dde21 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 20:22:11 +0800 Subject: [PATCH 07/14] crypto/kzg4844: use reflect.TypeFor (#32319) --- crypto/kzg4844/kzg4844.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index baf9c9655b..9da2386368 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -31,9 +31,9 @@ import ( var content embed.FS var ( - blobT = reflect.TypeOf(Blob{}) - commitmentT = reflect.TypeOf(Commitment{}) - proofT = reflect.TypeOf(Proof{}) + blobT = reflect.TypeFor[Blob]() + commitmentT = reflect.TypeFor[Commitment]() + proofT = reflect.TypeFor[Proof]() CellProofsPerBlob = 128 ) From ec97ac70851c81962a1b999e01b339a3348d6892 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 20:30:03 +0800 Subject: [PATCH 08/14] common, common/hexutil: use reflect.TypeFor (#32321) --- common/hexutil/json.go | 10 +++++----- common/types.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/hexutil/json.go b/common/hexutil/json.go index e0ac98f52d..6b9f412078 100644 --- a/common/hexutil/json.go +++ b/common/hexutil/json.go @@ -28,11 +28,11 @@ import ( ) var ( - bytesT = reflect.TypeOf(Bytes(nil)) - bigT = reflect.TypeOf((*Big)(nil)) - uintT = reflect.TypeOf(Uint(0)) - uint64T = reflect.TypeOf(Uint64(0)) - u256T = reflect.TypeOf((*uint256.Int)(nil)) + bytesT = reflect.TypeFor[Bytes]() + bigT = reflect.TypeFor[*Big]() + uintT = reflect.TypeFor[Uint]() + uint64T = reflect.TypeFor[Uint64]() + u256T = reflect.TypeFor[*uint256.Int]() ) // Bytes marshals/unmarshals as a JSON string with 0x prefix. diff --git a/common/types.go b/common/types.go index fdb25f1b34..db4de8bcbd 100644 --- a/common/types.go +++ b/common/types.go @@ -42,8 +42,8 @@ const ( ) var ( - hashT = reflect.TypeOf(Hash{}) - addressT = reflect.TypeOf(Address{}) + hashT = reflect.TypeFor[Hash]() + addressT = reflect.TypeFor[Address]() // MaxAddress represents the maximum possible address value. MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff") @@ -466,7 +466,7 @@ func isString(input []byte) bool { // UnmarshalJSON parses a hash in hex syntax. func (d *Decimal) UnmarshalJSON(input []byte) error { if !isString(input) { - return &json.UnmarshalTypeError{Value: "non-string", Type: reflect.TypeOf(uint64(0))} + return &json.UnmarshalTypeError{Value: "non-string", Type: reflect.TypeFor[uint64]()} } if i, err := strconv.ParseUint(string(input[1:len(input)-1]), 10, 64); err == nil { *d = Decimal(i) From 2e3971aed1fbdfed9013611140d8afaf68eed8ff Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 20:36:20 +0800 Subject: [PATCH 09/14] beacon/merkle: use reflect.TypeFor (#32322) --- beacon/merkle/merkle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon/merkle/merkle.go b/beacon/merkle/merkle.go index 30896f9b01..64dfadfab5 100644 --- a/beacon/merkle/merkle.go +++ b/beacon/merkle/merkle.go @@ -32,7 +32,7 @@ type Value [32]byte // Values represent a series of merkle tree leaves/nodes. type Values []Value -var valueT = reflect.TypeOf(Value{}) +var valueT = reflect.TypeFor[Value]() // UnmarshalJSON parses a merkle value in hex syntax. func (m *Value) UnmarshalJSON(input []byte) error { From f9f85d0227078397eb293579081ec98753f8e4c5 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 20:53:36 +0800 Subject: [PATCH 10/14] core: use reflect.TypeFor (#32320) https://github.com/golang/go/issues/60088 --- core/forkid/forkid.go | 7 +++---- core/tracing/journal_test.go | 2 +- core/types/block.go | 2 +- core/types/withdrawal.go | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 4db366da82..7406a3f53a 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -241,9 +241,8 @@ func checksumToBytes(hash uint32) [4]byte { // them, one for the block number based forks and the second for the timestamps. func gatherForks(config *params.ChainConfig, genesis uint64) ([]uint64, []uint64) { // Gather all the fork block numbers via reflection - kind := reflect.TypeOf(params.ChainConfig{}) + kind := reflect.TypeFor[params.ChainConfig]() conf := reflect.ValueOf(config).Elem() - x := uint64(0) var ( forksByBlock []uint64 forksByTime []uint64 @@ -258,12 +257,12 @@ func gatherForks(config *params.ChainConfig, genesis uint64) ([]uint64, []uint64 } // Extract the fork rule block number or timestamp and aggregate it - if field.Type == reflect.TypeOf(&x) { + if field.Type == reflect.TypeFor[*uint64]() { if rule := conf.Field(i).Interface().(*uint64); rule != nil { forksByTime = append(forksByTime, *rule) } } - if field.Type == reflect.TypeOf(new(big.Int)) { + if field.Type == reflect.TypeFor[*big.Int]() { if rule := conf.Field(i).Interface().(*big.Int); rule != nil { forksByBlock = append(forksByBlock, rule.Uint64()) } diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index d9616a2ce8..99447e1e1d 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -293,7 +293,7 @@ func newTracerAllHooks() *tracerAllHooks { t := &tracerAllHooks{hooksCalled: make(map[string]bool)} // Initialize all hooks to false. We will use this to // get total count of hooks. - hooksType := reflect.TypeOf((*Hooks)(nil)).Elem() + hooksType := reflect.TypeFor[Hooks]() for i := 0; i < hooksType.NumField(); i++ { t.hooksCalled[hooksType.Field(i).Name] = false } diff --git a/core/types/block.go b/core/types/block.go index b284fb3b16..da9614793a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -128,7 +128,7 @@ func (h *Header) Hash() common.Hash { return rlpHash(h) } -var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size()) +var headerSize = common.StorageSize(reflect.TypeFor[Header]().Size()) // Size returns the approximate memory used by all internal contents. It is used // to approximate and limit the memory consumption of various caches. diff --git a/core/types/withdrawal.go b/core/types/withdrawal.go index 6f99e53b56..2cf00195a0 100644 --- a/core/types/withdrawal.go +++ b/core/types/withdrawal.go @@ -49,7 +49,7 @@ type Withdrawals []*Withdrawal // Len returns the length of s. func (s Withdrawals) Len() int { return len(s) } -var withdrawalSize = int(reflect.TypeOf(Withdrawal{}).Size()) +var withdrawalSize = int(reflect.TypeFor[Withdrawal]().Size()) func (s Withdrawals) Size() int { return withdrawalSize * len(s) From e979438a55739169dc6ab2ca0f2da668888a76d6 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 21:03:18 +0800 Subject: [PATCH 11/14] p2p/enode: use atomic.Pointer in LocalNode (#32360) --- p2p/enode/localnode.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/p2p/enode/localnode.go b/p2p/enode/localnode.go index 6e79c9cbdc..d8fa6a9202 100644 --- a/p2p/enode/localnode.go +++ b/p2p/enode/localnode.go @@ -45,7 +45,7 @@ const ( // current process. Setting ENR entries via the Set method updates the record. A new version // of the record is signed on demand when the Node method is called. type LocalNode struct { - cur atomic.Value // holds a non-nil node pointer while the record is up-to-date + cur atomic.Pointer[Node] // holds a non-nil node pointer while the record is up-to-date id ID key *ecdsa.PrivateKey @@ -82,7 +82,7 @@ func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode { } ln.seq = db.localSeq(ln.id) ln.update = time.Now() - ln.cur.Store((*Node)(nil)) + ln.cur.Store(nil) return ln } @@ -94,7 +94,7 @@ func (ln *LocalNode) Database() *DB { // Node returns the current version of the local node record. func (ln *LocalNode) Node() *Node { // If we have a valid record, return that - n := ln.cur.Load().(*Node) + n := ln.cur.Load() if n != nil { return n } @@ -105,7 +105,7 @@ func (ln *LocalNode) Node() *Node { // Double check the current record, since multiple goroutines might be waiting // on the write mutex. - if n = ln.cur.Load().(*Node); n != nil { + if n = ln.cur.Load(); n != nil { return n } @@ -121,7 +121,7 @@ func (ln *LocalNode) Node() *Node { ln.sign() ln.update = time.Now() - return ln.cur.Load().(*Node) + return ln.cur.Load() } // Seq returns the current sequence number of the local node record. @@ -276,11 +276,11 @@ func (e *lnEndpoint) get() (newIP net.IP, newPort uint16) { } func (ln *LocalNode) invalidate() { - ln.cur.Store((*Node)(nil)) + ln.cur.Store(nil) } func (ln *LocalNode) sign() { - if n := ln.cur.Load().(*Node); n != nil { + if n := ln.cur.Load(); n != nil { return // no changes } From bd6797eafa592472a5b6b495ed3a1a68c022f013 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 21:56:25 +0800 Subject: [PATCH 12/14] signer/core/apitypes: simplify reflect []byte creation (#32315) Co-authored-by: Felix Lange --- signer/core/apitypes/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go index b5fd5a2854..dcbd04867c 100644 --- a/signer/core/apitypes/types.go +++ b/signer/core/apitypes/types.go @@ -544,7 +544,7 @@ func parseBytes(encType interface{}) ([]byte, bool) { // Handle array types. val := reflect.ValueOf(encType) if val.Kind() == reflect.Array && val.Type().Elem().Kind() == reflect.Uint8 { - v := reflect.MakeSlice(reflect.TypeOf([]byte{}), val.Len(), val.Len()) + v := reflect.ValueOf(make([]byte, val.Len())) reflect.Copy(v, val) return v.Bytes(), true } From 4e7bc2bdc89e1fa27d259ef3efe7a59d2d2142d2 Mon Sep 17 00:00:00 2001 From: cui Date: Thu, 7 Aug 2025 21:58:27 +0800 Subject: [PATCH 13/14] rlp: use reflect.TypeFor (#32317) Co-authored-by: Felix Lange --- rlp/decode.go | 8 ++++---- rlp/encode.go | 2 +- rlp/raw.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rlp/decode.go b/rlp/decode.go index 0fbca243ee..5a06f35ec0 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -148,9 +148,9 @@ func addErrorContext(err error, ctx string) error { } var ( - decoderInterface = reflect.TypeOf(new(Decoder)).Elem() - bigInt = reflect.TypeOf(big.Int{}) - u256Int = reflect.TypeOf(uint256.Int{}) + decoderInterface = reflect.TypeFor[Decoder]() + bigInt = reflect.TypeFor[big.Int]() + u256Int = reflect.TypeFor[uint256.Int]() ) func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error) { @@ -512,7 +512,7 @@ func makeNilPtrDecoder(etype reflect.Type, etypeinfo *typeinfo, ts rlpstruct.Tag } } -var ifsliceType = reflect.TypeOf([]interface{}{}) +var ifsliceType = reflect.TypeFor[[]any]() func decodeInterface(s *Stream, val reflect.Value) error { if val.Type().NumMethod() != 0 { diff --git a/rlp/encode.go b/rlp/encode.go index 3645bbfda0..623932a90b 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -133,7 +133,7 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int { return sizesize + 1 } -var encoderInterface = reflect.TypeOf(new(Encoder)).Elem() +var encoderInterface = reflect.TypeFor[Encoder]() // makeWriter creates a writer function for the given type. func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { diff --git a/rlp/raw.go b/rlp/raw.go index 879e3bfe5d..cec90346a1 100644 --- a/rlp/raw.go +++ b/rlp/raw.go @@ -26,7 +26,7 @@ import ( // not verify whether the content of RawValues is valid RLP. type RawValue []byte -var rawValueType = reflect.TypeOf(RawValue{}) +var rawValueType = reflect.TypeFor[RawValue]() // StringSize returns the encoded size of a string. func StringSize(s string) uint64 { From f86870f5da77e2563fdb7151078be24c12fa2076 Mon Sep 17 00:00:00 2001 From: radik878 Date: Thu, 7 Aug 2025 17:31:02 +0300 Subject: [PATCH 14/14] eth/downloader: fix incomplete code comment (#32354) --- eth/downloader/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/downloader/api.go b/eth/downloader/api.go index c98f9a2c3f..f97371de5f 100644 --- a/eth/downloader/api.go +++ b/eth/downloader/api.go @@ -200,7 +200,7 @@ func (s *SyncStatusSubscription) Unsubscribe() { } // SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates. -// The given channel must receive interface values, the result can either. +// The given channel must receive interface values, the result can either be a SyncingResult or false. func (api *DownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription { api.installSyncSubscription <- status return &SyncStatusSubscription{api: api, c: status}