package pss import ( "bytes" "context" "encoding/hex" "fmt" "io/ioutil" "math/rand" "os" "testing" "time" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/protocols" "github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" p2ptest "github.com/ethereum/go-ethereum/p2p/testing" "github.com/ethereum/go-ethereum/pot" "github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/storage" ) const ( pssServiceName = "pss" bzzServiceName = "bzz" ) var services = newServices() func init() { adapters.RegisterServices(services) h := log.CallerFileHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(true))) log.Root().SetHandler(h) } func TestPssCache(t *testing.T) { var err error to, _ := hex.DecodeString("08090a0b0c0d0e0f1011121314150001020304050607161718191a1b1c1d1e1f") oaddr, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") //uaddr, _ := hex.DecodeString("101112131415161718191a1b1c1d1e1f000102030405060708090a0b0c0d0e0f") //proofbytes := []byte{241, 172, 117, 105, 88, 154, 82, 33, 176, 188, 91, 244, 245, 85, 86, 16, 120, 232, 70, 45, 182, 188, 99, 103, 157, 3, 202, 121, 252, 21, 129, 22} proofbytes, _ := hex.DecodeString("ad312dca94df401555cfdeb85a6a1f87fb8f240f08dc36af246bd9d4d41efd89") ps := newTestPss(oaddr) pp := NewPssParams() data := []byte("foo") datatwo := []byte("bar") fwdaddr := network.RandomAddr() msg := &PssMsg{ Payload: &PssEnvelope{ TTL: 0, From: oaddr, Topic: pssPingTopic, Payload: data, }, To: to, } msgtwo := &PssMsg{ Payload: &PssEnvelope{ TTL: 0, From: oaddr, Topic: pssPingTopic, Payload: datatwo, }, To: to, } digest, err := ps.storeMsg(msg) if err != nil { t.Fatalf("could not store cache msgone: %v", err) } digesttwo, err := ps.storeMsg(msgtwo) if err != nil { t.Fatalf("could not store cache msgtwo: %v", err) } if !bytes.Equal(digest[:], proofbytes) { t.Fatalf("digest - got: %x, expected: %x", digest, proofbytes) } if digest == digesttwo { t.Fatalf("different msgs return same crc: %d", digesttwo) } // check the sender cache err = ps.addFwdCacheSender(fwdaddr.Over(), digest) if err != nil { t.Fatalf("write to pss sender cache failed: %v", err) } if !ps.checkFwdCache(fwdaddr.Over(), digest) { t.Fatalf("message %v should have SENDER record in cache but checkCache returned false", msg) } if ps.checkFwdCache(fwdaddr.Over(), digesttwo) { t.Fatalf("message %v should NOT have SENDER record in cache but checkCache returned true", msgtwo) } // check the expire cache err = ps.addFwdCacheExpire(digest) if err != nil { t.Fatalf("write to pss expire cache failed: %v", err) } if !ps.checkFwdCache(nil, digest) { t.Fatalf("message %v should have EXPIRE record in cache but checkCache returned false", msg) } if ps.checkFwdCache(nil, digesttwo) { t.Fatalf("message %v should NOT have EXPIRE record in cache but checkCache returned true", msgtwo) } time.Sleep(pp.Cachettl) if ps.checkFwdCache(nil, digest) { t.Fatalf("message %v should have expired from cache but checkCache returned true", msg) } err = ps.AddToCache(fwdaddr.Over(), msgtwo) if err != nil { t.Fatalf("public accessor cache write failed: %v", err) } if !ps.checkFwdCache(fwdaddr.Over(), digesttwo) { t.Fatalf("message %v should have SENDER record in cache but checkCache returned false", msgtwo) } } func TestPssRegisterHandler(t *testing.T) { var err error addr := network.RandomAddr() ps := newTestPss(addr.OAddr) from := network.RandomAddr() payload := []byte("payload") topic := NewTopic(pssSpec.Name, int(pssSpec.Version)) wrongtopic := NewTopic("foo", 42) checkMsg := func(msg []byte, p *p2p.Peer, sender []byte) error { if !bytes.Equal(from.OAddr, sender) { return fmt.Errorf("sender mismatch. expected %x, got %x", from.OAddr, sender) } if !bytes.Equal(msg, payload) { return fmt.Errorf("sender mismatch. expected %x, got %x", msg, payload) } return nil } deregister := ps.Register(&topic, checkMsg) pssmsg := &PssMsg{Payload: NewPssEnvelope(from.OAddr, topic, payload)} err = ps.Process(pssmsg) if err != nil { t.Fatal(err) } var i int err = ps.Process(&PssMsg{Payload: NewPssEnvelope(from.OAddr, wrongtopic, payload)}) expErr := "" if err == nil || err.Error() == expErr { t.Fatalf("unhandled topic expected '%v', got '%v'", expErr, err) } deregister2 := ps.Register(&topic, func(msg []byte, p *p2p.Peer, sender []byte) error { i++; return nil }) err = ps.Process(pssmsg) if err != nil { t.Fatal(err) } if i != 1 { t.Fatalf("second registerer handler did not run") } deregister() deregister2() err = ps.Process(&PssMsg{Payload: NewPssEnvelope(from.OAddr, topic, payload)}) expErr = "" if err == nil || err.Error() == expErr { t.Fatalf("reregister handler expected %v, got %v", expErr, err) } } func TestPssSimpleLinear(t *testing.T) { var err error nodeconfig := adapters.RandomNodeConfig() addr := network.NewAddrFromNodeID(nodeconfig.ID) _ = p2ptest.NewTestPeerPool() ps := newTestPss(addr.Over()) ping := &pssPing{ quitC: make(chan struct{}), } err = RegisterPssProtocol(ps, &pssPingTopic, pssPingProtocol, newPssPingProtocol(ping.pssPingHandler)) if err != nil { t.Fatalf("Failed to register virtual protocol in pss: %v", err) } run := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { id := p.ID() pp := protocols.NewPeer(p, rw, pssSpec) bp := &testPssPeer{ Peer: pp, addr: network.ToOverlayAddr(id[:]), } h := pot.NewHashAddressFromBytes(bp.addr) ps.fwdPool[h.Address] = pp ps.Overlay.On(bp) defer ps.Overlay.Off(bp) log.Debug(fmt.Sprintf("%v", ps.Overlay)) return bp.Run(ps.handlePssMsg) } pt := p2ptest.NewProtocolTester(t, nodeconfig.ID, 2, run) msg := newPssPingMsg(ps, network.ToOverlayAddr(pt.IDs[0].Bytes()), pssPingProtocol, pssPingTopic, []byte{1, 2, 3}) exchange := p2ptest.Exchange{ Expects: []p2ptest.Expect{ p2ptest.Expect{ Code: 0, Msg: msg, Peer: pt.IDs[0], }, }, Triggers: []p2ptest.Trigger{ p2ptest.Trigger{ Code: 0, Msg: msg, Peer: pt.IDs[1], }, }, } err = pt.TestExchanges(exchange) if err != nil { t.Fatalf("exchange failed %v", err) } } func TestPssFullRandom10_5_5(t *testing.T) { adapter := adapters.NewSimAdapter(services) testPssFullRandom(t, adapter, 10, 5, 5) } func testPssFullRandom(t *testing.T, adapter adapters.NodeAdapter, nodecount int, fullnodecount int, msgcount int) { var lastid discover.NodeID nodeCount := 5 net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ ID: "0", }) defer net.Shutdown() trigger := make(chan discover.NodeID) ids := make([]discover.NodeID, nodeCount) fullids := ids[0:fullnodecount] fullpeers := [][]byte{} for i := 0; i < nodeCount; i++ { nodeconfig := adapters.RandomNodeConfig() nodeconfig.Services = []string{"bzz", "pss"} node, err := net.NewNodeWithConfig(nodeconfig) if err != nil { t.Fatalf("error starting node: %s", err) } if err := net.Start(node.ID()); err != nil { t.Fatalf("error starting node %s: %s", node.ID().TerminalString(), err) } if err := triggerChecks(trigger, net, node.ID()); err != nil { t.Fatal("error triggering checks for node %s: %s", node.ID().TerminalString(), err) } ids[i] = node.ID() if i < fullnodecount { fullpeers = append(fullpeers, network.ToOverlayAddr(node.ID().Bytes())) } } // run a simulation which connects the 10 nodes in a ring and waits // for full peer discovery action := func(ctx context.Context) error { for i, id := range ids { var peerID discover.NodeID if i == 0 { peerID = ids[len(ids)-1] } else { peerID = ids[i-1] } if err := net.Connect(id, peerID); err != nil { return err } } return nil } check := func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } node := net.GetNode(id) if node == nil { return false, fmt.Errorf("unknown node: %s", id) } client, err := node.Client() if err != nil { return false, fmt.Errorf("error getting node client: %s", err) } for _, fid := range fullids { if fid == id { fpeeridx := rand.Int() % (fullnodecount - 1) log.Debug(fmt.Sprintf("fpeeridx %d, fpeer len %d", fpeeridx, len(fullpeers))) if bytes.Equal(fullpeers[fpeeridx], network.ToOverlayAddr(fid.Bytes())) { fpeeridx++ } msg := pssPingMsg{Created: time.Now()} code, _ := pssPingProtocol.GetCode(&pssPingMsg{}) pmsg, _ := newProtocolMsg(code, msg) client.CallContext(context.Background(), nil, "pss_sendRaw", pssPingTopic, PssAPIMsg{ Addr: fullpeers[fpeeridx], Msg: pmsg, }) } } lastid = id return true, nil } timeout := 5 * time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: ids, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } trigger = make(chan discover.NodeID) action = func(ctx context.Context) error { return nil } check = func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } return true, nil } timeout = 5 * time.Second ctx, cancel = context.WithTimeout(context.Background(), timeout) defer cancel() result = simulations.NewSimulation(net).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: fullids, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } t.Log("Simulation Passed:") t.Logf("Duration: %s", result.FinishedAt.Sub(result.StartedAt)) time.Sleep(time.Second * 2) } // triggerChecks triggers a simulation step check whenever a peer is added or // removed from the given node func triggerChecks(trigger chan discover.NodeID, net *simulations.Network, id discover.NodeID) error { gotpeer := make(map[discover.NodeID]bool) node := net.GetNode(id) if node == nil { return fmt.Errorf("unknown node: %s", id) } client, err := node.Client() if err != nil { return err } peerevents := make(chan *p2p.PeerEvent) peersub, err := client.Subscribe(context.Background(), "admin", peerevents, "peerEvents") if err != nil { return fmt.Errorf("error getting peer events for node %v: %s", id, err) } msgevents := make(chan PssAPIMsg) msgsub, err := client.Subscribe(context.Background(), "pss", msgevents, "newMsg", pssPingTopic) if err != nil { return fmt.Errorf("error getting peer events for node %v: %s", id, err) } go func() { defer msgsub.Unsubscribe() defer peersub.Unsubscribe() for { select { case event := <-peerevents: if event.Type == "add" && !gotpeer[event.Peer] { trigger <- id gotpeer[event.Peer] = true } case <-msgevents: trigger <- id case err := <-peersub.Err(): if err != nil { log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err) } return case err := <-msgsub.Err(): if err != nil { log.Error(fmt.Sprintf("error getting msg for node %v", id), "err", err) } return } } }() return nil } func newServices() adapters.Services { stateStore := adapters.NewSimStateStore() kademlias := make(map[discover.NodeID]*network.Kademlia) kademlia := func(id discover.NodeID) *network.Kademlia { if k, ok := kademlias[id]; ok { return k } addr := network.NewAddrFromNodeID(id) params := network.NewKadParams() params.MinProxBinSize = 2 params.MaxBinSize = 3 params.MinBinSize = 1 params.MaxRetries = 1000 params.RetryExponent = 2 params.RetryInterval = 1000000 kademlias[id] = network.NewKademlia(addr.Over(), params) return kademlias[id] } return adapters.Services{ "pss": func(id discover.NodeID, snapshot []byte) node.Service { cachedir, err := ioutil.TempDir("", "pss-cache") if err != nil { log.Error("create pss cache tmpdir failed", "error", err) return nil } dpa, err := storage.NewLocalDPA(cachedir) if err != nil { log.Error("local dpa creation failed", "error", err) return nil } pssp := NewPssParams() ps := NewPss(kademlia(id), dpa, pssp) ping := &pssPing{ quitC: make(chan struct{}), } err = RegisterPssProtocol(ps, &pssPingTopic, pssPingProtocol, newPssPingProtocol(ping.pssPingHandler)) if err != nil { log.Error("Couldnt register pss protocol", "err", err) os.Exit(1) } return ps }, "bzz": func(id discover.NodeID, snapshot []byte) node.Service { addr := network.NewAddrFromNodeID(id) config := &network.BzzConfig{ OverlayAddr: addr.Over(), UnderlayAddr: addr.Under(), HiveParams: network.NewHiveParams(), } return network.NewBzz(config, kademlia(id), stateStore) }, } } /* // example protocol implementation peer // message handlers are methods of this // channels allow receipt reporting from p2p.Protocol message handler type pssTestPeer struct { *protocols.Peer hasProtocol bool successC chan bool resultC chan int } // example node simulation peer // modeled from swarm/network/simulations/discovery/discovery_test.go - commit 08b1e42f // contains reporting channel for expect results so we can collect all async incoming msgs before deciding results type pssTestNode struct { *Hive *Pss id discover.NodeID network *simulations.Network trigger chan discover.NodeID run adapters.RunProtocol ct *protocols.CodeMap expectC chan []int ws *http.Handler apifunc func() []rpc.API } func (n *pssTestNode) Add(peer *bzzPeer) error { err := n.Hive.Add(peer) time.Sleep(time.Millisecond * 250) n.triggerCheck() return err } func (n *pssTestNode) triggerCheck() { go func() { n.trigger <- n.id }() } func (n *pssTestNode) OverlayAddr() []byte { return n.Pss.Overlay.BaseAddr() } func (n *pssTestNode) UnderlayAddr() []byte { return n.id.Bytes() } // the content of the msgs we're sending in the tests type pssTestPayload struct { Data string } func (m *pssTestPayload) String() string { return m.Data } type pssTestService struct { node *pssTestNode // get addrs from this msgFunc func(interface{}) error } func newPssTestService(t *testing.T, handlefunc func(interface{}) error, testnode *pssTestNode) *pssTestService { hp := NewHiveParams() hp.KeepAliveInterval = 300 bzz := NewBzz(testnode.OverlayAddr(), testnode.UnderlayAddr(), newTestStore()) testnode.Hive = NewHive(hp, testnode.Pss.Overlay, bzz) return &pssTestService{ //nid := adapters.NewNodeID(addr.UnderlayAddr()) msgFunc: handlefunc, node: testnode, } } func (self *pssTestService) Start(server *p2p.Server) error { return self.node.Hive.Start(server) } func (self *pssTestService) Stop() error { self.node.Hive.Stop() return nil } func (self *pssTestService) Protocols() []p2p.Protocol { bzz := NewBzz(self.node.OverlayAddr(), self.node.UnderlayAddr(), newTestStore()) return append(self.node.Hive.Protocols(), p2p.Protocol{ Name: PssProtocolName, Version: PssProtocolVersion, Length: PssProtocol.Length(), Run: bzz.RunProtocol(PssProtocol, self.Run), }) } func (self *pssTestService) APIs() []rpc.API { return []rpc.API{ rpc.API{ Namespace: "eth", Version: "0.1/pss", Service: NewPssApi(self.node.Pss), Public: true, }, } return nil } func (self *pssTestService) Run(peer *bzzPeer) error { self.node.Add(peer) defer self.node.Remove(peer) return peer.Run(self.msgFunc) } */ /* func testPssFullRandom(t *testing.T, numsends int, numnodes int, numfullnodes int) { var action func(ctx context.Context) error var i int var check func(ctx context.Context, id discover.NodeID) (bool, error) var ctx context.Context var result *simulations.StepResult var timeout time.Duration var cancel context.CancelFunc fullnodes := []discover.NodeID{} sends := []int{} // sender/receiver ids array indices pairs expectnodes := make(map[discover.NodeID]int) // how many messages we're expecting on each respective node expectnodesids := []discover.NodeID{} // the nodes to expect on (needed by checker) expectnodesresults := make(map[discover.NodeID][]int) // which messages expect actually got vct := protocols.NewCodeMap(map[uint64]interface{}{ 0: pssTestPayload{}, }) topic, _ := MakeTopic(protocolName, protocolVersion) trigger := make(chan discover.NodeID) testpeers := make(map[discover.NodeID]*pssTestPeer) net, nodes := newPssSimulationTester(t, numnodes, numfullnodes, trigger, vct, protocolName, protocolVersion, testpeers) ids := []discover.NodeID{} // connect the peers action = func(ctx context.Context) error { for id, _ := range nodes { ids = append(ids, id) if _, ok := testpeers[id]; ok { log.Trace(fmt.Sprintf("adding fullnode %x to testpeers %p", common.ByteLabel(id.Bytes()), testpeers)) fullnodes = append(fullnodes, id) } } for i, id := range ids { var peerID discover.NodeID if i != 0 { peerID = ids[i-1] if err := net.Connect(id, peerID); err != nil { return err } } } return nil } check = func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } node, ok := nodes[id] if !ok { return false, fmt.Errorf("unknown node: %s (%v)", id, node) } else { log.Trace(fmt.Sprintf("sim check ok node %v", id)) } return true, nil } timeout = 10 * time.Second ctx, cancel = context.WithTimeout(context.Background(), timeout) result = simulations.NewSimulation(net).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: ids, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } cancel() // ensure that we didn't get lost in concurrency issues if len(fullnodes) != numfullnodes { t.Fatalf("corrupt fullnodes array, expected %d, have %d", numfullnodes, len(fullnodes)) } // ensure that the channel is clean trigger = make(chan discover.NodeID) // randomly decide which nodes to send to and from rand.Seed(time.Now().Unix()) for i = 0; i < numsends; i++ { s := rand.Int() % numfullnodes r := s for r == s { r = rand.Int() % numfullnodes } log.Trace(fmt.Sprintf("rnd pss: idx %d->%d (%x -> %x)", s, r, common.ByteLabel(fullnodes[s].Bytes()), common.ByteLabel(fullnodes[r].Bytes()))) expectnodes[fullnodes[r]]++ sends = append(sends, s, r) } // distinct array of nodes to expect on for k, _ := range expectnodes { expectnodesids = append(expectnodesids, k) } // wait a bit for the kademlias to fill up z, _ := time.ParseDuration(fmt.Sprintf("%dms", (numnodes * 25))) if z.Seconds() < 1.0 { z = time.Second } time.Sleep(z) // send and monitor receive of pss action = func(ctx context.Context) error { code, _ := vct.GetCode(&pssTestPayload{}) for i := 0; i < len(sends); i += 2 { msgbytes, _ := makeMsg(code, &pssTestPayload{ Data: fmt.Sprintf("%v", i+1), }) go func(i int, expectnodesresults map[discover.NodeID][]int) { expectnode := fullnodes[sends[i+1]] // the receiving node sendnode := fullnodes[sends[i]] // the sending node oaddr := nodes[expectnode].OverlayAddr() err := nodes[sendnode].Pss.Send(oaddr, topic, msgbytes) if err != nil { t.Fatalf("could not send pss: %v", err) } select { // if the pss is delivered case <-testpeers[expectnode].successC: log.Trace(fmt.Sprintf("got successC from node %x", common.ByteLabel(expectnode.Bytes()))) expectnodesresults[expectnode] = append(expectnodesresults[expectnode], <-testpeers[expectnode].resultC) // if not we time out, -1 means fail tick case <-time.NewTimer(time.Second).C: log.Trace(fmt.Sprintf("result timed out on node %x", common.ByteLabel(expectnode.Bytes()))) expectnodesresults[expectnode] = append(expectnodesresults[expectnode], -1) } // we can safely send to the check handler if we got feedback for all msgs we sent to a particular node if len(expectnodesresults[expectnode]) == expectnodes[expectnode] { trigger <- expectnode nodes[expectnode].expectC <- expectnodesresults[expectnode] } }(i, expectnodesresults) } return nil } // results check = func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } receives := <-nodes[id].expectC log.Trace(fmt.Sprintf("expect received %d msgs on from node %x: %v", len(receives), common.ByteLabel(id.Bytes()), receives)) return true, nil } timeout = 10 * time.Second ctx, cancel = context.WithTimeout(context.Background(), timeout) defer cancel() result = simulations.NewSimulation(net).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: expectnodesids, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } t.Log("Simulation Passed:") for i := 0; i < len(sends); i += 2 { t.Logf("Pss #%d: oaddr %x -> %x (uaddr %x -> %x)", i/2+1, common.ByteLabel(nodes[fullnodes[sends[i]]].Pss.BaseAddr()), common.ByteLabel(nodes[fullnodes[sends[i+1]]].Pss.BaseAddr()), common.ByteLabel(fullnodes[sends[i]].Bytes()), common.ByteLabel(fullnodes[sends[i+1]].Bytes())) } totalfails := 0 for id, results := range expectnodesresults { fails := 0 for _, r := range results { if r == -1 { fails++ } } t.Logf("Node oaddr %x (uaddr %x) was sent %d msgs, of which %d failed", common.ByteLabel(nodes[id].Pss.BaseAddr()), common.ByteLabel(id.Bytes()), len(results), fails) totalfails += fails } t.Logf("Total sent: %d, total fail: %d (%.2f%%)", len(sends)/2, totalfails, (float32(totalfails)/float32(len(sends)/2+1))*100) for _, node := range nodes { logstring := fmt.Sprintf("Node oaddr %x kademlia: ", common.ByteLabel(node.Pss.Overlay.BaseAddr())) node.Pss.Overlay.EachConn(nil, 256, func(p Peer, po int, isprox bool) bool { logstring += fmt.Sprintf("%x ", common.ByteLabel(p.Over())) return true }) t.Log(logstring) } } func TestPssFullLinearEcho(t *testing.T) { var action func(ctx context.Context) error var check func(ctx context.Context, id discover.NodeID) (bool, error) var ctx context.Context var result *simulations.StepResult var timeout time.Duration var cancel context.CancelFunc var firstpssnode discover.NodeID var secondpssnode discover.NodeID vct := protocols.NewCodeMap(protocolName, protocolVersion, ProtocolMaxMsgSize) vct.Register(0, &pssTestPayload{}) topic, _ := MakeTopic(protocolName, protocolVersion) fullnodes := []discover.NodeID{} trigger := make(chan discover.NodeID) testpeers := make(map[discover.NodeID]*pssTestPeer) net, nodes := newPssSimulationTester(t, 3, 2, trigger, vct, protocolName, protocolVersion, testpeers) ids := []discover.NodeID{} // ohh risky! but the action for a specific id should come before the expect anyway action = func(ctx context.Context) error { var thinnodeid discover.NodeID for id, _ := range nodes { ids = append(ids, id) if _, ok := testpeers[id]; ok { log.Trace(fmt.Sprintf("adding fullnode %x to testpeers %p", common.ByteLabel(id.Bytes()), testpeers)) fullnodes = append(fullnodes, id) } else { thinnodeid = id } } if err := net.Connect(fullnodes[0], thinnodeid); err != nil { return err } if err := net.Connect(thinnodeid, fullnodes[1]); err != nil { return err } // for i, id := range ids { // var peerID discover.NodeID // if i != 0 { // peerID = ids[i-1] // if err := net.Connect(id, peerID); err != nil { // return err // } // } // } return nil } check = func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } node, ok := nodes[id] if !ok { return false, fmt.Errorf("unknown node: %s (%v)", id, node) } log.Trace(fmt.Sprintf("sim check ok node %v", id)) return true, nil } timeout = 10 * time.Second ctx, cancel = context.WithTimeout(context.Background(), timeout) result = simulations.NewSimulation(net).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: ids, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } cancel() nonode := &adapters.NodeID{} firstpssnode = nonode secondpssnode = nonode // first find a node that we're connected to for firstpssnode == nonode { log.Debug(fmt.Sprintf("Waiting for pss relaypeer for %x close to %x ...", common.ByteLabel(nodes[fullnodes[0]].OverlayAddr()), common.ByteLabel(nodes[ids[1]].OverlayAddr()))) nodes[fullnodes[0]].Pss.Overlay.EachConn(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { for _, id := range ids { if id.NodeID == p.ID() { firstpssnode = id log.Debug(fmt.Sprintf("PSS relay found; relaynode %v kademlia %v", common.ByteLabel(id.Bytes()), common.ByteLabel(firstpssnode.Bytes()))) } } if firstpssnode == nonode { return true } return false }) if firstpssnode == nonode { time.Sleep(time.Millisecond * 100) } } // then find the node it's connected to for secondpssnode == nonode { log.Debug(fmt.Sprintf("PSS kademlia: Waiting for recipientpeer for %x close to %x ...", common.ByteLabel(nodes[firstpssnode].OverlayAddr()), common.ByteLabel(nodes[fullnodes[1]].OverlayAddr()))) nodes[firstpssnode].Pss.Overlay.Eachc(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { for _, id := range ids { if id.NodeID == p.ID() && id.NodeID != fullnodes[0].NodeID { secondpssnode = id log.Debug(fmt.Sprintf("PSS recipient found; relaynode %v kademlia %v", common.ByteLabel(id.Bytes()), common.ByteLabel(secondpssnode.Bytes()))) } } if secondpssnode == nonode { return true } return false }) if secondpssnode == nonode { time.Sleep(time.Millisecond * 100) } } action = func(ctx context.Context) error { code, _ := vct.GetCode(&pssTestPayload{}) msgbytes, _ := makeMsg(code, &pssTestPayload{ Data: "ping", }) go func() { oaddr := nodes[secondpssnode].OverlayAddr() err := nodes[ids[0]].Pss.Send(oaddr, topic, msgbytes) if err != nil { t.Fatalf("could not send pss: %v", err) } trigger <- ids[0] }() return nil } check = func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } // also need to know if the protocolpeer is set up time.Sleep(time.Millisecond * 100) return <-testpeers[ids[0]].successC, nil //return true, nil } timeout = 10 * time.Second ctx, cancel = context.WithTimeout(context.Background(), timeout) defer cancel() result = simulations.NewSimulation(net).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: []discover.NodeID{ids[0]}, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } t.Log("Simulation Passed:") } func TestPssFullWS(t *testing.T) { // settings for ws servers var srvsendep = "localhost:18546" var srvrecvep = "localhost:18547" var clientrecvok, clientsendok bool var clientrecv, clientsend *rpc.Client var action func(ctx context.Context) error var check func(ctx context.Context, id discover.NodeID) (bool, error) var ctx context.Context var result *simulations.StepResult var timeout time.Duration var cancel context.CancelFunc var firstpssnode, secondpssnode discover.NodeID fullnodes := []discover.NodeID{} vct := protocols.NewCodeMap(protocolName, protocolVersion, ProtocolMaxMsgSize) vct.Register(0, &pssTestPayload{}) topic, _ := MakeTopic(pingTopicName, pingTopicVersion) trigger := make(chan discover.NodeID) testpeers := make(map[discover.NodeID]*pssTestPeer) simnet, nodes := newPssSimulationTester(t, 3, 2, trigger, vct, protocolName, protocolVersion, testpeers) ids := []discover.NodeID{} // ohh risky! but the action for a specific id should come before the expect anyway action = func(ctx context.Context) error { var thinnodeid discover.NodeID for id, node := range nodes { ids = append(ids, id) if _, ok := testpeers[id]; ok { log.Trace(fmt.Sprintf("adding fullnode %x to testpeers %p", common.ByteLabel(id.Bytes()), testpeers)) fullnodes = append(fullnodes, id) node.Pss.Register(topic, node.Pss.GetPingHandler()) srv := rpc.NewServer() for _, rpcapi := range node.apifunc() { srv.RegisterName(rpcapi.Namespace, rpcapi.Service) } ws := srv.WebsocketHandler([]string{"*"}) node.ws = &ws } else { thinnodeid = id } } if err := simnet.Connect(fullnodes[0], thinnodeid); err != nil { return err } if err := simnet.Connect(thinnodeid, fullnodes[1]); err != nil { return err } return nil } check = func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } node, ok := nodes[id] if !ok { return false, fmt.Errorf("unknown node: %s (%v)", id, node) } else { log.Trace(fmt.Sprintf("sim check ok node %v", id)) } return true, nil } timeout = 10 * time.Second ctx, cancel = context.WithTimeout(context.Background(), timeout) result = simulations.NewSimulation(simnet).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: ids, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } cancel() nonode := &adapters.NodeID{} firstpssnode = nonode secondpssnode = nonode // first find a node that we're connected to for firstpssnode == nonode { log.Debug(fmt.Sprintf("Waiting for pss relaypeer for %x close to %x ...", common.ByteLabel(nodes[fullnodes[0]].OverlayAddr()), common.ByteLabel(nodes[fullnodes[1]].OverlayAddr()))) nodes[fullnodes[0]].Pss.Overlay.EachLivePeer(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { for _, id := range ids { if id.NodeID == p.ID() { firstpssnode = id log.Debug(fmt.Sprintf("PSS relay found; relaynode %x", common.ByteLabel(nodes[firstpssnode].OverlayAddr()))) } } if firstpssnode == nonode { return true } return false }) if firstpssnode == nonode { time.Sleep(time.Millisecond * 100) } } // then find the node it's connected to for secondpssnode == nonode { log.Debug(fmt.Sprintf("PSS kademlia: Waiting for recipientpeer for %x close to %x ...", common.ByteLabel(nodes[firstpssnode].OverlayAddr()), common.ByteLabel(nodes[fullnodes[1]].OverlayAddr()))) nodes[firstpssnode].Pss.Overlay.EachConn(nodes[fullnodes[1]].OverlayAddr(), 256, func(p Peer, po int, isprox bool) bool { for _, id := range ids { if id.NodeID == p.ID() && id.NodeID != fullnodes[0].NodeID { secondpssnode = id log.Debug(fmt.Sprintf("PSS recipient found; relaynode %x", common.ByteLabel(nodes[secondpssnode].OverlayAddr()))) } } if secondpssnode == nonode { return true } return false }) if secondpssnode == nonode { time.Sleep(time.Millisecond * 100) } } srvrecvl, err := net.Listen("tcp", srvrecvep) if err != nil { t.Fatalf("Tcp (recv) on %s failed: %v", srvrecvep, err) } go func() { err := http.Serve(srvrecvl, *nodes[fullnodes[1]].ws) if err != nil { t.Fatalf("http serve (recv) on %s failed: %v", srvrecvep, err) } }() srvsendl, err := net.Listen("tcp", srvsendep) if err != nil { t.Fatalf("Tcp (send) on %s failed: %v", srvsendep, err) } go func() { err := http.Serve(srvsendl, *nodes[fullnodes[0]].ws) if err != nil { t.Fatalf("http serve (send) on %s failed: %v", srvrecvep, err) } }() for !clientrecvok { log.Trace("attempting clientrecv connect") clientrecv, err = rpc.DialWebsocket(context.Background(), "ws://"+srvrecvep, "ws://localhost") if err == nil { clientrecvok = true } else { log.Debug("clientrecv failed, retrying", "error", err) time.Sleep(time.Millisecond * 250) } } for !clientsendok { log.Trace("attempting clientsend connect") clientsend, err = rpc.DialWebsocket(context.Background(), "ws://"+srvsendep, "ws://localhost") if err == nil { clientsendok = true } else { log.Debug("clientsend failed, retrying", "error", err) time.Sleep(time.Millisecond * 250) } } trigger = make(chan discover.NodeID) ch := make(chan string) action = func(ctx context.Context) error { go func() { clientrecv.EthSubscribe(ctx, ch, "newMsg", topic) clientsend.Call(nil, "eth_sendRaw", nodes[secondpssnode].Pss.Overlay.BaseAddr(), topic, []byte("ping")) trigger <- secondpssnode }() return nil } check = func(ctx context.Context, id discover.NodeID) (bool, error) { select { case <-ctx.Done(): return false, ctx.Err() default: } select { case msg := <-ch: log.Trace(fmt.Sprintf("notify!: %v", msg)) case <-time.NewTimer(time.Second).C: log.Trace(fmt.Sprintf("no notifies :'(")) } // also need to know if the protocolpeer is set up return true, nil } timeout = 10 * time.Second ctx, cancel = context.WithTimeout(context.Background(), timeout) defer cancel() result = simulations.NewSimulation(simnet).Run(ctx, &simulations.Step{ Action: action, Trigger: trigger, Expect: &simulations.Expectation{ Nodes: []discover.NodeID{secondpssnode}, Check: check, }, }) if result.Error != nil { t.Fatalf("simulation failed: %s", result.Error) } t.Log("Simulation Passed:") } // test framework below // numnodes: how many nodes to create // pssnodeidx: on which node indices to start the pss // net: the simulated network // trigger: hook needed for simulation event reporting // vct: codemap for virtual protocol // name: name for virtual protocol (and pss topic) // version: name for virtual protocol (and pss topic) // testpeers: pss-specific peers, with hook needed for simulation event reporting // the simulation tester constructor is currently a hack to fit previous code with later stack using node.Services to start SimNodes func newPssSimulationTester(t *testing.T, numnodes int, numfullnodes int, trigger chan discover.NodeID, vct *protocols.CodeMap, name string, version int, testpeers map[discover.NodeID]*pssTestPeer) (*simulations.Network, map[discover.NodeID]*pssTestNode) { topic, _ := MakeTopic(name, version) nodes := make(map[discover.NodeID]*pssTestNode, numnodes) psss := make(map[discover.NodeID]*Pss) var simnet *simulations.Network serviceFunc := func(id discover.NodeID) node.Service { node := &pssTestNode{ Pss: psss[id], Hive: nil, id: id, network: simnet, trigger: trigger, ct: vct, apifunc: func() []rpc.API { return nil }, expectC: make(chan []int), } // set up handlers for the encapsulating PssMsg var handlefunc func(interface{}) error addr := NewPeerAddrFromNodeID(id) if testpeers[id] != nil { handlefunc = makePssHandleProtocol(psss[id]) log.Trace(fmt.Sprintf("Making full protocol id %x addr %x (testpeers %p)", common.ByteLabel(id.Bytes()), common.ByteLabel(addr.Over()), testpeers)) } else { handlefunc = makePssHandleForward(psss[id]) } // protocols are now registered by invoking node services // since adapters.SimNode implements p2p.Server, needed for the services to start, we use this as a convenience wrapper testservice := newPssTestService(t, handlefunc, node) // the network sim wants a adapters.NodeAdapter, so we pass back to it a SimNode // this is the SimNode member of the testNode initialized above, but assigned through the service start // that is so say: node == testservice.node, but we access it as a member of testservice below for clarity (to the extent that this can be clear) nodes[id] = testservice.node testservice.node.apifunc = testservice.APIs return testservice } adapter := adapters.NewSimAdapter(map[string]adapters.ServiceFunc{"pss": serviceFunc}) simnet = simulations.NewNetwork(adapter, &simulations.NetworkConfig{ ID: "0", Backend: true, }) configs := make([]*adapters.NodeConfig, numnodes) for i := 0; i < numnodes; i++ { configs[i] = adapters.RandomNodeConfig() configs[i].Service = "pss" } for i, conf := range configs { addr := NewPeerAddrFromNodeID(conf.ID) psss[conf.ID] = makePss(addr.Over()) if i < numfullnodes { tp := &pssTestPeer{ Peer: &protocols.Peer{ Peer: &p2p.Peer{}, }, successC: make(chan bool), resultC: make(chan int), } testpeers[conf.ID] = tp targetprotocol := makeCustomProtocol(name, version, vct, testpeers[conf.ID]) pssprotocol := NewPssProtocol(psss[conf.ID], &topic, vct, targetprotocol) psss[conf.ID].Register(topic, pssprotocol.GetHandler()) } if err := simnet.NewNodeWithConfig(conf); err != nil { t.Fatalf("error creating node %s: %s", conf.ID.Label(), err) } if err := simnet.Start(conf.ID); err != nil { t.Fatalf("error starting node %s: %s", conf.ID.Label(), err) } } return simnet, nodes } func makePss(addr []byte) *Pss { // set up storage cachedir, err := ioutil.TempDir("", "pss-cache") if err != nil { log.Error("create pss cache tmpdir failed", "error", err) os.Exit(1) } dpa, err := storage.NewLocalDPA(cachedir) if err != nil { log.Error("local dpa creation failed", "error", err) os.Exit(1) } // cannot use pyramidchunker as it still lacks joinfunc TestPssRegisterHandler(t *testing.T) { addr := RandomAddr() ps := newTestPss(addr.UnderlayAddr()) from := RandomAddr() payload := []byte("payload") topic := NewTopic(protocolName, protocolVersion) checkMsg := func(msg []byte, p *p2p.Peer, sender []byte) error { if !bytes.Equal(from.OverlayAddr(), sender) { return fmt.Errorf("sender mismatch. expected %x, got %x", from.OverlayAddr(), sender) } if !bytes.Equal(msg, payload) { return fmt.Errorf("sender mismatch. expected %x, got %x", msg, payload) } if !bytes.Equal(from.UnderlayAddr(), p.ID()) { return fmt.Errorf("sender mismatch. expected %x, got %x", from.UnderlayAddr(), p.ID()) } } deregister := ps.Register(topic, checkMsg) pssmsg := &PssMsg{Data: NewPssEnvelope(from, topic, payload)} err = ps.Process(pssmsg) if err != nil { t.Fatal(err) } var i int err = ps.Process(&PssMsg{Data: NewPssEnvelope(from, []byte("topic"), payload)}) expErr := "" if err == nil || err.Error() != expErr { t.Fatalf("unhandled topic expected %v, got %v", expErr, err) } deregister2 := ps.Register(topic, func(msg []byte, p *p2p.Peer, sender []byte) error { i++; return nil }) ps.Process(pssmsg) if err != nil { t.Fatal(err) } if i != 1 { t.Fatalf("second registerer handler did not run") } deregister() deregister2() err = ps.Process(&PssMsg{Data: NewPssEnvelope(from, topic, payload)}) expErr = "" if err == nil || err.Error() != expErr { t.Fatalf("reregister handler expected %v, got %v", expErr, err) } } // dpa.Chunker = storage.NewPyramidChunker(storage.NewChunkerParams()) kp := network.NewKadParams() kp.MinProxBinSize = 3 pp := NewPssParams() overlay := network.NewKademlia(addr, kp) ps := NewPss(overlay, dpa, pp) //overlay.Prune(time.Tick(time.Millisecond * 250)) return ps } func makeCustomProtocol(name string, version int, ct *protocols.CodeMap, testpeer *pssTestPeer) *p2p.Protocol { run := func(p *protocols.Peer) error { log.Trace(fmt.Sprintf("running pss vprotocol on peer %v", p)) if testpeer == nil { testpeer = &pssTestPeer{} } testpeer.Peer = p p.Register(&pssTestPayload{}, testpeer.SimpleHandlePssPayload) err := p.Run() return err } return protocols.NewProtocol(name, uint(version), run, ct, nil, nil) } func makePssHandleForward(ps *Pss) func(msg interface{}) error { // for the simple check it passes on the message if it's not for us return func(msg interface{}) error { pssmsg := msg.(*PssMsg) if ps.IsSelfRecipient(pssmsg) { log.Trace("pss for us .. yay!") } else { log.Trace("passing on pss") return ps.Forward(pssmsg) } return nil } } func makePssHandleProtocol(ps *Pss) func(msg interface{}) error { return func(msg interface{}) error { pssmsg := msg.(*PssMsg) if ps.IsSelfRecipient(pssmsg) { log.Trace("pss for us ... let's process!") env := pssmsg.Payload umsg := env.Payload // this will be rlp encrypted f := ps.GetHandler(env.Topic) if f == nil { return fmt.Errorf("No registered handler for topic '%s'", env.Topic) } nid := adapters.NewNodeID(env.SenderUAddr) p := p2p.NewPeer(nid.NodeID, fmt.Sprintf("%x", common.ByteLabel(nid.Bytes())), []p2p.Cap{}) return f(umsg, p, env.SenderOAddr) } else { log.Trace("pss was for someone else :'(") return ps.Forward(pssmsg) } return nil } } // echoes an incoming message // it comes in through // Any pointer receiver that has protocols.Peer func (ptp *pssTestPeer) SimpleHandlePssPayload(msg interface{}) error { pmsg := msg.(*pssTestPayload) log.Trace(fmt.Sprintf("pssTestPayloadhandler got message %v", pmsg)) if pmsg.Data == "ping" { pmsg.Data = "pong" log.Trace(fmt.Sprintf("pssTestPayloadhandler reply %v", pmsg)) ptp.Send(pmsg) } else if pmsg.Data == "pong" { ptp.successC <- true } else { res, err := strconv.Atoi(pmsg.Data) if err != nil { log.Trace(fmt.Sprintf("pssTestPayloadhandlererr %v", err)) ptp.successC <- false } else { log.Trace(fmt.Sprintf("pssTestPayloadhandler sending %d on chan", pmsg)) ptp.successC <- true ptp.resultC <- res } } return nil } */