2023-08-21 03:50:38 +00:00
|
|
|
package autorelay
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
|
|
)
|
|
|
|
|
|
|
|
// AutoRelay will call this function when it needs new candidates because it is
|
|
|
|
// not connected to the desired number of relays or we get disconnected from one
|
|
|
|
// of the relays. Implementations must send *at most* numPeers, and close the
|
|
|
|
// channel when they don't intend to provide any more peers. AutoRelay will not
|
|
|
|
// call the callback again until the channel is closed. Implementations should
|
|
|
|
// send new peers, but may send peers they sent before. AutoRelay implements a
|
|
|
|
// per-peer backoff (see WithBackoff). See WithMinInterval for setting the
|
|
|
|
// minimum interval between calls to the callback. The context.Context passed
|
|
|
|
// may be canceled when AutoRelay feels satisfied, it will be canceled when the
|
|
|
|
// node is shutting down. If the context is canceled you MUST close the output
|
|
|
|
// channel at some point.
|
|
|
|
type PeerSource func(ctx context.Context, num int) <-chan peer.AddrInfo
|
|
|
|
|
|
|
|
type config struct {
|
|
|
|
clock ClockWithInstantTimer
|
|
|
|
peerSource PeerSource
|
|
|
|
// minimum interval used to call the peerSource callback
|
|
|
|
minInterval time.Duration
|
|
|
|
// see WithMinCandidates
|
|
|
|
minCandidates int
|
|
|
|
// see WithMaxCandidates
|
|
|
|
maxCandidates int
|
|
|
|
// Delay until we obtain reservations with relays, if we have less than minCandidates candidates.
|
|
|
|
// See WithBootDelay.
|
|
|
|
bootDelay time.Duration
|
|
|
|
// backoff is the time we wait after failing to obtain a reservation with a candidate
|
|
|
|
backoff time.Duration
|
|
|
|
// Number of relays we strive to obtain a reservation with.
|
|
|
|
desiredRelays int
|
|
|
|
// see WithMaxCandidateAge
|
|
|
|
maxCandidateAge time.Duration
|
|
|
|
setMinCandidates bool
|
|
|
|
// see WithMetricsTracer
|
|
|
|
metricsTracer MetricsTracer
|
|
|
|
}
|
|
|
|
|
|
|
|
var defaultConfig = config{
|
|
|
|
clock: RealClock{},
|
|
|
|
minCandidates: 4,
|
|
|
|
maxCandidates: 20,
|
|
|
|
bootDelay: 3 * time.Minute,
|
|
|
|
backoff: time.Hour,
|
|
|
|
desiredRelays: 2,
|
|
|
|
maxCandidateAge: 30 * time.Minute,
|
|
|
|
minInterval: 30 * time.Second,
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
errAlreadyHavePeerSource = errors.New("can only use a single WithPeerSource or WithStaticRelays")
|
|
|
|
)
|
|
|
|
|
|
|
|
type Option func(*config) error
|
|
|
|
|
|
|
|
func WithStaticRelays(static []peer.AddrInfo) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
if c.peerSource != nil {
|
|
|
|
return errAlreadyHavePeerSource
|
|
|
|
}
|
|
|
|
|
|
|
|
WithPeerSource(func(ctx context.Context, numPeers int) <-chan peer.AddrInfo {
|
|
|
|
if len(static) < numPeers {
|
|
|
|
numPeers = len(static)
|
|
|
|
}
|
|
|
|
c := make(chan peer.AddrInfo, numPeers)
|
|
|
|
|
|
|
|
for i := 0; i < numPeers; i++ {
|
|
|
|
c <- static[i]
|
|
|
|
}
|
2024-10-12 18:55:17 +00:00
|
|
|
close(c)
|
2023-08-21 03:50:38 +00:00
|
|
|
return c
|
|
|
|
})(c)
|
|
|
|
WithMinCandidates(len(static))(c)
|
|
|
|
WithMaxCandidates(len(static))(c)
|
|
|
|
WithNumRelays(len(static))(c)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithPeerSource defines a callback for AutoRelay to query for more relay candidates.
|
|
|
|
func WithPeerSource(f PeerSource) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
if c.peerSource != nil {
|
|
|
|
return errAlreadyHavePeerSource
|
|
|
|
}
|
|
|
|
c.peerSource = f
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithNumRelays sets the number of relays we strive to obtain reservations with.
|
|
|
|
func WithNumRelays(n int) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.desiredRelays = n
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMaxCandidates sets the number of relay candidates that we buffer.
|
|
|
|
func WithMaxCandidates(n int) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.maxCandidates = n
|
|
|
|
if c.minCandidates > n {
|
|
|
|
c.minCandidates = n
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMinCandidates sets the minimum number of relay candidates we collect before to get a reservation
|
|
|
|
// with any of them (unless we've been running for longer than the boot delay).
|
|
|
|
// This is to make sure that we don't just randomly connect to the first candidate that we discover.
|
|
|
|
func WithMinCandidates(n int) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
if n > c.maxCandidates {
|
|
|
|
n = c.maxCandidates
|
|
|
|
}
|
|
|
|
c.minCandidates = n
|
|
|
|
c.setMinCandidates = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithBootDelay set the boot delay for finding relays.
|
|
|
|
// We won't attempt any reservation if we've have less than a minimum number of candidates.
|
|
|
|
// This prevents us to connect to the "first best" relay, and allows us to carefully select the relay.
|
|
|
|
// However, in case we haven't found enough relays after the boot delay, we use what we have.
|
|
|
|
func WithBootDelay(d time.Duration) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.bootDelay = d
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithBackoff sets the time we wait after failing to obtain a reservation with a candidate.
|
|
|
|
func WithBackoff(d time.Duration) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.backoff = d
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMaxCandidateAge sets the maximum age of a candidate.
|
|
|
|
// When we are connected to the desired number of relays, we don't ask the peer source for new candidates.
|
|
|
|
// This can lead to AutoRelay's candidate list becoming outdated, and means we won't be able
|
|
|
|
// to quickly establish a new relay connection if our existing connection breaks, if all the candidates
|
|
|
|
// have become stale.
|
|
|
|
func WithMaxCandidateAge(d time.Duration) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.maxCandidateAge = d
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// InstantTimer is a timer that triggers at some instant rather than some duration
|
|
|
|
type InstantTimer interface {
|
|
|
|
Reset(d time.Time) bool
|
|
|
|
Stop() bool
|
|
|
|
Ch() <-chan time.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClockWithInstantTimer is a clock that can create timers that trigger at some
|
|
|
|
// instant rather than some duration
|
|
|
|
type ClockWithInstantTimer interface {
|
|
|
|
Now() time.Time
|
|
|
|
Since(t time.Time) time.Duration
|
|
|
|
InstantTimer(when time.Time) InstantTimer
|
|
|
|
}
|
|
|
|
|
|
|
|
type RealTimer struct{ t *time.Timer }
|
|
|
|
|
|
|
|
var _ InstantTimer = (*RealTimer)(nil)
|
|
|
|
|
|
|
|
func (t RealTimer) Ch() <-chan time.Time {
|
|
|
|
return t.t.C
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t RealTimer) Reset(d time.Time) bool {
|
|
|
|
return t.t.Reset(time.Until(d))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t RealTimer) Stop() bool {
|
|
|
|
return t.t.Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
type RealClock struct{}
|
|
|
|
|
|
|
|
var _ ClockWithInstantTimer = RealClock{}
|
|
|
|
|
|
|
|
func (RealClock) Now() time.Time {
|
|
|
|
return time.Now()
|
|
|
|
}
|
|
|
|
func (RealClock) Since(t time.Time) time.Duration {
|
|
|
|
return time.Since(t)
|
|
|
|
}
|
|
|
|
func (RealClock) InstantTimer(when time.Time) InstantTimer {
|
|
|
|
t := time.NewTimer(time.Until(when))
|
|
|
|
return &RealTimer{t}
|
|
|
|
}
|
|
|
|
|
|
|
|
func WithClock(cl ClockWithInstantTimer) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.clock = cl
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMinInterval sets the minimum interval after which peerSource callback will be called for more
|
|
|
|
// candidates even if AutoRelay needs new candidates.
|
|
|
|
func WithMinInterval(interval time.Duration) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.minInterval = interval
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithMetricsTracer configures autorelay to use mt to track metrics
|
|
|
|
func WithMetricsTracer(mt MetricsTracer) Option {
|
|
|
|
return func(c *config) error {
|
|
|
|
c.metricsTracer = mt
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|