Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
e3ac56d5
Commit
e3ac56d5
authored
Sep 30, 2015
by
Jeffrey Wilcke
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1859 from fjl/fix-discover-refresh-race
p2p/discover: fix race involving the seed node iterator
parents
46ad5a5f
32dda976
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
199 additions
and
183 deletions
+199
-183
database.go
p2p/discover/database.go
+56
-40
database_test.go
p2p/discover/database_test.go
+37
-66
table.go
p2p/discover/table.go
+106
-62
table_test.go
p2p/discover/table_test.go
+0
-3
udp.go
p2p/discover/udp.go
+0
-11
udp_test.go
p2p/discover/udp_test.go
+0
-1
No files found.
p2p/discover/database.go
View file @
e3ac56d5
...
@@ -21,6 +21,7 @@ package discover
...
@@ -21,6 +21,7 @@ package discover
import
(
import
(
"bytes"
"bytes"
"crypto/rand"
"encoding/binary"
"encoding/binary"
"os"
"os"
"sync"
"sync"
...
@@ -46,11 +47,8 @@ var (
...
@@ -46,11 +47,8 @@ var (
// nodeDB stores all nodes we know about.
// nodeDB stores all nodes we know about.
type
nodeDB
struct
{
type
nodeDB
struct
{
lvl
*
leveldb
.
DB
// Interface to the database itself
lvl
*
leveldb
.
DB
// Interface to the database itself
seeder
iterator
.
Iterator
// Iterator for fetching possible seed nodes
self
NodeID
// Own node id to prevent adding it into the database
self
NodeID
// Own node id to prevent adding it into the database
runner
sync
.
Once
// Ensures we can start at most one expirer
runner
sync
.
Once
// Ensures we can start at most one expirer
quit
chan
struct
{}
// Channel to signal the expiring thread to stop
quit
chan
struct
{}
// Channel to signal the expiring thread to stop
}
}
...
@@ -302,52 +300,70 @@ func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
...
@@ -302,52 +300,70 @@ func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
return
db
.
storeInt64
(
makeKey
(
id
,
nodeDBDiscoverFindFails
),
int64
(
fails
))
return
db
.
storeInt64
(
makeKey
(
id
,
nodeDBDiscoverFindFails
),
int64
(
fails
))
}
}
// querySeeds retrieves a batch of nodes to be used as potential seed servers
// querySeeds retrieves random nodes to be used as potential seed nodes
// during bootstrapping the node into the network.
// for bootstrapping.
//
func
(
db
*
nodeDB
)
querySeeds
(
n
int
,
maxAge
time
.
Duration
)
[]
*
Node
{
// Ideal seeds are the most recently seen nodes (highest probability to be still
var
(
// alive), but yet untried. However, since leveldb only supports dumb iteration
now
=
time
.
Now
()
// we will instead start pulling in potential seeds that haven't been yet pinged
nodes
=
make
([]
*
Node
,
0
,
n
)
// since the start of the boot procedure.
it
=
db
.
lvl
.
NewIterator
(
nil
,
nil
)
//
id
NodeID
// If the database runs out of potential seeds, we restart the startup counter
)
// and start iterating over the peers again.
defer
it
.
Release
()
func
(
db
*
nodeDB
)
querySeeds
(
n
int
)
[]
*
Node
{
// Create a new seed iterator if none exists
seek
:
if
db
.
seeder
==
nil
{
for
seeks
:=
0
;
len
(
nodes
)
<
n
&&
seeks
<
n
*
5
;
seeks
++
{
db
.
seeder
=
db
.
lvl
.
NewIterator
(
nil
,
nil
)
// Seek to a random entry. The first byte is incremented by a
// random amount each time in order to increase the likelihood
// of hitting all existing nodes in very small databases.
ctr
:=
id
[
0
]
rand
.
Read
(
id
[
:
])
id
[
0
]
=
ctr
+
id
[
0
]
%
16
it
.
Seek
(
makeKey
(
id
,
nodeDBDiscoverRoot
))
n
:=
nextNode
(
it
)
if
n
==
nil
{
id
[
0
]
=
0
continue
seek
// iterator exhausted
}
if
n
.
ID
==
db
.
self
{
continue
seek
}
if
now
.
Sub
(
db
.
lastPong
(
n
.
ID
))
>
maxAge
{
continue
seek
}
for
i
:=
range
nodes
{
if
nodes
[
i
]
.
ID
==
n
.
ID
{
continue
seek
// duplicate
}
}
nodes
=
append
(
nodes
,
n
)
}
}
// Iterate over the nodes and find suitable seeds
return
nodes
nodes
:=
make
([]
*
Node
,
0
,
n
)
}
for
len
(
nodes
)
<
n
&&
db
.
seeder
.
Next
()
{
// Iterate until a discovery node is found
// reads the next node record from the iterator, skipping over other
id
,
field
:=
splitKey
(
db
.
seeder
.
Key
())
// database entries.
func
nextNode
(
it
iterator
.
Iterator
)
*
Node
{
for
end
:=
false
;
!
end
;
end
=
!
it
.
Next
()
{
id
,
field
:=
splitKey
(
it
.
Key
())
if
field
!=
nodeDBDiscoverRoot
{
if
field
!=
nodeDBDiscoverRoot
{
continue
continue
}
}
// Dump it if its a self reference
var
n
Node
if
bytes
.
Compare
(
id
[
:
],
db
.
self
[
:
])
==
0
{
if
err
:=
rlp
.
DecodeBytes
(
it
.
Value
(),
&
n
);
err
!=
nil
{
db
.
deleteNode
(
id
)
if
glog
.
V
(
logger
.
Warn
)
{
glog
.
Errorf
(
"invalid node %x: %v"
,
id
,
err
)
}
continue
continue
}
}
// Load it as a potential seed
return
&
n
if
node
:=
db
.
node
(
id
);
node
!=
nil
{
nodes
=
append
(
nodes
,
node
)
}
}
// Release the iterator if we reached the end
if
len
(
nodes
)
==
0
{
db
.
seeder
.
Release
()
db
.
seeder
=
nil
}
}
return
n
odes
return
n
il
}
}
// close flushes and closes the database files.
// close flushes and closes the database files.
func
(
db
*
nodeDB
)
close
()
{
func
(
db
*
nodeDB
)
close
()
{
if
db
.
seeder
!=
nil
{
db
.
seeder
.
Release
()
}
close
(
db
.
quit
)
close
(
db
.
quit
)
db
.
lvl
.
Close
()
db
.
lvl
.
Close
()
}
}
p2p/discover/database_test.go
View file @
e3ac56d5
...
@@ -162,9 +162,33 @@ var nodeDBSeedQueryNodes = []struct {
...
@@ -162,9 +162,33 @@ var nodeDBSeedQueryNodes = []struct {
node
*
Node
node
*
Node
pong
time
.
Time
pong
time
.
Time
}{
}{
// This one should not be in the result set because its last
// pong time is too far in the past.
{
{
node
:
newNode
(
node
:
newNode
(
MustHexID
(
"0x01d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
MustHexID
(
"0x84d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
net
.
IP
{
127
,
0
,
0
,
3
},
30303
,
30303
,
),
pong
:
time
.
Now
()
.
Add
(
-
3
*
time
.
Hour
),
},
// This one shouldn't be in in the result set because its
// nodeID is the local node's ID.
{
node
:
newNode
(
MustHexID
(
"0x57d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
net
.
IP
{
127
,
0
,
0
,
3
},
30303
,
30303
,
),
pong
:
time
.
Now
()
.
Add
(
-
4
*
time
.
Second
),
},
// These should be in the result set.
{
node
:
newNode
(
MustHexID
(
"0x22d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
net
.
IP
{
127
,
0
,
0
,
1
},
net
.
IP
{
127
,
0
,
0
,
1
},
30303
,
30303
,
30303
,
30303
,
...
@@ -173,7 +197,7 @@ var nodeDBSeedQueryNodes = []struct {
...
@@ -173,7 +197,7 @@ var nodeDBSeedQueryNodes = []struct {
},
},
{
{
node
:
newNode
(
node
:
newNode
(
MustHexID
(
"0x
02
d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
MustHexID
(
"0x
44
d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
net
.
IP
{
127
,
0
,
0
,
2
},
net
.
IP
{
127
,
0
,
0
,
2
},
30303
,
30303
,
30303
,
30303
,
...
@@ -182,7 +206,7 @@ var nodeDBSeedQueryNodes = []struct {
...
@@ -182,7 +206,7 @@ var nodeDBSeedQueryNodes = []struct {
},
},
{
{
node
:
newNode
(
node
:
newNode
(
MustHexID
(
"0x
03
d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
MustHexID
(
"0x
e2
d9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"
),
net
.
IP
{
127
,
0
,
0
,
3
},
net
.
IP
{
127
,
0
,
0
,
3
},
30303
,
30303
,
30303
,
30303
,
...
@@ -192,7 +216,7 @@ var nodeDBSeedQueryNodes = []struct {
...
@@ -192,7 +216,7 @@ var nodeDBSeedQueryNodes = []struct {
}
}
func
TestNodeDBSeedQuery
(
t
*
testing
.
T
)
{
func
TestNodeDBSeedQuery
(
t
*
testing
.
T
)
{
db
,
_
:=
newNodeDB
(
""
,
Version
,
NodeID
{}
)
db
,
_
:=
newNodeDB
(
""
,
Version
,
nodeDBSeedQueryNodes
[
1
]
.
node
.
ID
)
defer
db
.
close
()
defer
db
.
close
()
// Insert a batch of nodes for querying
// Insert a batch of nodes for querying
...
@@ -200,20 +224,24 @@ func TestNodeDBSeedQuery(t *testing.T) {
...
@@ -200,20 +224,24 @@ func TestNodeDBSeedQuery(t *testing.T) {
if
err
:=
db
.
updateNode
(
seed
.
node
);
err
!=
nil
{
if
err
:=
db
.
updateNode
(
seed
.
node
);
err
!=
nil
{
t
.
Fatalf
(
"node %d: failed to insert: %v"
,
i
,
err
)
t
.
Fatalf
(
"node %d: failed to insert: %v"
,
i
,
err
)
}
}
if
err
:=
db
.
updateLastPong
(
seed
.
node
.
ID
,
seed
.
pong
);
err
!=
nil
{
t
.
Fatalf
(
"node %d: failed to insert lastPong: %v"
,
i
,
err
)
}
}
}
// Retrieve the entire batch and check for duplicates
// Retrieve the entire batch and check for duplicates
seeds
:=
db
.
querySeeds
(
2
*
len
(
nodeDBSeedQueryNodes
))
seeds
:=
db
.
querySeeds
(
len
(
nodeDBSeedQueryNodes
)
*
2
,
time
.
Hour
)
if
len
(
seeds
)
!=
len
(
nodeDBSeedQueryNodes
)
{
t
.
Errorf
(
"seed count mismatch: have %v, want %v"
,
len
(
seeds
),
len
(
nodeDBSeedQueryNodes
))
}
have
:=
make
(
map
[
NodeID
]
struct
{})
have
:=
make
(
map
[
NodeID
]
struct
{})
for
_
,
seed
:=
range
seeds
{
for
_
,
seed
:=
range
seeds
{
have
[
seed
.
ID
]
=
struct
{}{}
have
[
seed
.
ID
]
=
struct
{}{}
}
}
want
:=
make
(
map
[
NodeID
]
struct
{})
want
:=
make
(
map
[
NodeID
]
struct
{})
for
_
,
seed
:=
range
nodeDBSeedQueryNodes
{
for
_
,
seed
:=
range
nodeDBSeedQueryNodes
[
2
:
]
{
want
[
seed
.
node
.
ID
]
=
struct
{}{}
want
[
seed
.
node
.
ID
]
=
struct
{}{}
}
}
if
len
(
seeds
)
!=
len
(
want
)
{
t
.
Errorf
(
"seed count mismatch: have %v, want %v"
,
len
(
seeds
),
len
(
want
))
}
for
id
,
_
:=
range
have
{
for
id
,
_
:=
range
have
{
if
_
,
ok
:=
want
[
id
];
!
ok
{
if
_
,
ok
:=
want
[
id
];
!
ok
{
t
.
Errorf
(
"extra seed: %v"
,
id
)
t
.
Errorf
(
"extra seed: %v"
,
id
)
...
@@ -224,63 +252,6 @@ func TestNodeDBSeedQuery(t *testing.T) {
...
@@ -224,63 +252,6 @@ func TestNodeDBSeedQuery(t *testing.T) {
t
.
Errorf
(
"missing seed: %v"
,
id
)
t
.
Errorf
(
"missing seed: %v"
,
id
)
}
}
}
}
// Make sure the next batch is empty (seed EOF)
seeds
=
db
.
querySeeds
(
2
*
len
(
nodeDBSeedQueryNodes
))
if
len
(
seeds
)
!=
0
{
t
.
Errorf
(
"seed count mismatch: have %v, want %v"
,
len
(
seeds
),
0
)
}
}
func
TestNodeDBSeedQueryContinuation
(
t
*
testing
.
T
)
{
db
,
_
:=
newNodeDB
(
""
,
Version
,
NodeID
{})
defer
db
.
close
()
// Insert a batch of nodes for querying
for
i
,
seed
:=
range
nodeDBSeedQueryNodes
{
if
err
:=
db
.
updateNode
(
seed
.
node
);
err
!=
nil
{
t
.
Fatalf
(
"node %d: failed to insert: %v"
,
i
,
err
)
}
}
// Iteratively retrieve the batch, checking for an empty batch on reset
for
i
:=
0
;
i
<
len
(
nodeDBSeedQueryNodes
);
i
++
{
if
seeds
:=
db
.
querySeeds
(
1
);
len
(
seeds
)
!=
1
{
t
.
Errorf
(
"1st iteration %d: seed count mismatch: have %v, want %v"
,
i
,
len
(
seeds
),
1
)
}
}
if
seeds
:=
db
.
querySeeds
(
1
);
len
(
seeds
)
!=
0
{
t
.
Errorf
(
"reset: seed count mismatch: have %v, want %v"
,
len
(
seeds
),
0
)
}
for
i
:=
0
;
i
<
len
(
nodeDBSeedQueryNodes
);
i
++
{
if
seeds
:=
db
.
querySeeds
(
1
);
len
(
seeds
)
!=
1
{
t
.
Errorf
(
"2nd iteration %d: seed count mismatch: have %v, want %v"
,
i
,
len
(
seeds
),
1
)
}
}
}
func
TestNodeDBSelfSeedQuery
(
t
*
testing
.
T
)
{
// Assign a node as self to verify evacuation
self
:=
nodeDBSeedQueryNodes
[
0
]
.
node
.
ID
db
,
_
:=
newNodeDB
(
""
,
Version
,
self
)
defer
db
.
close
()
// Insert a batch of nodes for querying
for
i
,
seed
:=
range
nodeDBSeedQueryNodes
{
if
err
:=
db
.
updateNode
(
seed
.
node
);
err
!=
nil
{
t
.
Fatalf
(
"node %d: failed to insert: %v"
,
i
,
err
)
}
}
// Retrieve the entire batch and check that self was evacuated
seeds
:=
db
.
querySeeds
(
2
*
len
(
nodeDBSeedQueryNodes
))
if
len
(
seeds
)
!=
len
(
nodeDBSeedQueryNodes
)
-
1
{
t
.
Errorf
(
"seed count mismatch: have %v, want %v"
,
len
(
seeds
),
len
(
nodeDBSeedQueryNodes
)
-
1
)
}
have
:=
make
(
map
[
NodeID
]
struct
{})
for
_
,
seed
:=
range
seeds
{
have
[
seed
.
ID
]
=
struct
{}{}
}
if
_
,
ok
:=
have
[
self
];
ok
{
t
.
Errorf
(
"self not evacuated"
)
}
}
}
func
TestNodeDBPersistency
(
t
*
testing
.
T
)
{
func
TestNodeDBPersistency
(
t
*
testing
.
T
)
{
...
...
p2p/discover/table.go
View file @
e3ac56d5
...
@@ -44,6 +44,10 @@ const (
...
@@ -44,6 +44,10 @@ const (
maxBondingPingPongs
=
16
maxBondingPingPongs
=
16
maxFindnodeFailures
=
5
maxFindnodeFailures
=
5
autoRefreshInterval
=
1
*
time
.
Hour
seedCount
=
30
seedMaxAge
=
5
*
24
*
time
.
Hour
)
)
type
Table
struct
{
type
Table
struct
{
...
@@ -52,6 +56,10 @@ type Table struct {
...
@@ -52,6 +56,10 @@ type Table struct {
nursery
[]
*
Node
// bootstrap nodes
nursery
[]
*
Node
// bootstrap nodes
db
*
nodeDB
// database of known nodes
db
*
nodeDB
// database of known nodes
refreshReq
chan
struct
{}
closeReq
chan
struct
{}
closed
chan
struct
{}
bondmu
sync
.
Mutex
bondmu
sync
.
Mutex
bonding
map
[
NodeID
]
*
bondproc
bonding
map
[
NodeID
]
*
bondproc
bondslots
chan
struct
{}
// limits total number of active bonding processes
bondslots
chan
struct
{}
// limits total number of active bonding processes
...
@@ -80,10 +88,7 @@ type transport interface {
...
@@ -80,10 +88,7 @@ type transport interface {
// bucket contains nodes, ordered by their last activity. the entry
// bucket contains nodes, ordered by their last activity. the entry
// that was most recently active is the first element in entries.
// that was most recently active is the first element in entries.
type
bucket
struct
{
type
bucket
struct
{
entries
[]
*
Node
}
lastLookup
time
.
Time
entries
[]
*
Node
}
func
newTable
(
t
transport
,
ourID
NodeID
,
ourAddr
*
net
.
UDPAddr
,
nodeDBPath
string
)
*
Table
{
func
newTable
(
t
transport
,
ourID
NodeID
,
ourAddr
*
net
.
UDPAddr
,
nodeDBPath
string
)
*
Table
{
// If no node database was given, use an in-memory one
// If no node database was given, use an in-memory one
...
@@ -93,11 +98,14 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
...
@@ -93,11 +98,14 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
db
,
_
=
newNodeDB
(
""
,
Version
,
ourID
)
db
,
_
=
newNodeDB
(
""
,
Version
,
ourID
)
}
}
tab
:=
&
Table
{
tab
:=
&
Table
{
net
:
t
,
net
:
t
,
db
:
db
,
db
:
db
,
self
:
newNode
(
ourID
,
ourAddr
.
IP
,
uint16
(
ourAddr
.
Port
),
uint16
(
ourAddr
.
Port
)),
self
:
newNode
(
ourID
,
ourAddr
.
IP
,
uint16
(
ourAddr
.
Port
),
uint16
(
ourAddr
.
Port
)),
bonding
:
make
(
map
[
NodeID
]
*
bondproc
),
bonding
:
make
(
map
[
NodeID
]
*
bondproc
),
bondslots
:
make
(
chan
struct
{},
maxBondingPingPongs
),
bondslots
:
make
(
chan
struct
{},
maxBondingPingPongs
),
refreshReq
:
make
(
chan
struct
{}),
closeReq
:
make
(
chan
struct
{}),
closed
:
make
(
chan
struct
{}),
}
}
for
i
:=
0
;
i
<
cap
(
tab
.
bondslots
);
i
++
{
for
i
:=
0
;
i
<
cap
(
tab
.
bondslots
);
i
++
{
tab
.
bondslots
<-
struct
{}{}
tab
.
bondslots
<-
struct
{}{}
...
@@ -105,6 +113,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
...
@@ -105,6 +113,7 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr, nodeDBPath string
for
i
:=
range
tab
.
buckets
{
for
i
:=
range
tab
.
buckets
{
tab
.
buckets
[
i
]
=
new
(
bucket
)
tab
.
buckets
[
i
]
=
new
(
bucket
)
}
}
go
tab
.
refreshLoop
()
return
tab
return
tab
}
}
...
@@ -163,10 +172,12 @@ func randUint(max uint32) uint32 {
...
@@ -163,10 +172,12 @@ func randUint(max uint32) uint32 {
// Close terminates the network listener and flushes the node database.
// Close terminates the network listener and flushes the node database.
func
(
tab
*
Table
)
Close
()
{
func
(
tab
*
Table
)
Close
()
{
if
tab
.
net
!=
nil
{
select
{
tab
.
net
.
close
()
case
<-
tab
.
closed
:
// already closed.
case
tab
.
closeReq
<-
struct
{}{}
:
<-
tab
.
closed
// wait for refreshLoop to end.
}
}
tab
.
db
.
close
()
}
}
// Bootstrap sets the bootstrap nodes. These nodes are used to connect
// Bootstrap sets the bootstrap nodes. These nodes are used to connect
...
@@ -183,7 +194,7 @@ func (tab *Table) Bootstrap(nodes []*Node) {
...
@@ -183,7 +194,7 @@ func (tab *Table) Bootstrap(nodes []*Node) {
tab
.
nursery
=
append
(
tab
.
nursery
,
&
cpy
)
tab
.
nursery
=
append
(
tab
.
nursery
,
&
cpy
)
}
}
tab
.
mutex
.
Unlock
()
tab
.
mutex
.
Unlock
()
tab
.
refresh
()
tab
.
re
questRe
fresh
()
}
}
// Lookup performs a network search for nodes close
// Lookup performs a network search for nodes close
...
@@ -204,15 +215,13 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
...
@@ -204,15 +215,13 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
asked
[
tab
.
self
.
ID
]
=
true
asked
[
tab
.
self
.
ID
]
=
true
tab
.
mutex
.
Lock
()
tab
.
mutex
.
Lock
()
// update last lookup stamp (for refresh logic)
tab
.
buckets
[
logdist
(
tab
.
self
.
sha
,
target
)]
.
lastLookup
=
time
.
Now
()
// generate initial result set
// generate initial result set
result
:=
tab
.
closest
(
target
,
bucketSize
)
result
:=
tab
.
closest
(
target
,
bucketSize
)
tab
.
mutex
.
Unlock
()
tab
.
mutex
.
Unlock
()
// If the result set is empty, all nodes were dropped, refresh
// If the result set is empty, all nodes were dropped, refresh
.
if
len
(
result
.
entries
)
==
0
{
if
len
(
result
.
entries
)
==
0
{
tab
.
refresh
()
tab
.
re
questRe
fresh
()
return
nil
return
nil
}
}
...
@@ -257,56 +266,86 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
...
@@ -257,56 +266,86 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
return
result
.
entries
return
result
.
entries
}
}
// refresh performs a lookup for a random target to keep buckets full, or seeds
func
(
tab
*
Table
)
requestRefresh
()
{
// the table if it is empty (initial bootstrap or discarded faulty peers).
select
{
func
(
tab
*
Table
)
refresh
()
{
case
tab
.
refreshReq
<-
struct
{}{}
:
seed
:=
true
case
<-
tab
.
closed
:
}
}
// If the discovery table is empty, seed with previously known nodes
func
(
tab
*
Table
)
refreshLoop
()
{
tab
.
mutex
.
Lock
()
defer
func
()
{
for
_
,
bucket
:=
range
tab
.
buckets
{
tab
.
db
.
close
()
if
len
(
bucket
.
entries
)
>
0
{
if
tab
.
net
!=
nil
{
seed
=
false
tab
.
net
.
close
()
break
}
}
}
close
(
tab
.
closed
)
tab
.
mutex
.
Unlock
()
}
()
// If the table is not empty, try to refresh using the live entries
timer
:=
time
.
NewTicker
(
autoRefreshInterval
)
if
!
seed
{
var
done
chan
struct
{}
// The Kademlia paper specifies that the bucket refresh should
for
{
// perform a refresh in the least recently used bucket. We cannot
select
{
// adhere to this because the findnode target is a 512bit value
case
<-
timer
.
C
:
// (not hash-sized) and it is not easily possible to generate a
if
done
==
nil
{
// sha3 preimage that falls into a chosen bucket.
done
=
make
(
chan
struct
{})
//
go
tab
.
doRefresh
(
done
)
// We perform a lookup with a random target instead.
}
var
target
NodeID
case
<-
tab
.
refreshReq
:
rand
.
Read
(
target
[
:
])
if
done
==
nil
{
done
=
make
(
chan
struct
{})
result
:=
tab
.
Lookup
(
target
)
go
tab
.
doRefresh
(
done
)
if
len
(
result
)
==
0
{
}
// Lookup failed, seed after all
case
<-
done
:
seed
=
true
done
=
nil
case
<-
tab
.
closeReq
:
if
done
!=
nil
{
<-
done
}
return
}
}
}
}
}
if
seed
{
// doRefresh performs a lookup for a random target to keep buckets
// Pick a batch of previously know seeds to lookup with
// full. seed nodes are inserted if the table is empty (initial
seeds
:=
tab
.
db
.
querySeeds
(
10
)
// bootstrap or discarded faulty peers).
for
_
,
seed
:=
range
seeds
{
func
(
tab
*
Table
)
doRefresh
(
done
chan
struct
{})
{
glog
.
V
(
logger
.
Debug
)
.
Infoln
(
"Seeding network with"
,
seed
)
defer
close
(
done
)
}
nodes
:=
append
(
tab
.
nursery
,
seeds
...
)
// The Kademlia paper specifies that the bucket refresh should
// perform a lookup in the least recently used bucket. We cannot
// adhere to this because the findnode target is a 512bit value
// (not hash-sized) and it is not easily possible to generate a
// sha3 preimage that falls into a chosen bucket.
// We perform a lookup with a random target instead.
var
target
NodeID
rand
.
Read
(
target
[
:
])
result
:=
tab
.
Lookup
(
target
)
if
len
(
result
)
>
0
{
return
}
// Bond with all the seed nodes (will pingpong only if failed recently)
// The table is empty. Load nodes from the database and insert
bonded
:=
tab
.
bondall
(
nodes
)
// them. This should yield a few previously seen nodes that are
if
len
(
bonded
)
>
0
{
// (hopefully) still alive.
tab
.
Lookup
(
tab
.
self
.
ID
)
seeds
:=
tab
.
db
.
querySeeds
(
seedCount
,
seedMaxAge
)
seeds
=
tab
.
bondall
(
append
(
seeds
,
tab
.
nursery
...
))
if
glog
.
V
(
logger
.
Debug
)
{
if
len
(
seeds
)
==
0
{
glog
.
Infof
(
"no seed nodes found"
)
}
for
_
,
n
:=
range
seeds
{
age
:=
time
.
Since
(
tab
.
db
.
lastPong
(
n
.
ID
))
glog
.
Infof
(
"seed node (age %v): %v"
,
age
,
n
)
}
}
// TODO: the Kademlia paper says that we're supposed to perform
// random lookups in all buckets further away than our closest neighbor.
}
}
tab
.
mutex
.
Lock
()
tab
.
stuff
(
seeds
)
tab
.
mutex
.
Unlock
()
// Finally, do a self lookup to fill up the buckets.
tab
.
Lookup
(
tab
.
self
.
ID
)
}
}
// closest returns the n nodes in the table that are closest to the
// closest returns the n nodes in the table that are closest to the
...
@@ -373,8 +412,9 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16
...
@@ -373,8 +412,9 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16
}
}
// If the node is unknown (non-bonded) or failed (remotely unknown), bond from scratch
// If the node is unknown (non-bonded) or failed (remotely unknown), bond from scratch
var
result
error
var
result
error
if
node
==
nil
||
fails
>
0
{
age
:=
time
.
Since
(
tab
.
db
.
lastPong
(
id
))
glog
.
V
(
logger
.
Detail
)
.
Infof
(
"Bonding %x: known=%v, fails=%v"
,
id
[
:
8
],
node
!=
nil
,
fails
)
if
node
==
nil
||
fails
>
0
||
age
>
nodeDBNodeExpiration
{
glog
.
V
(
logger
.
Detail
)
.
Infof
(
"Bonding %x: known=%t, fails=%d age=%v"
,
id
[
:
8
],
node
!=
nil
,
fails
,
age
)
tab
.
bondmu
.
Lock
()
tab
.
bondmu
.
Lock
()
w
:=
tab
.
bonding
[
id
]
w
:=
tab
.
bonding
[
id
]
...
@@ -435,13 +475,17 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd
...
@@ -435,13 +475,17 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd
// ping a remote endpoint and wait for a reply, also updating the node
// ping a remote endpoint and wait for a reply, also updating the node
// database accordingly.
// database accordingly.
func
(
tab
*
Table
)
ping
(
id
NodeID
,
addr
*
net
.
UDPAddr
)
error
{
func
(
tab
*
Table
)
ping
(
id
NodeID
,
addr
*
net
.
UDPAddr
)
error
{
// Update the last ping and send the message
tab
.
db
.
updateLastPing
(
id
,
time
.
Now
())
tab
.
db
.
updateLastPing
(
id
,
time
.
Now
())
if
err
:=
tab
.
net
.
ping
(
id
,
addr
);
err
!=
nil
{
if
err
:=
tab
.
net
.
ping
(
id
,
addr
);
err
!=
nil
{
return
err
return
err
}
}
// Pong received, update the database and return
tab
.
db
.
updateLastPong
(
id
,
time
.
Now
())
tab
.
db
.
updateLastPong
(
id
,
time
.
Now
())
// Start the background expiration goroutine after the first
// successful communication. Subsequent calls have no effect if it
// is already running. We do this here instead of somewhere else
// so that the search for seed nodes also considers older nodes
// that would otherwise be removed by the expiration.
tab
.
db
.
ensureExpirer
()
tab
.
db
.
ensureExpirer
()
return
nil
return
nil
}
}
...
...
p2p/discover/table_test.go
View file @
e3ac56d5
...
@@ -514,9 +514,6 @@ func (tn *preminedTestnet) findnode(toid NodeID, toaddr *net.UDPAddr, target Nod
...
@@ -514,9 +514,6 @@ func (tn *preminedTestnet) findnode(toid NodeID, toaddr *net.UDPAddr, target Nod
if
toaddr
.
Port
==
0
{
if
toaddr
.
Port
==
0
{
panic
(
"query to node at distance 0"
)
panic
(
"query to node at distance 0"
)
}
}
if
target
!=
tn
.
target
{
panic
(
"findnode with wrong target"
)
}
next
:=
uint16
(
toaddr
.
Port
)
-
1
next
:=
uint16
(
toaddr
.
Port
)
-
1
var
result
[]
*
Node
var
result
[]
*
Node
for
i
,
id
:=
range
tn
.
dists
[
toaddr
.
Port
]
{
for
i
,
id
:=
range
tn
.
dists
[
toaddr
.
Port
]
{
...
...
p2p/discover/udp.go
View file @
e3ac56d5
...
@@ -39,7 +39,6 @@ var (
...
@@ -39,7 +39,6 @@ var (
errPacketTooSmall
=
errors
.
New
(
"too small"
)
errPacketTooSmall
=
errors
.
New
(
"too small"
)
errBadHash
=
errors
.
New
(
"bad hash"
)
errBadHash
=
errors
.
New
(
"bad hash"
)
errExpired
=
errors
.
New
(
"expired"
)
errExpired
=
errors
.
New
(
"expired"
)
errBadVersion
=
errors
.
New
(
"version mismatch"
)
errUnsolicitedReply
=
errors
.
New
(
"unsolicited reply"
)
errUnsolicitedReply
=
errors
.
New
(
"unsolicited reply"
)
errUnknownNode
=
errors
.
New
(
"unknown node"
)
errUnknownNode
=
errors
.
New
(
"unknown node"
)
errTimeout
=
errors
.
New
(
"RPC timeout"
)
errTimeout
=
errors
.
New
(
"RPC timeout"
)
...
@@ -52,8 +51,6 @@ const (
...
@@ -52,8 +51,6 @@ const (
respTimeout
=
500
*
time
.
Millisecond
respTimeout
=
500
*
time
.
Millisecond
sendTimeout
=
500
*
time
.
Millisecond
sendTimeout
=
500
*
time
.
Millisecond
expiration
=
20
*
time
.
Second
expiration
=
20
*
time
.
Second
refreshInterval
=
1
*
time
.
Hour
)
)
// RPC packet types
// RPC packet types
...
@@ -312,10 +309,8 @@ func (t *udp) loop() {
...
@@ -312,10 +309,8 @@ func (t *udp) loop() {
plist
=
list
.
New
()
plist
=
list
.
New
()
timeout
=
time
.
NewTimer
(
0
)
timeout
=
time
.
NewTimer
(
0
)
nextTimeout
*
pending
// head of plist when timeout was last reset
nextTimeout
*
pending
// head of plist when timeout was last reset
refresh
=
time
.
NewTicker
(
refreshInterval
)
)
)
<-
timeout
.
C
// ignore first timeout
<-
timeout
.
C
// ignore first timeout
defer
refresh
.
Stop
()
defer
timeout
.
Stop
()
defer
timeout
.
Stop
()
resetTimeout
:=
func
()
{
resetTimeout
:=
func
()
{
...
@@ -344,9 +339,6 @@ func (t *udp) loop() {
...
@@ -344,9 +339,6 @@ func (t *udp) loop() {
resetTimeout
()
resetTimeout
()
select
{
select
{
case
<-
refresh
.
C
:
go
t
.
refresh
()
case
<-
t
.
closing
:
case
<-
t
.
closing
:
for
el
:=
plist
.
Front
();
el
!=
nil
;
el
=
el
.
Next
()
{
for
el
:=
plist
.
Front
();
el
!=
nil
;
el
=
el
.
Next
()
{
el
.
Value
.
(
*
pending
)
.
errc
<-
errClosed
el
.
Value
.
(
*
pending
)
.
errc
<-
errClosed
...
@@ -529,9 +521,6 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) er
...
@@ -529,9 +521,6 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) er
if
expired
(
req
.
Expiration
)
{
if
expired
(
req
.
Expiration
)
{
return
errExpired
return
errExpired
}
}
if
req
.
Version
!=
Version
{
return
errBadVersion
}
t
.
send
(
from
,
pongPacket
,
pong
{
t
.
send
(
from
,
pongPacket
,
pong
{
To
:
makeEndpoint
(
from
,
req
.
From
.
TCP
),
To
:
makeEndpoint
(
from
,
req
.
From
.
TCP
),
ReplyTok
:
mac
,
ReplyTok
:
mac
,
...
...
p2p/discover/udp_test.go
View file @
e3ac56d5
...
@@ -122,7 +122,6 @@ func TestUDP_packetErrors(t *testing.T) {
...
@@ -122,7 +122,6 @@ func TestUDP_packetErrors(t *testing.T) {
defer
test
.
table
.
Close
()
defer
test
.
table
.
Close
()
test
.
packetIn
(
errExpired
,
pingPacket
,
&
ping
{
From
:
testRemote
,
To
:
testLocalAnnounced
,
Version
:
Version
})
test
.
packetIn
(
errExpired
,
pingPacket
,
&
ping
{
From
:
testRemote
,
To
:
testLocalAnnounced
,
Version
:
Version
})
test
.
packetIn
(
errBadVersion
,
pingPacket
,
&
ping
{
From
:
testRemote
,
To
:
testLocalAnnounced
,
Version
:
99
,
Expiration
:
futureExp
})
test
.
packetIn
(
errUnsolicitedReply
,
pongPacket
,
&
pong
{
ReplyTok
:
[]
byte
{},
Expiration
:
futureExp
})
test
.
packetIn
(
errUnsolicitedReply
,
pongPacket
,
&
pong
{
ReplyTok
:
[]
byte
{},
Expiration
:
futureExp
})
test
.
packetIn
(
errUnknownNode
,
findnodePacket
,
&
findnode
{
Expiration
:
futureExp
})
test
.
packetIn
(
errUnknownNode
,
findnodePacket
,
&
findnode
{
Expiration
:
futureExp
})
test
.
packetIn
(
errUnsolicitedReply
,
neighborsPacket
,
&
neighbors
{
Expiration
:
futureExp
})
test
.
packetIn
(
errUnsolicitedReply
,
neighborsPacket
,
&
neighbors
{
Expiration
:
futureExp
})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment