The Tracker

1: 
module Tracker

State

Tom the Tracker keeps a dictionary of Peers as his active workers. Every worker is identified by a unique id that he assigns himself. A peer is never reused. Even if he is reconnecting to the same remote node, a new peer will be used.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
type PeerSlot = {
    Id: int
    Peer: Peer
    mutable State: TrackerPeerState
    }

let maxSlots = settings.MaxPeers

let mutable connectionCount = 0 // how many peers are in the connected state
let mutable seqId = 0 // current sequence id

The queues

  • incoming requests
  • messages to send to every peer
  • requests that were received but couldn't be assigned to a peer because they were all busy at the time
1: 
2: 
3: 
let incomingMessage = new Subject<BitcoinMessage>()
let broadcastToPeers = new Subject<BitcoinMessage>()
let pendingMessages = new Queue<TrackerCommand>()

A crude unique sequence generator

1: 
2: 
3: 
let nextId() = 
    seqId <- seqId + 1
    seqId

The employee directory. The Map is persistent and thread-safe and the reassignment to peerSlots only happens in a handler.

1: 
2: 
3: 
let mutable peerSlots = Map.empty<int, PeerSlot>

exception InvalidTransition of TrackerPeerState * TrackerPeerState

Check that the transition is valid. This should never raise an exception. It is an assertion.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let checkTransition oldState newState = 
    match (oldState, newState) with
    | (Allocated, Ready) |
        (_, Free) |
        (Ready, Busy) |
        (Busy, Ready) -> ignore()
    | other -> 
        logger.ErrorF "Invalid transition from %A to %A" oldState newState
        raise (InvalidTransition other)

let changeState id newState =
    let oldState = peerSlots.[id].State
    let peer: IPeer = peerSlots.[id].Peer :> IPeer
    logger.DebugF "State transition of peer(%d %A): %A->%A" id peer.Target oldState newState
    checkTransition oldState newState
    peerSlots.[id].State <- newState
    oldState

Once a peer becomes available, call this to grab a message that was put on the back burner

1: 
2: 
3: 
4: 
5: 
6: 
let dequeuePendingMessage() = 
    logger.DebugF "Dequeue Msgs"
    if pendingMessages.Count <> 0 then
        trackerIncoming.OnNext(pendingMessages.Dequeue())
    else
        logger.DebugF "Empty Queue"

Route a message

Every command besides GetHeader is treated the same way. Tom looks for an available peer and assigns the work to him. GetHeader is the exception because it should stick to a given peer because Bob who initiated the call, wants to catchup from a given node.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
let assign message ps = 
    let peer = ps.Peer :> IPeer
    logger.DebugF "Assigning %A to %A" message peer.Target
    changeState ps.Id Busy |> ignore
    peer.Receive(message)

let forward (command: TrackerCommand) (message: PeerCommand) =
    match message with
        | PeerCommand.GetHeaders(_, ts, peer) -> 
            peerSlots |> Map.tryFind peer.Id
            |> Option.bind (fun ps -> Option.conditional (ps.State = Ready) ps) |> Option.map (fun ps -> assign message ps)
            |> getOrElseF (fun () -> ts.SetResult(Observable.Throw(ArgumentException("Peer busy - cannot handle command"))))
        | _ -> 
            peerSlots |> Map.tryPick(fun _ ps -> if ps.State = Ready then Some(ps) else None)
            |> Option.map (fun ps -> assign message ps)
            |> getOrElseF (fun () ->
                let freePeers = peerSlots |> Seq.filter(fun ps -> ps.Value.State = Free) |> Seq.length
                logger.DebugF "No peer available - %d dead peers" freePeers
                pendingMessages.Enqueue command // park the command for now
                )

Employment cycle of a Peer

When Tom hires a new peer, he gives a unique id and a position in his directory. When Tom fires a peer, Tom removes the peer from his directory and looks for a replacement.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
let newPeer() = 
    let openSlotId = nextId()
    peerSlots <- peerSlots.Add(openSlotId, { Id = openSlotId; Peer = new Peer(openSlotId); State = Allocated })
    let peer = peerSlots.[openSlotId].Peer
    connectionCount <- connectionCount + 1
    peer

let freePeer (id: int) =
    peerSlots |> Map.tryFind id |> Option.iter (fun ps -> 
        logger.DebugF "Freeing %A" (peerSlots.[id].Peer :> IPeer).Target
        changeState id Free |> ignore
        let peer =  ps.Peer
        connectionCount <- connectionCount - 1
        if connectionCount < maxSlots then
            Db.updateState((peer :> IPeer).Target, -1) // blackball this peer
            let newPeer = Db.getPeer() // find a new peer
            newPeer |> Option.iter (fun p -> 
                Db.updateState(p, 1)
                trackerIncoming.OnNext(Connect p))
        peerSlots <- peerSlots.Remove id
        (peer :> IPeer).Receive Closed // tell the old peer to take a hike
    )

Handling a command

processCommand handles 3 types of commands:

  • connection requests. Either outgoing from this application or incoming
  • related to the state of the peer like SetReady, Close
  • commands. The last category is forwarded to a peer based on the 'amazing' routing algorithm - pick the first one who can do it
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
let mutable tip = Db.readHeader (Db.readTip())
let processCommand(command: TrackerCommand) =
    logger.DebugF "TrackerCommand> %A" command
    match command with
    | SetTip t -> tip <- t
    | GetPeers -> 
        let peers = Db.getPeers()
        let cFreeSlots = maxSlots - peerSlots.Count
        for peer in peers |> Seq.truncate cFreeSlots do
            Db.updateState(peer, 1)
            trackerIncoming.OnNext(Connect peer)
    | Connect target ->
        let peer = newPeer() :> IPeer
        peer.Receive(Open(target, tip))
    | IncomingConnection (stream, target) ->
        let peer = newPeer() :> IPeer
        peer.Receive(OpenStream(stream, target, tip))
    | SetReady id ->
        peerSlots.TryFind id |> Option.iter(fun ps ->
            let peer = ps.Peer
            let oldState = changeState id Ready
            if oldState = Allocated then 
                logger.InfoF "Connected to %A" peer
                blockchainIncoming.OnNext(Catchup(peer, null)) // Disable if you want no catchup on connect
            dequeuePendingMessage()
            )
    | Close id -> 
        freePeer id
        logger.DebugF "Connection count = %d" connectionCount
    | BitcoinMessage message -> forward command (Execute message)
    | Command peerCommand -> forward command peerCommand

let processAddr (addr: Addr) =
    for a in addr.Addrs do Db.updateAddr(a)

Helper functions that are called by other components. They are thread-safe and will enqueue messages for Tom

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let getHeaders(gh: GetHeaders, peer: IPeer): Task<IObservable<Headers>> = 
    let ts = new TaskCompletionSource<IObservable<Headers>>()
    trackerIncoming.OnNext(Command (PeerCommand.GetHeaders (gh, ts, peer)))
    ts.Task

let getBlocks(blockHashes: seq<byte[]>): Task<IPeer * IObservable<Block * byte[]>> =
    let invHashes = 
        seq { 
            for h in blockHashes do
                yield new InvEntry(blockInvType, h) }
        |> Seq.toList
    let gd = new GetData(invHashes)
    let ts = new TaskCompletionSource<IPeer * IObservable<Block * byte[]>>()
    trackerIncoming.OnNext(Command (PeerCommand.DownloadBlocks (gd, ts)))
    ts.Task

Send a message to every peer that is not busy

1: 
2: 
3: 
let processBroadcast (m: BitcoinMessage) = 
    for peerSlot in peerSlots do
        (peerSlot.Value.Peer :> IPeer).Receive(Execute m)

Server

The server binds to the port configured in the app.config file and listens to incoming connection. On a connection, it sends a message to Tom. At this time, there is no limit to the number of incoming connections

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
let port = defaultPort
let ipAddress = IPAddress.Any
let endpoint = IPEndPoint(ipAddress, port)
let cts = new CancellationTokenSource()
let listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
listener.Bind(endpoint)
listener.Listen(int SocketOptionName.MaxConnections)

let rec connectLoop() = 
    async {
      let! socket = Async.FromBeginEnd(listener.BeginAccept, listener.EndAccept)
      let stream = new NetworkStream(socket)
      trackerIncoming.OnNext(TrackerCommand.IncomingConnection(stream, socket.RemoteEndPoint :?> IPEndPoint))
      return! connectLoop()
    }

let startServer() = 
    logger.InfoF "Started listening on port %d" port
    Async.Start(connectLoop(), cts.Token)
module Tracker
namespace System
namespace System.Net
namespace System.Net.Sockets
namespace System.Collections
namespace System.Collections.Generic
namespace System.IO
namespace System.Text
namespace System.Threading
Multiple items
namespace System.Linq

--------------------
namespace Microsoft.FSharp.Linq
namespace System.Threading.Tasks
module Option

from Microsoft.FSharp.Core
type PeerSlot =
  {Id: int;
   Peer: obj;
   mutable State: obj;}

Full name: Tracker.PeerSlot
PeerSlot.Id: int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
PeerSlot.Peer: obj
PeerSlot.State: obj
val maxSlots : int

Full name: Tracker.maxSlots
val mutable connectionCount : int

Full name: Tracker.connectionCount
val mutable seqId : int

Full name: Tracker.seqId
val disposable : obj

Full name: Tracker.disposable
val scheduler : obj

Full name: Tracker.scheduler
val incomingMessage : obj

Full name: Tracker.incomingMessage
val broadcastToPeers : obj

Full name: Tracker.broadcastToPeers
val pendingMessages : obj

Full name: Tracker.pendingMessages
Multiple items
type Queue<'T> =
  new : unit -> Queue<'T> + 2 overloads
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member CopyTo : array:'T[] * arrayIndex:int -> unit
  member Count : int
  member Dequeue : unit -> 'T
  member Enqueue : item:'T -> unit
  member GetEnumerator : unit -> Enumerator<'T>
  member Peek : unit -> 'T
  member ToArray : unit -> 'T[]
  ...
  nested type Enumerator

Full name: System.Collections.Generic.Queue<_>

--------------------
Queue() : unit
Queue(capacity: int) : unit
Queue(collection: IEnumerable<'T>) : unit
val nextId : unit -> int

Full name: Tracker.nextId
val mutable peerSlots : Map<int,PeerSlot>

Full name: Tracker.peerSlots
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val empty<'Key,'T (requires comparison)> : Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.empty
exception InvalidTransition of obj * obj

Full name: Tracker.InvalidTransition
val checkTransition : oldState:'a -> newState:'b -> 'c

Full name: Tracker.checkTransition
val oldState : 'a
val newState : 'b
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val raise : exn:Exception -> 'T

Full name: Microsoft.FSharp.Core.Operators.raise
val changeState : id:int -> newState:'a -> obj

Full name: Tracker.changeState
val id : int
val newState : 'a
val oldState : obj
val peer : obj
val dequeuePendingMessage : unit -> 'a

Full name: Tracker.dequeuePendingMessage
val assign : message:'a -> ps:PeerSlot -> 'b

Full name: Tracker.assign
val message : 'a
val ps : PeerSlot
val forward : command:'a -> message:'b -> 'c

Full name: Tracker.forward
val command : 'a
val message : 'b
val tryFind : key:'Key -> table:Map<'Key,'T> -> 'T option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryFind
val bind : binder:('T -> 'U option) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.bind
val map : mapping:('T -> 'U) -> option:'T option -> 'U option

Full name: Microsoft.FSharp.Core.Option.map
module Observable

from Microsoft.FSharp.Control
Multiple items
type ArgumentException =
  inherit SystemException
  new : unit -> ArgumentException + 4 overloads
  member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
  member Message : string
  member ParamName : string

Full name: System.ArgumentException

--------------------
ArgumentException() : unit
ArgumentException(message: string) : unit
ArgumentException(message: string, innerException: exn) : unit
ArgumentException(message: string, paramName: string) : unit
ArgumentException(message: string, paramName: string, innerException: exn) : unit
val tryPick : chooser:('Key -> 'T -> 'U option) -> table:Map<'Key,'T> -> 'U option (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.tryPick
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
module Seq

from Microsoft.FSharp.Collections
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.filter
val length : source:seq<'T> -> int

Full name: Microsoft.FSharp.Collections.Seq.length
val newPeer : unit -> obj

Full name: Tracker.newPeer
val openSlotId : int
member Map.Add : key:'Key * value:'Value -> Map<'Key,'Value>
val freePeer : id:int -> unit

Full name: Tracker.freePeer
val iter : action:('T -> unit) -> option:'T option -> unit

Full name: Microsoft.FSharp.Core.Option.iter
val newPeer : obj option
val p : obj
member Map.Remove : key:'Key -> Map<'Key,'Value>
val mutable tip : obj

Full name: Tracker.tip
val processCommand : command:'a -> 'b

Full name: Tracker.processCommand
property Map.Count: int
val truncate : count:int -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.truncate
val id : x:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.id
member Map.TryFind : key:'Key -> 'Value option
val processAddr : addr:'a -> unit

Full name: Tracker.processAddr
val addr : 'a
val a : obj
val getHeaders : gh:'a * peer:'b -> 'c

Full name: Tracker.getHeaders
val gh : 'a
val peer : 'b
Multiple items
type Task =
  new : action:Action -> Task + 7 overloads
  member AsyncState : obj
  member ContinueWith : continuationAction:Action<Task> -> Task + 9 overloads
  member CreationOptions : TaskCreationOptions
  member Dispose : unit -> unit
  member Exception : AggregateException
  member Id : int
  member IsCanceled : bool
  member IsCompleted : bool
  member IsFaulted : bool
  ...

Full name: System.Threading.Tasks.Task

--------------------
type Task<'TResult> =
  inherit Task
  new : function:Func<'TResult> -> Task<'TResult> + 7 overloads
  member ContinueWith : continuationAction:Action<Task<'TResult>> -> Task + 9 overloads
  member Result : 'TResult with get, set
  static member Factory : TaskFactory<'TResult>

Full name: System.Threading.Tasks.Task<_>

--------------------
Task(action: Action) : unit
Task(action: Action, cancellationToken: CancellationToken) : unit
Task(action: Action, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj) : unit
Task(action: Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken) : unit
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit

--------------------
Task(function: Func<'TResult>) : unit
Task(function: Func<'TResult>, cancellationToken: CancellationToken) : unit
Task(function: Func<'TResult>, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj) : unit
Task(function: Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : unit
Task(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
type IObservable<'T> =
  member Subscribe : observer:IObserver<'T> -> IDisposable

Full name: System.IObservable<_>
val ts : obj
Multiple items
type TaskCompletionSource<'TResult> =
  new : unit -> TaskCompletionSource<'TResult> + 3 overloads
  member SetCanceled : unit -> unit
  member SetException : exception:Exception -> unit + 1 overload
  member SetResult : result:'TResult -> unit
  member Task : Task<'TResult>
  member TrySetCanceled : unit -> bool
  member TrySetException : exception:Exception -> bool + 1 overload
  member TrySetResult : result:'TResult -> bool

Full name: System.Threading.Tasks.TaskCompletionSource<_>

--------------------
TaskCompletionSource() : unit
TaskCompletionSource(creationOptions: TaskCreationOptions) : unit
TaskCompletionSource(state: obj) : unit
TaskCompletionSource(state: obj, creationOptions: TaskCreationOptions) : unit
val getBlocks : blockHashes:seq<byte []> -> Task<'a * IObservable<'b * byte []>>

Full name: Tracker.getBlocks
val blockHashes : seq<byte []>
Multiple items
val seq : sequence:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Core.Operators.seq

--------------------
type seq<'T> = IEnumerable<'T>

Full name: Microsoft.FSharp.Collections.seq<_>
Multiple items
val byte : value:'T -> byte (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.byte

--------------------
type byte = Byte

Full name: Microsoft.FSharp.Core.byte
val invHashes : obj list
val h : byte []
val toList : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.Seq.toList
val gd : obj
val ts : TaskCompletionSource<'a * IObservable<'b * byte []>>
property TaskCompletionSource.Task: Task<'a * IObservable<'b * byte []>>
val processBroadcast : m:'a -> unit

Full name: Tracker.processBroadcast
val m : 'a
val peerSlot : KeyValuePair<int,PeerSlot>
property KeyValuePair.Value: PeerSlot
val startTracker : unit -> 'a

Full name: Tracker.startTracker
val port : int

Full name: Tracker.port
val ipAddress : IPAddress

Full name: Tracker.ipAddress
Multiple items
type IPAddress =
  new : newAddress:int64 -> IPAddress + 2 overloads
  member Address : int64 with get, set
  member AddressFamily : AddressFamily
  member Equals : comparand:obj -> bool
  member GetAddressBytes : unit -> byte[]
  member GetHashCode : unit -> int
  member IsIPv6LinkLocal : bool
  member IsIPv6Multicast : bool
  member IsIPv6SiteLocal : bool
  member IsIPv6Teredo : bool
  ...

Full name: System.Net.IPAddress

--------------------
IPAddress(newAddress: int64) : unit
IPAddress(address: byte []) : unit
IPAddress(address: byte [], scopeid: int64) : unit
field IPAddress.Any
val endpoint : IPEndPoint

Full name: Tracker.endpoint
Multiple items
type IPEndPoint =
  inherit EndPoint
  new : address:int64 * port:int -> IPEndPoint + 1 overload
  member Address : IPAddress with get, set
  member AddressFamily : AddressFamily
  member Create : socketAddress:SocketAddress -> EndPoint
  member Equals : comparand:obj -> bool
  member GetHashCode : unit -> int
  member Port : int with get, set
  member Serialize : unit -> SocketAddress
  member ToString : unit -> string
  static val MinPort : int
  ...

Full name: System.Net.IPEndPoint

--------------------
IPEndPoint(address: int64, port: int) : unit
IPEndPoint(address: IPAddress, port: int) : unit
val cts : CancellationTokenSource

Full name: Tracker.cts
Multiple items
type CancellationTokenSource =
  new : unit -> CancellationTokenSource
  member Cancel : unit -> unit + 1 overload
  member Dispose : unit -> unit
  member IsCancellationRequested : bool
  member Token : CancellationToken
  static member CreateLinkedTokenSource : params tokens:CancellationToken[] -> CancellationTokenSource + 1 overload

Full name: System.Threading.CancellationTokenSource

--------------------
CancellationTokenSource() : unit
val listener : Socket

Full name: Tracker.listener
Multiple items
type Socket =
  new : socketInformation:SocketInformation -> Socket + 1 overload
  member Accept : unit -> Socket
  member AcceptAsync : e:SocketAsyncEventArgs -> bool
  member AddressFamily : AddressFamily
  member Available : int
  member BeginAccept : callback:AsyncCallback * state:obj -> IAsyncResult + 2 overloads
  member BeginConnect : remoteEP:EndPoint * callback:AsyncCallback * state:obj -> IAsyncResult + 3 overloads
  member BeginDisconnect : reuseSocket:bool * callback:AsyncCallback * state:obj -> IAsyncResult
  member BeginReceive : buffers:IList<ArraySegment<byte>> * socketFlags:SocketFlags * callback:AsyncCallback * state:obj -> IAsyncResult + 3 overloads
  member BeginReceiveFrom : buffer:byte[] * offset:int * size:int * socketFlags:SocketFlags * remoteEP:EndPoint * callback:AsyncCallback * state:obj -> IAsyncResult
  ...

Full name: System.Net.Sockets.Socket

--------------------
Socket(socketInformation: SocketInformation) : unit
Socket(addressFamily: AddressFamily, socketType: SocketType, protocolType: ProtocolType) : unit
type AddressFamily =
  | Unknown = -1
  | Unspecified = 0
  | Unix = 1
  | InterNetwork = 2
  | ImpLink = 3
  | Pup = 4
  | Chaos = 5
  | NS = 6
  | Ipx = 6
  | Iso = 7
  ...

Full name: System.Net.Sockets.AddressFamily
field AddressFamily.InterNetwork = 2
type SocketType =
  | Stream = 1
  | Dgram = 2
  | Raw = 3
  | Rdm = 4
  | Seqpacket = 5
  | Unknown = -1

Full name: System.Net.Sockets.SocketType
field SocketType.Stream = 1
type ProtocolType =
  | IP = 0
  | IPv6HopByHopOptions = 0
  | Icmp = 1
  | Igmp = 2
  | Ggp = 3
  | IPv4 = 4
  | Tcp = 6
  | Pup = 12
  | Udp = 17
  | Idp = 22
  ...

Full name: System.Net.Sockets.ProtocolType
field ProtocolType.Tcp = 6
Socket.Bind(localEP: EndPoint) : unit
Socket.Listen(backlog: int) : unit
type SocketOptionName =
  | Debug = 1
  | AcceptConnection = 2
  | ReuseAddress = 4
  | KeepAlive = 8
  | DontRoute = 16
  | Broadcast = 32
  | UseLoopback = 64
  | Linger = 128
  | OutOfBandInline = 256
  | DontLinger = -129
  ...

Full name: System.Net.Sockets.SocketOptionName
field SocketOptionName.MaxConnections = 2147483647
val connectLoop : unit -> Async<'a>

Full name: Tracker.connectLoop
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val socket : Socket
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
Socket.BeginAccept(callback: AsyncCallback, state: obj) : IAsyncResult
Socket.BeginAccept(receiveSize: int, callback: AsyncCallback, state: obj) : IAsyncResult
Socket.BeginAccept(acceptSocket: Socket, receiveSize: int, callback: AsyncCallback, state: obj) : IAsyncResult
Socket.EndAccept(asyncResult: IAsyncResult) : Socket
Socket.EndAccept(buffer: byref<byte []>, asyncResult: IAsyncResult) : Socket
Socket.EndAccept(buffer: byref<byte []>, bytesTransferred: byref<int>, asyncResult: IAsyncResult) : Socket
val stream : NetworkStream
Multiple items
type NetworkStream =
  inherit Stream
  new : socket:Socket -> NetworkStream + 3 overloads
  member BeginRead : buffer:byte[] * offset:int * size:int * callback:AsyncCallback * state:obj -> IAsyncResult
  member BeginWrite : buffer:byte[] * offset:int * size:int * callback:AsyncCallback * state:obj -> IAsyncResult
  member CanRead : bool
  member CanSeek : bool
  member CanTimeout : bool
  member CanWrite : bool
  member Close : timeout:int -> unit
  member DataAvailable : bool
  member EndRead : asyncResult:IAsyncResult -> int
  ...

Full name: System.Net.Sockets.NetworkStream

--------------------
NetworkStream(socket: Socket) : unit
NetworkStream(socket: Socket, ownsSocket: bool) : unit
NetworkStream(socket: Socket, access: FileAccess) : unit
NetworkStream(socket: Socket, access: FileAccess, ownsSocket: bool) : unit
property Socket.RemoteEndPoint: EndPoint
val startServer : unit -> unit

Full name: Tracker.startServer
static member Async.Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
property CancellationTokenSource.Token: CancellationToken