-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
[KS-70] Minimal RageP2P wrapper #12296
Conversation
I see that you haven't updated any README files. Would it make sense to do so? |
7fd1df3
to
ec55ca2
Compare
ec55ca2
to
49acf9b
Compare
49acf9b
to
b51b904
Compare
b51b904
to
122ef31
Compare
core/services/p2p/peer.go
Outdated
lggr logger.Logger | ||
} | ||
|
||
func NewPeer(cfg PeerConfig, lggr logger.Logger) (Peer, error) { |
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'm generally not a fan of returning interfaces: it's idiomatic in Go to return concrete types (eg. structs) and accept interfaces. So in this case, it's fine (but odd) to return *peer and then expect any users to use an interface type throughout.
Arguably I'd say the same thing about the Peer interface type -- it should live in the consuming package (or in a types package). If its purpose is to define the public interface, you can do that on the struct directly.
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.
Agreed with returning concrete types (fixing here).
I'm a fan of having an explicit interface because then I can generate mocks with Mockery. Can that be done for structs too?
If not, I'm OK with moving the interface to a "types" sub-package. I understand the arguments for placing it in the consuming package but what if there are multiple consumers? They shouldn't depend on one another so there would be multiple copies of the same interface and mock.
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'm not sure you can generate a mock from a struct, but in that case I would leave the interface as a private detail of the package (and specifically the test files) and use mockery on that
core/services/p2p/peer.go
Outdated
services.Service | ||
UpdatePeerAddresses(peerIDs []ragetypes.PeerID) error | ||
Send(peerID ragetypes.PeerID, msg []byte) error | ||
Receive() <-chan Message |
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.
Is there a benefit to exposing the internal detail of how reception happens (i.e. via a channel)? As opposed to exposing a generic Receive(Message) function?
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 would bet the channel makes things more difficult if you are mapping to grpc streams underneath.
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.
chan
seems fine for a raw API though 🤷
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.
By "generic Receive(Message) function?" do you mean a callback? I want to push all messages to some Receiver/Dispatcher object as they arrive, channel seems fine to me.
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 mean hiding the channel as an internal detail as opposed to having it as an external one. One benefit for exposing the channel I guess is that you can use it for synchronization with select
.
for _, pid := range peerIDs { | ||
pid := pid | ||
if pid == p.myID { // don't create a self-stream | ||
continue |
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.
Is it common in golang to use continues in for loops? I was told to avoid using them in most languages because they act a bit like jumps and can make it a little harder to understand control flow in loops. It's pretty easy to avoid using them:
if pid != p.myID {
.... rest of the for loop logic here
}
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.
You can make arguments either way. I remember reading an opinion that the more nested some logic is the harder it is to understand. I don't know of any Go-specific recommendations, maybe @jmank88 knows.
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 haven't seen much objection to continue
in go, but there is definitely a tendency towards reducing nesting/indentation, so yeah maybe that alleviates this concern.
I suppose that there is an argument that using a continue in a for like this is actually analogous to the go proverb "return early, return often", which is meant to reduce indentation 🤷
122ef31
to
dc51f88
Compare
dc51f88
to
a329ae0
Compare
a329ae0
to
b48d48e
Compare
A minimal counterpart of libOCR's peer_v2+endpoint_v2+bootstrapper_v2 with following differences: 1. Ability to change a set of peers dynamically. 2. Identify peers by their PeerIDs instead of Oracle IDs.
b48d48e
to
fd5c79b
Compare
Quality Gate passedIssues Measures |
A minimal counterpart of libOCR's peer_v2+endpoint_v2+bootstrapper_v2 with following differences: 1. Ability to change a set of peers dynamically. 2. Identify peers by their PeerIDs instead of Oracle IDs.
if err := p.discoverer.RemoveGroup(defaultGroupID); err != nil { | ||
p.lggr.Warnw("failed to remove old group", "groupID", defaultGroupID) | ||
} | ||
peerIDs := []ragetypes.PeerID{} | ||
for pid := range peers { | ||
peerIDs = append(peerIDs, pid) | ||
} | ||
if err := p.discoverer.AddGroup(defaultGroupID, peerIDs, p.cfg.Bootstrappers); err != nil { | ||
p.lggr.Warnw("failed to add group", "groupID", defaultGroupID) | ||
return err | ||
} | ||
return nil |
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.
Double checking, is your goal to have a fully connected graph of all peers?
I believe you could avoid the need to recreate the group (and thus potentially rediscover already known peers) by slightly modifying the logic here:
- keep track of latest group id
gid
you used - when
UpdateConnections
is called:- register the new set of peers under a new group id
gid'
- remove the old groupid
gid
gid := gid'
- register the new set of peers under a new group id
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.
Nice! I didn't realize that it's OK for groups to overlap. I'll change it in a separate PR, thanks :)
A minimal counterpart of libOCR's peer_v2+endpoint_v2+bootstrapper_v2 with following differences: