Unverified Commit 9f25f923 authored by Abhishek Upperwal's avatar Abhishek Upperwal Committed by GitHub
Browse files

Merge pull request #2 from libp2p/master

refactor merge
parents aedbe954 9e5ef7a4
5.0.17: QmWsV6kzPaYGBDVyuUfWBvyQygEc9Qrv9vzo8vZ7X4mdLN
6.0.6: QmY51bqSM5XgxQZqsBrQcRkKTnCb8EKpJpR9K6Qax7Njco
# go-libp2p release notes
## 6.0.0
We're pleased to announce go-libp2p 6.0.0. This release includes a massive
refactor of go-libp2p that paves the way for new transports such as QUIC.
Unfortunately, as it is broad sweeping, there are some breaking changes,
*especially* for maintainers of custom transports.
Below, we cover the changes you'll likely care about. For convenience, we've
broken this into a section for users and transport authors/maintainers. However,
transport maintainers should really read both sections.
### For Users
Libp2p users should be aware of a few major changes.
* Guarantees and performance concerning connect/disconnect notification
processing have improved.
* Handling of half-closed streams has changed (READ THIS SECTION).
* Some constructors and method signatures have changed slightly.
#### Dialing And Source Addresses
We've improved the logic that selects the source address when dialing. In the
past, you may have run into an issue where you couldn't dial non-local nodes
when listening on 127.0.0.1 as opposed to 0.0.0.0. This happened because
go-libp2p would randomly pick the source address from the set of addresses on
which the node was listening. We did this to ensure that the other end could
dial us back at the same address. Unfortunately, one can't use 127.0.0.1 as a
source address when dialing any non-local address so outbound dials failed.
go-libp2p now tries to be smarter about this and avoids picking source addresses
that have no route to the destination address.
#### Bandwidth Metrics
To start out on an unhappy note, bandwidth metrics are now less accurate. In the
past, transports returned "minimal" connections (e.g., a TCP connection) so we
could wrap these transport connections in "metrics" connections that counted
every byte sent and received.
Unfortunately, now that we've moved encryption and multiplexing down into the
transport layer, the connection we're wrapping has significantly more
under-the-covers overhead.
However, we do hope to improve this and get even *better* bandwidth metrics than
we did before. See [libp2p/go-libp2p-transport#31][] for details.
[libp2p/go-libp2p-transport#31]: https://github.com/libp2p/go-libp2p-transport/issues/31
#### Notifications
This release brings performance improvements and easy to reason about ordering
guarantees libp2p connect/disconnect events:
1. For any given connection/stream, libp2p will wait for all connect/open event
handlers to finish exit before triggering a disconnect/close event for the
connection/stream.
2. When a user calls the Close (or `Reset`) method on a connection or stream,
go-libp2p will process the close event asynchronously (i.e., not block the
call to `Close`). Otherwise, a call to `Close` from within a connect event
handler would deadlock.
3. Unless otherwise noted, events will be handled in parallel.
What does this mean for end users? Well:
1. Reference counting connections to a peer using connect/disconnect events
should "just work" and should never go negative.
2. Under heavy connect/disconnect loads, connecting to new peers should be
faster (usually).
For those interested in the history of this issue, ...
In the past, (dis)connect and stream open/close notifications have been a bit of
a pain point. For a long time, they were fired off in parallel and one could, for
example, process a disconnect notification before a connect notification (we had
to support *negative* ref-counts in several places to account for this).
After no end of trouble, we finally "fixed" this by synchronizing notification
delivery. We still delivered notifications to all notifiees in parallel, we just
processed the events in series.
Unfortunately, under heavy connect/disconnect load, new connections could easily
get stuck on open behind a queue of connect events all being handled in series.
In theory, these events should have been handled quickly but in practice, it's
very hard to avoid locks *entirely* (bitswap's event handlers were especially
problematic).
Worse, this serial delivery guarantee didn't actually provide us with an
*in-order* delivery guarantee as it was still possible for a disconnect to
happen before we even *started* to fire the connect event. The situation was
slightly better than before because the events couldn't overlap but still far
from optimal.
However, this has all been resolved now. From now on, you'll never receive a
disconnect event before a connect event.
#### Conn.GetStreams Signature Change
The signature of the `GetStreams` method on `go-libp2p-net.Conn` has changed from:
```go
GetStreams() ([]Stream, error)
```
To:
```go
GetStreams() []Stream
```
Listing the streams on an open connection should never involve IO or do anything
that can fail so we removed this error to improve usability.
#### Libp2p Constructor
If you're not already doing so, you should be using the `libp2p.New` constructor
to make your libp2p nodes. This release brings quite a few new options to the
libp2p constructor so if it hasn't been flexible enough for you in the past, I
recommend that you try again. A simple example can be found in the
[echo][example:echo] example.
Given this work and in an attempt to consolidate all of our configuration logic
in one place, we've removed all default transports from go-libp2p-swarm.
TL;DR: Please use the libp2p constructor.
#### Zombie Streams
From this release on, when you're done with a stream, you must either call
`Reset` on it (in case of an error) or close it and read an EOF (or some other
error). Otherwise, libp2p can't determine if the stream is *actually* closed and
will hang onto it indefinitely.
To make properly closing streams a bit easier, we've added two methods to
[go-libp2p-net][]: `AwaitEOF` and `FullClose`.
* `AwaitEOF(stream)` tries to read a single byte from the stream. If `Read`
returns an EOF, `AwaitEOF` returns success. Otherwise, if `Read` either reads
some data or returns some other error, `AwaitEOF` resets the stream and returns
an error. To avoid waiting indefinitely, `AwaitEOF` resets the stream
unconditionally after 1 minute.
* `FullClose(stream)` is a convenience function that closes the stream and then
calls `AwaitEOF` on it.
Like with libp2p notifications, this issue has a bit of history...
In the beginning, libp2p assumed that calling `Close` on a stream would close
the stream for both reading and writing. Unfortunately, *none* of our stream
multiplexers actually behaved this way. In practice, `Close` always closed the
stream for writing.
After realizing this, we made two changes:
1. We accepted the fact that `Close` only closed the stream for writing.
2. We added a `Reset` method for killing the stream (closing it in both
directions, throwing away any buffered data).
However, we ran into a bit of a snag because we try to track open streams and
need some way to tell when a stream has been closed. In the past this was easy:
when the user calls `Close` on the stream, stop tracking it. However, now that
`Close` only closes the stream for writing, we still *technically* needed to
track it until the *other* end closed the stream as well. Unfortunately, without
actually reading from the stream, we have no way of knowing about this.
Therefore, if the user calls `Close` on a stream and then walks away, we'd have
to hang onto the stream indefinitely.
Our solution was to simply stop tracking streams once they were closed for
writing. This wasn't the *correct* behavior but it avoided leaking memory in the
common case:
1. The user calls `Close` and drops all references to the stream.
2. The other end calls `Close` without writing any additional data.
3. The stream multiplexer observes both closes and drops *its* reference to the stream.
4. The garbage collector garbage collects the stream.
However, this meant that:
1. The list of "open" streams was technically incomplete.
2. If the other side either failed to call `Close` or tried to send data before
closing, the stream would remain "open" (until the connection was closed).
In this release, we've changed this behavior. Now, when you `Close` a stream for
writing, libp2p *continues* to track it. We only stop tracking it when either:
1. You call `Reset` (throwing away the stream).
2. You finish reading any data off of it and observe either an EOF or an error.
This way, we never "forget" about open streams or leave them in a half-forgotten
state.
In the future, I'd like to add a `CloseAndForget` method to streams that:
1. Closes the stream (sends an EOF).
2. Tells the swarm to stop tracking the stream.
3. Tells the stream muxer to stop tracking the stream and throw away any data
the other side may send (possibly resetting the stream on unexpected data).
However:
1. This would likely require modifying our stream muxers which may not be
feasible.
2. Explicitly waiting for an EOF is still the correct thing to do unless you
really don't care if the operation succeeded.
### For Transport Maintainers
For transport maintainers, quite a bit has changed. Before this change,
transports created simple, unencrypted, stream connections and it was the job of
the libp2p Network (go-libp2p-swarm) to negotiate security, multiplexing, etc.
However, when attempting to add support for the QUIC protocol, we realized that
this was going to be a problem: QUIC already handles authentication and
encryption (using TLS1.3) and multiplexing. After much debate, we inverted our
current architecture and made transports responsible for encrypting/multiplexing
their connections (before returning them).
To make this palatable, we've also introduced a new ["upgrader"
library][go-libp2p-transport-upgrader] for upgrading go-multiaddr-net
connections/listeners to full libp2p transport connections/listeners. Transports
that don't support encryption/multiplexing out of the box can expect to have an
upgrader passed into the constructor.
To get a feel for how this new transport system works, take a look at the TCP
and WebSocket transports and the transport interface documentation:
* [TCP Transport][go-tcp-transport]
* [WebSocket Transport][go-ws-transport]
* [Transport Interface][doc:go-libp2p-transport]
#### Deprecated Packages
This release sees the deprecation of a few packages:
* [go-peerstream][] has been deprecated and all functionality has been merged
into [go-libp2p-swarm][]. [go-peerstream][] was written as a general-purpose
(not libp2p specific) listener, connection, and stream manager. However, this
package caused more problems than it solved and was incompatible with the new
transport interface.
* [go-libp2p-interface-conn][] has been deprecated. These interfaces to bridge
the gap between transport-level connections and [go-libp2p-net][] connections
however, now that transport connections are fully multiplexed/encrypted, this
is no longer needed.
* [go-libp2p-conn][] has also been deprecated and most of the functionality has
been moved to [go-libp2p-transport-upgrader][]. This package used to provide
connection "upgrade" logic for upgrading transport-level connections to
[go-libp2p-interface-conn][] connections however, transport-level connections
now provide the required functionality out of the box.
#### Testing
We've moved `GenSwarmNetwork` in [go-libp2p-netutil][] to `GenSwarm` in
[go-libp2p-swarm/testing][] because:
1. The swarm duplicated this exact function for its own tests.
2. The swarm couldn't depend on [go-libp2p-netutil][] because
[go-libp2p-netutil][] depends on [go-libp2p-swarm][].
We've also added a new transport test suit
[go-libp2p-transport/test][]. If you implement a new transport, please consider
testing against these suite. If you find a bug in an existing transport, please
consider adding a test to this suite.
#### go-addr-util
In go-addr-util, we've removed the `SupportedTransportStrings` and
`SupportedTransportProtocols` transport registries and the associated
`AddTransport` function. These registries were updated by `init` functions in
packages providing transports and were used to keep track of known transports.
However, *importing* a transport doesn't mean any libp2p nodes have been
configured to actually *use* that transport. Therefore, in the new go-libp2p,
it's go-libp2p-swarm's job to keep track of which transports are supported
(i.e., which transports have been registered with the swarm).
We've also removed the associated `AddrUsable`, `FilterUsableAddrs`, and
`AddrUsableFunc` functions.
#### Pluggable Security Transports
This release brings a new pluggable security transport framework. Implementing a
new security framework is now as simple as:
1. Implement the interfaces defined in [go-conn-security][].
2. Pass it into the libp2p constructor using the `Security` option.
[go-conn-security]: https://github.com/libp2p/go-conn-security
[go-libp2p-conn]: https://github.com/libp2p/go-libp2p-conn
[go-libp2p-interface-conn]: https://github.com/libp2p/go-libp2p-interface-conn
[go-libp2p-net]: https://github.com/libp2p/go-libp2p-net
[go-libp2p-netutil]: https://github.com/libp2p/go-libp2p/go-libp2p-netutil
[go-libp2p-swarm]: https://github.com/libp2p/go-libp2p/go-libp2p-swarm
[go-libp2p-swarm/testing]: https://github.com/libp2p/go-libp2p/go-libp2p-swarm/testing
[go-libp2p-transport]: https://github.com/libp2p/go-libp2p-transport
[go-libp2p-transport/test]: https://github.com/libp2p/go-libp2p-transport/test
[go-libp2p-transport-upgrader]: https://github.com/libp2p/go-libp2p-transport-upgrader
[go-peerstream]: https://github.com/libp2p/go-peerstream
[go-tcp-transport]: https://github.com/libp2p/go-tcp-transport
[go-ws-transport]: https://github.com/libp2p/go-ws-transport
[example:echo]: https://github.com/libp2p/go-libp2p/tree/master/examples/echo
[doc:go-libp2p-transport]: https://godoc.org/github.com/libp2p/go-libp2p-transport
<h1 align="center">
<a href="libp2p.io"><img width="250" src="https://github.com/libp2p/libp2p/blob/master/logo/alternates/libp2p-logo-alt-2.png?raw=true" alt="libp2p hex logo" /></a>
<a href="libp2p.io"><img width="250" src="https://github.com/libp2p/libp2p/blob/master/logo/black-bg-2.png?raw=true" alt="libp2p hex logo" /></a>
</h1>
<h3 align="center">The Go implementation of the libp2p Networking Stack.</h3>
......
package config
import (
"context"
"fmt"
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
logging "github.com/ipfs/go-log"
circuit "github.com/libp2p/go-libp2p-circuit"
crypto "github.com/libp2p/go-libp2p-crypto"
host "github.com/libp2p/go-libp2p-host"
ifconnmgr "github.com/libp2p/go-libp2p-interface-connmgr"
pnet "github.com/libp2p/go-libp2p-interface-pnet"
metrics "github.com/libp2p/go-libp2p-metrics"
inet "github.com/libp2p/go-libp2p-net"
peer "github.com/libp2p/go-libp2p-peer"
pstore "github.com/libp2p/go-libp2p-peerstore"
swarm "github.com/libp2p/go-libp2p-swarm"
tptu "github.com/libp2p/go-libp2p-transport-upgrader"
filter "github.com/libp2p/go-maddr-filter"
ma "github.com/multiformats/go-multiaddr"
)
var log = logging.Logger("p2p-config")
// AddrsFactory is a function that takes a set of multiaddrs we're listening on and
// returns the set of multiaddrs we should advertise to the network.
type AddrsFactory = bhost.AddrsFactory
// NATManagerC is a NATManager constructor.
type NATManagerC func(inet.Network) bhost.NATManager
// Config describes a set of settings for a libp2p node
//
// This is *not* a stable interface. Use the options defined in the root
// package.
type Config struct {
PeerKey crypto.PrivKey
Transports []TptC
Muxers []MsMuxC
SecurityTransports []MsSecC
Insecure bool
Protector pnet.Protector
Relay bool
RelayOpts []circuit.RelayOpt
ListenAddrs []ma.Multiaddr
AddrsFactory bhost.AddrsFactory
Filters *filter.Filters
ConnManager ifconnmgr.ConnManager
NATManager NATManagerC
Peerstore pstore.Peerstore
Reporter metrics.Reporter
}
// NewNode constructs a new libp2p Host from the Config.
//
// This function consumes the config. Do not reuse it (really!).
func (cfg *Config) NewNode(ctx context.Context) (host.Host, error) {
// Check this early. Prevents us from even *starting* without verifying this.
if pnet.ForcePrivateNetwork && cfg.Protector == nil {
log.Error("tried to create a libp2p node with no Private" +
" Network Protector but usage of Private Networks" +
" is forced by the enviroment")
// Note: This is *also* checked the upgrader itself so it'll be
// enforced even *if* you don't use the libp2p constructor.
return nil, pnet.ErrNotInPrivateNetwork
}
if cfg.PeerKey == nil {
return nil, fmt.Errorf("no peer key specified")
}
// Obtain Peer ID from public key
pid, err := peer.IDFromPublicKey(cfg.PeerKey.GetPublic())
if err != nil {
return nil, err
}
if cfg.Peerstore == nil {
return nil, fmt.Errorf("no peerstore specified")
}
if !cfg.Insecure {
cfg.Peerstore.AddPrivKey(pid, cfg.PeerKey)
cfg.Peerstore.AddPubKey(pid, cfg.PeerKey.GetPublic())
}
// TODO: Make the swarm implementation configurable.
swrm := swarm.NewSwarm(ctx, pid, cfg.Peerstore, cfg.Reporter)
if cfg.Filters != nil {
swrm.Filters = cfg.Filters
}
// TODO: make host implementation configurable.
h, err := bhost.NewHost(ctx, swrm, &bhost.HostOpts{
ConnManager: cfg.ConnManager,
AddrsFactory: cfg.AddrsFactory,
NATManager: cfg.NATManager,
})
if err != nil {
swrm.Close()
return nil, err
}
upgrader := new(tptu.Upgrader)
upgrader.Protector = cfg.Protector
upgrader.Filters = swrm.Filters
if cfg.Insecure {
upgrader.Secure = makeInsecureTransport(pid)
} else {
upgrader.Secure, err = makeSecurityTransport(h, cfg.SecurityTransports)
if err != nil {
h.Close()
return nil, err
}
}
upgrader.Muxer, err = makeMuxer(h, cfg.Muxers)
if err != nil {
h.Close()
return nil, err
}
tpts, err := makeTransports(h, upgrader, cfg.Transports)
if err != nil {
h.Close()
return nil, err
}
for _, t := range tpts {
err = swrm.AddTransport(t)
if err != nil {
h.Close()
return nil, err
}
}
if cfg.Relay {
err := circuit.AddRelayTransport(swrm.Context(), h, upgrader, cfg.RelayOpts...)
if err != nil {
h.Close()
return nil, err
}
}
// TODO: This method succeeds if listening on one address succeeds. We
// should probably fail if listening on *any* addr fails.
if err := h.Network().Listen(cfg.ListenAddrs...); err != nil {
h.Close()
return nil, err
}
// TODO: Configure routing (it's a pain to setup).
// TODO: Bootstrapping.
return h, nil
}
// Option is a libp2p config option that can be given to the libp2p constructor
// (`libp2p.New`).
type Option func(cfg *Config) error
// Apply applies the given options to the config, returning the first error
// encountered (if any).
func (cfg *Config) Apply(opts ...Option) error {
for _, opt := range opts {
if err := opt(cfg); err != nil {
return err
}
}
return nil
}
package config
import (
"fmt"
"reflect"
security "github.com/libp2p/go-conn-security"
crypto "github.com/libp2p/go-libp2p-crypto"
host "github.com/libp2p/go-libp2p-host"
pnet "github.com/libp2p/go-libp2p-interface-pnet"
inet "github.com/libp2p/go-libp2p-net"
peer "github.com/libp2p/go-libp2p-peer"
pstore "github.com/libp2p/go-libp2p-peerstore"
transport "github.com/libp2p/go-libp2p-transport"
tptu "github.com/libp2p/go-libp2p-transport-upgrader"
filter "github.com/libp2p/go-maddr-filter"
mux "github.com/libp2p/go-stream-muxer"
)
var (
// interfaces
hostType = reflect.TypeOf((*host.Host)(nil)).Elem()
networkType = reflect.TypeOf((*inet.Network)(nil)).Elem()
transportType = reflect.TypeOf((*transport.Transport)(nil)).Elem()
muxType = reflect.TypeOf((*mux.Transport)(nil)).Elem()
securityType = reflect.TypeOf((*security.Transport)(nil)).Elem()
protectorType = reflect.TypeOf((*pnet.Protector)(nil)).Elem()
privKeyType = reflect.TypeOf((*crypto.PrivKey)(nil)).Elem()
pubKeyType = reflect.TypeOf((*crypto.PubKey)(nil)).Elem()
pstoreType = reflect.TypeOf((*pstore.Peerstore)(nil)).Elem()
// concrete types
peerIDType = reflect.TypeOf((peer.ID)(""))
filtersType = reflect.TypeOf((*filter.Filters)(nil))
upgraderType = reflect.TypeOf((*tptu.Upgrader)(nil))
)
var argTypes = map[reflect.Type]constructor{
upgraderType: func(h host.Host, u *tptu.Upgrader) interface{} { return u },
hostType: func(h host.Host, u *tptu.Upgrader) interface{} { return h },
networkType: func(h host.Host, u *tptu.Upgrader) interface{} { return h.Network() },
muxType: func(h host.Host, u *tptu.Upgrader) interface{} { return u.Muxer },
securityType: func(h host.Host, u *tptu.Upgrader) interface{} { return u.Secure },
protectorType: func(h host.Host, u *tptu.Upgrader) interface{} { return u.Protector },
filtersType: func(h host.Host, u *tptu.Upgrader) interface{} { return u.Filters },
peerIDType: func(h host.Host, u *tptu.Upgrader) interface{} { return h.ID() },
privKeyType: func(h host.Host, u *tptu.Upgrader) interface{} { return h.Peerstore().PrivKey(h.ID()) },
pubKeyType: func(h host.Host, u *tptu.Upgrader) interface{} { return h.Peerstore().PubKey(h.ID()) },
pstoreType: func(h host.Host, u *tptu.Upgrader) interface{} { return h.Peerstore() },
}
func newArgTypeSet(types ...reflect.Type) map[reflect.Type]constructor {
result := make(map[reflect.Type]constructor, len(types))
for _, ty := range types {
c, ok := argTypes[ty]
if !ok {
panic(fmt.Sprintf("missing constructor for type %s", ty))
}
result[ty] = c
}
return result
}
package config
import (
"fmt"
host "github.com/libp2p/go-libp2p-host"
mux "github.com/libp2p/go-stream-muxer"
msmux "github.com/whyrusleeping/go-smux-multistream"
)
// MuxC is a stream multiplex transport constructor
type MuxC func(h host.Host) (mux.Transport, error)
// MsMuxC is a tuple containing a multiplex transport constructor and a protocol
// ID.
type MsMuxC struct {
MuxC
ID string
}
var muxArgTypes = newArgTypeSet(hostType, networkType, peerIDType, pstoreType)
// MuxerConstructor creates a multiplex constructor from the passed parameter
// using reflection.
func MuxerConstructor(m interface{}) (MuxC, error) {
// Already constructed?
if t, ok := m.(mux.Transport); ok {
return func(_ host.Host) (mux.Transport, error) {
return t, nil
}, nil
}
ctor, err := makeConstructor(m, muxType, muxArgTypes)
if err != nil {
return nil, err
}
return func(h host.Host) (mux.Transport, error) {
t, err := ctor(h, nil)
if err != nil {
return nil, err
}
return t.(mux.Transport), nil
}, nil
}
func makeMuxer(h host.Host, tpts []MsMuxC) (mux.Transport, error) {
muxMuxer := msmux.NewBlankTransport()
transportSet := make(map[string]struct{}, len(tpts))
for _, tptC := range tpts {
if _, ok := transportSet[tptC.ID]; ok {
return nil, fmt.Errorf("duplicate muxer transport: %s", tptC.ID)
}
}
for _, tptC := range tpts {
tpt, err := tptC.MuxC(h)
if err != nil {
return nil, err
}
muxMuxer.AddTransport(tptC.ID, tpt)
}
return muxMuxer, nil
}
package config
import (
"testing"
peer "github.com/libp2p/go-libp2p-peer"
mux "github.com/libp2p/go-stream-muxer"
yamux "github.com/whyrusleeping/go-smux-yamux"
)
func TestMuxerSimple(t *testing.T) {
// single
_, err := MuxerConstructor(func(_ peer.ID) mux.Transport { return nil })
if err != nil {
t.Fatal(err)
}
}
func TestMuxerByValue(t *testing.T) {
_, err := MuxerConstructor(yamux.DefaultTransport)
if err != nil {
t.Fatal(err)
}
}
func TestMuxerDuplicate(t *testing.T) {
_, err := MuxerConstructor(func(_ peer.ID, _ peer.ID) mux.Transport { return nil })
if err != nil {
t.Fatal(err)
}
}
func TestMuxerError(t *testing.T) {
_, err := MuxerConstructor(func() (mux.Transport, error) { return nil, nil })
if err != nil {
t.Fatal(err)
}
}
func TestMuxerBadTypes(t *testing.T) {
for i, f := range []interface{}{
func() error { return nil },
func() string { return "" },
func() {},
func(string) mux.Transport { return nil },
func(string) (mux.Transport, error) { return nil, nil },
nil,
"testing",
} {
if _, err := MuxerConstructor(f); err == nil {
t.Fatalf("constructor %d with type %T should have failed", i, f)
}
}
}
package config
import (
"fmt"
"reflect"
"runtime"
host "github.com/libp2p/go-libp2p-host"
tptu "github.com/libp2p/go-libp2p-transport-upgrader"
)
var errorType = reflect.TypeOf((*error)(nil)).Elem()
// checks if a function returns either the specified type or the specified type
// and an error.
func checkReturnType(fnType, tptType reflect.Type) error {
switch fnType.NumOut() {
case 2:
if fnType.Out(1) != errorType {
return fmt.Errorf("expected (optional) second return value from transport constructor to be an error")
}
fallthrough
case 1:
if !fnType.Out(0).Implements(tptType) {
return fmt.Errorf("transport constructor returns %s which doesn't implement %s", fnType.Out(0), tptType)
}
default:
return fmt.Errorf("expected transport constructor to return a transport and, optionally, an error")
}
return nil
}
// Handles return values with optional errors. That is, return values of the
// form `(something, error)` or just `something`.
//
// Panics if the return value isn't of the correct form.
func handleReturnValue(out []reflect.Value) (interface{}, error) {
switch len(out) {
case 2:
err := out[1]
if err != (reflect.Value{}) && !err.IsNil() {
return nil, err.Interface().(error)
}
fallthrough
case 1:
tpt := out[0]
// Check for nil value and nil error.
if tpt == (reflect.Value{}) {
return nil, fmt.Errorf("unspecified error")
}
switch tpt.Kind() {
case reflect.Ptr, reflect.Interface, reflect.Func:
if tpt.IsNil() {
return nil, fmt.Errorf("unspecified error")
}
}
return tpt.Interface(), nil
default:
panic("expected 1 or 2 return values from transport constructor")
}
}
// calls the transport constructor and annotates the error with the name of the constructor.
func callConstructor(c reflect.Value, args []reflect.Value) (interface{}, error) {
val, err := handleReturnValue(c.Call(args))
if err != nil {
name := runtime.FuncForPC(c.Pointer()).Name()
if name != "" {
// makes debugging easier
return nil, fmt.Errorf("transport constructor %s failed: %s", name, err)
}
}
return val, err
}
type constructor func(h host.Host, u *tptu.Upgrader) interface{}
func makeArgumentConstructors(fnType reflect.Type, argTypes map[reflect.Type]constructor) ([]constructor, error) {
out := make([]constructor, fnType.NumIn())
for i := range out {
argType := fnType.In(i)
c, ok := argTypes[argType]
if !ok {
return nil, fmt.Errorf("argument %d has an unexpected type %s", i, argType.Name())
}
out[i] = c
}
return out, nil
}
// makes a transport constructor.
func makeConstructor(
tpt interface{},
tptType reflect.Type,
argTypes map[reflect.Type]constructor,
) (func(host.Host, *tptu.Upgrader) (interface{}, error), error) {
v := reflect.ValueOf(tpt)
// avoid panicing on nil/zero value.
if v == (reflect.Value{}) {
return nil, fmt.Errorf("expected a transport or transport constructor, got a %T", tpt)
}
t := v.Type()
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("expected a transport or transport constructor, got a %T", tpt)
}
if err := checkReturnType(t, tptType); err != nil {
return nil, err
}
argConstructors, err := makeArgumentConstructors(t, argTypes)
if err != nil {
return nil, err
}
return func(h host.Host, u *tptu.Upgrader) (interface{}, error) {
arguments := make([]reflect.Value, len(argConstructors))
for i, makeArg := range argConstructors {
arguments[i] = reflect.ValueOf(makeArg(h, u))
}
return callConstructor(v, arguments)
}, nil
}
package config
import (
"errors"
"reflect"
"strings"
"testing"
)
func TestHandleReturnValue(t *testing.T) {
// one value
v, err := handleReturnValue([]reflect.Value{reflect.ValueOf(1)})
if v.(int) != 1 {
t.Fatal("expected value")
}
if err != nil {
t.Fatal(err)
}
// Nil value
v, err = handleReturnValue([]reflect.Value{reflect.ValueOf(nil)})
if v != nil {
t.Fatal("expected no value")
}
if err == nil {
t.Fatal("expected an error")
}
// Nil value, nil err
v, err = handleReturnValue([]reflect.Value{reflect.ValueOf(nil), reflect.ValueOf(nil)})
if v != nil {
t.Fatal("expected no value")
}
if err == nil {
t.Fatal("expected an error")
}
// two values
v, err = handleReturnValue([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(nil)})
if v, ok := v.(int); !ok || v != 1 {
t.Fatalf("expected value of 1, got %v", v)
}
if err != nil {
t.Fatal("expected no error")
}
// an error
myError := errors.New("my error")
_, err = handleReturnValue([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(myError)})
if err != myError {
t.Fatal(err)
}
for _, vals := range [][]reflect.Value{
{reflect.ValueOf(1), reflect.ValueOf("not an error")},
{},
{reflect.ValueOf(1), reflect.ValueOf(myError), reflect.ValueOf(myError)},
} {
func() {
defer func() { recover() }()
handleReturnValue(vals)
t.Fatal("expected a panic")
}()
}
}
type foo interface {
foo() foo
}
var fooType = reflect.TypeOf((*foo)(nil)).Elem()
func TestCheckReturnType(t *testing.T) {
for i, fn := range []interface{}{
func() { panic("") },
func() error { panic("") },
func() (error, error) { panic("") },
func() (foo, error, error) { panic("") },
func() (foo, foo) { panic("") },
} {
if checkReturnType(reflect.TypeOf(fn), fooType) == nil {
t.Errorf("expected falure for case %d (type %T)", i, fn)
}
}
for i, fn := range []interface{}{
func() foo { panic("") },
func() (foo, error) { panic("") },
} {
if err := checkReturnType(reflect.TypeOf(fn), fooType); err != nil {
t.Errorf("expected success for case %d (type %T), got: %s", i, fn, err)
}
}
}
func constructFoo() foo {
return nil
}
type fooImpl struct{}
func (f *fooImpl) foo() foo { return nil }
func TestCallConstructor(t *testing.T) {
_, err := callConstructor(reflect.ValueOf(constructFoo), nil)
if err == nil {
t.Fatal("expected constructor to fail")
}
if !strings.Contains(err.Error(), "constructFoo") {
t.Errorf("expected error to contain the constructor name: %s", err)
}
v, err := callConstructor(reflect.ValueOf(func() foo { return &fooImpl{} }), nil)
if err != nil {
t.Fatal(err)
}
if _, ok := v.(*fooImpl); !ok {
t.Fatal("expected a fooImpl")
}
v, err = callConstructor(reflect.ValueOf(func() *fooImpl { return new(fooImpl) }), nil)
if err != nil {
t.Fatal(err)
}
if _, ok := v.(*fooImpl); !ok {
t.Fatal("expected a fooImpl")
}
_, err = callConstructor(reflect.ValueOf(func() (*fooImpl, error) { return nil, nil }), nil)
if err == nil {
t.Fatal("expected error")
}
v, err = callConstructor(reflect.ValueOf(func() (*fooImpl, error) { return new(fooImpl), nil }), nil)
if err != nil {
t.Fatal(err)
}
if _, ok := v.(*fooImpl); !ok {
t.Fatal("expected a fooImpl")
}
}
package config
import (
"fmt"
security "github.com/libp2p/go-conn-security"
csms "github.com/libp2p/go-conn-security-multistream"
insecure "github.com/libp2p/go-conn-security/insecure"
host "github.com/libp2p/go-libp2p-host"
peer "github.com/libp2p/go-libp2p-peer"
)
// SecC is a security transport constructor
type SecC func(h host.Host) (security.Transport, error)
// MsSecC is a tuple containing a security transport constructor and a protocol
// ID.
type MsSecC struct {
SecC
ID string
}
var securityArgTypes = newArgTypeSet(
hostType, networkType, peerIDType,
privKeyType, pubKeyType, pstoreType,
)
// SecurityConstructor creates a security constructor from the passed parameter
// using reflection.
func SecurityConstructor(sec interface{}) (SecC, error) {
// Already constructed?
if t, ok := sec.(security.Transport); ok {
return func(_ host.Host) (security.Transport, error) {
return t, nil
}, nil
}
ctor, err := makeConstructor(sec, securityType, securityArgTypes)
if err != nil {
return nil, err
}
return func(h host.Host) (security.Transport, error) {
t, err := ctor(h, nil)
if err != nil {
return nil, err
}
return t.(security.Transport), nil
}, nil
}
func makeInsecureTransport(id peer.ID) security.Transport {
secMuxer := new(csms.SSMuxer)
secMuxer.AddTransport(insecure.ID, insecure.New(id))
return secMuxer
}
func makeSecurityTransport(h host.Host, tpts []MsSecC) (security.Transport, error) {
secMuxer := new(csms.SSMuxer)
transportSet := make(map[string]struct{}, len(tpts))
for _, tptC := range tpts {
if _, ok := transportSet[tptC.ID]; ok {
return nil, fmt.Errorf("duplicate security transport: %s", tptC.ID)
}
}
for _, tptC := range tpts {
tpt, err := tptC.SecC(h)
if err != nil {
return nil, err
}
if _, ok := tpt.(*insecure.Transport); ok {
return nil, fmt.Errorf("cannot construct libp2p with an insecure transport, set the Insecure config option instead")
}
secMuxer.AddTransport(tptC.ID, tpt)
}
return secMuxer, nil
}
package config
import (
host "github.com/libp2p/go-libp2p-host"
transport "github.com/libp2p/go-libp2p-transport"
tptu "github.com/libp2p/go-libp2p-transport-upgrader"
)
// TptC is the type for libp2p transport constructors. You probably won't ever
// implement this function interface directly. Instead, pass your transport
// constructor to TransportConstructor.
type TptC func(h host.Host, u *tptu.Upgrader) (transport.Transport, error)
var transportArgTypes = argTypes
// TransportConstructor uses reflection to turn a function that constructs a
// transport into a TptC.
//
// You can pass either a constructed transport (something that implements
// `transport.Transport`) or a function that takes any of:
//
// * The local peer ID.
// * A transport connection upgrader.
// * A private key.
// * A public key.
// * A Host.
// * A Network.
// * A Peerstore.
// * An address filter.
// * A security transport.
// * A stream multiplexer transport.
// * A private network protector.
//
// And returns a type implementing transport.Transport and, optionally, an error
// (as the second argument).
func TransportConstructor(tpt interface{}) (TptC, error) {
// Already constructed?
if t, ok := tpt.(transport.Transport); ok {
return func(_ host.Host, _ *tptu.Upgrader) (transport.Transport, error) {
return t, nil
}, nil
}
ctor, err := makeConstructor(tpt, transportType, transportArgTypes)
if err != nil {
return nil, err
}
return func(h host.Host, u *tptu.Upgrader) (transport.Transport, error) {
t, err := ctor(h, u)
if err != nil {
return nil, err
}
return t.(transport.Transport), nil
}, nil
}
func makeTransports(h host.Host, u *tptu.Upgrader, tpts []TptC) ([]transport.Transport, error) {
transports := make([]transport.Transport, len(tpts))
for i, tC := range tpts {
t, err := tC(h, u)
if err != nil {
return nil, err
}
transports[i] = t
}
return transports, nil
}
package libp2p
// This file contains all the default configuration options.
import (
"crypto/rand"
crypto "github.com/libp2p/go-libp2p-crypto"
pstore "github.com/libp2p/go-libp2p-peerstore"
secio "github.com/libp2p/go-libp2p-secio"
tcp "github.com/libp2p/go-tcp-transport"
ws "github.com/libp2p/go-ws-transport"
mplex "github.com/whyrusleeping/go-smux-multiplex"
yamux "github.com/whyrusleeping/go-smux-yamux"
)
// DefaultSecurity is the default security option.
//
// Useful when you want to extend, but not replace, the supported transport
// security protocols.
var DefaultSecurity = Security(secio.ID, secio.New)
// DefaultMuxer configures libp2p to use the stream connection multiplexers.
//
// Use this option when you want to *extend* the set of multiplexers used by
// libp2p instead of replacing them.
var DefaultMuxers = ChainOptions(
Muxer("/yamux/1.0.0", yamux.DefaultTransport),
Muxer("/mplex/6.3.0", mplex.DefaultTransport),
)
// DefaultTransports are the default libp2p transports.
//
// Use this option when you want to *extend* the set of multiplexers used by
// libp2p instead of replacing them.
var DefaultTransports = ChainOptions(
Transport(tcp.NewTCPTransport),
Transport(ws.New),
)
// DefaultPeerstore configures libp2p to use the default peerstore.
var DefaultPeerstore Option = func(cfg *Config) error {
return cfg.Apply(Peerstore(pstore.NewPeerstore()))
}
// RandomIdentity generates a random identity (default behaviour)
var RandomIdentity = func(cfg *Config) error {
priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, rand.Reader)
if err != nil {
return err
}
return cfg.Apply(Identity(priv))
}
// Complete list of default options and when to fallback on them.
//
// Please *DON'T* specify default options any other way. Putting this all here
// makes tracking defaults *much* easier.
var defaults = []struct {
fallback func(cfg *Config) bool
opt Option
}{
{
fallback: func(cfg *Config) bool { return cfg.Transports == nil },
opt: DefaultTransports,
},
{
fallback: func(cfg *Config) bool { return cfg.Muxers == nil },
opt: DefaultMuxers,
},
{
fallback: func(cfg *Config) bool { return !cfg.Insecure && cfg.SecurityTransports == nil },
opt: DefaultSecurity,
},
{
fallback: func(cfg *Config) bool { return cfg.PeerKey == nil },
opt: RandomIdentity,
},
{
fallback: func(cfg *Config) bool { return cfg.Peerstore == nil },
opt: DefaultPeerstore,
},
}
// Defaults configures libp2p to use the default options. Can be combined with
// other options to *extend* the default options.
var Defaults Option = func(cfg *Config) error {
for _, def := range defaults {
if err := cfg.Apply(def.opt); err != nil {
return err
}
}
return nil
}
// FallbackDefaults applies default options to the libp2p node if and only if no
// other relevent options have been applied. will be appended to the options
// passed into New.
var FallbackDefaults Option = func(cfg *Config) error {
for _, def := range defaults {
if !def.fallback(cfg) {
continue
}
if err := cfg.Apply(def.opt); err != nil {
return err
}
}
return nil
}
package libp2p
import (
"fmt"
"runtime"
)
func traceError(err error, skip int) error {
if err == nil {
return nil
}
_, file, line, ok := runtime.Caller(skip + 1)
if !ok {
return err
}
return fmt.Errorf("%s:%d: %s", file, line, err)
}
......@@ -39,13 +39,13 @@ import (
mrand "math/rand"
"os"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-crypto"
"github.com/libp2p/go-libp2p-host"
"github.com/libp2p/go-libp2p-net"
"github.com/libp2p/go-libp2p-peer"
"github.com/libp2p/go-libp2p-peerstore"
"github.com/libp2p/go-libp2p-swarm"
"github.com/libp2p/go-libp2p/p2p/host/basic"
"github.com/multiformats/go-multiaddr"
)
......@@ -155,37 +155,27 @@ func main() {
}
// Creates a new RSA key pair for this host
prvKey, pubKey, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
if err != nil {
panic(err)
}
// Getting host ID from public key.
// host ID is the hash of public key
nodeID, _ := peer.IDFromPublicKey(pubKey)
// 0.0.0.0 will listen on any interface device
sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *sourcePort))
// Adding self to the peerstore.
ps := peerstore.NewPeerstore()
ps.AddPrivKey(nodeID, prvKey)
ps.AddPubKey(nodeID, pubKey)
// Creating a new Swarm network.
network, err := swarm.NewNetwork(context.Background(), []multiaddr.Multiaddr{sourceMultiAddr}, nodeID, ps, nil)
// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
host, err := libp2p.New(
context.Background(),
libp2p.ListenAddrs(sourceMultiAddr),
libp2p.Identity(prvKey),
)
if err != nil {
panic(err)
}
// NewHost constructs a new *BasicHost and activates it by attaching its
// stream and connection handlers to the given inet.Network (network).
// Other options like NATManager can also be added here.
// See docs: https://godoc.org/github.com/libp2p/go-libp2p/p2p/host/basic#HostOpts
host := basichost.New(network)
if *dest == "" {
// Set a function as stream handler.
// This function is called when a peer initiate a connection and starts a stream with this peer.
......
......@@ -49,7 +49,7 @@ func makeBasicHost(listenPort int, secio bool, randseed int64) (host.Host, error
}
if !secio {
opts = append(opts, libp2p.NoEncryption())
opts = append(opts, libp2p.NoSecurity)
}
basicHost, err := libp2p.New(context.Background(), opts...)
......
......@@ -12,16 +12,14 @@ import (
// We need to import libp2p's libraries that we use in this project.
// In order to work, these libraries need to be rewritten by gx-go.
crypto "github.com/libp2p/go-libp2p-crypto"
host "github.com/libp2p/go-libp2p-host"
inet "github.com/libp2p/go-libp2p-net"
peer "github.com/libp2p/go-libp2p-peer"
ps "github.com/libp2p/go-libp2p-peerstore"
swarm "github.com/libp2p/go-libp2p-swarm"
ma "github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr-net"
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
libp2p "github.com/libp2p/go-libp2p"
)
// Protocol defines the libp2p protocol that we will use for the libp2p proxy
......@@ -33,27 +31,11 @@ const Protocol = "/proxy-example/0.0.1"
// makeRandomHost creates a libp2p host with a randomly generated identity.
// This step is described in depth in other tutorials.
func makeRandomHost(port int) host.Host {
priv, pub, err := crypto.GenerateKeyPair(crypto.RSA, 2048)
host, err := libp2p.New(context.Background(), libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", port)))
if err != nil {
log.Fatalln(err)
}
pid, err := peer.IDFromPublicKey(pub)
if err != nil {
log.Fatalln(err)
}
listen, err := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", port))
if err != nil {
log.Fatalln(err)
}
ps := ps.NewPeerstore()
ps.AddPrivKey(pid, priv)
ps.AddPubKey(pid, pub)
n, err := swarm.NewNetwork(context.Background(),
[]ma.Multiaddr{listen}, pid, ps, nil)
if err != nil {
log.Fatalln(err)
}
return bhost.New(n)
return host
}
// ProxyService provides HTTP proxying on top of libp2p by launching an
......
......@@ -4,73 +4,56 @@ For most applications, the host is the basic building block you'll need to get s
The host is an abstraction that manages services on top of a swarm. It provides a clean interface to connect to a service on a given remote peer.
First, you'll need an ID, and a place to store that ID. To generate an ID, you can do the following:
If you want to create a host with a default configuration, you can do the following:
```go
import (
"context"
"crypto/rand"
"fmt"
libp2p "github.com/libp2p/go-libp2p"
crypto "github.com/libp2p/go-libp2p-crypto"
peer "github.com/libp2p/go-libp2p-peer"
pstore "github.com/libp2p/go-libp2p-peerstore"
)
// Generate an identity keypair using go's cryptographic randomness source
priv, pub, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
panic(err)
}
// A peers ID is the hash of its public key
pid, err := peer.IDFromPublicKey(pub)
// The context governs the lifetime of the libp2p node
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// To construct a simple host with all the default settings, just use `New`
h, err := libp2p.New(ctx)
if err != nil {
panic(err)
}
// We've created the identity, now we need to store it.
// A peerstore holds information about peers, including your own
ps := pstore.NewPeerstore()
ps.AddPrivKey(pid, priv)
ps.AddPubKey(pid, pub)
fmt.Printf("Hello World, my hosts ID is %s\n", h.ID())
```
Next, you'll need at least one address that you want to listen on. You can go from a string to a multiaddr like this:
If you want more control over the configuration, you can specify some options to the constructor. For a full list of all the configuration supported by the constructor see: [options.go](https://github.com/libp2p/go-libp2p/blob/master/options.go)
```go
import ma "github.com/multiformats/go-multiaddr"
...
In this snippet we generate our own ID and specified on which address we want to listen:
maddr, err := ma.NewMultiaddr("/ip4/0.0.0.0/tcp/9000")
```go
// Set your own keypair
priv, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
panic(err)
}
```
Now you know who you are, and where you live (in a manner of speaking). The next step is setting up a 'swarm network' to handle all the peers you will connect to. The swarm handles incoming connections from other peers, and handles the negotiation of new outbound connections.
h2, err := libp2p.New(ctx,
// Use your own created keypair
libp2p.Identity(priv),
```go
import (
"context"
swarm "github.com/libp2p/go-libp2p-swarm"
// Set your own listen address
// The config takes an array of addresses, specify as many as you want.
libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/9000"),
)
// Make a context to govern the lifespan of the swarm
ctx := context.Background()
// Put all this together
netw, err := swarm.NewNetwork(ctx, []ma.Multiaddr{maddr}, pid, ps, nil)
if err != nil {
panic(err)
}
```
At this point, we have everything needed to finally construct a host. That call is the simplest one so far:
```go
import bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
myhost := bhost.New(netw)
fmt.Printf("Hello World, my second hosts ID is %s\n", h2.ID())
```
And thats it, you have a libp2p host and you're ready to start doing some awesome p2p networking!
......
......@@ -8,10 +8,10 @@ import (
inet "github.com/libp2p/go-libp2p-net"
uuid "github.com/google/uuid"
"github.com/libp2p/go-libp2p-host"
pb "github.com/libp2p/go-libp2p/examples/multipro/pb"
protobufCodec "github.com/multiformats/go-multicodec/protobuf"
uuid "github.com/satori/go.uuid"
)
// pattern: /protocol-name/request-or-response-message/version
......@@ -126,7 +126,7 @@ func (e *EchoProtocol) Echo(host host.Host) bool {
// create message data
req := &pb.EchoRequest{
MessageData: e.node.NewMessageData(uuid.Must(uuid.NewV4()).String(), false),
MessageData: e.node.NewMessageData(uuid.New().String(), false),
Message: fmt.Sprintf("Echo from %s", e.node.ID())}
signature, err := e.node.signProtoMessage(req)
......
......@@ -6,11 +6,9 @@ import (
"log"
"math/rand"
libp2p "github.com/libp2p/go-libp2p"
crypto "github.com/libp2p/go-libp2p-crypto"
peer "github.com/libp2p/go-libp2p-peer"
ps "github.com/libp2p/go-libp2p-peerstore"
swarm "github.com/libp2p/go-libp2p-swarm"
bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
ma "github.com/multiformats/go-multiaddr"
)
......@@ -18,14 +16,13 @@ import (
func makeRandomNode(port int, done chan bool) *Node {
// Ignoring most errors for brevity
// See echo example for more details and better implementation
priv, pub, _ := crypto.GenerateKeyPair(crypto.Secp256k1, 256)
pid, _ := peer.IDFromPublicKey(pub)
priv, _, _ := crypto.GenerateKeyPair(crypto.Secp256k1, 256)
listen, _ := ma.NewMultiaddr(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", port))
peerStore := ps.NewPeerstore()
peerStore.AddPrivKey(pid, priv)
peerStore.AddPubKey(pid, pub)
n, _ := swarm.NewNetwork(context.Background(), []ma.Multiaddr{listen}, pid, peerStore, nil)
host := bhost.New(n)
host, _ := libp2p.New(
context.Background(),
libp2p.ListenAddrs(listen),
libp2p.Identity(priv),
)
return NewNode(host, done)
}
......
......@@ -6,11 +6,11 @@ import (
"fmt"
"log"
uuid "github.com/google/uuid"
"github.com/libp2p/go-libp2p-host"
inet "github.com/libp2p/go-libp2p-net"
p2p "github.com/libp2p/go-libp2p/examples/multipro/pb"
protobufCodec "github.com/multiformats/go-multicodec/protobuf"
uuid "github.com/satori/go.uuid"
)
// pattern: /protocol-name/request-or-response-message/version
......@@ -116,7 +116,7 @@ func (p *PingProtocol) Ping(host host.Host) bool {
log.Printf("%s: Sending ping to: %s....", p.node.ID(), host.ID())
// create message data
req := &p2p.PingRequest{MessageData: p.node.NewMessageData(uuid.Must(uuid.NewV4()).String(), false),
req := &p2p.PingRequest{MessageData: p.node.NewMessageData(uuid.New().String(), false),
Message: fmt.Sprintf("Ping from %s", p.node.ID())}
// sign the data
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment