mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 15:16:43 +00:00
Merge branch 'master' into enhance-simulation-api
This commit is contained in:
commit
c0f717f105
15 changed files with 244 additions and 104 deletions
|
|
@ -1659,13 +1659,17 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
log.Warn("The flag --txlookuplimit is deprecated and will be removed, please use --history.transactions")
|
||||
cfg.TransactionHistory = ctx.Uint64(TxLookupLimitFlag.Name)
|
||||
}
|
||||
if ctx.String(GCModeFlag.Name) == "archive" && cfg.TransactionHistory != 0 {
|
||||
if ctx.String(GCModeFlag.Name) == "archive" {
|
||||
if cfg.TransactionHistory != 0 {
|
||||
cfg.TransactionHistory = 0
|
||||
log.Warn("Disabled transaction unindexing for archive node")
|
||||
}
|
||||
|
||||
if cfg.StateScheme != rawdb.HashScheme {
|
||||
cfg.StateScheme = rawdb.HashScheme
|
||||
log.Warn("Forcing hash state-scheme for archive mode")
|
||||
}
|
||||
}
|
||||
if ctx.IsSet(LogHistoryFlag.Name) {
|
||||
cfg.LogHistory = ctx.Uint64(LogHistoryFlag.Name)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ func (s *filterTestSuite) filterFullRange(t *utesting.T) {
|
|||
|
||||
func (s *filterTestSuite) queryAndCheck(t *utesting.T, query *filterQuery) {
|
||||
query.run(s.cfg.client, s.cfg.historyPruneBlock)
|
||||
if query.Err == errPrunedHistory {
|
||||
return
|
||||
}
|
||||
if query.Err != nil {
|
||||
t.Errorf("Filter query failed (fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v)", query.FromBlock, query.ToBlock, query.Address, query.Topics, query.Err)
|
||||
return
|
||||
|
|
@ -126,6 +129,9 @@ func (s *filterTestSuite) fullRangeQueryAndCheck(t *utesting.T, query *filterQue
|
|||
Topics: query.Topics,
|
||||
}
|
||||
frQuery.run(s.cfg.client, s.cfg.historyPruneBlock)
|
||||
if frQuery.Err == errPrunedHistory {
|
||||
return
|
||||
}
|
||||
if frQuery.Err != nil {
|
||||
t.Errorf("Full range filter query failed (addresses: %v topics: %v error: %v)", frQuery.Address, frQuery.Topics, frQuery.Err)
|
||||
return
|
||||
|
|
@ -206,14 +212,11 @@ func (fq *filterQuery) run(client *client, historyPruneBlock *uint64) {
|
|||
Addresses: fq.Address,
|
||||
Topics: fq.Topics,
|
||||
})
|
||||
if err != nil {
|
||||
if err = validateHistoryPruneErr(fq.Err, uint64(fq.FromBlock), historyPruneBlock); err == errPrunedHistory {
|
||||
return
|
||||
} else if err != nil {
|
||||
fmt.Printf("Filter query failed: fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v\n",
|
||||
fq.FromBlock, fq.ToBlock, fq.Address, fq.Topics, err)
|
||||
}
|
||||
fq.Err = err
|
||||
}
|
||||
fq.results = logs
|
||||
fq.Err = validateHistoryPruneErr(err, uint64(fq.FromBlock), historyPruneBlock)
|
||||
}
|
||||
|
||||
func (fq *filterQuery) printError() {
|
||||
fmt.Printf("Filter query failed: fromBlock: %d toBlock: %d addresses: %v topics: %v error: %v\n",
|
||||
fq.FromBlock, fq.ToBlock, fq.Address, fq.Topics, fq.Err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ var (
|
|||
Action: filterGenCmd,
|
||||
Flags: []cli.Flag{
|
||||
filterQueryFileFlag,
|
||||
filterErrorFileFlag,
|
||||
},
|
||||
}
|
||||
filterQueryFileFlag = &cli.StringFlag{
|
||||
|
|
@ -72,8 +71,8 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
query := f.newQuery()
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
f.errors = append(f.errors, query)
|
||||
continue
|
||||
query.printError()
|
||||
exit("filter query failed")
|
||||
}
|
||||
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
|
||||
for {
|
||||
|
|
@ -90,8 +89,8 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
)
|
||||
}
|
||||
if extQuery.Err != nil {
|
||||
f.errors = append(f.errors, extQuery)
|
||||
break
|
||||
extQuery.printError()
|
||||
exit("filter query failed")
|
||||
}
|
||||
if len(extQuery.results) > maxFilterResultSize {
|
||||
break
|
||||
|
|
@ -101,7 +100,6 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
f.storeQuery(query)
|
||||
if time.Since(lastWrite) > time.Second*10 {
|
||||
f.writeQueries()
|
||||
f.writeErrors()
|
||||
lastWrite = time.Now()
|
||||
}
|
||||
}
|
||||
|
|
@ -112,18 +110,15 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
type filterTestGen struct {
|
||||
client *client
|
||||
queryFile string
|
||||
errorFile string
|
||||
|
||||
finalizedBlock int64
|
||||
queries [filterBuckets][]*filterQuery
|
||||
errors []*filterQuery
|
||||
}
|
||||
|
||||
func newFilterTestGen(ctx *cli.Context) *filterTestGen {
|
||||
return &filterTestGen{
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
errorFile: ctx.String(filterErrorFileFlag.Name),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -360,17 +355,6 @@ func (s *filterTestGen) writeQueries() {
|
|||
file.Close()
|
||||
}
|
||||
|
||||
// writeQueries serializes the generated errors to the error file.
|
||||
func (s *filterTestGen) writeErrors() {
|
||||
file, err := os.Create(s.errorFile)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("Error creating filter error file %s: %v", s.errorFile, err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
json.NewEncoder(file).Encode(s.errors)
|
||||
}
|
||||
|
||||
func mustGetFinalizedBlock(client *client) int64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
|
@ -41,7 +43,7 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
const passCount = 1
|
||||
const passCount = 3
|
||||
|
||||
func filterPerfCmd(ctx *cli.Context) error {
|
||||
cfg := testConfigFromCLI(ctx)
|
||||
|
|
@ -61,7 +63,10 @@ func filterPerfCmd(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
// Run test queries.
|
||||
var failed, mismatch int
|
||||
var (
|
||||
failed, pruned, mismatch int
|
||||
errors []*filterQuery
|
||||
)
|
||||
for i := 1; i <= passCount; i++ {
|
||||
fmt.Println("Performance test pass", i, "/", passCount)
|
||||
for len(queries) > 0 {
|
||||
|
|
@ -71,27 +76,35 @@ func filterPerfCmd(ctx *cli.Context) error {
|
|||
queries = queries[:len(queries)-1]
|
||||
start := time.Now()
|
||||
qt.query.run(cfg.client, cfg.historyPruneBlock)
|
||||
if qt.query.Err == errPrunedHistory {
|
||||
pruned++
|
||||
continue
|
||||
}
|
||||
qt.runtime = append(qt.runtime, time.Since(start))
|
||||
slices.Sort(qt.runtime)
|
||||
qt.medianTime = qt.runtime[len(qt.runtime)/2]
|
||||
if qt.query.Err != nil {
|
||||
qt.query.printError()
|
||||
errors = append(errors, qt.query)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
if rhash := qt.query.calculateHash(); *qt.query.ResultHash != rhash {
|
||||
fmt.Printf("Filter query result mismatch: fromBlock: %d toBlock: %d addresses: %v topics: %v expected hash: %064x calculated hash: %064x\n", qt.query.FromBlock, qt.query.ToBlock, qt.query.Address, qt.query.Topics, *qt.query.ResultHash, rhash)
|
||||
errors = append(errors, qt.query)
|
||||
mismatch++
|
||||
continue
|
||||
}
|
||||
processed = append(processed, qt)
|
||||
if len(processed)%50 == 0 {
|
||||
fmt.Println(" processed:", len(processed), "remaining", len(queries), "failed:", failed, "result mismatch:", mismatch)
|
||||
fmt.Println(" processed:", len(processed), "remaining", len(queries), "failed:", failed, "pruned:", pruned, "result mismatch:", mismatch)
|
||||
}
|
||||
}
|
||||
queries, processed = processed, nil
|
||||
}
|
||||
|
||||
// Show results and stats.
|
||||
fmt.Println("Performance test finished; processed:", len(queries), "failed:", failed, "result mismatch:", mismatch)
|
||||
fmt.Println("Performance test finished; processed:", len(queries), "failed:", failed, "pruned:", pruned, "result mismatch:", mismatch)
|
||||
stats := make([]bucketStats, len(f.queries))
|
||||
var wildcardStats bucketStats
|
||||
for _, qt := range queries {
|
||||
|
|
@ -114,11 +127,14 @@ func filterPerfCmd(ctx *cli.Context) error {
|
|||
sort.Slice(queries, func(i, j int) bool {
|
||||
return queries[i].medianTime > queries[j].medianTime
|
||||
})
|
||||
for i := 0; i < 10; i++ {
|
||||
q := queries[i]
|
||||
for i, q := range queries {
|
||||
if i >= 10 {
|
||||
break
|
||||
}
|
||||
fmt.Printf("Most expensive query #%-2d median runtime: %13v max runtime: %13v result count: %4d fromBlock: %9d toBlock: %9d addresses: %v topics: %v\n",
|
||||
i+1, q.medianTime, q.runtime[len(q.runtime)-1], len(q.query.results), q.query.FromBlock, q.query.ToBlock, q.query.Address, q.query.Topics)
|
||||
}
|
||||
writeErrors(ctx.String(filterErrorFileFlag.Name), errors)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -135,3 +151,14 @@ func (st *bucketStats) print(name string) {
|
|||
fmt.Printf("%-20s queries: %4d average block length: %12.2f average log count: %7.2f average runtime: %13v\n",
|
||||
name, st.count, float64(st.blocks)/float64(st.count), float64(st.logs)/float64(st.count), st.runtime/time.Duration(st.count))
|
||||
}
|
||||
|
||||
// writeQueries serializes the generated errors to the error file.
|
||||
func writeErrors(errorFile string, errors []*filterQuery) {
|
||||
file, err := os.Create(errorFile)
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("Error creating filter error file %s: %v", errorFile, err))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
json.NewEncoder(file).Encode(errors)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ type FilterMaps struct {
|
|||
indexLock sync.RWMutex
|
||||
indexedRange filterMapsRange
|
||||
indexedView *ChainView // always consistent with the log index
|
||||
hasTempRange bool
|
||||
|
||||
// also accessed by indexer and matcher backend but no locking needed.
|
||||
filterMapCache *lru.Cache[uint32, filterMap]
|
||||
|
|
@ -94,7 +95,7 @@ type FilterMaps struct {
|
|||
ptrTailUnindexMap uint32
|
||||
|
||||
targetView *ChainView
|
||||
matcherSyncRequest *FilterMapsMatcherBackend
|
||||
matcherSyncRequests []*FilterMapsMatcherBackend
|
||||
historyCutoff uint64
|
||||
finalBlock, lastFinal uint64
|
||||
lastFinalEpoch uint32
|
||||
|
|
@ -330,7 +331,7 @@ func (f *FilterMaps) init() error {
|
|||
fmr.blocks = common.NewRange(cp.BlockNumber+1, 0)
|
||||
fmr.maps = common.NewRange(uint32(bestLen)<<f.logMapsPerEpoch, 0)
|
||||
}
|
||||
f.setRange(batch, f.targetView, fmr)
|
||||
f.setRange(batch, f.targetView, fmr, false)
|
||||
return batch.Write()
|
||||
}
|
||||
|
||||
|
|
@ -373,9 +374,10 @@ func (f *FilterMaps) removeDbWithPrefix(prefix []byte, action string) bool {
|
|||
// setRange updates the indexed chain view and covered range and also adds the
|
||||
// changes to the given batch.
|
||||
// Note that this function assumes that the index write lock is being held.
|
||||
func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, newRange filterMapsRange) {
|
||||
func (f *FilterMaps) setRange(batch ethdb.KeyValueWriter, newView *ChainView, newRange filterMapsRange, isTempRange bool) {
|
||||
f.indexedView = newView
|
||||
f.indexedRange = newRange
|
||||
f.hasTempRange = isTempRange
|
||||
f.updateMatchersValidRange()
|
||||
if newRange.initialized {
|
||||
rs := rawdb.FilterMapsRange{
|
||||
|
|
@ -666,7 +668,7 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) error {
|
|||
} else {
|
||||
return errors.New("invalid tail epoch number")
|
||||
}
|
||||
f.setRange(f.db, f.indexedView, fmr)
|
||||
f.setRange(f.db, f.indexedView, fmr, false)
|
||||
first := f.mapRowIndex(firstMap, 0)
|
||||
count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first
|
||||
rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count))
|
||||
|
|
|
|||
|
|
@ -136,15 +136,18 @@ func (f *FilterMaps) processEvents() {
|
|||
// processSingleEvent processes a single event either in a blocking or
|
||||
// non-blocking manner.
|
||||
func (f *FilterMaps) processSingleEvent(blocking bool) bool {
|
||||
if f.matcherSyncRequest != nil && f.targetHeadIndexed() {
|
||||
f.matcherSyncRequest.synced()
|
||||
f.matcherSyncRequest = nil
|
||||
if !f.hasTempRange {
|
||||
for _, mb := range f.matcherSyncRequests {
|
||||
mb.synced()
|
||||
}
|
||||
f.matcherSyncRequests = nil
|
||||
}
|
||||
if blocking {
|
||||
select {
|
||||
case target := <-f.targetCh:
|
||||
f.setTarget(target)
|
||||
case f.matcherSyncRequest = <-f.matcherSyncCh:
|
||||
case mb := <-f.matcherSyncCh:
|
||||
f.matcherSyncRequests = append(f.matcherSyncRequests, mb)
|
||||
case f.blockProcessing = <-f.blockProcessingCh:
|
||||
case <-f.closeCh:
|
||||
f.stop = true
|
||||
|
|
@ -160,7 +163,8 @@ func (f *FilterMaps) processSingleEvent(blocking bool) bool {
|
|||
select {
|
||||
case target := <-f.targetCh:
|
||||
f.setTarget(target)
|
||||
case f.matcherSyncRequest = <-f.matcherSyncCh:
|
||||
case mb := <-f.matcherSyncCh:
|
||||
f.matcherSyncRequests = append(f.matcherSyncRequests, mb)
|
||||
case f.blockProcessing = <-f.blockProcessingCh:
|
||||
case <-f.closeCh:
|
||||
f.stop = true
|
||||
|
|
|
|||
|
|
@ -392,12 +392,16 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
|||
}
|
||||
// do not exit while in partially written state but do allow processing
|
||||
// events and pausing while block processing is in progress
|
||||
r.f.indexLock.Unlock()
|
||||
pauseCb()
|
||||
r.f.indexLock.Lock()
|
||||
batch = r.f.db.NewBatch()
|
||||
}
|
||||
}
|
||||
|
||||
r.f.setRange(batch, r.f.indexedView, tempRange)
|
||||
if tempRange != r.f.indexedRange {
|
||||
r.f.setRange(batch, r.f.indexedView, tempRange, true)
|
||||
}
|
||||
// add or update filter rows
|
||||
for rowIndex := uint32(0); rowIndex < r.f.mapHeight; rowIndex++ {
|
||||
var (
|
||||
|
|
@ -469,7 +473,7 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
|
|||
}
|
||||
r.finishedMaps = make(map[uint32]*renderedMap)
|
||||
r.finished.SetFirst(r.finished.AfterLast())
|
||||
r.f.setRange(batch, renderedView, newRange)
|
||||
r.f.setRange(batch, renderedView, newRange, false)
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Error writing log index update batch", "error", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ func (fm *FilterMapsMatcherBackend) synced() {
|
|||
indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
|
||||
}
|
||||
fm.syncCh <- SyncRange{
|
||||
HeadNumber: fm.f.indexedView.headNumber,
|
||||
HeadNumber: fm.f.targetView.headNumber,
|
||||
ValidBlocks: fm.validBlocks,
|
||||
IndexedBlocks: indexedBlocks,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,10 @@ var (
|
|||
// transactions is reached for specific accounts.
|
||||
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated accounts")
|
||||
|
||||
// ErrOutOfOrderTxFromDelegated is returned when the transaction with gapped
|
||||
// nonce received from the accounts with delegation or pending delegation.
|
||||
ErrOutOfOrderTxFromDelegated = errors.New("gapped-nonce tx from delegated accounts")
|
||||
|
||||
// ErrAuthorityReserved is returned if a transaction has an authorization
|
||||
// signed by an address which already has in-flight transactions known to the
|
||||
// pool.
|
||||
|
|
@ -606,33 +610,39 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
|
|||
return pool.validateAuth(tx)
|
||||
}
|
||||
|
||||
// checkDelegationLimit determines if the tx sender is delegated or has a
|
||||
// pending delegation, and if so, ensures they have at most one in-flight
|
||||
// **executable** transaction, e.g. disallow stacked and gapped transactions
|
||||
// from the account.
|
||||
func (pool *LegacyPool) checkDelegationLimit(tx *types.Transaction) error {
|
||||
from, _ := types.Sender(pool.signer, tx) // validated
|
||||
|
||||
// Short circuit if the sender has neither delegation nor pending delegation.
|
||||
if pool.currentState.GetCodeHash(from) == types.EmptyCodeHash && len(pool.all.auths[from]) == 0 {
|
||||
return nil
|
||||
}
|
||||
pending := pool.pending[from]
|
||||
if pending == nil {
|
||||
// Transaction with gapped nonce is not supported for delegated accounts
|
||||
if pool.pendingNonces.get(from) != tx.Nonce() {
|
||||
return ErrOutOfOrderTxFromDelegated
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Transaction replacement is supported
|
||||
if pending.Contains(tx.Nonce()) {
|
||||
return nil
|
||||
}
|
||||
return ErrInflightTxLimitReached
|
||||
}
|
||||
|
||||
// validateAuth verifies that the transaction complies with code authorization
|
||||
// restrictions brought by SetCode transaction type.
|
||||
func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
|
||||
from, _ := types.Sender(pool.signer, tx) // validated
|
||||
|
||||
// Allow at most one in-flight tx for delegated accounts or those with a
|
||||
// pending authorization.
|
||||
if pool.currentState.GetCodeHash(from) != types.EmptyCodeHash || len(pool.all.auths[from]) != 0 {
|
||||
var (
|
||||
count int
|
||||
exists bool
|
||||
)
|
||||
pending := pool.pending[from]
|
||||
if pending != nil {
|
||||
count += pending.Len()
|
||||
exists = pending.Contains(tx.Nonce())
|
||||
}
|
||||
queue := pool.queue[from]
|
||||
if queue != nil {
|
||||
count += queue.Len()
|
||||
exists = exists || queue.Contains(tx.Nonce())
|
||||
}
|
||||
// Replace the existing in-flight transaction for delegated accounts
|
||||
// are still supported
|
||||
if count >= 1 && !exists {
|
||||
return ErrInflightTxLimitReached
|
||||
}
|
||||
if err := pool.checkDelegationLimit(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
// Authorities cannot conflict with any pending or queued transactions.
|
||||
if auths := tx.SetCodeAuthorities(); len(auths) > 0 {
|
||||
|
|
|
|||
|
|
@ -2262,6 +2262,11 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
aa := common.Address{0xaa, 0xaa}
|
||||
statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...))
|
||||
statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)})
|
||||
|
||||
// Send gapped transaction, it should be rejected.
|
||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrOutOfOrderTxFromDelegated) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrOutOfOrderTxFromDelegated, err)
|
||||
}
|
||||
// Send transactions. First is accepted, second is rejected.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
|
||||
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||
|
|
@ -2269,7 +2274,7 @@ func TestSetCodeTransactions(t *testing.T) {
|
|||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||
}
|
||||
// Also check gapped transaction.
|
||||
// Check gapped transaction again.
|
||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keyA)); !errors.Is(err, ErrInflightTxLimitReached) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, ErrInflightTxLimitReached, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ func TestTraceCall(t *testing.T) {
|
|||
},
|
||||
config: nil,
|
||||
expectErr: nil,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`,
|
||||
},
|
||||
// Standard JSON trace upon the head, plain transfer.
|
||||
{
|
||||
|
|
@ -366,7 +366,7 @@ func TestTraceCall(t *testing.T) {
|
|||
},
|
||||
config: nil,
|
||||
expectErr: nil,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`,
|
||||
},
|
||||
// Upon the last state, default to the post block's state
|
||||
{
|
||||
|
|
@ -377,7 +377,7 @@ func TestTraceCall(t *testing.T) {
|
|||
Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))),
|
||||
},
|
||||
config: nil,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`,
|
||||
},
|
||||
// Before the first transaction, should be failed
|
||||
{
|
||||
|
|
@ -411,7 +411,7 @@ func TestTraceCall(t *testing.T) {
|
|||
},
|
||||
config: &TraceCallConfig{TxIndex: uintPtr(2)},
|
||||
expectErr: nil,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`,
|
||||
},
|
||||
// Standard JSON trace upon the non-existent block, error expects
|
||||
{
|
||||
|
|
@ -435,7 +435,7 @@ func TestTraceCall(t *testing.T) {
|
|||
},
|
||||
config: nil,
|
||||
expectErr: nil,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||
expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`,
|
||||
},
|
||||
// Tracing on 'pending' should fail:
|
||||
{
|
||||
|
|
@ -458,7 +458,7 @@ func TestTraceCall(t *testing.T) {
|
|||
BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||
},
|
||||
expectErr: nil,
|
||||
expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[
|
||||
expect: ` {"gas":53018,"failed":false,"returnValue":"0x","structLogs":[
|
||||
{"pc":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]},
|
||||
{"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`,
|
||||
},
|
||||
|
|
@ -535,7 +535,7 @@ func TestTraceTransaction(t *testing.T) {
|
|||
if !reflect.DeepEqual(have, &logger.ExecutionResult{
|
||||
Gas: params.TxGas,
|
||||
Failed: false,
|
||||
ReturnValue: "",
|
||||
ReturnValue: []byte{},
|
||||
StructLogs: []json.RawMessage{},
|
||||
}) {
|
||||
t.Error("Transaction tracing result is different")
|
||||
|
|
@ -596,7 +596,7 @@ func TestTraceBlock(t *testing.T) {
|
|||
// Trace head block
|
||||
{
|
||||
blockNumber: rpc.BlockNumber(genBlocks),
|
||||
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash),
|
||||
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}]`, txHash),
|
||||
},
|
||||
// Trace non-existent block
|
||||
{
|
||||
|
|
@ -606,12 +606,12 @@ func TestTraceBlock(t *testing.T) {
|
|||
// Trace latest block
|
||||
{
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash),
|
||||
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}]`, txHash),
|
||||
},
|
||||
// Trace pending block
|
||||
{
|
||||
blockNumber: rpc.PendingBlockNumber,
|
||||
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHash),
|
||||
want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}]`, txHash),
|
||||
},
|
||||
}
|
||||
for i, tc := range testSuite {
|
||||
|
|
@ -704,7 +704,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
randomAccounts[0].addr: override.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
|
||||
},
|
||||
},
|
||||
want: `{"gas":21000,"failed":false,"returnValue":""}`,
|
||||
want: `{"gas":21000,"failed":false,"returnValue":"0x"}`,
|
||||
},
|
||||
// Invalid call without state overriding
|
||||
{
|
||||
|
|
@ -749,7 +749,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: `{"gas":23347,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`,
|
||||
want: `{"gas":23347,"failed":false,"returnValue":"0x000000000000000000000000000000000000000000000000000000000000007b"}`,
|
||||
},
|
||||
{ // Override blocknumber
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
|
|
@ -761,7 +761,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
config: &TraceCallConfig{
|
||||
BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||
},
|
||||
want: `{"gas":59537,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000001337"}`,
|
||||
want: `{"gas":59537,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000001337"}`,
|
||||
},
|
||||
{ // Override blocknumber, and query a blockhash
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
|
|
@ -781,7 +781,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
config: &TraceCallConfig{
|
||||
BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||
},
|
||||
want: `{"gas":72666,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`,
|
||||
want: `{"gas":72666,"failed":false,"returnValue":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`,
|
||||
},
|
||||
/*
|
||||
pragma solidity =0.8.12;
|
||||
|
|
@ -815,7 +815,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`,
|
||||
want: `{"gas":44100,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000001"}`,
|
||||
},
|
||||
{ // Same again, this time with storage override
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
|
|
@ -833,7 +833,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
//want: `{"gas":46900,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000539"}`,
|
||||
want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`,
|
||||
want: `{"gas":44100,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000001"}`,
|
||||
},
|
||||
{ // No state override
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
|
|
@ -863,7 +863,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000077"}`,
|
||||
want: `{"gas":25288,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000077"}`,
|
||||
},
|
||||
{ // Full state override
|
||||
// The original storage is
|
||||
|
|
@ -901,7 +901,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000011"}`,
|
||||
want: `{"gas":25288,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000011"}`,
|
||||
},
|
||||
{ // Partial state override
|
||||
// The original storage is
|
||||
|
|
@ -939,7 +939,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: `{"gas":25288,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000055"}`,
|
||||
want: `{"gas":25288,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000055"}`,
|
||||
},
|
||||
{ // Call to precompile ECREC (0x01), but code was modified to add 1 to input
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
|
|
@ -960,7 +960,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: `{"gas":21167,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000002"}`,
|
||||
want: `{"gas":21167,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000002"}`,
|
||||
},
|
||||
{ // Call to ECREC Precompiled on a different address, expect the original behaviour of ECREC precompile
|
||||
blockNumber: rpc.LatestBlockNumber,
|
||||
|
|
@ -981,7 +981,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
want: `{"gas":25664,"failed":false,"returnValue":"000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074"}`,
|
||||
want: `{"gas":25664,"failed":false,"returnValue":"0x000000000000000000000000c6e93f4c1920eaeaa1e699f76a7a8c18e3056074"}`,
|
||||
},
|
||||
}
|
||||
for i, tc := range testSuite {
|
||||
|
|
@ -1084,7 +1084,7 @@ func TestTraceChain(t *testing.T) {
|
|||
backend.relHook = func() { rel.Add(1) }
|
||||
api := NewAPI(backend)
|
||||
|
||||
single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}`
|
||||
single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}}`
|
||||
var cases = []struct {
|
||||
start uint64
|
||||
end uint64
|
||||
|
|
@ -1198,7 +1198,7 @@ func TestTraceBlockWithBasefee(t *testing.T) {
|
|||
// Trace head block
|
||||
{
|
||||
blockNumber: rpc.BlockNumber(genBlocks),
|
||||
want: fmt.Sprintf(`[{"txHash":"%#x","result":{"gas":21002,"failed":false,"returnValue":"","structLogs":[{"pc":0,"op":"BASEFEE","gas":84000,"gasCost":2,"depth":1,"stack":[]},{"pc":1,"op":"STOP","gas":83998,"gasCost":0,"depth":1,"stack":["%#x"]}]}}]`, txHash, baseFee),
|
||||
want: fmt.Sprintf(`[{"txHash":"%#x","result":{"gas":21002,"failed":false,"returnValue":"0x","structLogs":[{"pc":0,"op":"BASEFEE","gas":84000,"gasCost":2,"depth":1,"stack":[]},{"pc":1,"op":"STOP","gas":83998,"gasCost":0,"depth":1,"stack":["%#x"]}]}}]`, txHash, baseFee),
|
||||
},
|
||||
}
|
||||
for i, tc := range testSuite {
|
||||
|
|
|
|||
|
|
@ -350,14 +350,13 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
|
|||
failed := l.err != nil
|
||||
returnData := common.CopyBytes(l.output)
|
||||
// Return data when successful and revert reason when reverted, otherwise empty.
|
||||
returnVal := fmt.Sprintf("%x", returnData)
|
||||
if failed && !errors.Is(l.err, vm.ErrExecutionReverted) {
|
||||
returnVal = ""
|
||||
returnData = []byte{}
|
||||
}
|
||||
return json.Marshal(&ExecutionResult{
|
||||
Gas: l.usedGas,
|
||||
Failed: failed,
|
||||
ReturnValue: returnVal,
|
||||
ReturnValue: returnData,
|
||||
StructLogs: l.logs,
|
||||
})
|
||||
}
|
||||
|
|
@ -527,6 +526,6 @@ func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.O
|
|||
type ExecutionResult struct {
|
||||
Gas uint64 `json:"gas"`
|
||||
Failed bool `json:"failed"`
|
||||
ReturnValue string `json:"returnValue"`
|
||||
ReturnValue hexutil.Bytes `json:"returnValue"`
|
||||
StructLogs []json.RawMessage `json:"structLogs"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,11 +50,20 @@ const (
|
|||
// encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns.
|
||||
type codecV5 interface {
|
||||
// Encode encodes a packet.
|
||||
Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error)
|
||||
//
|
||||
// If the underlying type of 'p' is *v5wire.Whoareyou, a Whoareyou challenge packet is
|
||||
// encoded. If the 'challenge' parameter is non-nil, the packet is encoded as a
|
||||
// handshake message packet. Otherwise, the packet will be encoded as an ordinary
|
||||
// message packet.
|
||||
Encode(id enode.ID, addr string, p v5wire.Packet, challenge *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error)
|
||||
|
||||
// Decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails.
|
||||
// The *enode.Node return value is non-nil when the input contains a handshake response.
|
||||
Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error)
|
||||
Decode(b []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error)
|
||||
|
||||
// CurrentChallenge returns the most recent WHOAREYOU challenge that was encoded to given node.
|
||||
// This will return a non-nil value if there is an active handshake attempt with the node, and nil otherwise.
|
||||
CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou
|
||||
}
|
||||
|
||||
// UDPv5 is the implementation of protocol version 5.
|
||||
|
|
@ -824,6 +833,19 @@ func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr netip.AddrPort
|
|||
|
||||
// handleUnknown initiates a handshake by responding with WHOAREYOU.
|
||||
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr netip.AddrPort) {
|
||||
currentChallenge := t.codec.CurrentChallenge(fromID, fromAddr.String())
|
||||
if currentChallenge != nil {
|
||||
// This case happens when the sender issues multiple concurrent requests.
|
||||
// Since we only support one in-progress handshake at a time, we need to tell
|
||||
// them which handshake attempt they need to complete. We tell them to use the
|
||||
// existing handshake attempt since the response to that one might still be in
|
||||
// transit.
|
||||
t.log.Debug("Repeating discv5 handshake challenge", "id", fromID, "addr", fromAddr)
|
||||
t.sendResponse(fromID, fromAddr, currentChallenge)
|
||||
return
|
||||
}
|
||||
|
||||
// Send a fresh challenge.
|
||||
challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
|
||||
crand.Read(challenge.IDNonce[:])
|
||||
if n := t.GetNode(fromID); n != nil {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,26 @@ func TestUDPv5_unknownPacket(t *testing.T) {
|
|||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUDPv5_unknownPacketKnownNode(t *testing.T) {
|
||||
t.Parallel()
|
||||
test := newUDPV5Test(t)
|
||||
defer test.close()
|
||||
|
||||
nonce := v5wire.Nonce{1, 2, 3}
|
||||
check := func(p *v5wire.Whoareyou, wantSeq uint64) {
|
||||
t.Helper()
|
||||
if p.Nonce != nonce {
|
||||
t.Error("wrong nonce in WHOAREYOU:", p.Nonce, nonce)
|
||||
}
|
||||
if p.IDNonce == ([16]byte{}) {
|
||||
t.Error("all zero ID nonce")
|
||||
}
|
||||
if p.RecordSeq != wantSeq {
|
||||
t.Errorf("wrong record seq %d in WHOAREYOU, want %d", p.RecordSeq, wantSeq)
|
||||
}
|
||||
}
|
||||
|
||||
// Make node known.
|
||||
n := test.getNode(test.remotekey, test.remoteaddr).Node()
|
||||
|
|
@ -151,6 +171,42 @@ func TestUDPv5_unknownPacket(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// This test checks that, when multiple 'unknown' packets are received during a handshake,
|
||||
// the node sticks to the first handshake attempt.
|
||||
func TestUDPv5_handshakeRepeatChallenge(t *testing.T) {
|
||||
t.Parallel()
|
||||
test := newUDPV5Test(t)
|
||||
defer test.close()
|
||||
|
||||
nonce1 := v5wire.Nonce{1}
|
||||
nonce2 := v5wire.Nonce{2}
|
||||
nonce3 := v5wire.Nonce{3}
|
||||
check := func(p *v5wire.Whoareyou, wantNonce v5wire.Nonce) {
|
||||
t.Helper()
|
||||
if p.Nonce != wantNonce {
|
||||
t.Error("wrong nonce in WHOAREYOU:", p.Nonce, wantNonce)
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown packet from unknown node.
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce1})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, nonce1)
|
||||
})
|
||||
|
||||
// Second unknown packet. Here we expect the response to reference the
|
||||
// first unknown packet.
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce2})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, nonce1)
|
||||
})
|
||||
// Third unknown packet. This should still return the first nonce.
|
||||
test.packetIn(&v5wire.Unknown{Nonce: nonce3})
|
||||
test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
|
||||
check(p, nonce1)
|
||||
})
|
||||
}
|
||||
|
||||
// This test checks that incoming FINDNODE calls are handled correctly.
|
||||
func TestUDPv5_findnodeHandling(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
|
@ -698,6 +754,8 @@ type testCodec struct {
|
|||
test *udpV5Test
|
||||
id enode.ID
|
||||
ctr uint64
|
||||
|
||||
sentChallenges map[enode.ID]*v5wire.Whoareyou
|
||||
}
|
||||
|
||||
type testCodecFrame struct {
|
||||
|
|
@ -712,11 +770,23 @@ func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wir
|
|||
var authTag v5wire.Nonce
|
||||
binary.BigEndian.PutUint64(authTag[:], c.ctr)
|
||||
|
||||
if w, ok := p.(*v5wire.Whoareyou); ok {
|
||||
// Store recently sent Whoareyou challenges.
|
||||
if c.sentChallenges == nil {
|
||||
c.sentChallenges = make(map[enode.ID]*v5wire.Whoareyou)
|
||||
}
|
||||
c.sentChallenges[toID] = w
|
||||
}
|
||||
|
||||
penc, _ := rlp.EncodeToBytes(p)
|
||||
frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
|
||||
return frame, authTag, err
|
||||
}
|
||||
|
||||
func (c *testCodec) CurrentChallenge(id enode.ID, addr string) *v5wire.Whoareyou {
|
||||
return c.sentChallenges[id]
|
||||
}
|
||||
|
||||
func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) {
|
||||
frame, p, err := c.decodeFrame(input)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -245,6 +245,12 @@ func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, err
|
|||
return c.buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// CurrentChallenge returns the latest challenge sent to the given node.
|
||||
// This will return non-nil while a handshake is in progress.
|
||||
func (c *Codec) CurrentChallenge(id enode.ID, addr string) *Whoareyou {
|
||||
return c.sc.getHandshake(id, addr)
|
||||
}
|
||||
|
||||
func (c *Codec) writeHeaders(head *Header) {
|
||||
c.buf.Reset()
|
||||
c.buf.Write(head.IV[:])
|
||||
|
|
|
|||
Loading…
Reference in a new issue