mirror of
https://github.com/luxfi/p2p.git
synced 2026-10-30 15:22:55 +00:00
213 lines
4.8 KiB
Go
213 lines
4.8 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package p2p
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/metric"
|
|
|
|
validators "github.com/luxfi/consensus/validator"
|
|
consensusversion "github.com/luxfi/consensus/version"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/math/set"
|
|
)
|
|
|
|
var (
|
|
_ validators.Connector = (*Network)(nil)
|
|
_ NodeSampler = (*PeerSampler)(nil)
|
|
|
|
labelNames = []string{opLabel, handlerLabel}
|
|
)
|
|
|
|
// ClientOption configures Client
|
|
type ClientOption interface {
|
|
apply(options *clientOptions)
|
|
}
|
|
|
|
type clientOptionFunc func(options *clientOptions)
|
|
|
|
func (o clientOptionFunc) apply(options *clientOptions) {
|
|
o(options)
|
|
}
|
|
|
|
// WithValidatorSampling configures Client.RequestAny to sample validators.
|
|
// If validators is nil, the default PeerSampler is kept.
|
|
func WithValidatorSampling(validators *Validators) ClientOption {
|
|
return clientOptionFunc(func(options *clientOptions) {
|
|
if validators != nil {
|
|
options.nodeSampler = validators
|
|
}
|
|
})
|
|
}
|
|
|
|
// clientOptions holds client-configurable values
|
|
type clientOptions struct {
|
|
// nodeSampler is used to select nodes to route Client.RequestAny to
|
|
nodeSampler NodeSampler
|
|
}
|
|
|
|
// NewNetwork returns an instance of Network
|
|
func NewNetwork(
|
|
log log.Logger,
|
|
sender Sender,
|
|
registerer metric.Registerer,
|
|
namespace string,
|
|
) (*Network, error) {
|
|
m := metrics{
|
|
msgTime: metric.NewGaugeVec(
|
|
metric.GaugeOpts{
|
|
Namespace: namespace,
|
|
Name: "msg_time",
|
|
Help: "message handling time (ns)",
|
|
},
|
|
labelNames,
|
|
),
|
|
msgCount: metric.NewCounterVec(
|
|
metric.CounterOpts{
|
|
Namespace: namespace,
|
|
Name: "msg_count",
|
|
Help: "message count (n)",
|
|
},
|
|
labelNames,
|
|
),
|
|
}
|
|
|
|
err := errors.Join()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Network{
|
|
Peers: &Peers{
|
|
set: set.NewSampleableSet[ids.NodeID](0),
|
|
},
|
|
log: log,
|
|
sender: sender,
|
|
router: newRouter(log, sender, m),
|
|
}, nil
|
|
}
|
|
|
|
// Network exposes networking state and supports building p2p application
|
|
// protocols
|
|
type Network struct {
|
|
Peers *Peers
|
|
|
|
log log.Logger
|
|
sender Sender
|
|
|
|
router *router
|
|
}
|
|
|
|
func (n *Network) Request(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) ([]byte, *Error) {
|
|
err := n.router.Request(ctx, nodeID, requestID, deadline, request)
|
|
if err != nil {
|
|
return nil, &Error{Code: -1, Message: err.Error()}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (n *Network) Response(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error {
|
|
return n.router.Response(ctx, nodeID, requestID, response)
|
|
}
|
|
|
|
func (n *Network) RequestFailed(ctx context.Context, nodeID ids.NodeID, requestID uint32, appErr *Error) error {
|
|
return n.router.RequestFailed(ctx, nodeID, requestID, appErr)
|
|
}
|
|
|
|
func (n *Network) Gossip(ctx context.Context, nodeID ids.NodeID, msg []byte) error {
|
|
return n.router.Gossip(ctx, nodeID, msg)
|
|
}
|
|
|
|
func (n *Network) Connected(_ context.Context, nodeID ids.NodeID, _ *consensusversion.Application) error {
|
|
n.Peers.add(nodeID)
|
|
return nil
|
|
}
|
|
|
|
func (n *Network) Disconnected(_ context.Context, nodeID ids.NodeID) error {
|
|
n.Peers.remove(nodeID)
|
|
return nil
|
|
}
|
|
|
|
// NewClient returns a Client that can be used to send messages for the
|
|
// corresponding protocol.
|
|
func (n *Network) NewClient(handlerID uint64, options ...ClientOption) *Client {
|
|
client := &Client{
|
|
handlerIDStr: strconv.FormatUint(handlerID, 10),
|
|
handlerPrefix: ProtocolPrefix(handlerID),
|
|
sender: n.sender,
|
|
router: n.router,
|
|
options: &clientOptions{
|
|
nodeSampler: &PeerSampler{
|
|
Peers: n.Peers,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, option := range options {
|
|
option.apply(client.options)
|
|
}
|
|
|
|
return client
|
|
}
|
|
|
|
// AddHandler reserves an identifier for an application protocol
|
|
func (n *Network) AddHandler(handlerID uint64, handler Handler) error {
|
|
return n.router.addHandler(handlerID, handler)
|
|
}
|
|
|
|
// Peers contains metadata about the current set of connected peers
|
|
type Peers struct {
|
|
lock sync.RWMutex
|
|
set set.SampleableSet[ids.NodeID]
|
|
}
|
|
|
|
func (p *Peers) add(nodeID ids.NodeID) {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
p.set.Add(nodeID)
|
|
}
|
|
|
|
func (p *Peers) remove(nodeID ids.NodeID) {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
|
|
p.set.Remove(nodeID)
|
|
}
|
|
|
|
func (p *Peers) has(nodeID ids.NodeID) bool {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
return p.set.Contains(nodeID)
|
|
}
|
|
|
|
// Sample returns a pseudo-random sample of up to limit Peers
|
|
func (p *Peers) Sample(limit int) []ids.NodeID {
|
|
p.lock.RLock()
|
|
defer p.lock.RUnlock()
|
|
|
|
return p.set.Sample(limit)
|
|
}
|
|
|
|
// PeerSampler implements NodeSampler
|
|
type PeerSampler struct {
|
|
Peers *Peers
|
|
}
|
|
|
|
func (p PeerSampler) Sample(_ context.Context, limit int) []ids.NodeID {
|
|
return p.Peers.Sample(limit)
|
|
}
|
|
|
|
func ProtocolPrefix(handlerID uint64) []byte {
|
|
return binary.AppendUvarint(nil, handlerID)
|
|
}
|