Merge tag 'v1.15.11' into release/geth-1.15.11-fh3.0

This commit is contained in:
Matthieu Vachon 2025-05-06 21:23:02 -04:00
commit 3334d91d3b
48 changed files with 5035 additions and 294 deletions

View file

@ -131,7 +131,7 @@ type executionPayloadEnvelopeMarshaling struct {
type PayloadStatusV1 struct {
Status string `json:"status"`
Witness *hexutil.Bytes `json:"witness"`
Witness *hexutil.Bytes `json:"witness,omitempty"`
LatestValidHash *common.Hash `json:"latestValidHash"`
ValidationError *string `json:"validationError"`
}

View file

@ -70,6 +70,7 @@ func (s *Suite) EthTests() []utesting.Test {
{Name: "Status", Fn: s.TestStatus},
// get block headers
{Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders},
{Name: "GetNonexistentBlockHeaders", Fn: s.TestGetNonexistentBlockHeaders},
{Name: "SimultaneousRequests", Fn: s.TestSimultaneousRequests},
{Name: "SameRequestID", Fn: s.TestSameRequestID},
{Name: "ZeroRequestID", Fn: s.TestZeroRequestID},
@ -158,6 +159,48 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) {
}
}
func (s *Suite) TestGetNonexistentBlockHeaders(t *utesting.T) {
t.Log(`This test sends GetBlockHeaders requests for nonexistent blocks (using max uint64 value)
to check if the node disconnects after receiving multiple invalid requests.`)
conn, err := s.dial()
if err != nil {
t.Fatalf("dial failed: %v", err)
}
defer conn.Close()
if err := conn.peer(s.chain, nil); err != nil {
t.Fatalf("peering failed: %v", err)
}
// Create request with max uint64 value for a nonexistent block
badReq := &eth.GetBlockHeadersPacket{
GetBlockHeadersRequest: &eth.GetBlockHeadersRequest{
Origin: eth.HashOrNumber{Number: ^uint64(0)},
Amount: 1,
Skip: 0,
Reverse: false,
},
}
// Send request 10 times. Some clients are lient on the first few invalids.
for i := 0; i < 10; i++ {
badReq.RequestId = uint64(i)
if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, badReq); err != nil {
if err == errDisc {
t.Fatalf("peer disconnected after %d requests", i+1)
}
t.Fatalf("write failed: %v", err)
}
}
// Check if peer disconnects at the end.
code, _, err := conn.Read()
if err == errDisc || code == discMsg {
t.Fatal("peer improperly disconnected")
}
}
func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
t.Log(`This test requests blocks headers from the node, performing two requests
concurrently, with different request IDs.`)

View file

@ -246,10 +246,13 @@ func initGenesis(ctx *cli.Context) error {
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
defer triedb.Close()
_, hash, _, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
_, hash, compatErr, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides)
if err != nil {
utils.Fatalf("Failed to write genesis block: %v", err)
}
if compatErr != nil {
utils.Fatalf("Failed to write chain config: %v", compatErr)
}
log.Info("Successfully wrote genesis state", "database", "chaindata", "hash", hash)
return nil

View file

@ -1855,10 +1855,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.String(CryptoKZGFlag.Name) != "gokzg" && ctx.String(CryptoKZGFlag.Name) != "ckzg" {
Fatalf("--%s flag must be 'gokzg' or 'ckzg'", CryptoKZGFlag.Name)
}
// The initialization of the KZG library can take up to 2 seconds
// We start this here in parallel, so it should be available
// once we start executing blocks. It's threadsafe.
go func() {
log.Info("Initializing the KZG library", "backend", ctx.String(CryptoKZGFlag.Name))
if err := kzg4844.UseCKZG(ctx.String(CryptoKZGFlag.Name) == "ckzg"); err != nil {
Fatalf("Failed to set KZG library implementation to %s: %v", ctx.String(CryptoKZGFlag.Name), err)
}
}()
// VM tracing config.
if name := ctx.String(VMTraceFlag.Name); name != "" {
cfg.VMTrace = name

View file

@ -234,6 +234,14 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return receipts
}
func (bc *BlockChain) GetRawReceiptsByHash(hash common.Hash) types.Receipts {
number := rawdb.ReadHeaderNumber(bc.db, hash)
if number == nil {
return nil
}
return rawdb.ReadRawReceipts(bc.db, hash, *number)
}
// GetUnclesInChain retrieves all the uncles from a given block backwards until
// a specific distance is reached.
func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header {
@ -262,42 +270,20 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
// GetTransactionLookup retrieves the lookup along with the transaction
// itself associate with the given transaction hash.
//
// An error will be returned if the transaction is not found, and background
// indexing for transactions is still in progress. The transaction might be
// reachable shortly once it's indexed.
//
// A null will be returned in the transaction is not found and background
// transaction indexing is already finished. The transaction is not existent
// from the node's perspective.
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction, error) {
// A null will be returned if the transaction is not found. This can be due to
// the transaction indexer not being finished. The caller must explicitly check
// the indexer progress.
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) {
bc.txLookupLock.RLock()
defer bc.txLookupLock.RUnlock()
// Short circuit if the txlookup already in the cache, retrieve otherwise
if item, exist := bc.txLookupCache.Get(hash); exist {
return item.lookup, item.transaction, nil
return item.lookup, item.transaction
}
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
if tx == nil {
progress, err := bc.TxIndexProgress()
if err != nil {
// No error is returned if the transaction indexing progress is unreachable
// due to unexpected internal errors. In such cases, it is impossible to
// determine whether the transaction does not exist or has simply not been
// indexed yet without a progress marker.
//
// In such scenarios, the transaction is treated as unreachable, though
// this is clearly an unintended and unexpected situation.
return nil, nil, nil
}
// The transaction indexing is not finished yet, returning an
// error to explicitly indicate it.
if !progress.Done() {
return nil, nil, errors.New("transaction indexing still in progress")
}
// The transaction is already indexed, the transaction is either
// not existent or not in the range of index, returning null.
return nil, nil, nil
return nil, nil
}
lookup := &rawdb.LegacyTxLookupEntry{
BlockHash: blockHash,
@ -308,7 +294,23 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo
lookup: lookup,
transaction: tx,
})
return lookup, tx, nil
return lookup, tx
}
// TxIndexDone returns true if the transaction indexer has finished indexing.
func (bc *BlockChain) TxIndexDone() bool {
progress, err := bc.TxIndexProgress()
if err != nil {
// No error is returned if the transaction indexing progress is unreachable
// due to unexpected internal errors. In such cases, it is impossible to
// determine whether the transaction does not exist or has simply not been
// indexed yet without a progress marker.
//
// In such scenarios, the transaction is treated as unreachable, though
// this is clearly an unintended and unexpected situation.
return true
}
return progress.Done()
}
// HasState checks if state trie is fully present in the database or not.
@ -404,7 +406,7 @@ func (bc *BlockChain) TxIndexProgress() (TxIndexProgress, error) {
if bc.txIndexer == nil {
return TxIndexProgress{}, errors.New("tx indexer is not enabled")
}
return bc.txIndexer.txIndexProgress()
return bc.txIndexer.txIndexProgress(), nil
}
// HistoryPruningCutoff returns the configured history pruning point.

View file

@ -29,6 +29,7 @@ type blockchain interface {
GetHeader(hash common.Hash, number uint64) *types.Header
GetCanonicalHash(number uint64) common.Hash
GetReceiptsByHash(hash common.Hash) types.Receipts
GetRawReceiptsByHash(hash common.Hash) types.Receipts
}
// ChainView represents an immutable view of a chain with a block id and a set
@ -102,10 +103,23 @@ func (cv *ChainView) Receipts(number uint64) types.Receipts {
blockHash := cv.BlockHash(number)
if blockHash == (common.Hash{}) {
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
return nil
}
return cv.chain.GetReceiptsByHash(blockHash)
}
// RawReceipts returns the set of receipts belonging to the block at the given
// block number. Does not derive the fields of the receipts, should only be
// used during creation of the filter maps, please use cv.Receipts during querying.
func (cv *ChainView) RawReceipts(number uint64) types.Receipts {
blockHash := cv.BlockHash(number)
if blockHash == (common.Hash{}) {
log.Error("Chain view: block hash unavailable", "number", number, "head", cv.headNumber)
return nil
}
return cv.chain.GetRawReceiptsByHash(blockHash)
}
// SharedRange returns the block range shared by two chain views.
func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
cv.lock.Lock()
@ -121,14 +135,6 @@ func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
return common.NewRange(0, sharedLen)
}
// limitedView returns a new chain view that is a truncated version of the parent view.
func (cv *ChainView) limitedView(newHead uint64) *ChainView {
if newHead >= cv.headNumber {
return cv
}
return NewChainView(cv.chain, newHead, cv.BlockHash(newHead))
}
// equalViews returns true if the two chain views are equivalent.
func equalViews(cv1, cv2 *ChainView) bool {
if cv1 == nil || cv2 == nil {

View file

@ -50,7 +50,7 @@ var (
)
const (
databaseVersion = 1 // reindexed if database version does not match
databaseVersion = 2 // reindexed if database version does not match
cachedLastBlocks = 1000 // last block of map pointers
cachedLvPointers = 1000 // first log value pointer of block pointers
cachedBaseRows = 100 // groups of base layer filter row data
@ -244,6 +244,8 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
disabledCh: make(chan struct{}),
exportFileName: config.ExportFileName,
Params: params,
targetView: initView,
indexedView: initView,
indexedRange: filterMapsRange{
initialized: initialized,
headIndexed: rs.HeadIndexed,
@ -265,16 +267,8 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows),
renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots),
}
f.checkRevertRange() // revert maps that are inconsistent with the current chain view
// Set initial indexer target.
f.targetView = initView
if f.indexedRange.initialized {
f.indexedView = f.initChainView(f.targetView)
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.HeadNumber()+1
if !f.indexedRange.headIndexed {
f.indexedRange.headDelimiter = 0
}
}
if f.indexedRange.hasIndexedBlocks() {
log.Info("Initialized log indexer",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
@ -303,29 +297,40 @@ func (f *FilterMaps) Stop() {
f.closeWg.Wait()
}
// initChainView returns a chain view consistent with both the current target
// view and the current state of the log index as found in the database, based
// on the last block of stored maps.
// Note that the returned view might be shorter than the existing index if
// the latest maps are not consistent with targetView.
func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
mapIndex := f.indexedRange.maps.AfterLast()
for {
var ok bool
mapIndex, ok = f.lastMapBoundaryBefore(mapIndex)
if !ok {
break
// checkRevertRange checks whether the existing index is consistent with the
// current indexed view and reverts inconsistent maps if necessary.
func (f *FilterMaps) checkRevertRange() {
if f.indexedRange.maps.Count() == 0 {
return
}
lastBlockNumber, lastBlockId, err := f.getLastBlockOfMap(mapIndex)
lastMap := f.indexedRange.maps.Last()
lastBlockNumber, lastBlockId, err := f.getLastBlockOfMap(lastMap)
if err != nil {
log.Error("Could not initialize indexed chain view", "error", err)
break
log.Error("Error initializing log index database; resetting log index", "error", err)
f.reset()
return
}
if lastBlockNumber <= chainView.HeadNumber() && chainView.BlockId(lastBlockNumber) == lastBlockId {
return chainView.limitedView(lastBlockNumber)
for lastBlockNumber > f.indexedView.HeadNumber() || f.indexedView.BlockId(lastBlockNumber) != lastBlockId {
// revert last map
if f.indexedRange.maps.Count() == 1 {
f.reset() // reset database if no rendered maps remained
return
}
lastMap--
newRange := f.indexedRange
newRange.maps.SetLast(lastMap)
lastBlockNumber, lastBlockId, err = f.getLastBlockOfMap(lastMap)
if err != nil {
log.Error("Error initializing log index database; resetting log index", "error", err)
f.reset()
return
}
newRange.blocks.SetAfterLast(lastBlockNumber) // lastBlockNumber is probably partially indexed
newRange.headIndexed = false
newRange.headDelimiter = 0
// only shorten range and leave map data; next head render will overwrite it
f.setRange(f.db, f.indexedView, newRange, false)
}
return chainView.limitedView(0)
}
// reset un-initializes the FilterMaps structure and removes all related data from
@ -662,15 +667,11 @@ func (f *FilterMaps) mapRowIndex(mapIndex, rowIndex uint32) uint64 {
}
// getBlockLvPointer returns the starting log value index where the log values
// generated by the given block are located. If blockNumber is beyond the current
// head then the first unoccupied log value index is returned.
// generated by the given block are located.
//
// Note that this function assumes that the indexer read lock is being held when
// called from outside the indexerLoop goroutine.
func (f *FilterMaps) getBlockLvPointer(blockNumber uint64) (uint64, error) {
if blockNumber >= f.indexedRange.blocks.AfterLast() && f.indexedRange.headIndexed {
return f.indexedRange.headDelimiter + 1, nil
}
if lvPointer, ok := f.lvPointerCache.Get(blockNumber); ok {
return lvPointer, nil
}

View file

@ -254,7 +254,7 @@ func (f *FilterMaps) tryIndexHead() error {
((!f.loggedHeadIndex && time.Since(f.startedHeadIndexAt) > headLogDelay) ||
time.Since(f.lastLogHeadIndex) > logFrequency) {
log.Info("Log index head rendering in progress",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
"firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
"remaining", f.indexedView.HeadNumber()-f.indexedRange.blocks.Last(),
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
@ -266,7 +266,7 @@ func (f *FilterMaps) tryIndexHead() error {
}
if f.loggedHeadIndex && f.indexedRange.hasIndexedBlocks() {
log.Info("Log index head rendering finished",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
"firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
}
@ -323,7 +323,7 @@ func (f *FilterMaps) tryIndexTail() (bool, error) {
if f.indexedRange.hasIndexedBlocks() && f.ptrTailIndex >= f.indexedRange.blocks.First() &&
(!f.loggedTailIndex || time.Since(f.lastLogTailIndex) > logFrequency) {
log.Info("Log index tail rendering in progress",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
"firstblock", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
"processed", f.ptrTailIndex-f.indexedRange.blocks.First()+tpb,
"remaining", remaining,
"next tail epoch percentage", f.indexedRange.tailPartialEpoch*100/f.mapsPerEpoch,
@ -346,7 +346,7 @@ func (f *FilterMaps) tryIndexTail() (bool, error) {
}
if f.loggedTailIndex && f.indexedRange.hasIndexedBlocks() {
log.Info("Log index tail rendering finished",
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
"firstblock", f.indexedRange.blocks.First(), "lastblock", f.indexedRange.blocks.Last(),
"processed", f.ptrTailIndex-f.indexedRange.blocks.First(),
"elapsed", common.PrettyDuration(time.Since(f.startedTailIndexAt)))
f.loggedTailIndex = false

View file

@ -515,6 +515,13 @@ func (tc *testChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
return tc.receipts[hash]
}
func (tc *testChain) GetRawReceiptsByHash(hash common.Hash) types.Receipts {
tc.lock.RLock()
defer tc.lock.RUnlock()
return tc.receipts[hash]
}
func (tc *testChain) addBlocks(count, maxTxPerBlock, maxLogsPerReceipt, maxTopicsPerLog int, random bool) {
tc.lock.Lock()
blockGen := func(i int, gen *core.BlockGen) {

View file

@ -468,15 +468,25 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
r.f.filterMapCache.Remove(mapIndex)
}
}
var blockNumber uint64
if r.finished.First() > 0 {
// in order to always ensure continuous block pointers, initialize
// blockNumber based on the last block of the previous map, then verify
// against the first block associated with each rendered map
lastBlock, _, err := r.f.getLastBlockOfMap(r.finished.First() - 1)
if err != nil {
return fmt.Errorf("failed to get last block of previous map %d: %v", r.finished.First()-1, err)
}
blockNumber = lastBlock + 1
}
// add or update block pointers
blockNumber := r.finishedMaps[r.finished.First()].firstBlock()
for mapIndex := range r.finished.Iter() {
renderedMap := r.finishedMaps[mapIndex]
if blockNumber != renderedMap.firstBlock() {
return fmt.Errorf("non-continuous block numbers in rendered map %d (next expected: %d first rendered: %d)", mapIndex, blockNumber, renderedMap.firstBlock())
}
r.f.storeLastBlockOfMap(batch, mapIndex, renderedMap.lastBlock, renderedMap.lastBlockId)
checkWriteCnt()
if blockNumber != renderedMap.firstBlock() {
panic("non-continuous block numbers")
}
for _, lvPtr := range renderedMap.blockLvPtrs {
r.f.storeBlockLvPointer(batch, blockNumber, lvPtr)
checkWriteCnt()
@ -693,7 +703,7 @@ func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock,
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.HeadNumber())
}
// get block receipts
receipts := f.targetView.Receipts(startBlock)
receipts := f.targetView.RawReceipts(startBlock)
if receipts == nil {
return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
}
@ -760,7 +770,7 @@ func (l *logIterator) next() error {
if l.delimiter {
l.delimiter = false
l.blockNumber++
l.receipts = l.chainView.Receipts(l.blockNumber)
l.receipts = l.chainView.RawReceipts(l.blockNumber)
if l.receipts == nil {
return fmt.Errorf("receipts not found for block %d", l.blockNumber)
}

View file

@ -82,13 +82,26 @@ func (fm *FilterMapsMatcherBackend) GetFilterMapRow(ctx context.Context, mapInde
}
// GetBlockLvPointer returns the starting log value index where the log values
// generated by the given block are located. If blockNumber is beyond the current
// head then the first unoccupied log value index is returned.
// generated by the given block are located. If blockNumber is beyond the last
// indexed block then the pointer will point right after this block, ensuring
// that the matcher does not fail and can return a set of results where the
// valid range is correct.
// GetBlockLvPointer implements MatcherBackend.
func (fm *FilterMapsMatcherBackend) GetBlockLvPointer(ctx context.Context, blockNumber uint64) (uint64, error) {
fm.f.indexLock.RLock()
defer fm.f.indexLock.RUnlock()
if blockNumber >= fm.f.indexedRange.blocks.AfterLast() {
if fm.f.indexedRange.headIndexed {
// return index after head block
return fm.f.indexedRange.headDelimiter + 1, nil
}
if fm.f.indexedRange.blocks.Count() > 0 {
// return index at the beginning of the last, partially indexed
// block (after the last fully indexed one)
blockNumber = fm.f.indexedRange.blocks.Last()
}
}
return fm.f.getBlockLvPointer(blockNumber)
}

View file

@ -365,6 +365,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
return nil, common.Hash{}, nil, errors.New("missing head header")
}
newCfg := genesis.chainConfigOrDefault(ghash, storedCfg)
if err := overrides.apply(newCfg); err != nil {
return nil, common.Hash{}, nil, err
}
// Sanity-check the new configuration.
if err := newCfg.CheckConfigForkOrder(); err != nil {

View file

@ -35,7 +35,7 @@ import (
// message no matter the execution itself is successful or not.
type ExecutionResult struct {
UsedGas uint64 // Total used gas, not including the refunded gas
RefundedGas uint64 // Total gas refunded after execution
MaxUsedGas uint64 // Maximum gas consumed during execution, excluding gas refunds.
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
}
@ -509,9 +509,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
}
// Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution.
peakGasUsed := st.gasUsed()
// Compute refund counter, capped to a refund quotient.
gasRefund := st.calcRefund()
st.gasRemaining += gasRefund
st.gasRemaining += st.calcRefund()
if rules.IsPrague {
// After EIP-7623: Data-heavy transactions pay the floor gas.
if st.gasUsed() < floorDataGas {
@ -521,6 +524,9 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
t.OnGasChange(prev, st.gasRemaining, tracing.GasChangeTxDataFloor)
}
}
if peakGasUsed < floorDataGas {
peakGasUsed = floorDataGas
}
}
st.returnGas()
@ -550,7 +556,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
return &ExecutionResult{
UsedGas: st.gasUsed(),
RefundedGas: gasRefund,
MaxUsedGas: peakGasUsed,
Err: vmerr,
ReturnData: ret,
}, nil

View file

@ -17,8 +17,8 @@
package core
import (
"errors"
"fmt"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
@ -47,11 +47,21 @@ type txIndexer struct {
// and all others shouldn't.
limit uint64
// The current head of blockchain for transaction indexing. This field
// is accessed by both the indexer and the indexing progress queries.
head atomic.Uint64
// The current tail of the indexed transactions, null indicates
// that no transactions have been indexed yet.
//
// This field is accessed by both the indexer and the indexing
// progress queries.
tail atomic.Pointer[uint64]
// cutoff denotes the block number before which the chain segment should
// be pruned and not available locally.
cutoff uint64
db ethdb.Database
progress chan chan TxIndexProgress
term chan chan struct{}
closed chan struct{}
}
@ -63,10 +73,12 @@ func newTxIndexer(limit uint64, chain *BlockChain) *txIndexer {
limit: limit,
cutoff: cutoff,
db: chain.db,
progress: make(chan chan TxIndexProgress),
term: make(chan chan struct{}),
closed: make(chan struct{}),
}
indexer.head.Store(indexer.resolveHead())
indexer.tail.Store(rawdb.ReadTxIndexTail(chain.db))
go indexer.loop(chain)
var msg string
@ -154,6 +166,7 @@ func (indexer *txIndexer) repair(head uint64) {
// A crash may occur between the two delete operations,
// potentially leaving dangling indexes in the database.
// However, this is considered acceptable.
indexer.tail.Store(nil)
rawdb.DeleteTxIndexTail(indexer.db)
rawdb.DeleteAllTxLookupEntries(indexer.db, nil)
log.Warn("Purge transaction indexes", "head", head, "tail", *tail)
@ -174,6 +187,7 @@ func (indexer *txIndexer) repair(head uint64) {
// Traversing the database directly within the transaction
// index namespace might be slow and expensive, but we
// have no choice.
indexer.tail.Store(nil)
rawdb.DeleteTxIndexTail(indexer.db)
rawdb.DeleteAllTxLookupEntries(indexer.db, nil)
log.Warn("Purge transaction indexes", "head", head, "cutoff", indexer.cutoff)
@ -187,6 +201,7 @@ func (indexer *txIndexer) repair(head uint64) {
// A crash may occur between the two delete operations,
// potentially leaving dangling indexes in the database.
// However, this is considered acceptable.
indexer.tail.Store(&indexer.cutoff)
rawdb.WriteTxIndexTail(indexer.db, indexer.cutoff)
rawdb.DeleteAllTxLookupEntries(indexer.db, func(txhash common.Hash, blob []byte) bool {
n := rawdb.DecodeTxLookupEntry(blob, indexer.db)
@ -218,14 +233,13 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
var (
stop chan struct{} // Non-nil if background routine is active
done chan struct{} // Non-nil if background routine is active
head = indexer.resolveHead() // The latest announced chain head
headCh = make(chan ChainHeadEvent)
sub = chain.SubscribeChainHeadEvent(headCh)
)
defer sub.Unsubscribe()
// Validate the transaction indexes and repair if necessary
head := indexer.head.Load()
indexer.repair(head)
// Launch the initial processing if chain is not empty (head != genesis).
@ -238,17 +252,18 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
for {
select {
case h := <-headCh:
indexer.head.Store(h.Header.Number.Uint64())
if done == nil {
stop = make(chan struct{})
done = make(chan struct{})
go indexer.run(h.Header.Number.Uint64(), stop, done)
}
head = h.Header.Number.Uint64()
case <-done:
stop = nil
done = nil
case ch := <-indexer.progress:
ch <- indexer.report(head)
indexer.tail.Store(rawdb.ReadTxIndexTail(indexer.db))
case ch := <-indexer.term:
if stop != nil {
close(stop)
@ -264,7 +279,7 @@ func (indexer *txIndexer) loop(chain *BlockChain) {
}
// report returns the tx indexing progress.
func (indexer *txIndexer) report(head uint64) TxIndexProgress {
func (indexer *txIndexer) report(head uint64, tail *uint64) TxIndexProgress {
// Special case if the head is even below the cutoff,
// nothing to index.
if head < indexer.cutoff {
@ -284,7 +299,6 @@ func (indexer *txIndexer) report(head uint64) TxIndexProgress {
}
// Compute how many blocks have been indexed
var indexed uint64
tail := rawdb.ReadTxIndexTail(indexer.db)
if tail != nil {
indexed = head - *tail + 1
}
@ -300,16 +314,12 @@ func (indexer *txIndexer) report(head uint64) TxIndexProgress {
}
}
// txIndexProgress retrieves the tx indexing progress, or an error if the
// background tx indexer is already stopped.
func (indexer *txIndexer) txIndexProgress() (TxIndexProgress, error) {
ch := make(chan TxIndexProgress, 1)
select {
case indexer.progress <- ch:
return <-ch, nil
case <-indexer.closed:
return TxIndexProgress{}, errors.New("indexer is closed")
}
// txIndexProgress retrieves the transaction indexing progress. The reported
// progress may slightly lag behind the actual indexing state, as the tail is
// only updated at the end of each indexing operation. However, this delay is
// considered acceptable.
func (indexer *txIndexer) txIndexProgress() TxIndexProgress {
return indexer.report(indexer.head.Load(), indexer.tail.Load())
}
// close shutdown the indexer. Safe to be called for multiple times.

View file

@ -123,7 +123,6 @@ func TestTxIndexer(t *testing.T) {
indexer := &txIndexer{
limit: 0,
db: db,
progress: make(chan chan TxIndexProgress),
}
for i, limit := range c.limits {
indexer.limit = limit
@ -243,7 +242,6 @@ func TestTxIndexerRepair(t *testing.T) {
indexer := &txIndexer{
limit: c.limit,
db: db,
progress: make(chan chan TxIndexProgress),
}
indexer.run(chainHead, make(chan struct{}), make(chan struct{}))
@ -435,12 +433,8 @@ func TestTxIndexerReport(t *testing.T) {
limit: c.limit,
cutoff: c.cutoff,
db: db,
progress: make(chan chan TxIndexProgress),
}
if c.tail != nil {
rawdb.WriteTxIndexTail(db, *c.tail)
}
p := indexer.report(c.head)
p := indexer.report(c.head, c.tail)
if p.Indexed != c.expIndexed {
t.Fatalf("Unexpected indexed: %d, expected: %d", p.Indexed, c.expIndexed)
}

View file

@ -1494,22 +1494,22 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
// equal number for all for accounts with many pending transactions.
func (pool *LegacyPool) truncatePending() {
pending := uint64(0)
for _, list := range pool.pending {
pending += uint64(list.Len())
// Assemble a spam order to penalize large transactors first
spammers := prque.New[uint64, common.Address](nil)
for addr, list := range pool.pending {
// Only evict transactions from high rollers
length := uint64(list.Len())
pending += length
if length > pool.config.AccountSlots {
spammers.Push(addr, length)
}
}
if pending <= pool.config.GlobalSlots {
return
}
pendingBeforeCap := pending
// Assemble a spam order to penalize large transactors first
spammers := prque.New[int64, common.Address](nil)
for addr, list := range pool.pending {
// Only evict transactions from high rollers
if uint64(list.Len()) > pool.config.AccountSlots {
spammers.Push(addr, int64(list.Len()))
}
}
// Gradually drop transactions from offenders
offenders := []common.Address{}
for pending > pool.config.GlobalSlots && !spammers.Empty() {
@ -1934,7 +1934,7 @@ func (pool *LegacyPool) Clear() {
pool.reserver.Release(addr)
}
pool.all.Clear()
pool.priced = newPricedList(pool.all)
pool.priced.Reheap()
pool.pending = make(map[common.Address]*list)
pool.queue = make(map[common.Address]*list)
pool.pendingNonces = newNoncer(pool.currentState)

View file

@ -149,6 +149,17 @@ func VerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
return gokzgVerifyBlobProof(blob, commitment, proof)
}
// ComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func ComputeCellProofs(blob *Blob) ([]Proof, error) {
if useCKZG.Load() {
return ckzgComputeCellProofs(blob)
}
return gokzgComputeCellProofs(blob)
}
// CalcBlobHashV1 calculates the 'versioned blob hash' of a commitment.
// The given hasher must be a sha256 hash instance, otherwise the result will be invalid!
func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) {

View file

@ -23,8 +23,8 @@ import (
"errors"
"sync"
gokzg4844 "github.com/crate-crypto/go-kzg-4844"
ckzg4844 "github.com/ethereum/c-kzg-4844/bindings/go"
gokzg4844 "github.com/crate-crypto/go-eth-kzg"
ckzg4844 "github.com/ethereum/c-kzg-4844/v2/bindings/go"
"github.com/ethereum/go-ethereum/common/hexutil"
)
@ -47,15 +47,21 @@ func ckzgInit() {
if err = gokzg4844.CheckTrustedSetupIsWellFormed(params); err != nil {
panic(err)
}
g1s := make([]byte, len(params.SetupG1Lagrange)*(len(params.SetupG1Lagrange[0])-2)/2)
g1Lag := make([]byte, len(params.SetupG1Lagrange)*(len(params.SetupG1Lagrange[0])-2)/2)
for i, g1 := range params.SetupG1Lagrange {
copy(g1Lag[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
}
g1s := make([]byte, len(params.SetupG1Monomial)*(len(params.SetupG1Monomial[0])-2)/2)
for i, g1 := range params.SetupG1Monomial {
copy(g1s[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
}
g2s := make([]byte, len(params.SetupG2)*(len(params.SetupG2[0])-2)/2)
for i, g2 := range params.SetupG2 {
copy(g2s[i*(len(g2)-2)/2:], hexutil.MustDecode(g2))
}
if err = ckzg4844.LoadTrustedSetup(g1s, g2s); err != nil {
// The last parameter determines the multiplication table, see https://notes.ethereum.org/@jtraglia/windowed_multiplications
// I think 6 is an decent compromise between size and speed
if err = ckzg4844.LoadTrustedSetup(g1s, g1Lag, g2s, 6); err != nil {
panic(err)
}
}
@ -125,3 +131,21 @@ func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
}
return nil
}
// ckzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
ckzgIniter.Do(ckzgInit)
_, proofs, err := ckzg4844.ComputeCellsAndKZGProofs((*ckzg4844.Blob)(blob))
if err != nil {
return []Proof{}, err
}
var p []Proof
for _, proof := range proofs {
p = append(p, (Proof)(proof))
}
return p, nil
}

View file

@ -60,3 +60,11 @@ func ckzgComputeBlobProof(blob *Blob, commitment Commitment) (Proof, error) {
func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
panic("unsupported platform")
}
// ckzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
panic("unsupported platform")
}

View file

@ -20,7 +20,7 @@ import (
"encoding/json"
"sync"
gokzg4844 "github.com/crate-crypto/go-kzg-4844"
gokzg4844 "github.com/crate-crypto/go-eth-kzg"
)
// context is the crypto primitive pre-seeded with the trusted setup parameters.
@ -96,3 +96,21 @@ func gokzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error
return context.VerifyBlobKZGProof((*gokzg4844.Blob)(blob), (gokzg4844.KZGCommitment)(commitment), (gokzg4844.KZGProof)(proof))
}
// gokzgComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
// the commitment.
//
// This method does not verify that the commitment is correct with respect to blob.
func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
gokzgIniter.Do(gokzgInit)
_, proofs, err := context.ComputeCellsAndKZGProofs((*gokzg4844.Blob)(blob), 0)
if err != nil {
return []Proof{}, err
}
var p []Proof
for _, proof := range proofs {
p = append(p, (Proof)(proof))
}
return p, nil
}

File diff suppressed because it is too large Load diff

View file

@ -349,22 +349,20 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
// GetTransaction retrieves the lookup along with the transaction itself associate
// with the given transaction hash.
//
// An error will be returned if the transaction is not found, and background
// indexing for transactions is still in progress. The error is used to indicate the
// scenario explicitly that the transaction might be reachable shortly.
//
// A null will be returned in the transaction is not found and background transaction
// indexing is already finished. The transaction is not existent from the perspective
// of node.
func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
lookup, tx, err := b.eth.blockchain.GetTransactionLookup(txHash)
if err != nil {
return false, nil, common.Hash{}, 0, 0, err
}
// A null will be returned if the transaction is not found. The transaction is not
// existent from the node's perspective. This can be due to the transaction indexer
// not being finished. The caller must explicitly check the indexer progress.
func (b *EthAPIBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
lookup, tx := b.eth.blockchain.GetTransactionLookup(txHash)
if lookup == nil || tx == nil {
return false, nil, common.Hash{}, 0, 0, nil
return false, nil, common.Hash{}, 0, 0
}
return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index, nil
return true, tx, lookup.BlockHash, lookup.BlockIndex, lookup.Index
}
// TxIndexDone returns true if the transaction indexer has finished indexing.
func (b *EthAPIBackend) TxIndexDone() bool {
return b.eth.blockchain.TxIndexDone()
}
func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
@ -391,7 +389,7 @@ func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.S
return b.eth.txPool.SubscribeTransactions(ch, true)
}
func (b *EthAPIBackend) SyncProgress() ethereum.SyncProgress {
func (b *EthAPIBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress {
prog := b.eth.Downloader().Progress()
if txProg, err := b.eth.blockchain.TxIndexProgress(); err == nil {
prog.TxIndexFinishedBlocks = txProg.Indexed

View file

@ -373,7 +373,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
}
valid := func(id *engine.PayloadID) engine.ForkChoiceResponse {
return engine.ForkChoiceResponse{
PayloadStatus: engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &update.HeadBlockHash},
PayloadStatus: engine.PayloadStatusV1{
Status: engine.VALID,
LatestValidHash: &update.HeadBlockHash,
},
PayloadID: id,
}
}

View file

@ -69,6 +69,9 @@ const (
// txGatherSlack is the interval used to collate almost-expired announces
// with network fetches.
txGatherSlack = 100 * time.Millisecond
// addTxsBatchSize it the max number of transactions to add in a single batch from a peer.
addTxsBatchSize = 128
)
var (
@ -329,8 +332,8 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
metas = make([]txMetadata, 0, len(txs))
)
// proceed in batches
for i := 0; i < len(txs); i += 128 {
end := i + 128
for i := 0; i < len(txs); i += addTxsBatchSize {
end := i + addTxsBatchSize
if end > len(txs) {
end = len(txs)
}
@ -372,7 +375,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
otherRejectMeter.Mark(otherreject)
// If 'other reject' is >25% of the deliveries in any batch, sleep a bit.
if otherreject > 128/4 {
if otherreject > addTxsBatchSize/4 {
time.Sleep(200 * time.Millisecond)
log.Debug("Peer delivering stale transactions", "peer", peer, "rejected", otherreject)
}

View file

@ -80,6 +80,13 @@ func (b *testBackend) GetReceiptsByHash(hash common.Hash) types.Receipts {
return r
}
func (b *testBackend) GetRawReceiptsByHash(hash common.Hash) types.Receipts {
if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil {
return rawdb.ReadRawReceipts(b.db, hash, *number)
}
return nil
}
func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {
var (
hash common.Hash

View file

@ -144,7 +144,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// There's a fairly high chance for the transaction to execute successfully
// with gasLimit set to the first execution's usedGas + gasRefund. Explicitly
// check that gas amount and use as a limit for the binary search.
optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63
optimisticGasLimit := (result.MaxUsedGas + params.CallStipend) * 64 / 63
if optimisticGasLimit < hi {
failed, _, err = execute(ctx, call, opts, optimisticGasLimit)
if err != nil {

View file

@ -82,7 +82,8 @@ type Backend interface {
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error)
GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
TxIndexDone() bool
RPCGasCap() uint64
ChainConfig() *params.ChainConfig
Engine() consensus.Engine
@ -777,6 +778,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Note: This copies the config, to not screw up the main config
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
}
evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{})
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
core.ProcessBeaconBlockRoot(*beaconRoot, evm)
@ -786,42 +788,45 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
}
for i, tx := range block.Transactions() {
// Prepare the transaction for un-traced execution
var (
msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())
vmConf vm.Config
dump *os.File
writer *bufio.Writer
err error
)
// If the transaction needs tracing, swap out the configs
if tx.Hash() == txHash || txHash == (common.Hash{}) {
// Generate a unique temporary file to dump it into
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
if txHash != (common.Hash{}) && tx.Hash() != txHash {
// Process the tx to update state, but don't trace it.
_, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
if err != nil {
return dumps, err
}
// Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
statedb.Finalise(evm.ChainConfig().IsEIP158(block.Number()))
continue
}
// The transaction should be traced.
// Generate a unique temporary file to dump it into.
prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4])
if !canon {
prefix = fmt.Sprintf("%valt-", prefix)
}
dump, err = os.CreateTemp(os.TempDir(), prefix)
var dump *os.File
dump, err := os.CreateTemp(os.TempDir(), prefix)
if err != nil {
return nil, err
}
dumps = append(dumps, dump.Name())
// Swap out the noop logger to the standard tracer
// Set up the tracer and EVM for the transaction.
var (
writer = bufio.NewWriter(dump)
vmConf = vm.Config{
Tracer: logger.NewJSONLogger(&logConfig, writer),
EnablePreimageRecording: true,
}
}
tracer = logger.NewJSONLogger(&logConfig, writer)
evm = vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{
Tracer: tracer,
NoBaseFee: true,
})
)
// Execute the transaction and flush any traces to disk
statedb.SetTxContext(tx.Hash(), i)
if vmConf.Tracer.OnTxStart != nil {
vmConf.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
}
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
if vmConf.Tracer.OnTxEnd != nil {
vmConf.Tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err)
if tracer.OnTxStart != nil {
tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
}
_, err = core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
if writer != nil {
writer.Flush()
}
@ -858,12 +863,13 @@ func containsTx(block *types.Block, hash common.Hash) bool {
// TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
found, _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash)
if err != nil {
found, _, blockHash, blockNumber, index := api.backend.GetTransaction(hash)
if !found {
// Warn in case tx indexer is not done.
if !api.backend.TxIndexDone() {
return nil, ethapi.NewTxIndexingError()
}
// Only mined txes are supported
if !found {
return nil, errTxNotFound
}
// It shouldn't happen in practice.

View file

@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"math/big"
"os"
"reflect"
"slices"
"sync/atomic"
@ -116,9 +117,13 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
return b.chain.GetBlockByNumber(uint64(number)), nil
}
func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
func (b *testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash)
return tx != nil, tx, hash, blockNumber, index, nil
return tx != nil, tx, hash, blockNumber, index
}
func (b *testBackend) TxIndexDone() bool {
return true
}
func (b *testBackend) RPCGasCap() uint64 {
@ -1214,3 +1219,118 @@ func TestTraceBlockWithBasefee(t *testing.T) {
}
}
}
func TestStandardTraceBlockToFile(t *testing.T) {
var (
// A sender who makes transactions, has some funds
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(1000000000000000)
// first contract the sender transacts with
aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43")
aaCode = []byte{byte(vm.PUSH1), 0x00, byte(vm.POP)}
// second contract the sender transacts with
bb = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f44")
bbCode = []byte{byte(vm.PUSH2), 0x00, 0x01, byte(vm.POP)}
)
genesis := &core.Genesis{
Config: params.TestChainConfig,
Alloc: types.GenesisAlloc{
address: {Balance: funds},
aa: {
Code: aaCode,
Nonce: 1,
Balance: big.NewInt(0),
},
bb: {
Code: bbCode,
Nonce: 1,
Balance: big.NewInt(0),
},
},
}
txHashs := make([]common.Hash, 0, 2)
backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) {
b.SetCoinbase(common.Address{1})
// first tx to aa
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &aa,
Value: big.NewInt(0),
Gas: 50000,
GasPrice: b.BaseFee(),
Data: nil,
}), types.HomesteadSigner{}, key)
b.AddTx(tx)
txHashs = append(txHashs, tx.Hash())
// second tx to bb
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 1,
To: &bb,
Value: big.NewInt(1),
Gas: 100000,
GasPrice: b.BaseFee(),
Data: nil,
}), types.HomesteadSigner{}, key)
b.AddTx(tx)
txHashs = append(txHashs, tx.Hash())
})
defer backend.chain.Stop()
var testSuite = []struct {
blockNumber rpc.BlockNumber
config *StdTraceConfig
want []string
}{
{
// test that all traces in the block were outputted if no trace config is specified
blockNumber: rpc.LatestBlockNumber,
config: nil,
want: []string{
`{"pc":0,"op":96,"gas":"0x7148","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":80,"gas":"0x7145","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"POP"}
{"pc":3,"op":0,"gas":"0x7143","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0x5"}
`,
`{"pc":0,"op":97,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}
{"pc":3,"op":80,"gas":"0x13495","gasCost":"0x2","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"POP"}
{"pc":4,"op":0,"gas":"0x13493","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0x5"}
`,
},
},
{
// test that only a specific tx is traced if specified
blockNumber: rpc.LatestBlockNumber,
config: &StdTraceConfig{TxHash: txHashs[1]},
want: []string{
`{"pc":0,"op":97,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}
{"pc":3,"op":80,"gas":"0x13495","gasCost":"0x2","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"POP"}
{"pc":4,"op":0,"gas":"0x13493","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":"0x5"}
`,
},
},
}
api := NewAPI(backend)
for i, tc := range testSuite {
block, _ := api.blockByNumber(context.Background(), tc.blockNumber)
txTraces, err := api.StandardTraceBlockToFile(context.Background(), block.Hash(), tc.config)
if err != nil {
t.Fatalf("test index %d received error %v", i, err)
}
for j, traceFileName := range txTraces {
traceReceived, err := os.ReadFile(traceFileName)
if err != nil {
t.Fatalf("could not read trace file: %v", err)
}
if tc.want[j] != string(traceReceived) {
t.Fatalf("unexpected trace result. expected\n'%s'\n\nreceived\n'%s'\n", tc.want[j], string(traceReceived))
}
}
}
}

View file

@ -131,7 +131,7 @@ func (ec *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumb
}
type rpcBlock struct {
Hash common.Hash `json:"hash"`
Hash *common.Hash `json:"hash"`
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []common.Hash `json:"uncles"`
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
@ -158,6 +158,12 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
if err := json.Unmarshal(raw, &body); err != nil {
return nil, err
}
// Pending blocks don't return a block hash, compute it for sender caching.
if body.Hash == nil {
tmp := head.Hash()
body.Hash = &tmp
}
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles")
@ -199,7 +205,7 @@ func (ec *Client) getBlock(ctx context.Context, method string, args ...interface
txs := make([]*types.Transaction, len(body.Transactions))
for i, tx := range body.Transactions {
if tx.From != nil {
setSenderFromServer(tx.tx, *tx.From, body.Hash)
setSenderFromServer(tx.tx, *tx.From, *body.Hash)
}
txs[i] = tx.tx
}

View file

@ -307,6 +307,12 @@ func testTransactionInBlock(t *testing.T, client *rpc.Client) {
if tx.Hash() != testTx2.Hash() {
t.Fatalf("unexpected transaction: %v", tx)
}
// Test pending block
_, err = ec.BlockByNumber(context.Background(), big.NewInt(int64(rpc.PendingBlockNumber)))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func testChainID(t *testing.T, client *rpc.Client) {
@ -619,6 +625,21 @@ func testAtFunctions(t *testing.T, client *rpc.Client) {
if gas != 21000 {
t.Fatalf("unexpected gas limit: %v", gas)
}
// Verify that sender address of pending transaction is saved in cache.
pendingBlock, err := ec.BlockByNumber(context.Background(), big.NewInt(int64(rpc.PendingBlockNumber)))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// No additional RPC should be required, ensure the server is not asked by
// canceling the context.
sender, err := ec.TransactionSender(newCanceledContext(), pendingBlock.Transactions()[0], pendingBlock.Hash(), 0)
if err != nil {
t.Fatal("unable to recover sender:", err)
}
if sender != testAddr {
t.Fatal("wrong sender:", sender)
}
}
func testTransactionSender(t *testing.T, client *rpc.Client) {
@ -640,10 +661,7 @@ func testTransactionSender(t *testing.T, client *rpc.Client) {
// The sender address is cached in tx1, so no additional RPC should be required in
// TransactionSender. Ensure the server is not asked by canceling the context here.
canceledCtx, cancel := context.WithCancel(context.Background())
cancel()
<-canceledCtx.Done() // Ensure the close of the Done channel
sender1, err := ec.TransactionSender(canceledCtx, tx1, block2.Hash(), 0)
sender1, err := ec.TransactionSender(newCanceledContext(), tx1, block2.Hash(), 0)
if err != nil {
t.Fatal(err)
}
@ -662,6 +680,13 @@ func testTransactionSender(t *testing.T, client *rpc.Client) {
}
}
func newCanceledContext() context.Context {
ctx, cancel := context.WithCancel(context.Background())
cancel()
<-ctx.Done() // Ensure the close of the Done channel
return ctx
}
func sendTransaction(ec *ethclient.Client) error {
chainID, err := ec.ChainID(context.Background())
if err != nil {

View file

@ -66,7 +66,7 @@ type backend interface {
CurrentHeader() *types.Header
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
Stats() (pending int, queued int)
SyncProgress() ethereum.SyncProgress
SyncProgress(ctx context.Context) ethereum.SyncProgress
}
// fullNodeBackend encompasses the functionality necessary for a full node
@ -766,7 +766,7 @@ func (s *Service) reportStats(conn *connWrapper) error {
)
// check if backend is a full node
if fullBackend, ok := s.backend.(fullNodeBackend); ok {
sync := fullBackend.SyncProgress()
sync := fullBackend.SyncProgress(context.Background())
syncing = !sync.Done()
price, _ := fullBackend.SuggestGasTipCap(context.Background())
@ -775,7 +775,7 @@ func (s *Service) reportStats(conn *connWrapper) error {
gasprice += int(basefee.Uint64())
}
} else {
sync := s.backend.SyncProgress()
sync := s.backend.SyncProgress(context.Background())
syncing = !sync.Done()
}
// Assemble the node stats and send it to the server

9
go.mod
View file

@ -13,7 +13,8 @@ require (
github.com/cespare/cp v0.1.0
github.com/cloudflare/cloudflare-go v0.114.0
github.com/cockroachdb/pebble v1.1.2
github.com/consensys/gnark-crypto v0.14.0
github.com/consensys/gnark-crypto v0.16.0
github.com/crate-crypto/go-eth-kzg v1.3.0
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a
github.com/crate-crypto/go-kzg-4844 v1.1.0
github.com/davecgh/go-spew v1.1.1
@ -21,7 +22,7 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
github.com/ethereum/c-kzg-4844 v1.0.0
github.com/ethereum/c-kzg-4844/v2 v2.1.0
github.com/ethereum/go-verkle v0.2.2
github.com/fatih/color v1.16.0
github.com/ferranbt/fastssz v0.1.2
@ -92,14 +93,14 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect
github.com/aws/smithy-go v1.15.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.17.0 // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/consensys/bavard v0.1.22 // indirect
github.com/consensys/bavard v0.1.27 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/deepmap/oapi-codegen v1.6.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect

18
go.sum
View file

@ -90,8 +90,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI=
github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
@ -124,12 +124,14 @@ github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwP
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A=
github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E=
github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0=
github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs=
github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs=
github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw4KoTAawo=
github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI=
github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4=
@ -164,8 +166,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w=
github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E=
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=

View file

@ -229,7 +229,7 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block)
return t.tx, t.block
}
// Try to return an already finalized transaction
found, tx, blockHash, _, index, _ := t.r.backend.GetTransaction(ctx, t.hash)
found, tx, blockHash, _, index := t.r.backend.GetTransaction(t.hash)
if found {
t.tx = tx
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
@ -1530,8 +1530,8 @@ func (s *SyncState) TxIndexRemainingBlocks() hexutil.Uint64 {
// - healingBytecode: number of bytecodes pending
// - txIndexFinishedBlocks: number of blocks whose transactions are indexed
// - txIndexRemainingBlocks: number of blocks whose transactions are not indexed yet
func (r *Resolver) Syncing() (*SyncState, error) {
progress := r.backend.SyncProgress()
func (r *Resolver) Syncing(ctx context.Context) (*SyncState, error) {
progress := r.backend.SyncProgress(ctx)
// Return not syncing if the synchronisation already completed
if progress.Done() {

View file

@ -144,8 +144,8 @@ func (api *EthereumAPI) BlobBaseFee(ctx context.Context) *hexutil.Big {
// - highestBlock: block number of the highest block header this node has received from peers
// - pulledStates: number of state entries processed until now
// - knownStates: number of known state entries that still need to be pulled
func (api *EthereumAPI) Syncing() (interface{}, error) {
progress := api.b.SyncProgress()
func (api *EthereumAPI) Syncing(ctx context.Context) (interface{}, error) {
progress := api.b.SyncProgress(ctx)
// Return not syncing if the synchronisation already completed
if progress.Done() {
@ -1333,17 +1333,19 @@ func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address comm
// GetTransactionByHash returns the transaction for the given hash
func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
// Try to return an already finalized transaction
found, tx, blockHash, blockNumber, index, err := api.b.GetTransaction(ctx, hash)
found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash)
if !found {
// No finalized transaction, try to retrieve it from the pool
if tx := api.b.GetPoolTransaction(hash); tx != nil {
return NewRPCPendingTransaction(tx, api.b.CurrentHeader(), api.b.ChainConfig()), nil
}
if err == nil {
return nil, nil
}
// If also not in the pool there is a chance the tx indexer is still in progress.
if !api.b.TxIndexDone() {
return nil, NewTxIndexingError()
}
// If the transaction is not found in the pool and the indexer is done, return nil
return nil, nil
}
header, err := api.b.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
@ -1354,27 +1356,31 @@ func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
found, tx, _, _, _, err := api.b.GetTransaction(ctx, hash)
found, tx, _, _, _ := api.b.GetTransaction(hash)
if !found {
if tx = api.b.GetPoolTransaction(hash); tx != nil {
return tx.MarshalBinary()
}
if err == nil {
return nil, nil
}
// If also not in the pool there is a chance the tx indexer is still in progress.
if !api.b.TxIndexDone() {
return nil, NewTxIndexingError()
}
// If the transaction is not found in the pool and the indexer is done, return nil
return nil, nil
}
return tx.MarshalBinary()
}
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
found, tx, blockHash, blockNumber, index, err := api.b.GetTransaction(ctx, hash)
if err != nil {
return nil, NewTxIndexingError() // transaction is not fully indexed
}
found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash)
if !found {
return nil, nil // transaction is not existent or reachable
// Make sure indexer is done.
if !api.b.TxIndexDone() {
return nil, NewTxIndexingError()
}
// No such tx.
return nil, nil
}
header, err := api.b.HeaderByHash(ctx, blockHash)
if err != nil {
@ -1774,16 +1780,18 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block
// GetRawTransaction returns the bytes of the transaction for the given hash.
func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
// Retrieve a finalized transaction, or a pooled otherwise
found, tx, _, _, _, err := api.b.GetTransaction(ctx, hash)
found, tx, _, _, _ := api.b.GetTransaction(hash)
if !found {
if tx = api.b.GetPoolTransaction(hash); tx != nil {
return tx.MarshalBinary()
}
if err == nil {
return nil, nil
}
// If also not in the pool there is a chance the tx indexer is still in progress.
if !api.b.TxIndexDone() {
return nil, NewTxIndexingError()
}
// Transaction is not found in the pool and the indexer is done.
return nil, nil
}
return tx.MarshalBinary()
}

View file

@ -472,7 +472,9 @@ func (b *testBackend) setPendingBlock(block *types.Block) {
b.pending = block
}
func (b testBackend) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} }
func (b testBackend) SyncProgress(ctx context.Context) ethereum.SyncProgress {
return ethereum.SyncProgress{}
}
func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return big.NewInt(0), nil
}
@ -589,9 +591,12 @@ func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) even
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
panic("implement me")
}
func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
func (b testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash)
return true, tx, blockHash, blockNumber, index, nil
return true, tx, blockHash, blockNumber, index
}
func (b testBackend) TxIndexDone() bool {
return true
}
func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") }
func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") }
@ -2483,6 +2488,77 @@ func TestSimulateV1ChainLinkage(t *testing.T) {
require.Equal(t, block2.Hash().Bytes(), []byte(results[2].Calls[1].ReturnValue), "returned blockhash for block2 does not match")
}
func TestSimulateV1TxSender(t *testing.T) {
var (
sender = common.Address{0xaa, 0xaa}
sender2 = common.Address{0xaa, 0xab}
sender3 = common.Address{0xaa, 0xac}
recipient = common.Address{0xbb, 0xbb}
gspec = &core.Genesis{
Config: params.MergedTestChainConfig,
Alloc: types.GenesisAlloc{
sender: {Balance: big.NewInt(params.Ether)},
sender2: {Balance: big.NewInt(params.Ether)},
sender3: {Balance: big.NewInt(params.Ether)},
},
}
ctx = context.Background()
)
backend := newTestBackend(t, 0, gspec, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {})
stateDB, baseHeader, err := backend.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
if err != nil {
t.Fatalf("failed to get state and header: %v", err)
}
sim := &simulator{
b: backend,
state: stateDB,
base: baseHeader,
chainConfig: backend.ChainConfig(),
gp: new(core.GasPool).AddGas(math.MaxUint64),
traceTransfers: false,
validate: false,
fullTx: true,
}
results, err := sim.execute(ctx, []simBlock{
{Calls: []TransactionArgs{
{From: &sender, To: &recipient, Value: (*hexutil.Big)(big.NewInt(1000))},
{From: &sender2, To: &recipient, Value: (*hexutil.Big)(big.NewInt(2000))},
{From: &sender3, To: &recipient, Value: (*hexutil.Big)(big.NewInt(3000))},
}},
{Calls: []TransactionArgs{
{From: &sender2, To: &recipient, Value: (*hexutil.Big)(big.NewInt(4000))},
}},
})
if err != nil {
t.Fatalf("simulation execution failed: %v", err)
}
require.Len(t, results, 2, "expected 2 simulated blocks")
require.Len(t, results[0].Block.Transactions(), 3, "expected 3 transaction in simulated block")
require.Len(t, results[1].Block.Transactions(), 1, "expected 1 transaction in 2nd simulated block")
enc, err := json.Marshal(results)
if err != nil {
t.Fatalf("failed to marshal results: %v", err)
}
type resultType struct {
Transactions []struct {
From common.Address `json:"from"`
}
}
var summary []resultType
if err := json.Unmarshal(enc, &summary); err != nil {
t.Fatalf("failed to unmarshal results: %v", err)
}
require.Len(t, summary, 2, "expected 2 simulated blocks")
require.Len(t, summary[0].Transactions, 3, "expected 3 transaction in simulated block")
require.Equal(t, sender, summary[0].Transactions[0].From, "sender address mismatch")
require.Equal(t, sender2, summary[0].Transactions[1].From, "sender address mismatch")
require.Equal(t, sender3, summary[0].Transactions[2].From, "sender address mismatch")
require.Len(t, summary[1].Transactions, 1, "expected 1 transaction in simulated block")
require.Equal(t, sender2, summary[1].Transactions[0].From, "sender address mismatch")
}
func TestSignTransaction(t *testing.T) {
t.Parallel()
// Initialize test accounts

View file

@ -41,7 +41,7 @@ import (
// both full and light clients) with access to necessary functions.
type Backend interface {
// General Ethereum API
SyncProgress() ethereum.SyncProgress
SyncProgress(ctx context.Context) ethereum.SyncProgress
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error)
@ -74,7 +74,8 @@ type Backend interface {
// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error
GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error)
GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
TxIndexDone() bool
GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)

View file

@ -78,11 +78,25 @@ type simBlockResult struct {
chainConfig *params.ChainConfig
Block *types.Block
Calls []simCallResult
// senders is a map of transaction hashes to their senders.
senders map[common.Hash]common.Address
}
func (r *simBlockResult) MarshalJSON() ([]byte, error) {
blockData := RPCMarshalBlock(r.Block, true, r.fullTx, r.chainConfig)
blockData["calls"] = r.Calls
// Set tx sender if user requested full tx objects.
if r.fullTx {
if raw, ok := blockData["transactions"].([]any); ok {
for _, tx := range raw {
if tx, ok := tx.(*RPCTransaction); ok {
tx.From = r.senders[tx.Hash]
} else {
return nil, errors.New("simulated transaction result has invalid type")
}
}
}
}
return json.Marshal(blockData)
}
@ -181,18 +195,18 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
parent = sim.base
)
for bi, block := range blocks {
result, callResults, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
result, callResults, senders, err := sim.processBlock(ctx, &block, headers[bi], parent, headers[:bi], timeout)
if err != nil {
return nil, err
}
headers[bi] = result.Header()
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults}
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders}
parent = result.Header()
}
return results, nil
}
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, error) {
func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, parent *types.Header, headers []*types.Header, timeout time.Duration) (*types.Block, []simCallResult, map[common.Hash]common.Address, error) {
// Set header fields that depend only on parent block.
// Parent hash is needed for evm.GetHashFn to work.
header.ParentHash = parent.Hash()
@ -222,7 +236,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
precompiles := sim.activePrecompiles(sim.base)
// State overrides are applied prior to execution of a block
if err := block.StateOverrides.Apply(sim.state, precompiles); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
var (
gasUsed, blobGasUsed uint64
@ -235,6 +249,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
NoBaseFee: !sim.validate,
Tracer: tracer.Hooks(),
}
// senders is a map of transaction hashes to their senders.
// Transaction objects contain only the signature, and we lose track
// of the sender when translating the arguments into a transaction object.
senders = make(map[common.Hash]common.Address)
)
tracingStateDB := vm.StateDB(sim.state)
if hooks := tracer.Hooks(); hooks != nil {
@ -255,16 +273,17 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
var allLogs []*types.Log
for i, call := range block.Calls {
if err := ctx.Err(); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
var (
tx = call.ToTransaction(types.DynamicFeeTxType)
txHash = tx.Hash()
)
txes[i] = tx
senders[txHash] = call.from()
tracer.reset(txHash, uint(i))
sim.state.SetTxContext(txHash, i)
// EoA check is always skipped, even in validation mode.
@ -272,7 +291,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
if err != nil {
txErr := txValidationError(err)
return nil, nil, txErr
return nil, nil, nil, txErr
}
// Update the state with pending changes.
var root []byte
@ -311,15 +330,15 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
requests = [][]byte{}
// EIP-6110
if err := core.ParseDepositLogs(&requests, allLogs, sim.chainConfig); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
// EIP-7002
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
// EIP-7251
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
}
if requests != nil {
@ -330,10 +349,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
chainHeadReader := &simChainHeadReader{ctx, sim.b}
b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
repairLogs(callResults, b.Hash())
return b, callResults, nil
return b, callResults, senders, nil
}
// repairLogs updates the block hash in the logs present in the result of

View file

@ -323,7 +323,9 @@ func (b *backendMock) CurrentHeader() *types.Header { return b.current }
func (b *backendMock) ChainConfig() *params.ChainConfig { return b.config }
// Other methods needed to implement Backend interface.
func (b *backendMock) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} }
func (b *backendMock) SyncProgress(ctx context.Context) ethereum.SyncProgress {
return ethereum.SyncProgress{}
}
func (b *backendMock) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, []*big.Int, []float64, error) {
return nil, nil, nil, nil, nil, nil, nil
}
@ -378,9 +380,10 @@ func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) eve
return nil
}
func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil }
func (b *backendMock) GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error) {
return false, nil, [32]byte{}, 0, 0, nil
func (b *backendMock) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
return false, nil, [32]byte{}, 0, 0
}
func (b *backendMock) TxIndexDone() bool { return true }
func (b *backendMock) GetPoolTransactions() (types.Transactions, error) { return nil, nil }
func (b *backendMock) GetPoolTransaction(txHash common.Hash) *types.Transaction { return nil }
func (b *backendMock) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {

View file

@ -570,7 +570,6 @@ func TestHTTPWriteTimeout(t *testing.T) {
// Send normal request
t.Run("message", func(t *testing.T) {
resp := rpcRequest(t, url, "test_sleep")
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
@ -584,7 +583,6 @@ func TestHTTPWriteTimeout(t *testing.T) {
t.Run("batch", func(t *testing.T) {
want := fmt.Sprintf("[%s,%s,%s]", greetRes, timeoutRes, timeoutRes)
resp := batchRpcRequest(t, url, []string{"test_greet", "test_sleep", "test_greet"})
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)

View file

@ -358,7 +358,7 @@ var (
Max: 9,
UpdateFraction: 5007716,
}
// DefaultBlobSchedule is the latest configured blob schedule for test chains.
// DefaultBlobSchedule is the latest configured blob schedule for Ethereum mainnet.
DefaultBlobSchedule = &BlobScheduleConfig{
Cancun: DefaultCancunBlobConfig,
Prague: DefaultPragueBlobConfig,

View file

@ -501,6 +501,10 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
if msg.isUnsubscribe() {
callb = h.unsubscribeCb
} else {
// Check method name length
if len(msg.Method) > maxMethodNameLength {
return msg.errorResponse(&invalidRequestError{fmt.Sprintf("method name too long: %d > %d", len(msg.Method), maxMethodNameLength)})
}
callb = h.reg.callback(msg.Method)
}
if callb == nil {
@ -536,6 +540,11 @@ func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMes
return msg.errorResponse(ErrNotificationsUnsupported)
}
// Check method name length
if len(msg.Method) > maxMethodNameLength {
return msg.errorResponse(&invalidRequestError{fmt.Sprintf("subscription name too long: %d > %d", len(msg.Method), maxMethodNameLength)})
}
// Subscription method name is first argument.
name, err := parseSubscriptionName(msg.Params)
if err != nil {

View file

@ -35,6 +35,7 @@ const (
subscribeMethodSuffix = "_subscribe"
unsubscribeMethodSuffix = "_unsubscribe"
notificationMethodSuffix = "_subscription"
maxMethodNameLength = 2048
defaultWriteTimeout = 10 * time.Second // used if context has no deadline
)

View file

@ -391,3 +391,77 @@ func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <-
}
}
}
func TestWebsocketMethodNameLengthLimit(t *testing.T) {
t.Parallel()
var (
srv = newTestServer()
httpsrv = httptest.NewServer(srv.WebsocketHandler([]string{"*"}))
wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:")
)
defer srv.Stop()
defer httpsrv.Close()
client, err := DialWebsocket(context.Background(), wsURL, "")
if err != nil {
t.Fatalf("can't dial: %v", err)
}
defer client.Close()
// Test cases
tests := []struct {
name string
method string
params []interface{}
expectedError string
isSubscription bool
}{
{
name: "valid method name",
method: "test_echo",
params: []interface{}{"test", 1},
expectedError: "",
isSubscription: false,
},
{
name: "method name too long",
method: "test_" + string(make([]byte, maxMethodNameLength+1)),
params: []interface{}{"test", 1},
expectedError: "method name too long",
isSubscription: false,
},
{
name: "valid subscription",
method: "nftest_subscribe",
params: []interface{}{"someSubscription", 1, 2},
expectedError: "",
isSubscription: true,
},
{
name: "subscription name too long",
method: string(make([]byte, maxMethodNameLength+1)) + "_subscribe",
params: []interface{}{"newHeads"},
expectedError: "subscription name too long",
isSubscription: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result interface{}
err := client.Call(&result, tt.method, tt.params...)
if tt.expectedError == "" {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
} else {
if err == nil {
t.Error("expected error, got nil")
} else if !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf("expected error containing %q, got %q", tt.expectedError, err.Error())
}
}
})
}
}

View file

@ -485,11 +485,19 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, valu
if len(keys) != len(values) {
return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values))
}
// Ensure the received batch is monotonic increasing and contains no deletions
// Ensure the received batch is
// - monotonically increasing,
// - not expanding down prefix-paths
// - and contains no deletions
for i := 0; i < len(keys); i++ {
if i < len(keys)-1 && bytes.Compare(keys[i], keys[i+1]) >= 0 {
if i < len(keys)-1 {
if bytes.Compare(keys[i], keys[i+1]) >= 0 {
return false, errors.New("range is not monotonically increasing")
}
if bytes.HasPrefix(keys[i+1], keys[i]) {
return false, errors.New("range contains path prefixes")
}
}
if len(values[i]) == 0 {
return false, errors.New("range contains deletion")
}

View file

@ -1000,3 +1000,35 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) {
t.Error("expected more to be false")
}
}
// TestRangeProofErrors tests a few cases where the prover is supposed
// to exit with errors
func TestRangeProofErrors(t *testing.T) {
// Different number of keys to values
_, err := VerifyRangeProof((common.Hash{}), []byte{}, make([][]byte, 5), make([][]byte, 4), nil)
if have, want := err.Error(), "inconsistent proof data, keys: 5, values: 4"; have != want {
t.Fatalf("wrong error, have %q, want %q", err.Error(), want)
}
// Non-increasing paths
_, err = VerifyRangeProof((common.Hash{}), []byte{},
[][]byte{[]byte{2, 1}, []byte{2, 1}}, make([][]byte, 2), nil)
if have, want := err.Error(), "range is not monotonically increasing"; have != want {
t.Fatalf("wrong error, have %q, want %q", err.Error(), want)
}
// A prefixed path is never motivated. Inserting the second element will
// require rewriting/overwriting the previous value-node, thus can only
// happen if the data is corrupt.
_, err = VerifyRangeProof((common.Hash{}), []byte{},
[][]byte{[]byte{2, 1}, []byte{2, 1, 2}},
[][]byte{[]byte{1}, []byte{1}}, nil)
if have, want := err.Error(), "range contains path prefixes"; have != want {
t.Fatalf("wrong error, have %q, want %q", err.Error(), want)
}
// Empty values (deletions)
_, err = VerifyRangeProof((common.Hash{}), []byte{},
[][]byte{[]byte{2, 1}, []byte{2, 2}},
[][]byte{[]byte{1}, []byte{}}, nil)
if have, want := err.Error(), "range contains deletion"; have != want {
t.Fatalf("wrong error, have %q, want %q", err.Error(), want)
}
}

78
triedb/preimages_test.go Normal file
View file

@ -0,0 +1,78 @@
// Copyright 2023 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 triedb
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/triedb/hashdb"
)
// TestDatabasePreimages tests the preimage functionality of the trie database.
func TestDatabasePreimages(t *testing.T) {
// Create a database with preimages enabled
memDB := rawdb.NewMemoryDatabase()
config := &Config{
Preimages: true,
HashDB: hashdb.Defaults,
}
db := NewDatabase(memDB, config)
defer db.Close()
// Test inserting and retrieving preimages
preimages := make(map[common.Hash][]byte)
for i := 0; i < 10; i++ {
data := []byte{byte(i), byte(i + 1), byte(i + 2)}
hash := common.BytesToHash(data)
preimages[hash] = data
}
// Insert preimages into the database
db.InsertPreimage(preimages)
// Verify all preimages are retrievable
for hash, data := range preimages {
retrieved := db.Preimage(hash)
if retrieved == nil {
t.Errorf("Preimage for %x not found", hash)
}
if !bytes.Equal(retrieved, data) {
t.Errorf("Preimage data mismatch: got %x want %x", retrieved, data)
}
}
// Test non-existent preimage
nonExistentHash := common.HexToHash("deadbeef")
if data := db.Preimage(nonExistentHash); data != nil {
t.Errorf("Unexpected preimage data for non-existent hash: %x", data)
}
// Force preimage commit and verify again
db.WritePreimages()
for hash, data := range preimages {
retrieved := db.Preimage(hash)
if retrieved == nil {
t.Errorf("Preimage for %x not found after forced commit", hash)
}
if !bytes.Equal(retrieved, data) {
t.Errorf("Preimage data mismatch after forced commit: got %x want %x", retrieved, data)
}
}
}

View file

@ -19,6 +19,6 @@ package version
const (
Major = 1 // Major version component of the current release
Minor = 15 // Minor version component of the current release
Patch = 10 // Patch version component of the current release
Patch = 11 // Patch version component of the current release
Meta = "stable" // Version metadata to append to the version string
)