@ -17,9 +17,22 @@
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"
)
/ * * *
@ -27,9 +40,9 @@ import (
* - 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 (
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 ( )
// 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 {
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
}