diff --git a/api/api_net.go b/api/api_net.go index 74581e3ac..ae53e4c0d 100644 --- a/api/api_net.go +++ b/api/api_net.go @@ -2,6 +2,7 @@ package api import ( "context" + "time" metrics "github.com/libp2p/go-libp2p-core/metrics" "github.com/libp2p/go-libp2p-core/network" @@ -25,6 +26,7 @@ type Net interface { NetConnectedness(context.Context, peer.ID) (network.Connectedness, error) //perm:read NetPeers(context.Context) ([]peer.AddrInfo, error) //perm:read + NetPing(context.Context, peer.ID) (time.Duration, error) //perm:read NetConnect(context.Context, peer.AddrInfo) error //perm:write NetAddrsListen(context.Context) (peer.AddrInfo, error) //perm:read NetDisconnect(context.Context, peer.ID) error //perm:write diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 1dce7fa9c..d59ed4aba 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -8,6 +8,7 @@ import ( context "context" json "encoding/json" reflect "reflect" + time "time" address "github.com/filecoin-project/go-address" bitfield "github.com/filecoin-project/go-bitfield" @@ -1856,6 +1857,21 @@ func (mr *MockFullNodeMockRecorder) NetPeers(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPeers", reflect.TypeOf((*MockFullNode)(nil).NetPeers), arg0) } +// NetPing mocks base method. +func (m *MockFullNode) NetPing(arg0 context.Context, arg1 peer.ID) (time.Duration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPing", arg0, arg1) + ret0, _ := ret[0].(time.Duration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPing indicates an expected call of NetPing. +func (mr *MockFullNodeMockRecorder) NetPing(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPing", reflect.TypeOf((*MockFullNode)(nil).NetPing), arg0, arg1) +} + // NetProtectAdd mocks base method. func (m *MockFullNode) NetProtectAdd(arg0 context.Context, arg1 []peer.ID) error { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index fa907fab7..4d41d3661 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -597,6 +597,8 @@ type NetStruct struct { NetPeers func(p0 context.Context) ([]peer.AddrInfo, error) `perm:"read"` + NetPing func(p0 context.Context, p1 peer.ID) (time.Duration, error) `perm:"read"` + NetProtectAdd func(p0 context.Context, p1 []peer.ID) error `perm:"admin"` NetProtectList func(p0 context.Context) ([]peer.ID, error) `perm:"read"` @@ -3708,6 +3710,17 @@ func (s *NetStub) NetPeers(p0 context.Context) ([]peer.AddrInfo, error) { return *new([]peer.AddrInfo), ErrNotSupported } +func (s *NetStruct) NetPing(p0 context.Context, p1 peer.ID) (time.Duration, error) { + if s.Internal.NetPing == nil { + return *new(time.Duration), ErrNotSupported + } + return s.Internal.NetPing(p0, p1) +} + +func (s *NetStub) NetPing(p0 context.Context, p1 peer.ID) (time.Duration, error) { + return *new(time.Duration), ErrNotSupported +} + func (s *NetStruct) NetProtectAdd(p0 context.Context, p1 []peer.ID) error { if s.Internal.NetProtectAdd == nil { return ErrNotSupported diff --git a/api/v0api/gomock_reflect_3555711957/prog.go b/api/v0api/gomock_reflect_3555711957/prog.go new file mode 100644 index 000000000..39ca2319d --- /dev/null +++ b/api/v0api/gomock_reflect_3555711957/prog.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/gob" + "flag" + "fmt" + "os" + "path" + "reflect" + + "github.com/golang/mock/mockgen/model" + + pkg_ "github.com/filecoin-project/lotus/api/v0api" +) + +var output = flag.String("output", "", "The output file name, or empty to use stdout.") + +func main() { + flag.Parse() + + its := []struct { + sym string + typ reflect.Type + }{ + + {"FullNode", reflect.TypeOf((*pkg_.FullNode)(nil)).Elem()}, + } + pkg := &model.Package{ + // NOTE: This behaves contrary to documented behaviour if the + // package name is not the final component of the import path. + // The reflect package doesn't expose the package name, though. + Name: path.Base("github.com/filecoin-project/lotus/api/v0api"), + } + + for _, it := range its { + intf, err := model.InterfaceFromInterfaceType(it.typ) + if err != nil { + fmt.Fprintf(os.Stderr, "Reflection: %v\n", err) + os.Exit(1) + } + intf.Name = it.sym + pkg.Interfaces = append(pkg.Interfaces, intf) + } + + outfile := os.Stdout + if len(*output) != 0 { + var err error + outfile, err = os.Create(*output) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to open output file %q", *output) + } + defer func() { + if err := outfile.Close(); err != nil { + fmt.Fprintf(os.Stderr, "failed to close output file %q", *output) + os.Exit(1) + } + }() + } + + if err := gob.NewEncoder(outfile).Encode(pkg); err != nil { + fmt.Fprintf(os.Stderr, "gob encode: %v\n", err) + os.Exit(1) + } +} diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go index e18c8bfe7..6cb80d894 100644 --- a/api/v0api/v0mocks/mock_full.go +++ b/api/v0api/v0mocks/mock_full.go @@ -7,6 +7,7 @@ package v0mocks import ( context "context" reflect "reflect" + time "time" address "github.com/filecoin-project/go-address" bitfield "github.com/filecoin-project/go-bitfield" @@ -1769,6 +1770,21 @@ func (mr *MockFullNodeMockRecorder) NetPeers(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPeers", reflect.TypeOf((*MockFullNode)(nil).NetPeers), arg0) } +// NetPing mocks base method. +func (m *MockFullNode) NetPing(arg0 context.Context, arg1 peer.ID) (time.Duration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPing", arg0, arg1) + ret0, _ := ret[0].(time.Duration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPing indicates an expected call of NetPing. +func (mr *MockFullNodeMockRecorder) NetPing(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPing", reflect.TypeOf((*MockFullNode)(nil).NetPing), arg0, arg1) +} + // NetProtectAdd mocks base method. func (m *MockFullNode) NetProtectAdd(arg0 context.Context, arg1 []peer.ID) error { m.ctrl.T.Helper() diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 61774ef34..e2831c65b 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 9d1247ec7..0753ba051 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index cae6788f9..c5d5734ca 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/cli/net.go b/cli/net.go index 0ff15e38a..c9a9a4392 100644 --- a/cli/net.go +++ b/cli/net.go @@ -1,12 +1,14 @@ package cli import ( + "context" "encoding/json" "fmt" "os" "sort" "strings" "text/tabwriter" + "time" "github.com/dustin/go-humanize" "github.com/urfave/cli/v2" @@ -28,6 +30,7 @@ var NetCmd = &cli.Command{ Usage: "Manage P2P Network", Subcommands: []*cli.Command{ NetPeers, + NetPing, NetConnect, NetListen, NetId, @@ -117,6 +120,82 @@ var NetPeers = &cli.Command{ }, } +var NetPing = &cli.Command{ + Name: "ping", + Usage: "Ping peers", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "count", + Value: 10, + Aliases: []string{"c"}, + Usage: "specify the number of times it should ping", + }, + &cli.DurationFlag{ + Name: "interval", + Value: time.Second, + Aliases: []string{"i"}, + Usage: "minimum time between pings", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 1 { + return xerrors.Errorf("please provide a peerID") + } + + api, closer, err := GetAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := ReqContext(cctx) + + pis, err := addrInfoFromArg(ctx, cctx) + if err != nil { + return err + } + + count := cctx.Int("count") + interval := cctx.Duration("interval") + + for _, pi := range pis { + err := api.NetConnect(ctx, pi) + if err != nil { + return xerrors.Errorf("connect: %w", err) + } + + fmt.Printf("PING %s\n", pi.ID) + var avg time.Duration + var successful int + + for i := 0; i < count && ctx.Err() == nil; i++ { + start := time.Now() + + rtt, err := api.NetPing(ctx, pi.ID) + if err != nil { + if ctx.Err() != nil { + break + } + log.Errorf("Ping failed: error=%v", err) + continue + } + fmt.Printf("Pong received: time=%v\n", rtt) + avg = avg + rtt + successful++ + + wctx, cancel := context.WithTimeout(ctx, time.Until(start.Add(interval))) + <-wctx.Done() + cancel() + } + + if successful > 0 { + fmt.Printf("Average latency: %v\n", avg/time.Duration(successful)) + } + } + return nil + }, +} + var NetScores = &cli.Command{ Name: "scores", Usage: "Print peers' pubsub scores", @@ -192,45 +271,9 @@ var NetConnect = &cli.Command{ defer closer() ctx := ReqContext(cctx) - pis, err := addrutil.ParseAddresses(ctx, cctx.Args().Slice()) + pis, err := addrInfoFromArg(ctx, cctx) if err != nil { - a, perr := address.NewFromString(cctx.Args().First()) - if perr != nil { - return err - } - - na, fc, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer fc() - - mi, err := na.StateMinerInfo(ctx, a, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - if mi.PeerId == nil { - return xerrors.Errorf("no PeerID for miner") - } - multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs)) - for i, a := range mi.Multiaddrs { - maddr, err := multiaddr.NewMultiaddrBytes(a) - if err != nil { - log.Warnf("parsing multiaddr %d (%x): %s", i, a, err) - continue - } - multiaddrs = append(multiaddrs, maddr) - } - - pi := peer.AddrInfo{ - ID: *mi.PeerId, - Addrs: multiaddrs, - } - - fmt.Printf("%s -> %s\n", a, pi) - - pis = append(pis, pi) + return err } for _, pi := range pis { @@ -247,6 +290,51 @@ var NetConnect = &cli.Command{ }, } +func addrInfoFromArg(ctx context.Context, cctx *cli.Context) ([]peer.AddrInfo, error) { + pis, err := addrutil.ParseAddresses(ctx, cctx.Args().Slice()) + if err != nil { + a, perr := address.NewFromString(cctx.Args().First()) + if perr != nil { + return nil, err + } + + na, fc, err := GetFullNodeAPI(cctx) + if err != nil { + return nil, err + } + defer fc() + + mi, err := na.StateMinerInfo(ctx, a, types.EmptyTSK) + if err != nil { + return nil, xerrors.Errorf("getting miner info: %w", err) + } + + if mi.PeerId == nil { + return nil, xerrors.Errorf("no PeerID for miner") + } + multiaddrs := make([]multiaddr.Multiaddr, 0, len(mi.Multiaddrs)) + for i, a := range mi.Multiaddrs { + maddr, err := multiaddr.NewMultiaddrBytes(a) + if err != nil { + log.Warnf("parsing multiaddr %d (%x): %s", i, a, err) + continue + } + multiaddrs = append(multiaddrs, maddr) + } + + pi := peer.AddrInfo{ + ID: *mi.PeerId, + Addrs: multiaddrs, + } + + fmt.Printf("%s -> %s\n", a, pi) + + pis = append(pis, pi) + } + + return pis, err +} + var NetId = &cli.Command{ Name: "id", Usage: "Get node identity", diff --git a/documentation/en/api-v0-methods-miner.md b/documentation/en/api-v0-methods-miner.md index a941d04c0..85a98d0e6 100644 --- a/documentation/en/api-v0-methods-miner.md +++ b/documentation/en/api-v0-methods-miner.md @@ -88,6 +88,7 @@ * [NetLimit](#NetLimit) * [NetPeerInfo](#NetPeerInfo) * [NetPeers](#NetPeers) + * [NetPing](#NetPing) * [NetProtectAdd](#NetProtectAdd) * [NetProtectList](#NetProtectList) * [NetProtectRemove](#NetProtectRemove) @@ -1851,6 +1852,20 @@ Response: ] ``` +### NetPing + + +Perms: read + +Inputs: +```json +[ + "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf" +] +``` + +Response: `60000000000` + ### NetProtectAdd diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index eb195df8a..6973a6651 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -131,6 +131,7 @@ * [NetLimit](#NetLimit) * [NetPeerInfo](#NetPeerInfo) * [NetPeers](#NetPeers) + * [NetPing](#NetPing) * [NetProtectAdd](#NetProtectAdd) * [NetProtectList](#NetProtectList) * [NetProtectRemove](#NetProtectRemove) @@ -3908,6 +3909,20 @@ Response: ] ``` +### NetPing + + +Perms: read + +Inputs: +```json +[ + "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf" +] +``` + +Response: `60000000000` + ### NetProtectAdd diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 48b4540fe..d2beb9f08 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -137,6 +137,7 @@ * [NetLimit](#NetLimit) * [NetPeerInfo](#NetPeerInfo) * [NetPeers](#NetPeers) + * [NetPing](#NetPing) * [NetProtectAdd](#NetProtectAdd) * [NetProtectList](#NetProtectList) * [NetProtectRemove](#NetProtectRemove) @@ -4270,6 +4271,20 @@ Response: ] ``` +### NetPing + + +Perms: read + +Inputs: +```json +[ + "12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf" +] +``` + +Response: `60000000000` + ### NetProtectAdd diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 6e7bd8eff..cd74a81fa 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -1200,6 +1200,7 @@ USAGE: COMMANDS: peers Print peers + ping Ping peers connect Connect to a peer listen List listen addresses id Get node identity @@ -1235,6 +1236,21 @@ OPTIONS: ``` +### lotus-miner net ping +``` +NAME: + lotus-miner net ping - Ping peers + +USAGE: + lotus-miner net ping [command options] [arguments...] + +OPTIONS: + --count value, -c value specify the number of times it should ping (default: 10) + --interval value, -i value minimum time between pings (default: 1s) + --help, -h show help (default: false) + +``` + ### lotus-miner net connect ``` NAME: diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index a08f01039..a9c729930 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2602,6 +2602,7 @@ USAGE: COMMANDS: peers Print peers + ping Ping peers connect Connect to a peer listen List listen addresses id Get node identity @@ -2637,6 +2638,21 @@ OPTIONS: ``` +### lotus net ping +``` +NAME: + lotus net ping - Ping peers + +USAGE: + lotus net ping [command options] [arguments...] + +OPTIONS: + --count value, -c value specify the number of times it should ping (default: 10) + --interval value, -i value minimum time between pings (default: 1s) + --help, -h show help (default: false) + +``` + ### lotus net connect ``` NAME: diff --git a/node/impl/net/net.go b/node/impl/net/net.go index 27e7734a1..fe0cf8c58 100644 --- a/node/impl/net/net.go +++ b/node/impl/net/net.go @@ -4,8 +4,10 @@ import ( "context" "sort" "strings" + "time" "go.uber.org/fx" + "golang.org/x/xerrors" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/metrics" @@ -15,6 +17,7 @@ import ( swarm "github.com/libp2p/go-libp2p-swarm" basichost "github.com/libp2p/go-libp2p/p2p/host/basic" "github.com/libp2p/go-libp2p/p2p/net/conngater" + "github.com/libp2p/go-libp2p/p2p/protocol/ping" ma "github.com/multiformats/go-multiaddr" "github.com/filecoin-project/lotus/api" @@ -177,6 +180,14 @@ func (a *NetAPI) NetBandwidthStatsByPeer(ctx context.Context) (map[string]metric return out, nil } +func (a *NetAPI) NetPing(ctx context.Context, p peer.ID) (time.Duration, error) { + result, ok := <-ping.Ping(ctx, a.Host, p) + if !ok { + return 0, xerrors.Errorf("didn't get ping result: %w", ctx.Err()) + } + return result.RTT, result.Error +} + func (a *NetAPI) NetBandwidthStatsByProtocol(ctx context.Context) (map[protocol.ID]metrics.Stats, error) { return a.Reporter.GetBandwidthByProtocol(), nil }