// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package network import ( "fmt" "sync" "testing" "time" "github.com/ethereum/go-ethereum/pot" ) func testKadPeerAddr(s string) *bzzAddr { a := pot.NewHashAddress(s).Bytes() return &bzzAddr{OAddr: a, UAddr: a} } type testDropPeer struct { Peer dropc chan error } type testDiscPeer struct { *testDropPeer lock *sync.Mutex notifications map[string]uint8 } type testPeerNotification struct { rec string addr string po uint8 } type testProxNotification struct { rec string po uint8 } type dropError struct { error addr string } func (self *testDropPeer) Drop(err error) { err2 := &dropError{err, overlayStr(self)} self.dropc <- err2 } func (self *testDiscPeer) NotifyProx(po uint8) error { key := overlayStr(self) self.lock.Lock() defer self.lock.Unlock() self.notifications[key] = po return nil } func (self *testDiscPeer) NotifyPeer(p OverlayPeer, po uint8) error { key := overlayStr(self) key += overlayStr(p) self.lock.Lock() defer self.lock.Unlock() self.notifications[key] = po return nil } type testKademlia struct { *Kademlia Discovery bool dropc chan error lock *sync.Mutex notifications map[string]uint8 } func newTestKademlia(b string) *testKademlia { params := NewKadParams() params.MinBinSize = 1 params.MinProxBinSize = 2 base := pot.NewHashAddress(b).Bytes() return &testKademlia{ NewKademlia(base, params), false, make(chan error), &sync.Mutex{}, make(map[string]uint8), } } func (k *testKademlia) newTestKadPeer(s string) Peer { dp := &testDropPeer{&bzzPeer{bzzAddr: testKadPeerAddr(s)}, k.dropc} if k.Discovery { return Peer(&testDiscPeer{dp, k.lock, k.notifications}) } return Peer(dp) } func overlayStr(a OverlayPeer) string { // log.Error(fmt.Sprintf("PeerAddr: %v (%T)", a, a)) // if a == (*KadPeer)(nil) || a == (*testDiscPeer)(nil) || a == (*bzzPeer)(nil) || a == nil { // return "" // } // var p Peer // s, ok := a.(*KadPeer) // if ok { // p = s.Peer // } else { // p = a.(*testDiscPeer).Peer // } // log.Error(fmt.Sprintf("PeerAddr: %v (%T)", p, p)) // if p == (Peer)(nil) || p == (*testDiscPeer)(nil) || p == (*bzzPeer)(nil) { // return "" // } // return pot.NewHashAddressFromBytes(p.OverlayAddr()).Bin()[:6] // if a == nil { // return "" // } // k, ok := a.(*KadPeer) // if ok && k.Peer != nil { // return pot.ToBin(a.(*KadPeer).Peer.Over())[:6] // } // return pot.ToBin(a.Over())[:6] return pot.ToBin(a.Address()) } func (k *testKademlia) On(ons ...string) *testKademlia { for _, s := range ons { p := k.newTestKadPeer(s) k.Kademlia.On(p) } return k } func (k *testKademlia) Off(offs ...string) *testKademlia { for _, s := range offs { k.Kademlia.Off(k.newTestKadPeer(s).(OverlayConn)) } return k } func (k *testKademlia) Register(regs ...string) *testKademlia { var ps []Addr for _, s := range regs { ps = append(ps, Addr(testKadPeerAddr(s))) } k.Kademlia.Register(ps...) return k } func testSuggestPeer(t *testing.T, k *testKademlia, expAddr string, expPo int, expWant bool) error { addr, o, want := k.SuggestPeer() if overlayStr(addr) != expAddr { return fmt.Errorf("incorrect peer address suggested. expected %v, got %v", expAddr, overlayStr(addr)) } if o != expPo { return fmt.Errorf("incorrect prox order suggested. expected %v, got %v", expPo, o) } if want != expWant { return fmt.Errorf("expected SuggestPeer to want peers: %v", expWant) } return nil } func TestSuggestPeerFindPeers(t *testing.T) { // 2 row gap, unsaturated proxbin, no callables -> want PO 0 k := newTestKademlia("000000").On("001000") // k.MinProxBinSize = 2 // k.MinBinSize = 2 err := testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } // 2 row gap, saturated proxbin, no callables -> want PO 0 k.On("000100") err = testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } // 1 row gap (1 less), saturated proxbin, no callables -> want PO 1 k.On("100000") err = testSuggestPeer(t, k, "", 1, true) if err != nil { t.Fatal(err.Error()) } // no gap (1 less), saturated proxbin, no callables -> do not want more k.On("010000", "001001") err = testSuggestPeer(t, k, "", 0, false) if err != nil { t.Fatal(err.Error()) } // oversaturated proxbin, > do not want more k.On("001001") err = testSuggestPeer(t, k, "", 0, false) if err != nil { t.Fatal(err.Error()) } // reintroduce gap, disconnected peer callable k.Off("010000") err = testSuggestPeer(t, k, "010000", 0, false) if err != nil { t.Fatal(err.Error()) } // second time disconnected peer not callable // with reasonably set Interval // err = testSuggestPeer(t, k, "010000", 2, true) err = testSuggestPeer(t, k, "", 1, true) if err != nil { t.Fatal(err.Error()) } // on and off again, peer callable again k.On("010000") k.Off("010000") err = testSuggestPeer(t, k, "010000", 0, false) if err != nil { t.Fatal(err.Error()) } k.On("010000") k.Off("010000") // PO1 disconnects // new closer peer appears, it is immediately wanted // k.Off("010000") k.Register("000101") err = testSuggestPeer(t, k, "000101", 0, false) if err != nil { t.Fatal(err.Error()) } // second time, gap filling err = testSuggestPeer(t, k, "010000", 0, false) if err != nil { t.Fatal(err.Error()) } k.On("010000") err = testSuggestPeer(t, k, "", 0, false) if err != nil { t.Fatal(err.Error()) } k.MinBinSize = 2 err = testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } k.On("100001") k.On("010001") err = testSuggestPeer(t, k, "", 0, false) if err != nil { t.Fatal(err.Error()) } k.MinBinSize = 3 k.On("100010") k.On("010010") err = testSuggestPeer(t, k, "", 2, true) if err != nil { t.Fatal(err.Error()) } k.On("001010") err = testSuggestPeer(t, k, "", 0, false) if err != nil { t.Fatal(err.Error()) } } func TestSuggestPeerRetries(t *testing.T) { // 2 row gap, unsaturated proxbin, no callables -> want PO 0 k := newTestKademlia("000000") cycle := 50 * time.Millisecond k.RetryInterval = int(cycle) k.MaxRetries = 3 k.RetryExponent = 3 k.Register("010000") k.On("000001", "000010") err := testSuggestPeer(t, k, "010000", 0, false) if err != nil { t.Fatal(err.Error()) } err = testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } // cycle *= time.Duration(k.RetryExponent) time.Sleep(cycle) err = testSuggestPeer(t, k, "010000", 0, false) if err != nil { t.Fatal(err.Error()) } err = testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } cycle *= time.Duration(k.RetryExponent) time.Sleep(cycle) err = testSuggestPeer(t, k, "010000", 0, false) if err != nil { t.Fatal(err.Error()) } err = testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } cycle *= time.Duration(k.RetryExponent) time.Sleep(cycle) err = testSuggestPeer(t, k, "010000", 0, false) if err != nil { t.Fatal(err.Error()) } err = testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } cycle *= time.Duration(k.RetryExponent) time.Sleep(cycle) err = testSuggestPeer(t, k, "", 0, true) if err != nil { t.Fatal(err.Error()) } } func TestPruning(t *testing.T) { k := newTestKademlia("000000") k.On("100000", "110000", "101000", "100100", "100010") k.On("010000", "011000", "010100", "010010", "010001") k.On("001000", "001100", "001010", "001001") k.MaxBinSize = 4 k.MinBinSize = 3 prune := make(chan time.Time) defer close(prune) k.Prune((<-chan time.Time)(prune)) prune <- time.Now() errc := make(chan error) timeout := time.NewTimer(1000 * time.Millisecond) n := 0 dropped := make(map[string]error) go func() { for e := range k.dropc { err := e.(*dropError) dropped[err.addr] = err.error n++ if n == 4 { break } } close(errc) }() select { case <-errc: case <-timeout.C: t.Fatalf("timeout waiting for 4 peers to be dropped") } // TODO: this is now based on just taking the first 2 peers // in order of connecting expDropped := []string{ "101000", "110000", "010100", "011000", } for _, addr := range expDropped { err := dropped[addr] if err == nil { t.Fatalf("expected peer %v to be dropped", addr) } if err.Error() != "bucket full" { t.Fatalf("incorrect error. expected %v, got %v", "bucket full", err) } } } func TestKademliaHiveString(t *testing.T) { k := newTestKademlia("000000").On("010000", "001000").Register("100000", "100001") h := k.String() expH := "\n=========================================================================\nMon Feb 27 12:10:28 UTC 2017 KΛÐΞMLIΛ hive: queen's address: 000000\npopulation: 2 (4), MinProxBinSize: 2, MinBinSize: 1, MaxBinSize: 4\n============ PROX LIMIT: 0 ==========================================\n000 0 | 2 840000 800000\n001 1 400000 | 1 400000\n002 1 200000 | 1 200000\n003 0 | 0\n004 0 | 0\n005 0 | 0\n006 0 | 0\n007 0 | 0\n=========================================================================" if expH[100:] != h[100:] { t.Fatalf("incorrect hive output. expected %v, got %v", expH, h) } } func (self *testKademlia) checkNotifications(npeers []*testPeerNotification, nprox []*testProxNotification) error { for _, pn := range npeers { key := pn.rec + pn.addr po, found := self.notifications[key] if !found || pn.po != po { return fmt.Errorf("%v, expected to have notified %v about peer %v (%v)", key, pn.rec, pn.addr, pn.po) } delete(self.notifications, key) } for _, pn := range nprox { key := pn.rec po, found := self.notifications[key] if !found || pn.po != po { return fmt.Errorf("expected to have notified %v about new prox limit %v", pn.rec, pn.po) } delete(self.notifications, key) } if len(self.notifications) > 0 { return fmt.Errorf("%v unexpected notifications", len(self.notifications)) } return nil } func TestNotifications(t *testing.T) { k := newTestKademlia("000000") k.Discovery = true k.MinProxBinSize = 3 k.On("010000", "001000") time.Sleep(100 * time.Millisecond) err := k.checkNotifications( []*testPeerNotification{ &testPeerNotification{"010000", "001000", 1}, }, []*testProxNotification{ &testProxNotification{"001000", 0}, &testProxNotification{"010000", 0}, }, ) if err != nil { t.Fatal(err.Error()) } k = k.On("100000") time.Sleep(100 * time.Millisecond) k.checkNotifications( []*testPeerNotification{ &testPeerNotification{"010000", "100000", 0}, &testPeerNotification{"001000", "100000", 0}, }, []*testProxNotification{ &testProxNotification{"100000", 0}, }, ) k = k.On("010001") time.Sleep(100 * time.Millisecond) k.checkNotifications( []*testPeerNotification{ &testPeerNotification{"010000", "010001", 5}, &testPeerNotification{"001000", "010001", 1}, &testPeerNotification{"100000", "010001", 0}, }, []*testProxNotification{ &testProxNotification{"100000", 0}, &testProxNotification{"010000", 0}, &testProxNotification{"010001", 0}, &testProxNotification{"001000", 0}, }, ) }