package enr import ( "crypto/ecdsa" "encoding/binary" "errors" "math" "math/rand" "net" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/multiformats/go-multiaddr" "go.uber.org/zap" ) func NewLocalnode(priv *ecdsa.PrivateKey) (*enode.LocalNode, error) { db, err := enode.OpenDB("") if err != nil { return nil, err } return enode.NewLocalNode(db, priv), nil } type ENROption func(*enode.LocalNode) error func WithMultiaddress(multiaddrs ...multiaddr.Multiaddr) ENROption { return func(localnode *enode.LocalNode) (err error) { // Randomly shuffle multiaddresses rand.Shuffle(len(multiaddrs), func(i, j int) { multiaddrs[i], multiaddrs[j] = multiaddrs[j], multiaddrs[i] }) // Testing how many multiaddresses we can write before we exceed the limit // By simulating what the localnode does when signing the enr, but without // causing a panic privk, err := crypto.GenerateKey() if err != nil { return err } // Adding extra multiaddresses. Should probably not exceed the enr max size of 300bytes failedOnceWritingENR := false couldWriteENRatLeastOnce := false successIdx := -1 for i := len(multiaddrs); i > 0; i-- { cpy := localnode.Node().Record() // Record() creates a copy for the current iteration cpy.Set(enr.WithEntry(MultiaddrENRField, marshalMultiaddress(multiaddrs[0:i]))) cpy.SetSeq(localnode.Seq() + 1) err = enode.SignV4(cpy, privk) if err == nil { couldWriteENRatLeastOnce = true successIdx = i break } failedOnceWritingENR = true } if failedOnceWritingENR && couldWriteENRatLeastOnce { // Could write a subset of multiaddresses but not all writeMultiaddressField(localnode, multiaddrs[0:successIdx]) } return nil } } func WithCapabilities(lightpush, filter, store, relay bool) ENROption { return func(localnode *enode.LocalNode) (err error) { wakuflags := NewWakuEnrBitfield(lightpush, filter, store, relay) return WithWakuBitfield(wakuflags)(localnode) } } func WithWakuBitfield(flags WakuEnrBitfield) ENROption { return func(localnode *enode.LocalNode) (err error) { localnode.Set(enr.WithEntry(WakuENRField, flags)) return nil } } func WithIP(ipAddr *net.TCPAddr) ENROption { return func(localnode *enode.LocalNode) (err error) { if ipAddr.Port == 0 { return ErrNoPortAvailable } localnode.SetStaticIP(ipAddr.IP) localnode.Set(enr.TCP(uint16(ipAddr.Port))) // TODO: ipv6? return nil } } func WithUDPPort(udpPort uint) ENROption { return func(localnode *enode.LocalNode) (err error) { if udpPort == 0 { return nil } if udpPort > math.MaxUint16 { return errors.New("invalid udp port number") } localnode.SetFallbackUDP(int(udpPort)) return nil } } func Update(logger *zap.Logger, localnode *enode.LocalNode, enrOptions ...ENROption) error { for _, opt := range enrOptions { err := opt(localnode) if err != nil { if errors.Is(err, ErrNoPortAvailable) { logger.Warn("no tcp port available. ENR will not contain tcp key") } else { return err } } } return nil } func marshalMultiaddress(addrAggr []multiaddr.Multiaddr) []byte { var fieldRaw []byte for _, addr := range addrAggr { maRaw := addr.Bytes() maSize := make([]byte, 2) binary.BigEndian.PutUint16(maSize, uint16(len(maRaw))) fieldRaw = append(fieldRaw, maSize...) fieldRaw = append(fieldRaw, maRaw...) } return fieldRaw } func writeMultiaddressField(localnode *enode.LocalNode, addrAggr []multiaddr.Multiaddr) { fieldRaw := marshalMultiaddress(addrAggr) localnode.Set(enr.WithEntry(MultiaddrENRField, fieldRaw)) } func DeleteField(localnode *enode.LocalNode, field string) { localnode.Delete(enr.WithEntry(field, struct{}{})) }