Fix and simplify RPC server error handling.

This change rewrites much of the error handling for the RPC server
components to match a more idiomatic Go error handling style as well as
fix several issues regarding error equality checks.

Closes #94.
This commit is contained in:
Josh Rickmar 2014-06-03 19:52:04 -05:00
parent 0cba485793
commit d863c75be7
8 changed files with 446 additions and 824 deletions

View file

@ -439,21 +439,19 @@ func (a *Account) Track() {
// Request notifications for transactions sending to all wallet // Request notifications for transactions sending to all wallet
// addresses. // addresses.
addrs := a.ActiveAddresses() addrs := a.ActiveAddresses()
addrstrs := make([]string, len(addrs)) addrstrs := make([]string, 0, len(addrs))
i := 0
for addr := range addrs { for addr := range addrs {
addrstrs[i] = addr.EncodeAddress() addrstrs = append(addrstrs, addr.EncodeAddress())
i++
} }
jsonErr := NotifyReceived(CurrentServerConn(), addrstrs) if err := NotifyReceived(CurrentServerConn(), addrstrs); err != nil {
if jsonErr != nil {
log.Error("Unable to request transaction updates for address.") log.Error("Unable to request transaction updates for address.")
} }
unspent, err := a.TxStore.UnspentOutputs() unspent, err := a.TxStore.UnspentOutputs()
if err != nil { if err != nil {
log.Errorf("Unable to access unspent outputs: %v", err) log.Errorf("Unable to access unspent outputs: %v", err)
return
} }
ReqSpentUtxoNtfns(unspent) ReqSpentUtxoNtfns(unspent)
} }
@ -621,11 +619,11 @@ func (a *Account) RecoverAddresses(n int) error {
// Run a goroutine to rescan blockchain for recovered addresses. // Run a goroutine to rescan blockchain for recovered addresses.
go func(addrs []string) { go func(addrs []string) {
jsonErr := Rescan(CurrentServerConn(), lastInfo.FirstBlock(), err := Rescan(CurrentServerConn(), lastInfo.FirstBlock(),
addrs, nil) addrs, nil)
if jsonErr != nil { if err != nil {
log.Errorf("Rescanning for recovered addresses failed: %v", log.Errorf("Rescanning for recovered addresses "+
jsonErr.Message) "failed: %v", err)
} }
}(addrStrs) }(addrStrs)
@ -660,7 +658,10 @@ func ReqSpentUtxoNtfns(credits []*txstore.Credit) {
ops = append(ops, op) ops = append(ops, op)
} }
NotifySpent(CurrentServerConn(), ops) if err := NotifySpent(CurrentServerConn(), ops); err != nil {
log.Errorf("Cannot request notifications for spent outputs: %v",
err)
}
} }
// TotalReceived iterates through an account's transaction history, returning the // TotalReceived iterates through an account's transaction history, returning the

View file

@ -473,7 +473,7 @@ func (am *AccountManager) rescanListener() {
case *RescanFinishedMsg: case *RescanFinishedMsg:
if e.Error != nil { if e.Error != nil {
log.Errorf("Rescan failed: %v", e.Error.Message) log.Errorf("Rescan failed: %v", e.Error)
break break
} }

11
cmd.go
View file

@ -54,11 +54,12 @@ func GetCurBlock() (wallet.BlockStamp, error) {
return bs, nil return bs, nil
} }
bb, jsonErr := GetBestBlock(CurrentServerConn()) bb, err := GetBestBlock(CurrentServerConn())
if jsonErr != nil { if err != nil {
return wallet.BlockStamp{ unknown := wallet.BlockStamp{
Height: int32(btcutil.BlockHeightUnknown), Height: int32(btcutil.BlockHeightUnknown),
}, jsonErr }
return unknown, err
} }
hash, err := btcwire.NewShaHashFromStr(bb.Hash) hash, err := btcwire.NewShaHashFromStr(bb.Hash)
@ -210,7 +211,7 @@ func main() {
// Perform handshake. // Perform handshake.
if err := Handshake(btcd); err != nil { if err := Handshake(btcd); err != nil {
var message string var message string
if jsonErr, ok := err.(*btcjson.Error); ok { if jsonErr, ok := err.(btcjson.Error); ok {
message = jsonErr.Message message = jsonErr.Message
} else { } else {
message = err.Error() message = err.Error()

View file

@ -17,7 +17,6 @@
package main package main
import ( import (
"github.com/conformal/btcjson"
"github.com/conformal/btcutil" "github.com/conformal/btcutil"
"github.com/conformal/btcwire" "github.com/conformal/btcwire"
) )
@ -51,7 +50,7 @@ func (r *RescanProgressMsg) ImplementsRescanMsg() {}
// possibly-finished rescan, or an error if the rescan failed. // possibly-finished rescan, or an error if the rescan failed.
type RescanFinishedMsg struct { type RescanFinishedMsg struct {
Addresses map[*Account][]btcutil.Address Addresses map[*Account][]btcutil.Address
Error *btcjson.Error Error error
} }
// ImplementsRescanMsg is implemented to satisify the RescanMsg // ImplementsRescanMsg is implemented to satisify the RescanMsg
@ -143,7 +142,7 @@ func (b *rescanBatch) merge(job *RescanJob) {
// Status types for the handler. // Status types for the handler.
type rescanProgress int32 type rescanProgress int32
type rescanFinished *btcjson.Error type rescanFinished error
// jobHandler runs the RescanManager's for-select loop to manage rescan jobs // jobHandler runs the RescanManager's for-select loop to manage rescan jobs
// and dispatch requests. // and dispatch requests.
@ -191,7 +190,7 @@ func (m *RescanManager) jobHandler() {
if m.msgs != nil { if m.msgs != nil {
m.msgs <- &RescanFinishedMsg{ m.msgs <- &RescanFinishedMsg{
Addresses: curBatch.addrs, Addresses: curBatch.addrs,
Error: (*btcjson.Error)(s), Error: error(s),
} }
} }
curBatch.done() curBatch.done()
@ -223,8 +222,8 @@ func (m *RescanManager) rpcHandler() {
} }
c := CurrentServerConn() c := CurrentServerConn()
jsonErr := Rescan(c, job.StartHeight, addrStrs, job.OutPoints) err := Rescan(c, job.StartHeight, addrStrs, job.OutPoints)
m.status <- rescanFinished(jsonErr) m.status <- rescanFinished(err)
} }
} }

10
rpc.go
View file

@ -34,13 +34,15 @@ type RawRPCResponse struct {
// to by the interface rather than using the rules in the encoding/json // to by the interface rather than using the rules in the encoding/json
// package to allocate a new variable for the result. The final result // package to allocate a new variable for the result. The final result
// and JSON-RPC error is returned. // and JSON-RPC error is returned.
func (r *RawRPCResponse) FinishUnmarshal(v interface{}) (interface{}, *btcjson.Error) { //
// If the returned error is non-nil, it will be a btcjson.Error.
func (r *RawRPCResponse) FinishUnmarshal(v interface{}) (interface{}, error) {
// JSON-RPC spec makes this handling easier-ish because both result and // JSON-RPC spec makes this handling easier-ish because both result and
// error cannot be non-nil. // error cannot be non-nil.
var jsonErr *btcjson.Error
if r.Error != nil { if r.Error != nil {
var jsonErr btcjson.Error
if err := json.Unmarshal([]byte(*r.Error), &jsonErr); err != nil { if err := json.Unmarshal([]byte(*r.Error), &jsonErr); err != nil {
return nil, &btcjson.Error{ return nil, btcjson.Error{
Code: btcjson.ErrParse.Code, Code: btcjson.ErrParse.Code,
Message: err.Error(), Message: err.Error(),
} }
@ -49,7 +51,7 @@ func (r *RawRPCResponse) FinishUnmarshal(v interface{}) (interface{}, *btcjson.E
} }
if r.Result != nil { if r.Result != nil {
if err := json.Unmarshal([]byte(*r.Result), &v); err != nil { if err := json.Unmarshal([]byte(*r.Result), &v); err != nil {
return nil, &btcjson.Error{ return nil, btcjson.Error{
Code: btcjson.ErrParse.Code, Code: btcjson.ErrParse.Code,
Message: err.Error(), Message: err.Error(),
} }

View file

@ -269,20 +269,19 @@ func unmarshalNotification(s string) (btcjson.Cmd, error) {
// GetBestBlock gets both the block height and hash of the best block // GetBestBlock gets both the block height and hash of the best block
// in the main chain. // in the main chain.
func GetBestBlock(rpc ServerConn) (*btcws.GetBestBlockResult, *btcjson.Error) { func GetBestBlock(rpc ServerConn) (*btcws.GetBestBlockResult, error) {
cmd := btcws.NewGetBestBlockCmd(<-NewJSONID) cmd := btcws.NewGetBestBlockCmd(<-NewJSONID)
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
var resultData btcws.GetBestBlockResult var resultData btcws.GetBestBlockResult
_, jsonErr := response.FinishUnmarshal(&resultData) if _, err := response.FinishUnmarshal(&resultData); err != nil {
if jsonErr != nil { return nil, err
return nil, jsonErr
} }
return &resultData, nil return &resultData, nil
} }
// GetBlock requests details about a block with the given hash. // GetBlock requests details about a block with the given hash.
func GetBlock(rpc ServerConn, blockHash string) (*btcjson.BlockResult, *btcjson.Error) { func GetBlock(rpc ServerConn, blockHash string) (*btcjson.BlockResult, error) {
// NewGetBlockCmd should never fail with no optargs. If this does fail, // NewGetBlockCmd should never fail with no optargs. If this does fail,
// panic now rather than later. // panic now rather than later.
cmd, err := btcjson.NewGetBlockCmd(<-NewJSONID, blockHash) cmd, err := btcjson.NewGetBlockCmd(<-NewJSONID, blockHash)
@ -292,64 +291,62 @@ func GetBlock(rpc ServerConn, blockHash string) (*btcjson.BlockResult, *btcjson.
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
var resultData btcjson.BlockResult var resultData btcjson.BlockResult
_, jsonErr := response.FinishUnmarshal(&resultData) if _, err := response.FinishUnmarshal(&resultData); err != nil {
if jsonErr != nil { return nil, err
return nil, jsonErr
} }
return &resultData, nil return &resultData, nil
} }
// GetCurrentNet requests the network a bitcoin RPC server is running on. // GetCurrentNet requests the network a bitcoin RPC server is running on.
func GetCurrentNet(rpc ServerConn) (btcwire.BitcoinNet, *btcjson.Error) { func GetCurrentNet(rpc ServerConn) (btcwire.BitcoinNet, error) {
cmd := btcws.NewGetCurrentNetCmd(<-NewJSONID) cmd := btcws.NewGetCurrentNetCmd(<-NewJSONID)
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
var resultData uint32 var resultData uint32
_, jsonErr := response.FinishUnmarshal(&resultData) if _, err := response.FinishUnmarshal(&resultData); err != nil {
if jsonErr != nil { return 0, err
return 0, jsonErr
} }
return btcwire.BitcoinNet(resultData), nil return btcwire.BitcoinNet(resultData), nil
} }
// NotifyBlocks requests blockconnected and blockdisconnected notifications. // NotifyBlocks requests blockconnected and blockdisconnected notifications.
func NotifyBlocks(rpc ServerConn) *btcjson.Error { func NotifyBlocks(rpc ServerConn) error {
cmd := btcws.NewNotifyBlocksCmd(<-NewJSONID) cmd := btcws.NewNotifyBlocksCmd(<-NewJSONID)
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
_, jsonErr := response.FinishUnmarshal(nil) _, err := response.FinishUnmarshal(nil)
return jsonErr return err
} }
// NotifyReceived requests notifications for new transactions that spend // NotifyReceived requests notifications for new transactions that spend
// to any of the addresses in addrs. // to any of the addresses in addrs.
func NotifyReceived(rpc ServerConn, addrs []string) *btcjson.Error { func NotifyReceived(rpc ServerConn, addrs []string) error {
cmd := btcws.NewNotifyReceivedCmd(<-NewJSONID, addrs) cmd := btcws.NewNotifyReceivedCmd(<-NewJSONID, addrs)
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
_, jsonErr := response.FinishUnmarshal(nil) _, err := response.FinishUnmarshal(nil)
return jsonErr return err
} }
// NotifySpent requests notifications for when a transaction is processed which // NotifySpent requests notifications for when a transaction is processed which
// spends op. // spends op.
func NotifySpent(rpc ServerConn, outpoints []*btcwire.OutPoint) *btcjson.Error { func NotifySpent(rpc ServerConn, outpoints []*btcwire.OutPoint) error {
ops := make([]btcws.OutPoint, 0, len(outpoints)) ops := make([]btcws.OutPoint, 0, len(outpoints))
for _, op := range outpoints { for _, op := range outpoints {
ops = append(ops, *btcws.NewOutPointFromWire(op)) ops = append(ops, *btcws.NewOutPointFromWire(op))
} }
cmd := btcws.NewNotifySpentCmd(<-NewJSONID, ops) cmd := btcws.NewNotifySpentCmd(<-NewJSONID, ops)
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
_, jsonErr := response.FinishUnmarshal(nil) _, err := response.FinishUnmarshal(nil)
return jsonErr return err
} }
// Rescan requests a blockchain rescan for transactions to any number of // Rescan requests a blockchain rescan for transactions to any number of
// addresses and notifications to inform wallet about such transactions. // addresses and notifications to inform wallet about such transactions.
func Rescan(rpc ServerConn, beginBlock int32, addrs []string, func Rescan(rpc ServerConn, beginBlock int32, addrs []string,
outpoints []*btcwire.OutPoint) *btcjson.Error { outpoints []*btcwire.OutPoint) error {
ops := make([]btcws.OutPoint, len(outpoints)) ops := make([]btcws.OutPoint, 0, len(outpoints))
for i := range outpoints { for _, op := range outpoints {
ops[i] = *btcws.NewOutPointFromWire(outpoints[i]) ops = append(ops, *btcws.NewOutPointFromWire(op))
} }
// NewRescanCmd should never fail with no optargs. If this does fail, // NewRescanCmd should never fail with no optargs. If this does fail,
// panic now rather than later. // panic now rather than later.
@ -358,12 +355,12 @@ func Rescan(rpc ServerConn, beginBlock int32, addrs []string,
panic(err) panic(err)
} }
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
_, jsonErr := response.FinishUnmarshal(nil) _, err = response.FinishUnmarshal(nil)
return jsonErr return err
} }
// SendRawTransaction sends a hex-encoded transaction for relay. // SendRawTransaction sends a hex-encoded transaction for relay.
func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjson.Error) { func SendRawTransaction(rpc ServerConn, hextx string) (txid string, err error) {
// NewSendRawTransactionCmd should never fail. In the exceptional case // NewSendRawTransactionCmd should never fail. In the exceptional case
// where it does, panic here rather than later. // where it does, panic here rather than later.
cmd, err := btcjson.NewSendRawTransactionCmd(<-NewJSONID, hextx) cmd, err := btcjson.NewSendRawTransactionCmd(<-NewJSONID, hextx)
@ -373,11 +370,8 @@ func SendRawTransaction(rpc ServerConn, hextx string) (txid string, error *btcjs
response := <-rpc.SendRequest(NewServerRequest(cmd)) response := <-rpc.SendRequest(NewServerRequest(cmd))
var resultData string var resultData string
_, jsonErr := response.FinishUnmarshal(&resultData) _, err = response.FinishUnmarshal(&resultData)
if jsonErr != nil { return resultData, err
return "", jsonErr
}
return resultData, nil
} }
// GetRawTransaction returns a future representing a pending GetRawTransaction // GetRawTransaction returns a future representing a pending GetRawTransaction
@ -396,22 +390,21 @@ func GetRawTransactionAsync(rpc ServerConn, txsha *btcwire.ShaHash) chan RawRPCR
// GetRawTransactionAsyncResult waits for the pending command in request - // GetRawTransactionAsyncResult waits for the pending command in request -
// the reqsult of a previous GetRawTransactionAsync() call - and returns either // the reqsult of a previous GetRawTransactionAsync() call - and returns either
// the requested transaction, or an error. // the requested transaction, or an error.
func GetRawTransactionAsyncResult(request chan RawRPCResponse) (*btcutil.Tx, func GetRawTransactionAsyncResult(request chan RawRPCResponse) (*btcutil.Tx, error) {
*btcjson.Error) {
response := <-request response := <-request
var resultData string var resultData string
_, jsonErr := response.FinishUnmarshal(&resultData) _, err := response.FinishUnmarshal(&resultData)
if jsonErr != nil { if err != nil {
return nil, jsonErr return nil, err
} }
serializedTx, err := hex.DecodeString(resultData) serializedTx, err := hex.DecodeString(resultData)
if err != nil { if err != nil {
return nil, &btcjson.ErrDecodeHexString return nil, btcjson.ErrDecodeHexString
} }
utx, err := btcutil.NewTxFromBytes(serializedTx) utx, err := btcutil.NewTxFromBytes(serializedTx)
if err != nil { if err != nil {
return nil, &btcjson.ErrDeserialization return nil, btcjson.ErrDeserialization
} }
return utx, nil return utx, nil
} }
@ -419,26 +412,7 @@ func GetRawTransactionAsyncResult(request chan RawRPCResponse) (*btcutil.Tx,
// GetRawTransaction sends the non-verbose version of a getrawtransaction // GetRawTransaction sends the non-verbose version of a getrawtransaction
// request to receive the serialized transaction referenced by txsha. If // request to receive the serialized transaction referenced by txsha. If
// successful, the transaction is decoded and returned as a btcutil.Tx. // successful, the transaction is decoded and returned as a btcutil.Tx.
func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, *btcjson.Error) { func GetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcutil.Tx, error) {
resp := GetRawTransactionAsync(rpc, txsha) resp := GetRawTransactionAsync(rpc, txsha)
return GetRawTransactionAsyncResult(resp) return GetRawTransactionAsyncResult(resp)
} }
// VerboseGetRawTransaction sends the verbose version of a getrawtransaction
// request to receive details about a transaction.
func VerboseGetRawTransaction(rpc ServerConn, txsha *btcwire.ShaHash) (*btcjson.TxRawResult, *btcjson.Error) {
// NewGetRawTransactionCmd should never fail with a single optarg. If
// it does, panic now rather than later.
cmd, err := btcjson.NewGetRawTransactionCmd(<-NewJSONID, txsha.String(), 1)
if err != nil {
panic(err)
}
response := <-rpc.SendRequest(NewServerRequest(cmd))
var resultData btcjson.TxRawResult
_, jsonErr := response.FinishUnmarshal(&resultData)
if jsonErr != nil {
return nil, jsonErr
}
return &resultData, nil
}

File diff suppressed because it is too large Load diff

View file

@ -212,23 +212,13 @@ func genCertPair(certFile, keyFile string) error {
return nil return nil
} }
// ParseRequest parses a command or notification out of a JSON-RPC request,
// returning any errors as a JSON-RPC error.
func ParseRequest(msg []byte) (btcjson.Cmd, *btcjson.Error) {
cmd, err := btcjson.ParseMarshaledCmd(msg)
if err != nil || cmd.Id() == nil {
return cmd, &btcjson.ErrInvalidRequest
}
return cmd, nil
}
// ReplyToFrontend responds to a marshaled JSON-RPC request with a // ReplyToFrontend responds to a marshaled JSON-RPC request with a
// marshaled JSON-RPC response for both standard and extension // marshaled JSON-RPC response for both standard and extension
// (websocket) clients. The returned error is ErrBadAuth if a // (websocket) clients. The returned error is ErrBadAuth if a
// missing, incorrect, or duplicate authentication request is // missing, incorrect, or duplicate authentication request is
// received. // received.
func (s *server) ReplyToFrontend(msg []byte, ws, authenticated bool) ([]byte, error) { func (s *server) ReplyToFrontend(msg []byte, ws, authenticated bool) ([]byte, error) {
cmd, jsonErr := ParseRequest(msg) cmd, parseErr := btcjson.ParseMarshaledCmd(msg)
var id interface{} var id interface{}
if cmd != nil { if cmd != nil {
id = cmd.Id() id = cmd.Id()
@ -259,10 +249,10 @@ func (s *server) ReplyToFrontend(msg []byte, ws, authenticated bool) ([]byte, er
return nil, nil return nil, nil
} }
if jsonErr != nil { if parseErr != nil {
response := btcjson.Reply{ response := btcjson.Reply{
Id: &id, Id: &id,
Error: jsonErr, Error: &btcjson.ErrInvalidRequest,
} }
mresponse, err := json.Marshal(response) mresponse, err := json.Marshal(response)
// We expect the marshal to succeed. If it doesn't, it // We expect the marshal to succeed. If it doesn't, it
@ -725,16 +715,18 @@ func BtcdConnect(certificates []byte) (*BtcdRPCConn, error) {
// single TrackSince function (or similar) which requests address // single TrackSince function (or similar) which requests address
// notifications and performs the rescan since some block height. // notifications and performs the rescan since some block height.
func Handshake(rpc ServerConn) error { func Handshake(rpc ServerConn) error {
net, jsonErr := GetCurrentNet(rpc) net, err := GetCurrentNet(rpc)
if jsonErr != nil { if err != nil {
return jsonErr return err
} }
if net != activeNet.Net { if net != activeNet.Net {
return errors.New("btcd and btcwallet running on different Bitcoin networks") return errors.New("btcd and btcwallet running on different Bitcoin networks")
} }
// Request notifications for connected and disconnected blocks. // Request notifications for connected and disconnected blocks.
NotifyBlocks(rpc) if err := NotifyBlocks(rpc); err != nil {
return err
}
// Get current best block. If this is before than the oldest // Get current best block. If this is before than the oldest
// saved block hash, assume that this btcd instance is not yet // saved block hash, assume that this btcd instance is not yet
@ -767,8 +759,7 @@ func Handshake(rpc ServerConn) error {
log.Debugf("Checking for previous saved block with height %v hash %v", log.Debugf("Checking for previous saved block with height %v hash %v",
bs.Height, bs.Hash) bs.Height, bs.Hash)
_, jsonErr := GetBlock(rpc, bs.Hash.String()) if _, err := GetBlock(rpc, bs.Hash.String()); err != nil {
if jsonErr != nil {
continue continue
} }