mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 07:06:42 +00:00
1986 lines
56 KiB
Go
1986 lines
56 KiB
Go
package discover
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ecdsa"
|
|
crand "crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"math/rand"
|
|
"net"
|
|
"net/netip"
|
|
"slices"
|
|
"sort"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/common/mclock"
|
|
"github.com/ethereum/go-ethereum/metrics"
|
|
"github.com/ethereum/go-ethereum/p2p/discover/v5wire"
|
|
|
|
"github.com/VictoriaMetrics/fastcache"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/p2p/discover/portalwire"
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
|
"github.com/ethereum/go-ethereum/portalnetwork/storage"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
ssz "github.com/ferranbt/fastssz"
|
|
"github.com/holiman/uint256"
|
|
"github.com/optimism-java/utp-go"
|
|
"github.com/optimism-java/utp-go/libutp"
|
|
"github.com/prysmaticlabs/go-bitfield"
|
|
"github.com/tetratelabs/wabin/leb128"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
|
|
// TalkResp message is a response message so the session is established and a
|
|
// regular discv5 packet is assumed for size calculation.
|
|
// Regular message = IV + header + message
|
|
// talkResp message = rlp: [request-id, response]
|
|
talkRespOverhead = 16 + // IV size
|
|
55 + // header size
|
|
1 + // talkResp msg id
|
|
3 + // rlp encoding outer list, max length will be encoded in 2 bytes
|
|
9 + // request id (max = 8) + 1 byte from rlp encoding byte string
|
|
3 + // rlp encoding response byte string, max length in 2 bytes
|
|
16 // HMAC
|
|
|
|
portalFindnodesResultLimit = 32
|
|
|
|
defaultUTPConnectTimeout = 15 * time.Second
|
|
|
|
defaultUTPWriteTimeout = 60 * time.Second
|
|
|
|
defaultUTPReadTimeout = 60 * time.Second
|
|
|
|
// These are the concurrent offers per Portal wire protocol that is running.
|
|
// Using the `offerQueue` allows for limiting the amount of offers send and
|
|
// thus how many streams can be started.
|
|
// TODO:
|
|
// More thought needs to go into this as it is currently on a per network
|
|
// basis. Keep it simple like that? Or limit it better at the stream transport
|
|
// level? In the latter case, this might still need to be checked/blocked at
|
|
// the very start of sending the offer, because blocking/waiting too long
|
|
// between the received accept message and actually starting the stream and
|
|
// sending data could give issues due to timeouts on the other side.
|
|
// And then there are still limits to be applied also for FindContent and the
|
|
// incoming directions.
|
|
concurrentOffers = 50
|
|
)
|
|
|
|
const (
|
|
TransientOfferRequestKind byte = 0x01
|
|
PersistOfferRequestKind byte = 0x02
|
|
)
|
|
|
|
type ClientTag string
|
|
|
|
func (c ClientTag) ENRKey() string { return "c" }
|
|
|
|
const Tag ClientTag = "shisui"
|
|
|
|
var ErrNilContentKey = errors.New("content key cannot be nil")
|
|
|
|
var ContentNotFound = storage.ErrContentNotFound
|
|
|
|
var ErrEmptyResp = errors.New("empty resp")
|
|
|
|
var MaxDistance = hexutil.MustDecode("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
|
|
|
|
type ContentElement struct {
|
|
Node enode.ID
|
|
ContentKeys [][]byte
|
|
Contents [][]byte
|
|
}
|
|
|
|
type ContentEntry struct {
|
|
ContentKey []byte
|
|
Content []byte
|
|
}
|
|
|
|
type TransientOfferRequest struct {
|
|
Contents []*ContentEntry
|
|
}
|
|
|
|
type PersistOfferRequest struct {
|
|
ContentKeys [][]byte
|
|
}
|
|
|
|
type OfferRequest struct {
|
|
Kind byte
|
|
Request interface{}
|
|
}
|
|
|
|
type OfferRequestWithNode struct {
|
|
Request *OfferRequest
|
|
Node *enode.Node
|
|
}
|
|
|
|
type ContentInfoResp struct {
|
|
Content []byte
|
|
UtpTransfer bool
|
|
}
|
|
|
|
type traceContentInfoResp struct {
|
|
Node *enode.Node
|
|
Flag byte
|
|
Content any
|
|
UtpTransfer bool
|
|
}
|
|
|
|
type PortalProtocolOption func(p *PortalProtocol)
|
|
|
|
type PortalProtocolConfig struct {
|
|
BootstrapNodes []*enode.Node
|
|
// NodeIP net.IP
|
|
ListenAddr string
|
|
NetRestrict *netutil.Netlist
|
|
NodeRadius *uint256.Int
|
|
RadiusCacheSize int
|
|
NodeDBPath string
|
|
NAT nat.Interface
|
|
clock mclock.Clock
|
|
}
|
|
|
|
func DefaultPortalProtocolConfig() *PortalProtocolConfig {
|
|
return &PortalProtocolConfig{
|
|
BootstrapNodes: make([]*enode.Node, 0),
|
|
ListenAddr: ":9009",
|
|
NetRestrict: nil,
|
|
RadiusCacheSize: 32 * 1024 * 1024,
|
|
NodeDBPath: "",
|
|
clock: mclock.System{},
|
|
}
|
|
}
|
|
|
|
type PortalProtocol struct {
|
|
table *Table
|
|
|
|
protocolId string
|
|
protocolName string
|
|
|
|
DiscV5 *UDPv5
|
|
utp *utp.Listener
|
|
utpSm *utp.SocketManager
|
|
packetRouter *utp.PacketRouter
|
|
connIdGen libutp.ConnIdGenerator
|
|
ListenAddr string
|
|
localNode *enode.LocalNode
|
|
Log log.Logger
|
|
PrivateKey *ecdsa.PrivateKey
|
|
NetRestrict *netutil.Netlist
|
|
BootstrapNodes []*enode.Node
|
|
conn UDPConn
|
|
|
|
validSchemes enr.IdentityScheme
|
|
radiusCache *fastcache.Cache
|
|
closeCtx context.Context
|
|
cancelCloseCtx context.CancelFunc
|
|
storage storage.ContentStorage
|
|
toContentId func(contentKey []byte) []byte
|
|
|
|
contentQueue chan *ContentElement
|
|
offerQueue chan *OfferRequestWithNode
|
|
|
|
portMappingRegister chan *portMapping
|
|
clock mclock.Clock
|
|
NAT nat.Interface
|
|
|
|
portalMetrics *portalMetrics
|
|
}
|
|
|
|
func defaultContentIdFunc(contentKey []byte) []byte {
|
|
digest := sha256.Sum256(contentKey)
|
|
return digest[:]
|
|
}
|
|
|
|
func NewPortalProtocol(config *PortalProtocolConfig, protocolId portalwire.ProtocolId, privateKey *ecdsa.PrivateKey, conn UDPConn, localNode *enode.LocalNode, discV5 *UDPv5, storage storage.ContentStorage, contentQueue chan *ContentElement, opts ...PortalProtocolOption) (*PortalProtocol, error) {
|
|
closeCtx, cancelCloseCtx := context.WithCancel(context.Background())
|
|
|
|
protocol := &PortalProtocol{
|
|
protocolId: string(protocolId),
|
|
protocolName: protocolId.Name(),
|
|
ListenAddr: config.ListenAddr,
|
|
Log: log.New("protocol", protocolId.Name()),
|
|
PrivateKey: privateKey,
|
|
NetRestrict: config.NetRestrict,
|
|
BootstrapNodes: config.BootstrapNodes,
|
|
radiusCache: fastcache.New(config.RadiusCacheSize),
|
|
closeCtx: closeCtx,
|
|
cancelCloseCtx: cancelCloseCtx,
|
|
localNode: localNode,
|
|
validSchemes: enode.ValidSchemes,
|
|
storage: storage,
|
|
toContentId: defaultContentIdFunc,
|
|
contentQueue: contentQueue,
|
|
offerQueue: make(chan *OfferRequestWithNode, concurrentOffers),
|
|
conn: conn,
|
|
DiscV5: discV5,
|
|
NAT: config.NAT,
|
|
clock: config.clock,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(protocol)
|
|
}
|
|
|
|
if metrics.Enabled {
|
|
protocol.portalMetrics = newPortalMetrics(protocolId.Name())
|
|
}
|
|
|
|
return protocol, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) Start() error {
|
|
p.setupPortMapping()
|
|
|
|
err := p.setupDiscV5AndTable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.DiscV5.RegisterTalkHandler(p.protocolId, p.handleTalkRequest)
|
|
p.DiscV5.RegisterTalkHandler(string(portalwire.Utp), p.handleUtpTalkRequest)
|
|
|
|
go p.table.loop()
|
|
|
|
for i := 0; i < concurrentOffers; i++ {
|
|
go p.offerWorker()
|
|
}
|
|
|
|
// wait for both initialization processes to complete
|
|
<-p.DiscV5.tab.initDone
|
|
<-p.table.initDone
|
|
return nil
|
|
}
|
|
|
|
func (p *PortalProtocol) Stop() {
|
|
p.cancelCloseCtx()
|
|
p.table.close()
|
|
p.DiscV5.Close()
|
|
err := p.utp.Close()
|
|
if err != nil {
|
|
p.Log.Error("failed to close utp listener", "err", err)
|
|
}
|
|
}
|
|
func (p *PortalProtocol) RoutingTableInfo() [][]string {
|
|
p.table.mutex.Lock()
|
|
defer p.table.mutex.Unlock()
|
|
nodes := make([][]string, 0)
|
|
for _, b := range &p.table.buckets {
|
|
bucketNodes := make([]string, 0)
|
|
for _, n := range b.entries {
|
|
bucketNodes = append(bucketNodes, "0x"+n.ID().String())
|
|
}
|
|
nodes = append(nodes, bucketNodes)
|
|
}
|
|
p.Log.Trace("routingTableInfo resp:", "nodes", nodes)
|
|
return nodes
|
|
}
|
|
|
|
func (p *PortalProtocol) AddEnr(n *enode.Node) {
|
|
// immediately add the node to the routing table
|
|
p.table.mutex.Lock()
|
|
defer p.table.mutex.Unlock()
|
|
added := p.table.handleAddNode(addNodeOp{node: n, isInbound: true, forceSetLive: true})
|
|
if !added {
|
|
p.Log.Warn("add node failed", "id", n.ID(), "ip", n.IPAddr())
|
|
return
|
|
}
|
|
id := n.ID().String()
|
|
p.radiusCache.Set([]byte(id), MaxDistance)
|
|
}
|
|
|
|
func (p *PortalProtocol) Radius() *uint256.Int {
|
|
return p.storage.Radius()
|
|
}
|
|
|
|
func (p *PortalProtocol) setupUDPListening() error {
|
|
laddr := p.conn.LocalAddr().(*net.UDPAddr)
|
|
p.localNode.SetFallbackUDP(laddr.Port)
|
|
p.Log.Debug("UDP listener up", "addr", laddr)
|
|
// TODO: NAT
|
|
if !laddr.IP.IsLoopback() && !laddr.IP.IsPrivate() {
|
|
p.portMappingRegister <- &portMapping{
|
|
protocol: "UDP",
|
|
name: "ethereum portal peer discovery",
|
|
port: laddr.Port,
|
|
}
|
|
}
|
|
|
|
var err error
|
|
p.packetRouter = utp.NewPacketRouter(
|
|
func(buf []byte, addr *net.UDPAddr) (int, error) {
|
|
p.Log.Info("will send to target data", "ip", addr.IP.To4().String(), "port", addr.Port, "bufLength", len(buf))
|
|
|
|
if n, ok := p.DiscV5.GetCachedNode(addr.String()); ok {
|
|
//_, err := p.DiscV5.TalkRequestToID(id, addr, string(portalwire.UTPNetwork), buf)
|
|
req := &v5wire.TalkRequest{Protocol: string(portalwire.Utp), Message: buf}
|
|
p.DiscV5.sendFromAnotherThreadWithNode(n, netip.AddrPortFrom(netutil.IPToAddr(addr.IP), uint16(addr.Port)), req)
|
|
|
|
return len(buf), err
|
|
} else {
|
|
p.Log.Warn("not found target node info", "ip", addr.IP.To4().String(), "port", addr.Port, "bufLength", len(buf))
|
|
return 0, fmt.Errorf("not found target node id")
|
|
}
|
|
})
|
|
|
|
ctx := context.Background()
|
|
var logger *zap.Logger
|
|
|
|
if p.Log.Enabled(ctx, log.LevelDebug) || p.Log.Enabled(ctx, log.LevelTrace) {
|
|
logger, err = zap.NewDevelopmentConfig().Build()
|
|
} else {
|
|
logger, err = zap.NewProductionConfig().Build()
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.utpSm, err = utp.NewSocketManagerWithOptions("utp", laddr, utp.WithLogger(logger.Named(p.ListenAddr)), utp.WithPacketRouter(p.packetRouter), utp.WithMaxPacketSize(1145))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.utp, err = utp.ListenUTPOptions("utp", (*utp.Addr)(laddr), utp.WithSocketManager(p.utpSm))
|
|
|
|
p.connIdGen = utp.NewConnIdGenerator()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *PortalProtocol) setupDiscV5AndTable() error {
|
|
err := p.setupUDPListening()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg := Config{
|
|
PrivateKey: p.PrivateKey,
|
|
NetRestrict: p.NetRestrict,
|
|
Bootnodes: p.BootstrapNodes,
|
|
Log: p.Log,
|
|
}
|
|
|
|
p.table, err = newTable(p, p.localNode.Database(), cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PortalProtocol) ping(node *enode.Node) (uint64, error) {
|
|
pong, err := p.pingInner(node)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return pong.EnrSeq, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) pingInner(node *enode.Node) (*portalwire.Pong, error) {
|
|
enrSeq := p.Self().Seq()
|
|
radiusBytes, err := p.Radius().MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
customPayload := &portalwire.PingPongCustomData{
|
|
Radius: radiusBytes,
|
|
}
|
|
|
|
customPayloadBytes, err := customPayload.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pingRequest := &portalwire.Ping{
|
|
EnrSeq: enrSeq,
|
|
CustomPayload: customPayloadBytes,
|
|
}
|
|
|
|
p.Log.Trace(">> PING/"+p.protocolName, "protocol", p.protocolName, "ip", p.Self().IP().String(), "source", p.Self().ID(), "target", node.ID(), "ping", pingRequest)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentPing.Mark(1)
|
|
}
|
|
pingRequestBytes, err := pingRequest.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
talkRequestBytes := make([]byte, 0, len(pingRequestBytes)+1)
|
|
talkRequestBytes = append(talkRequestBytes, portalwire.PING)
|
|
talkRequestBytes = append(talkRequestBytes, pingRequestBytes...)
|
|
|
|
talkResp, err := p.DiscV5.TalkRequest(node, p.protocolId, talkRequestBytes)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.Log.Trace("<< PONG/"+p.protocolName, "source", p.Self().ID(), "target", node.ID(), "res", talkResp)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedPong.Mark(1)
|
|
}
|
|
|
|
return p.processPong(node, talkResp)
|
|
}
|
|
|
|
func (p *PortalProtocol) findNodes(node *enode.Node, distances []uint) ([]*enode.Node, error) {
|
|
if p.localNode.ID().String() == node.ID().String() {
|
|
return make([]*enode.Node, 0), nil
|
|
}
|
|
|
|
distancesBytes := make([][2]byte, len(distances))
|
|
for i, distance := range distances {
|
|
copy(distancesBytes[i][:], ssz.MarshalUint16(make([]byte, 0), uint16(distance)))
|
|
}
|
|
|
|
findNodes := &portalwire.FindNodes{
|
|
Distances: distancesBytes,
|
|
}
|
|
|
|
p.Log.Trace(">> FIND_NODES/"+p.protocolName, "id", node.ID(), "findNodes", findNodes)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentFindNodes.Mark(1)
|
|
}
|
|
findNodesBytes, err := findNodes.MarshalSSZ()
|
|
if err != nil {
|
|
p.Log.Error("failed to marshal find nodes request", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
talkRequestBytes := make([]byte, 0, len(findNodesBytes)+1)
|
|
talkRequestBytes = append(talkRequestBytes, portalwire.FINDNODES)
|
|
talkRequestBytes = append(talkRequestBytes, findNodesBytes...)
|
|
|
|
talkResp, err := p.DiscV5.TalkRequest(node, p.protocolId, talkRequestBytes)
|
|
if err != nil {
|
|
p.Log.Error("failed to send find nodes request", "ip", node.IP().String(), "port", node.UDP(), "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
return p.processNodes(node, talkResp, distances)
|
|
}
|
|
|
|
func (p *PortalProtocol) findContent(node *enode.Node, contentKey []byte) (byte, interface{}, error) {
|
|
findContent := &portalwire.FindContent{
|
|
ContentKey: contentKey,
|
|
}
|
|
|
|
p.Log.Trace(">> FIND_CONTENT/"+p.protocolName, "id", node.ID(), "findContent", findContent)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentFindContent.Mark(1)
|
|
}
|
|
findContentBytes, err := findContent.MarshalSSZ()
|
|
if err != nil {
|
|
p.Log.Error("failed to marshal find content request", "err", err)
|
|
return 0xff, nil, err
|
|
}
|
|
|
|
talkRequestBytes := make([]byte, 0, len(findContentBytes)+1)
|
|
talkRequestBytes = append(talkRequestBytes, portalwire.FINDCONTENT)
|
|
talkRequestBytes = append(talkRequestBytes, findContentBytes...)
|
|
|
|
talkResp, err := p.DiscV5.TalkRequest(node, p.protocolId, talkRequestBytes)
|
|
if err != nil {
|
|
p.Log.Error("failed to send find content request", "ip", node.IP().String(), "port", node.UDP(), "err", err)
|
|
return 0xff, nil, err
|
|
}
|
|
|
|
return p.processContent(node, talkResp)
|
|
}
|
|
|
|
func (p *PortalProtocol) offer(node *enode.Node, offerRequest *OfferRequest) ([]byte, error) {
|
|
contentKeys := getContentKeys(offerRequest)
|
|
|
|
offer := &portalwire.Offer{
|
|
ContentKeys: contentKeys,
|
|
}
|
|
|
|
p.Log.Trace(">> OFFER/"+p.protocolName, "offer", offer)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentOffer.Mark(1)
|
|
}
|
|
offerBytes, err := offer.MarshalSSZ()
|
|
if err != nil {
|
|
p.Log.Error("failed to marshal offer request", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
talkRequestBytes := make([]byte, 0, len(offerBytes)+1)
|
|
talkRequestBytes = append(talkRequestBytes, portalwire.OFFER)
|
|
talkRequestBytes = append(talkRequestBytes, offerBytes...)
|
|
|
|
talkResp, err := p.DiscV5.TalkRequest(node, p.protocolId, talkRequestBytes)
|
|
if err != nil {
|
|
p.Log.Error("failed to send offer request", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
return p.processOffer(node, talkResp, offerRequest)
|
|
}
|
|
|
|
func (p *PortalProtocol) processOffer(target *enode.Node, resp []byte, request *OfferRequest) ([]byte, error) {
|
|
var err error
|
|
if len(resp) == 0 {
|
|
return nil, ErrEmptyResp
|
|
}
|
|
if resp[0] != portalwire.ACCEPT {
|
|
return nil, fmt.Errorf("invalid accept response")
|
|
}
|
|
|
|
p.Log.Info("will process Offer", "id", target.ID(), "ip", target.IP().To4().String(), "port", target.UDP())
|
|
|
|
accept := &portalwire.Accept{}
|
|
err = accept.UnmarshalSSZ(resp[1:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.Log.Trace("<< ACCEPT/"+p.protocolName, "id", target.ID(), "accept", accept)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedAccept.Mark(1)
|
|
}
|
|
isAdded := p.table.addFoundNode(target, true)
|
|
if isAdded {
|
|
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
} else {
|
|
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
}
|
|
var contentKeyLen int
|
|
if request.Kind == TransientOfferRequestKind {
|
|
contentKeyLen = len(request.Request.(*TransientOfferRequest).Contents)
|
|
} else {
|
|
contentKeyLen = len(request.Request.(*PersistOfferRequest).ContentKeys)
|
|
}
|
|
|
|
contentKeyBitlist := bitfield.Bitlist(accept.ContentKeys)
|
|
if contentKeyBitlist.Len() != uint64(contentKeyLen) {
|
|
return nil, fmt.Errorf("accepted content key bitlist has invalid size, expected %d, got %d", contentKeyLen, contentKeyBitlist.Len())
|
|
}
|
|
|
|
if contentKeyBitlist.Count() == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
connId := binary.BigEndian.Uint16(accept.ConnectionId[:])
|
|
go func(ctx context.Context) {
|
|
var conn net.Conn
|
|
defer func() {
|
|
if conn == nil {
|
|
return
|
|
}
|
|
err := conn.Close()
|
|
if err != nil {
|
|
p.Log.Error("failed to close connection", "err", err)
|
|
}
|
|
}()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
contents := make([][]byte, 0, contentKeyBitlist.Count())
|
|
var content []byte
|
|
if request.Kind == TransientOfferRequestKind {
|
|
for _, index := range contentKeyBitlist.BitIndices() {
|
|
content = request.Request.(*TransientOfferRequest).Contents[index].Content
|
|
contents = append(contents, content)
|
|
}
|
|
} else {
|
|
for _, index := range contentKeyBitlist.BitIndices() {
|
|
contentKey := request.Request.(*PersistOfferRequest).ContentKeys[index]
|
|
contentId := p.toContentId(contentKey)
|
|
if contentId != nil {
|
|
content, err = p.storage.Get(contentKey, contentId)
|
|
if err != nil {
|
|
p.Log.Error("failed to get content from storage", "err", err)
|
|
contents = append(contents, []byte{})
|
|
} else {
|
|
contents = append(contents, content)
|
|
}
|
|
} else {
|
|
contents = append(contents, []byte{})
|
|
}
|
|
}
|
|
}
|
|
|
|
var contentsPayload []byte
|
|
contentsPayload, err = encodeContents(contents)
|
|
if err != nil {
|
|
p.Log.Error("failed to encode contents", "err", err)
|
|
return
|
|
}
|
|
|
|
connctx, conncancel := context.WithTimeout(ctx, defaultUTPConnectTimeout)
|
|
laddr := p.utp.Addr().(*utp.Addr)
|
|
raddr := &utp.Addr{IP: target.IP(), Port: target.UDP()}
|
|
p.Log.Info("will connect to: ", "addr", raddr.String(), "connId", connId)
|
|
conn, err = utp.DialUTPOptions("utp", laddr, raddr, utp.WithContext(connctx), utp.WithSocketManager(p.utpSm), utp.WithConnId(uint32(connId)))
|
|
conncancel()
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpOutFailConn.Inc(1)
|
|
}
|
|
p.Log.Error("failed to dial utp connection", "err", err)
|
|
return
|
|
}
|
|
|
|
err = conn.SetWriteDeadline(time.Now().Add(defaultUTPWriteTimeout))
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpOutFailDeadline.Inc(1)
|
|
}
|
|
p.Log.Error("failed to set write deadline", "err", err)
|
|
return
|
|
}
|
|
|
|
var written int
|
|
written, err = conn.Write(contentsPayload)
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpOutFailWrite.Inc(1)
|
|
}
|
|
p.Log.Error("failed to write to utp connection", "err", err)
|
|
return
|
|
}
|
|
p.Log.Trace(">> CONTENT/"+p.protocolName, "id", target.ID(), "contents", contents, "size", written)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentContent.Mark(1)
|
|
p.portalMetrics.utpOutSuccess.Inc(1)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}(p.closeCtx)
|
|
|
|
return accept.ContentKeys, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) processContent(target *enode.Node, resp []byte) (byte, interface{}, error) {
|
|
if len(resp) == 0 {
|
|
return 0x00, nil, ErrEmptyResp
|
|
}
|
|
|
|
if resp[0] != portalwire.CONTENT {
|
|
return 0xff, nil, fmt.Errorf("invalid content response")
|
|
}
|
|
|
|
p.Log.Info("will process content", "id", target.ID(), "ip", target.IP().To4().String(), "port", target.UDP())
|
|
|
|
switch resp[1] {
|
|
case portalwire.ContentRawSelector:
|
|
content := &portalwire.Content{}
|
|
err := content.UnmarshalSSZ(resp[2:])
|
|
if err != nil {
|
|
return 0xff, nil, err
|
|
}
|
|
|
|
p.Log.Trace("<< CONTENT/"+p.protocolName, "id", target.ID(), "content", content)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedContent.Mark(1)
|
|
}
|
|
isAdded := p.table.addFoundNode(target, true)
|
|
if isAdded {
|
|
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
} else {
|
|
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
}
|
|
return resp[1], content.Content, nil
|
|
case portalwire.ContentConnIdSelector:
|
|
connIdMsg := &portalwire.ConnectionId{}
|
|
err := connIdMsg.UnmarshalSSZ(resp[2:])
|
|
if err != nil {
|
|
return 0xff, nil, err
|
|
}
|
|
|
|
p.Log.Trace("<< CONTENT_CONNECTION_ID/"+p.protocolName, "id", target.ID(), "resp", common.Bytes2Hex(resp), "connIdMsg", connIdMsg)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedContent.Mark(1)
|
|
}
|
|
isAdded := p.table.addFoundNode(target, true)
|
|
if isAdded {
|
|
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
} else {
|
|
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
}
|
|
connctx, conncancel := context.WithTimeout(p.closeCtx, defaultUTPConnectTimeout)
|
|
laddr := p.utp.Addr().(*utp.Addr)
|
|
raddr := &utp.Addr{IP: target.IP(), Port: target.UDP()}
|
|
connId := binary.BigEndian.Uint16(connIdMsg.Id[:])
|
|
p.Log.Info("will connect to: ", "addr", raddr.String(), "connId", connId)
|
|
conn, err := utp.DialUTPOptions("utp", laddr, raddr, utp.WithContext(connctx), utp.WithSocketManager(p.utpSm), utp.WithConnId(uint32(connId)))
|
|
defer func() {
|
|
if conn == nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpInFailConn.Inc(1)
|
|
}
|
|
return
|
|
}
|
|
err := conn.Close()
|
|
if err != nil {
|
|
p.Log.Error("failed to close connection", "err", err)
|
|
}
|
|
}()
|
|
conncancel()
|
|
if err != nil {
|
|
return 0xff, nil, err
|
|
}
|
|
|
|
err = conn.SetReadDeadline(time.Now().Add(defaultUTPReadTimeout))
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpInFailDeadline.Inc(1)
|
|
}
|
|
return 0xff, nil, err
|
|
}
|
|
// Read ALL the data from the connection until EOF and return it
|
|
data, err := io.ReadAll(conn)
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpInFailRead.Inc(1)
|
|
}
|
|
p.Log.Error("failed to read from utp connection", "err", err)
|
|
return 0xff, nil, err
|
|
}
|
|
p.Log.Trace("<< CONTENT/"+p.protocolName, "id", target.ID(), "size", len(data), "data", data)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedContent.Mark(1)
|
|
p.portalMetrics.utpInSuccess.Inc(1)
|
|
}
|
|
return resp[1], data, nil
|
|
case portalwire.ContentEnrsSelector:
|
|
enrs := &portalwire.Enrs{}
|
|
err := enrs.UnmarshalSSZ(resp[2:])
|
|
|
|
if err != nil {
|
|
return 0xff, nil, err
|
|
}
|
|
|
|
p.Log.Trace("<< CONTENT_ENRS/"+p.protocolName, "id", target.ID(), "enrs", enrs)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedContent.Mark(1)
|
|
}
|
|
isAdded := p.table.addFoundNode(target, true)
|
|
if isAdded {
|
|
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
} else {
|
|
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
}
|
|
nodes := p.filterNodes(target, enrs.Enrs, nil)
|
|
return resp[1], nodes, nil
|
|
default:
|
|
return 0xff, nil, fmt.Errorf("invalid content response")
|
|
}
|
|
}
|
|
|
|
func (p *PortalProtocol) processNodes(target *enode.Node, resp []byte, distances []uint) ([]*enode.Node, error) {
|
|
if len(resp) == 0 {
|
|
return nil, ErrEmptyResp
|
|
}
|
|
|
|
if resp[0] != portalwire.NODES {
|
|
return nil, fmt.Errorf("invalid nodes response")
|
|
}
|
|
|
|
nodesResp := &portalwire.Nodes{}
|
|
err := nodesResp.UnmarshalSSZ(resp[1:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
isAdded := p.table.addFoundNode(target, true)
|
|
if isAdded {
|
|
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
} else {
|
|
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
}
|
|
nodes := p.filterNodes(target, nodesResp.Enrs, distances)
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) filterNodes(target *enode.Node, enrs [][]byte, distances []uint) []*enode.Node {
|
|
var (
|
|
nodes []*enode.Node
|
|
seen = make(map[enode.ID]struct{})
|
|
err error
|
|
verified = 0
|
|
n *enode.Node
|
|
)
|
|
|
|
for _, b := range enrs {
|
|
record := &enr.Record{}
|
|
err = rlp.DecodeBytes(b, record)
|
|
if err != nil {
|
|
p.Log.Error("Invalid record in nodes response", "id", target.ID(), "err", err)
|
|
continue
|
|
}
|
|
n, err = p.verifyResponseNode(target, record, distances, seen)
|
|
if err != nil {
|
|
p.Log.Error("Invalid record in nodes response", "id", target.ID(), "err", err)
|
|
continue
|
|
}
|
|
verified++
|
|
nodes = append(nodes, n)
|
|
}
|
|
|
|
p.Log.Trace("<< NODES/"+p.protocolName, "id", target.ID(), "total", len(enrs), "verified", verified, "nodes", nodes)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedNodes.Mark(1)
|
|
}
|
|
return nodes
|
|
}
|
|
|
|
func (p *PortalProtocol) processPong(target *enode.Node, resp []byte) (*portalwire.Pong, error) {
|
|
if len(resp) == 0 {
|
|
return nil, ErrEmptyResp
|
|
}
|
|
if resp[0] != portalwire.PONG {
|
|
return nil, fmt.Errorf("invalid pong response")
|
|
}
|
|
pong := &portalwire.Pong{}
|
|
err := pong.UnmarshalSSZ(resp[1:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.Log.Trace("<< PONG_RESPONSE/"+p.protocolName, "id", target.ID(), "pong", pong)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedPong.Mark(1)
|
|
}
|
|
|
|
customPayload := &portalwire.PingPongCustomData{}
|
|
err = customPayload.UnmarshalSSZ(pong.CustomPayload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.Log.Trace("<< PONG_RESPONSE/"+p.protocolName, "id", target.ID(), "pong", pong, "customPayload", customPayload)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedPong.Mark(1)
|
|
}
|
|
isAdded := p.table.addFoundNode(target, true)
|
|
if isAdded {
|
|
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
} else {
|
|
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", target.IP(), "port", target.UDP())
|
|
}
|
|
|
|
p.radiusCache.Set([]byte(target.ID().String()), customPayload.Radius)
|
|
return pong, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) handleUtpTalkRequest(id enode.ID, addr *net.UDPAddr, msg []byte) []byte {
|
|
if n := p.DiscV5.getNode(id); n != nil {
|
|
p.table.addInboundNode(n)
|
|
}
|
|
p.Log.Trace("receive utp data", "addr", addr, "msg-length", len(msg))
|
|
p.packetRouter.ReceiveMessage(msg, addr)
|
|
return []byte("")
|
|
}
|
|
|
|
func (p *PortalProtocol) handleTalkRequest(id enode.ID, addr *net.UDPAddr, msg []byte) []byte {
|
|
if n := p.DiscV5.getNode(id); n != nil {
|
|
p.table.addInboundNode(n)
|
|
}
|
|
|
|
msgCode := msg[0]
|
|
|
|
switch msgCode {
|
|
case portalwire.PING:
|
|
pingRequest := &portalwire.Ping{}
|
|
err := pingRequest.UnmarshalSSZ(msg[1:])
|
|
if err != nil {
|
|
p.Log.Error("failed to unmarshal ping request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
p.Log.Trace("<< PING/"+p.protocolName, "protocol", p.protocolName, "source", id, "pingRequest", pingRequest)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedPing.Mark(1)
|
|
}
|
|
resp, err := p.handlePing(id, pingRequest)
|
|
if err != nil {
|
|
p.Log.Error("failed to handle ping request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
return resp
|
|
case portalwire.FINDNODES:
|
|
findNodesRequest := &portalwire.FindNodes{}
|
|
err := findNodesRequest.UnmarshalSSZ(msg[1:])
|
|
if err != nil {
|
|
p.Log.Error("failed to unmarshal find nodes request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
p.Log.Trace("<< FIND_NODES/"+p.protocolName, "protocol", p.protocolName, "source", id, "findNodesRequest", findNodesRequest)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedFindNodes.Mark(1)
|
|
}
|
|
resp, err := p.handleFindNodes(addr, findNodesRequest)
|
|
if err != nil {
|
|
p.Log.Error("failed to handle find nodes request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
return resp
|
|
case portalwire.FINDCONTENT:
|
|
findContentRequest := &portalwire.FindContent{}
|
|
err := findContentRequest.UnmarshalSSZ(msg[1:])
|
|
if err != nil {
|
|
p.Log.Error("failed to unmarshal find content request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
p.Log.Trace("<< FIND_CONTENT/"+p.protocolName, "protocol", p.protocolName, "source", id, "findContentRequest", findContentRequest)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedFindContent.Mark(1)
|
|
}
|
|
resp, err := p.handleFindContent(id, addr, findContentRequest)
|
|
if err != nil {
|
|
p.Log.Error("failed to handle find content request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
return resp
|
|
case portalwire.OFFER:
|
|
offerRequest := &portalwire.Offer{}
|
|
err := offerRequest.UnmarshalSSZ(msg[1:])
|
|
if err != nil {
|
|
p.Log.Error("failed to unmarshal offer request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
p.Log.Trace("<< OFFER/"+p.protocolName, "protocol", p.protocolName, "source", id, "offerRequest", offerRequest)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedOffer.Mark(1)
|
|
}
|
|
resp, err := p.handleOffer(id, addr, offerRequest)
|
|
if err != nil {
|
|
p.Log.Error("failed to handle offer request", "err", err)
|
|
return nil
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PortalProtocol) handlePing(id enode.ID, ping *portalwire.Ping) ([]byte, error) {
|
|
pingCustomPayload := &portalwire.PingPongCustomData{}
|
|
err := pingCustomPayload.UnmarshalSSZ(ping.CustomPayload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p.radiusCache.Set([]byte(id.String()), pingCustomPayload.Radius)
|
|
|
|
enrSeq := p.Self().Seq()
|
|
radiusBytes, err := p.Radius().MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pongCustomPayload := &portalwire.PingPongCustomData{
|
|
Radius: radiusBytes,
|
|
}
|
|
|
|
pongCustomPayloadBytes, err := pongCustomPayload.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pong := &portalwire.Pong{
|
|
EnrSeq: enrSeq,
|
|
CustomPayload: pongCustomPayloadBytes,
|
|
}
|
|
|
|
p.Log.Trace(">> PONG/"+p.protocolName, "protocol", p.protocolName, "source", id, "pong", pong)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentPong.Mark(1)
|
|
}
|
|
pongBytes, err := pong.MarshalSSZ()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
talkRespBytes := make([]byte, 0, len(pongBytes)+1)
|
|
talkRespBytes = append(talkRespBytes, portalwire.PONG)
|
|
talkRespBytes = append(talkRespBytes, pongBytes...)
|
|
|
|
return talkRespBytes, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) handleFindNodes(fromAddr *net.UDPAddr, request *portalwire.FindNodes) ([]byte, error) {
|
|
distances := make([]uint, len(request.Distances))
|
|
for i, distance := range request.Distances {
|
|
distances[i] = uint(ssz.UnmarshallUint16(distance[:]))
|
|
}
|
|
|
|
nodes := p.collectTableNodes(fromAddr.IP, distances, portalFindnodesResultLimit)
|
|
|
|
nodesOverhead := 1 + 1 + 4 // msg id + total + container offset
|
|
maxPayloadSize := maxPacketSize - talkRespOverhead - nodesOverhead
|
|
enrOverhead := 4 //per added ENR, 4 bytes offset overhead
|
|
|
|
enrs := p.truncateNodes(nodes, maxPayloadSize, enrOverhead)
|
|
|
|
nodesMsg := &portalwire.Nodes{
|
|
Total: 1,
|
|
Enrs: enrs,
|
|
}
|
|
|
|
p.Log.Trace(">> NODES/"+p.protocolName, "protocol", p.protocolName, "source", fromAddr, "nodes", nodesMsg)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentNodes.Mark(1)
|
|
}
|
|
nodesMsgBytes, err := nodesMsg.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
talkRespBytes := make([]byte, 0, len(nodesMsgBytes)+1)
|
|
talkRespBytes = append(talkRespBytes, portalwire.NODES)
|
|
talkRespBytes = append(talkRespBytes, nodesMsgBytes...)
|
|
|
|
return talkRespBytes, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) handleFindContent(id enode.ID, addr *net.UDPAddr, request *portalwire.FindContent) ([]byte, error) {
|
|
contentOverhead := 1 + 1 // msg id + SSZ Union selector
|
|
maxPayloadSize := maxPacketSize - talkRespOverhead - contentOverhead
|
|
enrOverhead := 4 //per added ENR, 4 bytes offset overhead
|
|
var err error
|
|
contentKey := request.ContentKey
|
|
contentId := p.toContentId(contentKey)
|
|
if contentId == nil {
|
|
return nil, ErrNilContentKey
|
|
}
|
|
|
|
var content []byte
|
|
content, err = p.storage.Get(contentKey, contentId)
|
|
if err != nil && !errors.Is(err, ContentNotFound) {
|
|
return nil, err
|
|
}
|
|
|
|
if errors.Is(err, ContentNotFound) {
|
|
closestNodes := p.findNodesCloseToContent(contentId, portalFindnodesResultLimit)
|
|
for i, n := range closestNodes {
|
|
if n.ID() == id {
|
|
closestNodes = append(closestNodes[:i], closestNodes[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
enrs := p.truncateNodes(closestNodes, maxPayloadSize, enrOverhead)
|
|
// TODO fix when no content and no enrs found
|
|
if len(enrs) == 0 {
|
|
enrs = nil
|
|
}
|
|
|
|
enrsMsg := &portalwire.Enrs{
|
|
Enrs: enrs,
|
|
}
|
|
|
|
p.Log.Trace(">> CONTENT_ENRS/"+p.protocolName, "protocol", p.protocolName, "source", addr, "enrs", enrsMsg)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentContent.Mark(1)
|
|
}
|
|
var enrsMsgBytes []byte
|
|
enrsMsgBytes, err = enrsMsg.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contentMsgBytes := make([]byte, 0, len(enrsMsgBytes)+1)
|
|
contentMsgBytes = append(contentMsgBytes, portalwire.ContentEnrsSelector)
|
|
contentMsgBytes = append(contentMsgBytes, enrsMsgBytes...)
|
|
|
|
talkRespBytes := make([]byte, 0, len(contentMsgBytes)+1)
|
|
talkRespBytes = append(talkRespBytes, portalwire.CONTENT)
|
|
talkRespBytes = append(talkRespBytes, contentMsgBytes...)
|
|
|
|
return talkRespBytes, nil
|
|
} else if len(content) <= maxPayloadSize {
|
|
rawContentMsg := &portalwire.Content{
|
|
Content: content,
|
|
}
|
|
|
|
p.Log.Trace(">> CONTENT_RAW/"+p.protocolName, "protocol", p.protocolName, "source", addr, "content", rawContentMsg)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentContent.Mark(1)
|
|
}
|
|
|
|
var rawContentMsgBytes []byte
|
|
rawContentMsgBytes, err = rawContentMsg.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contentMsgBytes := make([]byte, 0, len(rawContentMsgBytes)+1)
|
|
contentMsgBytes = append(contentMsgBytes, portalwire.ContentRawSelector)
|
|
contentMsgBytes = append(contentMsgBytes, rawContentMsgBytes...)
|
|
|
|
talkRespBytes := make([]byte, 0, len(contentMsgBytes)+1)
|
|
talkRespBytes = append(talkRespBytes, portalwire.CONTENT)
|
|
talkRespBytes = append(talkRespBytes, contentMsgBytes...)
|
|
|
|
return talkRespBytes, nil
|
|
} else {
|
|
connId := p.connIdGen.GenCid(id, false)
|
|
connIdSend := connId.SendId()
|
|
|
|
go func(bctx context.Context) {
|
|
var conn *utp.Conn
|
|
defer func() {
|
|
p.connIdGen.Remove(connId)
|
|
if conn == nil {
|
|
return
|
|
}
|
|
err := conn.Close()
|
|
if err != nil {
|
|
p.Log.Error("failed to close connection", "err", err)
|
|
}
|
|
}()
|
|
for {
|
|
select {
|
|
case <-bctx.Done():
|
|
return
|
|
default:
|
|
ctx, cancel := context.WithTimeout(bctx, defaultUTPConnectTimeout)
|
|
|
|
p.Log.Debug("will accept find content conn from: ", "source", addr, "connId", connId)
|
|
conn, err = p.utp.AcceptUTPContext(ctx, connIdSend)
|
|
cancel()
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpOutFailConn.Inc(1)
|
|
}
|
|
p.Log.Error("failed to accept utp connection for handle find content", "connId", connIdSend, "err", err)
|
|
return
|
|
}
|
|
|
|
err = conn.SetWriteDeadline(time.Now().Add(defaultUTPWriteTimeout))
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpOutFailDeadline.Inc(1)
|
|
}
|
|
p.Log.Error("failed to set write deadline", "err", err)
|
|
return
|
|
}
|
|
|
|
var n int
|
|
n, err = conn.Write(content)
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpOutFailWrite.Inc(1)
|
|
}
|
|
p.Log.Error("failed to write content to utp connection", "err", err)
|
|
return
|
|
}
|
|
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpOutSuccess.Inc(1)
|
|
}
|
|
p.Log.Trace("wrote content size to utp connection", "n", n)
|
|
return
|
|
}
|
|
}
|
|
}(p.closeCtx)
|
|
|
|
idBuffer := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(idBuffer, uint16(connIdSend))
|
|
connIdMsg := &portalwire.ConnectionId{
|
|
Id: idBuffer,
|
|
}
|
|
|
|
p.Log.Trace(">> CONTENT_CONNECTION_ID/"+p.protocolName, "protocol", p.protocolName, "source", addr, "connId", connIdMsg)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentContent.Mark(1)
|
|
}
|
|
var connIdMsgBytes []byte
|
|
connIdMsgBytes, err = connIdMsg.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contentMsgBytes := make([]byte, 0, len(connIdMsgBytes)+1)
|
|
contentMsgBytes = append(contentMsgBytes, portalwire.ContentConnIdSelector)
|
|
contentMsgBytes = append(contentMsgBytes, connIdMsgBytes...)
|
|
|
|
talkRespBytes := make([]byte, 0, len(contentMsgBytes)+1)
|
|
talkRespBytes = append(talkRespBytes, portalwire.CONTENT)
|
|
talkRespBytes = append(talkRespBytes, contentMsgBytes...)
|
|
|
|
return talkRespBytes, nil
|
|
}
|
|
}
|
|
|
|
func (p *PortalProtocol) handleOffer(id enode.ID, addr *net.UDPAddr, request *portalwire.Offer) ([]byte, error) {
|
|
var err error
|
|
contentKeyBitlist := bitfield.NewBitlist(uint64(len(request.ContentKeys)))
|
|
if len(p.contentQueue) >= cap(p.contentQueue) {
|
|
acceptMsg := &portalwire.Accept{
|
|
ConnectionId: []byte{0, 0},
|
|
ContentKeys: contentKeyBitlist,
|
|
}
|
|
|
|
p.Log.Trace(">> ACCEPT/"+p.protocolName, "protocol", p.protocolName, "source", addr, "accept", acceptMsg)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentAccept.Mark(1)
|
|
}
|
|
var acceptMsgBytes []byte
|
|
acceptMsgBytes, err = acceptMsg.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
talkRespBytes := make([]byte, 0, len(acceptMsgBytes)+1)
|
|
talkRespBytes = append(talkRespBytes, portalwire.ACCEPT)
|
|
talkRespBytes = append(talkRespBytes, acceptMsgBytes...)
|
|
|
|
return talkRespBytes, nil
|
|
}
|
|
|
|
contentKeys := make([][]byte, 0)
|
|
for i, contentKey := range request.ContentKeys {
|
|
contentId := p.toContentId(contentKey)
|
|
if contentId != nil {
|
|
if inRange(p.Self().ID(), p.Radius(), contentId) {
|
|
if _, err = p.storage.Get(contentKey, contentId); err != nil {
|
|
contentKeyBitlist.SetBitAt(uint64(i), true)
|
|
contentKeys = append(contentKeys, contentKey)
|
|
}
|
|
}
|
|
} else {
|
|
return nil, ErrNilContentKey
|
|
}
|
|
}
|
|
|
|
idBuffer := make([]byte, 2)
|
|
if contentKeyBitlist.Count() != 0 {
|
|
connId := p.connIdGen.GenCid(id, false)
|
|
connIdSend := connId.SendId()
|
|
|
|
go func(bctx context.Context) {
|
|
var conn *utp.Conn
|
|
defer func() {
|
|
p.connIdGen.Remove(connId)
|
|
if conn == nil {
|
|
return
|
|
}
|
|
err := conn.Close()
|
|
if err != nil {
|
|
p.Log.Error("failed to close connection", "err", err)
|
|
}
|
|
}()
|
|
for {
|
|
select {
|
|
case <-bctx.Done():
|
|
return
|
|
default:
|
|
ctx, cancel := context.WithTimeout(bctx, defaultUTPConnectTimeout)
|
|
|
|
p.Log.Debug("will accept offer conn from: ", "source", addr, "connId", connId)
|
|
conn, err = p.utp.AcceptUTPContext(ctx, connIdSend)
|
|
cancel()
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpInFailConn.Inc(1)
|
|
}
|
|
p.Log.Error("failed to accept utp connection for handle offer", "connId", connIdSend, "err", err)
|
|
return
|
|
}
|
|
|
|
err = conn.SetReadDeadline(time.Now().Add(defaultUTPReadTimeout))
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpInFailDeadline.Inc(1)
|
|
}
|
|
p.Log.Error("failed to set read deadline", "err", err)
|
|
return
|
|
}
|
|
// Read ALL the data from the connection until EOF and return it
|
|
var data []byte
|
|
data, err = io.ReadAll(conn)
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpInFailRead.Inc(1)
|
|
}
|
|
p.Log.Error("failed to read from utp connection", "err", err)
|
|
return
|
|
}
|
|
p.Log.Trace("<< OFFER_CONTENT/"+p.protocolName, "id", id, "size", len(data), "data", data)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesReceivedContent.Mark(1)
|
|
}
|
|
|
|
err = p.handleOfferedContents(id, contentKeys, data)
|
|
if err != nil {
|
|
p.Log.Error("failed to handle offered Contents", "err", err)
|
|
return
|
|
}
|
|
|
|
if metrics.Enabled {
|
|
p.portalMetrics.utpInSuccess.Inc(1)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}(p.closeCtx)
|
|
|
|
binary.BigEndian.PutUint16(idBuffer, uint16(connIdSend))
|
|
} else {
|
|
binary.BigEndian.PutUint16(idBuffer, uint16(0))
|
|
}
|
|
|
|
acceptMsg := &portalwire.Accept{
|
|
ConnectionId: idBuffer,
|
|
ContentKeys: []byte(contentKeyBitlist),
|
|
}
|
|
|
|
p.Log.Trace(">> ACCEPT/"+p.protocolName, "protocol", p.protocolName, "source", addr, "accept", acceptMsg)
|
|
if metrics.Enabled {
|
|
p.portalMetrics.messagesSentAccept.Mark(1)
|
|
}
|
|
var acceptMsgBytes []byte
|
|
acceptMsgBytes, err = acceptMsg.MarshalSSZ()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
talkRespBytes := make([]byte, 0, len(acceptMsgBytes)+1)
|
|
talkRespBytes = append(talkRespBytes, portalwire.ACCEPT)
|
|
talkRespBytes = append(talkRespBytes, acceptMsgBytes...)
|
|
|
|
return talkRespBytes, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) handleOfferedContents(id enode.ID, keys [][]byte, payload []byte) error {
|
|
contents, err := decodeContents(payload)
|
|
if err != nil {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.contentDecodedFalse.Inc(1)
|
|
}
|
|
return err
|
|
}
|
|
|
|
keyLen := len(keys)
|
|
contentLen := len(contents)
|
|
if keyLen != contentLen {
|
|
if metrics.Enabled {
|
|
p.portalMetrics.contentDecodedFalse.Inc(1)
|
|
}
|
|
return fmt.Errorf("content keys len %d doesn't match content values len %d", keyLen, contentLen)
|
|
}
|
|
|
|
contentElement := &ContentElement{
|
|
Node: id,
|
|
ContentKeys: keys,
|
|
Contents: contents,
|
|
}
|
|
|
|
p.contentQueue <- contentElement
|
|
|
|
if metrics.Enabled {
|
|
p.portalMetrics.contentDecodedTrue.Inc(1)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *PortalProtocol) Self() *enode.Node {
|
|
return p.localNode.Node()
|
|
}
|
|
|
|
func (p *PortalProtocol) RequestENR(n *enode.Node) (*enode.Node, error) {
|
|
nodes, err := p.findNodes(n, []uint{0})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(nodes) != 1 {
|
|
return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes))
|
|
}
|
|
return nodes[0], nil
|
|
}
|
|
|
|
func (p *PortalProtocol) verifyResponseNode(sender *enode.Node, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) {
|
|
n, err := enode.New(p.validSchemes, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = netutil.CheckRelayIP(sender.IP(), n.IP()); err != nil {
|
|
return nil, err
|
|
}
|
|
if p.NetRestrict != nil && !p.NetRestrict.Contains(n.IP()) {
|
|
return nil, errors.New("not contained in netrestrict list")
|
|
}
|
|
if n.UDP() <= 1024 {
|
|
return nil, errLowPort
|
|
}
|
|
if distances != nil {
|
|
nd := enode.LogDist(sender.ID(), n.ID())
|
|
if !slices.Contains(distances, uint(nd)) {
|
|
return nil, errors.New("does not match any requested distance")
|
|
}
|
|
}
|
|
if _, ok := seen[n.ID()]; ok {
|
|
return nil, fmt.Errorf("duplicate record")
|
|
}
|
|
seen[n.ID()] = struct{}{}
|
|
return n, nil
|
|
}
|
|
|
|
// lookupRandom looks up a random target.
|
|
// This is needed to satisfy the transport interface.
|
|
func (p *PortalProtocol) lookupRandom() []*enode.Node {
|
|
return p.newRandomLookup(p.closeCtx).run()
|
|
}
|
|
|
|
// lookupSelf looks up our own node ID.
|
|
// This is needed to satisfy the transport interface.
|
|
func (p *PortalProtocol) lookupSelf() []*enode.Node {
|
|
return p.newLookup(p.closeCtx, p.Self().ID()).run()
|
|
}
|
|
|
|
func (p *PortalProtocol) newRandomLookup(ctx context.Context) *lookup {
|
|
var target enode.ID
|
|
_, _ = crand.Read(target[:])
|
|
return p.newLookup(ctx, target)
|
|
}
|
|
|
|
func (p *PortalProtocol) newLookup(ctx context.Context, target enode.ID) *lookup {
|
|
return newLookup(ctx, p.table, target, func(n *enode.Node) ([]*enode.Node, error) {
|
|
return p.lookupWorker(n, target)
|
|
})
|
|
}
|
|
|
|
// lookupWorker performs FINDNODE calls against a single node during lookup.
|
|
func (p *PortalProtocol) lookupWorker(destNode *enode.Node, target enode.ID) ([]*enode.Node, error) {
|
|
var (
|
|
dists = lookupDistances(target, destNode.ID())
|
|
nodes = nodesByDistance{target: target}
|
|
err error
|
|
)
|
|
var r []*enode.Node
|
|
|
|
r, err = p.findNodes(destNode, dists)
|
|
if errors.Is(err, errClosed) {
|
|
return nil, err
|
|
}
|
|
for _, n := range r {
|
|
if n.ID() != p.Self().ID() {
|
|
isAdded := p.table.addFoundNode(n, false)
|
|
if isAdded {
|
|
log.Debug("Node added to bucket", "protocol", p.protocolName, "node", n.IP(), "port", n.UDP())
|
|
} else {
|
|
log.Debug("Node added to replacements list", "protocol", p.protocolName, "node", n.IP(), "port", n.UDP())
|
|
}
|
|
nodes.push(n, portalFindnodesResultLimit)
|
|
}
|
|
}
|
|
return nodes.entries, err
|
|
}
|
|
|
|
func (p *PortalProtocol) offerWorker() {
|
|
for {
|
|
select {
|
|
case <-p.closeCtx.Done():
|
|
return
|
|
case offerRequestWithNode := <-p.offerQueue:
|
|
p.Log.Trace("offerWorker", "offerRequestWithNode", offerRequestWithNode)
|
|
_, err := p.offer(offerRequestWithNode.Node, offerRequestWithNode.Request)
|
|
if err != nil {
|
|
p.Log.Error("failed to offer", "err", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *PortalProtocol) truncateNodes(nodes []*enode.Node, maxSize int, enrOverhead int) [][]byte {
|
|
res := make([][]byte, 0)
|
|
totalSize := 0
|
|
for _, n := range nodes {
|
|
enrBytes, err := rlp.EncodeToBytes(n.Record())
|
|
if err != nil {
|
|
p.Log.Error("failed to encode n", "err", err)
|
|
continue
|
|
}
|
|
|
|
if totalSize+len(enrBytes)+enrOverhead > maxSize {
|
|
break
|
|
} else {
|
|
res = append(res, enrBytes)
|
|
totalSize += len(enrBytes)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (p *PortalProtocol) findNodesCloseToContent(contentId []byte, limit int) []*enode.Node {
|
|
allNodes := p.table.NodeList()
|
|
sort.Slice(allNodes, func(i, j int) bool {
|
|
return enode.LogDist(allNodes[i].ID(), enode.ID(contentId)) < enode.LogDist(allNodes[j].ID(), enode.ID(contentId))
|
|
})
|
|
|
|
if len(allNodes) > limit {
|
|
allNodes = allNodes[:limit]
|
|
} else {
|
|
allNodes = allNodes[:]
|
|
}
|
|
|
|
return allNodes
|
|
}
|
|
|
|
// Lookup performs a recursive lookup for the given target.
|
|
// It returns the closest nodes to target.
|
|
func (p *PortalProtocol) Lookup(target enode.ID) []*enode.Node {
|
|
return p.newLookup(p.closeCtx, target).run()
|
|
}
|
|
|
|
// Resolve searches for a specific Node with the given ID and tries to get the most recent
|
|
// version of the Node record for it. It returns n if the Node could not be resolved.
|
|
func (p *PortalProtocol) Resolve(n *enode.Node) *enode.Node {
|
|
if intable := p.table.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
|
|
n = intable
|
|
}
|
|
// Try asking directly. This works if the Node is still responding on the endpoint we have.
|
|
if resp, err := p.RequestENR(n); err == nil {
|
|
return resp
|
|
}
|
|
// Otherwise do a network lookup.
|
|
result := p.Lookup(n.ID())
|
|
for _, rn := range result {
|
|
if rn.ID() == n.ID() && rn.Seq() > n.Seq() {
|
|
return rn
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// ResolveNodeId searches for a specific Node with the given ID.
|
|
// It returns nil if the nodeId could not be resolved.
|
|
func (p *PortalProtocol) ResolveNodeId(id enode.ID) *enode.Node {
|
|
if id == p.Self().ID() {
|
|
p.Log.Debug("Resolve Self Id", "id", id.String())
|
|
return p.Self()
|
|
}
|
|
|
|
n := p.table.getNode(id)
|
|
if n != nil {
|
|
p.Log.Debug("found Id in table and will request enr from the node", "id", id.String())
|
|
// Try asking directly. This works if the Node is still responding on the endpoint we have.
|
|
if resp, err := p.RequestENR(n); err == nil {
|
|
return resp
|
|
}
|
|
}
|
|
|
|
// Otherwise do a network lookup.
|
|
result := p.Lookup(id)
|
|
for _, rn := range result {
|
|
if rn.ID() == id {
|
|
if n != nil && rn.Seq() <= n.Seq() {
|
|
return n
|
|
} else {
|
|
return rn
|
|
}
|
|
}
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
func (p *PortalProtocol) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
|
|
var bn []*enode.Node
|
|
var nodes []*enode.Node
|
|
var processed = make(map[uint]struct{})
|
|
for _, dist := range distances {
|
|
// Reject duplicate / invalid distances.
|
|
_, seen := processed[dist]
|
|
if seen || dist > 256 {
|
|
continue
|
|
}
|
|
processed[dist] = struct{}{}
|
|
|
|
checkLive := !p.table.cfg.NoFindnodeLivenessCheck
|
|
for _, n := range p.table.appendBucketNodes(dist, bn[:0], checkLive) {
|
|
// Apply some pre-checks to avoid sending invalid nodes.
|
|
// Note liveness is checked by appendLiveNodes.
|
|
if netutil.CheckRelayIP(rip, n.IP()) != nil {
|
|
continue
|
|
}
|
|
nodes = append(nodes, n)
|
|
if len(nodes) >= limit {
|
|
return nodes
|
|
}
|
|
}
|
|
}
|
|
return nodes
|
|
}
|
|
|
|
func (p *PortalProtocol) ContentLookup(contentKey, contentId []byte) ([]byte, bool, error) {
|
|
lookupContext, cancel := context.WithCancel(context.Background())
|
|
|
|
resChan := make(chan *traceContentInfoResp, alpha)
|
|
hasResult := int32(0)
|
|
|
|
result := ContentInfoResp{}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
for res := range resChan {
|
|
if res.Flag != portalwire.ContentEnrsSelector {
|
|
result.Content = res.Content.([]byte)
|
|
result.UtpTransfer = res.UtpTransfer
|
|
}
|
|
}
|
|
}()
|
|
|
|
newLookup(lookupContext, p.table, enode.ID(contentId), func(n *enode.Node) ([]*enode.Node, error) {
|
|
return p.contentLookupWorker(n, contentKey, resChan, cancel, &hasResult)
|
|
}).run()
|
|
close(resChan)
|
|
|
|
wg.Wait()
|
|
if hasResult == 1 {
|
|
return result.Content, result.UtpTransfer, nil
|
|
}
|
|
defer cancel()
|
|
return nil, false, ContentNotFound
|
|
}
|
|
|
|
func (p *PortalProtocol) TraceContentLookup(contentKey, contentId []byte) (*TraceContentResult, error) {
|
|
lookupContext, cancel := context.WithCancel(context.Background())
|
|
// resp channel
|
|
resChan := make(chan *traceContentInfoResp, alpha)
|
|
|
|
hasResult := int32(0)
|
|
|
|
traceContentRes := &TraceContentResult{}
|
|
|
|
selfHexId := "0x" + p.Self().ID().String()
|
|
|
|
trace := &Trace{
|
|
Origin: selfHexId,
|
|
TargetId: hexutil.Encode(contentId),
|
|
StartedAtMs: int(time.Now().UnixMilli()),
|
|
Responses: make(map[string]RespByNode),
|
|
Metadata: make(map[string]*NodeMetadata),
|
|
Cancelled: make([]string, 0),
|
|
}
|
|
|
|
nodes := p.table.findnodeByID(enode.ID(contentId), bucketSize, false)
|
|
|
|
localResponse := make([]string, 0, len(nodes.entries))
|
|
for _, node := range nodes.entries {
|
|
id := "0x" + node.ID().String()
|
|
localResponse = append(localResponse, id)
|
|
}
|
|
trace.Responses[selfHexId] = RespByNode{
|
|
DurationMs: 0,
|
|
RespondedWith: localResponse,
|
|
}
|
|
|
|
dis := p.Distance(p.Self().ID(), enode.ID(contentId))
|
|
|
|
trace.Metadata[selfHexId] = &NodeMetadata{
|
|
Enr: p.Self().String(),
|
|
Distance: hexutil.Encode(dis[:]),
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
for res := range resChan {
|
|
node := res.Node
|
|
hexId := "0x" + node.ID().String()
|
|
dis := p.Distance(node.ID(), enode.ID(contentId))
|
|
p.Log.Debug("reveice res", "id", hexId, "flag", res.Flag)
|
|
trace.Metadata[hexId] = &NodeMetadata{
|
|
Enr: node.String(),
|
|
Distance: hexutil.Encode(dis[:]),
|
|
}
|
|
// no content return
|
|
if traceContentRes.Content == "" {
|
|
if res.Flag == portalwire.ContentRawSelector || res.Flag == portalwire.ContentConnIdSelector {
|
|
trace.ReceivedFrom = hexId
|
|
content := res.Content.([]byte)
|
|
traceContentRes.Content = hexutil.Encode(content)
|
|
traceContentRes.UtpTransfer = res.UtpTransfer
|
|
trace.Responses[hexId] = RespByNode{}
|
|
} else {
|
|
nodes := res.Content.([]*enode.Node)
|
|
respByNode := RespByNode{
|
|
RespondedWith: make([]string, 0, len(nodes)),
|
|
}
|
|
for _, node := range nodes {
|
|
idInner := "0x" + node.ID().String()
|
|
respByNode.RespondedWith = append(respByNode.RespondedWith, idInner)
|
|
if _, ok := trace.Metadata[idInner]; !ok {
|
|
dis := p.Distance(node.ID(), enode.ID(contentId))
|
|
trace.Metadata[idInner] = &NodeMetadata{
|
|
Enr: node.String(),
|
|
Distance: hexutil.Encode(dis[:]),
|
|
}
|
|
}
|
|
trace.Responses[hexId] = respByNode
|
|
}
|
|
}
|
|
} else {
|
|
trace.Cancelled = append(trace.Cancelled, hexId)
|
|
}
|
|
}
|
|
}()
|
|
|
|
lookup := newLookup(lookupContext, p.table, enode.ID(contentId), func(n *enode.Node) ([]*enode.Node, error) {
|
|
return p.contentLookupWorker(n, contentKey, resChan, cancel, &hasResult)
|
|
})
|
|
lookup.run()
|
|
close(resChan)
|
|
|
|
wg.Wait()
|
|
if hasResult == 0 {
|
|
cancel()
|
|
}
|
|
traceContentRes.Trace = *trace
|
|
|
|
return traceContentRes, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) contentLookupWorker(n *enode.Node, contentKey []byte, resChan chan<- *traceContentInfoResp, cancel context.CancelFunc, done *int32) ([]*enode.Node, error) {
|
|
wrapedNode := make([]*enode.Node, 0)
|
|
flag, content, err := p.findContent(n, contentKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Log.Debug("traceContentLookupWorker reveice response", "ip", n.IP().String(), "flag", flag)
|
|
|
|
switch flag {
|
|
case portalwire.ContentRawSelector, portalwire.ContentConnIdSelector:
|
|
content, ok := content.([]byte)
|
|
if !ok {
|
|
return wrapedNode, fmt.Errorf("failed to assert to raw content, value is: %v", content)
|
|
}
|
|
res := &traceContentInfoResp{
|
|
Node: n,
|
|
Flag: flag,
|
|
Content: content,
|
|
UtpTransfer: false,
|
|
}
|
|
if flag == portalwire.ContentConnIdSelector {
|
|
res.UtpTransfer = true
|
|
}
|
|
if atomic.CompareAndSwapInt32(done, 0, 1) {
|
|
p.Log.Debug("contentLookupWorker find content", "ip", n.IP().String(), "port", n.UDP())
|
|
resChan <- res
|
|
cancel()
|
|
}
|
|
return wrapedNode, err
|
|
case portalwire.ContentEnrsSelector:
|
|
nodes, ok := content.([]*enode.Node)
|
|
if !ok {
|
|
return wrapedNode, fmt.Errorf("failed to assert to enrs content, value is: %v", content)
|
|
}
|
|
resChan <- &traceContentInfoResp{
|
|
Node: n,
|
|
Flag: flag,
|
|
Content: content,
|
|
UtpTransfer: false,
|
|
}
|
|
return nodes, nil
|
|
}
|
|
return wrapedNode, nil
|
|
}
|
|
|
|
func (p *PortalProtocol) ToContentId(contentKey []byte) []byte {
|
|
return p.toContentId(contentKey)
|
|
}
|
|
|
|
func (p *PortalProtocol) InRange(contentId []byte) bool {
|
|
return inRange(p.Self().ID(), p.Radius(), contentId)
|
|
}
|
|
|
|
func (p *PortalProtocol) Get(contentKey []byte, contentId []byte) ([]byte, error) {
|
|
content, err := p.storage.Get(contentKey, contentId)
|
|
p.Log.Trace("get local storage", "contentId", hexutil.Encode(contentId), "content", hexutil.Encode(content), "err", err)
|
|
return content, err
|
|
}
|
|
|
|
func (p *PortalProtocol) Put(contentKey []byte, contentId []byte, content []byte) error {
|
|
err := p.storage.Put(contentKey, contentId, content)
|
|
p.Log.Trace("put local storage", "contentId", hexutil.Encode(contentId), "content", hexutil.Encode(content), "err", err)
|
|
return err
|
|
}
|
|
|
|
func (p *PortalProtocol) GetContent() chan *ContentElement {
|
|
return p.contentQueue
|
|
}
|
|
|
|
func (p *PortalProtocol) Gossip(srcNodeId *enode.ID, contentKeys [][]byte, content [][]byte) (int, error) {
|
|
if len(content) == 0 {
|
|
return 0, errors.New("empty content")
|
|
}
|
|
|
|
contentList := make([]*ContentEntry, 0, portalwire.ContentKeysLimit)
|
|
for i := 0; i < len(content); i++ {
|
|
contentEntry := &ContentEntry{
|
|
ContentKey: contentKeys[i],
|
|
Content: content[i],
|
|
}
|
|
contentList = append(contentList, contentEntry)
|
|
}
|
|
|
|
contentId := p.toContentId(contentKeys[0])
|
|
if contentId == nil {
|
|
return 0, ErrNilContentKey
|
|
}
|
|
|
|
maxClosestNodes := 4
|
|
maxFartherNodes := 4
|
|
closestLocalNodes := p.findNodesCloseToContent(contentId, 32)
|
|
p.Log.Debug("closest local nodes", "count", len(closestLocalNodes))
|
|
|
|
gossipNodes := make([]*enode.Node, 0)
|
|
for _, n := range closestLocalNodes {
|
|
radius, found := p.radiusCache.HasGet(nil, []byte(n.ID().String()))
|
|
if found {
|
|
p.Log.Debug("found closest local nodes", "nodeId", n.ID(), "addr", n.IPAddr().String())
|
|
nodeRadius := new(uint256.Int)
|
|
err := nodeRadius.UnmarshalSSZ(radius)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if inRange(n.ID(), nodeRadius, contentId) {
|
|
if srcNodeId == nil {
|
|
gossipNodes = append(gossipNodes, n)
|
|
} else if n.ID() != *srcNodeId {
|
|
gossipNodes = append(gossipNodes, n)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(gossipNodes) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
var finalGossipNodes []*enode.Node
|
|
if len(gossipNodes) > maxClosestNodes {
|
|
fartherNodes := gossipNodes[maxClosestNodes:]
|
|
rand.Shuffle(len(fartherNodes), func(i, j int) {
|
|
fartherNodes[i], fartherNodes[j] = fartherNodes[j], fartherNodes[i]
|
|
})
|
|
finalGossipNodes = append(gossipNodes[:maxClosestNodes], fartherNodes[:min(maxFartherNodes, len(fartherNodes))]...)
|
|
} else {
|
|
finalGossipNodes = gossipNodes
|
|
}
|
|
|
|
for _, n := range finalGossipNodes {
|
|
transientOfferRequest := &TransientOfferRequest{
|
|
Contents: contentList,
|
|
}
|
|
|
|
offerRequest := &OfferRequest{
|
|
Kind: TransientOfferRequestKind,
|
|
Request: transientOfferRequest,
|
|
}
|
|
|
|
offerRequestWithNode := &OfferRequestWithNode{
|
|
Node: n,
|
|
Request: offerRequest,
|
|
}
|
|
p.offerQueue <- offerRequestWithNode
|
|
}
|
|
|
|
return len(finalGossipNodes), nil
|
|
}
|
|
|
|
func (p *PortalProtocol) Distance(a, b enode.ID) enode.ID {
|
|
res := [32]byte{}
|
|
for i := range a {
|
|
res[i] = a[i] ^ b[i]
|
|
}
|
|
return res
|
|
}
|
|
|
|
func inRange(nodeId enode.ID, nodeRadius *uint256.Int, contentId []byte) bool {
|
|
distance := enode.LogDist(nodeId, enode.ID(contentId))
|
|
disBig := new(big.Int).SetInt64(int64(distance))
|
|
return nodeRadius.CmpBig(disBig) > 0
|
|
}
|
|
|
|
func encodeContents(contents [][]byte) ([]byte, error) {
|
|
contentsBytes := make([]byte, 0)
|
|
for _, content := range contents {
|
|
contentLen := len(content)
|
|
contentLenBytes := leb128.EncodeUint32(uint32(contentLen))
|
|
contentsBytes = append(contentsBytes, contentLenBytes...)
|
|
contentsBytes = append(contentsBytes, content...)
|
|
}
|
|
|
|
return contentsBytes, nil
|
|
}
|
|
|
|
func decodeContents(payload []byte) ([][]byte, error) {
|
|
contents := make([][]byte, 0)
|
|
buffer := bytes.NewBuffer(payload)
|
|
|
|
for {
|
|
contentLen, contentLenLen, err := leb128.DecodeUint32(bytes.NewReader(buffer.Bytes()))
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
return contents, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
buffer.Next(int(contentLenLen))
|
|
|
|
content := make([]byte, contentLen)
|
|
_, err = buffer.Read(content)
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
return contents, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
contents = append(contents, content)
|
|
}
|
|
}
|
|
|
|
func getContentKeys(request *OfferRequest) [][]byte {
|
|
if request.Kind == TransientOfferRequestKind {
|
|
contentKeys := make([][]byte, 0)
|
|
contents := request.Request.(*TransientOfferRequest).Contents
|
|
for _, content := range contents {
|
|
contentKeys = append(contentKeys, content.ContentKey)
|
|
}
|
|
|
|
return contentKeys
|
|
} else {
|
|
return request.Request.(*PersistOfferRequest).ContentKeys
|
|
}
|
|
}
|