457 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			457 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2016 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 <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| package discv5
 | |
| 
 | |
| import (
 | |
| 	"crypto/ecdsa"
 | |
| 	"encoding/binary"
 | |
| 	"fmt"
 | |
| 	"math/rand"
 | |
| 	"net"
 | |
| 	"strconv"
 | |
| 	"sync"
 | |
| 	"sync/atomic"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/ethereum/go-ethereum/common"
 | |
| )
 | |
| 
 | |
| // In this test, nodes try to randomly resolve each other.
 | |
| func TestSimRandomResolve(t *testing.T) {
 | |
| 	t.Skip("boring")
 | |
| 	if runWithPlaygroundTime(t) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	sim := newSimulation()
 | |
| 	bootnode := sim.launchNode(false)
 | |
| 
 | |
| 	// A new node joins every 10s.
 | |
| 	launcher := time.NewTicker(10 * time.Second)
 | |
| 	go func() {
 | |
| 		for range launcher.C {
 | |
| 			net := sim.launchNode(false)
 | |
| 			go randomResolves(t, sim, net)
 | |
| 			if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
 | |
| 				panic(err)
 | |
| 			}
 | |
| 			fmt.Printf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16])
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	time.Sleep(3 * time.Hour)
 | |
| 	launcher.Stop()
 | |
| 	sim.shutdown()
 | |
| 	sim.printStats()
 | |
| }
 | |
| 
 | |
| func TestSimTopics(t *testing.T) {
 | |
| 	t.Skip("NaCl test")
 | |
| 	if runWithPlaygroundTime(t) {
 | |
| 		return
 | |
| 	}
 | |
| 	sim := newSimulation()
 | |
| 	bootnode := sim.launchNode(false)
 | |
| 
 | |
| 	go func() {
 | |
| 		nets := make([]*Network, 1024)
 | |
| 		for i := range nets {
 | |
| 			net := sim.launchNode(false)
 | |
| 			nets[i] = net
 | |
| 			if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
 | |
| 				panic(err)
 | |
| 			}
 | |
| 			time.Sleep(time.Second * 5)
 | |
| 		}
 | |
| 
 | |
| 		for i, net := range nets {
 | |
| 			if i < 256 {
 | |
| 				stop := make(chan struct{})
 | |
| 				go net.RegisterTopic(testTopic, stop)
 | |
| 				go func() {
 | |
| 					//time.Sleep(time.Second * 36000)
 | |
| 					time.Sleep(time.Second * 40000)
 | |
| 					close(stop)
 | |
| 				}()
 | |
| 				time.Sleep(time.Millisecond * 100)
 | |
| 			}
 | |
| 			//			time.Sleep(time.Second * 10)
 | |
| 			//time.Sleep(time.Second)
 | |
| 			/*if i%500 == 499 {
 | |
| 				time.Sleep(time.Second * 9501)
 | |
| 			} else {
 | |
| 				time.Sleep(time.Second)
 | |
| 			}*/
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// A new node joins every 10s.
 | |
| 	/*	launcher := time.NewTicker(5 * time.Second)
 | |
| 		cnt := 0
 | |
| 		var printNet *Network
 | |
| 		go func() {
 | |
| 			for range launcher.C {
 | |
| 				cnt++
 | |
| 				if cnt <= 1000 {
 | |
| 					log := false //(cnt == 500)
 | |
| 					net := sim.launchNode(log)
 | |
| 					if log {
 | |
| 						printNet = net
 | |
| 					}
 | |
| 					if cnt > 500 {
 | |
| 						go net.RegisterTopic(testTopic, nil)
 | |
| 					}
 | |
| 					if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
 | |
| 						panic(err)
 | |
| 					}
 | |
| 				}
 | |
| 				//fmt.Printf("launched @ %v: %x\n", time.Now(), net.Self().ID[:16])
 | |
| 			}
 | |
| 		}()
 | |
| 	*/
 | |
| 	time.Sleep(55000 * time.Second)
 | |
| 	//launcher.Stop()
 | |
| 	sim.shutdown()
 | |
| 	//sim.printStats()
 | |
| 	//printNet.log.printLogs()
 | |
| }
 | |
| 
 | |
| /*func testHierarchicalTopics(i int) []Topic {
 | |
| 	digits := strconv.FormatInt(int64(256+i/4), 4)
 | |
| 	res := make([]Topic, 5)
 | |
| 	for i, _ := range res {
 | |
| 		res[i] = Topic("foo" + digits[1:i+1])
 | |
| 	}
 | |
| 	return res
 | |
| }*/
 | |
| 
 | |
| func testHierarchicalTopics(i int) []Topic {
 | |
| 	digits := strconv.FormatInt(int64(128+i/8), 2)
 | |
| 	res := make([]Topic, 8)
 | |
| 	for i := range res {
 | |
| 		res[i] = Topic("foo" + digits[1:i+1])
 | |
| 	}
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| func TestSimTopicHierarchy(t *testing.T) {
 | |
| 	t.Skip("NaCl test")
 | |
| 	if runWithPlaygroundTime(t) {
 | |
| 		return
 | |
| 	}
 | |
| 	sim := newSimulation()
 | |
| 	bootnode := sim.launchNode(false)
 | |
| 
 | |
| 	go func() {
 | |
| 		nets := make([]*Network, 1024)
 | |
| 		for i := range nets {
 | |
| 			net := sim.launchNode(false)
 | |
| 			nets[i] = net
 | |
| 			if err := net.SetFallbackNodes([]*Node{bootnode.Self()}); err != nil {
 | |
| 				panic(err)
 | |
| 			}
 | |
| 			time.Sleep(time.Second * 5)
 | |
| 		}
 | |
| 
 | |
| 		stop := make(chan struct{})
 | |
| 		for i, net := range nets {
 | |
| 			//if i < 256 {
 | |
| 			for _, topic := range testHierarchicalTopics(i)[:5] {
 | |
| 				//fmt.Println("reg", topic)
 | |
| 				go net.RegisterTopic(topic, stop)
 | |
| 			}
 | |
| 			time.Sleep(time.Millisecond * 100)
 | |
| 			//}
 | |
| 		}
 | |
| 		time.Sleep(time.Second * 90000)
 | |
| 		close(stop)
 | |
| 	}()
 | |
| 
 | |
| 	time.Sleep(100000 * time.Second)
 | |
| 	sim.shutdown()
 | |
| }
 | |
| 
 | |
| func randomResolves(t *testing.T, s *simulation, net *Network) {
 | |
| 	randtime := func() time.Duration {
 | |
| 		return time.Duration(rand.Intn(50)+20) * time.Second
 | |
| 	}
 | |
| 	lookup := func(target NodeID) bool {
 | |
| 		result := net.Resolve(target)
 | |
| 		return result != nil && result.ID == target
 | |
| 	}
 | |
| 
 | |
| 	timer := time.NewTimer(randtime())
 | |
| 	for {
 | |
| 		select {
 | |
| 		case <-timer.C:
 | |
| 			target := s.randomNode().Self().ID
 | |
| 			if !lookup(target) {
 | |
| 				t.Errorf("node %x: target %x not found", net.Self().ID[:8], target[:8])
 | |
| 			}
 | |
| 			timer.Reset(randtime())
 | |
| 		case <-net.closed:
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type simulation struct {
 | |
| 	mu      sync.RWMutex
 | |
| 	nodes   map[NodeID]*Network
 | |
| 	nodectr uint32
 | |
| }
 | |
| 
 | |
| func newSimulation() *simulation {
 | |
| 	return &simulation{nodes: make(map[NodeID]*Network)}
 | |
| }
 | |
| 
 | |
| func (s *simulation) shutdown() {
 | |
| 	s.mu.RLock()
 | |
| 	alive := make([]*Network, 0, len(s.nodes))
 | |
| 	for _, n := range s.nodes {
 | |
| 		alive = append(alive, n)
 | |
| 	}
 | |
| 	defer s.mu.RUnlock()
 | |
| 
 | |
| 	for _, n := range alive {
 | |
| 		n.Close()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *simulation) printStats() {
 | |
| 	s.mu.Lock()
 | |
| 	defer s.mu.Unlock()
 | |
| 	fmt.Println("node counter:", s.nodectr)
 | |
| 	fmt.Println("alive nodes:", len(s.nodes))
 | |
| 
 | |
| 	// for _, n := range s.nodes {
 | |
| 	// 	fmt.Printf("%x\n", n.tab.self.ID[:8])
 | |
| 	// 	transport := n.conn.(*simTransport)
 | |
| 	// 	fmt.Println("   joined:", transport.joinTime)
 | |
| 	// 	fmt.Println("   sends:", transport.hashctr)
 | |
| 	// 	fmt.Println("   table size:", n.tab.count)
 | |
| 	// }
 | |
| 
 | |
| 	/*for _, n := range s.nodes {
 | |
| 		fmt.Println()
 | |
| 		fmt.Printf("*** Node %x\n", n.tab.self.ID[:8])
 | |
| 		n.log.printLogs()
 | |
| 	}*/
 | |
| 
 | |
| }
 | |
| 
 | |
| func (s *simulation) randomNode() *Network {
 | |
| 	s.mu.Lock()
 | |
| 	defer s.mu.Unlock()
 | |
| 
 | |
| 	n := rand.Intn(len(s.nodes))
 | |
| 	for _, net := range s.nodes {
 | |
| 		if n == 0 {
 | |
| 			return net
 | |
| 		}
 | |
| 		n--
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *simulation) launchNode(log bool) *Network {
 | |
| 	var (
 | |
| 		num = s.nodectr
 | |
| 		key = newkey()
 | |
| 		id  = PubkeyID(&key.PublicKey)
 | |
| 		ip  = make(net.IP, 4)
 | |
| 	)
 | |
| 	s.nodectr++
 | |
| 	binary.BigEndian.PutUint32(ip, num)
 | |
| 	ip[0] = 10
 | |
| 	addr := &net.UDPAddr{IP: ip, Port: 30303}
 | |
| 
 | |
| 	transport := &simTransport{joinTime: time.Now(), sender: id, senderAddr: addr, sim: s, priv: key}
 | |
| 	net, err := newNetwork(transport, key.PublicKey, nil, "<no database>", nil)
 | |
| 	if err != nil {
 | |
| 		panic("cannot launch new node: " + err.Error())
 | |
| 	}
 | |
| 
 | |
| 	s.mu.Lock()
 | |
| 	s.nodes[id] = net
 | |
| 	s.mu.Unlock()
 | |
| 
 | |
| 	return net
 | |
| }
 | |
| 
 | |
| func (s *simulation) dropNode(id NodeID) {
 | |
| 	s.mu.Lock()
 | |
| 	n := s.nodes[id]
 | |
| 	delete(s.nodes, id)
 | |
| 	s.mu.Unlock()
 | |
| 
 | |
| 	n.Close()
 | |
| }
 | |
| 
 | |
| type simTransport struct {
 | |
| 	joinTime   time.Time
 | |
| 	sender     NodeID
 | |
| 	senderAddr *net.UDPAddr
 | |
| 	sim        *simulation
 | |
| 	hashctr    uint64
 | |
| 	priv       *ecdsa.PrivateKey
 | |
| }
 | |
| 
 | |
| func (st *simTransport) localAddr() *net.UDPAddr {
 | |
| 	return st.senderAddr
 | |
| }
 | |
| 
 | |
| func (st *simTransport) Close() {}
 | |
| 
 | |
| func (st *simTransport) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) {
 | |
| 	hash = st.nextHash()
 | |
| 	var raw []byte
 | |
| 	if ptype == pongPacket {
 | |
| 		var err error
 | |
| 		raw, _, err = encodePacket(st.priv, byte(ptype), data)
 | |
| 		if err != nil {
 | |
| 			panic(err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	st.sendPacket(remote.ID, ingressPacket{
 | |
| 		remoteID:   st.sender,
 | |
| 		remoteAddr: st.senderAddr,
 | |
| 		hash:       hash,
 | |
| 		ev:         ptype,
 | |
| 		data:       data,
 | |
| 		rawData:    raw,
 | |
| 	})
 | |
| 	return hash
 | |
| }
 | |
| 
 | |
| func (st *simTransport) sendPing(remote *Node, remoteAddr *net.UDPAddr, topics []Topic) []byte {
 | |
| 	hash := st.nextHash()
 | |
| 	st.sendPacket(remote.ID, ingressPacket{
 | |
| 		remoteID:   st.sender,
 | |
| 		remoteAddr: st.senderAddr,
 | |
| 		hash:       hash,
 | |
| 		ev:         pingPacket,
 | |
| 		data: &ping{
 | |
| 			Version:    4,
 | |
| 			From:       rpcEndpoint{IP: st.senderAddr.IP, UDP: uint16(st.senderAddr.Port), TCP: 30303},
 | |
| 			To:         rpcEndpoint{IP: remoteAddr.IP, UDP: uint16(remoteAddr.Port), TCP: 30303},
 | |
| 			Expiration: uint64(time.Now().Unix() + int64(expiration)),
 | |
| 			Topics:     topics,
 | |
| 		},
 | |
| 	})
 | |
| 	return hash
 | |
| }
 | |
| 
 | |
| func (st *simTransport) sendPong(remote *Node, pingHash []byte) {
 | |
| 	raddr := remote.addr()
 | |
| 
 | |
| 	st.sendPacket(remote.ID, ingressPacket{
 | |
| 		remoteID:   st.sender,
 | |
| 		remoteAddr: st.senderAddr,
 | |
| 		hash:       st.nextHash(),
 | |
| 		ev:         pongPacket,
 | |
| 		data: &pong{
 | |
| 			To:         rpcEndpoint{IP: raddr.IP, UDP: uint16(raddr.Port), TCP: 30303},
 | |
| 			ReplyTok:   pingHash,
 | |
| 			Expiration: uint64(time.Now().Unix() + int64(expiration)),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (st *simTransport) sendFindnodeHash(remote *Node, target common.Hash) {
 | |
| 	st.sendPacket(remote.ID, ingressPacket{
 | |
| 		remoteID:   st.sender,
 | |
| 		remoteAddr: st.senderAddr,
 | |
| 		hash:       st.nextHash(),
 | |
| 		ev:         findnodeHashPacket,
 | |
| 		data: &findnodeHash{
 | |
| 			Target:     target,
 | |
| 			Expiration: uint64(time.Now().Unix() + int64(expiration)),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (st *simTransport) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) {
 | |
| 	//fmt.Println("send", topics, pong)
 | |
| 	st.sendPacket(remote.ID, ingressPacket{
 | |
| 		remoteID:   st.sender,
 | |
| 		remoteAddr: st.senderAddr,
 | |
| 		hash:       st.nextHash(),
 | |
| 		ev:         topicRegisterPacket,
 | |
| 		data: &topicRegister{
 | |
| 			Topics: topics,
 | |
| 			Idx:    uint(idx),
 | |
| 			Pong:   pong,
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (st *simTransport) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) {
 | |
| 	rnodes := make([]rpcNode, len(nodes))
 | |
| 	for i := range nodes {
 | |
| 		rnodes[i] = nodeToRPC(nodes[i])
 | |
| 	}
 | |
| 	st.sendPacket(remote.ID, ingressPacket{
 | |
| 		remoteID:   st.sender,
 | |
| 		remoteAddr: st.senderAddr,
 | |
| 		hash:       st.nextHash(),
 | |
| 		ev:         topicNodesPacket,
 | |
| 		data:       &topicNodes{Echo: queryHash, Nodes: rnodes},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (st *simTransport) sendNeighbours(remote *Node, nodes []*Node) {
 | |
| 	// TODO: send multiple packets
 | |
| 	rnodes := make([]rpcNode, len(nodes))
 | |
| 	for i := range nodes {
 | |
| 		rnodes[i] = nodeToRPC(nodes[i])
 | |
| 	}
 | |
| 	st.sendPacket(remote.ID, ingressPacket{
 | |
| 		remoteID:   st.sender,
 | |
| 		remoteAddr: st.senderAddr,
 | |
| 		hash:       st.nextHash(),
 | |
| 		ev:         neighborsPacket,
 | |
| 		data: &neighbors{
 | |
| 			Nodes:      rnodes,
 | |
| 			Expiration: uint64(time.Now().Unix() + int64(expiration)),
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func (st *simTransport) nextHash() []byte {
 | |
| 	v := atomic.AddUint64(&st.hashctr, 1)
 | |
| 	var hash common.Hash
 | |
| 	binary.BigEndian.PutUint64(hash[:], v)
 | |
| 	return hash[:]
 | |
| }
 | |
| 
 | |
| const packetLoss = 0 // 1/1000
 | |
| 
 | |
| func (st *simTransport) sendPacket(remote NodeID, p ingressPacket) {
 | |
| 	if rand.Int31n(1000) >= packetLoss {
 | |
| 		st.sim.mu.RLock()
 | |
| 		recipient := st.sim.nodes[remote]
 | |
| 		st.sim.mu.RUnlock()
 | |
| 
 | |
| 		time.AfterFunc(200*time.Millisecond, func() {
 | |
| 			recipient.reqReadPacket(p)
 | |
| 		})
 | |
| 	}
 | |
| }
 |