| 
									
										
										
										
											2016-11-09 02:01:56 +01:00
										 |  |  | // Copyright 2016 The go-ethereum Authors | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | // 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 ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"encoding/binary" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"math" | 
					
						
							|  |  |  | 	"math/rand" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/common" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/common/mclock" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/crypto" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	ticketTimeBucketLen = time.Minute | 
					
						
							|  |  |  | 	timeWindow          = 10 // * ticketTimeBucketLen | 
					
						
							|  |  |  | 	wantTicketsInWindow = 10 | 
					
						
							|  |  |  | 	collectFrequency    = time.Second * 30 | 
					
						
							|  |  |  | 	registerFrequency   = time.Second * 60 | 
					
						
							|  |  |  | 	maxCollectDebt      = 10 | 
					
						
							|  |  |  | 	maxRegisterDebt     = 5 | 
					
						
							|  |  |  | 	keepTicketConst     = time.Minute * 10 | 
					
						
							|  |  |  | 	keepTicketExp       = time.Minute * 5 | 
					
						
							|  |  |  | 	targetWaitTime      = time.Minute * 10 | 
					
						
							|  |  |  | 	topicQueryTimeout   = time.Second * 5 | 
					
						
							|  |  |  | 	topicQueryResend    = time.Minute | 
					
						
							|  |  |  | 	// topic radius detection | 
					
						
							|  |  |  | 	maxRadius           = 0xffffffffffffffff | 
					
						
							|  |  |  | 	radiusTC            = time.Minute * 20 | 
					
						
							|  |  |  | 	radiusBucketsPerBit = 8 | 
					
						
							|  |  |  | 	minSlope            = 1 | 
					
						
							|  |  |  | 	minPeakSize         = 40 | 
					
						
							|  |  |  | 	maxNoAdjust         = 20 | 
					
						
							|  |  |  | 	lookupWidth         = 8 | 
					
						
							|  |  |  | 	minRightSum         = 20 | 
					
						
							|  |  |  | 	searchForceQuery    = 4 | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // timeBucket represents absolute monotonic time in minutes. | 
					
						
							|  |  |  | // It is used as the index into the per-topic ticket buckets. | 
					
						
							|  |  |  | type timeBucket int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type ticket struct { | 
					
						
							|  |  |  | 	topics  []Topic | 
					
						
							|  |  |  | 	regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The serial number that was issued by the server. | 
					
						
							|  |  |  | 	serial uint32 | 
					
						
							|  |  |  | 	// Used by registrar, tracks absolute time when the ticket was created. | 
					
						
							|  |  |  | 	issueTime mclock.AbsTime | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Fields used only by registrants | 
					
						
							|  |  |  | 	node   *Node  // the registrar node that signed this ticket | 
					
						
							|  |  |  | 	refCnt int    // tracks number of topics that will be registered using this ticket | 
					
						
							|  |  |  | 	pong   []byte // encoded pong packet signed by the registrar | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ticketRef refers to a single topic in a ticket. | 
					
						
							|  |  |  | type ticketRef struct { | 
					
						
							|  |  |  | 	t   *ticket | 
					
						
							|  |  |  | 	idx int // index of the topic in t.topics and t.regTime | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ref ticketRef) topic() Topic { | 
					
						
							|  |  |  | 	return ref.t.topics[ref.idx] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (ref ticketRef) topicRegTime() mclock.AbsTime { | 
					
						
							|  |  |  | 	return ref.t.regTime[ref.idx] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) { | 
					
						
							|  |  |  | 	wps := p.data.(*pong).WaitPeriods | 
					
						
							|  |  |  | 	if len(topics) != len(wps) { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if rlpHash(topics) != p.data.(*pong).TopicHash { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("bad topic hash") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t := &ticket{ | 
					
						
							|  |  |  | 		issueTime: localTime, | 
					
						
							|  |  |  | 		node:      node, | 
					
						
							|  |  |  | 		topics:    topics, | 
					
						
							|  |  |  | 		pong:      p.rawData, | 
					
						
							|  |  |  | 		regTime:   make([]mclock.AbsTime, len(wps)), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Convert wait periods to local absolute time. | 
					
						
							|  |  |  | 	for i, wp := range wps { | 
					
						
							|  |  |  | 		t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return t, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func ticketToPong(t *ticket, pong *pong) { | 
					
						
							|  |  |  | 	pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second)) | 
					
						
							|  |  |  | 	pong.TopicHash = rlpHash(t.topics) | 
					
						
							|  |  |  | 	pong.TicketSerial = t.serial | 
					
						
							|  |  |  | 	pong.WaitPeriods = make([]uint32, len(t.regTime)) | 
					
						
							|  |  |  | 	for i, regTime := range t.regTime { | 
					
						
							|  |  |  | 		pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type ticketStore struct { | 
					
						
							|  |  |  | 	// radius detector and target address generator | 
					
						
							|  |  |  | 	// exists for both searched and registered topics | 
					
						
							|  |  |  | 	radius map[Topic]*topicRadius | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Contains buckets (for each absolute minute) of tickets | 
					
						
							|  |  |  | 	// that can be used in that minute. | 
					
						
							|  |  |  | 	// This is only set if the topic is being registered. | 
					
						
							|  |  |  | 	tickets     map[Topic]topicTickets | 
					
						
							|  |  |  | 	regtopics   []Topic | 
					
						
							|  |  |  | 	nodes       map[*Node]*ticket | 
					
						
							|  |  |  | 	nodeLastReq map[*Node]reqInfo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lastBucketFetched timeBucket | 
					
						
							|  |  |  | 	nextTicketCached  *ticketRef | 
					
						
							|  |  |  | 	nextTicketReg     mclock.AbsTime | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	searchTopicMap        map[Topic]searchTopic | 
					
						
							|  |  |  | 	nextTopicQueryCleanup mclock.AbsTime | 
					
						
							|  |  |  | 	queriesSent           map[*Node]map[common.Hash]sentQuery | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type searchTopic struct { | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 	foundChn chan<- *Node | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type sentQuery struct { | 
					
						
							|  |  |  | 	sent   mclock.AbsTime | 
					
						
							|  |  |  | 	lookup lookupInfo | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type topicTickets struct { | 
					
						
							|  |  |  | 	buckets             map[timeBucket][]ticketRef | 
					
						
							|  |  |  | 	nextLookup, nextReg mclock.AbsTime | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newTicketStore() *ticketStore { | 
					
						
							|  |  |  | 	return &ticketStore{ | 
					
						
							|  |  |  | 		radius:         make(map[Topic]*topicRadius), | 
					
						
							|  |  |  | 		tickets:        make(map[Topic]topicTickets), | 
					
						
							|  |  |  | 		nodes:          make(map[*Node]*ticket), | 
					
						
							|  |  |  | 		nodeLastReq:    make(map[*Node]reqInfo), | 
					
						
							|  |  |  | 		searchTopicMap: make(map[Topic]searchTopic), | 
					
						
							|  |  |  | 		queriesSent:    make(map[*Node]map[common.Hash]sentQuery), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // addTopic starts tracking a topic. If register is true, | 
					
						
							|  |  |  | // the local node will register the topic and tickets will be collected. | 
					
						
							|  |  |  | func (s *ticketStore) addTopic(t Topic, register bool) { | 
					
						
							|  |  |  | 	debugLog(fmt.Sprintf(" addTopic(%v, %v)", t, register)) | 
					
						
							|  |  |  | 	if s.radius[t] == nil { | 
					
						
							|  |  |  | 		s.radius[t] = newTopicRadius(t) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if register && s.tickets[t].buckets == nil { | 
					
						
							|  |  |  | 		s.tickets[t] = topicTickets{buckets: make(map[timeBucket][]ticketRef)} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) { | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 	s.addTopic(t, false) | 
					
						
							|  |  |  | 	if s.searchTopicMap[t].foundChn == nil { | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 		s.searchTopicMap[t] = searchTopic{foundChn: foundChn} | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) removeSearchTopic(t Topic) { | 
					
						
							|  |  |  | 	if st := s.searchTopicMap[t]; st.foundChn != nil { | 
					
						
							|  |  |  | 		delete(s.searchTopicMap, t) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // removeRegisterTopic deletes all tickets for the given topic. | 
					
						
							|  |  |  | func (s *ticketStore) removeRegisterTopic(topic Topic) { | 
					
						
							|  |  |  | 	debugLog(fmt.Sprintf(" removeRegisterTopic(%v)", topic)) | 
					
						
							|  |  |  | 	for _, list := range s.tickets[topic].buckets { | 
					
						
							|  |  |  | 		for _, ref := range list { | 
					
						
							|  |  |  | 			ref.t.refCnt-- | 
					
						
							|  |  |  | 			if ref.t.refCnt == 0 { | 
					
						
							|  |  |  | 				delete(s.nodes, ref.t.node) | 
					
						
							|  |  |  | 				delete(s.nodeLastReq, ref.t.node) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	delete(s.tickets, topic) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) regTopicSet() []Topic { | 
					
						
							|  |  |  | 	topics := make([]Topic, 0, len(s.tickets)) | 
					
						
							|  |  |  | 	for topic := range s.tickets { | 
					
						
							|  |  |  | 		topics = append(topics, topic) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return topics | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // nextRegisterLookup returns the target of the next lookup for ticket collection. | 
					
						
							|  |  |  | func (s *ticketStore) nextRegisterLookup() (lookup lookupInfo, delay time.Duration) { | 
					
						
							|  |  |  | 	debugLog("nextRegisterLookup()") | 
					
						
							|  |  |  | 	firstTopic, ok := s.iterRegTopics() | 
					
						
							|  |  |  | 	for topic := firstTopic; ok; { | 
					
						
							|  |  |  | 		debugLog(fmt.Sprintf(" checking topic %v, len(s.tickets[topic]) = %d", topic, len(s.tickets[topic].buckets))) | 
					
						
							|  |  |  | 		if s.tickets[topic].buckets != nil && s.needMoreTickets(topic) { | 
					
						
							|  |  |  | 			next := s.radius[topic].nextTarget(false) | 
					
						
							|  |  |  | 			debugLog(fmt.Sprintf(" %x 1s", next.target[:8])) | 
					
						
							|  |  |  | 			return next, 100 * time.Millisecond | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		topic, ok = s.iterRegTopics() | 
					
						
							|  |  |  | 		if topic == firstTopic { | 
					
						
							|  |  |  | 			break // We have checked all topics. | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	debugLog(" null, 40s") | 
					
						
							|  |  |  | 	return lookupInfo{}, 40 * time.Second | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo { | 
					
						
							|  |  |  | 	tr := s.radius[topic] | 
					
						
							|  |  |  | 	target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery) | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 	if target.radiusLookup { | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 		tr.radiusLookupCnt++ | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 		tr.radiusLookupCnt = 0 | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return target | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // iterRegTopics returns topics to register in arbitrary order. | 
					
						
							|  |  |  | // The second return value is false if there are no topics. | 
					
						
							|  |  |  | func (s *ticketStore) iterRegTopics() (Topic, bool) { | 
					
						
							|  |  |  | 	debugLog("iterRegTopics()") | 
					
						
							|  |  |  | 	if len(s.regtopics) == 0 { | 
					
						
							|  |  |  | 		if len(s.tickets) == 0 { | 
					
						
							|  |  |  | 			debugLog(" false") | 
					
						
							|  |  |  | 			return "", false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Refill register list. | 
					
						
							|  |  |  | 		for t := range s.tickets { | 
					
						
							|  |  |  | 			s.regtopics = append(s.regtopics, t) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	topic := s.regtopics[len(s.regtopics)-1] | 
					
						
							|  |  |  | 	s.regtopics = s.regtopics[:len(s.regtopics)-1] | 
					
						
							|  |  |  | 	debugLog(" " + string(topic) + " true") | 
					
						
							|  |  |  | 	return topic, true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) needMoreTickets(t Topic) bool { | 
					
						
							|  |  |  | 	return s.tickets[t].nextLookup < mclock.Now() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ticketsInWindow returns the tickets of a given topic in the registration window. | 
					
						
							|  |  |  | func (s *ticketStore) ticketsInWindow(t Topic) []ticketRef { | 
					
						
							|  |  |  | 	ltBucket := s.lastBucketFetched | 
					
						
							|  |  |  | 	var res []ticketRef | 
					
						
							|  |  |  | 	tickets := s.tickets[t].buckets | 
					
						
							|  |  |  | 	for g := ltBucket; g < ltBucket+timeWindow; g++ { | 
					
						
							|  |  |  | 		res = append(res, tickets[g]...) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	debugLog(fmt.Sprintf("ticketsInWindow(%v) = %v", t, len(res))) | 
					
						
							|  |  |  | 	return res | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) removeExcessTickets(t Topic) { | 
					
						
							|  |  |  | 	tickets := s.ticketsInWindow(t) | 
					
						
							|  |  |  | 	if len(tickets) <= wantTicketsInWindow { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sort.Sort(ticketRefByWaitTime(tickets)) | 
					
						
							|  |  |  | 	for _, r := range tickets[wantTicketsInWindow:] { | 
					
						
							|  |  |  | 		s.removeTicketRef(r) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type ticketRefByWaitTime []ticketRef | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Len is the number of elements in the collection. | 
					
						
							|  |  |  | func (s ticketRefByWaitTime) Len() int { | 
					
						
							|  |  |  | 	return len(s) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r ticketRef) waitTime() mclock.AbsTime { | 
					
						
							|  |  |  | 	return r.t.regTime[r.idx] - r.t.issueTime | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Less reports whether the element with | 
					
						
							|  |  |  | // index i should sort before the element with index j. | 
					
						
							|  |  |  | func (s ticketRefByWaitTime) Less(i, j int) bool { | 
					
						
							|  |  |  | 	return s[i].waitTime() < s[j].waitTime() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Swap swaps the elements with indexes i and j. | 
					
						
							|  |  |  | func (s ticketRefByWaitTime) Swap(i, j int) { | 
					
						
							|  |  |  | 	s[i], s[j] = s[j], s[i] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) addTicketRef(r ticketRef) { | 
					
						
							|  |  |  | 	topic := r.t.topics[r.idx] | 
					
						
							|  |  |  | 	t := s.tickets[topic] | 
					
						
							|  |  |  | 	if t.buckets == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen)) | 
					
						
							|  |  |  | 	t.buckets[bucket] = append(t.buckets[bucket], r) | 
					
						
							|  |  |  | 	r.t.refCnt++ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt | 
					
						
							|  |  |  | 	if t.nextLookup < min { | 
					
						
							|  |  |  | 		t.nextLookup = min | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.nextLookup += mclock.AbsTime(collectFrequency) | 
					
						
							|  |  |  | 	s.tickets[topic] = t | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	//s.removeExcessTickets(topic) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) nextFilteredTicket() (t *ticketRef, wait time.Duration) { | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		t, wait = s.nextRegisterableTicket() | 
					
						
							|  |  |  | 		if t == nil { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		regTime := now + mclock.AbsTime(wait) | 
					
						
							|  |  |  | 		topic := t.t.topics[t.idx] | 
					
						
							|  |  |  | 		if regTime >= s.tickets[topic].nextReg { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		s.removeTicketRef(*t) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) ticketRegistered(t ticketRef) { | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	topic := t.t.topics[t.idx] | 
					
						
							|  |  |  | 	tt := s.tickets[topic] | 
					
						
							|  |  |  | 	min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt | 
					
						
							|  |  |  | 	if min > tt.nextReg { | 
					
						
							|  |  |  | 		tt.nextReg = min | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	tt.nextReg += mclock.AbsTime(registerFrequency) | 
					
						
							|  |  |  | 	s.tickets[topic] = tt | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	s.removeTicketRef(t) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // nextRegisterableTicket returns the next ticket that can be used | 
					
						
							|  |  |  | // to register. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If the returned wait time <= zero the ticket can be used. For a positive | 
					
						
							|  |  |  | // wait time, the caller should requery the next ticket later. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // A ticket can be returned more than once with <= zero wait time in case | 
					
						
							|  |  |  | // the ticket contains multiple topics. | 
					
						
							|  |  |  | func (s *ticketStore) nextRegisterableTicket() (t *ticketRef, wait time.Duration) { | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if t == nil { | 
					
						
							|  |  |  | 			debugLog(" nil") | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			debugLog(fmt.Sprintf(" node = %x sn = %v wait = %v", t.t.node.ID[:8], t.t.serial, wait)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	debugLog("nextRegisterableTicket()") | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 	if s.nextTicketCached != nil { | 
					
						
							|  |  |  | 		return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for bucket := s.lastBucketFetched; ; bucket++ { | 
					
						
							|  |  |  | 		var ( | 
					
						
							|  |  |  | 			empty      = true    // true if there are no tickets | 
					
						
							|  |  |  | 			nextTicket ticketRef // uninitialized if this bucket is empty | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		for _, tickets := range s.tickets { | 
					
						
							|  |  |  | 			//s.removeExcessTickets(topic) | 
					
						
							|  |  |  | 			if len(tickets.buckets) != 0 { | 
					
						
							|  |  |  | 				empty = false | 
					
						
							|  |  |  | 				if list := tickets.buckets[bucket]; list != nil { | 
					
						
							|  |  |  | 					for _, ref := range list { | 
					
						
							|  |  |  | 						//debugLog(fmt.Sprintf(" nrt bucket = %d node = %x sn = %v wait = %v", bucket, ref.t.node.ID[:8], ref.t.serial, time.Duration(ref.topicRegTime()-now))) | 
					
						
							|  |  |  | 						if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() { | 
					
						
							|  |  |  | 							nextTicket = ref | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if empty { | 
					
						
							|  |  |  | 			return nil, 0 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if nextTicket.t != nil { | 
					
						
							|  |  |  | 			wait = time.Duration(nextTicket.topicRegTime() - now) | 
					
						
							|  |  |  | 			s.nextTicketCached = &nextTicket | 
					
						
							|  |  |  | 			return &nextTicket, wait | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		s.lastBucketFetched = bucket | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // removeTicket removes a ticket from the ticket store | 
					
						
							|  |  |  | func (s *ticketStore) removeTicketRef(ref ticketRef) { | 
					
						
							|  |  |  | 	debugLog(fmt.Sprintf("removeTicketRef(node = %x sn = %v)", ref.t.node.ID[:8], ref.t.serial)) | 
					
						
							|  |  |  | 	topic := ref.topic() | 
					
						
							|  |  |  | 	tickets := s.tickets[topic].buckets | 
					
						
							|  |  |  | 	if tickets == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen)) | 
					
						
							|  |  |  | 	list := tickets[bucket] | 
					
						
							|  |  |  | 	idx := -1 | 
					
						
							|  |  |  | 	for i, bt := range list { | 
					
						
							|  |  |  | 		if bt.t == ref.t { | 
					
						
							|  |  |  | 			idx = i | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if idx == -1 { | 
					
						
							|  |  |  | 		panic(nil) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	list = append(list[:idx], list[idx+1:]...) | 
					
						
							|  |  |  | 	if len(list) != 0 { | 
					
						
							|  |  |  | 		tickets[bucket] = list | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		delete(tickets, bucket) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	ref.t.refCnt-- | 
					
						
							|  |  |  | 	if ref.t.refCnt == 0 { | 
					
						
							|  |  |  | 		delete(s.nodes, ref.t.node) | 
					
						
							|  |  |  | 		delete(s.nodeLastReq, ref.t.node) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make nextRegisterableTicket return the next available ticket. | 
					
						
							|  |  |  | 	s.nextTicketCached = nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type lookupInfo struct { | 
					
						
							|  |  |  | 	target       common.Hash | 
					
						
							|  |  |  | 	topic        Topic | 
					
						
							|  |  |  | 	radiusLookup bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type reqInfo struct { | 
					
						
							|  |  |  | 	pingHash []byte | 
					
						
							|  |  |  | 	lookup   lookupInfo | 
					
						
							|  |  |  | 	time     mclock.AbsTime | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // returns -1 if not found | 
					
						
							|  |  |  | func (t *ticket) findIdx(topic Topic) int { | 
					
						
							|  |  |  | 	for i, tt := range t.topics { | 
					
						
							|  |  |  | 		if tt == topic { | 
					
						
							|  |  |  | 			return i | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return -1 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) registerLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte) { | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 	for i, n := range nodes { | 
					
						
							|  |  |  | 		if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { | 
					
						
							|  |  |  | 			if lookup.radiusLookup { | 
					
						
							|  |  |  | 				if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { | 
					
						
							|  |  |  | 					s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				if s.nodes[n] == nil { | 
					
						
							|  |  |  | 					s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) searchLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte, query func(n *Node, topic Topic) []byte) { | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 	for i, n := range nodes { | 
					
						
							|  |  |  | 		if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius { | 
					
						
							|  |  |  | 			if lookup.radiusLookup { | 
					
						
							|  |  |  | 				if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC { | 
					
						
							|  |  |  | 					s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} // else { | 
					
						
							|  |  |  | 			if s.canQueryTopic(n, lookup.topic) { | 
					
						
							|  |  |  | 				hash := query(n, lookup.topic) | 
					
						
							| 
									
										
										
										
											2016-11-12 21:02:02 +01:00
										 |  |  | 				if hash != nil { | 
					
						
							|  |  |  | 					s.addTopicQuery(common.BytesToHash(hash), n, lookup) | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			//} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t *ticket) { | 
					
						
							|  |  |  | 	for i, topic := range t.topics { | 
					
						
							|  |  |  | 		if tt, ok := s.radius[topic]; ok { | 
					
						
							|  |  |  | 			tt.adjustWithTicket(now, targetHash, ticketRef{t, i}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) addTicket(localTime mclock.AbsTime, pingHash []byte, t *ticket) { | 
					
						
							|  |  |  | 	debugLog(fmt.Sprintf("add(node = %x sn = %v)", t.node.ID[:8], t.serial)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lastReq, ok := s.nodeLastReq[t.node] | 
					
						
							|  |  |  | 	if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.adjustWithTicket(localTime, lastReq.lookup.target, t) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if lastReq.lookup.radiusLookup || s.nodes[t.node] != nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	topic := lastReq.lookup.topic | 
					
						
							|  |  |  | 	topicIdx := t.findIdx(topic) | 
					
						
							|  |  |  | 	if topicIdx == -1 { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bucket := timeBucket(localTime / mclock.AbsTime(ticketTimeBucketLen)) | 
					
						
							|  |  |  | 	if s.lastBucketFetched == 0 || bucket < s.lastBucketFetched { | 
					
						
							|  |  |  | 		s.lastBucketFetched = bucket | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if _, ok := s.tickets[topic]; ok { | 
					
						
							|  |  |  | 		wait := t.regTime[topicIdx] - localTime | 
					
						
							|  |  |  | 		rnd := rand.ExpFloat64() | 
					
						
							|  |  |  | 		if rnd > 10 { | 
					
						
							|  |  |  | 			rnd = 10 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if float64(wait) < float64(keepTicketConst)+float64(keepTicketExp)*rnd { | 
					
						
							|  |  |  | 			// use the ticket to register this topic | 
					
						
							|  |  |  | 			//fmt.Println("addTicket", t.node.ID[:8], t.node.addr().String(), t.serial, t.pong) | 
					
						
							|  |  |  | 			s.addTicketRef(ticketRef{t, topicIdx}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if t.refCnt > 0 { | 
					
						
							|  |  |  | 		s.nextTicketCached = nil | 
					
						
							|  |  |  | 		s.nodes[t.node] = t | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) getNodeTicket(node *Node) *ticket { | 
					
						
							|  |  |  | 	if s.nodes[node] == nil { | 
					
						
							|  |  |  | 		debugLog(fmt.Sprintf("getNodeTicket(%x) sn = nil", node.ID[:8])) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		debugLog(fmt.Sprintf("getNodeTicket(%x) sn = %v", node.ID[:8], s.nodes[node].serial)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return s.nodes[node] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) canQueryTopic(node *Node, topic Topic) bool { | 
					
						
							|  |  |  | 	qq := s.queriesSent[node] | 
					
						
							|  |  |  | 	if qq != nil { | 
					
						
							|  |  |  | 		now := mclock.Now() | 
					
						
							|  |  |  | 		for _, sq := range qq { | 
					
						
							|  |  |  | 			if sq.lookup.topic == topic && sq.sent > now-mclock.AbsTime(topicQueryResend) { | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) addTopicQuery(hash common.Hash, node *Node, lookup lookupInfo) { | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 	qq := s.queriesSent[node] | 
					
						
							|  |  |  | 	if qq == nil { | 
					
						
							|  |  |  | 		qq = make(map[common.Hash]sentQuery) | 
					
						
							|  |  |  | 		s.queriesSent[node] = qq | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	qq[hash] = sentQuery{sent: now, lookup: lookup} | 
					
						
							|  |  |  | 	s.cleanupTopicQueries(now) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) cleanupTopicQueries(now mclock.AbsTime) { | 
					
						
							|  |  |  | 	if s.nextTopicQueryCleanup > now { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	exp := now - mclock.AbsTime(topicQueryResend) | 
					
						
							|  |  |  | 	for n, qq := range s.queriesSent { | 
					
						
							|  |  |  | 		for h, q := range qq { | 
					
						
							|  |  |  | 			if q.sent < exp { | 
					
						
							|  |  |  | 				delete(qq, h) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if len(qq) == 0 { | 
					
						
							|  |  |  | 			delete(s.queriesSent, n) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.nextTopicQueryCleanup = now + mclock.AbsTime(topicQueryTimeout) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNode) (timeout bool) { | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 	//fmt.Println("got", from.addr().String(), hash, len(nodes)) | 
					
						
							|  |  |  | 	qq := s.queriesSent[from] | 
					
						
							|  |  |  | 	if qq == nil { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	q, ok := qq[hash] | 
					
						
							|  |  |  | 	if !ok || now > q.sent+mclock.AbsTime(topicQueryTimeout) { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	inside := float64(0) | 
					
						
							|  |  |  | 	if len(nodes) > 0 { | 
					
						
							|  |  |  | 		inside = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.radius[q.lookup.topic].adjust(now, q.lookup.target, from.sha, inside) | 
					
						
							|  |  |  | 	chn := s.searchTopicMap[q.lookup.topic].foundChn | 
					
						
							|  |  |  | 	if chn == nil { | 
					
						
							|  |  |  | 		//fmt.Println("no channel") | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, node := range nodes { | 
					
						
							|  |  |  | 		ip := node.IP | 
					
						
							|  |  |  | 		if ip.IsUnspecified() || ip.IsLoopback() { | 
					
						
							|  |  |  | 			ip = from.IP | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 		n := NewNode(node.ID, ip, node.UDP-1, node.TCP-1) // subtract one from port while discv5 is running in test mode on UDPport+1 | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 		select { | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 		case chn <- n: | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 		default: | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type topicRadius struct { | 
					
						
							|  |  |  | 	topic             Topic | 
					
						
							|  |  |  | 	topicHashPrefix   uint64 | 
					
						
							|  |  |  | 	radius, minRadius uint64 | 
					
						
							|  |  |  | 	buckets           []topicRadiusBucket | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 	converged         bool | 
					
						
							|  |  |  | 	radiusLookupCnt   int | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type topicRadiusEvent int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	trOutside topicRadiusEvent = iota | 
					
						
							|  |  |  | 	trInside | 
					
						
							|  |  |  | 	trNoAdjust | 
					
						
							|  |  |  | 	trCount | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type topicRadiusBucket struct { | 
					
						
							|  |  |  | 	weights    [trCount]float64 | 
					
						
							|  |  |  | 	lastTime   mclock.AbsTime | 
					
						
							|  |  |  | 	value      float64 | 
					
						
							|  |  |  | 	lookupSent map[common.Hash]mclock.AbsTime | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *topicRadiusBucket) update(now mclock.AbsTime) { | 
					
						
							|  |  |  | 	if now == b.lastTime { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	exp := math.Exp(-float64(now-b.lastTime) / float64(radiusTC)) | 
					
						
							|  |  |  | 	for i, w := range b.weights { | 
					
						
							|  |  |  | 		b.weights[i] = w * exp | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	b.lastTime = now | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for target, tm := range b.lookupSent { | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 		if now-tm > mclock.AbsTime(respTimeout) { | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 			b.weights[trNoAdjust] += 1 | 
					
						
							|  |  |  | 			delete(b.lookupSent, target) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (b *topicRadiusBucket) adjust(now mclock.AbsTime, inside float64) { | 
					
						
							|  |  |  | 	b.update(now) | 
					
						
							|  |  |  | 	if inside <= 0 { | 
					
						
							|  |  |  | 		b.weights[trOutside] += 1 | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		if inside >= 1 { | 
					
						
							|  |  |  | 			b.weights[trInside] += 1 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			b.weights[trInside] += inside | 
					
						
							|  |  |  | 			b.weights[trOutside] += 1 - inside | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newTopicRadius(t Topic) *topicRadius { | 
					
						
							|  |  |  | 	topicHash := crypto.Keccak256Hash([]byte(t)) | 
					
						
							|  |  |  | 	topicHashPrefix := binary.BigEndian.Uint64(topicHash[0:8]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return &topicRadius{ | 
					
						
							|  |  |  | 		topic:           t, | 
					
						
							|  |  |  | 		topicHashPrefix: topicHashPrefix, | 
					
						
							|  |  |  | 		radius:          maxRadius, | 
					
						
							|  |  |  | 		minRadius:       maxRadius, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) getBucketIdx(addrHash common.Hash) int { | 
					
						
							|  |  |  | 	prefix := binary.BigEndian.Uint64(addrHash[0:8]) | 
					
						
							|  |  |  | 	var log2 float64 | 
					
						
							|  |  |  | 	if prefix != r.topicHashPrefix { | 
					
						
							|  |  |  | 		log2 = math.Log2(float64(prefix ^ r.topicHashPrefix)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	bucket := int((64 - log2) * radiusBucketsPerBit) | 
					
						
							|  |  |  | 	max := 64*radiusBucketsPerBit - 1 | 
					
						
							|  |  |  | 	if bucket > max { | 
					
						
							|  |  |  | 		return max | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if bucket < 0 { | 
					
						
							|  |  |  | 		return 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return bucket | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) targetForBucket(bucket int) common.Hash { | 
					
						
							|  |  |  | 	min := math.Pow(2, 64-float64(bucket+1)/radiusBucketsPerBit) | 
					
						
							|  |  |  | 	max := math.Pow(2, 64-float64(bucket)/radiusBucketsPerBit) | 
					
						
							|  |  |  | 	a := uint64(min) | 
					
						
							|  |  |  | 	b := randUint64n(uint64(max - min)) | 
					
						
							|  |  |  | 	xor := a + b | 
					
						
							|  |  |  | 	if xor < a { | 
					
						
							|  |  |  | 		xor = ^uint64(0) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	prefix := r.topicHashPrefix ^ xor | 
					
						
							|  |  |  | 	var target common.Hash | 
					
						
							|  |  |  | 	binary.BigEndian.PutUint64(target[0:8], prefix) | 
					
						
							| 
									
										
										
										
											2016-11-09 01:04:36 +01:00
										 |  |  | 	globalRandRead(target[8:]) | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 	return target | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-09 01:04:36 +01:00
										 |  |  | // package rand provides a Read function in Go 1.6 and later, but | 
					
						
							|  |  |  | // we can't use it yet because we still support Go 1.5. | 
					
						
							|  |  |  | func globalRandRead(b []byte) { | 
					
						
							|  |  |  | 	pos := 0 | 
					
						
							|  |  |  | 	val := 0 | 
					
						
							|  |  |  | 	for n := 0; n < len(b); n++ { | 
					
						
							|  |  |  | 		if pos == 0 { | 
					
						
							|  |  |  | 			val = rand.Int() | 
					
						
							|  |  |  | 			pos = 7 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		b[n] = byte(val) | 
					
						
							|  |  |  | 		val >>= 8 | 
					
						
							|  |  |  | 		pos-- | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | func (r *topicRadius) isInRadius(addrHash common.Hash) bool { | 
					
						
							|  |  |  | 	nodePrefix := binary.BigEndian.Uint64(addrHash[0:8]) | 
					
						
							|  |  |  | 	dist := nodePrefix ^ r.topicHashPrefix | 
					
						
							|  |  |  | 	return dist < r.radius | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) chooseLookupBucket(a, b int) int { | 
					
						
							|  |  |  | 	if a < 0 { | 
					
						
							|  |  |  | 		a = 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if a > b { | 
					
						
							|  |  |  | 		return -1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	c := 0 | 
					
						
							|  |  |  | 	for i := a; i <= b; i++ { | 
					
						
							|  |  |  | 		if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { | 
					
						
							|  |  |  | 			c++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if c == 0 { | 
					
						
							|  |  |  | 		return -1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	rnd := randUint(uint32(c)) | 
					
						
							|  |  |  | 	for i := a; i <= b; i++ { | 
					
						
							|  |  |  | 		if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust { | 
					
						
							|  |  |  | 			if rnd == 0 { | 
					
						
							|  |  |  | 				return i | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			rnd-- | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	panic(nil) // should never happen | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) needMoreLookups(a, b int, maxValue float64) bool { | 
					
						
							|  |  |  | 	var max float64 | 
					
						
							|  |  |  | 	if a < 0 { | 
					
						
							|  |  |  | 		a = 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if b >= len(r.buckets) { | 
					
						
							|  |  |  | 		b = len(r.buckets) - 1 | 
					
						
							|  |  |  | 		if r.buckets[b].value > max { | 
					
						
							|  |  |  | 			max = r.buckets[b].value | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if b >= a { | 
					
						
							|  |  |  | 		for i := a; i <= b; i++ { | 
					
						
							|  |  |  | 			if r.buckets[i].value > max { | 
					
						
							|  |  |  | 				max = r.buckets[i].value | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return maxValue-max < minPeakSize | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) { | 
					
						
							|  |  |  | 	maxBucket := 0 | 
					
						
							|  |  |  | 	maxValue := float64(0) | 
					
						
							|  |  |  | 	now := mclock.Now() | 
					
						
							|  |  |  | 	v := float64(0) | 
					
						
							| 
									
										
										
										
											2017-01-06 15:52:03 +01:00
										 |  |  | 	for i := range r.buckets { | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 		r.buckets[i].update(now) | 
					
						
							|  |  |  | 		v += r.buckets[i].weights[trOutside] - r.buckets[i].weights[trInside] | 
					
						
							|  |  |  | 		r.buckets[i].value = v | 
					
						
							|  |  |  | 		//fmt.Printf("%v %v | ", v, r.buckets[i].weights[trNoAdjust]) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	//fmt.Println() | 
					
						
							|  |  |  | 	slopeCross := -1 | 
					
						
							|  |  |  | 	for i, b := range r.buckets { | 
					
						
							|  |  |  | 		v := b.value | 
					
						
							|  |  |  | 		if v < float64(i)*minSlope { | 
					
						
							|  |  |  | 			slopeCross = i | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if v > maxValue { | 
					
						
							|  |  |  | 			maxValue = v | 
					
						
							|  |  |  | 			maxBucket = i + 1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	minRadBucket := len(r.buckets) | 
					
						
							|  |  |  | 	sum := float64(0) | 
					
						
							|  |  |  | 	for minRadBucket > 0 && sum < minRightSum { | 
					
						
							|  |  |  | 		minRadBucket-- | 
					
						
							|  |  |  | 		b := r.buckets[minRadBucket] | 
					
						
							|  |  |  | 		sum += b.weights[trInside] + b.weights[trOutside] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r.minRadius = uint64(math.Pow(2, 64-float64(minRadBucket)/radiusBucketsPerBit)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	lookupLeft := -1 | 
					
						
							|  |  |  | 	if r.needMoreLookups(0, maxBucket-lookupWidth-1, maxValue) { | 
					
						
							|  |  |  | 		lookupLeft = r.chooseLookupBucket(maxBucket-lookupWidth, maxBucket-1) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	lookupRight := -1 | 
					
						
							|  |  |  | 	if slopeCross != maxBucket && (minRadBucket <= maxBucket || r.needMoreLookups(maxBucket+lookupWidth, len(r.buckets)-1, maxValue)) { | 
					
						
							|  |  |  | 		for len(r.buckets) <= maxBucket+lookupWidth { | 
					
						
							|  |  |  | 			r.buckets = append(r.buckets, topicRadiusBucket{lookupSent: make(map[common.Hash]mclock.AbsTime)}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		lookupRight = r.chooseLookupBucket(maxBucket, maxBucket+lookupWidth-1) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if lookupLeft == -1 { | 
					
						
							|  |  |  | 		radiusLookup = lookupRight | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		if lookupRight == -1 { | 
					
						
							|  |  |  | 			radiusLookup = lookupLeft | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			if randUint(2) == 0 { | 
					
						
							|  |  |  | 				radiusLookup = lookupLeft | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				radiusLookup = lookupRight | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	//fmt.Println("mb", maxBucket, "sc", slopeCross, "mrb", minRadBucket, "ll", lookupLeft, "lr", lookupRight, "mv", maxValue) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if radiusLookup == -1 { | 
					
						
							|  |  |  | 		// no more radius lookups needed at the moment, return a radius | 
					
						
							| 
									
										
										
										
											2016-11-27 13:12:11 +01:00
										 |  |  | 		r.converged = true | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 		rad := maxBucket | 
					
						
							|  |  |  | 		if minRadBucket < rad { | 
					
						
							|  |  |  | 			rad = minRadBucket | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		radius = ^uint64(0) | 
					
						
							|  |  |  | 		if rad > 0 { | 
					
						
							|  |  |  | 			radius = uint64(math.Pow(2, 64-float64(rad)/radiusBucketsPerBit)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		r.radius = radius | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) nextTarget(forceRegular bool) lookupInfo { | 
					
						
							|  |  |  | 	if !forceRegular { | 
					
						
							|  |  |  | 		_, radiusLookup := r.recalcRadius() | 
					
						
							|  |  |  | 		if radiusLookup != -1 { | 
					
						
							|  |  |  | 			target := r.targetForBucket(radiusLookup) | 
					
						
							|  |  |  | 			r.buckets[radiusLookup].lookupSent[target] = mclock.Now() | 
					
						
							|  |  |  | 			return lookupInfo{target: target, topic: r.topic, radiusLookup: true} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	radExt := r.radius / 2 | 
					
						
							|  |  |  | 	if radExt > maxRadius-r.radius { | 
					
						
							|  |  |  | 		radExt = maxRadius - r.radius | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	rnd := randUint64n(r.radius) + randUint64n(2*radExt) | 
					
						
							|  |  |  | 	if rnd > radExt { | 
					
						
							|  |  |  | 		rnd -= radExt | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		rnd = radExt - rnd | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	prefix := r.topicHashPrefix ^ rnd | 
					
						
							|  |  |  | 	var target common.Hash | 
					
						
							|  |  |  | 	binary.BigEndian.PutUint64(target[0:8], prefix) | 
					
						
							| 
									
										
										
										
											2016-11-09 01:04:36 +01:00
										 |  |  | 	globalRandRead(target[8:]) | 
					
						
							| 
									
										
										
										
											2016-10-19 13:04:55 +02:00
										 |  |  | 	return lookupInfo{target: target, topic: r.topic, radiusLookup: false} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t ticketRef) { | 
					
						
							|  |  |  | 	wait := t.t.regTime[t.idx] - t.t.issueTime | 
					
						
							|  |  |  | 	inside := float64(wait)/float64(targetWaitTime) - 0.5 | 
					
						
							|  |  |  | 	if inside > 1 { | 
					
						
							|  |  |  | 		inside = 1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if inside < 0 { | 
					
						
							|  |  |  | 		inside = 0 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r.adjust(now, targetHash, t.t.node.sha, inside) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *topicRadius) adjust(now mclock.AbsTime, targetHash, addrHash common.Hash, inside float64) { | 
					
						
							|  |  |  | 	bucket := r.getBucketIdx(addrHash) | 
					
						
							|  |  |  | 	//fmt.Println("adjust", bucket, len(r.buckets), inside) | 
					
						
							|  |  |  | 	if bucket >= len(r.buckets) { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r.buckets[bucket].adjust(now, inside) | 
					
						
							|  |  |  | 	delete(r.buckets[bucket].lookupSent, targetHash) | 
					
						
							|  |  |  | } |