-
Notifications
You must be signed in to change notification settings - Fork 445
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: address and proto books #590
Conversation
655fe5d
to
d118b4d
Compare
@jacobheun I made a first pass through the PeerStore improvements milestone 1. This includes a brief explanation on the first post of the PR with PeerStore current usage and data structures decision. Moreover, it includes a README file with the PeerStore goals and a brief writing on its implementation and API. I would say that for now, it would be great to have your feedback on the questions/decisions written on the PR and on the README (via PR review comments). You can also look into the structure and APIs for the |
d118b4d
to
de32f25
Compare
9f5f43a
to
84a4c8a
Compare
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
84a4c8a
to
63583ab
Compare
63583ab
to
d3e4ad2
Compare
@jacobheun this is now ready to review It did not introduce any breaking change yet, as I left the older API of I will do a follow up PR with the breaking change for the peerStore API with the PRs of the modules using it. This will precede the breaking change PRs to remove |
b3df579
to
c4df833
Compare
c4df833
to
eb27b8d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Havent gone through everything yet, but sharing the review early as there is enough to go on while I go through the rest.
src/peer-store/README.md
Outdated
Libp2p's Peerstore is responsible for keeping an updated register with the relevant information of the known peers. It should gather environment changes, be able to take decisions and notice interested parties of relevant changes. The Peerstore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. These book components have similar characteristics with the `Javascript Map` implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't mention the Map piece. It's not compliant with a Map and we can't iterate on them, so I would avoid comparing the two.
It should gather environment changes, be able to take decisions and notice interested parties of relevant changes.
Can you clarify this? It's sounds like the PeerStore tries to be smart ("decisions"), it should not be smart. It should be the single source of truth for all peer data.
- If a subsystem learns about peer data it needs to go directly to the peer store.
- If any system/application/person wants to know about peers or listen for updates, they need to go to the peerstore.
src/peer-store/README.md
Outdated
The PeerStore manages the high level operations on its inner books. Moreover, the peerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter. | ||
|
||
(Future considerations: Peerstore should manage a job runner to trigger books runners for data trimming or computations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't provide enough information for future devs (including us). We should make a dedicated Future considerations
header and be a bit more descriptive about what the PeerStore might wish to do and why. What kind of data trimmings? What computations? These can be concise, but should be clear. For example:
## Future Considerations
- If multiaddr TTLs are added, the PeerStore may schedule jobs to delete all addresses that exceed the TTL to prevent AddressBook bloating
d645b63
to
b06040b
Compare
doc/API.md
Outdated
|
||
Find the stored information of a given peer. | ||
|
||
`peerStore.find(peerId)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just call this peerStore.get(peerId)
? I'm worried users might misinterpret this as a network search for the peer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree! The question is, should I do it now, or in the next PR? (this will break the return value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some additional things.
I was thinking it might be helpful to add some tests that exercise some user scenarios. Such as:
- I want to find all peers who support protocol
x
. - Given an address with no peer id, I want to find all peers that have this address.
This might help us catch any usability issues up front.
get (peerId) { | ||
if (!PeerId.isPeerId(peerId)) { | ||
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can skip the type checks of peerId
in these methods. We're documenting everything, so I feel like these are unnecessary restrictions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't like to see cannot access toString()
of undefined as an error, as someone needs to go into the code/docs and see what is the issue. This way, I think the error is useful here. if we do not try to access properties of it, I would agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can table this discussion for outside of this PR as we're already doing similar checks elsewhere. In short, I'd rather see us move to support static type checking for users that want it instead of doing runtime checks. While they're not big, doing these type checks everywhere slowly adds to bundle size as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we open a general issue to discuss this?
src/peer-store/book.js
Outdated
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) | ||
} | ||
|
||
const rec = this.data.get(peerId.toString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use PeerId.toB58String
everywhere, until we do a targeted switch to base32, otherwise we risk hitting unexpected behavior.
const rec = this.data.get(peerId.toString()) | |
const rec = this.data.get(peerId.toB58String()) |
src/peer-store/address-book.js
Outdated
* @property {number} validity NOT USED YET | ||
* @property {number} confidence NOT USED YET |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should just remove these until they're in place.
* @property {number} validity NOT USED YET | |
* @property {number} confidence NOT USED YET |
src/peer-store/address-book.js
Outdated
* "peer" - emitted when a peer is discovered by the node. | ||
* "change:multiaddrs" - emitted when the known multiaddrs of a peer change. | ||
*/ | ||
this._ps = peerStore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this just be set in the Book base class?
src/peer-store/address-book.js
Outdated
throw errcode(new Error('addresses must be provided'), ERR_INVALID_PARAMETERS) | ||
} | ||
|
||
if (!Array.isArray(addresses)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just simplify the API now and require set
be provided an array? We could avoid these checks, and just let addresses.forEach
throw the error if it's not an array.
// Add recorded uniquely to the new array (Union) | ||
rec && rec.forEach((mi) => { | ||
if (!multiaddrInfos.find(r => r.multiaddr.equals(mi.multiaddr))) { | ||
multiaddrInfos.push(mi) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably avoid mutating multiaddrInfos and rec here. I'd suggest composing a new array for the union and setting that as the new record. This would help prevent accidental modification of the underlying records.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure how this was before I moving code around to get set
and add
. However, now the multiaddrInfos
is created by this._toMultiaddrInfos()
function and it is "pushed" with data from the current record, and then set as the new data and not returned.
So, the only possible scenario of second effects here that I can see is if someone did get before and then changes the data after the add
. So, I would say that for being safe we only need to destruct the record m
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other scenario is if someone changes the multiaddr provided! So yes, it might be better to create a new array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but if we just create a new array, I think we will not solve any of these scenarios
f521893
to
30b0ae9
Compare
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
30b0ae9
to
f938535
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some minor things and API consistency changes. Also added some things to think about for followup PRs.
FYI: I updated the base branch of the PR to be 0.28.x instead of master
if (!PeerInfo.isPeerInfo(peerInfo)) { | ||
throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) | ||
} | ||
// TODO: This is not a `peer-info` instance anymore, but an object with the data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps to avoid confusion, when we get rid of PeerInfo we should start calling variables peerData
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was having a hard time with this naming confusion. Good suggestion! 👍
Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
db2e890
to
7dcc78e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good 👍
* feat: address and proto books * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: minor fixes and initial tests added * chore: integrate new peer-store with code using adapters for other modules * chore: do not use peerstore.put on get-peer-info * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: add new peer store tests * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* feat: address and proto books * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: minor fixes and initial tests added * chore: integrate new peer-store with code using adapters for other modules * chore: do not use peerstore.put on get-peer-info * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: add new peer store tests * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* feat: address and proto books * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: minor fixes and initial tests added * chore: integrate new peer-store with code using adapters for other modules * chore: do not use peerstore.put on get-peer-info * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: add new peer store tests * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> Co-authored-by: Jacob Heun <jacobheun@gmail.com>
* feat: address and proto books * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: minor fixes and initial tests added * chore: integrate new peer-store with code using adapters for other modules * chore: do not use peerstore.put on get-peer-info * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * chore: add new peer store tests * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> Co-authored-by: Jacob Heun <jacobheun@gmail.com>
AddressBook and ProtoBook
This is the initial milestone regarding
js-libp2p
PeerStore improvements described on libp2p/js-libp2p#582This PR restructures the initial version of
peerStore
, where we simply had a record of PeerInfos into data structures with different concerns and own logic.We will start by introducing the
AddressBook
andProtoBook
since this was the content being used from the PeerInfos previously stored. In next PRs we will add other "books", namely the keybook and metadata book.peer-info
from Ping (initial version implemented)PeerStore usage
Change Peers data
identifyService
Upgrader: onConnection handler
Discovery
DHT
Get Peers data
Registrar
protocol:change
event and to iterate on all the known peers to check if they run the protocoldialProtocol
Dialer
peerStore.multiaddrsForPeer
to get the multiaddrs for a PeerInfogetPeerInfo
ping
Datastructure
The books datastructures where designed with consistency in mind among them and with their underlying data structure (Map). All should contain the main map methods
set
,get
,has
anddelete
, which should work in the same way as the map methods by default, but with some extra optional parameters to add additional super powers.They were separated on their own instead of living on the top level
peerStore
for some reasons:set(peerId, data, [options = { replace = true }]) :
Array<data>
Set known data
{Array<multiaddr>, Array<string>, ...}
of a provided peer. By default, if previous data exists, it will replace it. If thereplace
options is explicitly defined tofalse
, the data will be merged.When the recorded data is inserted a new event should be triggered for the interested parties. If a record for the provided peer already exists and the new data is not relevant (i.e the final content will be the same) the operation just returns the final content, but no event is triggered for efficiency.
get(peerId) :
Array<data>
Get known data of a provided peer. If the peer does not exist, it returns
undefined
.has(peerId) :
boolean
Do we have data for this peer?
delete(peerId, [data]) :
boolean
Deletes the provided peer from the book. If data is provided, just remove the data
Returns true if the peer's data was fond and the data was deleted (or the partial data did not already exist). If data is modified or removed, the proper event change is triggered to the interest parties.
Concern: what if the array of data gets empty? should we remove the peer? I don't see any good reason to keep it!
General open questions
peer
?