Skip to content

Commit

Permalink
feat: cerified addressbook
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos authored and jacobheun committed Aug 12, 2020
1 parent 40e4e94 commit e0ed258
Show file tree
Hide file tree
Showing 6 changed files with 404 additions and 21 deletions.
148 changes: 136 additions & 12 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,95 @@ 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.PeerRecord contained in a record envelope.
* This will return a boolean that indicates if the record was successfully processed and integrated
* 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()) {
log('signing key does not match PeerId in the PeerRecord')
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
}

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

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

// TODO: new record with different addresses from stored record
// - Remove the older ones?
// - Change to uncertified?

// TODO: events
// Should a multiaddr only modified to certified trigger an event?
// - Needed for persistent peer store
this._setData(peerId, {
addresses,
record: {
raw: envelope.marshal(),
seqNumber: peerRecord.seqNumber
}
})
log(`stored provided peer record for ${id}`)

return true
}

/**
* Get an Envelope containing a PeerRecord for the given peer.
* @param {PeerId} peerId
* @return {Promise<Envelope>}
*/
getPeerRecord (peerId) {
const entry = this.data.get(peerId.toB58String())

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

return Envelope.createFromProtobuf(entry.record.raw)
}

/**
* Set known multiaddrs of a provided peer.
* @override
Expand All @@ -64,7 +160,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 @@ -83,7 +180,10 @@ class AddressBook extends Book {
}
}

this._setData(peerId, addresses)
this._setData(peerId, {
addresses,
record: entry.record
})
log(`stored provided multiaddrs for ${id}`)

// Notify the existance of a new peer
Expand All @@ -109,7 +209,9 @@ 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

// Add recorded uniquely to the new array (Union)
rec && rec.forEach((mi) => {
Expand All @@ -125,7 +227,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 +242,31 @@ class AddressBook extends Book {
return this
}

/**
* Get the known data of a provided peer.
* @override
* @param {PeerId} peerId
* @returns {Array<data>}
*/
get (peerId) {
// TODO: should we return Entry instead??
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 +281,8 @@ class AddressBook extends Book {
}

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

Expand All @@ -177,13 +301,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
8 changes: 5 additions & 3 deletions src/peer-store/persistent/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,11 @@ class PersistentPeerStore extends PeerStore {

this.addressBook._setData(
peerId,
decoded.addrs.map((address) => ({
multiaddr: multiaddr(address.multiaddr)
})),
{
addresses: decoded.addrs.map((address) => ({
multiaddr: multiaddr(address.multiaddr)
}))
},
{ emit: false })
break
case 'keys':
Expand Down
15 changes: 15 additions & 0 deletions src/peer-store/persistent/pb/address-book.proto.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ const protons = require('protons')

const message = `
message Addresses {
// Address represents a single multiaddr.
message Address {
required bytes multiaddr = 1;
}
// CertifiedRecord contains a serialized signed PeerRecord used to
// populate the signedAddrs list.
message CertifiedRecord {
// The Seq counter from the signed PeerRecord envelope
uint64 seq = 1;
// The serialized bytes of the SignedEnvelope containing the PeerRecord.
bytes raw = 2;
}
// The known multiaddrs.
repeated Address addrs = 1;
// The most recently received signed PeerRecord.
CertifiedRecord certified_record = 2;
}
`

Expand Down
12 changes: 7 additions & 5 deletions src/record/envelope/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,6 @@ const formatSignaturePayload = (domain, payloadType, payload) => {
])
}

/**
* Unmarshal a serialized Envelope protobuf message.
* @param {Buffer} data
* @return {Envelope}
*/
const unmarshalEnvelope = async (data) => {
const envelopeData = Protobuf.decode(data)
const peerId = await PeerId.createFromPubKey(envelopeData.public_key)
Expand All @@ -129,6 +124,13 @@ const unmarshalEnvelope = async (data) => {
})
}

/**
* Unmarshal a serialized Envelope protobuf message.
* @param {Buffer} data
* @return {Promise<Envelope>}
*/
Envelope.createFromProtobuf = unmarshalEnvelope

/**
* Seal marshals the given Record, places the marshaled bytes inside an Envelope
* and signs it with the given peerId's private key.
Expand Down
Loading

0 comments on commit e0ed258

Please sign in to comment.