diff --git a/beacon/params/networks.go b/beacon/params/networks.go index 7e73a2c69c..51f67e0c97 100644 --- a/beacon/params/networks.go +++ b/beacon/params/networks.go @@ -41,7 +41,8 @@ var ( AddFork("ALTAIR", 74240, []byte{1, 0, 0, 0}). AddFork("BELLATRIX", 144896, []byte{2, 0, 0, 0}). AddFork("CAPELLA", 194048, []byte{3, 0, 0, 0}). - AddFork("DENEB", 269568, []byte{4, 0, 0, 0}) + AddFork("DENEB", 269568, []byte{4, 0, 0, 0}). + AddFork("ELECTRA", 364032, []byte{5, 0, 0, 0}) SepoliaLightConfig = (&ChainConfig{ GenesisValidatorsRoot: common.HexToHash("0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078"), diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 289eff0a8f..134deee237 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4099,7 +4099,7 @@ func TestEIP7702(t *testing.T) { // The way the auths are combined, it becomes // 1. tx -> addr1 which is delegated to 0xaaaa // 2. addr1:0xaaaa calls into addr2:0xbbbb - // 3. addr2:0xbbbb writes to storage + // 3. addr2:0xbbbb writes to storage auth1, _ := types.SignSetCode(key1, types.SetCodeAuthorization{ ChainID: *uint256.MustFromBig(gspec.Config.ChainID), Address: aa, diff --git a/core/filtermaps/filtermaps.go b/core/filtermaps/filtermaps.go index 920167ca8d..3da7f4b721 100644 --- a/core/filtermaps/filtermaps.go +++ b/core/filtermaps/filtermaps.go @@ -85,11 +85,17 @@ type FilterMaps struct { // fields written by the indexer and read by matcher backend. Indexer can // read them without a lock and write them under indexLock write lock. // Matcher backend can read them under indexLock read lock. - indexLock sync.RWMutex - indexedRange filterMapsRange - cleanedEpochsBefore uint32 // all unindexed data cleaned before this point - indexedView *ChainView // always consistent with the log index - hasTempRange bool + indexLock sync.RWMutex + indexedRange filterMapsRange + indexedView *ChainView // always consistent with the log index + hasTempRange bool + + // cleanedEpochsBefore indicates that all unindexed data before this point + // has been cleaned. + // + // This field is only accessed and modified within tryUnindexTail, so no + // explicit locking is required. + cleanedEpochsBefore uint32 // also accessed by indexer and matcher backend but no locking needed. filterMapCache *lru.Cache[uint32, filterMap] @@ -248,15 +254,16 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f }, // deleting last unindexed epoch might have been interrupted by shutdown cleanedEpochsBefore: max(rs.MapsFirst>>params.logMapsPerEpoch, 1) - 1, - historyCutoff: historyCutoff, - finalBlock: finalBlock, - matcherSyncCh: make(chan *FilterMapsMatcherBackend), - matchers: make(map[*FilterMapsMatcherBackend]struct{}), - filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps), - lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks), - lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers), - baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows), - renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots), + + historyCutoff: historyCutoff, + finalBlock: finalBlock, + matcherSyncCh: make(chan *FilterMapsMatcherBackend), + matchers: make(map[*FilterMapsMatcherBackend]struct{}), + filterMapCache: lru.NewCache[uint32, filterMap](cachedFilterMaps), + lastBlockCache: lru.NewCache[uint32, lastBlockOfMap](cachedLastBlocks), + lvPointerCache: lru.NewCache[uint64, uint64](cachedLvPointers), + baseRowsCache: lru.NewCache[uint64, [][]uint32](cachedBaseRows), + renderSnapshots: lru.NewCache[uint64, *renderedMap](cachedRenderSnapshots), } // Set initial indexer target. @@ -444,6 +451,7 @@ func (f *FilterMaps) safeDeleteWithLogs(deleteFn func(db ethdb.KeyValueStore, ha // setRange updates the indexed chain view and covered range and also adds the // changes to the given batch. +// // Note that this function assumes that the index write lock is being held. func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, newRange filterMapsRange, isTempRange bool) { f.indexedView = newView @@ -477,6 +485,7 @@ func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, ne // Note that this function assumes that the log index structure is consistent // with the canonical chain at the point where the given log value index points. // If this is not the case then an invalid result or an error may be returned. +// // Note that this function assumes that the indexer read lock is being held when // called from outside the indexerLoop goroutine. func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) { @@ -655,6 +664,7 @@ 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. +// // 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) { @@ -762,7 +772,7 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) { return false, errors.New("invalid tail epoch number") } // remove index data - if err := f.safeDeleteWithLogs(func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error { + deleteFn := func(db ethdb.KeyValueStore, hashScheme bool, stopCb func(bool) bool) error { first := f.mapRowIndex(firstMap, 0) count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first if err := rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count), hashScheme, stopCb); err != nil { @@ -786,10 +796,13 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) { f.lvPointerCache.Remove(blockNumber) } return nil - }, fmt.Sprintf("Deleting tail epoch #%d", epoch), func() bool { + } + action := fmt.Sprintf("Deleting tail epoch #%d", epoch) + stopFn := func() bool { f.processEvents() return f.stop || !f.targetHeadIndexed() - }); err == nil { + } + if err := f.safeDeleteWithLogs(deleteFn, action, stopFn); err == nil { // everything removed; mark as cleaned and report success if f.cleanedEpochsBefore == epoch { f.cleanedEpochsBefore = epoch + 1 @@ -808,6 +821,9 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) (bool, error) { } // exportCheckpoints exports epoch checkpoints in the format used by checkpoints.go. +// +// Note: acquiring the indexLock read lock is unnecessary here, as this function +// is always called within the indexLoop. func (f *FilterMaps) exportCheckpoints() { finalLvPtr, err := f.getBlockLvPointer(f.finalBlock + 1) if err != nil { diff --git a/core/filtermaps/indexer.go b/core/filtermaps/indexer.go index 787197345a..3ec49ca116 100644 --- a/core/filtermaps/indexer.go +++ b/core/filtermaps/indexer.go @@ -43,6 +43,8 @@ func (f *FilterMaps) indexerLoop() { log.Info("Started log indexer") for !f.stop { + // Note: acquiring the indexLock read lock is unnecessary here, + // as the `indexedRange` is accessed within the indexerLoop. if !f.indexedRange.initialized { if f.targetView.HeadNumber() == 0 { // initialize when chain head is available @@ -105,7 +107,7 @@ type targetUpdate struct { historyCutoff, finalBlock uint64 } -// SetTargetView sets a new target chain view for the indexer to render. +// SetTarget sets a new target chain view for the indexer to render. // Note that SetTargetView never blocks. func (f *FilterMaps) SetTarget(targetView *ChainView, historyCutoff, finalBlock uint64) { if targetView == nil { @@ -178,6 +180,8 @@ func (f *FilterMaps) processSingleEvent(blocking bool) bool { if f.stop { return false } + // Note: acquiring the indexLock read lock is unnecessary here, + // as this function is always called within the indexLoop. if !f.hasTempRange { for _, mb := range f.matcherSyncRequests { mb.synced() diff --git a/core/filtermaps/matcher_backend.go b/core/filtermaps/matcher_backend.go index ee18a0694c..9751783754 100644 --- a/core/filtermaps/matcher_backend.go +++ b/core/filtermaps/matcher_backend.go @@ -111,17 +111,17 @@ func (fm *FilterMapsMatcherBackend) GetLogByLvIndex(ctx context.Context, lvIndex // synced signals to the matcher that has triggered a synchronisation that it // has been finished and the log index is consistent with the chain head passed // as a parameter. +// // Note that if the log index head was far behind the chain head then it might not // be synced up to the given head in a single step. Still, the latest chain head // should be passed as a parameter and the existing log index should be consistent // with that chain. +// +// Note: acquiring the indexLock read lock is unnecessary here, as this function +// is always called within the indexLoop. func (fm *FilterMapsMatcherBackend) synced() { - fm.f.indexLock.RLock() fm.f.matchersLock.Lock() - defer func() { - fm.f.matchersLock.Unlock() - fm.f.indexLock.RUnlock() - }() + defer fm.f.matchersLock.Unlock() indexedBlocks := fm.f.indexedRange.blocks if !fm.f.indexedRange.headIndexed && !indexedBlocks.IsEmpty() { @@ -154,6 +154,8 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange case <-ctx.Done(): return SyncRange{}, ctx.Err() case <-fm.f.disabledCh: + // Note: acquiring the indexLock read lock is unnecessary here, + // as the indexer has already been terminated. return SyncRange{IndexedView: fm.f.indexedView}, nil } select { @@ -162,6 +164,8 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange case <-ctx.Done(): return SyncRange{}, ctx.Err() case <-fm.f.disabledCh: + // Note: acquiring the indexLock read lock is unnecessary here, + // as the indexer has already been terminated. return SyncRange{IndexedView: fm.f.indexedView}, nil } } @@ -170,7 +174,9 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange // valid range with the current indexed range. This function should be called // whenever a part of the log index has been removed, before adding new blocks // to it. -// Note that this function assumes that the index read lock is being held. +// +// Note: acquiring the indexLock read lock is unnecessary here, as this function +// is always called within the indexLoop. func (f *FilterMaps) updateMatchersValidRange() { f.matchersLock.Lock() defer f.matchersLock.Unlock() diff --git a/core/state_processor_test.go b/core/state_processor_test.go index a6ca2781f8..ffdf063812 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -255,7 +255,8 @@ func TestStateProcessorErrors(t *testing.T) { }, want: "could not apply tx 0 [0xc18d10f4c809dbdfa1a074c3300de9bc4b7f16a20f0ec667f6f67312b71b956a]: EIP-7702 transaction with empty auth list (sender 0x71562b71999873DB5b286dF957af199Ec94617F7)", }, - // ErrSetCodeTxCreate cannot be tested: it is impossible to create a SetCode-tx with nil `to`. + // ErrSetCodeTxCreate cannot be tested here: it is impossible to create a SetCode-tx with nil `to`. + // The EstimateGas API tests test this case. } { block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config, false) _, err := blockchain.InsertChain(types.Blocks{block}) diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 872b3b03dc..352e6abc2c 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -754,6 +754,9 @@ func toCallArg(msg ethereum.CallMsg) interface{} { if msg.BlobHashes != nil { arg["blobVersionedHashes"] = msg.BlobHashes } + if msg.AuthorizationList != nil { + arg["authorizationList"] = msg.AuthorizationList + } return arg } diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index 02b2598b37..39ac4ff20f 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -251,6 +251,9 @@ func toCallArg(msg ethereum.CallMsg) interface{} { if msg.BlobHashes != nil { arg["blobVersionedHashes"] = msg.BlobHashes } + if msg.AuthorizationList != nil { + arg["authorizationList"] = msg.AuthorizationList + } return arg } diff --git a/interfaces.go b/interfaces.go index 53e2e3ae16..54a215d6e7 100644 --- a/interfaces.go +++ b/interfaces.go @@ -156,6 +156,9 @@ type CallMsg struct { // For BlobTxType BlobGasFeeCap *big.Int BlobHashes []common.Hash + + // For SetCodeTxType + AuthorizationList []types.SetCodeAuthorization } // A ContractCaller provides contract calls, essentially transactions that are executed by diff --git a/internal/debug/api.go b/internal/debug/api.go index 5e93585bf8..1bac36e908 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -35,6 +35,7 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/hashicorp/go-bexpr" ) @@ -237,6 +238,24 @@ func (*HandlerT) SetGCPercent(v int) int { return debug.SetGCPercent(v) } +// SetMemoryLimit sets the GOMEMLIMIT for the process. It returns the previous limit. +// Note: +// +// - The input limit is provided as bytes. A negative input does not adjust the limit +// +// - A zero limit or a limit that's lower than the amount of memory used by the Go +// runtime may cause the garbage collector to run nearly continuously. However, +// the application may still make progress. +// +// - Setting the limit too low will cause Geth to become unresponsive. +// +// - Geth also allocates memory off-heap, particularly for fastCache and Pebble, +// which can be non-trivial (a few gigabytes by default). +func (*HandlerT) SetMemoryLimit(limit int64) int64 { + log.Info("Setting memory limit", "size", common.PrettyDuration(limit)) + return debug.SetMemoryLimit(limit) +} + func writeProfile(name, file string) error { p := pprof.Lookup(name) log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index aff051937d..5071e2412f 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -668,6 +668,11 @@ func TestEstimateGas(t *testing.T) { b.SetPoS() })) + setCodeAuthorization, _ := types.SignSetCode(accounts[0].key, types.SetCodeAuthorization{ + Address: accounts[0].addr, + Nonce: uint64(genBlocks + 1), + }) + var testSuite = []struct { blockNumber rpc.BlockNumber call TransactionArgs @@ -846,6 +851,50 @@ func TestEstimateGas(t *testing.T) { }, want: 21000, }, + // Should be able to estimate SetCodeTx. + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(0)), + AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization}, + }, + want: 46000, + }, + // Should retrieve the code of 0xef0001 || accounts[0].addr and return an invalid opcode error. + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[0].addr, + Value: (*hexutil.Big)(big.NewInt(0)), + AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization}, + }, + expectErr: errors.New("invalid opcode: opcode 0xef not defined"), + }, + // SetCodeTx with empty authorization list should fail. + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + To: &common.Address{}, + Value: (*hexutil.Big)(big.NewInt(0)), + AuthorizationList: []types.SetCodeAuthorization{}, + }, + expectErr: core.ErrEmptyAuthList, + }, + // SetCodeTx with nil `to` should fail. + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + To: nil, + Value: (*hexutil.Big)(big.NewInt(0)), + AuthorizationList: []types.SetCodeAuthorization{setCodeAuthorization}, + }, + expectErr: core.ErrSetCodeTxCreate, + }, } for i, tc := range testSuite { result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) @@ -855,7 +904,7 @@ func TestEstimateGas(t *testing.T) { continue } if !errors.Is(err, tc.expectErr) { - if !reflect.DeepEqual(err, tc.expectErr) { + if err.Error() != tc.expectErr.Error() { t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) } } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 8ac8f44958..aed6bbbdb9 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -269,6 +269,11 @@ web3._extend({ call: 'debug_setGCPercent', params: 1, }), + new web3._extend.Method({ + name: 'setMemoryLimit', + call: 'debug_setMemoryLimit', + params: 1, + }), new web3._extend.Method({ name: 'memStats', call: 'debug_memStats', diff --git a/miner/miner.go b/miner/miner.go index 595ef8081c..3cec054d19 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -52,7 +52,7 @@ type Config struct { // DefaultConfig contains default settings for miner. var DefaultConfig = Config{ - GasCeil: 30_000_000, + GasCeil: 36_000_000, GasPrice: big.NewInt(params.GWei / 1000), // The default recommit time is chosen as two seconds since diff --git a/version/version.go b/version/version.go index 53a1bae280..56c0eedc31 100644 --- a/version/version.go +++ b/version/version.go @@ -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 = 9 // Patch version component of the current release + Patch = 10 // Patch version component of the current release Meta = "stable" // Version metadata to append to the version string )