Skip to content

Commit 24d44f3

Browse files
committed
Merge pull request #791 from fjl/discover-sha3-distance
p2p/discover: sha3-based node distance
2 parents 323216e + 2adcc31 commit 24d44f3

File tree

14 files changed

+651
-292
lines changed

14 files changed

+651
-292
lines changed

common/types.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package common
22

3-
import "math/big"
3+
import (
4+
"math/big"
5+
"math/rand"
6+
"reflect"
7+
)
48

59
const (
610
hashLength = 32
@@ -48,6 +52,15 @@ func (h *Hash) Set(other Hash) {
4852
}
4953
}
5054

55+
// Generate implements testing/quick.Generator.
56+
func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value {
57+
m := rand.Intn(len(h))
58+
for i := len(h) - 1; i > m; i-- {
59+
h[i] = byte(rand.Uint32())
60+
}
61+
return reflect.ValueOf(h)
62+
}
63+
5164
/////////// Address
5265
func BytesToAddress(b []byte) Address {
5366
var a Address

eth/backend.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,8 @@ func (s *Ethereum) NodeInfo() *NodeInfo {
277277
NodeUrl: node.String(),
278278
NodeID: node.ID.String(),
279279
IP: node.IP.String(),
280-
DiscPort: node.DiscPort,
281-
TCPPort: node.TCPPort,
280+
DiscPort: int(node.UDP),
281+
TCPPort: int(node.TCP),
282282
ListenAddr: s.net.ListenAddr,
283283
Td: s.ChainManager().Td().String(),
284284
}

p2p/discover/database.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"sync"
1111
"time"
1212

13+
"github.com/ethereum/go-ethereum/crypto"
1314
"github.com/ethereum/go-ethereum/logger"
1415
"github.com/ethereum/go-ethereum/logger/glog"
1516
"github.com/ethereum/go-ethereum/rlp"
@@ -167,6 +168,7 @@ func (db *nodeDB) node(id NodeID) *Node {
167168
glog.V(logger.Warn).Infof("failed to decode node RLP: %v", err)
168169
return nil
169170
}
171+
node.sha = crypto.Sha3Hash(node.ID[:])
170172
return node
171173
}
172174

p2p/discover/database_test.go

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net"
77
"os"
88
"path/filepath"
9+
"reflect"
910
"testing"
1011
"time"
1112
)
@@ -85,11 +86,12 @@ func TestNodeDBInt64(t *testing.T) {
8586
}
8687

8788
func TestNodeDBFetchStore(t *testing.T) {
88-
node := &Node{
89-
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
90-
IP: net.IP([]byte{192, 168, 0, 1}),
91-
TCPPort: 30303,
92-
}
89+
node := newNode(
90+
MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
91+
net.IP{192, 168, 0, 1},
92+
30303,
93+
30303,
94+
)
9395
inst := time.Now()
9496

9597
db, _ := newNodeDB("", Version)
@@ -124,34 +126,40 @@ func TestNodeDBFetchStore(t *testing.T) {
124126
}
125127
if stored := db.node(node.ID); stored == nil {
126128
t.Errorf("node: not found")
127-
} else if !bytes.Equal(stored.ID[:], node.ID[:]) || !stored.IP.Equal(node.IP) || stored.TCPPort != node.TCPPort {
129+
} else if !reflect.DeepEqual(stored, node) {
128130
t.Errorf("node: data mismatch: have %v, want %v", stored, node)
129131
}
130132
}
131133

132134
var nodeDBSeedQueryNodes = []struct {
133-
node Node
135+
node *Node
134136
pong time.Time
135137
}{
136138
{
137-
node: Node{
138-
ID: MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
139-
IP: []byte{127, 0, 0, 1},
140-
},
139+
node: newNode(
140+
MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
141+
net.IP{127, 0, 0, 1},
142+
30303,
143+
30303,
144+
),
141145
pong: time.Now().Add(-2 * time.Second),
142146
},
143147
{
144-
node: Node{
145-
ID: MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
146-
IP: []byte{127, 0, 0, 2},
147-
},
148+
node: newNode(
149+
MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
150+
net.IP{127, 0, 0, 2},
151+
30303,
152+
30303,
153+
),
148154
pong: time.Now().Add(-3 * time.Second),
149155
},
150156
{
151-
node: Node{
152-
ID: MustHexID("0x03d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
153-
IP: []byte{127, 0, 0, 3},
154-
},
157+
node: newNode(
158+
MustHexID("0x03d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
159+
net.IP{127, 0, 0, 3},
160+
30303,
161+
30303,
162+
),
155163
pong: time.Now().Add(-1 * time.Second),
156164
},
157165
}
@@ -162,7 +170,7 @@ func TestNodeDBSeedQuery(t *testing.T) {
162170

163171
// Insert a batch of nodes for querying
164172
for i, seed := range nodeDBSeedQueryNodes {
165-
if err := db.updateNode(&seed.node); err != nil {
173+
if err := db.updateNode(seed.node); err != nil {
166174
t.Fatalf("node %d: failed to insert: %v", i, err)
167175
}
168176
}
@@ -202,7 +210,7 @@ func TestNodeDBSeedQueryContinuation(t *testing.T) {
202210

203211
// Insert a batch of nodes for querying
204212
for i, seed := range nodeDBSeedQueryNodes {
205-
if err := db.updateNode(&seed.node); err != nil {
213+
if err := db.updateNode(seed.node); err != nil {
206214
t.Fatalf("node %d: failed to insert: %v", i, err)
207215
}
208216
}
@@ -266,22 +274,26 @@ func TestNodeDBPersistency(t *testing.T) {
266274
}
267275

268276
var nodeDBExpirationNodes = []struct {
269-
node Node
277+
node *Node
270278
pong time.Time
271279
exp bool
272280
}{
273281
{
274-
node: Node{
275-
ID: MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
276-
IP: []byte{127, 0, 0, 1},
277-
},
282+
node: newNode(
283+
MustHexID("0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
284+
net.IP{127, 0, 0, 1},
285+
30303,
286+
30303,
287+
),
278288
pong: time.Now().Add(-nodeDBNodeExpiration + time.Minute),
279289
exp: false,
280290
}, {
281-
node: Node{
282-
ID: MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
283-
IP: []byte{127, 0, 0, 2},
284-
},
291+
node: newNode(
292+
MustHexID("0x02d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
293+
net.IP{127, 0, 0, 2},
294+
30303,
295+
30303,
296+
),
285297
pong: time.Now().Add(-nodeDBNodeExpiration - time.Minute),
286298
exp: true,
287299
},
@@ -293,7 +305,7 @@ func TestNodeDBExpiration(t *testing.T) {
293305

294306
// Add all the test nodes and set their last pong time
295307
for i, seed := range nodeDBExpirationNodes {
296-
if err := db.updateNode(&seed.node); err != nil {
308+
if err := db.updateNode(seed.node); err != nil {
297309
t.Fatalf("node %d: failed to insert: %v", i, err)
298310
}
299311
if err := db.updateLastPong(seed.node.ID, seed.pong); err != nil {

p2p/discover/node.go

Lines changed: 48 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,62 @@ import (
66
"encoding/hex"
77
"errors"
88
"fmt"
9-
"io"
109
"math/big"
1110
"math/rand"
1211
"net"
1312
"net/url"
1413
"strconv"
1514
"strings"
1615

16+
"github.com/ethereum/go-ethereum/common"
1717
"github.com/ethereum/go-ethereum/crypto"
1818
"github.com/ethereum/go-ethereum/crypto/secp256k1"
19-
"github.com/ethereum/go-ethereum/rlp"
2019
)
2120

2221
const nodeIDBits = 512
2322

2423
// Node represents a host on the network.
2524
type Node struct {
26-
ID NodeID
27-
IP net.IP
25+
IP net.IP // len 4 for IPv4 or 16 for IPv6
26+
UDP, TCP uint16 // port numbers
27+
ID NodeID // the node's public key
2828

29-
DiscPort int // UDP listening port for discovery protocol
30-
TCPPort int // TCP listening port for RLPx
29+
// This is a cached copy of sha3(ID) which is used for node
30+
// distance calculations. This is part of Node in order to make it
31+
// possible to write tests that need a node at a certain distance.
32+
// In those tests, the content of sha will not actually correspond
33+
// with ID.
34+
sha common.Hash
3135
}
3236

33-
func newNode(id NodeID, addr *net.UDPAddr) *Node {
37+
func newNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node {
38+
if ipv4 := ip.To4(); ipv4 != nil {
39+
ip = ipv4
40+
}
3441
return &Node{
35-
ID: id,
36-
IP: addr.IP,
37-
DiscPort: addr.Port,
38-
TCPPort: addr.Port,
42+
IP: ip,
43+
UDP: udpPort,
44+
TCP: tcpPort,
45+
ID: id,
46+
sha: crypto.Sha3Hash(id[:]),
3947
}
4048
}
4149

42-
func (n *Node) isValid() bool {
43-
// TODO: don't accept localhost, LAN addresses from internet hosts
44-
return !n.IP.IsMulticast() && !n.IP.IsUnspecified() && n.TCPPort != 0 && n.DiscPort != 0
45-
}
46-
4750
func (n *Node) addr() *net.UDPAddr {
48-
return &net.UDPAddr{IP: n.IP, Port: n.DiscPort}
51+
return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)}
4952
}
5053

5154
// The string representation of a Node is a URL.
5255
// Please see ParseNode for a description of the format.
5356
func (n *Node) String() string {
54-
addr := net.TCPAddr{IP: n.IP, Port: n.TCPPort}
57+
addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)}
5558
u := url.URL{
5659
Scheme: "enode",
5760
User: url.User(fmt.Sprintf("%x", n.ID[:])),
5861
Host: addr.String(),
5962
}
60-
if n.DiscPort != n.TCPPort {
61-
u.RawQuery = "discport=" + strconv.Itoa(n.DiscPort)
63+
if n.UDP != n.TCP {
64+
u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP))
6265
}
6366
return u.String()
6467
}
@@ -80,36 +83,47 @@ func (n *Node) String() string {
8083
//
8184
// enode://<hex node id>@10.3.58.6:30303?discport=30301
8285
func ParseNode(rawurl string) (*Node, error) {
83-
var n Node
86+
var (
87+
id NodeID
88+
ip net.IP
89+
tcpPort, udpPort uint64
90+
)
8491
u, err := url.Parse(rawurl)
8592
if u.Scheme != "enode" {
8693
return nil, errors.New("invalid URL scheme, want \"enode\"")
8794
}
95+
// Parse the Node ID from the user portion.
8896
if u.User == nil {
8997
return nil, errors.New("does not contain node ID")
9098
}
91-
if n.ID, err = HexID(u.User.String()); err != nil {
99+
if id, err = HexID(u.User.String()); err != nil {
92100
return nil, fmt.Errorf("invalid node ID (%v)", err)
93101
}
94-
ip, port, err := net.SplitHostPort(u.Host)
102+
// Parse the IP address.
103+
host, port, err := net.SplitHostPort(u.Host)
95104
if err != nil {
96105
return nil, fmt.Errorf("invalid host: %v", err)
97106
}
98-
if n.IP = net.ParseIP(ip); n.IP == nil {
107+
if ip = net.ParseIP(host); ip == nil {
99108
return nil, errors.New("invalid IP address")
100109
}
101-
if n.TCPPort, err = strconv.Atoi(port); err != nil {
110+
// Ensure the IP is 4 bytes long for IPv4 addresses.
111+
if ipv4 := ip.To4(); ipv4 != nil {
112+
ip = ipv4
113+
}
114+
// Parse the port numbers.
115+
if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
102116
return nil, errors.New("invalid port")
103117
}
118+
udpPort = tcpPort
104119
qv := u.Query()
105-
if qv.Get("discport") == "" {
106-
n.DiscPort = n.TCPPort
107-
} else {
108-
if n.DiscPort, err = strconv.Atoi(qv.Get("discport")); err != nil {
120+
if qv.Get("discport") != "" {
121+
udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
122+
if err != nil {
109123
return nil, errors.New("invalid discport in query")
110124
}
111125
}
112-
return &n, nil
126+
return newNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil
113127
}
114128

115129
// MustParseNode parses a node URL. It panics if the URL is not valid.
@@ -121,22 +135,6 @@ func MustParseNode(rawurl string) *Node {
121135
return n
122136
}
123137

124-
func (n Node) EncodeRLP(w io.Writer) error {
125-
return rlp.Encode(w, rpcNode{IP: n.IP.String(), Port: uint16(n.TCPPort), ID: n.ID})
126-
}
127-
func (n *Node) DecodeRLP(s *rlp.Stream) (err error) {
128-
var ext rpcNode
129-
if err = s.Decode(&ext); err == nil {
130-
n.TCPPort = int(ext.Port)
131-
n.DiscPort = int(ext.Port)
132-
n.ID = ext.ID
133-
if n.IP = net.ParseIP(ext.IP); n.IP == nil {
134-
return errors.New("invalid IP string")
135-
}
136-
}
137-
return err
138-
}
139-
140138
// NodeID is a unique identifier for each node.
141139
// The node identifier is a marshaled elliptic curve public key.
142140
type NodeID [nodeIDBits / 8]byte
@@ -221,7 +219,7 @@ func recoverNodeID(hash, sig []byte) (id NodeID, err error) {
221219
// distcmp compares the distances a->target and b->target.
222220
// Returns -1 if a is closer to target, 1 if b is closer to target
223221
// and 0 if they are equal.
224-
func distcmp(target, a, b NodeID) int {
222+
func distcmp(target, a, b common.Hash) int {
225223
for i := range target {
226224
da := a[i] ^ target[i]
227225
db := b[i] ^ target[i]
@@ -271,7 +269,7 @@ var lzcount = [256]int{
271269
}
272270

273271
// logdist returns the logarithmic distance between a and b, log2(a ^ b).
274-
func logdist(a, b NodeID) int {
272+
func logdist(a, b common.Hash) int {
275273
lz := 0
276274
for i := range a {
277275
x := a[i] ^ b[i]
@@ -285,8 +283,8 @@ func logdist(a, b NodeID) int {
285283
return len(a)*8 - lz
286284
}
287285

288-
// randomID returns a random NodeID such that logdist(a, b) == n
289-
func randomID(a NodeID, n int) (b NodeID) {
286+
// hashAtDistance returns a random hash such that logdist(a, b) == n
287+
func hashAtDistance(a common.Hash, n int) (b common.Hash) {
290288
if n == 0 {
291289
return a
292290
}

0 commit comments

Comments
 (0)