mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
p2p/enr: improve API and tests
API changes: - Load no longer returns (bool, error), just error. Missing keys can be detected using IsNotFound. - Load errors contain the key that the caller tried to load. - NodeAddr doesn't return error anymore. I expect we'll be using this method often and it will never return an error for records decoded from RLP. Maybe it should panic if "secp256k1" doesn't exist, not sure about that yet. - The Signed method can be used to check for a signature.
This commit is contained in:
parent
67301a1c23
commit
f9de82bdb0
3 changed files with 221 additions and 298 deletions
108
p2p/enr/enr.go
108
p2p/enr/enr.go
|
|
@ -14,7 +14,17 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package enr implements the Ethereum Node Record as per https://github.com/ethereum/EIPs/pull/778
|
// Package enr implements Ethereum Node Records as defined in EIP-778. A node record holds
|
||||||
|
// arbitrary information about a node on the peer-to-peer network.
|
||||||
|
//
|
||||||
|
// Records contain named keys. To store and retrieve keys in a record, use the Key
|
||||||
|
// interface.
|
||||||
|
//
|
||||||
|
// Records must signed before transmitting them to another node. Decoding a record verifies
|
||||||
|
// its signature. When creating a record, set the keys you want, then call Sign to add the
|
||||||
|
// signature. Modifying a record invalidates the signature.
|
||||||
|
//
|
||||||
|
// Package enr supports the "secp256k1-keccak" identity scheme.
|
||||||
package enr
|
package enr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -35,6 +45,8 @@ import (
|
||||||
|
|
||||||
const SizeLimit = 300 // maximum encoded size of a node record in bytes
|
const SizeLimit = 300 // maximum encoded size of a node record in bytes
|
||||||
|
|
||||||
|
const ID_SECP256k1_KECCAK = ID("secp256k1-keccak") // the default identity scheme
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNoID = errors.New("unknown or unspecified identity scheme")
|
errNoID = errors.New("unknown or unspecified identity scheme")
|
||||||
errInvalidSigsize = errors.New("invalid signature size")
|
errInvalidSigsize = errors.New("invalid signature size")
|
||||||
|
|
@ -43,15 +55,16 @@ var (
|
||||||
errDuplicateKey = errors.New("record contains duplicate key")
|
errDuplicateKey = errors.New("record contains duplicate key")
|
||||||
errIncompletePair = errors.New("record contains incomplete k/v pair")
|
errIncompletePair = errors.New("record contains incomplete k/v pair")
|
||||||
errTooBig = fmt.Errorf("record bigger than %d bytes", SizeLimit)
|
errTooBig = fmt.Errorf("record bigger than %d bytes", SizeLimit)
|
||||||
|
errEncodeUnsigned = errors.New("can't encode unsigned record")
|
||||||
|
errNotFound = errors.New("no such key in record")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Key is implemented by known node record key types.
|
// Record represents a node record. The zero value is an empty record.
|
||||||
//
|
type Record struct {
|
||||||
// To define a new key that is to be included in a node record,
|
seq uint32 // sequence number
|
||||||
// create a Go type that satisfies this interface. The type should
|
signature []byte // the signature
|
||||||
// also implement rlp.Decoder if additional checks are needed on the value.
|
raw []byte // RLP encoded record
|
||||||
type Key interface {
|
pairs []pair // sorted list of all key/value pairs
|
||||||
ENRKey() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pair is a key/value pair in a record.
|
// pair is a key/value pair in a record.
|
||||||
|
|
@ -60,36 +73,38 @@ type pair struct {
|
||||||
v rlp.RawValue
|
v rlp.RawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record represents Ethereum Node Record
|
// Signed reports whether the record has a valid signature.
|
||||||
type Record struct {
|
func (r *Record) Signed() bool {
|
||||||
seq uint32 // sequence number
|
return r.signature != nil
|
||||||
signature []byte // record's signature
|
|
||||||
raw []byte // RLP encoded record
|
|
||||||
pairs []pair // sorted list of all key/value pairs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seq return record's sequence number
|
// Seq returns the sequence number.
|
||||||
func (r Record) Seq() uint32 {
|
func (r *Record) Seq() uint32 {
|
||||||
return r.seq
|
return r.seq
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSeq update record's sequence number. Nodes should increase the number whenever the record changes.
|
// SetSeq updates the record sequence number. This invalidates any signature on the record.
|
||||||
|
// Calling SetSeq is usually not required because signing the redord increments the
|
||||||
|
// sequence number.
|
||||||
func (r *Record) SetSeq(s uint32) {
|
func (r *Record) SetSeq(s uint32) {
|
||||||
r.signature = nil
|
r.signature = nil
|
||||||
r.seq = s
|
r.seq = s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load is loading a key/value pair based on provided key from the record.
|
// Load retrieves the valud of a key/value pair. The given Key must be a pointer and will
|
||||||
// It returns false if such key cannot be found.
|
// be set to the value of the key in the record.
|
||||||
// It returns an error if there is a problem with RLP decoding of the pair.
|
//
|
||||||
func (r *Record) Load(k Key) (bool, error) {
|
// Errors returned by Load are wrapped in KeyError. You can distinguish decoding errors
|
||||||
|
// from missing keys using the IsNotFound function.
|
||||||
|
func (r *Record) Load(k Key) error {
|
||||||
i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= k.ENRKey() })
|
i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= k.ENRKey() })
|
||||||
|
|
||||||
if i < len(r.pairs) && r.pairs[i].k == k.ENRKey() {
|
if i < len(r.pairs) && r.pairs[i].k == k.ENRKey() {
|
||||||
return true, rlp.DecodeBytes(r.pairs[i].v, k)
|
if err := rlp.DecodeBytes(r.pairs[i].v, k); err != nil {
|
||||||
|
return &KeyError{Key: k.ENRKey(), Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return &KeyError{Key: k.ENRKey(), Err: errNotFound}
|
||||||
return false, errors.New("record does not exist")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set adds or updates the given key in the record.
|
// Set adds or updates the given key in the record.
|
||||||
|
|
@ -120,17 +135,17 @@ func (r *Record) Set(k Key) {
|
||||||
r.pairs = append(r.pairs, pair{k.ENRKey(), blob})
|
r.pairs = append(r.pairs, pair{k.ENRKey(), blob})
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeRLP implements rlp.Encoder.
|
// EncodeRLP implements rlp.Encoder. Encoding fails if
|
||||||
// Sign must be called prior to calling rlp.Encode
|
// the record is unsigned.
|
||||||
func (r Record) EncodeRLP(w io.Writer) error {
|
func (r Record) EncodeRLP(w io.Writer) error {
|
||||||
if r.signature == nil {
|
if !r.Signed() {
|
||||||
return errors.New("record is not signed")
|
return errEncodeUnsigned
|
||||||
}
|
}
|
||||||
_, err := w.Write(r.raw)
|
_, err := w.Write(r.raw)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeRLP implements rlp.Decoder.
|
// DecodeRLP implements rlp.Decoder. Decoding verifies the signature.
|
||||||
func (r *Record) DecodeRLP(s *rlp.Stream) error {
|
func (r *Record) DecodeRLP(s *rlp.Stream) error {
|
||||||
raw, err := s.Raw()
|
raw, err := s.Raw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -191,29 +206,24 @@ func (r *Record) DecodeRLP(s *rlp.Stream) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeAddr returns node's address - keccak256 hash of the public key.
|
// NodeAddr returns the node address. The return value will be nil if the record is
|
||||||
func (r *Record) NodeAddr() ([]byte, error) {
|
// unsigned.
|
||||||
|
func (r *Record) NodeAddr() []byte {
|
||||||
var secp256k1 Secp256k1
|
var secp256k1 Secp256k1
|
||||||
|
if r.Load(&secp256k1) != nil {
|
||||||
_, err := r.Load(&secp256k1)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pk := btcec.PublicKey(secp256k1)
|
pk := btcec.PublicKey(secp256k1)
|
||||||
|
return crypto.Keccak256(pk.SerializeCompressed())
|
||||||
digest := crypto.Keccak256Hash(pk.SerializeCompressed())
|
|
||||||
|
|
||||||
return digest.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign signs the record with the provided private key.
|
// Sign signs the record with the given private key. It updates the record's identity
|
||||||
// It updates record's identity scheme and public key.
|
// scheme, public key and increments the sequence number. Sign returns an error if the
|
||||||
// It returns an error if signed record is bigger than SizeLimit bytes.
|
// encoded record is larger than the size limit.
|
||||||
func (r *Record) Sign(privkey *ecdsa.PrivateKey) error {
|
func (r *Record) Sign(privkey *ecdsa.PrivateKey) error {
|
||||||
pk := (*btcec.PublicKey)(&privkey.PublicKey)
|
pk := (*btcec.PublicKey)(&privkey.PublicKey)
|
||||||
r.seq = r.seq + 1
|
r.seq = r.seq + 1
|
||||||
r.Set(ID(ID_SECP256k1_KECCAK))
|
r.Set(ID_SECP256k1_KECCAK)
|
||||||
r.Set(Secp256k1(*pk))
|
r.Set(Secp256k1(*pk))
|
||||||
return r.signAndEncode(privkey)
|
return r.signAndEncode(privkey)
|
||||||
}
|
}
|
||||||
|
|
@ -246,11 +256,9 @@ func (r *Record) signAndEncode(privkey *ecdsa.PrivateKey) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.raw) > SizeLimit {
|
if len(r.raw) > SizeLimit {
|
||||||
return errTooBig
|
return errTooBig
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,15 +266,13 @@ func (r *Record) verifySignature() error {
|
||||||
// Get identity scheme, public key, signature.
|
// Get identity scheme, public key, signature.
|
||||||
var id ID
|
var id ID
|
||||||
var secp256k1 Secp256k1
|
var secp256k1 Secp256k1
|
||||||
if _, err := r.Load(&id); err != nil {
|
if err := r.Load(&id); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if id != ID_SECP256k1_KECCAK {
|
} else if id != ID_SECP256k1_KECCAK {
|
||||||
return errNoID
|
return errNoID
|
||||||
}
|
}
|
||||||
if ok, err := r.Load(&secp256k1); err != nil {
|
if err := r.Load(&secp256k1); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !ok {
|
|
||||||
return fmt.Errorf("can't verify signature: missing %q key", secp256k1.ENRKey())
|
|
||||||
}
|
}
|
||||||
sig, err := parseCompactSignature(r.signature)
|
sig, err := parseCompactSignature(r.signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -19,60 +19,108 @@ package enr
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
privkeyHex = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
privkeyHex = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
|
||||||
|
privkey, _ = crypto.HexToECDSA(privkeyHex)
|
||||||
|
pubkeyBytes, _ = hex.DecodeString("03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
|
||||||
|
pubkey, _ = btcec.ParsePubKey(pubkeyBytes, btcec.S256())
|
||||||
)
|
)
|
||||||
|
|
||||||
var rnd *rand.Rand
|
var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
func init() {
|
// TestGetSetID tests encoding/decoding and setting/getting of the ID key.
|
||||||
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestGetSetID tests encoding/decoding and setting/getting of the enr.ID type
|
|
||||||
func TestGetSetID(t *testing.T) {
|
func TestGetSetID(t *testing.T) {
|
||||||
id := ID("someid")
|
id := ID("someid")
|
||||||
var r Record
|
var r Record
|
||||||
r.Set(id)
|
r.Set(id)
|
||||||
|
|
||||||
var id2 ID
|
var id2 ID
|
||||||
|
require.NoError(t, r.Load(&id2))
|
||||||
_, err := r.Load(&id2)
|
assert.Equal(t, id, id2)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if id != id2 {
|
|
||||||
t.Fatalf("got %#v, expected %#v", id2, id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestGetSetIP4 tests encoding/decoding and setting/getting of the enr.IP4 type
|
// TestGetSetIP4 tests encoding/decoding and setting/getting of the IP4 key.
|
||||||
func TestGetSetIP4(t *testing.T) {
|
func TestGetSetIP4(t *testing.T) {
|
||||||
ip := IP4(net.IP{192, 168, 0, 3})
|
ip := IP4{192, 168, 0, 3}
|
||||||
var r Record
|
var r Record
|
||||||
r.Set(ip)
|
r.Set(ip)
|
||||||
|
|
||||||
var ip2 IP4
|
var ip2 IP4
|
||||||
|
require.NoError(t, r.Load(&ip2))
|
||||||
|
assert.Equal(t, ip, ip2)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := r.Load(&ip2)
|
// TestGetSetIP6 tests encoding/decoding and setting/getting of the IP6 key.
|
||||||
if err != nil {
|
func TestGetSetIP6(t *testing.T) {
|
||||||
|
ip := IP6{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}
|
||||||
|
var r Record
|
||||||
|
r.Set(ip)
|
||||||
|
|
||||||
|
var ip2 IP6
|
||||||
|
require.NoError(t, r.Load(&ip2))
|
||||||
|
assert.Equal(t, ip, ip2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetSetDiscPort tests encoding/decoding and setting/getting of the DiscPort key.
|
||||||
|
func TestGetSetDiscPort(t *testing.T) {
|
||||||
|
port := DiscPort(30309)
|
||||||
|
var r Record
|
||||||
|
r.Set(port)
|
||||||
|
|
||||||
|
var port2 DiscPort
|
||||||
|
require.NoError(t, r.Load(&port2))
|
||||||
|
assert.Equal(t, port, port2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetSetSecp256k1 tests encoding/decoding and setting/getting of the Secp256k1 key.
|
||||||
|
func TestGetSetSecp256k1(t *testing.T) {
|
||||||
|
var r Record
|
||||||
|
if err := r.Sign(privkey); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Compare(ip, ip2) != 0 {
|
var pk Secp256k1
|
||||||
t.Fatalf("got %#v, expected %#v", ip2, ip)
|
require.NoError(t, r.Load(&pk))
|
||||||
|
assert.EqualValues(t, pubkey, &pk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadErrors(t *testing.T) {
|
||||||
|
var r Record
|
||||||
|
ip4 := IP4{127, 0, 0, 1}
|
||||||
|
r.Set(ip4)
|
||||||
|
|
||||||
|
// Check error for missing keys.
|
||||||
|
var ip6 IP6
|
||||||
|
err := r.Load(&ip6)
|
||||||
|
if !IsNotFound(err) {
|
||||||
|
t.Error("IsNotFound should return true for missing key")
|
||||||
|
}
|
||||||
|
assert.Equal(t, &KeyError{Key: ip6.ENRKey(), Err: errNotFound}, err)
|
||||||
|
|
||||||
|
// Check error for invalid keys.
|
||||||
|
var list []uint
|
||||||
|
err = r.Load(WithKey(IP4{}.ENRKey(), &list))
|
||||||
|
kerr, ok := err.(*KeyError)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected KeyError, got %T", err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, kerr.Key, ip4.ENRKey())
|
||||||
|
assert.Error(t, kerr.Err)
|
||||||
|
if IsNotFound(err) {
|
||||||
|
t.Error("IsNotFound should return false for decoding errors")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,103 +155,36 @@ func TestSortedGetAndSet(t *testing.T) {
|
||||||
for i, w := range tt.want {
|
for i, w := range tt.want {
|
||||||
// set got's key from r.pair[i], so that we preserve order of pairs
|
// set got's key from r.pair[i], so that we preserve order of pairs
|
||||||
got := pair{k: r.pairs[i].k}
|
got := pair{k: r.pairs[i].k}
|
||||||
if ok, err := r.Load(WithKey(w.k, &got.v)); !ok || err != nil {
|
assert.NoError(t, r.Load(WithKey(w.k, &got.v)))
|
||||||
t.Fatal(err)
|
assert.Equal(t, w, got)
|
||||||
}
|
|
||||||
|
|
||||||
if got != w {
|
|
||||||
t.Fatalf("expected %#v, got %#v", w, got)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
r.Set(ip)
|
|
||||||
|
|
||||||
var ip2 IP6
|
|
||||||
|
|
||||||
_, err := r.Load(&ip2)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.Compare(ip, ip2) != 0 {
|
|
||||||
t.Fatalf("got %#v, expected %#v", ip2, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestGetSetDiscPort tests encoding/decoding and setting/getting of the enr.DiscPort type
|
|
||||||
func TestGetSetDiscPort(t *testing.T) {
|
|
||||||
port := DiscPort(30309)
|
|
||||||
var r Record
|
|
||||||
r.Set(port)
|
|
||||||
|
|
||||||
var port2 DiscPort
|
|
||||||
|
|
||||||
_, err := r.Load(&port2)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if port != port2 {
|
|
||||||
t.Fatalf("got %#v, expected %#v", port2, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r Record
|
|
||||||
|
|
||||||
err = r.Sign(privkey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pk Secp256k1
|
|
||||||
|
|
||||||
_, err = r.Load(&pk)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := (*btcec.PublicKey)(&pk).SerializeCompressed()
|
|
||||||
expected := (*btcec.PublicKey)(&privkey.PublicKey).SerializeCompressed()
|
|
||||||
if bytes.Compare(got, expected) != 0 {
|
|
||||||
t.Fatalf("got %#v, expected %#v", got, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDirty tests record signature removal on setting of new key/value pair in record.
|
// TestDirty tests record signature removal on setting of new key/value pair in record.
|
||||||
func TestDirty(t *testing.T) {
|
func TestDirty(t *testing.T) {
|
||||||
privkey, err := crypto.HexToECDSA(privkeyHex)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r Record
|
var r Record
|
||||||
|
|
||||||
err = r.Sign(privkey)
|
if r.Signed() {
|
||||||
if err != nil {
|
t.Error("Signed returned true for zero record")
|
||||||
t.Fatal(err)
|
}
|
||||||
|
if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
|
||||||
|
t.Errorf("expected errEncodeUnsigned, got %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := rlp.EncodeToBytes(r); err != nil {
|
require.NoError(t, r.Sign(privkey))
|
||||||
t.Fatal(err)
|
if !r.Signed() {
|
||||||
|
t.Error("Signed return false for signed record")
|
||||||
}
|
}
|
||||||
|
_, err := rlp.EncodeToBytes(r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
r.SetSeq(3)
|
r.SetSeq(3)
|
||||||
|
if r.Signed() {
|
||||||
if _, err := rlp.EncodeToBytes(r); err == nil {
|
t.Error("Signed returned true for modified record")
|
||||||
t.Fatal("expected err, got nil")
|
}
|
||||||
|
if _, err := rlp.EncodeToBytes(r); err != errEncodeUnsigned {
|
||||||
|
t.Errorf("expected errEncodeUnsigned, got %#v")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,88 +199,45 @@ func TestGetSetOverwrite(t *testing.T) {
|
||||||
r.Set(ip2)
|
r.Set(ip2)
|
||||||
|
|
||||||
var ip3 IP4
|
var ip3 IP4
|
||||||
|
require.NoError(t, r.Load(&ip3))
|
||||||
_, err := r.Load(&ip3)
|
assert.Equal(t, ip2, ip3)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes.Compare(ip2, ip3) != 0 {
|
|
||||||
t.Fatalf("got %#v, expected %#v", ip2, ip3)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record.
|
// TestSignEncodeAndDecode tests signing, RLP encoding and RLP decoding of a record.
|
||||||
func TestSignEncodeAndDecode(t *testing.T) {
|
func TestSignEncodeAndDecode(t *testing.T) {
|
||||||
privkey, err := crypto.HexToECDSA(privkeyHex)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r Record
|
var r Record
|
||||||
port := DiscPort(30303)
|
r.Set(DiscPort(30303))
|
||||||
r.Set(port)
|
r.Set(IP4(net.ParseIP("127.0.0.1")))
|
||||||
|
require.NoError(t, r.Sign(privkey))
|
||||||
ipv4 := IP4(net.ParseIP("127.0.0.1"))
|
|
||||||
r.Set(ipv4)
|
|
||||||
|
|
||||||
err = r.Sign(privkey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
blob, err := rlp.EncodeToBytes(r)
|
blob, err := rlp.EncodeToBytes(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var r2 Record
|
var r2 Record
|
||||||
err = rlp.DecodeBytes(blob, &r2)
|
require.NoError(t, rlp.DecodeBytes(blob, &r2))
|
||||||
if err != nil {
|
assert.Equal(t, r, r2)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(r, r2) {
|
|
||||||
t.Errorf("records not deep equal ; got\n%#v, expected\n%#v", r2, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
blob2, err := rlp.EncodeToBytes(r2)
|
blob2, err := rlp.EncodeToBytes(r2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, blob, blob2)
|
||||||
if bytes.Compare(blob, blob2) != 0 {
|
|
||||||
t.Errorf("serialised records not equal ; got\n%#v, expected\n%#v", blob2, blob)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestNodeAddress tests that record returns correct node address - keccak256 hash of the public key.
|
func TestNodeAddr(t *testing.T) {
|
||||||
func TestNodeAddress(t *testing.T) {
|
|
||||||
privkey, err := crypto.HexToECDSA(privkeyHex)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r Record
|
var r Record
|
||||||
|
if addr := r.NodeAddr(); addr != nil {
|
||||||
err = r.Sign(privkey)
|
t.Errorf("wrong address on empty record: got %v, want %v", addr, nil)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := r.NodeAddr()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require.NoError(t, r.Sign(privkey))
|
||||||
expected := "caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726"
|
expected := "caaa1485d83b18b32ed9ad666026151bf0cae8a0a88c857ae2d4c5be2daa6726"
|
||||||
got := hex.EncodeToString(addr)
|
assert.Equal(t, expected, hex.EncodeToString(r.NodeAddr()))
|
||||||
if got != expected {
|
|
||||||
t.Errorf("got\n%#v, expected\n%#v", got, expected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPythonInterop tests that Go implementation can successfully RLP decode a record produced by Python implementation.
|
// TestPythonInterop checks that we can decode and verify a record produced by the Python
|
||||||
|
// implementation.
|
||||||
func TestPythonInterop(t *testing.T) {
|
func TestPythonInterop(t *testing.T) {
|
||||||
enc, _ := hex.DecodeString("f896b840638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454018664697363763582765f82696490736563703235366b312d6b656363616b83697034847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
|
enc, _ := hex.DecodeString("f896b840638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454018664697363763582765f82696490736563703235366b312d6b656363616b83697034847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
|
||||||
var r Record
|
var r Record
|
||||||
|
|
@ -316,95 +254,62 @@ func TestPythonInterop(t *testing.T) {
|
||||||
if r.Seq() != wantSeq {
|
if r.Seq() != wantSeq {
|
||||||
t.Errorf("wrong seq: got %d, want %d", r.Seq(), wantSeq)
|
t.Errorf("wrong seq: got %d, want %d", r.Seq(), wantSeq)
|
||||||
}
|
}
|
||||||
if addr, _ := r.NodeAddr(); !bytes.Equal(addr, wantAddr) {
|
if addr := r.NodeAddr(); !bytes.Equal(addr, wantAddr) {
|
||||||
t.Errorf("wrong addr: got %x, want %x", addr, wantAddr)
|
t.Errorf("wrong addr: got %x, want %x", addr, wantAddr)
|
||||||
}
|
}
|
||||||
want := map[Key]interface{}{new(IP4): &wantIP, new(DiscPort): &wantDiscport}
|
want := map[Key]interface{}{new(IP4): &wantIP, new(DiscPort): &wantDiscport}
|
||||||
for k, v := range want {
|
for k, v := range want {
|
||||||
if _, err := r.Load(k); err != nil {
|
desc := fmt.Sprintf("loading key %q", k.ENRKey())
|
||||||
t.Errorf("can't load %q: %v", k.ENRKey(), err)
|
if assert.NoError(t, r.Load(k), desc) {
|
||||||
} else if !reflect.DeepEqual(k, v) {
|
assert.Equal(t, k, v, desc)
|
||||||
t.Errorf("wrong %q: got %v, want %v", k.ENRKey(), k, v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed.
|
// TestRecordTooBig tests that records bigger than SizeLimit bytes cannot be signed.
|
||||||
func TestRecordTooBig(t *testing.T) {
|
func TestRecordTooBig(t *testing.T) {
|
||||||
privkey, err := crypto.HexToECDSA(privkeyHex)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r Record
|
var r Record
|
||||||
|
|
||||||
key := randomString(10)
|
key := randomString(10)
|
||||||
|
|
||||||
// set a big value for random key, expect error
|
// set a big value for random key, expect error
|
||||||
r.Set(WithKey(key, randomString(300)))
|
r.Set(WithKey(key, randomString(300)))
|
||||||
err = r.Sign(privkey)
|
if err := r.Sign(privkey); err != errTooBig {
|
||||||
if err != errTooBig {
|
|
||||||
t.Fatalf("expected to get errTooBig, got %#v", err)
|
t.Fatalf("expected to get errTooBig, got %#v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set an acceptable value for random key, expect no error
|
// set an acceptable value for random key, expect no error
|
||||||
r.Set(WithKey(key, randomString(100)))
|
r.Set(WithKey(key, randomString(100)))
|
||||||
err = r.Sign(privkey)
|
require.NoError(t, r.Sign(privkey))
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
|
// TestSignEncodeAndDecodeRandom tests encoding/decoding of records containing random key/value pairs.
|
||||||
func TestSignEncodeAndDecodeRandom(t *testing.T) {
|
func TestSignEncodeAndDecodeRandom(t *testing.T) {
|
||||||
privkey, err := crypto.HexToECDSA(privkeyHex)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r Record
|
var r Record
|
||||||
|
|
||||||
// random key/value pairs for testing
|
// random key/value pairs for testing
|
||||||
pairs := map[string]uint32{}
|
pairs := map[string]uint32{}
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
key := randomString(7)
|
key := randomString(7)
|
||||||
value := rnd.Uint32()
|
value := rnd.Uint32()
|
||||||
|
|
||||||
pair := WithKey(key, &value)
|
|
||||||
r.Set(pair)
|
|
||||||
|
|
||||||
pairs[key] = value
|
pairs[key] = value
|
||||||
|
r.Set(WithKey(key, &value))
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Sign(privkey); err != nil {
|
require.NoError(t, r.Sign(privkey))
|
||||||
t.Fatal(err)
|
_, err := rlp.EncodeToBytes(r)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = rlp.EncodeToBytes(r)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range pairs {
|
for k, v := range pairs {
|
||||||
|
desc := fmt.Sprintf("key %q", k)
|
||||||
var got uint32
|
var got uint32
|
||||||
buf := WithKey(k, &got)
|
buf := WithKey(k, &got)
|
||||||
|
require.NoError(t, r.Load(buf), desc)
|
||||||
if ok, err := r.Load(buf); !ok || err != nil {
|
require.Equal(t, v, got, desc)
|
||||||
t.Fatal(ok, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got != v {
|
|
||||||
t.Fatalf("got %#v, expected %#v", got, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomString(strlen int) string {
|
func randomString(strlen int) string {
|
||||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
b := make([]byte, strlen)
|
||||||
result := make([]byte, strlen)
|
rnd.Read(b)
|
||||||
for i := range result {
|
return string(b)
|
||||||
result[i] = chars[rnd.Intn(len(chars))]
|
|
||||||
}
|
|
||||||
return string(result)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,21 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Key is implemented by known node record key types.
|
||||||
|
//
|
||||||
|
// To define a new key that is to be included in a node record,
|
||||||
|
// create a Go type that satisfies this interface. The type should
|
||||||
|
// also implement rlp.Decoder if additional checks are needed on the value.
|
||||||
|
type Key interface {
|
||||||
|
ENRKey() string
|
||||||
|
}
|
||||||
|
|
||||||
type generic struct {
|
type generic struct {
|
||||||
key string
|
key string
|
||||||
value interface{}
|
value interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g generic) ENRKey() string {
|
func (g generic) ENRKey() string { return g.key }
|
||||||
return g.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g generic) EncodeRLP(w io.Writer) error {
|
func (g generic) EncodeRLP(w io.Writer) error {
|
||||||
return rlp.Encode(w, g.value)
|
return rlp.Encode(w, g.value)
|
||||||
|
|
@ -43,37 +50,27 @@ func (g *generic) DecodeRLP(s *rlp.Stream) error {
|
||||||
return s.Decode(g.value)
|
return s.Decode(g.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithKey returns a new Key that can be set in a Record.
|
// WithKey wraps any value with a key name. It can be used to set and load arbitrary values
|
||||||
// v must implement the rlp.Encoder and rlp.Decoder interface.
|
// in a record. The value v must be supported by rlp. To use WithKey with Load, the value
|
||||||
|
// must be a pointer.
|
||||||
func WithKey(k string, v interface{}) Key {
|
func WithKey(k string, v interface{}) Key {
|
||||||
return &generic{key: k, value: v}
|
return &generic{key: k, value: v}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiscPort represents an UDP port for discovery v5.
|
// DiscPort is the "discv5" key, which holds the UDP port for discovery v5.
|
||||||
type DiscPort uint16
|
type DiscPort uint16
|
||||||
|
|
||||||
// ENRKey returns the node record key for an UDP port for discovery.
|
func (v DiscPort) ENRKey() string { return "discv5" }
|
||||||
func (DiscPort) ENRKey() string {
|
|
||||||
return "discv5"
|
|
||||||
}
|
|
||||||
|
|
||||||
const ID_SECP256k1_KECCAK = "secp256k1-keccak" // identity scheme identifier
|
// ID is the "id" key, which holds the name of the identity scheme.
|
||||||
|
|
||||||
// ID is the name of the identity scheme, e.g. "secp256k1-keccak".
|
|
||||||
type ID string
|
type ID string
|
||||||
|
|
||||||
// ENRKey returns the node record key for its identity scheme.
|
func (v ID) ENRKey() string { return "id" }
|
||||||
func (ID) ENRKey() string {
|
|
||||||
return "id"
|
|
||||||
}
|
|
||||||
|
|
||||||
// IP4 represents an 4-byte IPv4 address in a node record.
|
// IP4 is the "ip4" key, which holds a 4-byte IPv4 address.
|
||||||
type IP4 net.IP
|
type IP4 net.IP
|
||||||
|
|
||||||
// ENRKey returns the node record key for an IPv4 address.
|
func (v IP4) ENRKey() string { return "ip4" }
|
||||||
func (IP4) ENRKey() string {
|
|
||||||
return "ip4"
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeRLP implements rlp.Encoder.
|
// EncodeRLP implements rlp.Encoder.
|
||||||
func (v IP4) EncodeRLP(w io.Writer) error {
|
func (v IP4) EncodeRLP(w io.Writer) error {
|
||||||
|
|
@ -95,13 +92,10 @@ func (v *IP4) DecodeRLP(s *rlp.Stream) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP6 represents an 16-byte IPv6 address in a node record.
|
// IP6 is the "ip6" key, which holds a 16-byte IPv6 address.
|
||||||
type IP6 net.IP
|
type IP6 net.IP
|
||||||
|
|
||||||
// ENRKey returns the node record key for an IPv6 address.
|
func (v IP6) ENRKey() string { return "ip6" }
|
||||||
func (IP6) ENRKey() string {
|
|
||||||
return "ip6"
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeRLP implements rlp.Encoder.
|
// EncodeRLP implements rlp.Encoder.
|
||||||
func (v IP6) EncodeRLP(w io.Writer) error {
|
func (v IP6) EncodeRLP(w io.Writer) error {
|
||||||
|
|
@ -120,13 +114,10 @@ func (v *IP6) DecodeRLP(s *rlp.Stream) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secp256k1 is compressed secp256k1 public key.
|
// Secp256k1 is the "secp256k1" key, which holds a public key.
|
||||||
type Secp256k1 ecdsa.PublicKey
|
type Secp256k1 ecdsa.PublicKey
|
||||||
|
|
||||||
// ENRKey returns the node record key for the secp256k1 public key.
|
func (v Secp256k1) ENRKey() string { return "secp256k1" }
|
||||||
func (Secp256k1) ENRKey() string {
|
|
||||||
return "secp256k1"
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeRLP implements rlp.Encoder.
|
// EncodeRLP implements rlp.Encoder.
|
||||||
func (v Secp256k1) EncodeRLP(w io.Writer) error {
|
func (v Secp256k1) EncodeRLP(w io.Writer) error {
|
||||||
|
|
@ -151,3 +142,24 @@ func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyError is an error related to a key.
|
||||||
|
type KeyError struct {
|
||||||
|
Key string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements error.
|
||||||
|
func (err *KeyError) Error() string {
|
||||||
|
if err.Err == errNotFound {
|
||||||
|
return fmt.Sprintf("missing ENR key %q", err.Key)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("ENR key %q: %v", err.Key, err.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotFound reports whether the given error means that a key/value pair is
|
||||||
|
// missing from a record.
|
||||||
|
func IsNotFound(err error) bool {
|
||||||
|
kerr, ok := err.(*KeyError)
|
||||||
|
return ok && kerr.Err == errNotFound
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue