Merge branch 'master' into enhance-simulation-api

This commit is contained in:
Rez 2025-03-21 15:39:52 +11:00
commit c0f717f105
No known key found for this signature in database
15 changed files with 244 additions and 104 deletions

View file

@ -1659,12 +1659,16 @@ 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 {
cfg.TransactionHistory = 0
log.Warn("Disabled transaction unindexing for archive node")
if ctx.String(GCModeFlag.Name) == "archive" {
if cfg.TransactionHistory != 0 {
cfg.TransactionHistory = 0
log.Warn("Disabled transaction unindexing for archive node")
}
cfg.StateScheme = rawdb.HashScheme
log.Warn("Forcing hash state-scheme for archive mode")
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)

View file

@ -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)
}

View file

@ -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()

View file

@ -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)
}

View file

@ -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))

View file

@ -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

View file

@ -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)
}

View file

@ -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,
}

View file

@ -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 {

View file

@ -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)
}

View file

@ -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 {

View file

@ -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"`
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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[:])