Split the massive switch statement in rpcserver into a map of handlers.

Cleans up a the code a lot and removes the need to be careful about
shadowing err.

Much rejoicing from jcv, jrick and davec.
This commit is contained in:
Owain G. Ainsworth 2013-10-29 15:42:34 +00:00
parent b96cb4317a
commit c242d75866

View file

@ -308,101 +308,62 @@ func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) {
log.Debugf(msg) log.Debugf(msg)
} }
// jsonRead abstracts the JSON unmarshalling and reply handling used type commandHandler func(*rpcServer, btcjson.Cmd, chan []byte) (interface{}, error)
// by both RPC and websockets. If called from websocket code, a non-nil
// wallet notification channel can be used to automatically register
// various notifications for the wallet.
func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply btcjson.Reply, err error) {
cmd, err := btcjson.ParseMarshaledCmd(body)
if err != nil {
jsonError := btcjson.ErrParse
reply = btcjson.Reply{ var handlers = map[string]commandHandler{
Result: nil, "decoderawtransaction": handleDecodeRawTransaction,
Error: &jsonError, "getbestblockhash": handleGetBestBlockHash,
Id: nil, "getblock": handleGetBlock,
} "getblockcount": handleGetBlockCount,
"getblockhash": handleGetBlockHash,
"getconnectioncount": handleGetConnectionCount,
"getdifficulty": handleGetDifficulty,
"getgenerate": handleGetGenerate,
"gethashespersec": handleGetHashesPerSec,
"getrawmempool": handleGetRawMempool,
"getrawtransaction": handleGetRawTransaction,
"sendrawtransaction": handleSendRawTransaction,
"setgenerate": handleSetGenerate,
"stop": handleStop,
}
log.Tracef("RPCS: reply: %v", reply) // handleDecodeRawTransaction handles decoderawtransaction commands.
func handleDecodeRawTransaction(s *rpcServer, cmd btcjson.Cmd,
return reply, jsonError walletNotification chan []byte) (interface{}, error) {
}
log.Tracef("RPCS: received: %v", cmd)
// Set final reply based on error if non-nil.
defer func() {
if err != nil {
id := cmd.Id()
if jsonErr, ok := err.(btcjson.Error); ok {
reply = btcjson.Reply{
Error: &jsonErr,
Id: &id,
}
err = errors.New(jsonErr.Message)
} else {
// In the case where we did not have a btcjson
// error to begin with, make a new one to send,
// but this really should not happen.
rawJSONError := btcjson.Error{
Code: -32603,
Message: err.Error(),
}
reply = btcjson.Reply{
Error: &rawJSONError,
Id: &id,
}
}
}
}()
id := cmd.Id()
// Deal with commands
switch c := cmd.(type) {
case *btcjson.DecodeRawTransactionCmd:
// TODO: use c.HexTx and fill result with info. // TODO: use c.HexTx and fill result with info.
txReply := btcjson.TxRawDecodeResult{} return btcjson.TxRawDecodeResult{}, nil
reply = btcjson.Reply{ }
Result: txReply,
Error: nil,
Id: &id,
}
case *btcjson.GetBestBlockHashCmd: // handleGetBestBlockHash implements the getbestblockhash command.
func handleGetBestBlockHash(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
var sha *btcwire.ShaHash var sha *btcwire.ShaHash
sha, _, err = s.server.db.NewestSha() sha, _, err := s.server.db.NewestSha()
if err != nil { if err != nil {
log.Errorf("RPCS: Error getting newest sha: %v", err) log.Errorf("RPCS: Error getting newest sha: %v", err)
err = btcjson.ErrBestBlockHash return nil, btcjson.ErrBestBlockHash
return
}
reply = btcjson.Reply{
Result: sha.String(),
Id: &id,
} }
case *btcjson.GetBlockCmd: return sha.String, nil
var sha *btcwire.ShaHash }
sha, err = btcwire.NewShaHashFromStr(c.Hash)
// handleGetBlock implements the getblock command.
func handleGetBlock(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
c := cmd.(*btcjson.GetBlockCmd)
sha, err := btcwire.NewShaHashFromStr(c.Hash)
if err != nil { if err != nil {
log.Errorf("RPCS: Error generating sha: %v", err) log.Errorf("RPCS: Error generating sha: %v", err)
err = btcjson.ErrBlockNotFound return nil, btcjson.ErrBlockNotFound
return
} }
var blk *btcutil.Block blk, err := s.server.db.FetchBlockBySha(sha)
blk, err = s.server.db.FetchBlockBySha(sha)
if err != nil { if err != nil {
log.Errorf("RPCS: Error fetching sha: %v", err) log.Errorf("RPCS: Error fetching sha: %v", err)
err = btcjson.ErrBlockNotFound return nil, btcjson.ErrBlockNotFound
return
} }
idx := blk.Height() idx := blk.Height()
var buf []byte buf, err := blk.Bytes()
buf, err = blk.Bytes()
if err != nil { if err != nil {
log.Errorf("RPCS: Error fetching block: %v", err) log.Errorf("RPCS: Error fetching block: %v", err)
err = btcjson.ErrBlockNotFound return nil, btcjson.ErrBlockNotFound
return
} }
txList, _ := blk.TxShas() txList, _ := blk.TxShas()
@ -412,12 +373,10 @@ func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply
txNames[i] = v.String() txNames[i] = v.String()
} }
var maxidx int64 _, maxidx, err := s.server.db.NewestSha()
_, maxidx, err = s.server.db.NewestSha()
if err != nil { if err != nil {
log.Errorf("RPCS: Cannot get newest sha: %v", err) log.Errorf("RPCS: Cannot get newest sha: %v", err)
err = btcjson.ErrBlockNotFound return nil, btcjson.ErrBlockNotFound
return
} }
blockHeader := &blk.MsgBlock().Header blockHeader := &blk.MsgBlock().Header
@ -442,121 +401,102 @@ func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply
shaNext, err = s.server.db.FetchBlockShaByHeight(int64(idx + 1)) shaNext, err = s.server.db.FetchBlockShaByHeight(int64(idx + 1))
if err != nil { if err != nil {
log.Errorf("RPCS: No next block: %v", err) log.Errorf("RPCS: No next block: %v", err)
err = btcjson.ErrBlockNotFound return nil, btcjson.ErrBlockNotFound
return
} }
blockReply.NextHash = shaNext.String() blockReply.NextHash = shaNext.String()
} }
reply = btcjson.Reply{ return blockReply, nil
Result: blockReply, }
Error: nil,
Id: &id,
}
case *btcjson.GetBlockCountCmd: // handleGetBlockCount implements the getblockcount command.
var maxidx int64 func handleGetBlockCount(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
_, maxidx, err = s.server.db.NewestSha() _, maxidx, err := s.server.db.NewestSha()
if err != nil { if err != nil {
log.Errorf("RPCS: Error getting newest sha: %v", err) log.Errorf("RPCS: Error getting newest sha: %v", err)
err = btcjson.ErrBlockCount return nil, btcjson.ErrBlockCount
return
}
reply = btcjson.Reply{
Result: maxidx,
Id: &id,
} }
case *btcjson.GetBlockHashCmd: return maxidx, nil
var sha *btcwire.ShaHash }
sha, err = s.server.db.FetchBlockShaByHeight(c.Index)
// handleGetBlockHash implements the getblockhash command.
func handleGetBlockHash(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
c := cmd.(*btcjson.GetBlockHashCmd)
sha, err := s.server.db.FetchBlockShaByHeight(c.Index)
if err != nil { if err != nil {
log.Errorf("[RCPS] Error getting block: %v", err) log.Errorf("[RCPS] Error getting block: %v", err)
err = btcjson.ErrOutOfRange return nil, btcjson.ErrOutOfRange
return
}
reply = btcjson.Reply{
Result: sha.String(),
Id: &id,
} }
case *btcjson.GetConnectionCountCmd: return sha.String(), nil
var count int }
reply = btcjson.Reply{ // handleGetConnectionCount implements the getconnectioncount command.
Result: count, func handleGetConnectionCount(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
Id: &id, // TODO fillmein.
} return 0, nil
}
case *btcjson.GetDifficultyCmd: // handleGetDifficulty implements the getdifficulty command.
var sha *btcwire.ShaHash func handleGetDifficulty(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
sha, _, err = s.server.db.NewestSha() sha, _, err := s.server.db.NewestSha()
if err != nil { if err != nil {
log.Errorf("RPCS: Error getting sha: %v", err) log.Errorf("RPCS: Error getting sha: %v", err)
err = btcjson.ErrDifficulty return nil, btcjson.ErrDifficulty
return
} }
var blk *btcutil.Block blk, err := s.server.db.FetchBlockBySha(sha)
blk, err = s.server.db.FetchBlockBySha(sha)
if err != nil { if err != nil {
log.Errorf("RPCS: Error getting block: %v", err) log.Errorf("RPCS: Error getting block: %v", err)
err = btcjson.ErrDifficulty return nil, btcjson.ErrDifficulty
return
} }
blockHeader := &blk.MsgBlock().Header blockHeader := &blk.MsgBlock().Header
reply = btcjson.Reply{
Result: getDifficultyRatio(blockHeader.Bits),
Id: &id,
}
case *btcjson.GetGenerateCmd: return getDifficultyRatio(blockHeader.Bits), nil
}
// handleGetGenerate implements the getgenerate command.
func handleGetGenerate(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
// btcd does not do mining so we can hardcode replies here. // btcd does not do mining so we can hardcode replies here.
reply = btcjson.Reply{ return false, nil
Result: false, }
Id: &id,
}
case *btcjson.GetHashesPerSecCmd: // handleGetHashesPerSec implements the gethashespersec command.
func handleGetHashesPerSec(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
// btcd does not do mining so we can hardcode replies here. // btcd does not do mining so we can hardcode replies here.
reply = btcjson.Reply{ return 0, nil
Result: 0, }
Id: &id,
}
case *btcjson.GetRawMempoolCmd: // handleGetRawMempool implements the getrawmempool command.
func handleGetRawMempool(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
hashes := s.server.txMemPool.TxShas() hashes := s.server.txMemPool.TxShas()
hashStrings := make([]string, len(hashes)) hashStrings := make([]string, len(hashes))
for i := 0; i < len(hashes); i++ { for i := 0; i < len(hashes); i++ {
hashStrings[i] = hashes[i].String() hashStrings[i] = hashes[i].String()
} }
reply = btcjson.Reply{ return hashStrings, nil
Result: hashStrings, }
Id: &id,
}
case *btcjson.GetRawTransactionCmd: // handleGetRawTransaction implements the getrawtransaction command.
func handleGetRawTransaction(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
c := cmd.(*btcjson.GetRawTransactionCmd)
if c.Verbose { if c.Verbose {
// TODO: check error code. tx is not checked before // TODO: check error code. tx is not checked before
// this point. // this point.
txSha, _ := btcwire.NewShaHashFromStr(c.Txid) txSha, _ := btcwire.NewShaHashFromStr(c.Txid)
var txS *btcwire.MsgTx txList, err := s.server.db.FetchTxBySha(txSha)
var txList []*btcdb.TxListReply
txList, err = s.server.db.FetchTxBySha(txSha)
if err != nil { if err != nil {
log.Errorf("RPCS: Error fetching tx: %v", err) log.Errorf("RPCS: Error fetching tx: %v", err)
err = btcjson.ErrNoTxInfo return nil, btcjson.ErrNoTxInfo
return
} }
lastTx := len(txList) - 1 lastTx := len(txList) - 1
txS = txList[lastTx].Tx txS := txList[lastTx].Tx
blksha := txList[lastTx].BlkSha blksha := txList[lastTx].BlkSha
var blk *btcutil.Block blk, err := s.server.db.FetchBlockBySha(blksha)
blk, err = s.server.db.FetchBlockBySha(blksha)
if err != nil { if err != nil {
log.Errorf("RPCS: Error fetching sha: %v", err) log.Errorf("RPCS: Error fetching sha: %v", err)
err = btcjson.ErrBlockNotFound return nil, btcjson.ErrBlockNotFound
return
} }
idx := blk.Height() idx := blk.Height()
@ -593,12 +533,10 @@ func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply
} }
} }
var maxidx int64 _, maxidx, err := s.server.db.NewestSha()
_, maxidx, err = s.server.db.NewestSha()
if err != nil { if err != nil {
log.Errorf("RPCS: Cannot get newest sha: %v", err) log.Errorf("RPCS: Cannot get newest sha: %v", err)
err = btcjson.ErrNoNewestBlockInfo return nil, btcjson.ErrNoNewestBlockInfo
return
} }
confirmations := uint64(1 + maxidx - idx) confirmations := uint64(1 + maxidx - idx)
@ -616,33 +554,30 @@ func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply
BlockHash: blksha.String(), BlockHash: blksha.String(),
Confirmations: confirmations, Confirmations: confirmations,
} }
reply = btcjson.Reply{ return txReply, nil
Result: txReply,
Error: nil,
Id: &id,
}
} else { } else {
// Don't return details // Don't return details
// not used yet // not used yet
return nil, nil
} }
}
case *btcjson.SendRawTransactionCmd: // handleSendRawTransaction implements the sendrawtransaction command.
func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
c := cmd.(*btcjson.SendRawTransactionCmd)
// Deserialize and send off to tx relay // Deserialize and send off to tx relay
var serializedTx []byte serializedTx, err := hex.DecodeString(c.HexTx)
serializedTx, err = hex.DecodeString(c.HexTx)
if err != nil { if err != nil {
err = btcjson.ErrDecodeHexString return nil, btcjson.ErrDecodeHexString
return
} }
msgtx := btcwire.NewMsgTx() msgtx := btcwire.NewMsgTx()
err = msgtx.Deserialize(bytes.NewBuffer(serializedTx)) err = msgtx.Deserialize(bytes.NewBuffer(serializedTx))
if err != nil { if err != nil {
err = btcjson.Error{ err := btcjson.Error{
Code: -22, Code: -22,
Message: "Unable to deserialize raw tx", Message: "Unable to deserialize raw tx",
} }
return return nil, err
} }
tx := btcutil.NewTx(msgtx) tx := btcutil.NewTx(msgtx)
err = s.server.txMemPool.ProcessTransaction(tx) err = s.server.txMemPool.ProcessTransaction(tx)
@ -652,12 +587,10 @@ func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply
Code: -22, Code: -22,
Message: "Failed to process transaction", Message: "Failed to process transaction",
} }
return return nil, err
} }
var result interface{}
txsha := tx.Sha() txsha := tx.Sha()
result = txsha.String()
// If called from websocket code, add a mined tx hashes // If called from websocket code, add a mined tx hashes
// request. // request.
@ -665,27 +598,46 @@ func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply
s.ws.requests.AddMinedTxRequest(walletNotification, txsha) s.ws.requests.AddMinedTxRequest(walletNotification, txsha)
} }
reply = btcjson.Reply{ return txsha.String(), nil
Result: result, }
Error: nil,
Id: &id,
}
case *btcjson.SetGenerateCmd: // handleSetGenerate implements the setgenerate command.
func handleSetGenerate(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
// btcd does not do mining so we can hardcode replies here. // btcd does not do mining so we can hardcode replies here.
return nil, nil
}
// handleStop implements the stop command.
func handleStop(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
s.server.Stop()
return "btcd stopping.", nil
}
// jsonRead abstracts the JSON unmarshalling and reply handling used
// by both RPC and websockets. If called from websocket code, a non-nil
// wallet notification channel can be used to automatically register
// various notifications for the wallet.
func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply btcjson.Reply, err error) {
cmd, err := btcjson.ParseMarshaledCmd(body)
if err != nil {
jsonError := btcjson.ErrParse
reply = btcjson.Reply{ reply = btcjson.Reply{
Result: nil, Result: nil,
Id: &id, Error: &jsonError,
Id: nil,
} }
case *btcjson.StopCmd: log.Tracef("RPCS: reply: %v", reply)
reply = btcjson.Reply{
Result: "btcd stopping.",
Id: &id,
}
s.server.Stop()
default: return reply, jsonError
}
log.Tracef("RPCS: received: %v", cmd)
id := cmd.Id()
handler, ok := handlers[cmd.Method()]
if !ok {
jsonError := btcjson.Error{ jsonError := btcjson.Error{
Code: -32601, Code: -32601,
Message: "Method not found", Message: "Method not found",
@ -695,10 +647,38 @@ func jsonRead(body []byte, s *rpcServer, walletNotification chan []byte) (reply
Error: &jsonError, Error: &jsonError,
Id: &id, Id: &id,
} }
err = ErrMethodNotImplemented return reply, ErrMethodNotImplemented
} }
return result, err := handler(s, cmd, walletNotification)
if err != nil {
if jsonErr, ok := err.(btcjson.Error); ok {
reply = btcjson.Reply{
Error: &jsonErr,
Id: &id,
}
err = errors.New(jsonErr.Message)
} else {
// In the case where we did not have a btcjson
// error to begin with, make a new one to send,
// but this really should not happen.
rawJSONError := btcjson.Error{
Code: -32603,
Message: err.Error(),
}
reply = btcjson.Reply{
Error: &rawJSONError,
Id: &id,
}
}
} else {
reply = btcjson.Reply{
Result: result,
Id: &id,
}
}
return reply, err
} }
func jsonWSRead(walletNotification chan []byte, replychan chan *btcjson.Reply, body []byte, s *rpcServer) error { func jsonWSRead(walletNotification chan []byte, replychan chan *btcjson.Reply, body []byte, s *rpcServer) error {