diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index df6ce8c85d..7d0a0a83d2 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -58,6 +58,7 @@ type pair struct { v rlp.RawValue } +// Record represents Ethereum Node Record type Record struct { seq uint32 // sequence number signature []byte // record's signature @@ -65,15 +66,20 @@ type Record struct { pairs []pair // sorted list of all key/value pairs } +// Seq return record's sequence number func (r Record) Seq() uint32 { return r.seq } +// SetSeq update record's sequence number. Nodes should increase the number whenever the record changes. func (r *Record) SetSeq(s uint32) { r.signature = nil r.seq = s } +// Load is loading a key/value pair based on provided key from the record. +// It returns false if such key cannot be found. +// It returns an error if there is a problem with RLP decoding of the pair. func (r *Record) Load(k Key) (bool, error) { i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= k.ENRKey() }) @@ -109,6 +115,8 @@ func (r *Record) Set(k Key) { r.pairs = append(r.pairs, pair{k.ENRKey(), blob}) } +// EncodeRLP implements rlp.Encoder. +// Sign must be called prior to calling rlp.Encode func (r Record) EncodeRLP(w io.Writer) error { if r.signature == nil { return errors.New("record is not signed") @@ -117,6 +125,7 @@ func (r Record) EncodeRLP(w io.Writer) error { return err } +// DecodeRLP implements rlp.Decoder. func (r *Record) DecodeRLP(s *rlp.Stream) error { raw, err := s.Raw() if err != nil { @@ -174,6 +183,7 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error { return nil } +// NodeAddr returns node's address - keccak256 hash of the public key. func (r *Record) NodeAddr() ([]byte, error) { var secp256k1 Secp256k1 @@ -189,6 +199,9 @@ func (r *Record) NodeAddr() ([]byte, error) { return digest.Bytes(), nil } +// Sign signs the record with the provided private key. +// It updates record's identity scheme and public key. +// It returns an error if signed record is bigger than 300 bytes. func (r *Record) Sign(privkey *ecdsa.PrivateKey) error { pk := (*btcec.PublicKey)(&privkey.PublicKey) r.seq = r.seq + 1 diff --git a/p2p/enr/enr_test.go b/p2p/enr/enr_test.go index 707506996b..cdc77ea15f 100644 --- a/p2p/enr/enr_test.go +++ b/p2p/enr/enr_test.go @@ -34,6 +34,7 @@ const ( privkeyHex = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" ) +// TestGetSetID tests encoding/decoding and setting/getting of the enr.ID type func TestGetSetID(t *testing.T) { id := ID("someid") var r Record @@ -51,6 +52,7 @@ func TestGetSetID(t *testing.T) { } } +// TestGetSetIP4 tests encoding/decoding and setting/getting of the enr.IP4 type func TestGetSetIP4(t *testing.T) { ip := IP4(net.IP{192, 168, 0, 3}) var r Record @@ -68,6 +70,7 @@ func TestGetSetIP4(t *testing.T) { } } +// TestGetSetIP6 tests encoding/decoding and setting/getting of the enr.IP6 type func TestGetSetIP6(t *testing.T) { ip := IP6(net.IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}) var r Record @@ -85,6 +88,7 @@ func TestGetSetIP6(t *testing.T) { } } +// TestGetSetDiscPort tests encoding/decoding and setting/getting of the enr.DiscPort type func TestGetSetDiscPort(t *testing.T) { port := DiscPort(30309) var r Record @@ -102,6 +106,7 @@ func TestGetSetDiscPort(t *testing.T) { } } +// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the enr.Secp256k1 type func TestGetSetSecp256k1(t *testing.T) { privkey, err := crypto.HexToECDSA(privkeyHex) if err != nil { @@ -129,6 +134,7 @@ func TestGetSetSecp256k1(t *testing.T) { } } +// TestDirty tests record signature removal on setting of new key/value pair in record. func TestDirty(t *testing.T) { privkey, err := crypto.HexToECDSA(privkeyHex) if err != nil { @@ -153,6 +159,7 @@ func TestDirty(t *testing.T) { } } +// TestGetSetOverwrite tests value overwrite when setting a new value with an existing key in record. func TestGetSetOverwrite(t *testing.T) { var r Record @@ -174,6 +181,7 @@ func TestGetSetOverwrite(t *testing.T) { } } +// TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record. func TestSignEncodeAndDecode(t *testing.T) { privkey, err := crypto.HexToECDSA(privkeyHex) if err != nil { @@ -217,6 +225,7 @@ func TestSignEncodeAndDecode(t *testing.T) { } } +// TestNodeAddress tests that record returns correct node address - keccak256 hash of the public key. func TestNodeAddress(t *testing.T) { privkey, err := crypto.HexToECDSA(privkeyHex) if err != nil { @@ -242,6 +251,7 @@ func TestNodeAddress(t *testing.T) { } } +// TestPythonInterop tests that Go implementation can successfully RLP decode a record produced by Python implementation. func TestPythonInterop(t *testing.T) { enc, _ := hex.DecodeString("f896b840638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454018664697363763582765f82696490736563703235366b312d6b656363616b83697034847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138") var r Record @@ -271,6 +281,7 @@ func TestPythonInterop(t *testing.T) { } } +// TestRecordTooBig tests that records bigger than 300 bytes cannot be signed. func TestRecordTooBig(t *testing.T) { privkey, err := crypto.HexToECDSA(privkeyHex) if err != nil { @@ -296,6 +307,7 @@ func TestRecordTooBig(t *testing.T) { } } +// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs. func TestSignEncodeAndDecodeRandom(t *testing.T) { privkey, err := crypto.HexToECDSA(privkeyHex) if err != nil { diff --git a/p2p/enr/keys.go b/p2p/enr/keys.go index ef94126803..b256f444ce 100644 --- a/p2p/enr/keys.go +++ b/p2p/enr/keys.go @@ -43,6 +43,8 @@ func (g *generic) DecodeRLP(s *rlp.Stream) error { return s.Decode(&g.value) } +// WithKey returns a new Key that can be set in a Record. +// v must implement the rlp.Encoder and rlp.Decoder interface. func WithKey(k string, v interface{}) Key { return &generic{key: k, value: v} } @@ -50,6 +52,7 @@ func WithKey(k string, v interface{}) Key { // DiscPort represents an UDP port for discovery v5. type DiscPort uint16 +// ENRKey returns the node record key for an UDP port for discovery. func (DiscPort) ENRKey() string { return "discv5" } @@ -59,6 +62,7 @@ const ID_SECP256k1_KECCAK = "secp256k1-keccak" // identity scheme identifier // ID is the name of the identity scheme, e.g. "secp256k1-keccak". type ID string +// ENRKey returns the node record key for its identity scheme. func (ID) ENRKey() string { return "id" } @@ -71,6 +75,7 @@ func (IP4) ENRKey() string { return "ip4" } +// EncodeRLP implements rlp.Encoder. func (v IP4) EncodeRLP(w io.Writer) error { ip4 := net.IP(v).To4() if ip4 == nil { @@ -79,6 +84,7 @@ func (v IP4) EncodeRLP(w io.Writer) error { return rlp.Encode(w, ip4) } +// DecodeRLP implements rlp.Decoder. func (v *IP4) DecodeRLP(s *rlp.Stream) error { if err := s.Decode((*net.IP)(v)); err != nil { return err @@ -97,11 +103,13 @@ func (IP6) ENRKey() string { return "ip6" } +// EncodeRLP implements rlp.Encoder. func (v IP6) EncodeRLP(w io.Writer) error { ip6 := net.IP(v) return rlp.Encode(w, ip6) } +// DecodeRLP implements rlp.Decoder. func (v *IP6) DecodeRLP(s *rlp.Stream) error { if err := s.Decode((*net.IP)(v)); err != nil { return err @@ -115,16 +123,19 @@ func (v *IP6) DecodeRLP(s *rlp.Stream) error { // Secp256k1 is compressed secp256k1 public key. type Secp256k1 ecdsa.PublicKey +// ENRKey returns the node record key for the secp256k1 public key. func (Secp256k1) ENRKey() string { return "secp256k1" } +// EncodeRLP implements rlp.Encoder. func (v Secp256k1) EncodeRLP(w io.Writer) error { pk := btcec.PublicKey(v) return rlp.Encode(w, pk.SerializeCompressed()) } +// DecodeRLP implements rlp.Decoder. func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error { buf := make([]byte, 33) if err := s.Decode(&buf); err != nil {