-
Notifications
You must be signed in to change notification settings - Fork 215
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
Fix libp2p identify race #6573
base: develop
Are you sure you want to change the base?
Fix libp2p identify race #6573
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -25,12 +25,14 @@ | |||
tptu "github.com/libp2p/go-libp2p/p2p/net/upgrader" | ||||
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay" | ||||
"github.com/libp2p/go-libp2p/p2p/protocol/holepunch" | ||||
"github.com/libp2p/go-libp2p/p2p/protocol/identify" | ||||
"github.com/libp2p/go-libp2p/p2p/security/noise" | ||||
quic "github.com/libp2p/go-libp2p/p2p/transport/quic" | ||||
"github.com/libp2p/go-libp2p/p2p/transport/quicreuse" | ||||
"github.com/libp2p/go-libp2p/p2p/transport/tcp" | ||||
ma "github.com/multiformats/go-multiaddr" | ||||
"github.com/prometheus/client_golang/prometheus" | ||||
"go.uber.org/fx" | ||||
"go.uber.org/zap" | ||||
"go.uber.org/zap/zapcore" | ||||
|
||||
|
@@ -282,6 +284,8 @@ | |||
return nil, fmt.Errorf("can't set up connection gater: %w", err) | ||||
} | ||||
|
||||
var identifyConn func(network.Conn) | ||||
|
||||
pt := peerinfo.NewPeerInfoTracker() | ||||
lopts := []libp2p.Option{ | ||||
libp2p.Identity(key), | ||||
|
@@ -295,6 +299,11 @@ | |||
cfg.AutoNATServer.PeerMax, | ||||
cfg.AutoNATServer.ResetPeriod), | ||||
libp2p.ConnectionGater(g), | ||||
libp2p.WithFxOption(fx.Invoke(func(ids identify.IDService) { | ||||
identifyConn = func(c network.Conn) { | ||||
ids.IdentifyConn(c) | ||||
} | ||||
})), | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like a hack to me, especially since later down we panic if this doesn't work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not fail unless There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It now uses a variable of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still feels like we are digging deep through the layers of Us interjecting the build process of whatever is behind the interface returned by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The nature of Line 302 in 1fd07d4
It can also be possible to get hold on the *BasicHost via other DI tricks while not using WithFxOption but IMO that would be more hacky
|
||||
} | ||||
if cfg.EnableTCPTransport { | ||||
lopts = append(lopts, | ||||
|
@@ -413,6 +422,9 @@ | |||
if err != nil { | ||||
return nil, fmt.Errorf("failed to initialize libp2p host: %w", err) | ||||
} | ||||
if identifyConn == nil { | ||||
panic("BUG: identify service not set") | ||||
} | ||||
fasmat marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
g.updateHost(h) | ||||
h.Network().Notify(p2pmetrics.NewConnectionsMeeter()) | ||||
pt.Start(h.Network()) | ||||
|
@@ -427,6 +439,7 @@ | |||
WithBootnodes(bootnodesMap), | ||||
WithDirectNodes(g.direct), | ||||
WithPeerInfo(pt), | ||||
WithIdentifyConn(identifyConn), | ||||
) | ||||
return Upgrade(h, opts...) | ||||
} | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ import ( | |
"github.com/libp2p/go-libp2p/core/network" | ||
"github.com/libp2p/go-libp2p/core/peer" | ||
"github.com/libp2p/go-libp2p/core/protocol" | ||
basichost "github.com/libp2p/go-libp2p/p2p/host/basic" | ||
"github.com/libp2p/go-libp2p/p2p/host/eventbus" | ||
ma "github.com/multiformats/go-multiaddr" | ||
"go.uber.org/zap" | ||
|
@@ -75,6 +76,12 @@ func WithPeerInfo(pi peerinfo.PeerInfo) Opt { | |
} | ||
} | ||
|
||
func WithIdentifyConn(identifyConn func(network.Conn)) Opt { | ||
return func(fh *Host) { | ||
fh.identifyConn = identifyConn | ||
} | ||
} | ||
|
||
// Host is a convenience wrapper for all p2p related functionality required to run | ||
// a full spacemesh node. | ||
type Host struct { | ||
|
@@ -112,7 +119,8 @@ type Host struct { | |
value network.Reachability | ||
} | ||
|
||
ping *Ping | ||
ping *Ping | ||
identifyConn func(network.Conn) | ||
} | ||
|
||
// Upgrade creates Host instance from host.Host. | ||
|
@@ -128,6 +136,15 @@ func Upgrade(h host.Host, opts ...Opt) (*Host, error) { | |
for _, opt := range opts { | ||
opt(fh) | ||
} | ||
if fh.identifyConn == nil { | ||
// If no IDService is provided, which may be the case in the tests, | ||
// we can try to get it from the host, assuming it's a *basichost.BasicHost. | ||
if bh, ok := h.(*basichost.BasicHost); ok { | ||
fh.identifyConn = func(conn network.Conn) { | ||
bh.IDService().IdentifyConn(conn) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if this cast fails? If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added some clarifying comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe instead of accessing libp2p internals we should just use the methods that are exposed via the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sadly there seems to be no other way, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if the peer is already connected - There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If peer is already connected and is listed among connected peers, this does not mean |
||
} | ||
cfg := fh.cfg | ||
bootnodes, err := parseIntoAddr(fh.cfg.Bootnodes) | ||
if err != nil { | ||
|
@@ -504,3 +521,19 @@ func (fh *Host) trackNetEvents() error { | |
func (fh *Host) PeerInfo() peerinfo.PeerInfo { | ||
return fh.peerInfo | ||
} | ||
|
||
// Identify ensures that the given peer is identified via libp2p identify protocol. | ||
// Identification is initiated after connecting to the peer, and the set of protocols for | ||
// the peer in the ProtoBook is not guaranteed to be correct until identification | ||
// finishes. | ||
// Note that the set of the protocols in the ProtoBook for a particular peer may also | ||
// change via a push identity notification when the peer adds a new handler via | ||
// SetStreamHandler (e.g. sets up a new Server). | ||
func (fh *Host) Identify(p peer.ID) { | ||
for _, c := range fh.Network().ConnsToPeer(p) { | ||
// IDService.IdentifyConn is a no-op if the connection is already | ||
// identified, but otherwise we need to wait for identification to finish | ||
// to have proper set of protocols. | ||
fh.identifyConn(c) | ||
} | ||
} |
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 believe this:
has basically the same effect without a) needing to intercept the building process of the
libp2p
host and b) allowing us to pass a timeout toConnect
in case we want to abort early if something goes wrong.Connect
in both implementations ofHost
eventually callsIdentifyWait
which is also called byIdentifyCon
but allows passing a context in case we want to abort early.Wdyt?
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 will not work b/c
Connect
is noop in case if the peer is already connected:https://github.com/libp2p/go-libp2p/blob/v0.38.1/p2p/host/basic/basic_host.go#L803
So it will not call
IdentifyWait
in this case (it is invoked fromdialPeer
: https://github.com/libp2p/go-libp2p/blob/v0.38.1/p2p/host/basic/basic_host.go#L826)And if you force dial via a context option, there will be adverse side effects such as trying to establish a new connection, invoking the gater etc.
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.
There may be a way of trying to create a new stream which also calls
IdentifyWait
, but I do not like it either as it adds overhead of creating a new stream when it's not really needed, producing unwanted network trafficThere 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 I understand. For the network status to be connected doesn't this mean
Connect
has already been called and it is OK thatConnect
is a no-op? The first call to it will result inIdentifyWait
being called?Again to me this feels wrong - pulling internals out of libp2p that look like they might not be intended to be used outside of the library (
IdentifyConn
looks like a helper method for tests of theIDService
)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.
The peer is listed in the addressbook before
Connect
finishes.The need to access internals is rather unfortunate but it's due to some inconvenient decisions in
go-libp2p
codebase. BasicallyIDService
can be easily accessed if you know for sure yourHost
is a*BasicHost
, but it is harder to reach if there are someHost
wrappers. SoIDService
is not fully internal.IdentifyConn
just callsIdentifyWait
(which is not only used in tests) and waits on the channel it returns