From 83260dfd12bf6117d1fc290aea836bc7bc6a5bec Mon Sep 17 00:00:00 2001
From: Yusef Napora <yusef@protocol.ai>
Date: Fri, 20 Dec 2019 16:13:11 -0500
Subject: [PATCH] add routing state records, extend peerstore API

---
 go.mod                         |   1 +
 go.sum                         |   1 +
 peerstore/peerstore.go         |  11 +-
 routing/pb/Makefile            |  11 +
 routing/pb/routing_state.pb.go | 621 +++++++++++++++++++++++++++++++++
 routing/pb/routing_state.proto |  12 +
 routing/state.go               | 116 ++++++
 7 files changed, 772 insertions(+), 1 deletion(-)
 create mode 100644 routing/pb/Makefile
 create mode 100644 routing/pb/routing_state.pb.go
 create mode 100644 routing/pb/routing_state.proto
 create mode 100644 routing/state.go

diff --git a/go.mod b/go.mod
index 36826c5..7e16549 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ require (
 	github.com/btcsuite/btcd v0.20.1-beta
 	github.com/coreos/go-semver v0.3.0
 	github.com/gogo/protobuf v1.3.1
+	github.com/golang/protobuf v1.3.1
 	github.com/ipfs/go-cid v0.0.4
 	github.com/jbenet/goprocess v0.1.3
 	github.com/libp2p/go-flow-metrics v0.0.3
diff --git a/go.sum b/go.sum
index 34f6f23..30ec683 100644
--- a/go.sum
+++ b/go.sum
@@ -34,6 +34,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
diff --git a/peerstore/peerstore.go b/peerstore/peerstore.go
index 7f2c01c..399b6fe 100644
--- a/peerstore/peerstore.go
+++ b/peerstore/peerstore.go
@@ -96,6 +96,10 @@ type AddrBook interface {
 	// If the manager has a longer TTL, the operation is a no-op for that address
 	AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration)
 
+	// AddCertifiedAddrs adds addresses from a PeerStateRecord contained
+	// in the given SignedEnvelope.
+	AddCertifiedAddrs(envelope *ic.SignedEnvelope, ttl time.Duration) error
+
 	// SetAddr calls mgr.SetAddrs(p, addr, ttl)
 	SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration)
 
@@ -107,9 +111,14 @@ type AddrBook interface {
 	// the given oldTTL to have the given newTTL.
 	UpdateAddrs(p peer.ID, oldTTL time.Duration, newTTL time.Duration)
 
-	// Addresses returns all known (and valid) addresses for a given peer
+	// Addrs returns all known (and valid) addresses for a given peer
 	Addrs(p peer.ID) []ma.Multiaddr
 
+	// CertifiedAddrs returns all known addresses for a peer that have
+	// been certified by that peer. CertifiedAddrs are contained in
+	// a SignedEnvelope and added to the Peerstore using AddCertifiedAddrs.
+	CertifiedAddrs(p peer.ID) []ma.Multiaddr
+
 	// AddrStream returns a channel that gets all addresses for a given
 	// peer sent on it. If new addresses are added after the call is made
 	// they will be sent along through the channel as well.
diff --git a/routing/pb/Makefile b/routing/pb/Makefile
new file mode 100644
index 0000000..df34e54
--- /dev/null
+++ b/routing/pb/Makefile
@@ -0,0 +1,11 @@
+PB = $(wildcard *.proto)
+GO = $(PB:.proto=.pb.go)
+
+all: $(GO)
+
+%.pb.go: %.proto
+		protoc --proto_path=$(GOPATH)/src:. --gogofaster_out=. $<
+
+clean:
+		rm -f *.pb.go
+		rm -f *.go
diff --git a/routing/pb/routing_state.pb.go b/routing/pb/routing_state.pb.go
new file mode 100644
index 0000000..82b475a
--- /dev/null
+++ b/routing/pb/routing_state.pb.go
@@ -0,0 +1,621 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: routing_state.proto
+
+package routing_pb
+
+import (
+	fmt "fmt"
+	github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto"
+	proto "github.com/gogo/protobuf/proto"
+	io "io"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
+
+type RoutingStateRecord struct {
+	PeerId    []byte                            `protobuf:"bytes,1,req,name=peerId" json:"peerId"`
+	Seq       uint64                            `protobuf:"varint,2,req,name=seq" json:"seq"`
+	Addresses []*RoutingStateRecord_AddressInfo `protobuf:"bytes,3,rep,name=addresses" json:"addresses,omitempty"`
+}
+
+func (m *RoutingStateRecord) Reset()         { *m = RoutingStateRecord{} }
+func (m *RoutingStateRecord) String() string { return proto.CompactTextString(m) }
+func (*RoutingStateRecord) ProtoMessage()    {}
+func (*RoutingStateRecord) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2bc7f62bc26d3acc, []int{0}
+}
+func (m *RoutingStateRecord) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *RoutingStateRecord) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_RoutingStateRecord.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalTo(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *RoutingStateRecord) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RoutingStateRecord.Merge(m, src)
+}
+func (m *RoutingStateRecord) XXX_Size() int {
+	return m.Size()
+}
+func (m *RoutingStateRecord) XXX_DiscardUnknown() {
+	xxx_messageInfo_RoutingStateRecord.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RoutingStateRecord proto.InternalMessageInfo
+
+func (m *RoutingStateRecord) GetPeerId() []byte {
+	if m != nil {
+		return m.PeerId
+	}
+	return nil
+}
+
+func (m *RoutingStateRecord) GetSeq() uint64 {
+	if m != nil {
+		return m.Seq
+	}
+	return 0
+}
+
+func (m *RoutingStateRecord) GetAddresses() []*RoutingStateRecord_AddressInfo {
+	if m != nil {
+		return m.Addresses
+	}
+	return nil
+}
+
+type RoutingStateRecord_AddressInfo struct {
+	Multiaddr []byte `protobuf:"bytes,1,req,name=multiaddr" json:"multiaddr"`
+}
+
+func (m *RoutingStateRecord_AddressInfo) Reset()         { *m = RoutingStateRecord_AddressInfo{} }
+func (m *RoutingStateRecord_AddressInfo) String() string { return proto.CompactTextString(m) }
+func (*RoutingStateRecord_AddressInfo) ProtoMessage()    {}
+func (*RoutingStateRecord_AddressInfo) Descriptor() ([]byte, []int) {
+	return fileDescriptor_2bc7f62bc26d3acc, []int{0, 0}
+}
+func (m *RoutingStateRecord_AddressInfo) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *RoutingStateRecord_AddressInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_RoutingStateRecord_AddressInfo.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalTo(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *RoutingStateRecord_AddressInfo) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RoutingStateRecord_AddressInfo.Merge(m, src)
+}
+func (m *RoutingStateRecord_AddressInfo) XXX_Size() int {
+	return m.Size()
+}
+func (m *RoutingStateRecord_AddressInfo) XXX_DiscardUnknown() {
+	xxx_messageInfo_RoutingStateRecord_AddressInfo.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RoutingStateRecord_AddressInfo proto.InternalMessageInfo
+
+func (m *RoutingStateRecord_AddressInfo) GetMultiaddr() []byte {
+	if m != nil {
+		return m.Multiaddr
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*RoutingStateRecord)(nil), "routing.pb.RoutingStateRecord")
+	proto.RegisterType((*RoutingStateRecord_AddressInfo)(nil), "routing.pb.RoutingStateRecord.AddressInfo")
+}
+
+func init() { proto.RegisterFile("routing_state.proto", fileDescriptor_2bc7f62bc26d3acc) }
+
+var fileDescriptor_2bc7f62bc26d3acc = []byte{
+	// 198 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2e, 0xca, 0x2f, 0x2d,
+	0xc9, 0xcc, 0x4b, 0x8f, 0x2f, 0x2e, 0x49, 0x2c, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
+	0xe2, 0x82, 0x0a, 0xea, 0x15, 0x24, 0x29, 0x1d, 0x67, 0xe4, 0x12, 0x0a, 0x82, 0x70, 0x83, 0x41,
+	0x4a, 0x82, 0x52, 0x93, 0xf3, 0x8b, 0x52, 0x84, 0x64, 0xb8, 0xd8, 0x0a, 0x52, 0x53, 0x8b, 0x3c,
+	0x53, 0x24, 0x18, 0x15, 0x98, 0x34, 0x78, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0x8a,
+	0x09, 0x89, 0x71, 0x31, 0x17, 0xa7, 0x16, 0x4a, 0x30, 0x29, 0x30, 0x69, 0xb0, 0x40, 0xa5, 0x40,
+	0x02, 0x42, 0x1e, 0x5c, 0x9c, 0x89, 0x29, 0x29, 0x45, 0xa9, 0xc5, 0xc5, 0xa9, 0xc5, 0x12, 0xcc,
+	0x0a, 0xcc, 0x1a, 0xdc, 0x46, 0x5a, 0x7a, 0x08, 0xcb, 0xf4, 0x30, 0x2d, 0xd2, 0x73, 0x84, 0xa8,
+	0xf7, 0xcc, 0x4b, 0xcb, 0x0f, 0x42, 0x68, 0x96, 0x32, 0xe4, 0xe2, 0x46, 0x92, 0x11, 0x52, 0xe2,
+	0xe2, 0xcc, 0x2d, 0xcd, 0x29, 0xc9, 0x04, 0x29, 0x40, 0x71, 0x11, 0x42, 0xd8, 0x49, 0xe2, 0xc4,
+	0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1,
+	0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0x00, 0x01, 0x00, 0x00, 0xff, 0xff, 0x13, 0x14,
+	0xbe, 0xc4, 0x05, 0x01, 0x00, 0x00,
+}
+
+func (m *RoutingStateRecord) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalTo(dAtA)
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *RoutingStateRecord) MarshalTo(dAtA []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	if m.PeerId != nil {
+		dAtA[i] = 0xa
+		i++
+		i = encodeVarintRoutingState(dAtA, i, uint64(len(m.PeerId)))
+		i += copy(dAtA[i:], m.PeerId)
+	}
+	dAtA[i] = 0x10
+	i++
+	i = encodeVarintRoutingState(dAtA, i, uint64(m.Seq))
+	if len(m.Addresses) > 0 {
+		for _, msg := range m.Addresses {
+			dAtA[i] = 0x1a
+			i++
+			i = encodeVarintRoutingState(dAtA, i, uint64(msg.Size()))
+			n, err := msg.MarshalTo(dAtA[i:])
+			if err != nil {
+				return 0, err
+			}
+			i += n
+		}
+	}
+	return i, nil
+}
+
+func (m *RoutingStateRecord_AddressInfo) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalTo(dAtA)
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *RoutingStateRecord_AddressInfo) MarshalTo(dAtA []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	if m.Multiaddr != nil {
+		dAtA[i] = 0xa
+		i++
+		i = encodeVarintRoutingState(dAtA, i, uint64(len(m.Multiaddr)))
+		i += copy(dAtA[i:], m.Multiaddr)
+	}
+	return i, nil
+}
+
+func encodeVarintRoutingState(dAtA []byte, offset int, v uint64) int {
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return offset + 1
+}
+func (m *RoutingStateRecord) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.PeerId != nil {
+		l = len(m.PeerId)
+		n += 1 + l + sovRoutingState(uint64(l))
+	}
+	n += 1 + sovRoutingState(uint64(m.Seq))
+	if len(m.Addresses) > 0 {
+		for _, e := range m.Addresses {
+			l = e.Size()
+			n += 1 + l + sovRoutingState(uint64(l))
+		}
+	}
+	return n
+}
+
+func (m *RoutingStateRecord_AddressInfo) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Multiaddr != nil {
+		l = len(m.Multiaddr)
+		n += 1 + l + sovRoutingState(uint64(l))
+	}
+	return n
+}
+
+func sovRoutingState(x uint64) (n int) {
+	for {
+		n++
+		x >>= 7
+		if x == 0 {
+			break
+		}
+	}
+	return n
+}
+func sozRoutingState(x uint64) (n int) {
+	return sovRoutingState(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *RoutingStateRecord) Unmarshal(dAtA []byte) error {
+	var hasFields [1]uint64
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowRoutingState
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: RoutingStateRecord: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: RoutingStateRecord: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PeerId", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRoutingState
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.PeerId = append(m.PeerId[:0], dAtA[iNdEx:postIndex]...)
+			if m.PeerId == nil {
+				m.PeerId = []byte{}
+			}
+			iNdEx = postIndex
+			hasFields[0] |= uint64(0x00000001)
+		case 2:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Seq", wireType)
+			}
+			m.Seq = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRoutingState
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Seq |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			hasFields[0] |= uint64(0x00000002)
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRoutingState
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Addresses = append(m.Addresses, &RoutingStateRecord_AddressInfo{})
+			if err := m.Addresses[len(m.Addresses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipRoutingState(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+	if hasFields[0]&uint64(0x00000001) == 0 {
+		return github_com_gogo_protobuf_proto.NewRequiredNotSetError("peerId")
+	}
+	if hasFields[0]&uint64(0x00000002) == 0 {
+		return github_com_gogo_protobuf_proto.NewRequiredNotSetError("seq")
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *RoutingStateRecord_AddressInfo) Unmarshal(dAtA []byte) error {
+	var hasFields [1]uint64
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowRoutingState
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: AddressInfo: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: AddressInfo: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Multiaddr", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowRoutingState
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Multiaddr = append(m.Multiaddr[:0], dAtA[iNdEx:postIndex]...)
+			if m.Multiaddr == nil {
+				m.Multiaddr = []byte{}
+			}
+			iNdEx = postIndex
+			hasFields[0] |= uint64(0x00000001)
+		default:
+			iNdEx = preIndex
+			skippy, err := skipRoutingState(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			if (iNdEx + skippy) < 0 {
+				return ErrInvalidLengthRoutingState
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+	if hasFields[0]&uint64(0x00000001) == 0 {
+		return github_com_gogo_protobuf_proto.NewRequiredNotSetError("multiaddr")
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipRoutingState(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowRoutingState
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowRoutingState
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+			return iNdEx, nil
+		case 1:
+			iNdEx += 8
+			return iNdEx, nil
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowRoutingState
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthRoutingState
+			}
+			iNdEx += length
+			if iNdEx < 0 {
+				return 0, ErrInvalidLengthRoutingState
+			}
+			return iNdEx, nil
+		case 3:
+			for {
+				var innerWire uint64
+				var start int = iNdEx
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return 0, ErrIntOverflowRoutingState
+					}
+					if iNdEx >= l {
+						return 0, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					innerWire |= (uint64(b) & 0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				innerWireType := int(innerWire & 0x7)
+				if innerWireType == 4 {
+					break
+				}
+				next, err := skipRoutingState(dAtA[start:])
+				if err != nil {
+					return 0, err
+				}
+				iNdEx = start + next
+				if iNdEx < 0 {
+					return 0, ErrInvalidLengthRoutingState
+				}
+			}
+			return iNdEx, nil
+		case 4:
+			return iNdEx, nil
+		case 5:
+			iNdEx += 4
+			return iNdEx, nil
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+	}
+	panic("unreachable")
+}
+
+var (
+	ErrInvalidLengthRoutingState = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowRoutingState   = fmt.Errorf("proto: integer overflow")
+)
diff --git a/routing/pb/routing_state.proto b/routing/pb/routing_state.proto
new file mode 100644
index 0000000..0d6f63e
--- /dev/null
+++ b/routing/pb/routing_state.proto
@@ -0,0 +1,12 @@
+syntax = "proto2";
+package routing.pb;
+
+message RoutingStateRecord {
+    message AddressInfo {
+        required bytes multiaddr = 1;
+    }
+
+    required bytes peerId = 1;
+    required uint64 seq = 2;
+    repeated AddressInfo addresses = 3;
+}
diff --git a/routing/state.go b/routing/state.go
new file mode 100644
index 0000000..490f99c
--- /dev/null
+++ b/routing/state.go
@@ -0,0 +1,116 @@
+package routing
+
+import (
+	"errors"
+	"github.com/gogo/protobuf/proto"
+	"github.com/libp2p/go-libp2p-core/crypto"
+	"github.com/libp2p/go-libp2p-core/peer"
+	ma "github.com/multiformats/go-multiaddr"
+	pb "github.com/libp2p/go-libp2p-core/routing/pb"
+)
+
+// The domain string used for routing state records contained in a SignedEnvelope.
+const StateEnvelopeDomain = "libp2p-routing-record"
+
+// AnnotatedAddr will extend the Multiaddr type with additional metadata, as
+// extensions are added to the routing state record spec. It's defined now to
+// make refactoring simpler in the future.
+type AnnotatedAddr struct {
+	ma.Multiaddr
+}
+
+// RoutingState contains a snapshot of public, transient state (e.g. addresses, supported protocols)
+// for a peer at a given point in time, where "time" is defined by the sequence counter
+// field Seq. Greater Seq values are later in time than lesser values, but there are no
+// guarantees about the wall-clock time between any two Seq values.
+//
+// Note that Seq values are peer-specific and can only be compared for records with equal PeerIDs.
+type RoutingState struct {
+	// PeerID is the ID of the peer this record pertains to.
+	PeerID peer.ID
+
+	// Seq is an increment-only sequence counter used to order RoutingState records in time.
+	Seq uint64
+
+	// Addresses contains the public addresses of the peer this record pertains to.
+	Addresses []*AnnotatedAddr
+}
+
+// UnmarshalRoutingState unpacks a peer RoutingState record from a serialized protobuf representation.
+func UnmarshalRoutingState(serialized []byte) (*RoutingState, error) {
+	msg := pb.RoutingStateRecord{}
+	err := proto.Unmarshal(serialized, &msg)
+	if err != nil {
+		return nil, err
+	}
+	id, err := peer.IDFromBytes(msg.PeerId)
+	if err != nil {
+		return nil, err
+	}
+	return &RoutingState{
+		PeerID:    id,
+		Seq:       msg.Seq,
+		Addresses: addrsFromProtobuf(msg.Addresses),
+	}, nil
+}
+
+// RoutingStateFromEnvelope unwraps a peer RoutingState record from a SignedEnvelope.
+// This method will fail if the signature is invalid, or if the record
+// belongs to a peer other than the one that signed the envelope.
+func RoutingStateFromEnvelope(envelope *crypto.SignedEnvelope) (*RoutingState, error) {
+	msgBytes, err := envelope.Open(StateEnvelopeDomain)
+	if err != nil {
+		return nil, err
+	}
+	state, err := UnmarshalRoutingState(msgBytes)
+	if err != nil {
+		return nil, err
+	}
+	if !state.PeerID.MatchesPublicKey(envelope.PublicKey) {
+		return nil, errors.New("peer id in routing state record does not match signing key")
+	}
+	return state, nil
+}
+
+// Marshal serializes a RoutingState record to protobuf and returns its byte representation.
+func (s *RoutingState) Marshal() ([]byte, error) {
+	id, err := s.PeerID.MarshalBinary()
+	if err != nil {
+		return nil, err
+	}
+	msg := pb.RoutingStateRecord{
+		PeerId: id,
+		Seq: s.Seq,
+		Addresses: addrsToProtobuf(s.Addresses),
+	}
+	return proto.Marshal(&msg)
+}
+
+// Multiaddrs returns the addresses from a RoutingState record without any metadata annotations.
+func (s *RoutingState) Multiaddrs() []ma.Multiaddr {
+	out := make([]ma.Multiaddr, len(s.Addresses))
+	for i, addr := range s.Addresses {
+		out[i] = addr.Multiaddr
+	}
+	return out
+}
+
+func addrsFromProtobuf(addrs []*pb.RoutingStateRecord_AddressInfo) []*AnnotatedAddr {
+	out := make([]*AnnotatedAddr, 0)
+	for _, addr := range addrs {
+		a, err := ma.NewMultiaddrBytes(addr.Multiaddr)
+		if err != nil {
+			continue
+		}
+		out = append(out, &AnnotatedAddr{Multiaddr: a})
+	}
+	return out
+}
+
+func addrsToProtobuf(addrs []*AnnotatedAddr) []*pb.RoutingStateRecord_AddressInfo {
+	out := make([]*pb.RoutingStateRecord_AddressInfo, 0)
+	for _, addr := range addrs {
+		out = append(out, &pb.RoutingStateRecord_AddressInfo{Multiaddr: addr.Bytes()})
+	}
+	return out
+}
\ No newline at end of file