-
Notifications
You must be signed in to change notification settings - Fork 20.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
swarm/network: hive bug: needed shallow peers are not sent to nodes beyond connection's proximity order #19326
Changes from all commits
b351467
d8f4011
80afc94
6506604
a89902e
a47e168
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,19 +17,32 @@ | |
package network | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
crand "crypto/rand" | ||
"encoding/binary" | ||
"fmt" | ||
"math/rand" | ||
"net" | ||
"sort" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/ethereum/go-ethereum/p2p" | ||
"github.com/ethereum/go-ethereum/p2p/enode" | ||
"github.com/ethereum/go-ethereum/p2p/protocols" | ||
p2ptest "github.com/ethereum/go-ethereum/p2p/testing" | ||
"github.com/ethereum/go-ethereum/swarm/pot" | ||
) | ||
|
||
/*** | ||
* | ||
* - after connect, that outgoing subpeersmsg is sent | ||
* | ||
*/ | ||
func TestDiscovery(t *testing.T) { | ||
func TestSubPeersMsg(t *testing.T) { | ||
params := NewHiveParams() | ||
s, pp, err := newHiveTester(t, params, 1, nil) | ||
s, pp, err := newHiveTester(params, 1, nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
@@ -58,3 +71,192 @@ func TestDiscovery(t *testing.T) { | |
t.Fatal(err) | ||
} | ||
} | ||
|
||
const ( | ||
frncmx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
maxPO = 8 // PO of pivot and control; chosen to test enough cases but not run too long | ||
maxPeerPO = 6 // pivot has no peers closer than this to the control peer | ||
maxPeersPerPO = 3 | ||
) | ||
|
||
// TestInitialPeersMsg tests if peersMsg response to incoming subPeersMsg is correct | ||
func TestInitialPeersMsg(t *testing.T) { | ||
for po := 0; po < maxPO; po++ { | ||
for depth := 0; depth < maxPO; depth++ { | ||
t.Run(fmt.Sprintf("PO=%d,advertised depth=%d", po, depth), func(t *testing.T) { | ||
testInitialPeersMsg(t, po, depth) | ||
}) | ||
} | ||
} | ||
} | ||
|
||
// testInitialPeersMsg tests that the correct set of peer info is sent | ||
// to another peer after receiving their subPeersMsg request | ||
func testInitialPeersMsg(t *testing.T, peerPO, peerDepth int) { | ||
// generate random pivot address | ||
prvkey, err := crypto.GenerateKey() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
defer func(orig func([]*BzzAddr) []*BzzAddr) { | ||
sortPeers = orig | ||
}(sortPeers) | ||
sortPeers = testSortPeers | ||
pivotAddr := pot.NewAddressFromBytes(PrivateKeyToBzzKey(prvkey)) | ||
// generate control peers address at peerPO wrt pivot | ||
peerAddr := pot.RandomAddressAt(pivotAddr, peerPO) | ||
// construct kademlia and hive | ||
to := NewKademlia(pivotAddr[:], NewKadParams()) | ||
hive := NewHive(NewHiveParams(), to, nil) | ||
|
||
// expected addrs in peersMsg response | ||
var expBzzAddrs []*BzzAddr | ||
connect := func(a pot.Address, po int) (addrs []*BzzAddr) { | ||
n := rand.Intn(maxPeersPerPO) | ||
for i := 0; i < n; i++ { | ||
peer, err := newDiscPeer(pot.RandomAddressAt(a, po)) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
hive.On(peer) | ||
addrs = append(addrs, peer.BzzAddr) | ||
} | ||
return addrs | ||
} | ||
register := func(a pot.Address, po int) { | ||
addr := pot.RandomAddressAt(a, po) | ||
hive.Register(&BzzAddr{OAddr: addr[:]}) | ||
} | ||
|
||
// generate connected and just registered peers | ||
for po := maxPeerPO; po >= 0; po-- { | ||
// create a fake connected peer at po from peerAddr | ||
ons := connect(peerAddr, po) | ||
// create a fake registered address at po from peerAddr | ||
register(peerAddr, po) | ||
// we collect expected peer addresses only up till peerPO | ||
if po < peerDepth { | ||
continue | ||
} | ||
expBzzAddrs = append(expBzzAddrs, ons...) | ||
} | ||
|
||
// add extra connections closer to pivot than control | ||
for po := peerPO + 1; po < maxPO; po++ { | ||
ons := connect(pivotAddr, po) | ||
if peerDepth <= peerPO { | ||
expBzzAddrs = append(expBzzAddrs, ons...) | ||
} | ||
} | ||
|
||
// create a special bzzBaseTester in which we can associate `enode.ID` to the `bzzAddr` we created above | ||
s, _, err := newBzzBaseTesterWithAddrs(prvkey, [][]byte{peerAddr[:]}, DiscoverySpec, hive.Run) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// peerID to use in the protocol tester testExchange expect/trigger | ||
peerID := s.Nodes[0].ID() | ||
// block until control peer is found among hive peers | ||
found := false | ||
for attempts := 0; attempts < 20; attempts++ { | ||
if _, found = hive.peers[peerID]; found { | ||
break | ||
} | ||
time.Sleep(1 * time.Millisecond) | ||
} | ||
|
||
if !found { | ||
t.Fatal("timeout waiting for peer connection to start") | ||
} | ||
|
||
// pivotDepth is the advertised depth of the pivot node we expect in the outgoing subPeersMsg | ||
pivotDepth := hive.saturation() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't find a compelling explanation why this is the correct function to use for this test and not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the logic. Polemic on the ambiguous naming aside. |
||
// the test exchange is as follows: | ||
// 1. pivot sends to the control peer a `subPeersMsg` advertising its depth (ignored) | ||
// 2. peer sends to pivot a `subPeersMsg` advertising its own depth (arbitrarily chosen) | ||
// 3. pivot responds with `peersMsg` with the set of expected peers | ||
err = s.TestExchanges( | ||
p2ptest.Exchange{ | ||
Label: "outgoing subPeersMsg", | ||
Expects: []p2ptest.Expect{ | ||
{ | ||
Code: 1, | ||
Msg: &subPeersMsg{Depth: uint8(pivotDepth)}, | ||
Peer: peerID, | ||
}, | ||
}, | ||
}, | ||
p2ptest.Exchange{ | ||
Label: "trigger subPeersMsg and expect peersMsg", | ||
Triggers: []p2ptest.Trigger{ | ||
{ | ||
Code: 1, | ||
Msg: &subPeersMsg{Depth: uint8(peerDepth)}, | ||
Peer: peerID, | ||
}, | ||
}, | ||
Expects: []p2ptest.Expect{ | ||
{ | ||
Code: 0, | ||
Msg: &peersMsg{Peers: testSortPeers(expBzzAddrs)}, | ||
Peer: peerID, | ||
Timeout: 100 * time.Millisecond, | ||
}, | ||
}, | ||
}) | ||
|
||
// for values MaxPeerPO < peerPO < MaxPO the pivot has no peers to offer to the control peer | ||
// in this case, no peersMsg will be sent out, and we would run into a time out | ||
if len(expBzzAddrs) == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: I still hold the intuition that maybe this test tries to test too many things at once. (clean code: test one concept per test, ideally with one assert) But I let that go. On the other hand, please consider my version for the same logic. What do you think? Is it simpler?
Note:
|
||
if err != nil { | ||
if err.Error() != "exchange #1 \"trigger subPeersMsg and expect peersMsg\": timed out" { | ||
t.Fatalf("expected timeout, got %v", err) | ||
} | ||
return | ||
} | ||
t.Fatalf("expected timeout, got no error") | ||
} | ||
|
||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func testSortPeers(peers []*BzzAddr) []*BzzAddr { | ||
comp := func(i, j int) bool { | ||
vi := binary.BigEndian.Uint64(peers[i].OAddr) | ||
vj := binary.BigEndian.Uint64(peers[j].OAddr) | ||
return vi < vj | ||
} | ||
sort.Slice(peers, comp) | ||
return peers | ||
} | ||
|
||
// as we are not creating a real node via the protocol, | ||
// we need to create the discovery peer objects for the additional kademlia | ||
// nodes manually | ||
func newDiscPeer(addr pot.Address) (*Peer, error) { | ||
pKey, err := ecdsa.GenerateKey(crypto.S256(), crand.Reader) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pubKey := pKey.PublicKey | ||
nod := enode.NewV4(&pubKey, net.IPv4(127, 0, 0, 1), 0, 0) | ||
bzzAddr := &BzzAddr{OAddr: addr[:], UAddr: []byte(nod.String())} | ||
id := nod.ID() | ||
p2pPeer := p2p.NewPeer(id, id.String(), nil) | ||
return NewPeer(&BzzPeer{ | ||
Peer: protocols.NewPeer(p2pPeer, &dummyMsgRW{}, DiscoverySpec), | ||
BzzAddr: bzzAddr, | ||
}, nil), nil | ||
} | ||
|
||
type dummyMsgRW struct{} | ||
|
||
func (d *dummyMsgRW) ReadMsg() (p2p.Msg, error) { | ||
return p2p.Msg{}, nil | ||
} | ||
func (d *dummyMsgRW) WriteMsg(msg p2p.Msg) error { | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: 'iff' => 'if'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iff means if and only if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: then I would prefer 'if and only if' because that requires no knowledge of the abbreviation; that has to be written just once and read many times