-
Notifications
You must be signed in to change notification settings - Fork 2.2k
lnp2p: add new simplified package for interacting with the p2p network #10224
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
base: actor
Are you sure you want to change the base?
Conversation
This commit establishes the foundational interfaces and types for the new P2P connection layer. The design prioritizes flexibility and testability by defining clean abstractions that decouple the P2P layer from lnd's internal subsystems. The key interfaces introduced include BrontideConn for abstracting encrypted connections, P2PConnection for high-level connection management, and supporting types for connection state management, node addressing, key generation, and connection lifecycle events. These interfaces enable multiple implementation strategies, from simple direct connections for testing to sophisticated actor-based systems for production use.
SimplePeer provides a lightweight implementation of the P2PConnection interface that focuses on simplicity and ease of use. This implementation handles the essential P2P operations including connection establishment, message exchange, and graceful disconnection. The design uses Go iterators for message streaming, providing a clean API for consuming incoming messages. It includes built-in ping/pong handling, connection state management, and event notification for monitoring connection lifecycle changes. SimplePeer also supports an optional broadcast mode that integrates with lnd's msgmux router, enabling dual-mode message handling where messages can be consumed both through the iterator pattern and routed to registered handlers simultaneously.
This commit introduces PeerActor, a sophisticated actor-based implementation of the P2PConnection interface that leverages lnd's actor framework for concurrent message processing and distribution. The implementation features the MessageSink pattern, which allows service keys to register with optional message filtering predicates. This enables fine-grained control over which messages each handler receives, supporting scenarios where different subsystems need to process only specific message types or messages matching certain criteria. PeerActor manages connection lifecycle through actor messages, distributes incoming messages to registered service keys in parallel, and provides actor-based APIs for service key management. The design prioritizes scalability and concurrent processing while maintaining clean separation between connection management and message handling logic.
This commit establishes a robust testing foundation for the P2P layer by introducing mock implementations and test harnesses that simplify test setup and reduce boilerplate. The test infrastructure includes mockP2PConnection for simulating P2P connections with configurable behaviors, mockBrontideConn for testing at the transport layer, and a sophisticated test harness system that provides helper methods for common test patterns. The harness manages actor systems, connection expectations, and service key registration, enabling concise and expressive tests. A key addition is the newTestPubKey() helper that eliminates verbose key generation patterns throughout tests, improving readability. The infrastructure supports both simple unit tests and complex integration scenarios with multiple peers and concurrent operations.
This commit introduces comprehensive unit tests that validate the core functionality of both SimplePeer and PeerActor implementations. The tests leverage the previously added test infrastructure to minimize boilerplate while ensuring thorough coverage. For SimplePeer, the tests verify connection lifecycle, message exchange, state transitions, address handling, ping/pong responses, broadcast mode integration, and various error conditions. Special attention is given to timeout handling and proper cleanup on disconnection. For PeerActor, the tests cover message distribution to service keys, the MessageSink filtering mechanism, concurrent operations, service key management, and proper actor lifecycle handling. The tests also validate the refreshActorRefs mechanism that handles dynamic service registration and deregistration.
The integration tests validate complex multi-component scenarios that exercise the full P2P stack. These tests go beyond unit testing to verify that the various components work correctly together in realistic usage patterns. SimplePeer integration tests include establishing real connections with mock servers, handling connection failures and retries, message streaming with iterators, and init message exchange validation. The tests also cover the broadcast mode where messages are simultaneously consumed through iterators and routed to msgmux handlers. PeerActor integration tests focus on distributed message processing, including ping/pong internal handling, error and warning message distribution to multiple handlers, dynamic service key registration during message processing, and proper cleanup when actors disappear. These tests ensure that the actor-based message distribution maintains correctness under concurrent operations.
This commit provides practical examples demonstrating how to use the P2P layer in various scenarios. The examples serve as both documentation and functional tests, showing real-world usage patterns that developers can adapt for their needs. The examples cover SimplePeer usage for basic connections and message exchange, PeerActor setup with service key registration and message filtering, broadcast mode configuration for dual message consumption, custom connection behaviors with mock implementations, and proper error handling and cleanup patterns. Each example is self-contained and runnable as a test, ensuring they remain valid as the API evolves. These examples are particularly valuable for developers integrating the P2P layer into testing frameworks or building custom peer behaviors for protocol experimentation.
This commit establishes lnp2p as a standalone Go module with its own dependency management. The module is versioned independently from the main lnd repository, allowing it to be imported and used by external projects including testing frameworks and experimental implementations. The dependencies are carefully curated to include only what's necessary for the P2P layer functionality, including lnd's actor framework for the actor-based implementation, core lnd packages for wire protocol and cryptographic operations, and testing dependencies like testify for the comprehensive test suite. This modular approach enables the P2P layer to evolve independently while maintaining compatibility with the broader Lightning Network ecosystem.
The lnd/actor package provides the actor framework that powers the concurrent message processing capabilities of the new P2P layer. This dependency enables the PeerActor implementation to leverage actor-based concurrency patterns for scalable message distribution. The actor framework offers a robust foundation for building concurrent systems with message-passing semantics, which aligns well with the P2P layer's requirements for handling multiple message streams and service registrations concurrently.
The test harness files were incorrectly named without the _test.go suffix, causing build failures when compiling the package. These files reference types that are only available in test builds (mockP2PConnection, mockBrontideConn, etc.), so they must be marked as test files themselves. This change renames test_harness.go to test_harness_test.go and test_mock_harness.go to test_mock_harness_test.go, ensuring they're only compiled during test builds when their dependencies are available. The test suite continues to pass with full coverage.
An example of the base API: target, err := lnp2p.ParseNodeAddress("[email protected]:9735")
if err != nil {
return err
}
cfg := lnp2p.SimplePeerConfig{
KeyGenerator: &lnp2p.EphemeralKeyGenerator{},
Target: *target,
Features: lnp2p.DefaultFeatures(),
Timeouts: lnp2p.DefaultTimeouts(),
}
peer, err := lnp2p.NewSimplePeer(cfg)
if err != nil {
return err
}
defer peer.Close()
if err := peer.Connect(context.Background()); err != nil {
return err
} This'll connect out, do the There's an iterator that can be used to recv messages, and also connection events: for msg := range peer.ReceiveMessages() {
switch m := msg.(type) {
case *lnwire.ChannelUpdate:
processUpdate(m)
case *lnwire.Error:
log.Printf("Peer error: %s", m.Data)
case *lnwire.Ping:
} for event := range peer.ConnectionEvents() {
log.Printf("[%s] State: %s", event.Timestamp, event.State)
if event.State == lnp2p.StateConnected {
// Connection established.
} else if event.State == lnp2p.StateDisconnected && event.Error != nil {
// Handle disconnection.
}
} There's also an API that's target, err := lnp2p.ParseNodeAddress("[email protected]:9735")
if err != nil {
return err
}
cfg := lnp2p.ActorWithConnConfig{
SimplePeerCfg: lnp2p.SimplePeerConfig{
KeyGenerator: &lnp2p.EphemeralKeyGenerator{},
Target: *target,
Features: lnp2p.DefaultFeatures(),
Timeouts: lnp2p.DefaultTimeouts(),
},
ActorSystem: system,
ServiceKey: channelKey,
ActorName: "channel-peer",
MessageSinks: []*lnp2p.MessageSink{
{
ServiceKey: channelKey,
Filter: channelFilter,
},
},
}
peerActor, actorRef, err := lnp2p.NewActorWithConn(cfg)
if err != nil {
return err
}
startResult := peerActor.Start(ctx)
if resp, err := startResult.Unpack(); err != nil || !resp.Success {
return err
} Here's a glimpse re how the actor system can be used to re-work the way sub-systems discover, and iteract with each other: system := actor.NewActorSystem()
defer system.Shutdown()
// Make a new service key to handle channel update messges.
channelKey := actor.NewServiceKey[lnp2p.PeerMessage, lnp2p.PeerResponse](
"channel-handler",
)
// We'll use a simple handler that just processes the updates directly, and register it with the main system.
behavior := actor.NewFunctionBehavior(func(ctx context.Context, msg lnp2p.PeerMessage) fn.Result[lnp2p.PeerResponse] {
if m, ok := msg.(*lnp2p.MessageReceived); ok {
if update, ok := m.Message.(*lnwire.ChannelUpdate); ok {
// Process channel update.
processChannelUpdate(update)
return fn.Ok[lnp2p.PeerResponse](
&lnp2p.MessageReceivedAck{Processed: true},
)
}
}
return fn.Ok[lnp2p.PeerResponse](nil)
})
actor.RegisterWithSystem(system, "channel-handler", channelKey, behavior)
// Make a MessageSink with a filter, then register it w/ the peer actor.
channelFilter := func(msg lnwire.Message) bool {
switch msg.(type) {
case *lnwire.ChannelUpdate, *lnwire.ChannelAnnouncement:
return true
default:
return false
}
}
updateSink := &lnp2p.MessageSink{
ServiceKey: channelKey,
Filter: channelFilter,
}
success := peerActor.AddMessageSink(gossipSink) |
I was working on creating some ad hoc tests to examine the p2p performance behavior of some new PRs I was working on, and reached for the
peer.Brontide
package. However, it fell short, as the package is very tightly coupled to all the sub-systems it interacts with, and also carries several assumptions re the way thatlnd
is set up.This PR is an attempt to introduce a new pacakge
lnp2p
, that implements the bare essentials of p2p connectivity (brontide handshake, init sending, ping handling, etc). This can be used to write ad hoc tests, and even expand our integration tests to test things like protocol violations, as we can use this package to basically MiTM between sub systems.I've also built on top of my
actor
package to show what a version of the peer as an actor would look like. The final version is pretty barebores, but it's easy to see how goroutines like the ping checker can actually be implemented as an actor that uses aMessageSink
to only filter for ping messages, then issues a disconnect message if violated, etc, etc.Keeping it in draft for now, as it'll likely evolve as I continue to write these p2p tests.
Diff looks large, but it's mostly tests as I was shooting for 90% + test coverage. Likely some duplication there as well.
Ideally we can eventually use this directly in
peer.Brontide
, but I think we shouldn't rush quite into that, as we've seen the recent impact of p2p bugs and how that can destabalize nodes. So for now, we can have it just be a package for experiments and tests.