Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: certified addressbook #683

Merged
merged 9 commits into from
Jul 23, 2020
2 changes: 1 addition & 1 deletion .aegir.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const after = async () => {
}

module.exports = {
bundlesize: { maxSize: '202kB' },
bundlesize: { maxSize: '205kB' },
hooks: {
pre: before,
post: after
Expand Down
43 changes: 18 additions & 25 deletions src/identify/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ class IdentifyService {
this._protocols = protocols

this.handleMessage = this.handleMessage.bind(this)

// TODO: this should be stored in the certified AddressBook in follow up PR
this._selfRecord = undefined
}

/**
Expand Down Expand Up @@ -184,22 +181,19 @@ class IdentifyService {
// Get the observedAddr if there is one
observedAddr = IdentifyService.getCleanMultiaddr(observedAddr)

let addresses

try {
const envelope = await Envelope.openAndCertify(signedPeerRecord, PeerRecord.DOMAIN)
const peerRecord = await PeerRecord.createFromProtobuf(envelope.payload)

addresses = peerRecord.multiaddrs
if (this.peerStore.addressBook.consumePeerRecord(envelope)) {
this.peerStore.protoBook.set(id, protocols)
return
}
} catch (err) {
log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
// Try Legacy
addresses = listenAddrs
}

// Update peers data in PeerStore
// LEGACY: Update peers data in PeerStore
try {
this.peerStore.addressBook.set(id, addresses.map((addr) => multiaddr(addr)))
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
} catch (err) {
log.error('received invalid addrs', err)
}
Expand Down Expand Up @@ -293,21 +287,19 @@ class IdentifyService {

const id = connection.remotePeer

let addresses

try {
const envelope = await Envelope.openAndCertify(message.signedPeerRecord, PeerRecord.DOMAIN)
const peerRecord = await PeerRecord.createFromProtobuf(envelope.payload)

addresses = peerRecord.multiaddrs
if (this.peerStore.addressBook.consumePeerRecord(envelope)) {
this.peerStore.protoBook.set(id, message.protocols)
return
}
} catch (err) {
log('received invalid envelope, discard it and fallback to listenAddrs is available', err)
// Try Legacy
addresses = message.listenAddrs
}

// LEGACY: Update peers data in PeerStore
try {
this.peerStore.addressBook.set(id, addresses.map((addr) => multiaddr(addr)))
this.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr)))
} catch (err) {
log.error('received invalid addrs', err)
}
Expand All @@ -321,9 +313,11 @@ class IdentifyService {
* @return {Buffer}
*/
async _getSelfPeerRecord () {
const selfSignedPeerRecord = this.peerStore.addressBook.getRawEnvelope(this.peerId)

// TODO: support invalidation when dynamic multiaddrs are supported
if (this._selfRecord) {
return this._selfRecord
if (selfSignedPeerRecord) {
return selfSignedPeerRecord
}

try {
Expand All @@ -332,10 +326,9 @@ class IdentifyService {
multiaddrs: this._libp2p.multiaddrs
})
const envelope = await Envelope.seal(peerRecord, this.peerId)
this.peerStore.addressBook.consumePeerRecord(envelope)

this._selfRecord = envelope.marshal()

return this._selfRecord
return this.peerStore.addressBook.getRawEnvelope(this.peerId)
} catch (err) {
log.error('failed to get self peer record')
}
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ class Libp2p extends EventEmitter {

this.peerStore = (this.datastore && this._options.peerStore.persistence)
? new PersistentPeerStore({
peerId: this.peerId,
datastore: this.datastore,
...this._options.peerStore
})
: new PeerStore()
: new PeerStore({ peerId: this.peerId })

// Addresses {listen, announce, noAnnounce}
this.addresses = this._options.addresses
Expand Down
164 changes: 148 additions & 16 deletions src/peer-store/address-book.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ const multiaddr = require('multiaddr')
const PeerId = require('peer-id')

const Book = require('./book')
const PeerRecord = require('../record/peer-record')

const {
codes: { ERR_INVALID_PARAMETERS }
} = require('../errors')
const Envelope = require('../record/envelope')

/**
* The AddressBook is responsible for keeping the known multiaddrs
Expand All @@ -23,8 +25,23 @@ class AddressBook extends Book {
* Address object
* @typedef {Object} Address
* @property {Multiaddr} multiaddr peer multiaddr.
* @property {boolean} isCertified obtained from a signed peer record.
*/

/**
* CertifiedRecord object
* @typedef {Object} CertifiedRecord
* @property {Buffer} raw raw envelope.
* @property {number} seqNumber seq counter.
*/

/**
* Entry object for the addressBook
* @typedef {Object} Entry
* @property {Array<Address>} addresses peer Addresses.
* @property {CertifiedRecord} record certified peer record.
*/

/**
* @constructor
* @param {PeerStore} peerStore
Expand All @@ -39,16 +56,104 @@ class AddressBook extends Book {
peerStore,
eventName: 'change:multiaddrs',
eventProperty: 'multiaddrs',
eventTransformer: (data) => data.map((address) => address.multiaddr)
eventTransformer: (data) => {
if (!data.addresses) {
return []
}
return data.addresses.map((address) => address.multiaddr)
}
})

/**
* Map known peers to their known Addresses.
* @type {Map<string, Array<Address>>}
* Map known peers to their known Address Entries.
* @type {Map<string, Array<Entry>>}
*/
this.data = new Map()
}

/**
* ConsumePeerRecord adds addresses from a signed peer record contained in a record envelope.
* This will return a boolean that indicates if the record was successfully processed and added
* into the AddressBook.
* @param {Envelope} envelope
* @return {boolean}
*/
consumePeerRecord (envelope) {
let peerRecord
try {
peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
} catch (err) {
log.error('invalid peer record received')
return false
}

// Verify peerId
if (peerRecord.peerId.toB58String() !== envelope.peerId.toB58String()) {
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
log('signing key does not match PeerId in the PeerRecord')
return false
}

// ensure the record has multiaddrs
if (!peerRecord.multiaddrs || !peerRecord.multiaddrs.length) {
return false
}

const peerId = peerRecord.peerId
const id = peerId.toB58String()
const entry = this.data.get(id) || {}
const storedRecord = entry.record

// ensure seq is greater than, or equal to, the last received
if (storedRecord && storedRecord.seqNumber >= peerRecord.seqNumber) {
return false
}

const addresses = this._toAddresses(peerRecord.multiaddrs, true)

// Replace unsigned addresses by the new ones from the record
// TODO: Once we have ttls for the addresses, we should merge these in.
this._setData(peerId, {
addresses,
record: {
raw: envelope.marshal(),
seqNumber: peerRecord.seqNumber
}
})
log(`stored provided peer record for ${id}`)

return true
}

/**
* Get a peer raw envelope.
* @param {PeerId} peerId
* @return {Buffer}
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
*/
getRawEnvelope (peerId) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a convenience method for getRawSelfEnvelope?
We are using this providing own peerId in the identify protocol, and I am also using it now in rendezvous

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the need here? We should already have easy access to libp2p, libp2p.peerStore.addressBook.getRawEnvelope(libp2p.peerId), I'm not seeing what we'd gain from this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not needed, but could potentially help. I did not have access to the self peerId in the rendezvous server (in the previous PR state), and I needed to change everything to provide libp2p because of that. But yeah, it was mostly a question, we don't need it, it could just help in some cases

const entry = this.data.get(peerId.toB58String())

if (!entry || !entry.record || !entry.record.raw) {
return undefined
}

return entry.record.raw
}

/**
* Get an Envelope containing a PeerRecord for the given peer.
* @param {PeerId} peerId
* @return {Promise<Envelope>}
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
*/
getPeerRecord (peerId) {
const raw = this.getRawEnvelope(peerId)

if (!raw) {
return undefined
}

return Envelope.createFromProtobuf(raw)
}

/**
* Set known multiaddrs of a provided peer.
* @override
Expand All @@ -64,7 +169,8 @@ class AddressBook extends Book {

const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const rec = this.data.get(id)
const entry = this.data.get(id) || {}
const rec = entry.addresses

// Not replace multiaddrs
if (!addresses.length) {
Expand All @@ -73,7 +179,7 @@ class AddressBook extends Book {

// Already knows the peer
if (rec && rec.length === addresses.length) {
const intersection = rec.filter((mi) => addresses.some((newMi) => mi.multiaddr.equals(newMi.multiaddr)))
const intersection = rec.filter((addr) => addresses.some((newAddr) => addr.multiaddr.equals(newAddr.multiaddr)))

// Are new addresses equal to the old ones?
// If yes, no changes needed!
Expand All @@ -83,7 +189,10 @@ class AddressBook extends Book {
}
}

this._setData(peerId, addresses)
this._setData(peerId, {
addresses,
record: entry.record
})
jacobheun marked this conversation as resolved.
Show resolved Hide resolved
log(`stored provided multiaddrs for ${id}`)

// Notify the existance of a new peer
Expand All @@ -109,12 +218,14 @@ class AddressBook extends Book {

const addresses = this._toAddresses(multiaddrs)
const id = peerId.toB58String()
const rec = this.data.get(id)

const entry = this.data.get(id) || {}
const rec = entry.addresses
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved

// Add recorded uniquely to the new array (Union)
rec && rec.forEach((mi) => {
if (!addresses.find(r => r.multiaddr.equals(mi.multiaddr))) {
addresses.push(mi)
rec && rec.forEach((addr) => {
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
if (!addresses.find(r => r.multiaddr.equals(addr.multiaddr))) {
addresses.push(addr)
}
})

Expand All @@ -125,7 +236,10 @@ class AddressBook extends Book {
return this
}

this._setData(peerId, addresses)
this._setData(peerId, {
addresses,
record: entry.record
})

log(`added provided multiaddrs for ${id}`)

Expand All @@ -137,13 +251,30 @@ class AddressBook extends Book {
return this
}

/**
* Get the known data of a provided peer.
* @override
* @param {PeerId} peerId
* @returns {Array<data>}
*/
get (peerId) {
if (!PeerId.isPeerId(peerId)) {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}

const entry = this.data.get(peerId.toB58String())

return entry && entry.addresses ? [...entry.addresses] : undefined
}

/**
* Transforms received multiaddrs into Address.
* @private
* @param {Array<Multiaddr>} multiaddrs
* @param {boolean} [isCertified]
* @returns {Array<Address>}
*/
_toAddresses (multiaddrs) {
_toAddresses (multiaddrs, isCertified = false) {
if (!multiaddrs) {
log.error('multiaddrs must be provided to store data')
throw errcode(new Error('multiaddrs must be provided'), ERR_INVALID_PARAMETERS)
Expand All @@ -158,7 +289,8 @@ class AddressBook extends Book {
}

addresses.push({
multiaddr: addr
multiaddr: addr,
isCertified
})
})

Expand All @@ -176,13 +308,13 @@ class AddressBook extends Book {
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
}

const record = this.data.get(peerId.toB58String())
const entry = this.data.get(peerId.toB58String())

if (!record) {
if (!entry || !entry.addresses) {
return undefined
}

return record.map((address) => {
return entry.addresses.map((address) => {
const multiaddr = address.multiaddr

const idString = multiaddr.getPeerId()
Expand Down
Loading