waku-org/go-waku

View on GitHub
waku/v2/protocol/enr/enr.go

Summary

Maintainability
A
2 hrs
Test Coverage
D
68%
package enr

import (
    "encoding/binary"
    "errors"
    "fmt"

    "github.com/ethereum/go-ethereum/p2p/enode"
    "github.com/ethereum/go-ethereum/p2p/enr"
    "github.com/libp2p/go-libp2p/core/peer"
    "github.com/multiformats/go-multiaddr"
    "github.com/waku-org/go-waku/waku/v2/utils"
)

var ErrNoPortAvailable = errors.New("port not available")

// WakuENRField is the name of the ENR field that contains information about which protocols are supported by the node
const WakuENRField = "waku2"

// MultiaddrENRField is the name of the ENR field that will contain multiaddresses that cannot be described using the
// already available ENR fields (i.e. in the case of websocket connections)
const MultiaddrENRField = "multiaddrs"

const ShardingIndicesListEnrField = "rs"

const ShardingBitVectorEnrField = "rsv"

// WakuEnrBitfield is a8-bit flag field to indicate Waku capabilities. Only the 4 LSBs are currently defined according to RFC31 (https://rfc.vac.dev/spec/31/).
type WakuEnrBitfield = uint8

func GetWakuEnrBitField(node *enode.Node) (WakuEnrBitfield, error) {
    enrField := []byte{}
    err := node.Record().Load(enr.WithEntry(WakuENRField, &enrField))
    if err != nil {
        if enr.IsNotFound(err) {
            return 0, nil
        }
        return 0, err
    }

    if len(enrField) == 0 {
        return 0, err
    }

    return WakuEnrBitfield(enrField[0]), nil
}

// NewWakuEnrBitfield creates a WakuEnrBitField whose value will depend on which protocols are enabled in the node
func NewWakuEnrBitfield(lightpush, filter, store, relay bool) WakuEnrBitfield {
    var v uint8

    if lightpush {
        v |= (1 << 3)
    }

    if filter {
        v |= (1 << 2)
    }

    if store {
        v |= (1 << 1)
    }

    if relay {
        v |= (1 << 0)
    }

    return v
}

// EnodeToMultiaddress converts an enode into a multiaddress
func enodeToMultiAddr(node *enode.Node) (multiaddr.Multiaddr, error) {
    pubKey := utils.EcdsaPubKeyToSecp256k1PublicKey(node.Pubkey())
    peerID, err := peer.IDFromPublicKey(pubKey)
    if err != nil {
        return nil, err
    }

    ipType := "ip4"
    portNumber := node.TCP()

    if portNumber == 0 {
        return nil, ErrNoPortAvailable
    }

    if utils.IsIPv6(node.IP().String()) {
        ipType = "ip6"
        var port enr.TCP6
        if err := node.Record().Load(&port); err != nil {
            return nil, err
        }
        portNumber = int(port)
    }

    return multiaddr.NewMultiaddr(fmt.Sprintf("/%s/%s/tcp/%d/p2p/%s", ipType, node.IP(), portNumber, peerID))
}

// Multiaddress is used to extract all the multiaddresses that are part of a ENR record
func Multiaddress(node *enode.Node) (peer.ID, []multiaddr.Multiaddr, error) {
    pubKey := utils.EcdsaPubKeyToSecp256k1PublicKey(node.Pubkey())
    peerID, err := peer.IDFromPublicKey(pubKey)
    if err != nil {
        return "", nil, err
    }

    var result []multiaddr.Multiaddr

    addr, err := enodeToMultiAddr(node)
    if err != nil {
        if !errors.Is(err, ErrNoPortAvailable) {
            return "", nil, err
        }
    } else {
        result = append(result, addr)
    }

    var multiaddrRaw []byte
    if err := node.Record().Load(enr.WithEntry(MultiaddrENRField, &multiaddrRaw)); err != nil {
        if !enr.IsNotFound(err) {
            return "", nil, err
        }
        // No multiaddr entry on enr
        return peerID, result, nil
    }

    if len(multiaddrRaw) < 2 {
        // There was no error loading the multiaddr field, but its length is incorrect
        return peerID, result, nil
    }

    offset := 0
    for {
        maSize := binary.BigEndian.Uint16(multiaddrRaw[offset : offset+2])
        if len(multiaddrRaw) < offset+2+int(maSize) {
            return "", nil, errors.New("invalid multiaddress field length")
        }
        maRaw := multiaddrRaw[offset+2 : offset+2+int(maSize)]
        addr, err := multiaddr.NewMultiaddrBytes(maRaw)
        if err != nil {
            // The value is not a multiaddress. Ignoring...
            continue
        }

        hostInfoStr := fmt.Sprintf("/p2p/%s", peerID.String())
        _, pID := peer.SplitAddr(addr)
        if pID != "" && pID != peerID {
            // Addresses in the ENR that contain a p2p component are circuit relay addr
            hostInfoStr = "/p2p-circuit" + hostInfoStr
        }

        hostInfo, err := multiaddr.NewMultiaddr(hostInfoStr)
        if err != nil {
            return "", nil, err
        }
        result = append(result, addr.Encapsulate(hostInfo))

        offset += 2 + int(maSize)
        if offset >= len(multiaddrRaw) {
            break
        }
    }

    return peerID, result, nil
}

// EnodeToPeerInfo extracts the peer ID and multiaddresses defined in an ENR
func EnodeToPeerInfo(node *enode.Node) (*peer.AddrInfo, error) {
    _, addresses, err := Multiaddress(node)
    if err != nil {
        return nil, err
    }

    res, err := peer.AddrInfosFromP2pAddrs(addresses...)
    if err != nil {
        return nil, err
    }
    if len(res) == 0 {
        return nil, errors.New("could not retrieve peer addresses from enr")
    }
    return &res[0], nil
}