465 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			465 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 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// glog.SetV(6) | ||
|  | 	// glog.SetToStderr(true) | ||
|  | 
 | ||
|  | 	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 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// glog.SetV(6) | ||
|  | 	// glog.SetToStderr(true) | ||
|  | 
 | ||
|  | 	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>") | ||
|  | 	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) | ||
|  | 		}) | ||
|  | 	} | ||
|  | } |