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,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") log.Warn("The flag --txlookuplimit is deprecated and will be removed, please use --history.transactions")
cfg.TransactionHistory = ctx.Uint64(TxLookupLimitFlag.Name) 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 cfg.TransactionHistory = 0
log.Warn("Disabled transaction unindexing for archive node") log.Warn("Disabled transaction unindexing for archive node")
}
if cfg.StateScheme != rawdb.HashScheme {
cfg.StateScheme = rawdb.HashScheme cfg.StateScheme = rawdb.HashScheme
log.Warn("Forcing hash state-scheme for archive mode") log.Warn("Forcing hash state-scheme for archive mode")
} }
}
if ctx.IsSet(LogHistoryFlag.Name) { if ctx.IsSet(LogHistoryFlag.Name) {
cfg.LogHistory = ctx.Uint64(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) { func (s *filterTestSuite) queryAndCheck(t *utesting.T, query *filterQuery) {
query.run(s.cfg.client, s.cfg.historyPruneBlock) query.run(s.cfg.client, s.cfg.historyPruneBlock)
if query.Err == errPrunedHistory {
return
}
if query.Err != nil { 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) 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 return
@ -126,6 +129,9 @@ func (s *filterTestSuite) fullRangeQueryAndCheck(t *utesting.T, query *filterQue
Topics: query.Topics, Topics: query.Topics,
} }
frQuery.run(s.cfg.client, s.cfg.historyPruneBlock) frQuery.run(s.cfg.client, s.cfg.historyPruneBlock)
if frQuery.Err == errPrunedHistory {
return
}
if frQuery.Err != nil { if frQuery.Err != nil {
t.Errorf("Full range filter query failed (addresses: %v topics: %v error: %v)", frQuery.Address, frQuery.Topics, frQuery.Err) t.Errorf("Full range filter query failed (addresses: %v topics: %v error: %v)", frQuery.Address, frQuery.Topics, frQuery.Err)
return return
@ -206,14 +212,11 @@ func (fq *filterQuery) run(client *client, historyPruneBlock *uint64) {
Addresses: fq.Address, Addresses: fq.Address,
Topics: fq.Topics, 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.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, Action: filterGenCmd,
Flags: []cli.Flag{ Flags: []cli.Flag{
filterQueryFileFlag, filterQueryFileFlag,
filterErrorFileFlag,
}, },
} }
filterQueryFileFlag = &cli.StringFlag{ filterQueryFileFlag = &cli.StringFlag{
@ -72,8 +71,8 @@ func filterGenCmd(ctx *cli.Context) error {
query := f.newQuery() query := f.newQuery()
query.run(f.client, nil) query.run(f.client, nil)
if query.Err != nil { if query.Err != nil {
f.errors = append(f.errors, query) query.printError()
continue exit("filter query failed")
} }
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize { if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
for { for {
@ -90,8 +89,8 @@ func filterGenCmd(ctx *cli.Context) error {
) )
} }
if extQuery.Err != nil { if extQuery.Err != nil {
f.errors = append(f.errors, extQuery) extQuery.printError()
break exit("filter query failed")
} }
if len(extQuery.results) > maxFilterResultSize { if len(extQuery.results) > maxFilterResultSize {
break break
@ -101,7 +100,6 @@ func filterGenCmd(ctx *cli.Context) error {
f.storeQuery(query) f.storeQuery(query)
if time.Since(lastWrite) > time.Second*10 { if time.Since(lastWrite) > time.Second*10 {
f.writeQueries() f.writeQueries()
f.writeErrors()
lastWrite = time.Now() lastWrite = time.Now()
} }
} }
@ -112,18 +110,15 @@ func filterGenCmd(ctx *cli.Context) error {
type filterTestGen struct { type filterTestGen struct {
client *client client *client
queryFile string queryFile string
errorFile string
finalizedBlock int64 finalizedBlock int64
queries [filterBuckets][]*filterQuery queries [filterBuckets][]*filterQuery
errors []*filterQuery
} }
func newFilterTestGen(ctx *cli.Context) *filterTestGen { func newFilterTestGen(ctx *cli.Context) *filterTestGen {
return &filterTestGen{ return &filterTestGen{
client: makeClient(ctx), client: makeClient(ctx),
queryFile: ctx.String(filterQueryFileFlag.Name), queryFile: ctx.String(filterQueryFileFlag.Name),
errorFile: ctx.String(filterErrorFileFlag.Name),
} }
} }
@ -360,17 +355,6 @@ func (s *filterTestGen) writeQueries() {
file.Close() 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 { func mustGetFinalizedBlock(client *client) int64 {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel() defer cancel()

View file

@ -17,8 +17,10 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
"os"
"slices" "slices"
"sort" "sort"
"time" "time"
@ -41,7 +43,7 @@ var (
} }
) )
const passCount = 1 const passCount = 3
func filterPerfCmd(ctx *cli.Context) error { func filterPerfCmd(ctx *cli.Context) error {
cfg := testConfigFromCLI(ctx) cfg := testConfigFromCLI(ctx)
@ -61,7 +63,10 @@ func filterPerfCmd(ctx *cli.Context) error {
} }
// Run test queries. // Run test queries.
var failed, mismatch int var (
failed, pruned, mismatch int
errors []*filterQuery
)
for i := 1; i <= passCount; i++ { for i := 1; i <= passCount; i++ {
fmt.Println("Performance test pass", i, "/", passCount) fmt.Println("Performance test pass", i, "/", passCount)
for len(queries) > 0 { for len(queries) > 0 {
@ -71,27 +76,35 @@ func filterPerfCmd(ctx *cli.Context) error {
queries = queries[:len(queries)-1] queries = queries[:len(queries)-1]
start := time.Now() start := time.Now()
qt.query.run(cfg.client, cfg.historyPruneBlock) qt.query.run(cfg.client, cfg.historyPruneBlock)
if qt.query.Err == errPrunedHistory {
pruned++
continue
}
qt.runtime = append(qt.runtime, time.Since(start)) qt.runtime = append(qt.runtime, time.Since(start))
slices.Sort(qt.runtime) slices.Sort(qt.runtime)
qt.medianTime = qt.runtime[len(qt.runtime)/2] qt.medianTime = qt.runtime[len(qt.runtime)/2]
if qt.query.Err != nil { if qt.query.Err != nil {
qt.query.printError()
errors = append(errors, qt.query)
failed++ failed++
continue continue
} }
if rhash := qt.query.calculateHash(); *qt.query.ResultHash != rhash { 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) 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 continue
} }
processed = append(processed, qt) processed = append(processed, qt)
if len(processed)%50 == 0 { 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 queries, processed = processed, nil
} }
// Show results and stats. // 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)) stats := make([]bucketStats, len(f.queries))
var wildcardStats bucketStats var wildcardStats bucketStats
for _, qt := range queries { for _, qt := range queries {
@ -114,11 +127,14 @@ func filterPerfCmd(ctx *cli.Context) error {
sort.Slice(queries, func(i, j int) bool { sort.Slice(queries, func(i, j int) bool {
return queries[i].medianTime > queries[j].medianTime return queries[i].medianTime > queries[j].medianTime
}) })
for i := 0; i < 10; i++ { for i, q := range queries {
q := queries[i] 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", 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) 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 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", 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)) 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 indexLock sync.RWMutex
indexedRange filterMapsRange indexedRange filterMapsRange
indexedView *ChainView // always consistent with the log index indexedView *ChainView // always consistent with the log index
hasTempRange bool
// also accessed by indexer and matcher backend but no locking needed. // also accessed by indexer and matcher backend but no locking needed.
filterMapCache *lru.Cache[uint32, filterMap] filterMapCache *lru.Cache[uint32, filterMap]
@ -94,7 +95,7 @@ type FilterMaps struct {
ptrTailUnindexMap uint32 ptrTailUnindexMap uint32
targetView *ChainView targetView *ChainView
matcherSyncRequest *FilterMapsMatcherBackend matcherSyncRequests []*FilterMapsMatcherBackend
historyCutoff uint64 historyCutoff uint64
finalBlock, lastFinal uint64 finalBlock, lastFinal uint64
lastFinalEpoch uint32 lastFinalEpoch uint32
@ -330,7 +331,7 @@ func (f *FilterMaps) init() error {
fmr.blocks = common.NewRange(cp.BlockNumber+1, 0) fmr.blocks = common.NewRange(cp.BlockNumber+1, 0)
fmr.maps = common.NewRange(uint32(bestLen)<<f.logMapsPerEpoch, 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() 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 // setRange updates the indexed chain view and covered range and also adds the
// changes to the given batch. // changes to the given batch.
// Note that this function assumes that the index write lock is being held. // 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.indexedView = newView
f.indexedRange = newRange f.indexedRange = newRange
f.hasTempRange = isTempRange
f.updateMatchersValidRange() f.updateMatchersValidRange()
if newRange.initialized { if newRange.initialized {
rs := rawdb.FilterMapsRange{ rs := rawdb.FilterMapsRange{
@ -666,7 +668,7 @@ func (f *FilterMaps) deleteTailEpoch(epoch uint32) error {
} else { } else {
return errors.New("invalid tail epoch number") 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) first := f.mapRowIndex(firstMap, 0)
count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first count := f.mapRowIndex(firstMap+f.mapsPerEpoch, 0) - first
rawdb.DeleteFilterMapRows(f.db, common.NewRange(first, count)) 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 // processSingleEvent processes a single event either in a blocking or
// non-blocking manner. // non-blocking manner.
func (f *FilterMaps) processSingleEvent(blocking bool) bool { func (f *FilterMaps) processSingleEvent(blocking bool) bool {
if f.matcherSyncRequest != nil && f.targetHeadIndexed() { if !f.hasTempRange {
f.matcherSyncRequest.synced() for _, mb := range f.matcherSyncRequests {
f.matcherSyncRequest = nil mb.synced()
}
f.matcherSyncRequests = nil
} }
if blocking { if blocking {
select { select {
case target := <-f.targetCh: case target := <-f.targetCh:
f.setTarget(target) 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.blockProcessing = <-f.blockProcessingCh:
case <-f.closeCh: case <-f.closeCh:
f.stop = true f.stop = true
@ -160,7 +163,8 @@ func (f *FilterMaps) processSingleEvent(blocking bool) bool {
select { select {
case target := <-f.targetCh: case target := <-f.targetCh:
f.setTarget(target) 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.blockProcessing = <-f.blockProcessingCh:
case <-f.closeCh: case <-f.closeCh:
f.stop = true 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 // do not exit while in partially written state but do allow processing
// events and pausing while block processing is in progress // events and pausing while block processing is in progress
r.f.indexLock.Unlock()
pauseCb() pauseCb()
r.f.indexLock.Lock()
batch = r.f.db.NewBatch() 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 // add or update filter rows
for rowIndex := uint32(0); rowIndex < r.f.mapHeight; rowIndex++ { for rowIndex := uint32(0); rowIndex < r.f.mapHeight; rowIndex++ {
var ( var (
@ -469,7 +473,7 @@ func (r *mapRenderer) writeFinishedMaps(pauseCb func() bool) error {
} }
r.finishedMaps = make(map[uint32]*renderedMap) r.finishedMaps = make(map[uint32]*renderedMap)
r.finished.SetFirst(r.finished.AfterLast()) 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 { if err := batch.Write(); err != nil {
log.Crit("Error writing log index update batch", "error", err) 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 indexedBlocks.SetAfterLast(indexedBlocks.Last()) // remove partially indexed last block
} }
fm.syncCh <- SyncRange{ fm.syncCh <- SyncRange{
HeadNumber: fm.f.indexedView.headNumber, HeadNumber: fm.f.targetView.headNumber,
ValidBlocks: fm.validBlocks, ValidBlocks: fm.validBlocks,
IndexedBlocks: indexedBlocks, IndexedBlocks: indexedBlocks,
} }

View file

@ -67,6 +67,10 @@ var (
// transactions is reached for specific accounts. // transactions is reached for specific accounts.
ErrInflightTxLimitReached = errors.New("in-flight transaction limit reached for delegated 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 // ErrAuthorityReserved is returned if a transaction has an authorization
// signed by an address which already has in-flight transactions known to the // signed by an address which already has in-flight transactions known to the
// pool. // pool.
@ -606,33 +610,39 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
return pool.validateAuth(tx) 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 // validateAuth verifies that the transaction complies with code authorization
// restrictions brought by SetCode transaction type. // restrictions brought by SetCode transaction type.
func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { 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 // Allow at most one in-flight tx for delegated accounts or those with a
// pending authorization. // pending authorization.
if pool.currentState.GetCodeHash(from) != types.EmptyCodeHash || len(pool.all.auths[from]) != 0 { if err := pool.checkDelegationLimit(tx); err != nil {
var ( return err
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
}
} }
// Authorities cannot conflict with any pending or queued transactions. // Authorities cannot conflict with any pending or queued transactions.
if auths := tx.SetCodeAuthorities(); len(auths) > 0 { if auths := tx.SetCodeAuthorities(); len(auths) > 0 {

View file

@ -2262,6 +2262,11 @@ func TestSetCodeTransactions(t *testing.T) {
aa := common.Address{0xaa, 0xaa} aa := common.Address{0xaa, 0xaa}
statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...)) statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...))
statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)}) 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. // Send transactions. First is accepted, second is rejected.
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keyA)); err != nil {
t.Fatalf("%s: failed to add remote transaction: %v", name, err) 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) { 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) 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) { 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) 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, config: nil,
expectErr: 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. // Standard JSON trace upon the head, plain transfer.
{ {
@ -366,7 +366,7 @@ func TestTraceCall(t *testing.T) {
}, },
config: nil, config: nil,
expectErr: 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 // 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))), Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))),
}, },
config: nil, config: nil,
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`,
}, },
// Before the first transaction, should be failed // Before the first transaction, should be failed
{ {
@ -411,7 +411,7 @@ func TestTraceCall(t *testing.T) {
}, },
config: &TraceCallConfig{TxIndex: uintPtr(2)}, config: &TraceCallConfig{TxIndex: uintPtr(2)},
expectErr: nil, 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 // Standard JSON trace upon the non-existent block, error expects
{ {
@ -435,7 +435,7 @@ func TestTraceCall(t *testing.T) {
}, },
config: nil, config: nil,
expectErr: nil, expectErr: nil,
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, expect: `{"gas":21000,"failed":false,"returnValue":"0x","structLogs":[]}`,
}, },
// Tracing on 'pending' should fail: // Tracing on 'pending' should fail:
{ {
@ -458,7 +458,7 @@ func TestTraceCall(t *testing.T) {
BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
}, },
expectErr: nil, 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":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]},
{"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`, {"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{ if !reflect.DeepEqual(have, &logger.ExecutionResult{
Gas: params.TxGas, Gas: params.TxGas,
Failed: false, Failed: false,
ReturnValue: "", ReturnValue: []byte{},
StructLogs: []json.RawMessage{}, StructLogs: []json.RawMessage{},
}) { }) {
t.Error("Transaction tracing result is different") t.Error("Transaction tracing result is different")
@ -596,7 +596,7 @@ func TestTraceBlock(t *testing.T) {
// Trace head block // Trace head block
{ {
blockNumber: rpc.BlockNumber(genBlocks), 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 // Trace non-existent block
{ {
@ -606,12 +606,12 @@ func TestTraceBlock(t *testing.T) {
// Trace latest block // Trace latest block
{ {
blockNumber: rpc.LatestBlockNumber, 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 // Trace pending block
{ {
blockNumber: rpc.PendingBlockNumber, 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 { 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)))}, 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 // 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 { // Override blocknumber
blockNumber: rpc.LatestBlockNumber, blockNumber: rpc.LatestBlockNumber,
@ -761,7 +761,7 @@ func TestTracingWithOverrides(t *testing.T) {
config: &TraceCallConfig{ config: &TraceCallConfig{
BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, 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 { // Override blocknumber, and query a blockhash
blockNumber: rpc.LatestBlockNumber, blockNumber: rpc.LatestBlockNumber,
@ -781,7 +781,7 @@ func TestTracingWithOverrides(t *testing.T) {
config: &TraceCallConfig{ config: &TraceCallConfig{
BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))}, 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; 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 { // Same again, this time with storage override
blockNumber: rpc.LatestBlockNumber, blockNumber: rpc.LatestBlockNumber,
@ -833,7 +833,7 @@ func TestTracingWithOverrides(t *testing.T) {
}, },
}, },
//want: `{"gas":46900,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000539"}`, //want: `{"gas":46900,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000539"}`,
want: `{"gas":44100,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000000001"}`, want: `{"gas":44100,"failed":false,"returnValue":"0x0000000000000000000000000000000000000000000000000000000000000001"}`,
}, },
{ // No state override { // No state override
blockNumber: rpc.LatestBlockNumber, 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 { // Full state override
// The original storage is // 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 { // Partial state override
// The original storage is // 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 { // Call to precompile ECREC (0x01), but code was modified to add 1 to input
blockNumber: rpc.LatestBlockNumber, 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 { // Call to ECREC Precompiled on a different address, expect the original behaviour of ECREC precompile
blockNumber: rpc.LatestBlockNumber, 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 { for i, tc := range testSuite {
@ -1084,7 +1084,7 @@ func TestTraceChain(t *testing.T) {
backend.relHook = func() { rel.Add(1) } backend.relHook = func() { rel.Add(1) }
api := NewAPI(backend) 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 { var cases = []struct {
start uint64 start uint64
end uint64 end uint64
@ -1198,7 +1198,7 @@ func TestTraceBlockWithBasefee(t *testing.T) {
// Trace head block // Trace head block
{ {
blockNumber: rpc.BlockNumber(genBlocks), 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 { for i, tc := range testSuite {

View file

@ -350,14 +350,13 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
failed := l.err != nil failed := l.err != nil
returnData := common.CopyBytes(l.output) returnData := common.CopyBytes(l.output)
// Return data when successful and revert reason when reverted, otherwise empty. // Return data when successful and revert reason when reverted, otherwise empty.
returnVal := fmt.Sprintf("%x", returnData)
if failed && !errors.Is(l.err, vm.ErrExecutionReverted) { if failed && !errors.Is(l.err, vm.ErrExecutionReverted) {
returnVal = "" returnData = []byte{}
} }
return json.Marshal(&ExecutionResult{ return json.Marshal(&ExecutionResult{
Gas: l.usedGas, Gas: l.usedGas,
Failed: failed, Failed: failed,
ReturnValue: returnVal, ReturnValue: returnData,
StructLogs: l.logs, StructLogs: l.logs,
}) })
} }
@ -527,6 +526,6 @@ func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.O
type ExecutionResult struct { type ExecutionResult struct {
Gas uint64 `json:"gas"` Gas uint64 `json:"gas"`
Failed bool `json:"failed"` Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"` ReturnValue hexutil.Bytes `json:"returnValue"`
StructLogs []json.RawMessage `json:"structLogs"` 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. // encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns.
type codecV5 interface { type codecV5 interface {
// Encode encodes a packet. // 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. // 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. // 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. // 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. // handleUnknown initiates a handshake by responding with WHOAREYOU.
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr netip.AddrPort) { 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} challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
crand.Read(challenge.IDNonce[:]) crand.Read(challenge.IDNonce[:])
if n := t.GetNode(fromID); n != nil { 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) { test.waitPacketOut(func(p *v5wire.Whoareyou, addr netip.AddrPort, _ v5wire.Nonce) {
check(p, 0) 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. // Make node known.
n := test.getNode(test.remotekey, test.remoteaddr).Node() 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. // This test checks that incoming FINDNODE calls are handled correctly.
func TestUDPv5_findnodeHandling(t *testing.T) { func TestUDPv5_findnodeHandling(t *testing.T) {
t.Parallel() t.Parallel()
@ -698,6 +754,8 @@ type testCodec struct {
test *udpV5Test test *udpV5Test
id enode.ID id enode.ID
ctr uint64 ctr uint64
sentChallenges map[enode.ID]*v5wire.Whoareyou
} }
type testCodecFrame struct { type testCodecFrame struct {
@ -712,11 +770,23 @@ func (c *testCodec) Encode(toID enode.ID, addr string, p v5wire.Packet, _ *v5wir
var authTag v5wire.Nonce var authTag v5wire.Nonce
binary.BigEndian.PutUint64(authTag[:], c.ctr) 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) penc, _ := rlp.EncodeToBytes(p)
frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc}) frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.Kind(), penc})
return frame, authTag, err 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) { func (c *testCodec) Decode(input []byte, addr string) (enode.ID, *enode.Node, v5wire.Packet, error) {
frame, p, err := c.decodeFrame(input) frame, p, err := c.decodeFrame(input)
if err != nil { 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 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) { func (c *Codec) writeHeaders(head *Header) {
c.buf.Reset() c.buf.Reset()
c.buf.Write(head.IV[:]) c.buf.Write(head.IV[:])