add support for the ping rpc command.

And the pingtime and pingwait fields of getpeerinfo.
This commit is contained in:
Owain G. Ainsworth 2014-01-21 15:03:15 +00:00
parent 1487a352da
commit 9cb5190ac2
4 changed files with 100 additions and 37 deletions

40
peer.go
View file

@ -162,6 +162,10 @@ type peer struct {
blockProcessed chan bool blockProcessed chan bool
quit chan bool quit chan bool
userAgent string userAgent string
pingStatsMtx sync.Mutex // protects lastPing*
lastPingNonce uint64 // Set to nonce if we have a pending ping.
lastPingTime time.Time // Time we sent last ping.
lastPingMicros int64 // Time for last ping to return.
} }
// String returns the peer's address and directionality as a human-readable // String returns the peer's address and directionality as a human-readable
@ -921,6 +925,30 @@ func (p *peer) handlePingMsg(msg *btcwire.MsgPing) {
} }
} }
// handlePongMsg is invoked when a peer recieved a pong bitcoin message.
// recent clients (protocol version > BIP0031Version), and if we had send a ping
// previosuly we update our ping time statistics. If the client is too old or
// we had not send a ping we ignore it.
func (p *peer) handlePongMsg(msg *btcwire.MsgPong) {
p.pingStatsMtx.Lock()
defer p.pingStatsMtx.Unlock()
// Arguably we could use a buffered channel here sending data
// in a fifo manner whenever we send a ping, or a list keeping track of
// the times of each ping. For now we just make a best effort and
// only record stats if it was for the last ping sent. Any preceding
// and overlapping pings will be ignored. It is unlikely to occur
// without large usage of the ping rpc call since we ping
// infrequently enough that if they overlap we would have timed out
// the peer.
if p.protocolVersion > btcwire.BIP0031Version &&
p.lastPingNonce != 0 && msg.Nonce == p.lastPingNonce {
p.lastPingMicros = time.Now().Sub(p.lastPingTime).Nanoseconds()
p.lastPingMicros /= 1000 // convert to usec.
p.lastPingNonce = 0
}
}
// readMessage reads the next bitcoin message from the peer with logging. // readMessage reads the next bitcoin message from the peer with logging.
func (p *peer) readMessage() (msg btcwire.Message, buf []byte, err error) { func (p *peer) readMessage() (msg btcwire.Message, buf []byte, err error) {
msg, buf, err = btcwire.ReadMessage(p.conn, p.protocolVersion, p.btcnet) msg, buf, err = btcwire.ReadMessage(p.conn, p.protocolVersion, p.btcnet)
@ -1092,8 +1120,7 @@ out:
markConnected = true markConnected = true
case *btcwire.MsgPong: case *btcwire.MsgPong:
// Don't do anything, but could try to work out network p.handlePongMsg(msg)
// timing or similar.
case *btcwire.MsgAlert: case *btcwire.MsgAlert:
p.server.BroadcastMessage(msg, p) p.server.BroadcastMessage(msg, p)
@ -1324,13 +1351,20 @@ out:
// specially. // specially.
peerLog.Tracef("%s: recieved from queuehandler", p) peerLog.Tracef("%s: recieved from queuehandler", p)
reset := true reset := true
switch msg.msg.(type) { switch m := msg.msg.(type) {
case *btcwire.MsgVersion: case *btcwire.MsgVersion:
// should get an ack // should get an ack
case *btcwire.MsgGetAddr: case *btcwire.MsgGetAddr:
// should get addresses // should get addresses
case *btcwire.MsgPing: case *btcwire.MsgPing:
// expects pong // expects pong
// Also set up statistics.
p.pingStatsMtx.Lock()
if p.protocolVersion > btcwire.BIP0031Version {
p.lastPingNonce = m.Nonce
p.lastPingTime = time.Now()
}
p.pingStatsMtx.Unlock()
case *btcwire.MsgMemPool: case *btcwire.MsgMemPool:
// Should return an inv. // Should return an inv.
case *btcwire.MsgGetData: case *btcwire.MsgGetData:

View file

@ -49,28 +49,29 @@ type commandHandler func(*rpcServer, btcjson.Cmd) (interface{}, error)
// a dependancy loop. // a dependancy loop.
var rpcHandlers map[string]commandHandler var rpcHandlers map[string]commandHandler
var rpcHandlersBeforeInit = map[string]commandHandler{ var rpcHandlersBeforeInit = map[string]commandHandler{
"addnode": handleAddNode, "addnode": handleAddNode,
"createrawtransaction": handleCreateRawTransaction, "createrawtransaction": handleCreateRawTransaction,
"debuglevel": handleDebugLevel, "debuglevel": handleDebugLevel,
"decoderawtransaction": handleDecodeRawTransaction, "decoderawtransaction": handleDecodeRawTransaction,
"decodescript": handleDecodeScript, "decodescript": handleDecodeScript,
"getbestblockhash": handleGetBestBlockHash, "getbestblockhash": handleGetBestBlockHash,
"getblock": handleGetBlock, "getblock": handleGetBlock,
"getblockcount": handleGetBlockCount, "getblockcount": handleGetBlockCount,
"getblockhash": handleGetBlockHash, "getblockhash": handleGetBlockHash,
"getconnectioncount": handleGetConnectionCount, "getconnectioncount": handleGetConnectionCount,
"getdifficulty": handleGetDifficulty, "getdifficulty": handleGetDifficulty,
"getgenerate": handleGetGenerate, "getgenerate": handleGetGenerate,
"gethashespersec": handleGetHashesPerSec, "gethashespersec": handleGetHashesPerSec,
"getpeerinfo": handleGetPeerInfo, "getpeerinfo": handleGetPeerInfo,
"getrawmempool": handleGetRawMempool, "getrawmempool": handleGetRawMempool,
"getrawtransaction": handleGetRawTransaction, "getrawtransaction": handleGetRawTransaction,
"help": handleHelp, "help": handleHelp,
"sendrawtransaction": handleSendRawTransaction, "ping": handlePing,
"setgenerate": handleSetGenerate, "sendrawtransaction": handleSendRawTransaction,
"stop": handleStop, "setgenerate": handleSetGenerate,
"submitblock": handleSubmitBlock, "stop": handleStop,
"verifychain": handleVerifyChain, "submitblock": handleSubmitBlock,
"verifychain": handleVerifyChain,
} }
func init() { func init() {
@ -126,15 +127,14 @@ var rpcAskWallet = map[string]bool{
// Commands that are temporarily unimplemented. // Commands that are temporarily unimplemented.
var rpcUnimplemented = map[string]bool{ var rpcUnimplemented = map[string]bool{
"getaddednodeinfo": true, "getaddednodeinfo": true,
"getblocktemplate": true, "getblocktemplate": true,
"getinfo": true, "getinfo": true,
"getmininginfo": true, "getmininginfo": true,
"getnettotals": true, "getnettotals": true,
"getnetworkhashps": true, "getnetworkhashps": true,
"getnewaddress": true, "getnewaddress": true,
"getwork": true, "getwork": true,
"ping": true,
} }
// rpcServer holds the items the rpc server may need to access (config, // rpcServer holds the items the rpc server may need to access (config,
@ -1037,7 +1037,7 @@ NOTE: btcd does not mine so this will always return false. The call is provided
for compatibility only.`, for compatibility only.`,
"getpeerinfo": ` "getpeerinfo": `
NOTE: btcd does not currently implement all fields. the "bytessent", NOTE: btcd does not currently implement all fields. the "bytessent",
"bytesrecv", "pingtime", "pingwait" and "syncnode" fields are not yet "bytesrecv" and "syncnode" fields are not yet
implemented.`, implemented.`,
"sendrawtransaction": ` "sendrawtransaction": `
NOTE: btcd does not currently support the "allowhighfees" parameter.`, NOTE: btcd does not currently support the "allowhighfees" parameter.`,
@ -1085,13 +1085,26 @@ func handleHelp(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
// search the main list of hanlders since we do not wish to provide help // search the main list of hanlders since we do not wish to provide help
// for commands that are unimplemented or relate to wallet // for commands that are unimplemented or relate to wallet
// functionality. // functionality.
if _, ok := rpcHandlers[help.Command]; !ok { if _, ok := rpcHandlers[help.Command]; !ok {
return "", fmt.Errorf("help: unknown command: %s", help.Command) return "", fmt.Errorf("help: unknown command: %s", help.Command)
} }
return getHelpText(help.Command) return getHelpText(help.Command)
} }
// handlePing implements the ping command.
func handlePing(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
// Ask server to ping \o_
nonce, err := btcwire.RandomUint64()
if err != nil {
return nil, fmt.Errorf("Not sending ping - can not generate "+
"nonce: %v", err)
}
s.server.BroadcastMessage(btcwire.NewMsgPing(nonce))
return nil, nil
}
// handleSendRawTransaction implements the sendrawtransaction command. // handleSendRawTransaction implements the sendrawtransaction command.
func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
c := cmd.(*btcjson.SendRawTransactionCmd) c := cmd.(*btcjson.SendRawTransactionCmd)

View file

@ -266,6 +266,8 @@ type PeerInfo struct {
LastRecv int64 `json:"lastrecv"` LastRecv int64 `json:"lastrecv"`
BytesSent int `json:"bytessent"` BytesSent int `json:"bytessent"`
BytesRecv int `json:"bytesrecv"` BytesRecv int `json:"bytesrecv"`
PingTime int64 `json:"pingtime"`
PingWait int64 `json:"pingwait,omitempty"`
ConnTime int64 `json:"conntime"` ConnTime int64 `json:"conntime"`
Version uint32 `json:"version"` Version uint32 `json:"version"`
SubVer string `json:"subver"` SubVer string `json:"subver"`
@ -333,6 +335,14 @@ func (s *server) handleQuery(querymsg interface{}, state *peerState) {
BanScore: 0, BanScore: 0,
SyncNode: false, // TODO(oga) for now. bm knows this. SyncNode: false, // TODO(oga) for now. bm knows this.
} }
p.pingStatsMtx.Lock()
info.PingTime = p.lastPingMicros
if p.lastPingNonce != 0 {
wait := time.Now().Sub(p.lastPingTime).Nanoseconds()
// We actually want microseconds.
info.PingWait = wait / 1000
}
p.pingStatsMtx.Unlock()
infos = append(infos, info) infos = append(infos, info)
}) })
msg.reply <- infos msg.reply <- infos

View file

@ -64,9 +64,10 @@ var commandHandlers = map[string]*handlerData{
"getpeerinfo": &handlerData{0, 0, displayJSONDump, nil, makeGetPeerInfo, ""}, "getpeerinfo": &handlerData{0, 0, displayJSONDump, nil, makeGetPeerInfo, ""},
"getrawmempool": &handlerData{0, 1, displayJSONDump, []conversionHandler{toBool}, makeGetRawMempool, "[verbose=false]"}, "getrawmempool": &handlerData{0, 1, displayJSONDump, []conversionHandler{toBool}, makeGetRawMempool, "[verbose=false]"},
"getrawtransaction": &handlerData{1, 1, displayJSONDump, []conversionHandler{nil, toBool}, makeGetRawTransaction, "<txhash> [verbose=false]"}, "getrawtransaction": &handlerData{1, 1, displayJSONDump, []conversionHandler{nil, toBool}, makeGetRawTransaction, "<txhash> [verbose=false]"},
"help": &handlerData{0, 1, displayGeneric, nil, makeHelp, "[commandName]"}, "help": &handlerData{0, 1, displayGeneric, nil, makeHelp, "[commandName]"},
"importprivkey": &handlerData{1, 2, displayGeneric, []conversionHandler{nil, nil, toBool}, makeImportPrivKey, "<wifprivkey> [label] [rescan=true]"}, "importprivkey": &handlerData{1, 2, displayGeneric, []conversionHandler{nil, nil, toBool}, makeImportPrivKey, "<wifprivkey> [label] [rescan=true]"},
"listtransactions": &handlerData{0, 3, displayJSONDump, []conversionHandler{nil, toInt, toInt}, makeListTransactions, "[account] [count=10] [from=0]"}, "listtransactions": &handlerData{0, 3, displayJSONDump, []conversionHandler{nil, toInt, toInt}, makeListTransactions, "[account] [count=10] [from=0]"},
"ping": &handlerData{0, 0, displayGeneric, nil, makePing, ""},
"verifychain": &handlerData{0, 2, displayJSONDump, []conversionHandler{toInt, toInt}, makeVerifyChain, "[level] [numblocks]"}, "verifychain": &handlerData{0, 2, displayJSONDump, []conversionHandler{toInt, toInt}, makeVerifyChain, "[level] [numblocks]"},
"sendrawtransaction": &handlerData{1, 0, displayGeneric, nil, makeSendRawTransaction, "<hextx>"}, "sendrawtransaction": &handlerData{1, 0, displayGeneric, nil, makeSendRawTransaction, "<hextx>"},
"stop": &handlerData{0, 0, displayGeneric, nil, makeStop, ""}, "stop": &handlerData{0, 0, displayGeneric, nil, makeStop, ""},
@ -353,6 +354,11 @@ func makeListTransactions(args []interface{}) (btcjson.Cmd, error) {
return btcjson.NewListTransactionsCmd("btcctl", optargs...) return btcjson.NewListTransactionsCmd("btcctl", optargs...)
} }
// makePing generates the cmd structure for ping commands.
func makePing(args []interface{}) (btcjson.Cmd, error) {
return btcjson.NewPingCmd("btcctl")
}
// makeSendRawTransaction generates the cmd structure for sendrawtransaction // makeSendRawTransaction generates the cmd structure for sendrawtransaction
// commands. // commands.
func makeSendRawTransaction(args []interface{}) (btcjson.Cmd, error) { func makeSendRawTransaction(args []interface{}) (btcjson.Cmd, error) {