From 58702220ac523ee43ba643c0d16c40e24e4f240d Mon Sep 17 00:00:00 2001
From: Ferran Borreguero <ferranbt@protonmail.com>
Date: Tue, 19 Oct 2021 09:11:36 +0200
Subject: [PATCH] Add peers module

---
 command/account.go                     |   6 +-
 command/main.go                        |  32 ++
 command/peers.go                       |  43 ++
 command/peers_add.go                   |  72 +++
 command/peers_list.go                  |  81 +++
 command/peers_remove.go                |  72 +++
 command/peers_status.go                |  81 +++
 command/server/proto/server.pb.go      | 729 +++++++++++++++++++++++--
 command/server/proto/server.proto      |  50 ++
 command/server/proto/server_grpc.pb.go | 144 +++++
 command/server/service.go              |  71 +++
 11 files changed, 1333 insertions(+), 48 deletions(-)
 create mode 100644 command/peers.go
 create mode 100644 command/peers_add.go
 create mode 100644 command/peers_list.go
 create mode 100644 command/peers_remove.go
 create mode 100644 command/peers_status.go

diff --git a/command/account.go b/command/account.go
index 948ab3299..747b6b8db 100644
--- a/command/account.go
+++ b/command/account.go
@@ -18,7 +18,11 @@ func (a *Account) Help() string {
   
   Display the status of a specific deployment:
 
-    $ bor account import`
+    $ bor account import
+    
+  List the imported accounts in the keystore:
+    
+    $ bor account list`
 }
 
 // Synopsis implements the cli.Command interface
diff --git a/command/main.go b/command/main.go
index c4b9f3887..fab6f55ce 100644
--- a/command/main.go
+++ b/command/main.go
@@ -84,6 +84,31 @@ func commands() map[string]cli.CommandFactory {
 				Meta: meta,
 			}, nil
 		},
+		"peers": func() (cli.Command, error) {
+			return &PeersCommand{
+				UI: ui,
+			}, nil
+		},
+		"peers add": func() (cli.Command, error) {
+			return &PeersAddCommand{
+				Meta2: meta2,
+			}, nil
+		},
+		"peers remove": func() (cli.Command, error) {
+			return &PeersRemoveCommand{
+				Meta2: meta2,
+			}, nil
+		},
+		"peers list": func() (cli.Command, error) {
+			return &PeersListCommand{
+				Meta2: meta2,
+			}, nil
+		},
+		"peers status": func() (cli.Command, error) {
+			return &PeersStatusCommand{
+				Meta2: meta2,
+			}, nil
+		},
 	}
 }
 
@@ -173,3 +198,10 @@ func formatList(in []string) string {
 	columnConf.Empty = "<none>"
 	return columnize.Format(in, columnConf)
 }
+
+func formatKV(in []string) string {
+	columnConf := columnize.DefaultConfig()
+	columnConf.Empty = "<none>"
+	columnConf.Glue = " = "
+	return columnize.Format(in, columnConf)
+}
diff --git a/command/peers.go b/command/peers.go
new file mode 100644
index 000000000..bfe56842d
--- /dev/null
+++ b/command/peers.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+	"github.com/mitchellh/cli"
+)
+
+// PeersCommand is the command to group the peers commands
+type PeersCommand struct {
+	UI cli.Ui
+}
+
+// Help implements the cli.Command interface
+func (c *PeersCommand) Help() string {
+	return `Usage: bor peers <subcommand>
+
+  This command groups actions to interact with peers.
+	
+  List the connected peers:
+  
+    $ bor account new
+	
+  Add a new peer by enode:
+  
+    $ bor account import
+
+  Remove a connected peer by enode:
+
+    $ bor peers remove <enode>
+
+  Display information about a peer:
+
+    $ bor peers status <peer id>`
+}
+
+// Synopsis implements the cli.Command interface
+func (c *PeersCommand) Synopsis() string {
+	return "Interact with peers"
+}
+
+// Run implements the cli.Command interface
+func (c *PeersCommand) Run(args []string) int {
+	return cli.RunResultHelp
+}
diff --git a/command/peers_add.go b/command/peers_add.go
new file mode 100644
index 000000000..796b89668
--- /dev/null
+++ b/command/peers_add.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+	"context"
+
+	"github.com/ethereum/go-ethereum/command/flagset"
+	"github.com/ethereum/go-ethereum/command/server/proto"
+)
+
+// PeersAddCommand is the command to group the peers commands
+type PeersAddCommand struct {
+	*Meta2
+
+	trusted bool
+}
+
+// Help implements the cli.Command interface
+func (p *PeersAddCommand) Help() string {
+	return `Usage: bor peers add <enode>
+
+  Joins the local client to another remote peer.
+
+  ` + p.Flags().Help()
+}
+
+func (p *PeersAddCommand) Flags() *flagset.Flagset {
+	flags := p.NewFlagSet("peers add")
+
+	flags.BoolFlag(&flagset.BoolFlag{
+		Name:  "trusted",
+		Usage: "Add the peer as a trusted",
+		Value: &p.trusted,
+	})
+
+	return flags
+}
+
+// Synopsis implements the cli.Command interface
+func (c *PeersAddCommand) Synopsis() string {
+	return "Join the client to a remote peer"
+}
+
+// Run implements the cli.Command interface
+func (c *PeersAddCommand) Run(args []string) int {
+	flags := c.Flags()
+	if err := flags.Parse(args); err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	args = flags.Args()
+	if len(args) != 1 {
+		c.UI.Error("No enode address provided")
+		return 1
+	}
+
+	borClt, err := c.BorConn()
+	if err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	req := &proto.PeersAddRequest{
+		Enode:   args[0],
+		Trusted: c.trusted,
+	}
+	if _, err := borClt.PeersAdd(context.Background(), req); err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+	return 0
+}
diff --git a/command/peers_list.go b/command/peers_list.go
new file mode 100644
index 000000000..47e9e4caf
--- /dev/null
+++ b/command/peers_list.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/command/flagset"
+	"github.com/ethereum/go-ethereum/command/server/proto"
+)
+
+// PeersListCommand is the command to group the peers commands
+type PeersListCommand struct {
+	*Meta2
+}
+
+// Help implements the cli.Command interface
+func (p *PeersListCommand) Help() string {
+	return `Usage: bor peers list
+
+  Build an archive containing Bor pprof traces
+
+  ` + p.Flags().Help()
+}
+
+func (p *PeersListCommand) Flags() *flagset.Flagset {
+	flags := p.NewFlagSet("peers list")
+
+	return flags
+}
+
+// Synopsis implements the cli.Command interface
+func (c *PeersListCommand) Synopsis() string {
+	return ""
+}
+
+// Run implements the cli.Command interface
+func (c *PeersListCommand) Run(args []string) int {
+	flags := c.Flags()
+	if err := flags.Parse(args); err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	borClt, err := c.BorConn()
+	if err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	req := &proto.PeersListRequest{}
+	resp, err := borClt.PeersList(context.Background(), req)
+	if err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	c.UI.Output(formatPeers(resp.Peers))
+	return 0
+}
+
+func formatPeers(peers []*proto.Peer) string {
+	if len(peers) == 0 {
+		return "No peers found"
+	}
+
+	rows := make([]string, len(peers)+1)
+	rows[0] = "ID|Enode|Name|Caps|Static|Trusted"
+	for i, d := range peers {
+		enode := strings.TrimPrefix(d.Enode, "enode://")
+
+		rows[i+1] = fmt.Sprintf("%s|%s|%s|%s|%v|%v",
+			d.Id,
+			enode[:10],
+			d.Name,
+			strings.Join(d.Caps, ","),
+			d.Static,
+			d.Trusted)
+	}
+	return formatList(rows)
+}
diff --git a/command/peers_remove.go b/command/peers_remove.go
new file mode 100644
index 000000000..432a35fdc
--- /dev/null
+++ b/command/peers_remove.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+	"context"
+
+	"github.com/ethereum/go-ethereum/command/flagset"
+	"github.com/ethereum/go-ethereum/command/server/proto"
+)
+
+// PeersRemoveCommand is the command to group the peers commands
+type PeersRemoveCommand struct {
+	*Meta2
+
+	trusted bool
+}
+
+// Help implements the cli.Command interface
+func (p *PeersRemoveCommand) Help() string {
+	return `Usage: bor peers remove <enode>
+
+  Disconnects the local client from a connected peer if exists.
+
+  ` + p.Flags().Help()
+}
+
+func (p *PeersRemoveCommand) Flags() *flagset.Flagset {
+	flags := p.NewFlagSet("peers remove")
+
+	flags.BoolFlag(&flagset.BoolFlag{
+		Name:  "trusted",
+		Usage: "Add the peer as a trusted",
+		Value: &p.trusted,
+	})
+
+	return flags
+}
+
+// Synopsis implements the cli.Command interface
+func (c *PeersRemoveCommand) Synopsis() string {
+	return "Disconnects a peer from the client"
+}
+
+// Run implements the cli.Command interface
+func (c *PeersRemoveCommand) Run(args []string) int {
+	flags := c.Flags()
+	if err := flags.Parse(args); err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	args = flags.Args()
+	if len(args) != 1 {
+		c.UI.Error("No enode address provided")
+		return 1
+	}
+
+	borClt, err := c.BorConn()
+	if err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	req := &proto.PeersRemoveRequest{
+		Enode:   args[0],
+		Trusted: c.trusted,
+	}
+	if _, err := borClt.PeersRemove(context.Background(), req); err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+	return 0
+}
diff --git a/command/peers_status.go b/command/peers_status.go
new file mode 100644
index 000000000..adb7b2e85
--- /dev/null
+++ b/command/peers_status.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/command/flagset"
+	"github.com/ethereum/go-ethereum/command/server/proto"
+)
+
+// PeersStatusCommand is the command to group the peers commands
+type PeersStatusCommand struct {
+	*Meta2
+}
+
+// Help implements the cli.Command interface
+func (p *PeersStatusCommand) Help() string {
+	return `Usage: bor peers status <peer id>
+
+  Display the status of a peer by its id.
+
+  ` + p.Flags().Help()
+}
+
+func (p *PeersStatusCommand) Flags() *flagset.Flagset {
+	flags := p.NewFlagSet("peers status")
+
+	return flags
+}
+
+// Synopsis implements the cli.Command interface
+func (c *PeersStatusCommand) Synopsis() string {
+	return "Display the status of a peer"
+}
+
+// Run implements the cli.Command interface
+func (c *PeersStatusCommand) Run(args []string) int {
+	flags := c.Flags()
+	if err := flags.Parse(args); err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	args = flags.Args()
+	if len(args) != 1 {
+		c.UI.Error("No enode address provided")
+		return 1
+	}
+
+	borClt, err := c.BorConn()
+	if err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	req := &proto.PeersStatusRequest{
+		Enode: args[0],
+	}
+	resp, err := borClt.PeersStatus(context.Background(), req)
+	if err != nil {
+		c.UI.Error(err.Error())
+		return 1
+	}
+
+	c.UI.Output(formatPeer(resp.Peer))
+	return 0
+}
+
+func formatPeer(peer *proto.Peer) string {
+	base := formatKV([]string{
+		fmt.Sprintf("Name|%s", peer.Name),
+		fmt.Sprintf("ID|%s", peer.Id),
+		fmt.Sprintf("ENR|%s", peer.Enr),
+		fmt.Sprintf("Capabilities|%s", strings.Join(peer.Caps, ",")),
+		fmt.Sprintf("Enode|%s", peer.Enode),
+		fmt.Sprintf("Static|%v", peer.Static),
+		fmt.Sprintf("Trusted|%v", peer.Trusted),
+	})
+	return base
+}
diff --git a/command/server/proto/server.pb.go b/command/server/proto/server.pb.go
index 87ace6509..a6c13fc7d 100644
--- a/command/server/proto/server.pb.go
+++ b/command/server/proto/server.pb.go
@@ -71,7 +71,467 @@ func (x PprofRequest_Type) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use PprofRequest_Type.Descriptor instead.
 func (PprofRequest_Type) EnumDescriptor() ([]byte, []int) {
-	return file_command_server_proto_server_proto_rawDescGZIP(), []int{0, 0}
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{9, 0}
+}
+
+type PeersAddRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Enode   string `protobuf:"bytes,1,opt,name=enode,proto3" json:"enode,omitempty"`
+	Trusted bool   `protobuf:"varint,2,opt,name=trusted,proto3" json:"trusted,omitempty"`
+}
+
+func (x *PeersAddRequest) Reset() {
+	*x = PeersAddRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersAddRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersAddRequest) ProtoMessage() {}
+
+func (x *PeersAddRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersAddRequest.ProtoReflect.Descriptor instead.
+func (*PeersAddRequest) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *PeersAddRequest) GetEnode() string {
+	if x != nil {
+		return x.Enode
+	}
+	return ""
+}
+
+func (x *PeersAddRequest) GetTrusted() bool {
+	if x != nil {
+		return x.Trusted
+	}
+	return false
+}
+
+type PeersAddResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *PeersAddResponse) Reset() {
+	*x = PeersAddResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersAddResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersAddResponse) ProtoMessage() {}
+
+func (x *PeersAddResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersAddResponse.ProtoReflect.Descriptor instead.
+func (*PeersAddResponse) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{1}
+}
+
+type PeersRemoveRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Enode   string `protobuf:"bytes,1,opt,name=enode,proto3" json:"enode,omitempty"`
+	Trusted bool   `protobuf:"varint,2,opt,name=trusted,proto3" json:"trusted,omitempty"`
+}
+
+func (x *PeersRemoveRequest) Reset() {
+	*x = PeersRemoveRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersRemoveRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersRemoveRequest) ProtoMessage() {}
+
+func (x *PeersRemoveRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersRemoveRequest.ProtoReflect.Descriptor instead.
+func (*PeersRemoveRequest) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *PeersRemoveRequest) GetEnode() string {
+	if x != nil {
+		return x.Enode
+	}
+	return ""
+}
+
+func (x *PeersRemoveRequest) GetTrusted() bool {
+	if x != nil {
+		return x.Trusted
+	}
+	return false
+}
+
+type PeersRemoveResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *PeersRemoveResponse) Reset() {
+	*x = PeersRemoveResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersRemoveResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersRemoveResponse) ProtoMessage() {}
+
+func (x *PeersRemoveResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersRemoveResponse.ProtoReflect.Descriptor instead.
+func (*PeersRemoveResponse) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{3}
+}
+
+type PeersListRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *PeersListRequest) Reset() {
+	*x = PeersListRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersListRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersListRequest) ProtoMessage() {}
+
+func (x *PeersListRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersListRequest.ProtoReflect.Descriptor instead.
+func (*PeersListRequest) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{4}
+}
+
+type PeersListResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Peers []*Peer `protobuf:"bytes,1,rep,name=peers,proto3" json:"peers,omitempty"`
+}
+
+func (x *PeersListResponse) Reset() {
+	*x = PeersListResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersListResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersListResponse) ProtoMessage() {}
+
+func (x *PeersListResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersListResponse.ProtoReflect.Descriptor instead.
+func (*PeersListResponse) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *PeersListResponse) GetPeers() []*Peer {
+	if x != nil {
+		return x.Peers
+	}
+	return nil
+}
+
+type PeersStatusRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Enode string `protobuf:"bytes,1,opt,name=enode,proto3" json:"enode,omitempty"`
+}
+
+func (x *PeersStatusRequest) Reset() {
+	*x = PeersStatusRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersStatusRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersStatusRequest) ProtoMessage() {}
+
+func (x *PeersStatusRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersStatusRequest.ProtoReflect.Descriptor instead.
+func (*PeersStatusRequest) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *PeersStatusRequest) GetEnode() string {
+	if x != nil {
+		return x.Enode
+	}
+	return ""
+}
+
+type PeersStatusResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Peer *Peer `protobuf:"bytes,1,opt,name=peer,proto3" json:"peer,omitempty"`
+}
+
+func (x *PeersStatusResponse) Reset() {
+	*x = PeersStatusResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PeersStatusResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PeersStatusResponse) ProtoMessage() {}
+
+func (x *PeersStatusResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PeersStatusResponse.ProtoReflect.Descriptor instead.
+func (*PeersStatusResponse) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *PeersStatusResponse) GetPeer() *Peer {
+	if x != nil {
+		return x.Peer
+	}
+	return nil
+}
+
+type Peer struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Id      string   `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	Enode   string   `protobuf:"bytes,2,opt,name=enode,proto3" json:"enode,omitempty"`
+	Enr     string   `protobuf:"bytes,3,opt,name=enr,proto3" json:"enr,omitempty"`
+	Caps    []string `protobuf:"bytes,4,rep,name=caps,proto3" json:"caps,omitempty"`
+	Name    string   `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`
+	Trusted bool     `protobuf:"varint,6,opt,name=trusted,proto3" json:"trusted,omitempty"`
+	Static  bool     `protobuf:"varint,7,opt,name=static,proto3" json:"static,omitempty"`
+}
+
+func (x *Peer) Reset() {
+	*x = Peer{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_command_server_proto_server_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Peer) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Peer) ProtoMessage() {}
+
+func (x *Peer) ProtoReflect() protoreflect.Message {
+	mi := &file_command_server_proto_server_proto_msgTypes[8]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Peer.ProtoReflect.Descriptor instead.
+func (*Peer) Descriptor() ([]byte, []int) {
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *Peer) GetId() string {
+	if x != nil {
+		return x.Id
+	}
+	return ""
+}
+
+func (x *Peer) GetEnode() string {
+	if x != nil {
+		return x.Enode
+	}
+	return ""
+}
+
+func (x *Peer) GetEnr() string {
+	if x != nil {
+		return x.Enr
+	}
+	return ""
+}
+
+func (x *Peer) GetCaps() []string {
+	if x != nil {
+		return x.Caps
+	}
+	return nil
+}
+
+func (x *Peer) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *Peer) GetTrusted() bool {
+	if x != nil {
+		return x.Trusted
+	}
+	return false
+}
+
+func (x *Peer) GetStatic() bool {
+	if x != nil {
+		return x.Static
+	}
+	return false
 }
 
 type PprofRequest struct {
@@ -87,7 +547,7 @@ type PprofRequest struct {
 func (x *PprofRequest) Reset() {
 	*x = PprofRequest{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_command_server_proto_server_proto_msgTypes[0]
+		mi := &file_command_server_proto_server_proto_msgTypes[9]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -100,7 +560,7 @@ func (x *PprofRequest) String() string {
 func (*PprofRequest) ProtoMessage() {}
 
 func (x *PprofRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_command_server_proto_server_proto_msgTypes[0]
+	mi := &file_command_server_proto_server_proto_msgTypes[9]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -113,7 +573,7 @@ func (x *PprofRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use PprofRequest.ProtoReflect.Descriptor instead.
 func (*PprofRequest) Descriptor() ([]byte, []int) {
-	return file_command_server_proto_server_proto_rawDescGZIP(), []int{0}
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{9}
 }
 
 func (x *PprofRequest) GetType() PprofRequest_Type {
@@ -149,7 +609,7 @@ type PprofResponse struct {
 func (x *PprofResponse) Reset() {
 	*x = PprofResponse{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_command_server_proto_server_proto_msgTypes[1]
+		mi := &file_command_server_proto_server_proto_msgTypes[10]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -162,7 +622,7 @@ func (x *PprofResponse) String() string {
 func (*PprofResponse) ProtoMessage() {}
 
 func (x *PprofResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_command_server_proto_server_proto_msgTypes[1]
+	mi := &file_command_server_proto_server_proto_msgTypes[10]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -175,7 +635,7 @@ func (x *PprofResponse) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use PprofResponse.ProtoReflect.Descriptor instead.
 func (*PprofResponse) Descriptor() ([]byte, []int) {
-	return file_command_server_proto_server_proto_rawDescGZIP(), []int{1}
+	return file_command_server_proto_server_proto_rawDescGZIP(), []int{10}
 }
 
 func (x *PprofResponse) GetPayload() string {
@@ -197,33 +657,81 @@ var File_command_server_proto_server_proto protoreflect.FileDescriptor
 var file_command_server_proto_server_proto_rawDesc = []byte{
 	0x0a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
 	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x01, 0x0a, 0x0c, 0x50,
-	0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x74,
-	0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54,
-	0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f,
-	0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66,
-	0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x26, 0x0a,
-	0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x10,
-	0x00, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52,
-	0x41, 0x43, 0x45, 0x10, 0x02, 0x22, 0xa2, 0x01, 0x0a, 0x0d, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f,
-	0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61,
-	0x64, 0x12, 0x3b, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73,
-	0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x3a,
-	0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
-	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
-	0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0x39, 0x0a, 0x03, 0x42, 0x6f,
+	0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x41, 0x0a, 0x0f, 0x50, 0x65,
+	0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a,
+	0x05, 0x65, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6e,
+	0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x22, 0x12, 0x0a,
+	0x10, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x22, 0x44, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6e, 0x6f, 0x64, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a,
+	0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
+	0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x73,
+	0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12,
+	0x0a, 0x10, 0x50, 0x65, 0x65, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x22, 0x36, 0x0a, 0x11, 0x50, 0x65, 0x65, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50,
+	0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x22, 0x2a, 0x0a, 0x12, 0x50, 0x65,
+	0x65, 0x72, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x14, 0x0a, 0x05, 0x65, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x65, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0x36, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x73, 0x53,
+	0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a,
+	0x04, 0x70, 0x65, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x22, 0x98,
+	0x01, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6e, 0x6f, 0x64, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a,
+	0x03, 0x65, 0x6e, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x72, 0x12,
+	0x12, 0x0a, 0x04, 0x63, 0x61, 0x70, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x63,
+	0x61, 0x70, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x75, 0x73, 0x74,
+	0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65,
+	0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28,
+	0x08, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x22, 0x98, 0x01, 0x0a, 0x0c, 0x50, 0x70,
+	0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x74, 0x79,
+	0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x79,
+	0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66,
+	0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69,
+	0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x22, 0x26, 0x0a, 0x04,
+	0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x4f, 0x4b, 0x55, 0x50, 0x10, 0x00,
+	0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41,
+	0x43, 0x45, 0x10, 0x02, 0x22, 0xa2, 0x01, 0x0a, 0x0d, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64,
+	0x12, 0x3b, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45,
+	0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x3a, 0x0a,
+	0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
+	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
+	0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0xc2, 0x02, 0x0a, 0x03, 0x42, 0x6f,
 	0x72, 0x12, 0x32, 0x0a, 0x05, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f,
 	0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
 	0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x17, 0x5a, 0x15, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
-	0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64,
+	0x64, 0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41,
+	0x64, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x41, 0x64, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x6d, 0x6f, 0x76,
+	0x65, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52,
+	0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72,
+	0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x65,
+	0x65, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72,
+	0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
+	0x50, 0x65, 0x65, 0x72, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73,
+	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x17,
+	0x5a, 0x15, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65,
+	0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -239,23 +747,42 @@ func file_command_server_proto_server_proto_rawDescGZIP() []byte {
 }
 
 var file_command_server_proto_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_command_server_proto_server_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_command_server_proto_server_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
 var file_command_server_proto_server_proto_goTypes = []interface{}{
-	(PprofRequest_Type)(0), // 0: proto.PprofRequest.Type
-	(*PprofRequest)(nil),   // 1: proto.PprofRequest
-	(*PprofResponse)(nil),  // 2: proto.PprofResponse
-	nil,                    // 3: proto.PprofResponse.HeadersEntry
+	(PprofRequest_Type)(0),      // 0: proto.PprofRequest.Type
+	(*PeersAddRequest)(nil),     // 1: proto.PeersAddRequest
+	(*PeersAddResponse)(nil),    // 2: proto.PeersAddResponse
+	(*PeersRemoveRequest)(nil),  // 3: proto.PeersRemoveRequest
+	(*PeersRemoveResponse)(nil), // 4: proto.PeersRemoveResponse
+	(*PeersListRequest)(nil),    // 5: proto.PeersListRequest
+	(*PeersListResponse)(nil),   // 6: proto.PeersListResponse
+	(*PeersStatusRequest)(nil),  // 7: proto.PeersStatusRequest
+	(*PeersStatusResponse)(nil), // 8: proto.PeersStatusResponse
+	(*Peer)(nil),                // 9: proto.Peer
+	(*PprofRequest)(nil),        // 10: proto.PprofRequest
+	(*PprofResponse)(nil),       // 11: proto.PprofResponse
+	nil,                         // 12: proto.PprofResponse.HeadersEntry
 }
 var file_command_server_proto_server_proto_depIdxs = []int32{
-	0, // 0: proto.PprofRequest.type:type_name -> proto.PprofRequest.Type
-	3, // 1: proto.PprofResponse.headers:type_name -> proto.PprofResponse.HeadersEntry
-	1, // 2: proto.Bor.Pprof:input_type -> proto.PprofRequest
-	2, // 3: proto.Bor.Pprof:output_type -> proto.PprofResponse
-	3, // [3:4] is the sub-list for method output_type
-	2, // [2:3] is the sub-list for method input_type
-	2, // [2:2] is the sub-list for extension type_name
-	2, // [2:2] is the sub-list for extension extendee
-	0, // [0:2] is the sub-list for field type_name
+	9,  // 0: proto.PeersListResponse.peers:type_name -> proto.Peer
+	9,  // 1: proto.PeersStatusResponse.peer:type_name -> proto.Peer
+	0,  // 2: proto.PprofRequest.type:type_name -> proto.PprofRequest.Type
+	12, // 3: proto.PprofResponse.headers:type_name -> proto.PprofResponse.HeadersEntry
+	10, // 4: proto.Bor.Pprof:input_type -> proto.PprofRequest
+	1,  // 5: proto.Bor.PeersAdd:input_type -> proto.PeersAddRequest
+	3,  // 6: proto.Bor.PeersRemove:input_type -> proto.PeersRemoveRequest
+	5,  // 7: proto.Bor.PeersList:input_type -> proto.PeersListRequest
+	7,  // 8: proto.Bor.PeersStatus:input_type -> proto.PeersStatusRequest
+	11, // 9: proto.Bor.Pprof:output_type -> proto.PprofResponse
+	2,  // 10: proto.Bor.PeersAdd:output_type -> proto.PeersAddResponse
+	4,  // 11: proto.Bor.PeersRemove:output_type -> proto.PeersRemoveResponse
+	6,  // 12: proto.Bor.PeersList:output_type -> proto.PeersListResponse
+	8,  // 13: proto.Bor.PeersStatus:output_type -> proto.PeersStatusResponse
+	9,  // [9:14] is the sub-list for method output_type
+	4,  // [4:9] is the sub-list for method input_type
+	4,  // [4:4] is the sub-list for extension type_name
+	4,  // [4:4] is the sub-list for extension extendee
+	0,  // [0:4] is the sub-list for field type_name
 }
 
 func init() { file_command_server_proto_server_proto_init() }
@@ -265,7 +792,7 @@ func file_command_server_proto_server_proto_init() {
 	}
 	if !protoimpl.UnsafeEnabled {
 		file_command_server_proto_server_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PprofRequest); i {
+			switch v := v.(*PeersAddRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -277,6 +804,114 @@ func file_command_server_proto_server_proto_init() {
 			}
 		}
 		file_command_server_proto_server_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PeersAddResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PeersRemoveRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PeersRemoveResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PeersListRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PeersListResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PeersStatusRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PeersStatusResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Peer); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PprofRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_command_server_proto_server_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*PprofResponse); i {
 			case 0:
 				return &v.state
@@ -295,7 +930,7 @@ func file_command_server_proto_server_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_command_server_proto_server_proto_rawDesc,
 			NumEnums:      1,
-			NumMessages:   3,
+			NumMessages:   12,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/command/server/proto/server.proto b/command/server/proto/server.proto
index 2348e6dd6..0d1de7bef 100644
--- a/command/server/proto/server.proto
+++ b/command/server/proto/server.proto
@@ -6,6 +6,56 @@ option go_package = "/command/server/proto";
 
 service Bor {
     rpc Pprof(PprofRequest) returns (PprofResponse);
+
+    rpc PeersAdd(PeersAddRequest) returns (PeersAddResponse);
+
+    rpc PeersRemove(PeersRemoveRequest) returns (PeersRemoveResponse);
+
+    rpc PeersList(PeersListRequest) returns (PeersListResponse);
+
+    rpc PeersStatus(PeersStatusRequest) returns (PeersStatusResponse);
+}
+
+
+message PeersAddRequest {
+    string enode = 1;
+    bool trusted = 2;
+}
+
+message PeersAddResponse {
+}
+
+message PeersRemoveRequest {
+    string enode = 1;
+    bool trusted = 2;
+}
+
+message PeersRemoveResponse {
+}
+
+message PeersListRequest {
+}
+
+message PeersListResponse {
+    repeated Peer peers = 1;
+}
+
+message PeersStatusRequest {
+    string enode = 1;
+}
+
+message PeersStatusResponse {
+    Peer peer = 1;
+}
+
+message Peer {
+    string id = 1;
+    string enode = 2;
+    string enr = 3;
+    repeated string caps = 4;
+    string name = 5;
+    bool trusted = 6;
+    bool static = 7;
 }
 
 message PprofRequest {
diff --git a/command/server/proto/server_grpc.pb.go b/command/server/proto/server_grpc.pb.go
index 1c101f5e8..109911e95 100644
--- a/command/server/proto/server_grpc.pb.go
+++ b/command/server/proto/server_grpc.pb.go
@@ -19,6 +19,10 @@ const _ = grpc.SupportPackageIsVersion7
 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
 type BorClient interface {
 	Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.CallOption) (*PprofResponse, error)
+	PeersAdd(ctx context.Context, in *PeersAddRequest, opts ...grpc.CallOption) (*PeersAddResponse, error)
+	PeersRemove(ctx context.Context, in *PeersRemoveRequest, opts ...grpc.CallOption) (*PeersRemoveResponse, error)
+	PeersList(ctx context.Context, in *PeersListRequest, opts ...grpc.CallOption) (*PeersListResponse, error)
+	PeersStatus(ctx context.Context, in *PeersStatusRequest, opts ...grpc.CallOption) (*PeersStatusResponse, error)
 }
 
 type borClient struct {
@@ -38,11 +42,51 @@ func (c *borClient) Pprof(ctx context.Context, in *PprofRequest, opts ...grpc.Ca
 	return out, nil
 }
 
+func (c *borClient) PeersAdd(ctx context.Context, in *PeersAddRequest, opts ...grpc.CallOption) (*PeersAddResponse, error) {
+	out := new(PeersAddResponse)
+	err := c.cc.Invoke(ctx, "/proto.Bor/PeersAdd", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *borClient) PeersRemove(ctx context.Context, in *PeersRemoveRequest, opts ...grpc.CallOption) (*PeersRemoveResponse, error) {
+	out := new(PeersRemoveResponse)
+	err := c.cc.Invoke(ctx, "/proto.Bor/PeersRemove", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *borClient) PeersList(ctx context.Context, in *PeersListRequest, opts ...grpc.CallOption) (*PeersListResponse, error) {
+	out := new(PeersListResponse)
+	err := c.cc.Invoke(ctx, "/proto.Bor/PeersList", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *borClient) PeersStatus(ctx context.Context, in *PeersStatusRequest, opts ...grpc.CallOption) (*PeersStatusResponse, error) {
+	out := new(PeersStatusResponse)
+	err := c.cc.Invoke(ctx, "/proto.Bor/PeersStatus", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // BorServer is the server API for Bor service.
 // All implementations must embed UnimplementedBorServer
 // for forward compatibility
 type BorServer interface {
 	Pprof(context.Context, *PprofRequest) (*PprofResponse, error)
+	PeersAdd(context.Context, *PeersAddRequest) (*PeersAddResponse, error)
+	PeersRemove(context.Context, *PeersRemoveRequest) (*PeersRemoveResponse, error)
+	PeersList(context.Context, *PeersListRequest) (*PeersListResponse, error)
+	PeersStatus(context.Context, *PeersStatusRequest) (*PeersStatusResponse, error)
 	mustEmbedUnimplementedBorServer()
 }
 
@@ -53,6 +97,18 @@ type UnimplementedBorServer struct {
 func (UnimplementedBorServer) Pprof(context.Context, *PprofRequest) (*PprofResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method Pprof not implemented")
 }
+func (UnimplementedBorServer) PeersAdd(context.Context, *PeersAddRequest) (*PeersAddResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method PeersAdd not implemented")
+}
+func (UnimplementedBorServer) PeersRemove(context.Context, *PeersRemoveRequest) (*PeersRemoveResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method PeersRemove not implemented")
+}
+func (UnimplementedBorServer) PeersList(context.Context, *PeersListRequest) (*PeersListResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method PeersList not implemented")
+}
+func (UnimplementedBorServer) PeersStatus(context.Context, *PeersStatusRequest) (*PeersStatusResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method PeersStatus not implemented")
+}
 func (UnimplementedBorServer) mustEmbedUnimplementedBorServer() {}
 
 // UnsafeBorServer may be embedded to opt out of forward compatibility for this service.
@@ -84,6 +140,78 @@ func _Bor_Pprof_Handler(srv interface{}, ctx context.Context, dec func(interface
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Bor_PeersAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(PeersAddRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BorServer).PeersAdd(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/proto.Bor/PeersAdd",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BorServer).PeersAdd(ctx, req.(*PeersAddRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Bor_PeersRemove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(PeersRemoveRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BorServer).PeersRemove(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/proto.Bor/PeersRemove",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BorServer).PeersRemove(ctx, req.(*PeersRemoveRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Bor_PeersList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(PeersListRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BorServer).PeersList(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/proto.Bor/PeersList",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BorServer).PeersList(ctx, req.(*PeersListRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Bor_PeersStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(PeersStatusRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(BorServer).PeersStatus(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/proto.Bor/PeersStatus",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(BorServer).PeersStatus(ctx, req.(*PeersStatusRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 // Bor_ServiceDesc is the grpc.ServiceDesc for Bor service.
 // It's only intended for direct use with grpc.RegisterService,
 // and not to be introspected or modified (even as a copy)
@@ -95,6 +223,22 @@ var Bor_ServiceDesc = grpc.ServiceDesc{
 			MethodName: "Pprof",
 			Handler:    _Bor_Pprof_Handler,
 		},
+		{
+			MethodName: "PeersAdd",
+			Handler:    _Bor_PeersAdd_Handler,
+		},
+		{
+			MethodName: "PeersRemove",
+			Handler:    _Bor_PeersRemove_Handler,
+		},
+		{
+			MethodName: "PeersList",
+			Handler:    _Bor_PeersList_Handler,
+		},
+		{
+			MethodName: "PeersStatus",
+			Handler:    _Bor_PeersStatus_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "command/server/proto/server.proto",
diff --git a/command/server/service.go b/command/server/service.go
index 86ab38d5e..3b8b4d19e 100644
--- a/command/server/service.go
+++ b/command/server/service.go
@@ -3,9 +3,13 @@ package server
 import (
 	"context"
 	"encoding/hex"
+	"fmt"
+	"strings"
 
 	"github.com/ethereum/go-ethereum/command/server/pprof"
 	"github.com/ethereum/go-ethereum/command/server/proto"
+	"github.com/ethereum/go-ethereum/p2p"
+	"github.com/ethereum/go-ethereum/p2p/enode"
 )
 
 func (s *Server) Pprof(ctx context.Context, req *proto.PprofRequest) (*proto.PprofResponse, error) {
@@ -31,3 +35,70 @@ func (s *Server) Pprof(ctx context.Context, req *proto.PprofRequest) (*proto.Ppr
 	}
 	return resp, nil
 }
+
+func (s *Server) PeersAdd(ctx context.Context, req *proto.PeersAddRequest) (*proto.PeersAddResponse, error) {
+	node, err := enode.Parse(enode.ValidSchemes, req.Enode)
+	if err != nil {
+		return nil, fmt.Errorf("invalid enode: %v", err)
+	}
+	srv := s.node.Server()
+	if req.Trusted {
+		srv.AddTrustedPeer(node)
+	} else {
+		srv.AddPeer(node)
+	}
+	return &proto.PeersAddResponse{}, nil
+}
+
+func (s *Server) PeersRemove(ctx context.Context, req *proto.PeersRemoveRequest) (*proto.PeersRemoveResponse, error) {
+	node, err := enode.Parse(enode.ValidSchemes, req.Enode)
+	if err != nil {
+		return nil, fmt.Errorf("invalid enode: %v", err)
+	}
+	srv := s.node.Server()
+	if req.Trusted {
+		srv.RemoveTrustedPeer(node)
+	} else {
+		srv.RemovePeer(node)
+	}
+	return &proto.PeersRemoveResponse{}, nil
+}
+
+func (s *Server) PeersList(ctx context.Context, req *proto.PeersListRequest) (*proto.PeersListResponse, error) {
+	resp := &proto.PeersListResponse{}
+
+	peers := s.node.Server().PeersInfo()
+	for _, p := range peers {
+		resp.Peers = append(resp.Peers, peerInfoToPeer(p))
+	}
+	return resp, nil
+}
+
+func (s *Server) PeersStatus(ctx context.Context, req *proto.PeersStatusRequest) (*proto.PeersStatusResponse, error) {
+	var peerInfo *p2p.PeerInfo
+	for _, p := range s.node.Server().PeersInfo() {
+		if strings.HasPrefix(p.ID, req.Enode) {
+			if peerInfo != nil {
+				return nil, fmt.Errorf("more than one peer with the same prefix")
+			}
+			peerInfo = p
+		}
+	}
+	resp := &proto.PeersStatusResponse{}
+	if peerInfo != nil {
+		resp.Peer = peerInfoToPeer(peerInfo)
+	}
+	return resp, nil
+}
+
+func peerInfoToPeer(info *p2p.PeerInfo) *proto.Peer {
+	return &proto.Peer{
+		Id:      info.ID,
+		Enode:   info.Enode,
+		Enr:     info.ENR,
+		Caps:    info.Caps,
+		Name:    info.Name,
+		Trusted: info.Network.Trusted,
+		Static:  info.Network.Static,
+	}
+}
-- 
GitLab