p2p/discover: improve table addition code (#18974)
This change clears up confusion around the two ways in which nodes can be added to the table. When a neighbors packet is received as a reply to findnode, the nodes contained in the reply are added as 'seen' entries if sufficient space is available. When a ping is received and the endpoint verification has taken place, the remote node is added as a 'verified' entry or moved to the front of the bucket if present. This also updates the node's IP address and port if they have changed.
This commit is contained in:
		@@ -30,6 +30,7 @@ import (
 | 
			
		||||
	"github.com/ethereum/go-ethereum/crypto"
 | 
			
		||||
	"github.com/ethereum/go-ethereum/p2p/enode"
 | 
			
		||||
	"github.com/ethereum/go-ethereum/p2p/enr"
 | 
			
		||||
	"github.com/ethereum/go-ethereum/p2p/netutil"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestTable_pingReplace(t *testing.T) {
 | 
			
		||||
@@ -64,7 +65,7 @@ func testPingReplace(t *testing.T, newNodeIsResponding, lastInBucketIsResponding
 | 
			
		||||
	// its bucket if it is unresponsive. Revalidate again to ensure that
 | 
			
		||||
	transport.dead[last.ID()] = !lastInBucketIsResponding
 | 
			
		||||
	transport.dead[pingSender.ID()] = !newNodeIsResponding
 | 
			
		||||
	tab.add(pingSender)
 | 
			
		||||
	tab.addSeenNode(pingSender)
 | 
			
		||||
	tab.doRevalidate(make(chan struct{}, 1))
 | 
			
		||||
	tab.doRevalidate(make(chan struct{}, 1))
 | 
			
		||||
 | 
			
		||||
@@ -114,10 +115,14 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prop := func(nodes []*node, bumps []int) (ok bool) {
 | 
			
		||||
		tab, db := newTestTable(newPingRecorder())
 | 
			
		||||
		defer db.Close()
 | 
			
		||||
		defer tab.Close()
 | 
			
		||||
 | 
			
		||||
		b := &bucket{entries: make([]*node, len(nodes))}
 | 
			
		||||
		copy(b.entries, nodes)
 | 
			
		||||
		for i, pos := range bumps {
 | 
			
		||||
			b.bump(b.entries[pos])
 | 
			
		||||
			tab.bumpInBucket(b, b.entries[pos])
 | 
			
		||||
			if hasDuplicates(b.entries) {
 | 
			
		||||
				t.Logf("bucket has duplicates after %d/%d bumps:", i+1, len(bumps))
 | 
			
		||||
				for _, n := range b.entries {
 | 
			
		||||
@@ -126,6 +131,7 @@ func TestBucket_bumpNoDuplicates(t *testing.T) {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		checkIPLimitInvariant(t, tab)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if err := quick.Check(prop, cfg); err != nil {
 | 
			
		||||
@@ -142,11 +148,12 @@ func TestTable_IPLimit(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < tableIPLimit+1; i++ {
 | 
			
		||||
		n := nodeAtDistance(tab.self().ID(), i, net.IP{172, 0, 1, byte(i)})
 | 
			
		||||
		tab.add(n)
 | 
			
		||||
		tab.addSeenNode(n)
 | 
			
		||||
	}
 | 
			
		||||
	if tab.len() > tableIPLimit {
 | 
			
		||||
		t.Errorf("too many nodes in table")
 | 
			
		||||
	}
 | 
			
		||||
	checkIPLimitInvariant(t, tab)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This checks that the per-bucket IP limit is applied correctly.
 | 
			
		||||
@@ -159,11 +166,28 @@ func TestTable_BucketIPLimit(t *testing.T) {
 | 
			
		||||
	d := 3
 | 
			
		||||
	for i := 0; i < bucketIPLimit+1; i++ {
 | 
			
		||||
		n := nodeAtDistance(tab.self().ID(), d, net.IP{172, 0, 1, byte(i)})
 | 
			
		||||
		tab.add(n)
 | 
			
		||||
		tab.addSeenNode(n)
 | 
			
		||||
	}
 | 
			
		||||
	if tab.len() > bucketIPLimit {
 | 
			
		||||
		t.Errorf("too many nodes in table")
 | 
			
		||||
	}
 | 
			
		||||
	checkIPLimitInvariant(t, tab)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkIPLimitInvariant checks that ip limit sets contain an entry for every
 | 
			
		||||
// node in the table and no extra entries.
 | 
			
		||||
func checkIPLimitInvariant(t *testing.T, tab *Table) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
 | 
			
		||||
	tabset := netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit}
 | 
			
		||||
	for _, b := range tab.buckets {
 | 
			
		||||
		for _, n := range b.entries {
 | 
			
		||||
			tabset.Add(n.IP())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if tabset.String() != tab.ips.String() {
 | 
			
		||||
		t.Errorf("table IP set is incorrect:\nhave: %v\nwant: %v", tab.ips, tabset)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTable_closest(t *testing.T) {
 | 
			
		||||
@@ -281,6 +305,69 @@ func (*closeTest) Generate(rand *rand.Rand, size int) reflect.Value {
 | 
			
		||||
	return reflect.ValueOf(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTable_addVerifiedNode(t *testing.T) {
 | 
			
		||||
	tab, db := newTestTable(newPingRecorder())
 | 
			
		||||
	<-tab.initDone
 | 
			
		||||
	defer db.Close()
 | 
			
		||||
	defer tab.Close()
 | 
			
		||||
 | 
			
		||||
	// Insert two nodes.
 | 
			
		||||
	n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
 | 
			
		||||
	n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
 | 
			
		||||
	tab.addSeenNode(n1)
 | 
			
		||||
	tab.addSeenNode(n2)
 | 
			
		||||
 | 
			
		||||
	// Verify bucket content:
 | 
			
		||||
	bcontent := []*node{n1, n2}
 | 
			
		||||
	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
 | 
			
		||||
		t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add a changed version of n2.
 | 
			
		||||
	newrec := n2.Record()
 | 
			
		||||
	newrec.Set(enr.IP{99, 99, 99, 99})
 | 
			
		||||
	newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
 | 
			
		||||
	tab.addVerifiedNode(newn2)
 | 
			
		||||
 | 
			
		||||
	// Check that bucket is updated correctly.
 | 
			
		||||
	newBcontent := []*node{newn2, n1}
 | 
			
		||||
	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, newBcontent) {
 | 
			
		||||
		t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
 | 
			
		||||
	}
 | 
			
		||||
	checkIPLimitInvariant(t, tab)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTable_addSeenNode(t *testing.T) {
 | 
			
		||||
	tab, db := newTestTable(newPingRecorder())
 | 
			
		||||
	<-tab.initDone
 | 
			
		||||
	defer db.Close()
 | 
			
		||||
	defer tab.Close()
 | 
			
		||||
 | 
			
		||||
	// Insert two nodes.
 | 
			
		||||
	n1 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 1})
 | 
			
		||||
	n2 := nodeAtDistance(tab.self().ID(), 256, net.IP{88, 77, 66, 2})
 | 
			
		||||
	tab.addSeenNode(n1)
 | 
			
		||||
	tab.addSeenNode(n2)
 | 
			
		||||
 | 
			
		||||
	// Verify bucket content:
 | 
			
		||||
	bcontent := []*node{n1, n2}
 | 
			
		||||
	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
 | 
			
		||||
		t.Fatalf("wrong bucket content: %v", tab.bucket(n1.ID()).entries)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add a changed version of n2.
 | 
			
		||||
	newrec := n2.Record()
 | 
			
		||||
	newrec.Set(enr.IP{99, 99, 99, 99})
 | 
			
		||||
	newn2 := wrapNode(enode.SignNull(newrec, n2.ID()))
 | 
			
		||||
	tab.addSeenNode(newn2)
 | 
			
		||||
 | 
			
		||||
	// Check that bucket content is unchanged.
 | 
			
		||||
	if !reflect.DeepEqual(tab.bucket(n1.ID()).entries, bcontent) {
 | 
			
		||||
		t.Fatalf("wrong bucket content after update: %v", tab.bucket(n1.ID()).entries)
 | 
			
		||||
	}
 | 
			
		||||
	checkIPLimitInvariant(t, tab)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTable_Lookup(t *testing.T) {
 | 
			
		||||
	tab, db := newTestTable(lookupTestnet)
 | 
			
		||||
	defer db.Close()
 | 
			
		||||
@@ -535,7 +622,6 @@ func (tn *preminedTestnet) findnode(toid enode.ID, toaddr *net.UDPAddr, target e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (*preminedTestnet) close()                                        {}
 | 
			
		||||
func (*preminedTestnet) waitping(from enode.ID) error                  { return nil }
 | 
			
		||||
func (*preminedTestnet) ping(toid enode.ID, toaddr *net.UDPAddr) error { return nil }
 | 
			
		||||
 | 
			
		||||
// mine generates a testnet struct literal with nodes at
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user