diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 190fd57d2..e76e15177 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -39,6 +39,11 @@ web3._extend({ call: 'admin_addPeer', params: 1 }), + new web3._extend.Method({ + name: 'removePeer', + call: 'admin_removePeer', + params: 1 + }), new web3._extend.Method({ name: 'exportChain', call: 'admin_exportChain', diff --git a/node/api.go b/node/api.go index 9b2be9c2e..3523874ab 100644 --- a/node/api.go +++ b/node/api.go @@ -58,6 +58,22 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { return true, nil } +// RemovePeer disconnects from a a remote node if the connection exists +func (api *PrivateAdminAPI) RemovePeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + // Try to remove the url as a static peer and return + node, err := discover.ParseNode(url) + if err != nil { + return false, fmt.Errorf("invalid enode: %v", err) + } + server.RemovePeer(node) + return true, nil +} + // StartRPC starts the HTTP RPC API server. func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *string, apis *string) (bool, error) { api.node.lock.Lock() diff --git a/p2p/dial.go b/p2p/dial.go index c0e703d7d..691b8539e 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -121,6 +121,11 @@ func (s *dialstate) addStatic(n *discover.Node) { s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n} } +func (s *dialstate) removeStatic(n *discover.Node) { + // This removes a task so future attempts to connect will not be made. + delete(s.static, n.ID) +} + func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task { var newtasks []task isDialing := func(id discover.NodeID) bool { diff --git a/p2p/server.go b/p2p/server.go index 880aa7cf1..8e3cd93f9 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -142,6 +142,7 @@ type Server struct { quit chan struct{} addstatic chan *discover.Node + removestatic chan *discover.Node posthandshake chan *conn addpeer chan *conn delpeer chan *Peer @@ -257,6 +258,14 @@ func (srv *Server) AddPeer(node *discover.Node) { } } +// RemovePeer disconnects from the given node +func (srv *Server) RemovePeer(node *discover.Node) { + select { + case srv.removestatic <- node: + case <-srv.quit: + } +} + // Self returns the local node's endpoint information. func (srv *Server) Self() *discover.Node { srv.lock.Lock() @@ -327,6 +336,7 @@ func (srv *Server) Start() (err error) { srv.delpeer = make(chan *Peer) srv.posthandshake = make(chan *conn) srv.addstatic = make(chan *discover.Node) + srv.removestatic = make(chan *discover.Node) srv.peerOp = make(chan peerOpFunc) srv.peerOpDone = make(chan struct{}) @@ -395,6 +405,7 @@ type dialer interface { newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task taskDone(task, time.Time) addStatic(*discover.Node) + removeStatic(*discover.Node) } func (srv *Server) run(dialstate dialer) { @@ -458,6 +469,15 @@ running: // it will keep the node connected. glog.V(logger.Detail).Infoln("<-addstatic:", n) dialstate.addStatic(n) + case n := <-srv.removestatic: + // This channel is used by RemovePeer to send a + // disconnect request to a peer and begin the + // stop keeping the node connected + glog.V(logger.Detail).Infoln("<-removestatic:", n) + dialstate.removeStatic(n) + if p, ok := peers[n.ID]; ok { + p.Disconnect(DiscRequested) + } case op := <-srv.peerOp: // This channel is used by Peers and PeerCount. op(peers) diff --git a/p2p/server_test.go b/p2p/server_test.go index deb34f5bb..313d086ec 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -301,6 +301,8 @@ func (tg taskgen) taskDone(t task, now time.Time) { } func (tg taskgen) addStatic(*discover.Node) { } +func (tg taskgen) removeStatic(*discover.Node) { +} type testTask struct { index int