@ -24,16 +24,15 @@ package discover
import (
import (
"context"
"context"
crand "crypto/rand"
"encoding/binary"
"fmt"
"fmt"
mrand "math/rand"
"net"
"net"
"slices"
"sort"
"sort"
"sync"
"sync"
"time"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enode"
@ -55,21 +54,21 @@ const (
bucketIPLimit , bucketSubnet = 2 , 24 // at most 2 addresses from the same /24
bucketIPLimit , bucketSubnet = 2 , 24 // at most 2 addresses from the same /24
tableIPLimit , tableSubnet = 10 , 24
tableIPLimit , tableSubnet = 10 , 24
copyNodesInterval = 30 * time . Second
seedMinTableTime = 5 * time . Minute
seedMinTableTime = 5 * time . Minute
seedCount = 30
seedCount = 30
seedMaxAge = 5 * 24 * time . Hour
seedMaxAge = 5 * 24 * time . Hour
)
)
// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps
// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps
// itself up-to-date by verifying the liveness of neighbors and requesting their node
// itself up-to-date by verifying the liveness of neighbors and requesting their node
// records when announcements of a new record version are received.
// records when announcements of a new record version are received.
type Table struct {
type Table struct {
mutex sync . Mutex // protects buckets, bucket content, nursery, rand
mutex sync . Mutex // protects buckets, bucket content, nursery, rand
buckets [ nBuckets ] * bucket // index of known nodes by distance
buckets [ nBuckets ] * bucket // index of known nodes by distance
nursery [ ] * node // bootstrap nodes
nursery [ ] * node // bootstrap nodes
rand * mrand . Rand // source of randomness, periodically reseeded
rand reseedingRandom // source of randomness, periodically reseeded
ips netutil . DistinctNetSet
ips netutil . DistinctNetSet
revalidation tableRevalidation
db * enode . DB // database of known nodes
db * enode . DB // database of known nodes
net transport
net transport
@ -77,10 +76,14 @@ type Table struct {
log log . Logger
log log . Logger
// loop channels
// loop channels
refreshReq chan chan struct { }
refreshReq chan chan struct { }
initDone chan struct { }
revalResponseCh chan revalidationResponse
closeReq chan struct { }
addNodeCh chan addNodeOp
closed chan struct { }
addNodeHandled chan bool
trackRequestCh chan trackRequestOp
initDone chan struct { }
closeReq chan struct { }
closed chan struct { }
nodeAddedHook func ( * bucket , * node )
nodeAddedHook func ( * bucket , * node )
nodeRemovedHook func ( * bucket , * node )
nodeRemovedHook func ( * bucket , * node )
@ -104,22 +107,33 @@ type bucket struct {
index int
index int
}
}
type addNodeOp struct {
node * node
isInbound bool
}
type trackRequestOp struct {
node * node
foundNodes [ ] * node
success bool
}
func newTable ( t transport , db * enode . DB , cfg Config ) ( * Table , error ) {
func newTable ( t transport , db * enode . DB , cfg Config ) ( * Table , error ) {
cfg = cfg . withDefaults ( )
cfg = cfg . withDefaults ( )
tab := & Table {
tab := & Table {
net : t ,
net : t ,
db : db ,
db : db ,
cfg : cfg ,
cfg : cfg ,
log : cfg . Log ,
log : cfg . Log ,
refreshReq : make ( chan chan struct { } ) ,
refreshReq : make ( chan chan struct { } ) ,
initDone : make ( chan struct { } ) ,
revalResponseCh : make ( chan revalidationResponse ) ,
closeReq : make ( chan struct { } ) ,
addNodeCh : make ( chan addNodeOp ) ,
closed : make ( chan struct { } ) ,
addNodeHandled : make ( chan bool ) ,
rand : mrand . New ( mrand . NewSource ( 0 ) ) ,
trackRequestCh : make ( chan trackRequestOp ) ,
ips : netutil . DistinctNetSet { Subnet : tableSubnet , Limit : tableIPLimit } ,
initDone : make ( chan struct { } ) ,
}
closeReq : make ( chan struct { } ) ,
if err := tab . setFallbackNodes ( cfg . Bootnodes ) ; err != nil {
closed : make ( chan struct { } ) ,
return nil , err
ips : netutil . DistinctNetSet { Subnet : tableSubnet , Limit : tableIPLimit } ,
}
}
for i := range tab . buckets {
for i := range tab . buckets {
tab . buckets [ i ] = & bucket {
tab . buckets [ i ] = & bucket {
@ -127,41 +141,34 @@ func newTable(t transport, db *enode.DB, cfg Config) (*Table, error) {
ips : netutil . DistinctNetSet { Subnet : bucketSubnet , Limit : bucketIPLimit } ,
ips : netutil . DistinctNetSet { Subnet : bucketSubnet , Limit : bucketIPLimit } ,
}
}
}
}
tab . seedRand ( )
tab . rand . seed ( )
tab . loadSeedNodes ( )
tab . revalidation . init ( & cfg )
return tab , nil
}
func newMeteredTable ( t transport , db * enode . DB , cfg Config ) ( * Table , error ) {
// initial table content
tab , err := newTable ( t , db , cfg )
if err := tab . setFallbackNodes ( cfg . Bootnodes ) ; err != nil {
if err != nil {
return nil , err
return nil , err
}
}
if metrics . Enabled {
tab . loadSeedNodes ( )
tab . nodeAddedHook = func ( b * bucket , n * node ) {
bucketsCounter [ b . index ] . Inc ( 1 )
}
tab . nodeRemovedHook = func ( b * bucket , n * node ) {
bucketsCounter [ b . index ] . Dec ( 1 )
}
}
return tab , nil
return tab , nil
}
}
// Nodes returns all nodes contained in the table.
// Nodes returns all nodes contained in the table.
func ( tab * Table ) Nodes ( ) [ ] * enode . Node {
func ( tab * Table ) Nodes ( ) [ ] [ ] BucketNode {
if ! tab . isInitDone ( ) {
return nil
}
tab . mutex . Lock ( )
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
defer tab . mutex . Unlock ( )
var nodes [ ] * enode . Node
nodes := make ( [ ] [ ] BucketNode , len ( tab . buckets ) )
for _ , b := range & tab . buckets {
for i , b := range & tab . buckets {
for _ , n := range b . entries {
nodes [ i ] = make ( [ ] BucketNode , len ( b . entries ) )
nodes = append ( nodes , unwrapNode ( n ) )
for j , n := range b . entries {
nodes [ i ] [ j ] = BucketNode {
Node : n . Node ,
Checks : int ( n . livenessChecks ) ,
Live : n . isValidatedLive ,
AddedToTable : n . addedToTable ,
AddedToBucket : n . addedToBucket ,
}
}
}
}
}
return nodes
return nodes
@ -171,15 +178,6 @@ func (tab *Table) self() *enode.Node {
return tab . net . Self ( )
return tab . net . Self ( )
}
}
func ( tab * Table ) seedRand ( ) {
var b [ 8 ] byte
crand . Read ( b [ : ] )
tab . mutex . Lock ( )
tab . rand . Seed ( int64 ( binary . BigEndian . Uint64 ( b [ : ] ) ) )
tab . mutex . Unlock ( )
}
// getNode returns the node with the given ID or nil if it isn't in the table.
// getNode returns the node with the given ID or nil if it isn't in the table.
func ( tab * Table ) getNode ( id enode . ID ) * enode . Node {
func ( tab * Table ) getNode ( id enode . ID ) * enode . Node {
tab . mutex . Lock ( )
tab . mutex . Lock ( )
@ -239,52 +237,173 @@ func (tab *Table) refresh() <-chan struct{} {
return done
return done
}
}
// loop schedules runs of doRefresh, doRevalidate and copyLiveNodes.
// findnodeByID returns the n nodes in the table that are closest to the given id.
// This is used by the FINDNODE/v4 handler.
//
// The preferLive parameter says whether the caller wants liveness-checked results. If
// preferLive is true and the table contains any verified nodes, the result will not
// contain unverified nodes. However, if there are no verified nodes at all, the result
// will contain unverified nodes.
func ( tab * Table ) findnodeByID ( target enode . ID , nresults int , preferLive bool ) * nodesByDistance {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
// Scan all buckets. There might be a better way to do this, but there aren't that many
// buckets, so this solution should be fine. The worst-case complexity of this loop
// is O(tab.len() * nresults).
nodes := & nodesByDistance { target : target }
liveNodes := & nodesByDistance { target : target }
for _ , b := range & tab . buckets {
for _ , n := range b . entries {
nodes . push ( n , nresults )
if preferLive && n . isValidatedLive {
liveNodes . push ( n , nresults )
}
}
}
if preferLive && len ( liveNodes . entries ) > 0 {
return liveNodes
}
return nodes
}
// appendLiveNodes adds nodes at the given distance to the result slice.
// This is used by the FINDNODE/v5 handler.
func ( tab * Table ) appendLiveNodes ( dist uint , result [ ] * enode . Node ) [ ] * enode . Node {
if dist > 256 {
return result
}
if dist == 0 {
return append ( result , tab . self ( ) )
}
tab . mutex . Lock ( )
for _ , n := range tab . bucketAtDistance ( int ( dist ) ) . entries {
if n . isValidatedLive {
result = append ( result , n . Node )
}
}
tab . mutex . Unlock ( )
// Shuffle result to avoid always returning same nodes in FINDNODE/v5.
tab . rand . Shuffle ( len ( result ) , func ( i , j int ) {
result [ i ] , result [ j ] = result [ j ] , result [ i ]
} )
return result
}
// len returns the number of nodes in the table.
func ( tab * Table ) len ( ) ( n int ) {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
for _ , b := range & tab . buckets {
n += len ( b . entries )
}
return n
}
// addFoundNode adds a node which may not be live. If the bucket has space available,
// adding the node succeeds immediately. Otherwise, the node is added to the replacements
// list.
//
// The caller must not hold tab.mutex.
func ( tab * Table ) addFoundNode ( n * node ) bool {
op := addNodeOp { node : n , isInbound : false }
select {
case tab . addNodeCh <- op :
return <- tab . addNodeHandled
case <- tab . closeReq :
return false
}
}
// addInboundNode adds a node from an inbound contact. If the bucket has no space, the
// node is added to the replacements list.
//
// There is an additional safety measure: if the table is still initializing the node is
// not added. This prevents an attack where the table could be filled by just sending ping
// repeatedly.
//
// The caller must not hold tab.mutex.
func ( tab * Table ) addInboundNode ( n * node ) bool {
op := addNodeOp { node : n , isInbound : true }
select {
case tab . addNodeCh <- op :
return <- tab . addNodeHandled
case <- tab . closeReq :
return false
}
}
func ( tab * Table ) trackRequest ( n * node , success bool , foundNodes [ ] * node ) {
op := trackRequestOp { n , foundNodes , success }
select {
case tab . trackRequestCh <- op :
case <- tab . closeReq :
}
}
// loop is the main loop of Table.
func ( tab * Table ) loop ( ) {
func ( tab * Table ) loop ( ) {
var (
var (
revalidate = time . NewTimer ( tab . nextRevalidateTime ( ) )
refresh = time . NewTimer ( tab . nextRefreshTime ( ) )
refresh = time . NewTimer ( tab . nextRefreshTime ( ) )
refreshDone = make ( chan struct { } ) // where doRefresh reports completion
copyNodes = time . NewTicker ( copyNodesInterval )
waiting = [ ] chan struct { } { tab . initDone } // holds waiting callers while doRefresh runs
refreshDone = make ( chan struct { } ) // where doRefresh reports completion
revalTimer = mclock . NewAlarm ( tab . cfg . Clock )
revalidateDone chan struct { } // where doRevalidate reports completion
reseedRandTimer = time . NewTicker ( 10 * time . Minute )
waiting = [ ] chan struct { } { tab . initDone } // holds waiting callers while doRefresh runs
)
)
defer refresh . Stop ( )
defer refresh . Stop ( )
defer revalidate . Stop ( )
defer revalTimer . Stop ( )
defer copyNodes . Stop ( )
defer reseedRandTimer . Stop ( )
// Start initial refresh.
// Start initial refresh.
go tab . doRefresh ( refreshDone )
go tab . doRefresh ( refreshDone )
loop :
loop :
for {
for {
nextTime := tab . revalidation . run ( tab , tab . cfg . Clock . Now ( ) )
revalTimer . Schedule ( nextTime )
select {
select {
case <- reseedRandTimer . C :
tab . rand . seed ( )
case <- revalTimer . C ( ) :
case r := <- tab . revalResponseCh :
tab . revalidation . handleResponse ( tab , r )
case op := <- tab . addNodeCh :
tab . mutex . Lock ( )
ok := tab . handleAddNode ( op )
tab . mutex . Unlock ( )
tab . addNodeHandled <- ok
case op := <- tab . trackRequestCh :
tab . handleTrackRequest ( op )
case <- refresh . C :
case <- refresh . C :
tab . seedRand ( )
if refreshDone == nil {
if refreshDone == nil {
refreshDone = make ( chan struct { } )
refreshDone = make ( chan struct { } )
go tab . doRefresh ( refreshDone )
go tab . doRefresh ( refreshDone )
}
}
case req := <- tab . refreshReq :
case req := <- tab . refreshReq :
waiting = append ( waiting , req )
waiting = append ( waiting , req )
if refreshDone == nil {
if refreshDone == nil {
refreshDone = make ( chan struct { } )
refreshDone = make ( chan struct { } )
go tab . doRefresh ( refreshDone )
go tab . doRefresh ( refreshDone )
}
}
case <- refreshDone :
case <- refreshDone :
for _ , ch := range waiting {
for _ , ch := range waiting {
close ( ch )
close ( ch )
}
}
waiting , refreshDone = nil , nil
waiting , refreshDone = nil , nil
refresh . Reset ( tab . nextRefreshTime ( ) )
refresh . Reset ( tab . nextRefreshTime ( ) )
case <- revalidate . C :
revalidateDone = make ( chan struct { } )
go tab . doRevalidate ( revalidateDone )
case <- revalidateDone :
revalidate . Reset ( tab . nextRevalidateTime ( ) )
revalidateDone = nil
case <- copyNodes . C :
go tab . copyLiveNodes ( )
case <- tab . closeReq :
case <- tab . closeReq :
break loop
break loop
}
}
@ -296,9 +415,6 @@ loop:
for _ , ch := range waiting {
for _ , ch := range waiting {
close ( ch )
close ( ch )
}
}
if revalidateDone != nil {
<- revalidateDone
}
close ( tab . closed )
close ( tab . closed )
}
}
@ -335,169 +451,15 @@ func (tab *Table) loadSeedNodes() {
age := time . Since ( tab . db . LastPongReceived ( seed . ID ( ) , seed . IP ( ) ) )
age := time . Since ( tab . db . LastPongReceived ( seed . ID ( ) , seed . IP ( ) ) )
tab . log . Trace ( "Found seed node in database" , "id" , seed . ID ( ) , "addr" , seed . addr ( ) , "age" , age )
tab . log . Trace ( "Found seed node in database" , "id" , seed . ID ( ) , "addr" , seed . addr ( ) , "age" , age )
}
}
tab . addSeenNode ( seed )
tab . handleAddNode ( addNodeOp { node : seed , isInbound : false } )
}
}
}
}
// doRevalidate checks that the last node in a random bucket is still live and replaces or
// deletes the node if it isn't.
func ( tab * Table ) doRevalidate ( done chan <- struct { } ) {
defer func ( ) { done <- struct { } { } } ( )
last , bi := tab . nodeToRevalidate ( )
if last == nil {
// No non-empty bucket found.
return
}
// Ping the selected node and wait for a pong.
remoteSeq , err := tab . net . ping ( unwrapNode ( last ) )
// Also fetch record if the node replied and returned a higher sequence number.
if last . Seq ( ) < remoteSeq {
n , err := tab . net . RequestENR ( unwrapNode ( last ) )
if err != nil {
tab . log . Debug ( "ENR request failed" , "id" , last . ID ( ) , "addr" , last . addr ( ) , "err" , err )
} else {
last = & node { Node : * n , addedAt : last . addedAt , livenessChecks : last . livenessChecks }
}
}
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
b := tab . buckets [ bi ]
if err == nil {
// The node responded, move it to the front.
last . livenessChecks ++
tab . log . Debug ( "Revalidated node" , "b" , bi , "id" , last . ID ( ) , "checks" , last . livenessChecks )
tab . bumpInBucket ( b , last )
return
}
// No reply received, pick a replacement or delete the node if there aren't
// any replacements.
if r := tab . replace ( b , last ) ; r != nil {
tab . log . Debug ( "Replaced dead node" , "b" , bi , "id" , last . ID ( ) , "ip" , last . IP ( ) , "checks" , last . livenessChecks , "r" , r . ID ( ) , "rip" , r . IP ( ) )
} else {
tab . log . Debug ( "Removed dead node" , "b" , bi , "id" , last . ID ( ) , "ip" , last . IP ( ) , "checks" , last . livenessChecks )
}
}
// nodeToRevalidate returns the last node in a random, non-empty bucket.
func ( tab * Table ) nodeToRevalidate ( ) ( n * node , bi int ) {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
for _ , bi = range tab . rand . Perm ( len ( tab . buckets ) ) {
b := tab . buckets [ bi ]
if len ( b . entries ) > 0 {
last := b . entries [ len ( b . entries ) - 1 ]
return last , bi
}
}
return nil , 0
}
func ( tab * Table ) nextRevalidateTime ( ) time . Duration {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
return time . Duration ( tab . rand . Int63n ( int64 ( tab . cfg . PingInterval ) ) )
}
func ( tab * Table ) nextRefreshTime ( ) time . Duration {
func ( tab * Table ) nextRefreshTime ( ) time . Duration {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
half := tab . cfg . RefreshInterval / 2
half := tab . cfg . RefreshInterval / 2
return half + time . Duration ( tab . rand . Int63n ( int64 ( half ) ) )
return half + time . Duration ( tab . rand . Int63n ( int64 ( half ) ) )
}
}
// copyLiveNodes adds nodes from the table to the database if they have been in the table
// longer than seedMinTableTime.
func ( tab * Table ) copyLiveNodes ( ) {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
now := time . Now ( )
for _ , b := range & tab . buckets {
for _ , n := range b . entries {
if n . livenessChecks > 0 && now . Sub ( n . addedAt ) >= seedMinTableTime {
tab . db . UpdateNode ( unwrapNode ( n ) )
}
}
}
}
// findnodeByID returns the n nodes in the table that are closest to the given id.
// This is used by the FINDNODE/v4 handler.
//
// The preferLive parameter says whether the caller wants liveness-checked results. If
// preferLive is true and the table contains any verified nodes, the result will not
// contain unverified nodes. However, if there are no verified nodes at all, the result
// will contain unverified nodes.
func ( tab * Table ) findnodeByID ( target enode . ID , nresults int , preferLive bool ) * nodesByDistance {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
// Scan all buckets. There might be a better way to do this, but there aren't that many
// buckets, so this solution should be fine. The worst-case complexity of this loop
// is O(tab.len() * nresults).
nodes := & nodesByDistance { target : target }
liveNodes := & nodesByDistance { target : target }
for _ , b := range & tab . buckets {
for _ , n := range b . entries {
nodes . push ( n , nresults )
if preferLive && n . livenessChecks > 0 {
liveNodes . push ( n , nresults )
}
}
}
if preferLive && len ( liveNodes . entries ) > 0 {
return liveNodes
}
return nodes
}
// appendLiveNodes adds nodes at the given distance to the result slice.
func ( tab * Table ) appendLiveNodes ( dist uint , result [ ] * enode . Node ) [ ] * enode . Node {
if dist > 256 {
return result
}
if dist == 0 {
return append ( result , tab . self ( ) )
}
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
for _ , n := range tab . bucketAtDistance ( int ( dist ) ) . entries {
if n . livenessChecks >= 1 {
node := n . Node // avoid handing out pointer to struct field
result = append ( result , & node )
}
}
return result
}
// len returns the number of nodes in the table.
func ( tab * Table ) len ( ) ( n int ) {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
for _ , b := range & tab . buckets {
n += len ( b . entries )
}
return n
}
// bucketLen returns the number of nodes in the bucket for the given ID.
func ( tab * Table ) bucketLen ( id enode . ID ) int {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
return len ( tab . bucket ( id ) . entries )
}
// bucket returns the bucket for the given node ID hash.
// bucket returns the bucket for the given node ID hash.
func ( tab * Table ) bucket ( id enode . ID ) * bucket {
func ( tab * Table ) bucket ( id enode . ID ) * bucket {
d := enode . LogDist ( tab . self ( ) . ID ( ) , id )
d := enode . LogDist ( tab . self ( ) . ID ( ) , id )
@ -511,95 +473,6 @@ func (tab *Table) bucketAtDistance(d int) *bucket {
return tab . buckets [ d - bucketMinDistance - 1 ]
return tab . buckets [ d - bucketMinDistance - 1 ]
}
}
// addSeenNode adds a node which may or may not be live to the end of a bucket. If the
// bucket has space available, adding the node succeeds immediately. Otherwise, the node is
// added to the replacements list.
//
// The caller must not hold tab.mutex.
func ( tab * Table ) addSeenNode ( n * node ) {
if n . ID ( ) == tab . self ( ) . ID ( ) {
return
}
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
b := tab . bucket ( n . ID ( ) )
if contains ( b . entries , n . ID ( ) ) {
// Already in bucket, don't add.
return
}
if len ( b . entries ) >= bucketSize {
// Bucket full, maybe add as replacement.
tab . addReplacement ( b , n )
return
}
if ! tab . addIP ( b , n . IP ( ) ) {
// Can't add: IP limit reached.
return
}
// Add to end of bucket:
b . entries = append ( b . entries , n )
b . replacements = deleteNode ( b . replacements , n )
n . addedAt = time . Now ( )
if tab . nodeAddedHook != nil {
tab . nodeAddedHook ( b , n )
}
}
// addVerifiedNode adds a node whose existence has been verified recently to the front of a
// bucket. If the node is already in the bucket, it is moved to the front. If the bucket
// has no space, the node is added to the replacements list.
//
// There is an additional safety measure: if the table is still initializing the node
// is not added. This prevents an attack where the table could be filled by just sending
// ping repeatedly.
//
// The caller must not hold tab.mutex.
func ( tab * Table ) addVerifiedNode ( n * node ) {
if ! tab . isInitDone ( ) {
return
}
if n . ID ( ) == tab . self ( ) . ID ( ) {
return
}
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
b := tab . bucket ( n . ID ( ) )
if tab . bumpInBucket ( b , n ) {
// Already in bucket, moved to front.
return
}
if len ( b . entries ) >= bucketSize {
// Bucket full, maybe add as replacement.
tab . addReplacement ( b , n )
return
}
if ! tab . addIP ( b , n . IP ( ) ) {
// Can't add: IP limit reached.
return
}
// Add to front of bucket.
b . entries , _ = pushNode ( b . entries , n , bucketSize )
b . replacements = deleteNode ( b . replacements , n )
n . addedAt = time . Now ( )
if tab . nodeAddedHook != nil {
tab . nodeAddedHook ( b , n )
}
}
// delete removes an entry from the node table. It is used to evacuate dead nodes.
func ( tab * Table ) delete ( node * node ) {
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
tab . deleteInBucket ( tab . bucket ( node . ID ( ) ) , node )
}
func ( tab * Table ) addIP ( b * bucket , ip net . IP ) bool {
func ( tab * Table ) addIP ( b * bucket , ip net . IP ) bool {
if len ( ip ) == 0 {
if len ( ip ) == 0 {
return false // Nodes without IP cannot be added.
return false // Nodes without IP cannot be added.
@ -627,15 +500,51 @@ func (tab *Table) removeIP(b *bucket, ip net.IP) {
b . ips . Remove ( ip )
b . ips . Remove ( ip )
}
}
// handleAddNode adds the node in the request to the table, if there is space.
// The caller must hold tab.mutex.
func ( tab * Table ) handleAddNode ( req addNodeOp ) bool {
if req . node . ID ( ) == tab . self ( ) . ID ( ) {
return false
}
// For nodes from inbound contact, there is an additional safety measure: if the table
// is still initializing the node is not added.
if req . isInbound && ! tab . isInitDone ( ) {
return false
}
b := tab . bucket ( req . node . ID ( ) )
if tab . bumpInBucket ( b , req . node . Node ) {
// Already in bucket, update record.
return false
}
if len ( b . entries ) >= bucketSize {
// Bucket full, maybe add as replacement.
tab . addReplacement ( b , req . node )
return false
}
if ! tab . addIP ( b , req . node . IP ( ) ) {
// Can't add: IP limit reached.
return false
}
// Add to bucket.
b . entries = append ( b . entries , req . node )
b . replacements = deleteNode ( b . replacements , req . node )
tab . nodeAdded ( b , req . node )
return true
}
// addReplacement adds n to the replacement cache of bucket b.
func ( tab * Table ) addReplacement ( b * bucket , n * node ) {
func ( tab * Table ) addReplacement ( b * bucket , n * node ) {
for _ , e := range b . replacements {
if contains ( b . replacements , n . ID ( ) ) {
if e . ID ( ) == n . ID ( ) {
// TODO: update ENR
return // already in list
return
}
}
}
if ! tab . addIP ( b , n . IP ( ) ) {
if ! tab . addIP ( b , n . IP ( ) ) {
return
return
}
}
n . addedToTable = time . Now ( )
var removed * node
var removed * node
b . replacements , removed = pushNode ( b . replacements , n , maxReplacements )
b . replacements , removed = pushNode ( b . replacements , n , maxReplacements )
if removed != nil {
if removed != nil {
@ -643,59 +552,107 @@ func (tab *Table) addReplacement(b *bucket, n *node) {
}
}
}
}
// replace removes n from the replacement list and replaces 'last' with it if it is the
func ( tab * Table ) nodeAdded ( b * bucket , n * node ) {
// last entry in the bucket. If 'last' isn't the last entry, it has either been replaced
if n . addedToTable == ( time . Time { } ) {
// with someone else or became active.
n . addedToTable = time . Now ( )
func ( tab * Table ) replace ( b * bucket , last * node ) * node {
}
if len ( b . entries ) == 0 || b . entries [ len ( b . entries ) - 1 ] . ID ( ) != last . ID ( ) {
n . addedToBucket = time . Now ( )
// Entry has moved, don't replace it.
tab . revalidation . nodeAdded ( tab , n )
if tab . nodeAddedHook != nil {
tab . nodeAddedHook ( b , n )
}
if metrics . Enabled {
bucketsCounter [ b . index ] . Inc ( 1 )
}
}
func ( tab * Table ) nodeRemoved ( b * bucket , n * node ) {
tab . revalidation . nodeRemoved ( n )
if tab . nodeRemovedHook != nil {
tab . nodeRemovedHook ( b , n )
}
if metrics . Enabled {
bucketsCounter [ b . index ] . Dec ( 1 )
}
}
// deleteInBucket removes node n from the table.
// If there are replacement nodes in the bucket, the node is replaced.
func ( tab * Table ) deleteInBucket ( b * bucket , id enode . ID ) * node {
index := slices . IndexFunc ( b . entries , func ( e * node ) bool { return e . ID ( ) == id } )
if index == - 1 {
// Entry has been removed already.
return nil
return nil
}
}
// Still the last entry.
// Remove the node.
n := b . entries [ index ]
b . entries = slices . Delete ( b . entries , index , index + 1 )
tab . removeIP ( b , n . IP ( ) )
tab . nodeRemoved ( b , n )
// Add replacement.
if len ( b . replacements ) == 0 {
if len ( b . replacements ) == 0 {
tab . deleteInBucket ( b , last )
tab . log . Debug ( "Removed dead node" , "b" , b . index , "id" , n . ID ( ) , "ip" , n . IP ( ) )
return nil
return nil
}
}
r := b . replacements [ tab . rand . Intn ( len ( b . replacements ) ) ]
rindex := tab . rand . Intn ( len ( b . replacements ) )
b . replacements = deleteNode ( b . replacements , r )
rep := b . replacements [ rindex ]
b . entries [ len ( b . entries ) - 1 ] = r
b . replacements = slices . Delete ( b . replacements , rindex , rindex + 1 )
tab . removeIP ( b , last . IP ( ) )
b . entries = append ( b . entries , rep )
return r
tab . nodeAdded ( b , rep )
}
tab . log . Debug ( "Replaced dead node" , "b" , b . index , "id" , n . ID ( ) , "ip" , n . IP ( ) , "r" , rep . ID ( ) , "rip" , rep . IP ( ) )
return rep
// bumpInBucket moves the given node to the front of the bucket entry list
}
// if it is contained in that list.
func ( tab * Table ) bumpInBucket ( b * bucket , n * node ) bool {
// bumpInBucket updates the node record of n in the bucket.
for i := range b . entries {
func ( tab * Table ) bumpInBucket ( b * bucket , newRecord * enode . Node ) bool {
if b . entries [ i ] . ID ( ) == n . ID ( ) {
i := slices . IndexFunc ( b . entries , func ( elem * node ) bool {
if ! n . IP ( ) . Equal ( b . entries [ i ] . IP ( ) ) {
return elem . ID ( ) == newRecord . ID ( )
// Endpoint has changed, ensure that the new IP fits into table limits.
} )
tab . removeIP ( b , b . entries [ i ] . IP ( ) )
if i == - 1 {
if ! tab . addIP ( b , n . IP ( ) ) {
return false
// It doesn't, put the previous one back.
}
tab . addIP ( b , b . entries [ i ] . IP ( ) )
return false
if ! newRecord . IP ( ) . Equal ( b . entries [ i ] . IP ( ) ) {
}
// Endpoint has changed, ensure that the new IP fits into table limits.
}
tab . removeIP ( b , b . entries [ i ] . IP ( ) )
// Move it to the front.
if ! tab . addIP ( b , newRecord . IP ( ) ) {
copy ( b . entries [ 1 : ] , b . entries [ : i ] )
// It doesn't, put the previous one back.
b . entries [ 0 ] = n
tab . addIP ( b , b . entries [ i ] . IP ( ) )
return tru e
return fals e
}
}
}
}
return false
b . entries [ i ] . Node = newRecord
return true
}
}
func ( tab * Table ) deleteInBucket ( b * bucket , n * node ) {
func ( tab * Table ) handleTrackRequest ( op trackRequestOp ) {
// Check if the node is actually in the bucket so the removed hook
var fails int
// isn't called multiple times for the same node.
if op . success {
if ! contains ( b . entries , n . ID ( ) ) {
// Reset failure counter because it counts _consecutive_ failures.
return
tab . db . UpdateFindFails ( op . node . ID ( ) , op . node . IP ( ) , 0 )
} else {
fails = tab . db . FindFails ( op . node . ID ( ) , op . node . IP ( ) )
fails ++
tab . db . UpdateFindFails ( op . node . ID ( ) , op . node . IP ( ) , fails )
}
}
b . entries = deleteNode ( b . entries , n )
tab . removeIP ( b , n . IP ( ) )
tab . mutex . Lock ( )
if tab . nodeRemovedHook != nil {
defer tab . mutex . Unlock ( )
tab . nodeRemovedHook ( b , n )
b := tab . bucket ( op . node . ID ( ) )
// Remove the node from the local table if it fails to return anything useful too
// many times, but only if there are enough other nodes in the bucket. This latter
// condition specifically exists to make bootstrapping in smaller test networks more
// reliable.
if fails >= maxFindnodeFailures && len ( b . entries ) >= bucketSize / 4 {
tab . deleteInBucket ( b , op . node . ID ( ) )
}
// Add found nodes.
for _ , n := range op . foundNodes {
tab . handleAddNode ( addNodeOp { n , false } )
}
}
}
}