package pss import ( "fmt" "math/rand" "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" "github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/pot" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" ) type testCase struct { name string recipient []byte peers []pot.Address expected []int exclusive bool nFails int success bool errors string } var testCases []testCase // the purpose of this test is to see that pss.forward() function correctly // selects the peers for message forwarding, depending on the message address // and kademlia constellation. func TestForwardBasic(t *testing.T) { baseAddrBytes := make([]byte, 32) for i := 0; i < len(baseAddrBytes); i++ { baseAddrBytes[i] = 0xFF } var c testCase base := pot.NewAddressFromBytes(baseAddrBytes) var peerAddresses []pot.Address const depth = 10 for i := 0; i <= depth; i++ { // add two peers for each proximity order a := pot.RandomAddressAt(base, i) peerAddresses = append(peerAddresses, a) a = pot.RandomAddressAt(base, i) peerAddresses = append(peerAddresses, a) } // skip one level, add one peer at one level deeper. // as a result, we will have an edge case of three peers in nearest neighbours' bin. peerAddresses = append(peerAddresses, pot.RandomAddressAt(base, depth+2)) kad := network.NewKademlia(base[:], network.NewKadParams()) ps := createPss(t, kad) defer ps.Stop() addPeers(kad, peerAddresses) const firstNearest = depth * 2 // shallowest peer in the nearest neighbours' bin nearestNeighbours := []int{firstNearest, firstNearest + 1, firstNearest + 2} var all []int // indices of all the peers for i := 0; i < len(peerAddresses); i++ { all = append(all, i) } for i := 0; i < len(peerAddresses); i++ { // send msg directly to the known peers (recipient address == peer address) c = testCase{ name: fmt.Sprintf("Send direct to known, id: [%d]", i), recipient: peerAddresses[i][:], peers: peerAddresses, expected: []int{i}, exclusive: false, } testCases = append(testCases, c) } for i := 0; i < firstNearest; i++ { // send random messages with proximity orders, corresponding to PO of each bin, // with one peer being closer to the recipient address a := pot.RandomAddressAt(peerAddresses[i], 64) c = testCase{ name: fmt.Sprintf("Send random to each PO, id: [%d]", i), recipient: a[:], peers: peerAddresses, expected: []int{i}, exclusive: false, } testCases = append(testCases, c) } for i := 0; i < firstNearest; i++ { // send random messages with proximity orders, corresponding to PO of each bin, // with random proximity relative to the recipient address po := i / 2 a := pot.RandomAddressAt(base, po) c = testCase{ name: fmt.Sprintf("Send direct to known, id: [%d]", i), recipient: a[:], peers: peerAddresses, expected: []int{po * 2, po*2 + 1}, exclusive: true, } testCases = append(testCases, c) } for i := firstNearest; i < len(peerAddresses); i++ { // recipient address falls into the nearest neighbours' bin a := pot.RandomAddressAt(base, i) c = testCase{ name: fmt.Sprintf("recipient address falls into the nearest neighbours' bin, id: [%d]", i), recipient: a[:], peers: peerAddresses, expected: nearestNeighbours, exclusive: false, } testCases = append(testCases, c) } // send msg with proximity order much deeper than the deepest nearest neighbour a2 := pot.RandomAddressAt(base, 77) c = testCase{ name: "proximity order much deeper than the deepest nearest neighbour", recipient: a2[:], peers: peerAddresses, expected: nearestNeighbours, exclusive: false, } testCases = append(testCases, c) // test with partial addresses const part = 12 for i := 0; i < firstNearest; i++ { // send messages with partial address falling into different proximity orders po := i / 2 if i%8 != 0 { c = testCase{ name: fmt.Sprintf("partial address falling into different proximity orders, id: [%d]", i), recipient: peerAddresses[i][:i], peers: peerAddresses, expected: []int{po * 2, po*2 + 1}, exclusive: true, } testCases = append(testCases, c) } c = testCase{ name: fmt.Sprintf("extended partial address falling into different proximity orders, id: [%d]", i), recipient: peerAddresses[i][:part], peers: peerAddresses, expected: []int{po * 2, po*2 + 1}, exclusive: true, } testCases = append(testCases, c) } for i := firstNearest; i < len(peerAddresses); i++ { // partial address falls into the nearest neighbours' bin c = testCase{ name: fmt.Sprintf("partial address falls into the nearest neighbours' bin, id: [%d]", i), recipient: peerAddresses[i][:part], peers: peerAddresses, expected: nearestNeighbours, exclusive: false, } testCases = append(testCases, c) } // partial address with proximity order deeper than any of the nearest neighbour a3 := pot.RandomAddressAt(base, part) c = testCase{ name: "partial address with proximity order deeper than any of the nearest neighbour", recipient: a3[:part], peers: peerAddresses, expected: nearestNeighbours, exclusive: false, } testCases = append(testCases, c) // special cases where partial address matches a large group of peers // zero bytes of address is given, msg should be delivered to all the peers c = testCase{ name: "zero bytes of address is given", recipient: []byte{}, peers: peerAddresses, expected: all, exclusive: false, } testCases = append(testCases, c) // luminous radius of 8 bits, proximity order 8 indexAtPo8 := 16 c = testCase{ name: "luminous radius of 8 bits", recipient: []byte{0xFF}, peers: peerAddresses, expected: all[indexAtPo8:], exclusive: false, } testCases = append(testCases, c) // luminous radius of 256 bits, proximity order 8 a4 := pot.Address{} a4[0] = 0xFF c = testCase{ name: "luminous radius of 256 bits", recipient: a4[:], peers: peerAddresses, expected: []int{indexAtPo8, indexAtPo8 + 1}, exclusive: true, } testCases = append(testCases, c) // check correct behaviour in case send fails for i := 2; i < firstNearest-3; i += 2 { po := i / 2 // send random messages with proximity orders, corresponding to PO of each bin, // with different numbers of failed attempts. // msg should be received by only one of the deeper peers. a := pot.RandomAddressAt(base, po) c = testCase{ name: fmt.Sprintf("Send direct to known, id: [%d]", i), recipient: a[:], peers: peerAddresses, expected: all[i+1:], exclusive: true, nFails: rand.Int()%3 + 2, } testCases = append(testCases, c) } for _, c := range testCases { testForwardMsg(t, ps, &c) } } // this function tests the forwarding of a single message. the recipient address is passed as param, // along with addresses of all peers, and indices of those peers which are expected to receive the message. func testForwardMsg(t *testing.T, ps *Pss, c *testCase) { recipientAddr := c.recipient peers := c.peers expected := c.expected exclusive := c.exclusive nFails := c.nFails tries := 0 // number of previous failed tries resultMap := make(map[pot.Address]int) defer func() { sendFunc = sendMsg }() sendFunc = func(_ *Pss, sp *network.Peer, _ *PssMsg) bool { if tries < nFails { tries++ return false } a := pot.NewAddressFromBytes(sp.Address()) resultMap[a]++ return true } msg := newTestMsg(recipientAddr) ps.forward(msg) // check test results var fail bool precision := len(recipientAddr) if precision > 4 { precision = 4 } s := fmt.Sprintf("test [%s]\nmsg address: %x..., radius: %d", c.name, recipientAddr[:precision], 8*len(recipientAddr)) // false negatives (expected message didn't reach peer) if exclusive { var cnt int for _, i := range expected { a := peers[i] cnt += resultMap[a] resultMap[a] = 0 } if cnt != 1 { s += fmt.Sprintf("\n%d messages received by %d peers with indices: [%v]", cnt, len(expected), expected) fail = true } } else { for _, i := range expected { a := peers[i] received := resultMap[a] if received != 1 { s += fmt.Sprintf("\npeer number %d [%x...] received %d messages", i, a[:4], received) fail = true } resultMap[a] = 0 } } // false positives (unexpected message reached peer) for k, v := range resultMap { if v != 0 { // find the index of the false positive peer var j int for j = 0; j < len(peers); j++ { if peers[j] == k { break } } s += fmt.Sprintf("\npeer number %d [%x...] received %d messages", j, k[:4], v) fail = true } } if fail { t.Fatal(s) } } func addPeers(kad *network.Kademlia, addresses []pot.Address) { for _, a := range addresses { p := newTestDiscoveryPeer(a, kad) kad.On(p) } } func createPss(t *testing.T, kad *network.Kademlia) *Pss { privKey, err := crypto.GenerateKey() pssp := NewPssParams().WithPrivateKey(privKey) ps, err := NewPss(kad, pssp) if err != nil { t.Fatal(err.Error()) } return ps } func newTestDiscoveryPeer(addr pot.Address, kad *network.Kademlia) *network.Peer { rw := &p2p.MsgPipeRW{} p := p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}) pp := protocols.NewPeer(p, rw, &protocols.Spec{}) bp := &network.BzzPeer{ Peer: pp, BzzAddr: &network.BzzAddr{ OAddr: addr.Bytes(), UAddr: []byte(fmt.Sprintf("%x", addr[:])), }, } return network.NewPeer(bp, kad) } func newTestMsg(addr []byte) *PssMsg { msg := newPssMsg(&msgParams{}) msg.To = addr[:] msg.Expire = uint32(time.Now().Add(time.Second * 60).Unix()) msg.Payload = &whisper.Envelope{ Topic: [4]byte{}, Data: []byte("i have nothing to hide"), } return msg }