mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
Merge branch 'ethereum:master' into gethintegration
This commit is contained in:
commit
3969e18515
46 changed files with 773 additions and 382 deletions
|
|
@ -939,6 +939,7 @@ var bindTests = []struct {
|
|||
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil {
|
||||
t.Fatalf("block %d, event %d: raise failed: %v", i, j, err)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
}
|
||||
sim.Commit()
|
||||
}
|
||||
|
|
@ -1495,7 +1496,7 @@ var bindTests = []struct {
|
|||
if n != 3 {
|
||||
t.Fatalf("Invalid bar0 event")
|
||||
}
|
||||
case <-time.NewTimer(3 * time.Second).C:
|
||||
case <-time.NewTimer(10 * time.Second).C:
|
||||
t.Fatalf("Wait bar0 event timeout")
|
||||
}
|
||||
|
||||
|
|
@ -1506,7 +1507,7 @@ var bindTests = []struct {
|
|||
if n != 1 {
|
||||
t.Fatalf("Invalid bar event")
|
||||
}
|
||||
case <-time.NewTimer(3 * time.Second).C:
|
||||
case <-time.NewTimer(10 * time.Second).C:
|
||||
t.Fatalf("Wait bar event timeout")
|
||||
}
|
||||
close(stopCh)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0xf5606a019f0f1006e9ec2070695045f4334450362a48da4c5965314510e0b4c3
|
||||
0xd60e5310c5d52ced44cfb13be4e9f22a1e6a6dc56964c3cccd429182d26d72d0
|
||||
|
|
@ -1 +1 @@
|
|||
0x3c0cb4aa83beded1803d262664ba4392b1023f334d9cca02dc3a6925987ebe91
|
||||
0x02f0bb348b0d45f95a9b7e2bb5705768ad06548876cee03d880a2c9dabb1ff88
|
||||
|
|
@ -1 +1 @@
|
|||
0xa8d56457aa414523d93659aef1f7409bbfb72ad75e94d917c8c0b1baa38153ef
|
||||
0xa0dad451a230c01be6f2492980ec5bb412d8cf33351a75e8b172b5b84a5fd03a
|
||||
|
|
@ -363,9 +363,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not parse requests logs: %v", err))
|
||||
}
|
||||
// EIP-7002
|
||||
core.ProcessWithdrawalQueue(&requests, evm)
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
core.ProcessConsolidationQueue(&requests, evm)
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Commit block
|
||||
|
|
|
|||
|
|
@ -328,9 +328,13 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) {
|
|||
blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
|
||||
evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{})
|
||||
// EIP-7002
|
||||
ProcessWithdrawalQueue(&requests, evm)
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process withdrawal requests: %v", err))
|
||||
}
|
||||
// EIP-7251
|
||||
ProcessConsolidationQueue(&requests, evm)
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
panic(fmt.Sprintf("could not process consolidation requests: %v", err))
|
||||
}
|
||||
}
|
||||
return requests
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
package filtermaps
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -39,6 +41,7 @@ type blockchain interface {
|
|||
// of the underlying blockchain, it should only possess the block headers
|
||||
// and receipts up until the expected chain view head.
|
||||
type ChainView struct {
|
||||
lock sync.Mutex
|
||||
chain blockchain
|
||||
headNumber uint64
|
||||
hashes []common.Hash // block hashes starting backwards from headNumber until first canonical hash
|
||||
|
|
@ -55,47 +58,75 @@ func NewChainView(chain blockchain, number uint64, hash common.Hash) *ChainView
|
|||
return cv
|
||||
}
|
||||
|
||||
// getBlockHash returns the block hash belonging to the given block number.
|
||||
// HeadNumber returns the head block number of the chain view.
|
||||
func (cv *ChainView) HeadNumber() uint64 {
|
||||
return cv.headNumber
|
||||
}
|
||||
|
||||
// BlockHash returns the block hash belonging to the given block number.
|
||||
// Note that the hash of the head block is not returned because ChainView might
|
||||
// represent a view where the head block is currently being created.
|
||||
func (cv *ChainView) getBlockHash(number uint64) common.Hash {
|
||||
if number >= cv.headNumber {
|
||||
func (cv *ChainView) BlockHash(number uint64) common.Hash {
|
||||
cv.lock.Lock()
|
||||
defer cv.lock.Unlock()
|
||||
|
||||
if number > cv.headNumber {
|
||||
panic("invalid block number")
|
||||
}
|
||||
return cv.blockHash(number)
|
||||
}
|
||||
|
||||
// getBlockId returns the unique block id belonging to the given block number.
|
||||
// BlockId returns the unique block id belonging to the given block number.
|
||||
// Note that it is currently equal to the block hash. In the future it might
|
||||
// be a different id for future blocks if the log index root becomes part of
|
||||
// consensus and therefore rendering the index with the new head will happen
|
||||
// before the hash of that new head is available.
|
||||
func (cv *ChainView) getBlockId(number uint64) common.Hash {
|
||||
func (cv *ChainView) BlockId(number uint64) common.Hash {
|
||||
cv.lock.Lock()
|
||||
defer cv.lock.Unlock()
|
||||
|
||||
if number > cv.headNumber {
|
||||
panic("invalid block number")
|
||||
}
|
||||
return cv.blockHash(number)
|
||||
}
|
||||
|
||||
// getReceipts returns the set of receipts belonging to the block at the given
|
||||
// Header returns the block header at the given block number.
|
||||
func (cv *ChainView) Header(number uint64) *types.Header {
|
||||
return cv.chain.GetHeader(cv.BlockHash(number), number)
|
||||
}
|
||||
|
||||
// Receipts returns the set of receipts belonging to the block at the given
|
||||
// block number.
|
||||
func (cv *ChainView) getReceipts(number uint64) types.Receipts {
|
||||
if number > cv.headNumber {
|
||||
panic("invalid block number")
|
||||
}
|
||||
blockHash := cv.blockHash(number)
|
||||
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 cv.chain.GetReceiptsByHash(blockHash)
|
||||
}
|
||||
|
||||
// SharedRange returns the block range shared by two chain views.
|
||||
func (cv *ChainView) SharedRange(cv2 *ChainView) common.Range[uint64] {
|
||||
cv.lock.Lock()
|
||||
defer cv.lock.Unlock()
|
||||
|
||||
if cv == nil || cv2 == nil || !cv.extendNonCanonical() || !cv2.extendNonCanonical() {
|
||||
return common.Range[uint64]{}
|
||||
}
|
||||
var sharedLen uint64
|
||||
for n := min(cv.headNumber+1-uint64(len(cv.hashes)), cv2.headNumber+1-uint64(len(cv2.hashes))); n <= cv.headNumber && n <= cv2.headNumber && cv.blockHash(n) == cv2.blockHash(n); n++ {
|
||||
sharedLen = n + 1
|
||||
}
|
||||
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))
|
||||
return NewChainView(cv.chain, newHead, cv.BlockHash(newHead))
|
||||
}
|
||||
|
||||
// equalViews returns true if the two chain views are equivalent.
|
||||
|
|
@ -103,7 +134,7 @@ func equalViews(cv1, cv2 *ChainView) bool {
|
|||
if cv1 == nil || cv2 == nil {
|
||||
return false
|
||||
}
|
||||
return cv1.headNumber == cv2.headNumber && cv1.getBlockId(cv1.headNumber) == cv2.getBlockId(cv2.headNumber)
|
||||
return cv1.headNumber == cv2.headNumber && cv1.BlockId(cv1.headNumber) == cv2.BlockId(cv2.headNumber)
|
||||
}
|
||||
|
||||
// matchViews returns true if the two chain views are equivalent up until the
|
||||
|
|
@ -117,9 +148,9 @@ func matchViews(cv1, cv2 *ChainView, number uint64) bool {
|
|||
return false
|
||||
}
|
||||
if number == cv1.headNumber || number == cv2.headNumber {
|
||||
return cv1.getBlockId(number) == cv2.getBlockId(number)
|
||||
return cv1.BlockId(number) == cv2.BlockId(number)
|
||||
}
|
||||
return cv1.getBlockHash(number) == cv2.getBlockHash(number)
|
||||
return cv1.BlockHash(number) == cv2.BlockHash(number)
|
||||
}
|
||||
|
||||
// extendNonCanonical checks whether the previously known reverse list of head
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@
|
|||
{"blockNumber": 3101669, "blockId": "0xa6e66a18fed64d33178c7088f273c404be597662c34f6e6884cf2e24f3ea4ace", "firstIndex": 1073741353},
|
||||
{"blockNumber": 3221725, "blockId": "0xe771f897dece48b1583cc1d1d10de8015da57407eb1fdf239fdbe46eaab85143", "firstIndex": 1140850137},
|
||||
{"blockNumber": 3357164, "blockId": "0x6252d0aa54c79623b0680069c88d7b5c47983f0d5c4845b6c811b8d9b5e8ff3c", "firstIndex": 1207959453},
|
||||
{"blockNumber": 3447019, "blockId": "0xeb7d585e1e063f3cc05ed399fbf6c2df63c271f62f030acb804e9fb1e74b6dc1", "firstIndex": 1275067542}
|
||||
{"blockNumber": 3447019, "blockId": "0xeb7d585e1e063f3cc05ed399fbf6c2df63c271f62f030acb804e9fb1e74b6dc1", "firstIndex": 1275067542},
|
||||
{"blockNumber": 3546397, "blockId": "0xdabdef7defa4281180a57c5af121877b82274f15ccf074ea0096146f4c246df2", "firstIndex": 1342176778}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -260,5 +260,12 @@
|
|||
{"blockNumber": 21959188, "blockId": "0x6b9c482cc4822af2ad62a359b923062556ab6a046d1a39a8243b764848690601", "firstIndex": 17381193886},
|
||||
{"blockNumber": 21994911, "blockId": "0x0d99ef9dd42dd1c62fc5249eb4f618e7672ab93fa0ba7545c77360371cf972e5", "firstIndex": 17448302795},
|
||||
{"blockNumber": 22026007, "blockId": "0xf7cdc7f6694f2c2ed31fa8a607f65cfa59d0dd7d7424ab5c017f959ae2982c71", "firstIndex": 17515413036},
|
||||
{"blockNumber": 22059890, "blockId": "0x82b768a0dddefda2eefd3a33596ea2be075312f1dd4b01f6b0d377faca2af98b", "firstIndex": 17582521768}
|
||||
{"blockNumber": 22059890, "blockId": "0x82b768a0dddefda2eefd3a33596ea2be075312f1dd4b01f6b0d377faca2af98b", "firstIndex": 17582521768},
|
||||
{"blockNumber": 22090784, "blockId": "0xf97c2eaf9a550360ac24000c0ff17ffa388a2bdd6f73f2f36718e332edfa107a", "firstIndex": 17649630983},
|
||||
{"blockNumber": 22121157, "blockId": "0xa790025235db782e899f23d8b09663ec2d74ec149e4125d62989f98829b08e2d", "firstIndex": 17716724973},
|
||||
{"blockNumber": 22148056, "blockId": "0xbe25ac4f1bdd89a7db5782bf1157e6c4378d80220d67a029397853ef16cd1a4c", "firstIndex": 17783838366},
|
||||
{"blockNumber": 22168652, "blockId": "0x6ae43618c915e636794e2cc2d75dde9992766881c7405fe6479c045ed4bee57e", "firstIndex": 17850956277},
|
||||
{"blockNumber": 22190975, "blockId": "0x9437121647899a4b7b84d67fbea7cc6ff967481c2eab4328ccd86e2cefe19420", "firstIndex": 17918066140},
|
||||
{"blockNumber": 22234357, "blockId": "0x036030830134f9224160d5a0b62da35ec7813dc8855d554bd22e9d38545243ed", "firstIndex": 17985175075},
|
||||
{"blockNumber": 22276736, "blockId": "0x5ceb96d98aa1b4c1c2f2fa253ae9cdb1b04e0420c11bf795400e8762c0a1635c", "firstIndex": 18052284344}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -64,5 +64,9 @@
|
|||
{"blockNumber": 7730079, "blockId": "0x08e0d511a93e2a9c004b3456d77d687ae86247417ada1cdd1f85332a62dfed1d", "firstIndex": 4227846795},
|
||||
{"blockNumber": 7777515, "blockId": "0xeba8faed2e12bb9ed5e7fad19dd82662f15f6f4f512a0d232eedf1e676712428", "firstIndex": 4294966082},
|
||||
{"blockNumber": 7828860, "blockId": "0x62c054bd24be351dc4777460ee922a75fdcea5c9830455cbac6fa29552719c85", "firstIndex": 4362076087},
|
||||
{"blockNumber": 7869890, "blockId": "0x5664bcfa6151f798781e22bea4f9a2c796d097402350cb131e4e3c78e13e461a", "firstIndex": 4429183267}
|
||||
{"blockNumber": 7869890, "blockId": "0x5664bcfa6151f798781e22bea4f9a2c796d097402350cb131e4e3c78e13e461a", "firstIndex": 4429183267},
|
||||
{"blockNumber": 7911722, "blockId": "0x9a85e48e3135c97c51fc1786f2af0596c802e021b6c53cfca65a129cafcd23ed", "firstIndex": 4496287265},
|
||||
{"blockNumber": 7960147, "blockId": "0xc9359cc76d7090e1c8a031108f0ab7a8935d971efd4325fe53612a1d99562f6f", "firstIndex": 4563402388},
|
||||
{"blockNumber": 8030418, "blockId": "0x21867e68cd8327aed2da2601399d60f7f9e41dca4a4f2f9be982e5a2b9304a88", "firstIndex": 4630511616},
|
||||
{"blockNumber": 8087701, "blockId": "0x0fa8c8d7549cc9a8d308262706fe248efe759f8b63511efb1e7f3926e9af2dcb", "firstIndex": 4697614758}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ type FilterMaps struct {
|
|||
|
||||
// test hooks
|
||||
testDisableSnapshots, testSnapshotUsed bool
|
||||
testProcessEventsHook func()
|
||||
}
|
||||
|
||||
// filterMap is a full or partial in-memory representation of a filter map where
|
||||
|
|
@ -262,7 +263,7 @@ func NewFilterMaps(db ethdb.KeyValueStore, initView *ChainView, historyCutoff, f
|
|||
f.targetView = initView
|
||||
if f.indexedRange.initialized {
|
||||
f.indexedView = f.initChainView(f.targetView)
|
||||
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.headNumber+1
|
||||
f.indexedRange.headIndexed = f.indexedRange.blocks.AfterLast() == f.indexedView.HeadNumber()+1
|
||||
if !f.indexedRange.headIndexed {
|
||||
f.indexedRange.headDelimiter = 0
|
||||
}
|
||||
|
|
@ -313,7 +314,7 @@ func (f *FilterMaps) initChainView(chainView *ChainView) *ChainView {
|
|||
log.Error("Could not initialize indexed chain view", "error", err)
|
||||
break
|
||||
}
|
||||
if lastBlockNumber <= chainView.headNumber && chainView.getBlockId(lastBlockNumber) == lastBlockId {
|
||||
if lastBlockNumber <= chainView.HeadNumber() && chainView.BlockId(lastBlockNumber) == lastBlockId {
|
||||
return chainView.limitedView(lastBlockNumber)
|
||||
}
|
||||
}
|
||||
|
|
@ -370,7 +371,7 @@ func (f *FilterMaps) init() error {
|
|||
for min < max {
|
||||
mid := (min + max + 1) / 2
|
||||
cp := checkpointList[mid-1]
|
||||
if cp.BlockNumber <= f.targetView.headNumber && f.targetView.getBlockId(cp.BlockNumber) == cp.BlockId {
|
||||
if cp.BlockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(cp.BlockNumber) == cp.BlockId {
|
||||
min = mid
|
||||
} else {
|
||||
max = mid - 1
|
||||
|
|
@ -512,7 +513,7 @@ func (f *FilterMaps) getLogByLvIndex(lvIndex uint64) (*types.Log, error) {
|
|||
}
|
||||
}
|
||||
// get block receipts
|
||||
receipts := f.indexedView.getReceipts(firstBlockNumber)
|
||||
receipts := f.indexedView.Receipts(firstBlockNumber)
|
||||
if receipts == nil {
|
||||
return nil, fmt.Errorf("failed to retrieve receipts for block %d containing searched log value index %d: %v", firstBlockNumber, lvIndex, err)
|
||||
}
|
||||
|
|
@ -573,7 +574,7 @@ func (f *FilterMaps) getFilterMapRow(mapIndex, rowIndex uint32, baseLayerOnly bo
|
|||
}
|
||||
f.baseRowsCache.Add(baseMapRowIndex, baseRows)
|
||||
}
|
||||
baseRow := baseRows[mapIndex&(f.baseRowGroupLength-1)]
|
||||
baseRow := slices.Clone(baseRows[mapIndex&(f.baseRowGroupLength-1)])
|
||||
if baseLayerOnly {
|
||||
return baseRow, nil
|
||||
}
|
||||
|
|
@ -610,7 +611,9 @@ func (f *FilterMaps) storeFilterMapRowsOfGroup(batch ethdb.Batch, mapIndices []u
|
|||
if uint32(len(mapIndices)) != f.baseRowGroupLength { // skip base rows read if all rows are replaced
|
||||
var ok bool
|
||||
baseRows, ok = f.baseRowsCache.Get(baseMapRowIndex)
|
||||
if !ok {
|
||||
if ok {
|
||||
baseRows = slices.Clone(baseRows)
|
||||
} else {
|
||||
var err error
|
||||
baseRows, err = rawdb.ReadFilterMapBaseRows(f.db, baseMapRowIndex, f.baseRowGroupLength, f.logMapWidth)
|
||||
if err != nil {
|
||||
|
|
@ -656,7 +659,7 @@ func (f *FilterMaps) mapRowIndex(mapIndex, rowIndex uint32) uint64 {
|
|||
// 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, nil
|
||||
return f.indexedRange.headDelimiter + 1, nil
|
||||
}
|
||||
if lvPointer, ok := f.lvPointerCache.Get(blockNumber); ok {
|
||||
return lvPointer, nil
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func (f *FilterMaps) indexerLoop() {
|
|||
|
||||
for !f.stop {
|
||||
if !f.indexedRange.initialized {
|
||||
if f.targetView.headNumber == 0 {
|
||||
if f.targetView.HeadNumber() == 0 {
|
||||
// initialize when chain head is available
|
||||
f.processSingleEvent(true)
|
||||
continue
|
||||
|
|
@ -165,6 +165,9 @@ func (f *FilterMaps) waitForNewHead() {
|
|||
// processEvents processes all events, blocking only if a block processing is
|
||||
// happening and indexing should be suspended.
|
||||
func (f *FilterMaps) processEvents() {
|
||||
if f.testProcessEventsHook != nil {
|
||||
f.testProcessEventsHook()
|
||||
}
|
||||
for f.processSingleEvent(f.blockProcessing) {
|
||||
}
|
||||
}
|
||||
|
|
@ -249,7 +252,7 @@ func (f *FilterMaps) tryIndexHead() error {
|
|||
log.Info("Log index head rendering in progress",
|
||||
"first block", f.indexedRange.blocks.First(), "last block", f.indexedRange.blocks.Last(),
|
||||
"processed", f.indexedRange.blocks.AfterLast()-f.ptrHeadIndex,
|
||||
"remaining", f.indexedView.headNumber-f.indexedRange.blocks.Last(),
|
||||
"remaining", f.indexedView.HeadNumber()-f.indexedRange.blocks.Last(),
|
||||
"elapsed", common.PrettyDuration(time.Since(f.startedHeadIndexAt)))
|
||||
f.loggedHeadIndex = true
|
||||
f.lastLogHeadIndex = time.Now()
|
||||
|
|
@ -418,10 +421,10 @@ func (f *FilterMaps) needTailEpoch(epoch uint32) bool {
|
|||
// tailTargetBlock returns the target value for the tail block number according
|
||||
// to the log history parameter and the current index head.
|
||||
func (f *FilterMaps) tailTargetBlock() uint64 {
|
||||
if f.history == 0 || f.indexedView.headNumber < f.history {
|
||||
if f.history == 0 || f.indexedView.HeadNumber() < f.history {
|
||||
return 0
|
||||
}
|
||||
return f.indexedView.headNumber + 1 - f.history
|
||||
return f.indexedView.HeadNumber() + 1 - f.history
|
||||
}
|
||||
|
||||
// tailPartialBlocks returns the number of rendered blocks in the partially
|
||||
|
|
|
|||
|
|
@ -219,6 +219,58 @@ func testIndexerMatcherView(t *testing.T, concurrentRead bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLogsByIndex(t *testing.T) {
|
||||
ts := newTestSetup(t)
|
||||
defer func() {
|
||||
ts.fm.testProcessEventsHook = nil
|
||||
ts.close()
|
||||
}()
|
||||
|
||||
ts.chain.addBlocks(1000, 10, 3, 4, true)
|
||||
ts.setHistory(0, false)
|
||||
ts.fm.WaitIdle()
|
||||
firstLog := make([]uint64, 1001) // first valid log position per block
|
||||
lastLog := make([]uint64, 1001) // last valid log position per block
|
||||
for i := uint64(0); i <= ts.fm.indexedRange.headDelimiter; i++ {
|
||||
log, err := ts.fm.getLogByLvIndex(i)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting log by index %d: %v", i, err)
|
||||
}
|
||||
if log != nil {
|
||||
if firstLog[log.BlockNumber] == 0 {
|
||||
firstLog[log.BlockNumber] = i
|
||||
}
|
||||
lastLog[log.BlockNumber] = i
|
||||
}
|
||||
}
|
||||
var failed bool
|
||||
ts.fm.testProcessEventsHook = func() {
|
||||
if ts.fm.indexedRange.blocks.IsEmpty() {
|
||||
return
|
||||
}
|
||||
if lvi := firstLog[ts.fm.indexedRange.blocks.First()]; lvi != 0 {
|
||||
log, err := ts.fm.getLogByLvIndex(lvi)
|
||||
if log == nil || err != nil {
|
||||
t.Errorf("Error getting first log of indexed block range: %v", err)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
if lvi := lastLog[ts.fm.indexedRange.blocks.Last()]; lvi != 0 {
|
||||
log, err := ts.fm.getLogByLvIndex(lvi)
|
||||
if log == nil || err != nil {
|
||||
t.Errorf("Error getting last log of indexed block range: %v", err)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
chain := ts.chain.getCanonicalChain()
|
||||
for i := 0; i < 1000 && !failed; i++ {
|
||||
head := rand.Intn(len(chain))
|
||||
ts.chain.setCanonicalChain(chain[:head+1])
|
||||
ts.fm.WaitIdle()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexerCompareDb(t *testing.T) {
|
||||
ts := newTestSetup(t)
|
||||
defer ts.close()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
|
|
@ -107,7 +108,7 @@ func (f *FilterMaps) renderMapsFromSnapshot(cp *renderedMap) (*mapRenderer, erro
|
|||
filterMap: cp.filterMap.fullCopy(),
|
||||
mapIndex: cp.mapIndex,
|
||||
lastBlock: cp.lastBlock,
|
||||
blockLvPtrs: cp.blockLvPtrs,
|
||||
blockLvPtrs: slices.Clone(cp.blockLvPtrs),
|
||||
},
|
||||
finishedMaps: make(map[uint32]*renderedMap),
|
||||
finished: common.NewRange(cp.mapIndex, 0),
|
||||
|
|
@ -143,7 +144,8 @@ func (f *FilterMaps) lastCanonicalSnapshotOfMap(mapIndex uint32) *renderedMap {
|
|||
var best *renderedMap
|
||||
for _, blockNumber := range f.renderSnapshots.Keys() {
|
||||
if cp, _ := f.renderSnapshots.Get(blockNumber); cp != nil && blockNumber < f.indexedRange.blocks.AfterLast() &&
|
||||
blockNumber <= f.targetView.headNumber && f.targetView.getBlockId(blockNumber) == cp.lastBlockId &&
|
||||
blockNumber <= f.indexedView.HeadNumber() && f.indexedView.BlockId(blockNumber) == cp.lastBlockId &&
|
||||
blockNumber <= f.targetView.HeadNumber() && f.targetView.BlockId(blockNumber) == cp.lastBlockId &&
|
||||
cp.mapIndex == mapIndex && (best == nil || blockNumber > best.lastBlock) {
|
||||
best = cp
|
||||
}
|
||||
|
|
@ -172,7 +174,7 @@ func (f *FilterMaps) lastCanonicalMapBoundaryBefore(renderBefore uint32) (nextMa
|
|||
return 0, 0, 0, fmt.Errorf("failed to retrieve last block of reverse iterated map %d: %v", mapIndex, err)
|
||||
}
|
||||
if (f.indexedRange.headIndexed && mapIndex >= f.indexedRange.maps.Last()) ||
|
||||
lastBlock >= f.targetView.headNumber || lastBlockId != f.targetView.getBlockId(lastBlock) {
|
||||
lastBlock >= f.targetView.HeadNumber() || lastBlockId != f.targetView.BlockId(lastBlock) {
|
||||
continue // map is not full or inconsistent with targetView; roll back
|
||||
}
|
||||
lvPtr, err := f.getBlockLvPointer(lastBlock)
|
||||
|
|
@ -243,10 +245,10 @@ func (f *FilterMaps) loadHeadSnapshot() error {
|
|||
}
|
||||
}
|
||||
f.renderSnapshots.Add(f.indexedRange.blocks.Last(), &renderedMap{
|
||||
filterMap: fm,
|
||||
filterMap: fm.fullCopy(),
|
||||
mapIndex: f.indexedRange.maps.Last(),
|
||||
lastBlock: f.indexedRange.blocks.Last(),
|
||||
lastBlockId: f.indexedView.getBlockId(f.indexedRange.blocks.Last()),
|
||||
lastBlockId: f.indexedView.BlockId(f.indexedRange.blocks.Last()),
|
||||
blockLvPtrs: lvPtrs,
|
||||
finished: true,
|
||||
headDelimiter: f.indexedRange.headDelimiter,
|
||||
|
|
@ -263,7 +265,7 @@ func (r *mapRenderer) makeSnapshot() {
|
|||
filterMap: r.currentMap.filterMap.fastCopy(),
|
||||
mapIndex: r.currentMap.mapIndex,
|
||||
lastBlock: r.currentMap.lastBlock,
|
||||
lastBlockId: r.iterator.chainView.getBlockId(r.currentMap.lastBlock),
|
||||
lastBlockId: r.iterator.chainView.BlockId(r.currentMap.lastBlock),
|
||||
blockLvPtrs: r.currentMap.blockLvPtrs,
|
||||
finished: true,
|
||||
headDelimiter: r.iterator.lvIndex,
|
||||
|
|
@ -369,7 +371,7 @@ func (r *mapRenderer) renderCurrentMap(stopCb func() bool) (bool, error) {
|
|||
r.currentMap.finished = true
|
||||
r.currentMap.headDelimiter = r.iterator.lvIndex
|
||||
}
|
||||
r.currentMap.lastBlockId = r.f.targetView.getBlockId(r.currentMap.lastBlock)
|
||||
r.currentMap.lastBlockId = r.f.targetView.BlockId(r.currentMap.lastBlock)
|
||||
totalTime += time.Since(start)
|
||||
mapRenderTimer.Update(totalTime)
|
||||
mapLogValueMeter.Mark(logValuesProcessed)
|
||||
|
|
@ -535,6 +537,7 @@ func (r *mapRenderer) getTempRange() (filterMapsRange, error) {
|
|||
} else {
|
||||
tempRange.blocks.SetAfterLast(0)
|
||||
}
|
||||
tempRange.headIndexed = false
|
||||
tempRange.headDelimiter = 0
|
||||
}
|
||||
return tempRange, nil
|
||||
|
|
@ -565,8 +568,8 @@ func (r *mapRenderer) getUpdatedRange() (filterMapsRange, error) {
|
|||
lm := r.finishedMaps[r.finished.Last()]
|
||||
newRange.headIndexed = lm.finished
|
||||
if lm.finished {
|
||||
newRange.blocks.SetLast(r.f.targetView.headNumber)
|
||||
if lm.lastBlock != r.f.targetView.headNumber {
|
||||
newRange.blocks.SetLast(r.f.targetView.HeadNumber())
|
||||
if lm.lastBlock != r.f.targetView.HeadNumber() {
|
||||
panic("map rendering finished but last block != head block")
|
||||
}
|
||||
newRange.headDelimiter = lm.headDelimiter
|
||||
|
|
@ -664,13 +667,13 @@ var errUnindexedRange = errors.New("unindexed range")
|
|||
// given block's first log value entry (the block delimiter), according to the
|
||||
// current targetView.
|
||||
func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint64) (*logIterator, error) {
|
||||
if blockNumber > f.targetView.headNumber {
|
||||
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.headNumber)
|
||||
if blockNumber > f.targetView.HeadNumber() {
|
||||
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", blockNumber, f.targetView.HeadNumber())
|
||||
}
|
||||
if !f.indexedRange.blocks.Includes(blockNumber) {
|
||||
return nil, errUnindexedRange
|
||||
}
|
||||
finished := blockNumber == f.targetView.headNumber
|
||||
finished := blockNumber == f.targetView.HeadNumber()
|
||||
l := &logIterator{
|
||||
chainView: f.targetView,
|
||||
params: &f.Params,
|
||||
|
|
@ -686,11 +689,11 @@ func (f *FilterMaps) newLogIteratorFromBlockDelimiter(blockNumber, lvIndex uint6
|
|||
// newLogIteratorFromMapBoundary creates a logIterator starting at the given
|
||||
// map boundary, according to the current targetView.
|
||||
func (f *FilterMaps) newLogIteratorFromMapBoundary(mapIndex uint32, startBlock, startLvPtr uint64) (*logIterator, error) {
|
||||
if startBlock > f.targetView.headNumber {
|
||||
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.headNumber)
|
||||
if startBlock > f.targetView.HeadNumber() {
|
||||
return nil, fmt.Errorf("iterator entry point %d after target chain head block %d", startBlock, f.targetView.HeadNumber())
|
||||
}
|
||||
// get block receipts
|
||||
receipts := f.targetView.getReceipts(startBlock)
|
||||
receipts := f.targetView.Receipts(startBlock)
|
||||
if receipts == nil {
|
||||
return nil, fmt.Errorf("receipts not found for start block %d", startBlock)
|
||||
}
|
||||
|
|
@ -757,7 +760,7 @@ func (l *logIterator) next() error {
|
|||
if l.delimiter {
|
||||
l.delimiter = false
|
||||
l.blockNumber++
|
||||
l.receipts = l.chainView.getReceipts(l.blockNumber)
|
||||
l.receipts = l.chainView.Receipts(l.blockNumber)
|
||||
if l.receipts == nil {
|
||||
return fmt.Errorf("receipts not found for block %d", l.blockNumber)
|
||||
}
|
||||
|
|
@ -794,7 +797,7 @@ func (l *logIterator) enforceValidState() {
|
|||
}
|
||||
l.logIndex = 0
|
||||
}
|
||||
if l.blockNumber == l.chainView.headNumber {
|
||||
if l.blockNumber == l.chainView.HeadNumber() {
|
||||
l.finished = true
|
||||
} else {
|
||||
l.delimiter = true
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ type MatcherBackend interface {
|
|||
// all states of the chain since the previous SyncLogIndex or the creation of
|
||||
// the matcher backend.
|
||||
type SyncRange struct {
|
||||
HeadNumber uint64
|
||||
IndexedView *ChainView
|
||||
// block range where the index has not changed since the last matcher sync
|
||||
// and therefore the set of matches found in this region is guaranteed to
|
||||
// be valid and complete.
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func (fm *FilterMapsMatcherBackend) synced() {
|
|||
indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
|
||||
}
|
||||
fm.syncCh <- SyncRange{
|
||||
HeadNumber: fm.f.targetView.headNumber,
|
||||
IndexedView: fm.f.indexedView,
|
||||
ValidBlocks: fm.validBlocks,
|
||||
IndexedBlocks: indexedBlocks,
|
||||
}
|
||||
|
|
@ -154,7 +154,7 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
|
|||
case <-ctx.Done():
|
||||
return SyncRange{}, ctx.Err()
|
||||
case <-fm.f.disabledCh:
|
||||
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
||||
return SyncRange{IndexedView: fm.f.indexedView}, nil
|
||||
}
|
||||
select {
|
||||
case vr := <-syncCh:
|
||||
|
|
@ -162,7 +162,7 @@ func (fm *FilterMapsMatcherBackend) SyncLogIndex(ctx context.Context) (SyncRange
|
|||
case <-ctx.Done():
|
||||
return SyncRange{}, ctx.Err()
|
||||
case <-fm.f.disabledCh:
|
||||
return SyncRange{HeadNumber: fm.f.targetView.headNumber}, nil
|
||||
return SyncRange{IndexedView: fm.f.indexedView}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,9 +113,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
return nil, err
|
||||
}
|
||||
// EIP-7002
|
||||
ProcessWithdrawalQueue(&requests, evm)
|
||||
if err := ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// EIP-7251
|
||||
ProcessConsolidationQueue(&requests, evm)
|
||||
if err := ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
|
|
@ -265,17 +269,17 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) {
|
|||
|
||||
// ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract.
|
||||
// It returns the opaque request data returned by the contract.
|
||||
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) {
|
||||
processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
|
||||
func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||
return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress)
|
||||
}
|
||||
|
||||
// ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract.
|
||||
// It returns the opaque request data returned by the contract.
|
||||
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) {
|
||||
processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
|
||||
func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error {
|
||||
return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress)
|
||||
}
|
||||
|
||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) {
|
||||
func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error {
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
onSystemCallStart(tracer, evm.GetVMContext())
|
||||
if tracer.OnSystemCallEnd != nil {
|
||||
|
|
@ -292,17 +296,20 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte
|
|||
}
|
||||
evm.SetTxContext(NewEVMTxContext(msg))
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
ret, _, _ := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560)
|
||||
evm.StateDB.Finalise(true)
|
||||
if len(ret) == 0 {
|
||||
return // skip empty output
|
||||
if err != nil {
|
||||
return fmt.Errorf("system call failed to execute: %v", err)
|
||||
}
|
||||
if len(ret) == 0 {
|
||||
return nil // skip empty output
|
||||
}
|
||||
|
||||
// Append prefixed requestsData to the requests list.
|
||||
requestsData := make([]byte, len(ret)+1)
|
||||
requestsData[0] = requestType
|
||||
copy(requestsData[1:], ret)
|
||||
*requests = append(*requests, requestsData)
|
||||
return nil
|
||||
}
|
||||
|
||||
var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")
|
||||
|
|
|
|||
|
|
@ -1391,6 +1391,8 @@ func (p *BlobPool) add(tx *types.Transaction) (err error) {
|
|||
switch {
|
||||
case errors.Is(err, txpool.ErrUnderpriced):
|
||||
addUnderpricedMeter.Mark(1)
|
||||
case errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||
addUnderpricedMeter.Mark(1)
|
||||
case errors.Is(err, core.ErrNonceTooLow):
|
||||
addStaleMeter.Mark(1)
|
||||
case errors.Is(err, core.ErrNonceTooHigh):
|
||||
|
|
|
|||
|
|
@ -1484,7 +1484,7 @@ func TestAdd(t *testing.T) {
|
|||
{ // New account, no previous txs, nonce 0, but blob fee cap too low
|
||||
from: "alice",
|
||||
tx: makeUnsignedTx(0, 1, 1, 0),
|
||||
err: txpool.ErrUnderpriced,
|
||||
err: txpool.ErrTxGasPriceTooLow,
|
||||
},
|
||||
{ // Same as above but blob fee cap equals minimum, should be accepted
|
||||
from: "alice",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
package txpool
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAlreadyKnown is returned if the transactions is already contained
|
||||
|
|
@ -26,14 +28,19 @@ var (
|
|||
// ErrInvalidSender is returned if the transaction contains an invalid signature.
|
||||
ErrInvalidSender = errors.New("invalid sender")
|
||||
|
||||
// ErrUnderpriced is returned if a transaction's gas price is below the minimum
|
||||
// configured for the transaction pool.
|
||||
// ErrUnderpriced is returned if a transaction's gas price is too low to be
|
||||
// included in the pool. If the gas price is lower than the minimum configured
|
||||
// one for the transaction pool, use ErrTxGasPriceTooLow instead.
|
||||
ErrUnderpriced = errors.New("transaction underpriced")
|
||||
|
||||
// ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced
|
||||
// with a different one without the required price bump.
|
||||
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
||||
|
||||
// ErrTxGasPriceTooLow is returned if a transaction's gas price is below the
|
||||
// minimum configured for the transaction pool.
|
||||
ErrTxGasPriceTooLow = errors.New("transaction gas price below minimum")
|
||||
|
||||
// ErrAccountLimitExceeded is returned if a transaction would exceed the number
|
||||
// allowed by a pool for a single account.
|
||||
ErrAccountLimitExceeded = errors.New("account limit exceeded")
|
||||
|
|
|
|||
|
|
@ -82,12 +82,14 @@ func TestTransactionFutureAttack(t *testing.T) {
|
|||
// Create the pool to test the limit enforcement with
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
|
||||
|
||||
config := testTxPoolConfig
|
||||
config.GlobalQueue = 100
|
||||
config.GlobalSlots = 100
|
||||
pool := New(config, blockchain)
|
||||
pool.Init(config.PriceLimit, blockchain.CurrentBlock(), newReserver())
|
||||
defer pool.Close()
|
||||
|
||||
fillPool(t, pool)
|
||||
pending, _ := pool.Stats()
|
||||
// Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops
|
||||
|
|
@ -180,7 +182,9 @@ func TestTransactionZAttack(t *testing.T) {
|
|||
ivPending := countInvalidPending()
|
||||
t.Logf("invalid pending: %d\n", ivPending)
|
||||
|
||||
// Now, DETER-Z attack starts, let's add a bunch of expensive non-executables (from N accounts) along with balance-overdraft txs (from one account), and see if the pending-count drops
|
||||
// Now, DETER-Z attack starts, let's add a bunch of expensive non-executables
|
||||
// (from N accounts) along with balance-overdraft txs (from one account), and
|
||||
// see if the pending-count drops
|
||||
for j := 0; j < int(pool.config.GlobalQueue); j++ {
|
||||
futureTxs := types.Transactions{}
|
||||
key, _ := crypto.GenerateKey()
|
||||
|
|
|
|||
|
|
@ -413,7 +413,7 @@ func TestInvalidTransactions(t *testing.T) {
|
|||
|
||||
tx = transaction(1, 100000, key)
|
||||
pool.gasTip.Store(uint256.NewInt(1000))
|
||||
if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) {
|
||||
if err, want := pool.addRemote(tx), txpool.ErrTxGasPriceTooLow; !errors.Is(err, want) {
|
||||
t.Errorf("want %v have %v", want, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -484,7 +484,7 @@ func TestNegativeValue(t *testing.T) {
|
|||
tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key)
|
||||
from, _ := deriveSender(tx)
|
||||
testAddBalance(pool, from, big.NewInt(1))
|
||||
if err := pool.addRemote(tx); err != txpool.ErrNegativeValue {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrNegativeValue) {
|
||||
t.Error("expected", txpool.ErrNegativeValue, "got", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -497,7 +497,7 @@ func TestTipAboveFeeCap(t *testing.T) {
|
|||
|
||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key)
|
||||
|
||||
if err := pool.addRemote(tx); err != core.ErrTipAboveFeeCap {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, core.ErrTipAboveFeeCap) {
|
||||
t.Error("expected", core.ErrTipAboveFeeCap, "got", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -512,12 +512,12 @@ func TestVeryHighValues(t *testing.T) {
|
|||
veryBigNumber.Lsh(veryBigNumber, 300)
|
||||
|
||||
tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key)
|
||||
if err := pool.addRemote(tx); err != core.ErrTipVeryHigh {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, core.ErrTipVeryHigh) {
|
||||
t.Error("expected", core.ErrTipVeryHigh, "got", err)
|
||||
}
|
||||
|
||||
tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key)
|
||||
if err := pool.addRemote(tx2); err != core.ErrFeeCapVeryHigh {
|
||||
if err := pool.addRemote(tx2); !errors.Is(err, core.ErrFeeCapVeryHigh) {
|
||||
t.Error("expected", core.ErrFeeCapVeryHigh, "got", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1424,14 +1424,14 @@ func TestRepricing(t *testing.T) {
|
|||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Check that we can't add the old transactions back
|
||||
if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
||||
if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
|
|
@ -1476,14 +1476,14 @@ func TestMinGasPriceEnforced(t *testing.T) {
|
|||
tx := pricedTransaction(0, 100000, big.NewInt(2), key)
|
||||
pool.SetGasTip(big.NewInt(tx.GasPrice().Int64() + 1))
|
||||
|
||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("Min tip not enforced")
|
||||
}
|
||||
|
||||
tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key)
|
||||
pool.SetGasTip(big.NewInt(tx.GasTipCap().Int64() + 1))
|
||||
|
||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
if err := pool.Add([]*types.Transaction{tx}, true)[0]; !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("Min tip not enforced")
|
||||
}
|
||||
}
|
||||
|
|
@ -1560,16 +1560,16 @@ func TestRepricingDynamicFee(t *testing.T) {
|
|||
}
|
||||
// Check that we can't add the old transactions back
|
||||
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0])
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||
}
|
||||
tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1])
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||
}
|
||||
tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2])
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced)
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrTxGasPriceTooLow)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
|
|
@ -1673,7 +1673,7 @@ func TestUnderpricing(t *testing.T) {
|
|||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
// Ensure that replacing a pending transaction with a future transaction fails
|
||||
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != ErrFutureReplacePending {
|
||||
if err := pool.addRemoteSync(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); !errors.Is(err, ErrFutureReplacePending) {
|
||||
t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending)
|
||||
}
|
||||
pending, queued = pool.Stats()
|
||||
|
|
@ -1995,7 +1995,7 @@ func TestReplacement(t *testing.T) {
|
|||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil {
|
||||
t.Fatalf("failed to add original cheap pending transaction: %v", err)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil {
|
||||
|
|
@ -2008,7 +2008,7 @@ func TestReplacement(t *testing.T) {
|
|||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil {
|
||||
t.Fatalf("failed to add original proper pending transaction: %v", err)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil {
|
||||
|
|
@ -2022,7 +2022,7 @@ func TestReplacement(t *testing.T) {
|
|||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil {
|
||||
t.Fatalf("failed to add original cheap queued transaction: %v", err)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil {
|
||||
|
|
@ -2032,7 +2032,7 @@ func TestReplacement(t *testing.T) {
|
|||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil {
|
||||
t.Fatalf("failed to add original proper queued transaction: %v", err)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil {
|
||||
|
|
@ -2096,7 +2096,7 @@ func TestReplacementDynamicFee(t *testing.T) {
|
|||
}
|
||||
// 2. Don't bump tip or feecap => discard
|
||||
tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key)
|
||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
// 3. Bump both more than min => accept
|
||||
|
|
@ -2117,24 +2117,25 @@ func TestReplacementDynamicFee(t *testing.T) {
|
|||
if err := pool.addRemoteSync(tx); err != nil {
|
||||
t.Fatalf("failed to add original proper %s transaction: %v", stage, err)
|
||||
}
|
||||
|
||||
// 6. Bump tip max allowed so it's still underpriced => discard
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold-1), key)
|
||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
// 7. Bump fee cap max allowed so it's still underpriced => discard
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key)
|
||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
// 8. Bump tip min for acceptance => accept
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key)
|
||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
// 9. Bump fee cap min for acceptance => accept
|
||||
tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key)
|
||||
if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced {
|
||||
if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced)
|
||||
}
|
||||
// 10. Check events match expected (3 new executable txs during pending, 0 during queue)
|
||||
|
|
|
|||
46
core/txpool/locals/errors.go
Normal file
46
core/txpool/locals/errors.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package locals
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||
)
|
||||
|
||||
// IsTemporaryReject determines whether the given error indicates a temporary
|
||||
// reason to reject a transaction from being included in the txpool. The result
|
||||
// may change if the txpool's state changes later.
|
||||
func IsTemporaryReject(err error) bool {
|
||||
switch {
|
||||
case errors.Is(err, legacypool.ErrOutOfOrderTxFromDelegated):
|
||||
return true
|
||||
case errors.Is(err, txpool.ErrInflightTxLimitReached):
|
||||
return true
|
||||
case errors.Is(err, legacypool.ErrAuthorityReserved):
|
||||
return true
|
||||
case errors.Is(err, txpool.ErrUnderpriced):
|
||||
return true
|
||||
case errors.Is(err, legacypool.ErrTxPoolOverflow):
|
||||
return true
|
||||
case errors.Is(err, legacypool.ErrFutureReplacePending):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -74,32 +74,22 @@ func New(journalPath string, journalTime time.Duration, chainConfig *params.Chai
|
|||
|
||||
// Track adds a transaction to the tracked set.
|
||||
// Note: blob-type transactions are ignored.
|
||||
func (tracker *TxTracker) Track(tx *types.Transaction) error {
|
||||
return tracker.TrackAll([]*types.Transaction{tx})[0]
|
||||
func (tracker *TxTracker) Track(tx *types.Transaction) {
|
||||
tracker.TrackAll([]*types.Transaction{tx})
|
||||
}
|
||||
|
||||
// TrackAll adds a list of transactions to the tracked set.
|
||||
// Note: blob-type transactions are ignored.
|
||||
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
||||
func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
|
||||
var errors []error
|
||||
for _, tx := range txs {
|
||||
if tx.Type() == types.BlobTxType {
|
||||
errors = append(errors, nil)
|
||||
continue
|
||||
}
|
||||
// Ignore the transactions which are failed for fundamental
|
||||
// validation such as invalid parameters.
|
||||
if err := tracker.pool.ValidateTxBasics(tx); err != nil {
|
||||
log.Debug("Invalid transaction submitted", "hash", tx.Hash(), "err", err)
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
// If we're already tracking it, it's a no-op
|
||||
if _, ok := tracker.all[tx.Hash()]; ok {
|
||||
errors = append(errors, nil)
|
||||
continue
|
||||
}
|
||||
// Theoretically, checking the error here is unnecessary since sender recovery
|
||||
|
|
@ -108,11 +98,8 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
|||
// Therefore, the error is still checked just in case.
|
||||
addr, err := types.Sender(tracker.signer, tx)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
errors = append(errors, nil)
|
||||
|
||||
tracker.all[tx.Hash()] = tx
|
||||
if tracker.byAddr[addr] == nil {
|
||||
tracker.byAddr[addr] = legacypool.NewSortedMap()
|
||||
|
|
@ -124,7 +111,6 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) []error {
|
|||
}
|
||||
}
|
||||
localGauge.Update(int64(len(tracker.all)))
|
||||
return errors
|
||||
}
|
||||
|
||||
// recheck checks and returns any transactions that needs to be resubmitted.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package locals
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -91,10 +90,12 @@ func (env *testEnv) close() {
|
|||
env.chain.Stop()
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func (env *testEnv) setGasTip(gasTip uint64) {
|
||||
env.pool.SetGasTip(new(big.Int).SetUint64(gasTip))
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func (env *testEnv) makeTx(nonce uint64, gasPrice *big.Int) *types.Transaction {
|
||||
if nonce == 0 {
|
||||
head := env.chain.CurrentHeader()
|
||||
|
|
@ -121,6 +122,7 @@ func (env *testEnv) makeTxs(n int) []*types.Transaction {
|
|||
return txs
|
||||
}
|
||||
|
||||
// nolint:unused
|
||||
func (env *testEnv) commit() {
|
||||
head := env.chain.CurrentBlock()
|
||||
block := env.chain.GetBlock(head.Hash(), head.Number.Uint64())
|
||||
|
|
@ -137,60 +139,6 @@ func (env *testEnv) commit() {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRejectInvalids(t *testing.T) {
|
||||
env := newTestEnv(t, 10, 0, "")
|
||||
defer env.close()
|
||||
|
||||
var cases = []struct {
|
||||
gasTip uint64
|
||||
tx *types.Transaction
|
||||
expErr error
|
||||
commit bool
|
||||
}{
|
||||
{
|
||||
tx: env.makeTx(5, nil), // stale
|
||||
expErr: core.ErrNonceTooLow,
|
||||
},
|
||||
{
|
||||
tx: env.makeTx(11, nil), // future transaction
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
gasTip: params.GWei,
|
||||
tx: env.makeTx(0, new(big.Int).SetUint64(params.GWei/2)), // low price
|
||||
expErr: txpool.ErrUnderpriced,
|
||||
},
|
||||
{
|
||||
tx: types.NewTransaction(10, common.Address{0x00}, big.NewInt(1000), params.TxGas, big.NewInt(params.GWei), nil), // invalid signature
|
||||
expErr: types.ErrInvalidSig,
|
||||
},
|
||||
{
|
||||
commit: true,
|
||||
tx: env.makeTx(10, nil), // stale
|
||||
expErr: core.ErrNonceTooLow,
|
||||
},
|
||||
{
|
||||
tx: env.makeTx(11, nil),
|
||||
expErr: nil,
|
||||
},
|
||||
}
|
||||
for i, c := range cases {
|
||||
if c.gasTip != 0 {
|
||||
env.setGasTip(c.gasTip)
|
||||
}
|
||||
if c.commit {
|
||||
env.commit()
|
||||
}
|
||||
gotErr := env.tracker.Track(c.tx)
|
||||
if c.expErr == nil && gotErr != nil {
|
||||
t.Fatalf("%d, unexpected error: %v", i, gotErr)
|
||||
}
|
||||
if c.expErr != nil && !errors.Is(gotErr, c.expErr) {
|
||||
t.Fatalf("%d, unexpected error, want: %v, got: %v", i, c.expErr, gotErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResubmit(t *testing.T) {
|
||||
env := newTestEnv(t, 10, 0, "")
|
||||
defer env.close()
|
||||
|
|
|
|||
|
|
@ -324,31 +324,6 @@ func (p *TxPool) GetBlobs(vhashes []common.Hash) ([]*kzg4844.Blob, []*kzg4844.Pr
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// ValidateTxBasics checks whether a transaction is valid according to the consensus
|
||||
// rules, but does not check state-dependent validation such as sufficient balance.
|
||||
func (p *TxPool) ValidateTxBasics(tx *types.Transaction) error {
|
||||
addr, err := types.Sender(p.signer, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Reject transactions with stale nonce. Gapped-nonce future transactions
|
||||
// are considered valid and will be handled by the subpool according to its
|
||||
// internal policy.
|
||||
p.stateLock.RLock()
|
||||
nonce := p.state.GetNonce(addr)
|
||||
p.stateLock.RUnlock()
|
||||
|
||||
if nonce > tx.Nonce() {
|
||||
return core.ErrNonceTooLow
|
||||
}
|
||||
for _, subpool := range p.subpools {
|
||||
if subpool.Filter(tx) {
|
||||
return subpool.ValidateTxBasics(tx)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%w: received type %d", core.ErrTxTypeNotSupported, tx.Type())
|
||||
}
|
||||
|
||||
// Add enqueues a batch of transactions into the pool if they are valid. Due
|
||||
// to the large transaction churn, add may postpone fully integrating the tx
|
||||
// to a later point to batch multiple ones together.
|
||||
|
|
|
|||
|
|
@ -131,12 +131,12 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
||||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrUnderpriced, tx.GasTipCap(), opts.MinTip)
|
||||
return fmt.Errorf("%w: gas tip cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.GasTipCap(), opts.MinTip)
|
||||
}
|
||||
if tx.Type() == types.BlobTxType {
|
||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrUnderpriced, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||
}
|
||||
sidecar := tx.BlobTxSidecar()
|
||||
if sidecar == nil {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/locals"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||
|
|
@ -307,19 +308,24 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
|
|||
}
|
||||
|
||||
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
||||
locals := b.eth.localTxTracker
|
||||
if locals != nil {
|
||||
if err := locals.Track(signedTx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// No error will be returned to user if the transaction fails stateful
|
||||
// validation (e.g., no available slot), as the locally submitted transactions
|
||||
// may be resubmitted later via the local tracker.
|
||||
err := b.eth.txPool.Add([]*types.Transaction{signedTx}, false)[0]
|
||||
if err != nil && locals == nil {
|
||||
|
||||
// If the local transaction tracker is not configured, returns whatever
|
||||
// returned from the txpool.
|
||||
if b.eth.localTxTracker == nil {
|
||||
return err
|
||||
}
|
||||
// If the transaction fails with an error indicating it is invalid, or if there is
|
||||
// very little chance it will be accepted later (e.g., the gas price is below the
|
||||
// configured minimum, or the sender has insufficient funds to cover the cost),
|
||||
// propagate the error to the user.
|
||||
if err != nil && !locals.IsTemporaryReject(err) {
|
||||
return err
|
||||
}
|
||||
// No error will be returned to user if the transaction fails with a temporary
|
||||
// error and might be accepted later (e.g., the transaction pool is full).
|
||||
// Locally submitted transactions will be resubmitted later via the local tracker.
|
||||
b.eth.localTxTracker.Track(signedTx)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -437,6 +443,14 @@ func (b *EthAPIBackend) RPCTxFeeCap() float64 {
|
|||
return b.eth.config.RPCTxFeeCap
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) CurrentView() *filtermaps.ChainView {
|
||||
head := b.eth.blockchain.CurrentBlock()
|
||||
if head == nil {
|
||||
return nil
|
||||
}
|
||||
return filtermaps.NewChainView(b.eth.blockchain, head.Number.Uint64(), head.Hash())
|
||||
}
|
||||
|
||||
func (b *EthAPIBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
||||
return b.eth.filterMaps.NewMatcherBackend()
|
||||
}
|
||||
|
|
|
|||
157
eth/api_backend_test.go
Normal file
157
eth/api_backend_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/locals"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(1000_000_000_000_000)
|
||||
gspec = &core.Genesis{
|
||||
Config: params.MergedTestChainConfig,
|
||||
Alloc: types.GenesisAlloc{
|
||||
address: {Balance: funds},
|
||||
},
|
||||
Difficulty: common.Big0,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
signer = types.LatestSignerForChainID(gspec.Config.ChainID)
|
||||
)
|
||||
|
||||
func initBackend(withLocal bool) *EthAPIBackend {
|
||||
var (
|
||||
// Create a database pre-initialize with a genesis block
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
)
|
||||
chain, _ := core.NewBlockChain(db, nil, gspec, nil, engine, vm.Config{}, nil)
|
||||
|
||||
txconfig := legacypool.DefaultConfig
|
||||
txconfig.Journal = "" // Don't litter the disk with test journals
|
||||
|
||||
blobPool := blobpool.New(blobpool.Config{Datadir: ""}, chain, nil)
|
||||
legacyPool := legacypool.New(txconfig, chain)
|
||||
txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{legacyPool, blobPool})
|
||||
|
||||
eth := &Ethereum{
|
||||
blockchain: chain,
|
||||
txPool: txpool,
|
||||
}
|
||||
if withLocal {
|
||||
eth.localTxTracker = locals.New("", time.Minute, gspec.Config, txpool)
|
||||
}
|
||||
return &EthAPIBackend{
|
||||
eth: eth,
|
||||
}
|
||||
}
|
||||
|
||||
func makeTx(nonce uint64, gasPrice *big.Int, amount *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
|
||||
if gasPrice == nil {
|
||||
gasPrice = big.NewInt(params.GWei)
|
||||
}
|
||||
if amount == nil {
|
||||
amount = big.NewInt(1000)
|
||||
}
|
||||
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{0x00}, amount, params.TxGas, gasPrice, nil), signer, key)
|
||||
return tx
|
||||
}
|
||||
|
||||
type unsignedAuth struct {
|
||||
nonce uint64
|
||||
key *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
|
||||
var authList []types.SetCodeAuthorization
|
||||
for _, u := range unsigned {
|
||||
auth, _ := types.SignSetCode(u.key, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Address: common.Address{0x42},
|
||||
Nonce: u.nonce,
|
||||
})
|
||||
authList = append(authList, auth)
|
||||
}
|
||||
return pricedSetCodeTxWithAuth(nonce, gaslimit, gasFee, tip, key, authList)
|
||||
}
|
||||
|
||||
func pricedSetCodeTxWithAuth(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, authList []types.SetCodeAuthorization) *types.Transaction {
|
||||
return types.MustSignNewTx(key, signer, &types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
|
||||
Nonce: nonce,
|
||||
GasTipCap: tip,
|
||||
GasFeeCap: gasFee,
|
||||
Gas: gaslimit,
|
||||
To: common.Address{},
|
||||
Value: uint256.NewInt(100),
|
||||
Data: nil,
|
||||
AccessList: nil,
|
||||
AuthList: authList,
|
||||
})
|
||||
}
|
||||
|
||||
func TestSendTx(t *testing.T) {
|
||||
testSendTx(t, false)
|
||||
testSendTx(t, true)
|
||||
}
|
||||
|
||||
func testSendTx(t *testing.T, withLocal bool) {
|
||||
b := initBackend(withLocal)
|
||||
|
||||
txA := pricedSetCodeTx(0, 250000, uint256.NewInt(params.GWei), uint256.NewInt(params.GWei), key, []unsignedAuth{
|
||||
{
|
||||
nonce: 0,
|
||||
key: key,
|
||||
},
|
||||
})
|
||||
b.SendTx(context.Background(), txA)
|
||||
|
||||
txB := makeTx(1, nil, nil, key)
|
||||
err := b.SendTx(context.Background(), txB)
|
||||
|
||||
if withLocal {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error sending tx: %v", err)
|
||||
}
|
||||
} else {
|
||||
if !errors.Is(err, txpool.ErrInflightTxLimitReached) {
|
||||
t.Fatalf("Unexpected error, want: %v, got: %v", txpool.ErrInflightTxLimitReached, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -124,9 +125,13 @@ func NewSimulatedBeacon(period uint64, feeRecipient common.Address, eth *eth.Eth
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// cap the dev mode period to a reasonable maximum value to avoid
|
||||
// overflowing the time.Duration (int64) that it will occupy
|
||||
const maxPeriod = uint64(math.MaxInt64 / time.Second)
|
||||
return &SimulatedBeacon{
|
||||
eth: eth,
|
||||
period: period,
|
||||
period: min(period, maxPeriod),
|
||||
shutdownCh: make(chan struct{}),
|
||||
engineAPI: engineAPI,
|
||||
lastBlockTime: block.Time,
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
|||
// Track the transaction hash if the price is too low for us.
|
||||
// Avoid re-request this transaction when we receive another
|
||||
// announcement.
|
||||
if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) {
|
||||
if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow) {
|
||||
f.underpriced.Add(batch[j].Hash(), batch[j].Time())
|
||||
}
|
||||
// Track a few interesting failure types
|
||||
|
|
@ -355,7 +355,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
|||
case errors.Is(err, txpool.ErrAlreadyKnown):
|
||||
duplicate++
|
||||
|
||||
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced):
|
||||
case errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) || errors.Is(err, txpool.ErrTxGasPriceTooLow):
|
||||
underpriced++
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1244,10 +1244,12 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) {
|
|||
func(txs []*types.Transaction) []error {
|
||||
errs := make([]error, len(txs))
|
||||
for i := 0; i < len(errs); i++ {
|
||||
if i%2 == 0 {
|
||||
if i%3 == 0 {
|
||||
errs[i] = txpool.ErrUnderpriced
|
||||
} else {
|
||||
} else if i%3 == 1 {
|
||||
errs[i] = txpool.ErrReplaceUnderpriced
|
||||
} else {
|
||||
errs[i] = txpool.ErrTxGasPriceTooLow
|
||||
}
|
||||
}
|
||||
return errs
|
||||
|
|
|
|||
|
|
@ -146,25 +146,29 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
|
|||
}
|
||||
|
||||
const (
|
||||
rangeLogsTestSync = iota
|
||||
rangeLogsTestTrimmed
|
||||
rangeLogsTestIndexed
|
||||
rangeLogsTestUnindexed
|
||||
rangeLogsTestDone
|
||||
rangeLogsTestDone = iota // zero range
|
||||
rangeLogsTestSync // before sync; zero range
|
||||
rangeLogsTestSynced // after sync; valid blocks range
|
||||
rangeLogsTestIndexed // individual search range
|
||||
rangeLogsTestUnindexed // individual search range
|
||||
rangeLogsTestResults // results range after search iteration
|
||||
rangeLogsTestReorg // results range trimmed by reorg
|
||||
)
|
||||
|
||||
type rangeLogsTestEvent struct {
|
||||
event int
|
||||
begin, end uint64
|
||||
event int
|
||||
blocks common.Range[uint64]
|
||||
}
|
||||
|
||||
// searchSession represents a single search session.
|
||||
type searchSession struct {
|
||||
ctx context.Context
|
||||
filter *Filter
|
||||
mb filtermaps.MatcherBackend
|
||||
syncRange filtermaps.SyncRange // latest synchronized state with the matcher
|
||||
firstBlock, lastBlock uint64 // specified search range; each can be MaxUint64
|
||||
ctx context.Context
|
||||
filter *Filter
|
||||
mb filtermaps.MatcherBackend
|
||||
syncRange filtermaps.SyncRange // latest synchronized state with the matcher
|
||||
chainView *filtermaps.ChainView // can be more recent than the indexed view in syncRange
|
||||
// block ranges always refer to the current chainView
|
||||
firstBlock, lastBlock uint64 // specified search range; MaxUint64 means latest block
|
||||
searchRange common.Range[uint64] // actual search range; end trimmed to latest head
|
||||
matchRange common.Range[uint64] // range in which we have results (subset of searchRange)
|
||||
matches []*types.Log // valid set of matches in matchRange
|
||||
|
|
@ -182,84 +186,99 @@ func newSearchSession(ctx context.Context, filter *Filter, mb filtermaps.Matcher
|
|||
}
|
||||
// enforce a consistent state before starting the search in order to be able
|
||||
// to determine valid range later
|
||||
if err := s.syncMatcher(0); err != nil {
|
||||
var err error
|
||||
s.syncRange, err = s.mb.SyncLogIndex(s.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.updateChainView(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// syncMatcher performs a synchronization step with the matcher. The resulting
|
||||
// syncRange structure holds information about the latest range of indexed blocks
|
||||
// and the guaranteed valid blocks whose log index have not been changed since
|
||||
// the previous synchronization.
|
||||
// The function also performs trimming of the match set in order to always keep
|
||||
// it consistent with the synced matcher state.
|
||||
// Tail trimming is only performed if the first block of the valid log index range
|
||||
// is higher than trimTailThreshold. This is useful because unindexed log search
|
||||
// is not affected by the valid tail (on the other hand, valid head is taken into
|
||||
// account in order to provide reorg safety, even though the log index is not used).
|
||||
// In case of indexed search the tail is only trimmed if the first part of the
|
||||
// recently obtained results might be invalid. If guaranteed valid new results
|
||||
// have been added at the head of previously validated results then there is no
|
||||
// need to discard those even if the index tail have been unindexed since that.
|
||||
func (s *searchSession) syncMatcher(trimTailThreshold uint64) error {
|
||||
if s.filter.rangeLogsTestHook != nil && !s.matchRange.IsEmpty() {
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{event: rangeLogsTestSync, begin: s.matchRange.First(), end: s.matchRange.Last()}
|
||||
}
|
||||
var err error
|
||||
s.syncRange, err = s.mb.SyncLogIndex(s.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
// updateChainView updates to the latest view of the underlying chain and sets
|
||||
// searchRange by replacing MaxUint64 (meaning latest block) with actual head
|
||||
// number in the specified search range.
|
||||
// If the session already had an existing chain view and set of matches then
|
||||
// it also trims part of the match set that a chain reorg might have invalidated.
|
||||
func (s *searchSession) updateChainView() error {
|
||||
// update chain view based on current chain head (might be more recent than
|
||||
// the indexed view of syncRange as the indexer updates it asynchronously
|
||||
// with some delay
|
||||
newChainView := s.filter.sys.backend.CurrentView()
|
||||
if newChainView == nil {
|
||||
return errors.New("head block not available")
|
||||
}
|
||||
head := newChainView.HeadNumber()
|
||||
|
||||
// update actual search range based on current head number
|
||||
first := min(s.firstBlock, s.syncRange.HeadNumber)
|
||||
last := min(s.lastBlock, s.syncRange.HeadNumber)
|
||||
s.searchRange = common.NewRange(first, last+1-first)
|
||||
// discard everything that is not needed or might be invalid
|
||||
trimRange := s.syncRange.ValidBlocks
|
||||
if trimRange.First() <= trimTailThreshold {
|
||||
// everything before this point is already known to be valid; if this is
|
||||
// valid then keep everything before
|
||||
trimRange.SetFirst(0)
|
||||
firstBlock, lastBlock := s.firstBlock, s.lastBlock
|
||||
if firstBlock == math.MaxUint64 {
|
||||
firstBlock = head
|
||||
}
|
||||
trimRange = trimRange.Intersection(s.searchRange)
|
||||
s.trimMatches(trimRange)
|
||||
if s.filter.rangeLogsTestHook != nil {
|
||||
if !s.matchRange.IsEmpty() {
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{event: rangeLogsTestTrimmed, begin: s.matchRange.First(), end: s.matchRange.Last()}
|
||||
} else {
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{event: rangeLogsTestTrimmed, begin: 0, end: 0}
|
||||
}
|
||||
if lastBlock == math.MaxUint64 {
|
||||
lastBlock = head
|
||||
}
|
||||
if firstBlock > lastBlock || lastBlock > head {
|
||||
return errInvalidBlockRange
|
||||
}
|
||||
s.searchRange = common.NewRange(firstBlock, lastBlock+1-firstBlock)
|
||||
|
||||
// Trim existing match set in case a reorg may have invalidated some results
|
||||
if !s.matchRange.IsEmpty() {
|
||||
trimRange := newChainView.SharedRange(s.chainView).Intersection(s.searchRange)
|
||||
s.matchRange, s.matches = s.trimMatches(trimRange, s.matchRange, s.matches)
|
||||
}
|
||||
s.chainView = newChainView
|
||||
return nil
|
||||
}
|
||||
|
||||
// trimMatches removes any entries from the current set of matches that is outside
|
||||
// the given range.
|
||||
func (s *searchSession) trimMatches(trimRange common.Range[uint64]) {
|
||||
s.matchRange = s.matchRange.Intersection(trimRange)
|
||||
if s.matchRange.IsEmpty() {
|
||||
s.matches = nil
|
||||
return
|
||||
// trimMatches removes any entries from the specified set of matches that is
|
||||
// outside the given range.
|
||||
func (s *searchSession) trimMatches(trimRange, matchRange common.Range[uint64], matches []*types.Log) (common.Range[uint64], []*types.Log) {
|
||||
newRange := matchRange.Intersection(trimRange)
|
||||
if newRange == matchRange {
|
||||
return matchRange, matches
|
||||
}
|
||||
for len(s.matches) > 0 && s.matches[0].BlockNumber < s.matchRange.First() {
|
||||
s.matches = s.matches[1:]
|
||||
if newRange.IsEmpty() {
|
||||
return newRange, nil
|
||||
}
|
||||
for len(s.matches) > 0 && s.matches[len(s.matches)-1].BlockNumber > s.matchRange.Last() {
|
||||
s.matches = s.matches[:len(s.matches)-1]
|
||||
for len(matches) > 0 && matches[0].BlockNumber < newRange.First() {
|
||||
matches = matches[1:]
|
||||
}
|
||||
for len(matches) > 0 && matches[len(matches)-1].BlockNumber > newRange.Last() {
|
||||
matches = matches[:len(matches)-1]
|
||||
}
|
||||
return newRange, matches
|
||||
}
|
||||
|
||||
// searchInRange performs a single range search, either indexed or unindexed.
|
||||
func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) ([]*types.Log, error) {
|
||||
first, last := r.First(), r.Last()
|
||||
func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) (common.Range[uint64], []*types.Log, error) {
|
||||
if indexed {
|
||||
if s.filter.rangeLogsTestHook != nil {
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestIndexed, first, last}
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestIndexed, r}
|
||||
}
|
||||
results, err := s.filter.indexedLogs(s.ctx, s.mb, first, last)
|
||||
if err != filtermaps.ErrMatchAll {
|
||||
return results, err
|
||||
results, err := s.filter.indexedLogs(s.ctx, s.mb, r.First(), r.Last())
|
||||
if err != nil && !errors.Is(err, filtermaps.ErrMatchAll) {
|
||||
return common.Range[uint64]{}, nil, err
|
||||
}
|
||||
if err == nil {
|
||||
// sync with filtermaps matcher
|
||||
if s.filter.rangeLogsTestHook != nil {
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSync, common.Range[uint64]{}}
|
||||
}
|
||||
var syncErr error
|
||||
if s.syncRange, syncErr = s.mb.SyncLogIndex(s.ctx); syncErr != nil {
|
||||
return common.Range[uint64]{}, nil, syncErr
|
||||
}
|
||||
if s.filter.rangeLogsTestHook != nil {
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestSynced, s.syncRange.ValidBlocks}
|
||||
}
|
||||
// discard everything that might be invalid
|
||||
trimRange := s.syncRange.ValidBlocks.Intersection(s.chainView.SharedRange(s.syncRange.IndexedView))
|
||||
matchRange, matches := s.trimMatches(trimRange, r, results)
|
||||
return matchRange, matches, nil
|
||||
}
|
||||
// "match all" filters are not supported by filtermaps; fall back to
|
||||
// unindexed search which is the most efficient in this case
|
||||
|
|
@ -267,79 +286,85 @@ func (s *searchSession) searchInRange(r common.Range[uint64], indexed bool) ([]*
|
|||
// fall through to unindexed case
|
||||
}
|
||||
if s.filter.rangeLogsTestHook != nil {
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestUnindexed, first, last}
|
||||
s.filter.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestUnindexed, r}
|
||||
}
|
||||
return s.filter.unindexedLogs(s.ctx, first, last)
|
||||
matches, err := s.filter.unindexedLogs(s.ctx, s.chainView, r.First(), r.Last())
|
||||
if err != nil {
|
||||
return common.Range[uint64]{}, nil, err
|
||||
}
|
||||
return r, matches, nil
|
||||
}
|
||||
|
||||
// doSearchIteration performs a search on a range missing from an incomplete set
|
||||
// of results, adds the new section and removes invalidated entries.
|
||||
func (s *searchSession) doSearchIteration() error {
|
||||
switch {
|
||||
case s.syncRange.IndexedBlocks.IsEmpty():
|
||||
// indexer is not ready; fallback to completely unindexed search, do not check valid range
|
||||
var err error
|
||||
s.matchRange = s.searchRange
|
||||
s.matches, err = s.searchInRange(s.searchRange, false)
|
||||
return err
|
||||
|
||||
case s.matchRange.IsEmpty():
|
||||
// no results yet; try search in entire range
|
||||
indexedSearchRange := s.searchRange.Intersection(s.syncRange.IndexedBlocks)
|
||||
var err error
|
||||
if s.forceUnindexed = indexedSearchRange.IsEmpty(); !s.forceUnindexed {
|
||||
// indexed search on the intersection of indexed and searched range
|
||||
s.matchRange = indexedSearchRange
|
||||
s.matches, err = s.searchInRange(indexedSearchRange, true)
|
||||
matchRange, matches, err := s.searchInRange(indexedSearchRange, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.syncMatcher(0) // trim everything that the matcher considers potentially invalid
|
||||
s.matchRange = matchRange
|
||||
s.matches = matches
|
||||
return nil
|
||||
} else {
|
||||
// no intersection of indexed and searched range; unindexed search on the whole searched range
|
||||
s.matchRange = s.searchRange
|
||||
s.matches, err = s.searchInRange(s.searchRange, false)
|
||||
// no intersection of indexed and searched range; unindexed search on
|
||||
// the whole searched range
|
||||
matchRange, matches, err := s.searchInRange(s.searchRange, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.syncMatcher(math.MaxUint64) // unindexed search is not affected by the tail of valid range
|
||||
s.matchRange = matchRange
|
||||
s.matches = matches
|
||||
return nil
|
||||
}
|
||||
|
||||
case !s.matchRange.IsEmpty() && s.matchRange.First() > s.searchRange.First():
|
||||
// we have results but tail section is missing; do unindexed search for
|
||||
// the tail part but still allow indexed search for missing head section
|
||||
// Results are available, but the tail section is missing. Perform an unindexed
|
||||
// search for the missing tail, while still allowing indexed search for the head.
|
||||
//
|
||||
// The unindexed search is necessary because the tail portion of the indexes
|
||||
// has been pruned.
|
||||
tailRange := common.NewRange(s.searchRange.First(), s.matchRange.First()-s.searchRange.First())
|
||||
tailMatches, err := s.searchInRange(tailRange, false)
|
||||
_, tailMatches, err := s.searchInRange(tailRange, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.matches = append(tailMatches, s.matches...)
|
||||
s.matchRange = tailRange.Union(s.matchRange)
|
||||
return s.syncMatcher(math.MaxUint64) // unindexed search is not affected by the tail of valid range
|
||||
return nil
|
||||
|
||||
case !s.matchRange.IsEmpty() && s.matchRange.First() == s.searchRange.First() && s.searchRange.AfterLast() > s.matchRange.AfterLast():
|
||||
// we have results but head section is missing
|
||||
// Results are available, but the head section is missing. Try to perform
|
||||
// the indexed search for the missing head, or fallback to unindexed search
|
||||
// if the tail portion of indexed range has been pruned.
|
||||
headRange := common.NewRange(s.matchRange.AfterLast(), s.searchRange.AfterLast()-s.matchRange.AfterLast())
|
||||
if !s.forceUnindexed {
|
||||
indexedHeadRange := headRange.Intersection(s.syncRange.IndexedBlocks)
|
||||
if !indexedHeadRange.IsEmpty() && indexedHeadRange.First() == headRange.First() {
|
||||
// indexed head range search is possible
|
||||
headRange = indexedHeadRange
|
||||
} else {
|
||||
// The tail portion of the indexes has been pruned, falling back
|
||||
// to unindexed search.
|
||||
s.forceUnindexed = true
|
||||
}
|
||||
}
|
||||
headMatches, err := s.searchInRange(headRange, !s.forceUnindexed)
|
||||
headMatchRange, headMatches, err := s.searchInRange(headRange, !s.forceUnindexed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.matches = append(s.matches, headMatches...)
|
||||
s.matchRange = s.matchRange.Union(headRange)
|
||||
if s.forceUnindexed {
|
||||
return s.syncMatcher(math.MaxUint64) // unindexed search is not affected by the tail of valid range
|
||||
} else {
|
||||
return s.syncMatcher(headRange.First()) // trim if the tail of latest head search results might be invalid
|
||||
if headMatchRange.First() != s.matchRange.AfterLast() {
|
||||
// improbable corner case, first part of new head range invalidated by tail unindexing
|
||||
s.matches, s.matchRange = headMatches, headMatchRange
|
||||
return nil
|
||||
}
|
||||
s.matches = append(s.matches, headMatches...)
|
||||
s.matchRange = s.matchRange.Union(headMatchRange)
|
||||
return nil
|
||||
|
||||
default:
|
||||
panic("invalid search session state")
|
||||
|
|
@ -349,7 +374,7 @@ func (s *searchSession) doSearchIteration() error {
|
|||
func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([]*types.Log, error) {
|
||||
if f.rangeLogsTestHook != nil {
|
||||
defer func() {
|
||||
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestDone, 0, 0}
|
||||
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestDone, common.Range[uint64]{}}
|
||||
close(f.rangeLogsTestHook)
|
||||
}()
|
||||
}
|
||||
|
|
@ -366,7 +391,17 @@ func (f *Filter) rangeLogs(ctx context.Context, firstBlock, lastBlock uint64) ([
|
|||
}
|
||||
for session.searchRange != session.matchRange {
|
||||
if err := session.doSearchIteration(); err != nil {
|
||||
return session.matches, err
|
||||
return nil, err
|
||||
}
|
||||
if f.rangeLogsTestHook != nil {
|
||||
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestResults, session.matchRange}
|
||||
}
|
||||
mr := session.matchRange
|
||||
if err := session.updateChainView(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if f.rangeLogsTestHook != nil && session.matchRange != mr {
|
||||
f.rangeLogsTestHook <- rangeLogsTestEvent{rangeLogsTestReorg, session.matchRange}
|
||||
}
|
||||
}
|
||||
return session.matches, nil
|
||||
|
|
@ -382,7 +417,7 @@ func (f *Filter) indexedLogs(ctx context.Context, mb filtermaps.MatcherBackend,
|
|||
|
||||
// unindexedLogs returns the logs matching the filter criteria based on raw block
|
||||
// iteration and bloom matching.
|
||||
func (f *Filter) unindexedLogs(ctx context.Context, begin, end uint64) ([]*types.Log, error) {
|
||||
func (f *Filter) unindexedLogs(ctx context.Context, chainView *filtermaps.ChainView, begin, end uint64) ([]*types.Log, error) {
|
||||
start := time.Now()
|
||||
log.Debug("Performing unindexed log search", "begin", begin, "end", end)
|
||||
var matches []*types.Log
|
||||
|
|
@ -392,9 +427,14 @@ func (f *Filter) unindexedLogs(ctx context.Context, begin, end uint64) ([]*types
|
|||
return matches, ctx.Err()
|
||||
default:
|
||||
}
|
||||
header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber))
|
||||
if header == nil || err != nil {
|
||||
return matches, err
|
||||
if blockNumber > chainView.HeadNumber() {
|
||||
// check here so that we can return matches up until head along with
|
||||
// the error
|
||||
return matches, errInvalidBlockRange
|
||||
}
|
||||
header := chainView.Header(blockNumber)
|
||||
if header == nil {
|
||||
return matches, errors.New("header not found")
|
||||
}
|
||||
found, err := f.blockLogs(ctx, header)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ type Backend interface {
|
|||
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
||||
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
||||
|
||||
CurrentView() *filtermaps.ChainView
|
||||
NewMatcherBackend() filtermaps.MatcherBackend
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,11 @@ func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subsc
|
|||
return b.chainFeed.Subscribe(ch)
|
||||
}
|
||||
|
||||
func (b *testBackend) CurrentView() *filtermaps.ChainView {
|
||||
head := b.CurrentBlock()
|
||||
return filtermaps.NewChainView(b, head.Number.Uint64(), head.Hash())
|
||||
}
|
||||
|
||||
func (b *testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
||||
return b.fm.NewMatcherBackend()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -453,7 +453,8 @@ func TestRangeLogs(t *testing.T) {
|
|||
addresses = []common.Address{{}}
|
||||
)
|
||||
|
||||
expEvent := func(exp rangeLogsTestEvent) {
|
||||
expEvent := func(expEvent int, expFirst, expAfterLast uint64) {
|
||||
exp := rangeLogsTestEvent{expEvent, common.NewRange[uint64](expFirst, expAfterLast-expFirst)}
|
||||
event++
|
||||
ev := <-filter.rangeLogsTestHook
|
||||
if ev != exp {
|
||||
|
|
@ -472,7 +473,6 @@ func TestRangeLogs(t *testing.T) {
|
|||
for range filter.rangeLogsTestHook {
|
||||
}
|
||||
}(filter)
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
||||
}
|
||||
|
||||
updateHead := func() {
|
||||
|
|
@ -483,81 +483,122 @@ func TestRangeLogs(t *testing.T) {
|
|||
|
||||
// test case #1
|
||||
newFilter(300, 500)
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 401, 500})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 401, 500})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 401, 500})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 300, 400})
|
||||
expEvent(rangeLogsTestIndexed, 401, 501)
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 401, 601)
|
||||
expEvent(rangeLogsTestResults, 401, 501)
|
||||
expEvent(rangeLogsTestUnindexed, 300, 401)
|
||||
if _, err := bc.InsertChain(chain[600:700]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 300, 500})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 300, 500}) // unindexed search is not affected by trimmed tail
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestDone, 0, 0})
|
||||
expEvent(rangeLogsTestResults, 300, 501)
|
||||
expEvent(rangeLogsTestDone, 0, 0)
|
||||
|
||||
// test case #2
|
||||
newFilter(400, int64(rpc.LatestBlockNumber))
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 501, 700})
|
||||
expEvent(rangeLogsTestIndexed, 501, 701)
|
||||
if _, err := bc.InsertChain(chain[700:800]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 501, 700})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 601, 698})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 400, 600})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 698})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 698})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 699, 800})
|
||||
if err := bc.SetHead(750); err != nil {
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 601, 699)
|
||||
expEvent(rangeLogsTestResults, 601, 699)
|
||||
expEvent(rangeLogsTestUnindexed, 400, 601)
|
||||
expEvent(rangeLogsTestResults, 400, 699)
|
||||
expEvent(rangeLogsTestIndexed, 699, 801)
|
||||
if _, err := bc.SetCanonical(chain[749]); err != nil { // set head to block 750
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 800})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 748})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 749, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestDone, 0, 0})
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 601, 749)
|
||||
expEvent(rangeLogsTestResults, 400, 749)
|
||||
expEvent(rangeLogsTestIndexed, 749, 751)
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 551, 751)
|
||||
expEvent(rangeLogsTestResults, 400, 751)
|
||||
expEvent(rangeLogsTestDone, 0, 0)
|
||||
|
||||
// test case #3
|
||||
newFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber))
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 750, 750})
|
||||
if err := bc.SetHead(740); err != nil {
|
||||
expEvent(rangeLogsTestIndexed, 750, 751)
|
||||
if _, err := bc.SetCanonical(chain[739]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 750, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 740, 740})
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 551, 739)
|
||||
expEvent(rangeLogsTestResults, 0, 0)
|
||||
expEvent(rangeLogsTestIndexed, 740, 741)
|
||||
if _, err := bc.InsertChain(chain[740:750]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 740, 740})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 750, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 750, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 750, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestDone, 0, 0})
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 551, 739)
|
||||
expEvent(rangeLogsTestResults, 0, 0)
|
||||
expEvent(rangeLogsTestIndexed, 750, 751)
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 551, 751)
|
||||
expEvent(rangeLogsTestResults, 750, 751)
|
||||
expEvent(rangeLogsTestDone, 0, 0)
|
||||
|
||||
// test case #4
|
||||
if _, err := bc.SetCanonical(chain[499]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
newFilter(400, int64(rpc.LatestBlockNumber))
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 551, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 551, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 551, 750})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 400, 550})
|
||||
expEvent(rangeLogsTestIndexed, 400, 501)
|
||||
if _, err := bc.InsertChain(chain[500:650]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 451, 499)
|
||||
expEvent(rangeLogsTestResults, 451, 499)
|
||||
expEvent(rangeLogsTestUnindexed, 400, 451)
|
||||
expEvent(rangeLogsTestResults, 400, 499)
|
||||
// indexed head extension seems possible
|
||||
expEvent(rangeLogsTestIndexed, 499, 651)
|
||||
// further head extension causes tail unindexing in searched range
|
||||
if _, err := bc.InsertChain(chain[650:750]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 551, 649)
|
||||
// tail trimmed to 551; cannot merge with existing results
|
||||
expEvent(rangeLogsTestResults, 551, 649)
|
||||
expEvent(rangeLogsTestUnindexed, 400, 551)
|
||||
expEvent(rangeLogsTestResults, 400, 649)
|
||||
expEvent(rangeLogsTestIndexed, 649, 751)
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 551, 751)
|
||||
expEvent(rangeLogsTestResults, 400, 751)
|
||||
expEvent(rangeLogsTestDone, 0, 0)
|
||||
|
||||
// test case #5
|
||||
newFilter(400, int64(rpc.LatestBlockNumber))
|
||||
expEvent(rangeLogsTestIndexed, 551, 751)
|
||||
expEvent(rangeLogsTestSync, 0, 0)
|
||||
expEvent(rangeLogsTestSynced, 551, 751)
|
||||
expEvent(rangeLogsTestResults, 551, 751)
|
||||
expEvent(rangeLogsTestUnindexed, 400, 551)
|
||||
if _, err := bc.InsertChain(chain[750:1000]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 750})
|
||||
// indexed range affected by tail pruning so we have to discard the entire
|
||||
// match set
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 0, 0})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestIndexed, 801, 1000})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 801, 1000})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 801, 1000})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestUnindexed, 400, 800})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestSync, 400, 1000})
|
||||
expEvent(rangeLogsTestEvent{rangeLogsTestTrimmed, 400, 1000})
|
||||
expEvent(rangeLogsTestResults, 400, 751)
|
||||
// indexed tail already beyond results head; revert to unindexed head search
|
||||
expEvent(rangeLogsTestUnindexed, 751, 1001)
|
||||
if _, err := bc.SetCanonical(chain[899]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updateHead()
|
||||
expEvent(rangeLogsTestResults, 400, 1001)
|
||||
expEvent(rangeLogsTestReorg, 400, 901)
|
||||
expEvent(rangeLogsTestDone, 0, 0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) {
|
|||
bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit)
|
||||
if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil {
|
||||
maxBlobGas := eip4844.MaxBlobGasPerBlock(config, bf.header.Time)
|
||||
bf.results.blobGasUsedRatio = float64(*blobGasUsed) / float64(maxBlobGas)
|
||||
if maxBlobGas != 0 {
|
||||
bf.results.blobGasUsedRatio = float64(*blobGasUsed) / float64(maxBlobGas)
|
||||
}
|
||||
}
|
||||
|
||||
if len(percentiles) == 0 {
|
||||
|
|
|
|||
|
|
@ -25,16 +25,14 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/holiman/uint256"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
var _ bind.ContractBackend = (Client)(nil)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -619,6 +619,9 @@ func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
|||
func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) CurrentView() *filtermaps.ChainView {
|
||||
panic("implement me")
|
||||
}
|
||||
func (b testBackend) NewMatcherBackend() filtermaps.MatcherBackend {
|
||||
panic("implement me")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ type Backend interface {
|
|||
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
||||
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
||||
|
||||
CurrentView() *filtermaps.ChainView
|
||||
NewMatcherBackend() filtermaps.MatcherBackend
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -314,9 +314,13 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
|
|||
return nil, nil, err
|
||||
}
|
||||
// EIP-7002
|
||||
core.ProcessWithdrawalQueue(&requests, evm)
|
||||
if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// EIP-7251
|
||||
core.ProcessConsolidationQueue(&requests, evm)
|
||||
if err := core.ProcessConsolidationQueue(&requests, evm); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if requests != nil {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
|
|
|
|||
|
|
@ -401,6 +401,7 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
|
|||
|
||||
func (b *backendMock) Engine() consensus.Engine { return nil }
|
||||
|
||||
func (b *backendMock) CurrentView() *filtermaps.ChainView { return nil }
|
||||
func (b *backendMock) NewMatcherBackend() filtermaps.MatcherBackend { return nil }
|
||||
|
||||
func (b *backendMock) HistoryPruningCutoff() uint64 { return 0 }
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -127,9 +127,13 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo
|
|||
return &newPayloadResult{err: err}
|
||||
}
|
||||
// EIP-7002
|
||||
core.ProcessWithdrawalQueue(&requests, work.evm)
|
||||
if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
// EIP-7251 consolidations
|
||||
core.ProcessConsolidationQueue(&requests, work.evm)
|
||||
if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil {
|
||||
return &newPayloadResult{err: err}
|
||||
}
|
||||
}
|
||||
if requests != nil {
|
||||
reqHash := types.CalcRequestsHash(requests)
|
||||
|
|
|
|||
|
|
@ -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 = "unstable" // Version metadata to append to the version string
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue