// standardCmdReply checks that a parsed command is a standard // Bitcoin JSON-RPC command and runs the proper handler to reply to the // command. func standardCmdReply(cmd btcjson.Cmd, s *rpcServer) (reply btcjson.Reply) { id := cmd.Id() reply.Id = &id handler, ok := rpcHandlers[cmd.Method()] if !ok { reply.Error = &btcjson.ErrMethodNotFound return reply } result, err := handler(s, cmd) if err != nil { jsonErr, ok := err.(btcjson.Error) if !ok { // 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. jsonErr = btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } } reply.Error = &jsonErr } else { reply.Result = result } return reply }
// marshalAndSendPost marshals the passed command to JSON-RPC and sends it to // the server by issuing an HTTP POST request and returns a response channel // on which the reply will be delivered. Typically a new connection is opened // and closed for each command when using this method, however, the underlying // HTTP client might coalesce multiple commands depending on several factors // including the remote server configuration. func (c *Client) marshalAndSendPost(cmd btcjson.Cmd, responseChan chan *futureResult) { marshalledJSON, err := json.Marshal(cmd) if err != nil { responseChan <- &futureResult{reply: nil, err: err} return } // Generate a request to the configured RPC server. protocol := "http" if !c.config.DisableTLS { protocol = "https" } url := protocol + "://" + c.config.Host req, err := http.NewRequest("POST", url, bytes.NewReader(marshalledJSON)) if err != nil { responseChan <- &futureResult{reply: nil, err: err} return } req.Close = true req.Header.Set("Content-Type", "application/json") // Configure basic access authorization. req.SetBasicAuth(c.config.User, c.config.Pass) log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) c.sendPostRequest(req, cmd, responseChan) }
// handleNotifyNewTXs implements the notifynewtxs command extension for // websocket connections. func handleNotifyNewTXs(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} notifyCmd, ok := cmd.(*btcws.NotifyNewTXsCmd) if !ok { return btcjson.ErrInternal } for _, addr := range notifyCmd.Addresses { addr, err := btcutil.DecodeAddr(addr) if err != nil { return fmt.Errorf("cannot decode address: %v", err) } // TODO(jrick) Notifing for non-P2PKH addresses is currently // unsuported. if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok { return fmt.Errorf("address is not P2PKH: %v", addr.EncodeAddress()) } s.ws.AddTxRequest(wallet, addr.EncodeAddress()) } mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// sendCmd sends the passed command to the associated server and returns a // response channel on which the reply will be deliver at some point in the // future. It handles both websocket and HTTP POST mode depending on the // configuration of the client. func (c *Client) sendCmd(cmd btcjson.Cmd) chan *response { // Choose which marshal and send function to use depending on whether // the client running in HTTP POST mode or not. When running in HTTP // POST mode, the command is issued via an HTTP client. Otherwise, // the command is issued via the asynchronous websocket channels. responseChan := make(chan *response, 1) if c.config.HttpPostMode { c.marshalAndSendPost(cmd, responseChan) return responseChan } // Check whether the websocket connection has never been established, // in which case the handler goroutines are not running. select { case <-c.connEstablished: default: responseChan <- &response{err: ErrClientNotConnected} return responseChan } err := c.addRequest(cmd.Id().(uint64), &jsonRequest{ cmd: cmd, responseChan: responseChan, }) if err != nil { responseChan <- &response{err: err} return responseChan } c.marshalAndSend(cmd, responseChan) return responseChan }
// marshalAndSend marshals the passed command to JSON-RPC and sends it to the // server. It returns a response channel on which the reply will be delivered. func (c *Client) marshalAndSend(cmd btcjson.Cmd, responseChan chan *futureResult) { marshalledJSON, err := json.Marshal(cmd) if err != nil { responseChan <- &futureResult{reply: nil, err: err} return } log.Tracef("Sending command [%s] with id %d", cmd.Method(), cmd.Id()) c.sendMessage(marshalledJSON) }
// handleNotifySpent implements the notifyspent command extension for // websocket connections. func handleNotifySpent(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} notifyCmd, ok := cmd.(*btcws.NotifySpentCmd) if !ok { return btcjson.ErrInternal } s.ws.AddSpentRequest(wallet, notifyCmd.OutPoint) mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// handleGetCurrentNet implements the getcurrentnet command extension // for websocket connections. func handleGetCurrentNet(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} var net btcwire.BitcoinNet if cfg.TestNet3 { net = btcwire.TestNet3 } else { net = btcwire.MainNet } reply.Result = float64(net) mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// sendCmd sends the passed command to the associated server and returns a // response channel on which the reply will be deliver at some point in the // future. It handles both websocket and HTTP POST mode depending on the // configuration of the client. func (c *Client) sendCmd(cmd btcjson.Cmd) chan *futureResult { // Choose which marshal and send function to use depending on whether // the client running in HTTP POST mode or not. When running in HTTP // POST mode, the command is issued via an HTTP client. Otherwise, // the command is issued via the asynchronous websocket channels. responseChan := make(chan *futureResult, 1) if c.config.HttpPostMode { c.marshalAndSendPost(cmd, responseChan) return responseChan } c.addRequest(cmd.Id().(uint64), &jsonRequest{ cmd: cmd, responseChan: responseChan, }) c.marshalAndSend(cmd, responseChan) return responseChan }
// respondToAnyCmd checks that a parsed command is a standard or // extension JSON-RPC command and runs the proper handler to reply to // the command. Any and all responses are sent to the wallet from // this function. func respondToAnyCmd(cmd btcjson.Cmd, s *rpcServer, c handlerChans) *btcjson.Reply { // Lookup the websocket extension for the command and if it doesn't // exist fallback to handling the command as a standard command. wsHandler, ok := wsHandlers[cmd.Method()] if !ok { // No websocket-specific handler so handle like a legacy // RPC connection. response := standardCmdReply(cmd, s) return &response } result, jsonErr := wsHandler(s, cmd, c) id := cmd.Id() response := btcjson.Reply{ Id: &id, Result: result, Error: jsonErr, } return &response }
// handleGetBestBlock implements the getbestblock command extension // for websocket connections. func handleGetBestBlock(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} // All other "get block" commands give either the height, the // hash, or both but require the block SHA. This gets both for // the best block. sha, height, err := s.server.db.NewestSha() if err != nil { return btcjson.ErrBestBlockHash } reply.Result = map[string]interface{}{ "hash": sha.String(), "height": height, } mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// handleRescan implements the rescan command extension for websocket // connections. func handleRescan(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { rescanCmd, ok := cmd.(*btcws.RescanCmd) if !ok { return btcjson.ErrInternal } if len(rescanCmd.Addresses) == 1 { rpcsLog.Info("Beginning rescan for 1 address.") } else { rpcsLog.Infof("Beginning rescan for %v addresses.", len(rescanCmd.Addresses)) } minblock := int64(rescanCmd.BeginBlock) maxblock := int64(rescanCmd.EndBlock) // FetchHeightRange may not return a complete list of block shas for // the given range, so fetch range as many times as necessary. for { blkshalist, err := s.server.db.FetchHeightRange(minblock, maxblock) if err != nil { return err } if len(blkshalist) == 0 { break } for i := range blkshalist { blk, err := s.server.db.FetchBlockBySha(&blkshalist[i]) if err != nil { rpcsLog.Errorf("Error looking up block sha: %v", err) return err } for _, tx := range blk.Transactions() { var txReply *btcdb.TxListReply txouts: for txOutIdx, txout := range tx.MsgTx().TxOut { _, addrs, _, err := btcscript.ExtractPkScriptAddrs( txout.PkScript, s.server.btcnet) if err != nil { continue txouts } for i, addr := range addrs { encodedAddr := addr.EncodeAddress() if _, ok := rescanCmd.Addresses[encodedAddr]; ok { // TODO(jrick): This lookup is expensive and can be avoided // if the wallet is sent the previous outpoints for all inputs // of the tx, so any can removed from the utxo set (since // they are, as of this tx, now spent). if txReply == nil { txReplyList, err := s.server.db.FetchTxBySha(tx.Sha()) if err != nil { rpcsLog.Errorf("Tx Sha %v not found by db.", tx.Sha()) continue txouts } for i := range txReplyList { if txReplyList[i].Height == blk.Height() { txReply = txReplyList[i] break } } } ntfn := &btcws.ProcessedTxNtfn{ Receiver: encodedAddr, Amount: txout.Value, TxID: tx.Sha().String(), TxOutIndex: uint32(txOutIdx), PkScript: hex.EncodeToString(txout.PkScript), BlockHash: blkshalist[i].String(), BlockHeight: int32(blk.Height()), BlockIndex: tx.Index(), BlockTime: blk.MsgBlock().Header.Timestamp.Unix(), Spent: txReply.TxSpent[txOutIdx], } mntfn, _ := ntfn.MarshalJSON() wallet <- mntfn } } } } } if maxblock-minblock > int64(len(blkshalist)) { minblock += int64(len(blkshalist)) } else { break } } rpcsLog.Info("Finished rescan") id := cmd.Id() response := &btcjson.Reply{ Id: &id, Result: nil, Error: nil, } mresponse, _ := json.Marshal(response) wallet <- mresponse return nil }