// TestRpcReply tests JsonGetRaw by sending both a good and a bad buffer // to it. func TestRpcReply(t *testing.T) { buffer := new(bytes.Buffer) buffer2 := ioutil.NopCloser(buffer) _, err := btcjson.GetRaw(buffer2) if err != nil { t.Errorf("Error reading rpc reply.") } failBuf := &FailingReadCloser{} _, err = btcjson.GetRaw(failBuf) if err == nil { t.Errorf("Error, this should fail.") } return }
// handleSendPostMessage handles performing the passed HTTP request, reading the // result, unmarshalling it, and delivering the unmarhsalled result to the // provided response channel. func (c *Client) handleSendPostMessage(details *sendPostDetails) { // Post the request. cmd := details.command log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) httpResponse, err := c.httpClient.Do(details.request) if err != nil { details.responseChan <- &futureResult{reply: nil, err: err} return } // Read the raw bytes and close the response. respBytes, err := btcjson.GetRaw(httpResponse.Body) if err != nil { details.responseChan <- &futureResult{reply: nil, err: err} return } // Unmarshal the reply into a concrete result if possible. reply, err := btcjson.ReadResultCmd(cmd.Method(), respBytes) if err != nil { details.responseChan <- &futureResult{reply: nil, err: err} return } details.responseChan <- &futureResult{reply: &reply, err: nil} }
// jsonRPCRead is the RPC wrapper around the jsonRead function to handles // reading and responding to RPC messages. func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) { r.Close = true if atomic.LoadInt32(&s.shutdown) != 0 { return } body, err := btcjson.GetRaw(r.Body) if err != nil { log.Errorf("RPCS: Error getting json message: %v", err) return } // Error is intentionally ignored here. It's used in in the // websocket handler to tell when a method is not supported by // the standard RPC API, and is not needed here. Error logging // is done inside jsonRead, so no need to log the error here. reply, _ := jsonRead(body, s, nil) log.Tracef("[RPCS] reply: %v", reply) msg, err := btcjson.MarshallAndSend(reply, w) if err != nil { log.Errorf(msg) return } log.Debugf(msg) }
// jsonRPCRead is the RPC wrapper around the jsonRead function to handle reading // and responding to RPC messages. func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) { if atomic.LoadInt32(&s.shutdown) != 0 { return } body, err := btcjson.GetRaw(r.Body) if err != nil { rpcsLog.Errorf("Error getting json message: %v", err) return } var reply btcjson.Reply cmd, jsonErr := parseCmd(body) if cmd != nil { // Unmarshaling at least a valid JSON-RPC message succeeded. // Use the provided id for errors. id := cmd.Id() reply.Id = &id } if jsonErr != nil { reply.Error = jsonErr } else { reply = standardCmdReply(cmd, s) } rpcsLog.Tracef("reply: %v", reply) msg, err := btcjson.MarshallAndSend(reply, w) if err != nil { rpcsLog.Errorf(msg) return } rpcsLog.Debugf(msg) }
// handleSendPostMessage handles performing the passed HTTP request, reading the // result, unmarshalling it, and delivering the unmarhsalled result to the // provided response channel. func (c *Client) handleSendPostMessage(details *sendPostDetails) { // Post the request. cmd := details.command log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) httpResponse, err := c.httpClient.Do(details.request) if err != nil { details.responseChan <- &response{err: err} return } // Read the raw bytes and close the response. respBytes, err := btcjson.GetRaw(httpResponse.Body) if err != nil { details.responseChan <- &response{err: err} return } // Handle unsuccessful HTTP responses if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 { details.responseChan <- &response{err: errors.New(string(respBytes))} return } var resp rawResponse err = json.Unmarshal(respBytes, &resp) if err != nil { details.responseChan <- &response{err: err} return } res, err := resp.result() details.responseChan <- &response{result: res, err: err} }
// ServeRPCRequest processes and replies to a JSON-RPC client request. func (s *server) ServeRPCRequest(w http.ResponseWriter, r *http.Request) { body, err := btcjson.GetRaw(r.Body) if err != nil { log.Errorf("RPCS: Error getting JSON message: %v", err) } resp := ReplyToFrontend(body, false) if _, err := w.Write(resp); err != nil { log.Warnf("RPCS: could not respond to RPC request: %v", err) } }
// ServeRPCRequest processes and replies to a JSON-RPC client request. func (s *server) ServeRPCRequest(w http.ResponseWriter, r *http.Request) { body, err := btcjson.GetRaw(r.Body) if err != nil { log.Errorf("RPCS: Error getting JSON message: %v", err) } resp, err := s.ReplyToFrontend(body, false, true) if err == ErrBadAuth { http.Error(w, "401 Unauthorized.", http.StatusUnauthorized) return } if _, err := w.Write(resp); err != nil { log.Warnf("RPCS: could not respond to RPC request: %v", err) } }
// handleRPCRequest processes a JSON-RPC request from a frontend. func (s *server) handleRPCRequest(w http.ResponseWriter, r *http.Request) { body, err := btcjson.GetRaw(r.Body) if err != nil { log.Errorf("RPCS: Error getting JSON message: %v", err) } response := ProcessFrontendRequest(body, false) mresponse, err := json.Marshal(response) if err != nil { id := response.Id response = &btcjson.Reply{ Id: id, Error: &btcjson.ErrInternal, } mresponse, _ = json.Marshal(response) } if _, err := w.Write(mresponse); err != nil { log.Warnf("RPCS: could not respond to RPC request: %v", err) } }
// jsonRPCRead is the main function that handles reading messages, getting // the data the message requests, and writing the reply. func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) { _ = spew.Dump r.Close = true if atomic.LoadInt32(&s.shutdown) != 0 { return } var rawReply btcjson.Reply body, err := btcjson.GetRaw(r.Body) if err != nil { log.Errorf("[RPCS] Error getting json message: %v", err) return } var message btcjson.Message err = json.Unmarshal(body, &message) if err != nil { log.Errorf("[RPCS] Error unmarshalling json message: %v", err) jsonError := btcjson.Error{ Code: -32700, Message: "Parse error", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: nil, } log.Tracef("[RPCS] reply: %v", rawReply) msg, err := btcjson.MarshallAndSend(rawReply, w) if err != nil { log.Errorf(msg) return } log.Debugf(msg) return } log.Tracef("[RPCS] received: %v", message) // Deal with commands switch message.Method { case "stop": rawReply = btcjson.Reply{ Result: "btcd stopping.", Error: nil, Id: &message.Id, } s.server.Stop() case "getblockcount": _, maxidx, _ := s.server.db.NewestSha() rawReply = btcjson.Reply{ Result: maxidx, Error: nil, Id: &message.Id, } // btcd does not do mining so we can hardcode replies here. case "getgenerate": rawReply = btcjson.Reply{ Result: false, Error: nil, Id: &message.Id, } case "setgenerate": rawReply = btcjson.Reply{ Result: nil, Error: nil, Id: &message.Id, } case "gethashespersec": rawReply = btcjson.Reply{ Result: 0, Error: nil, Id: &message.Id, } case "getblockhash": var f interface{} err = json.Unmarshal(body, &f) m := f.(map[string]interface{}) var idx float64 for _, v := range m { switch vv := v.(type) { case []interface{}: for _, u := range vv { idx, _ = u.(float64) } default: } } sha, err := s.server.db.FetchBlockShaByHeight(int64(idx)) if err != nil { log.Errorf("[RCPS] Error getting block: %v", err) jsonError := btcjson.Error{ Code: -1, Message: "Block number out of range.", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } log.Tracef("[RPCS] reply: %v", rawReply) break } rawReply = btcjson.Reply{ Result: sha.String(), Error: nil, Id: &message.Id, } case "getblock": var f interface{} err = json.Unmarshal(body, &f) m := f.(map[string]interface{}) var hash string for _, v := range m { switch vv := v.(type) { case []interface{}: for _, u := range vv { hash, _ = u.(string) } default: } } sha, err := btcwire.NewShaHashFromStr(hash) if err != nil { log.Errorf("[RPCS] Error generating sha: %v", err) jsonError := btcjson.Error{ Code: -5, Message: "Block not found", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } log.Tracef("[RPCS] reply: %v", rawReply) break } blk, err := s.server.db.FetchBlockBySha(sha) if err != nil { log.Errorf("[RPCS] Error fetching sha: %v", err) jsonError := btcjson.Error{ Code: -5, Message: "Block not found", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } log.Tracef("[RPCS] reply: %v", rawReply) break } idx := blk.Height() buf, err := blk.Bytes() if err != nil { log.Errorf("[RPCS] Error fetching block: %v", err) jsonError := btcjson.Error{ Code: -5, Message: "Block not found", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } log.Tracef("[RPCS] reply: %v", rawReply) break } txList, _ := blk.TxShas() txNames := make([]string, len(txList)) for i, v := range txList { txNames[i] = v.String() } _, maxidx, err := s.server.db.NewestSha() if err != nil { log.Errorf("[RPCS] Cannot get newest sha: %v", err) return } blockHeader := &blk.MsgBlock().Header blockReply := btcjson.BlockResult{ Hash: hash, Version: blockHeader.Version, MerkleRoot: blockHeader.MerkleRoot.String(), PreviousHash: blockHeader.PrevBlock.String(), Nonce: blockHeader.Nonce, Time: blockHeader.Timestamp.Unix(), Confirmations: uint64(1 + maxidx - idx), Height: idx, Tx: txNames, Size: len(buf), Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), Difficulty: getDifficultyRatio(blockHeader.Bits), } // Get next block unless we are already at the top. if idx < maxidx { shaNext, err := s.server.db.FetchBlockShaByHeight(int64(idx + 1)) if err != nil { log.Errorf("[RPCS] No next block: %v", err) } else { blockReply.NextHash = shaNext.String() } } rawReply = btcjson.Reply{ Result: blockReply, Error: nil, Id: &message.Id, } case "getrawtransaction": var f interface{} err = json.Unmarshal(body, &f) m := f.(map[string]interface{}) var tx string var verbose float64 for _, v := range m { switch vv := v.(type) { case []interface{}: for _, u := range vv { switch uu := u.(type) { case string: tx = uu case float64: verbose = uu default: } } default: } } if int(verbose) != 1 { // Don't return details // not used yet } else { txSha, _ := btcwire.NewShaHashFromStr(tx) var txS *btcwire.MsgTx txList, err := s.server.db.FetchTxBySha(txSha) if err != nil { log.Errorf("[RPCS] Error fetching tx: %v", err) jsonError := btcjson.Error{ Code: -5, Message: "No information available about transaction", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } log.Tracef("[RPCS] reply: %v", rawReply) break } lastTx := len(txList) - 1 txS = txList[lastTx].Tx blksha := txList[lastTx].BlkSha blk, err := s.server.db.FetchBlockBySha(blksha) if err != nil { log.Errorf("[RPCS] Error fetching sha: %v", err) jsonError := btcjson.Error{ Code: -5, Message: "Block not found", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } log.Tracef("[RPCS] reply: %v", rawReply) break } idx := blk.Height() txOutList := txS.TxOut voutList := make([]btcjson.Vout, len(txOutList)) txInList := txS.TxIn vinList := make([]btcjson.Vin, len(txInList)) for i, v := range txInList { vinList[i].Sequence = float64(v.Sequence) disbuf, _ := btcscript.DisasmString(v.SignatureScript) vinList[i].ScriptSig.Asm = strings.Replace(disbuf, " ", "", -1) vinList[i].Vout = i + 1 log.Debugf(disbuf) } for i, v := range txOutList { voutList[i].N = i voutList[i].Value = float64(v.Value) / 100000000 isbuf, _ := btcscript.DisasmString(v.PkScript) voutList[i].ScriptPubKey.Asm = isbuf voutList[i].ScriptPubKey.ReqSig = strings.Count(isbuf, "OP_CHECKSIG") _, addr, err := btcscript.ScriptToAddress(v.PkScript) if err != nil { log.Errorf("[RPCS] Error getting address for %v: %v", txSha, err) } else { addrList := make([]string, 1) addrList[0] = addr voutList[i].ScriptPubKey.Addresses = addrList } } _, maxidx, err := s.server.db.NewestSha() if err != nil { log.Errorf("[RPCS] Cannot get newest sha: %v", err) return } confirmations := uint64(1 + maxidx - idx) blockHeader := &blk.MsgBlock().Header txReply := btcjson.TxRawResult{ Txid: tx, Vout: voutList, Vin: vinList, Version: txS.Version, LockTime: txS.LockTime, // This is not a typo, they are identical in // bitcoind as well. Time: blockHeader.Timestamp.Unix(), Blocktime: blockHeader.Timestamp.Unix(), BlockHash: blksha.String(), Confirmations: confirmations, } rawReply = btcjson.Reply{ Result: txReply, Error: nil, Id: &message.Id, } } case "decoderawtransaction": var f interface{} err = json.Unmarshal(body, &f) m := f.(map[string]interface{}) var hash string for _, v := range m { switch vv := v.(type) { case []interface{}: for _, u := range vv { hash, _ = u.(string) } default: } } spew.Dump(hash) txReply := btcjson.TxRawDecodeResult{} rawReply = btcjson.Reply{ Result: txReply, Error: nil, Id: &message.Id, } default: jsonError := btcjson.Error{ Code: -32601, Message: "Method not found", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } } msg, err := btcjson.MarshallAndSend(rawReply, w) if err != nil { log.Errorf(msg) return } log.Debugf(msg) return }