2018-04-03 20:00:35 +02:00
package dht
import (
2021-10-06 20:00:59 +02:00
"crypto/rand"
"encoding/hex"
2018-04-03 20:00:35 +02:00
"sync"
2018-04-05 17:35:57 +02:00
"time"
2018-04-03 20:00:35 +02:00
2021-10-06 20:00:59 +02:00
"github.com/lbryio/lbry.go/v3/dht/bits"
"github.com/lbryio/lbry.go/v3/extras/stop"
2018-04-03 20:00:35 +02:00
2021-10-06 20:00:59 +02:00
"github.com/cockroachdb/errors"
2018-06-25 21:48:57 +02:00
"github.com/sirupsen/logrus"
2020-09-01 18:46:56 +02:00
"go.uber.org/atomic"
2018-04-03 20:00:35 +02:00
)
2018-05-15 02:55:45 +02:00
// TODO: iterativeFindValue may be stopping early. if it gets a response with one peer, it should keep going because other nodes may know about more peers that have that blob
// TODO: or, it should try a tcp handshake with peers as it finds them, to make sure they are still online and have the blob
2018-06-25 21:48:57 +02:00
var cfLog * logrus . Logger
func init ( ) {
cfLog = logrus . StandardLogger ( )
}
2018-06-25 22:49:40 +02:00
func NodeFinderUseLogger ( l * logrus . Logger ) {
2018-06-25 21:48:57 +02:00
cfLog = l
}
2018-04-28 02:16:12 +02:00
type contactFinder struct {
2018-04-03 20:00:35 +02:00
findValue bool // true if we're using findValue
2018-06-14 17:48:02 +02:00
target bits . Bitmap
2018-04-28 02:16:12 +02:00
node * Node
2018-04-03 20:00:35 +02:00
2018-06-25 22:49:40 +02:00
grp * stop . Group
2018-04-03 20:00:35 +02:00
findValueMutex * sync . Mutex
2018-04-28 02:16:12 +02:00
findValueResult [ ] Contact
2018-04-03 20:00:35 +02:00
2018-04-28 02:16:12 +02:00
activeContactsMutex * sync . Mutex
activeContacts [ ] Contact
2018-04-03 20:00:35 +02:00
2018-04-05 17:35:57 +02:00
shortlistMutex * sync . Mutex
2018-04-28 02:16:12 +02:00
shortlist [ ] Contact
2018-06-14 17:48:02 +02:00
shortlistAdded map [ bits . Bitmap ] bool
2018-04-05 22:05:28 +02:00
2018-06-25 21:48:57 +02:00
closestContactMutex * sync . RWMutex
closestContact * Contact
notGettingCloser * atomic . Bool
2018-04-03 20:00:35 +02:00
}
2018-06-25 22:49:40 +02:00
func FindContacts ( node * Node , target bits . Bitmap , findValue bool , parentGrp * stop . Group ) ( [ ] Contact , bool , error ) {
2018-06-13 18:45:47 +02:00
cf := & contactFinder {
2018-04-28 02:16:12 +02:00
node : node ,
target : target ,
findValue : findValue ,
findValueMutex : & sync . Mutex { } ,
activeContactsMutex : & sync . Mutex { } ,
shortlistMutex : & sync . Mutex { } ,
2018-06-14 17:48:02 +02:00
shortlistAdded : make ( map [ bits . Bitmap ] bool ) ,
2018-06-25 22:49:40 +02:00
grp : stop . New ( parentGrp ) ,
2018-06-25 21:48:57 +02:00
closestContactMutex : & sync . RWMutex { } ,
notGettingCloser : atomic . NewBool ( false ) ,
2018-04-03 20:00:35 +02:00
}
2018-06-19 19:47:13 +02:00
2018-06-13 18:45:47 +02:00
return cf . Find ( )
2018-04-03 20:00:35 +02:00
}
2018-06-13 18:45:47 +02:00
func ( cf * contactFinder ) Stop ( ) {
2018-06-25 22:49:40 +02:00
cf . grp . StopAndWait ( )
2018-05-13 22:02:46 +02:00
}
2018-06-13 18:45:47 +02:00
func ( cf * contactFinder ) Find ( ) ( [ ] Contact , bool , error ) {
2018-04-28 02:16:12 +02:00
if cf . findValue {
2018-06-22 15:30:16 +02:00
cf . debug ( "starting iterativeFindValue" )
2018-04-05 22:05:28 +02:00
} else {
2018-06-22 15:30:16 +02:00
cf . debug ( "starting iterativeFindNode" )
2018-04-05 22:05:28 +02:00
}
2018-06-25 21:48:57 +02:00
2018-04-28 02:16:12 +02:00
cf . appendNewToShortlist ( cf . node . rt . GetClosest ( cf . target , alpha ) )
if len ( cf . shortlist ) == 0 {
2021-10-06 20:00:59 +02:00
return nil , false , errors . WithStack ( errors . Newf (
"[%s] find %s: no contacts in routing table" ,
cf . node . id . HexShort ( ) , cf . target . HexShort ( ) ,
) )
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
go cf . cycle ( false )
timeout := 5 * time . Second
CycleLoop :
for {
select {
case <- time . After ( timeout ) :
go cf . cycle ( false )
2018-06-25 22:49:40 +02:00
case <- cf . grp . Ch ( ) :
2018-06-25 21:48:57 +02:00
break CycleLoop
}
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
// TODO: what to do if we have less than K active contacts, shortlist is empty, but we have other contacts in our routing table whom we have not contacted. prolly contact them
2018-04-03 20:00:35 +02:00
2018-06-13 18:45:47 +02:00
var contacts [ ] Contact
var found bool
2018-04-28 02:16:12 +02:00
if cf . findValue && len ( cf . findValueResult ) > 0 {
2018-06-13 18:45:47 +02:00
contacts = cf . findValueResult
found = true
2018-04-03 20:00:35 +02:00
} else {
2018-06-13 18:45:47 +02:00
contacts = cf . activeContacts
if len ( contacts ) > bucketSize {
contacts = contacts [ : bucketSize ]
2018-04-03 20:00:35 +02:00
}
}
2018-06-13 18:45:47 +02:00
cf . Stop ( )
return contacts , found , nil
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
// cycle does a single cycle of sending alpha probes and checking results against closestNode
func ( cf * contactFinder ) cycle ( bigCycle bool ) {
2021-10-06 20:00:59 +02:00
cycleID := randString ( 6 )
2018-06-25 21:48:57 +02:00
if bigCycle {
cf . debug ( "LAUNCHING CYCLE %s, AND ITS A BIG CYCLE" , cycleID )
} else {
cf . debug ( "LAUNCHING CYCLE %s" , cycleID )
}
defer cf . debug ( "CYCLE %s DONE" , cycleID )
2018-04-03 20:00:35 +02:00
2018-06-25 21:48:57 +02:00
cf . closestContactMutex . RLock ( )
closestContact := cf . closestContact
cf . closestContactMutex . RUnlock ( )
2018-04-05 17:35:57 +02:00
2018-06-25 21:48:57 +02:00
var wg sync . WaitGroup
ch := make ( chan * Contact )
2018-04-05 17:35:57 +02:00
2018-06-25 21:48:57 +02:00
limit := alpha
if bigCycle {
limit = bucketSize
}
2018-04-05 22:05:28 +02:00
2018-06-25 21:48:57 +02:00
for i := 0 ; i < limit ; i ++ {
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
ch <- cf . probe ( cycleID )
} ( )
}
2018-04-05 17:35:57 +02:00
2018-06-25 21:48:57 +02:00
go func ( ) {
wg . Wait ( )
close ( ch )
} ( )
2018-04-05 17:35:57 +02:00
2018-06-25 21:48:57 +02:00
foundCloser := false
for {
c , more := <- ch
if ! more {
break
}
if c != nil && ( closestContact == nil || cf . target . Closer ( c . ID , closestContact . ID ) ) {
if closestContact != nil {
cf . debug ( "|%s| best contact improved: %s -> %s" , cycleID , closestContact . ID . HexShort ( ) , c . ID . HexShort ( ) )
2018-04-05 17:35:57 +02:00
} else {
2018-06-25 21:48:57 +02:00
cf . debug ( "|%s| best contact starting at %s" , cycleID , c . ID . HexShort ( ) )
2018-04-05 17:35:57 +02:00
}
2018-06-25 21:48:57 +02:00
foundCloser = true
closestContact = c
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
}
2018-04-03 20:00:35 +02:00
2018-06-25 21:48:57 +02:00
if cf . isSearchFinished ( ) {
2018-06-25 22:49:40 +02:00
cf . grp . Stop ( )
2018-06-25 21:48:57 +02:00
return
}
if foundCloser {
cf . closestContactMutex . Lock ( )
// have to check again after locking in case other probes found a closer one in the meantime
if cf . closestContact == nil || cf . target . Closer ( closestContact . ID , cf . closestContact . ID ) {
cf . closestContact = closestContact
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
cf . closestContactMutex . Unlock ( )
go cf . cycle ( false )
} else if ! bigCycle {
cf . debug ( "|%s| no improvement, running big cycle" , cycleID )
go cf . cycle ( true )
} else {
// big cycle ran and there was no improvement, so we're done
cf . debug ( "|%s| big cycle ran, still no improvement" , cycleID )
cf . notGettingCloser . Store ( true )
}
}
// probe sends a single probe, updates the lists, and returns the closest contact it found
func ( cf * contactFinder ) probe ( cycleID string ) * Contact {
maybeContact := cf . popFromShortlist ( )
if maybeContact == nil {
cf . debug ( "|%s| no contacts in shortlist, returning" , cycleID )
return nil
}
c := * maybeContact
if c . ID . Equals ( cf . node . id ) {
return nil
}
cf . debug ( "|%s| probe %s: launching" , cycleID , c . ID . HexShort ( ) )
req := Request { Arg : & cf . target }
if cf . findValue {
req . Method = findValueMethod
} else {
req . Method = findNodeMethod
}
var res * Response
2018-06-25 21:56:45 +02:00
resCh := cf . node . SendAsync ( c , req )
2018-06-25 21:48:57 +02:00
select {
case res = <- resCh :
2018-06-25 22:49:40 +02:00
case <- cf . grp . Ch ( ) :
2018-06-25 21:48:57 +02:00
cf . debug ( "|%s| probe %s: canceled" , cycleID , c . ID . HexShort ( ) )
return nil
}
if res == nil {
cf . debug ( "|%s| probe %s: req canceled or timed out" , cycleID , c . ID . HexShort ( ) )
return nil
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
if cf . findValue && res . FindValueKey != "" {
cf . debug ( "|%s| probe %s: got value" , cycleID , c . ID . HexShort ( ) )
cf . findValueMutex . Lock ( )
cf . findValueResult = res . Contacts
cf . findValueMutex . Unlock ( )
2018-06-25 22:49:40 +02:00
cf . grp . Stop ( )
2018-06-25 21:48:57 +02:00
return nil
}
cf . debug ( "|%s| probe %s: got %s" , cycleID , c . ID . HexShort ( ) , res . argsDebug ( ) )
cf . insertIntoActiveList ( c )
cf . appendNewToShortlist ( res . Contacts )
cf . activeContactsMutex . Lock ( )
contacts := cf . activeContacts
if len ( contacts ) > bucketSize {
contacts = contacts [ : bucketSize ]
}
contactsStr := ""
for _ , c := range contacts {
contactsStr += c . ID . HexShort ( ) + ", "
}
cf . activeContactsMutex . Unlock ( )
return cf . closest ( res . Contacts ... )
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
// appendNewToShortlist appends any new contacts to the shortlist and sorts it by distance
// contacts that have already been added to the shortlist in the past are ignored
2018-04-28 02:16:12 +02:00
func ( cf * contactFinder ) appendNewToShortlist ( contacts [ ] Contact ) {
cf . shortlistMutex . Lock ( )
defer cf . shortlistMutex . Unlock ( )
2018-04-03 20:00:35 +02:00
2018-04-28 02:16:12 +02:00
for _ , c := range contacts {
2018-05-19 19:05:30 +02:00
if _ , ok := cf . shortlistAdded [ c . ID ] ; ! ok {
2018-04-28 02:16:12 +02:00
cf . shortlist = append ( cf . shortlist , c )
2018-05-19 19:05:30 +02:00
cf . shortlistAdded [ c . ID ] = true
2018-04-03 20:00:35 +02:00
}
}
2018-07-25 17:44:11 +02:00
sortByDistance ( cf . shortlist , cf . target )
2018-04-03 20:00:35 +02:00
}
2018-06-25 21:48:57 +02:00
// popFromShortlist pops the first contact off the shortlist and returns it
2018-04-28 02:16:12 +02:00
func ( cf * contactFinder ) popFromShortlist ( ) * Contact {
cf . shortlistMutex . Lock ( )
defer cf . shortlistMutex . Unlock ( )
2018-04-03 20:00:35 +02:00
2018-04-28 02:16:12 +02:00
if len ( cf . shortlist ) == 0 {
2018-04-03 20:00:35 +02:00
return nil
}
2018-04-28 02:16:12 +02:00
first := cf . shortlist [ 0 ]
cf . shortlist = cf . shortlist [ 1 : ]
2018-04-03 20:00:35 +02:00
return & first
}
2018-06-25 21:48:57 +02:00
// insertIntoActiveList inserts the contact into appropriate place in the list of active contacts (sorted by distance)
2018-04-28 02:16:12 +02:00
func ( cf * contactFinder ) insertIntoActiveList ( contact Contact ) {
cf . activeContactsMutex . Lock ( )
defer cf . activeContactsMutex . Unlock ( )
2018-04-03 20:00:35 +02:00
inserted := false
2018-04-28 02:16:12 +02:00
for i , n := range cf . activeContacts {
2018-06-25 21:48:57 +02:00
if cf . target . Closer ( contact . ID , n . ID ) {
2018-04-28 02:16:12 +02:00
cf . activeContacts = append ( cf . activeContacts [ : i ] , append ( [ ] Contact { contact } , cf . activeContacts [ i : ] ... ) ... )
2018-04-03 20:00:35 +02:00
inserted = true
2018-04-05 17:35:57 +02:00
break
2018-04-03 20:00:35 +02:00
}
}
if ! inserted {
2018-04-28 02:16:12 +02:00
cf . activeContacts = append ( cf . activeContacts , contact )
2018-04-03 20:00:35 +02:00
}
}
2018-06-25 21:48:57 +02:00
// isSearchFinished returns true if the search is done and should be stopped
2018-04-28 02:16:12 +02:00
func ( cf * contactFinder ) isSearchFinished ( ) bool {
if cf . findValue && len ( cf . findValueResult ) > 0 {
2018-04-03 20:00:35 +02:00
return true
}
select {
2018-06-25 22:49:40 +02:00
case <- cf . grp . Ch ( ) :
2018-04-03 20:00:35 +02:00
return true
default :
}
2018-06-25 21:48:57 +02:00
if cf . notGettingCloser . Load ( ) {
return true
}
2018-04-03 20:00:35 +02:00
2018-06-25 21:48:57 +02:00
cf . activeContactsMutex . Lock ( )
defer cf . activeContactsMutex . Unlock ( )
2018-08-07 17:38:55 +02:00
return len ( cf . activeContacts ) >= bucketSize
2018-04-03 20:00:35 +02:00
}
2018-04-05 22:05:28 +02:00
2018-06-25 21:48:57 +02:00
func ( cf * contactFinder ) debug ( format string , args ... interface { } ) {
args = append ( [ ] interface { } { cf . node . id . HexShort ( ) } , append ( [ ] interface { } { cf . target . HexShort ( ) } , args ... ) ... )
cfLog . Debugf ( "[%s] find %s: " + format , args ... )
2018-04-28 02:16:12 +02:00
}
2018-06-25 21:48:57 +02:00
func ( cf * contactFinder ) closest ( contacts ... Contact ) * Contact {
if len ( contacts ) == 0 {
return nil
}
closest := contacts [ 0 ]
for _ , c := range contacts {
if cf . target . Closer ( c . ID , closest . ID ) {
closest = c
}
}
return & closest
2018-06-22 15:30:16 +02:00
}
2021-10-06 20:00:59 +02:00
// randString returns a random alphanumeric string of a given length
func randString ( length int ) string {
buf := make ( [ ] byte , length / 2 )
_ , err := rand . Reader . Read ( buf )
if err != nil {
panic ( err )
}
randStr := hex . EncodeToString ( buf ) [ : length ]
if len ( randStr ) < length {
panic ( "Could not create random string that is long enough" )
}
return randStr
}