// newBlockNotifyCheckTxOut is a helper function to iterate through // each transaction output of a new block and perform any checks and // notify listening frontends when necessary. func (s *rpcServer) newBlockNotifyCheckTxOut(block *btcutil.Block, tx *btcutil.Tx, spent []bool) { for wltNtfn, cxt := range s.ws.requests.m { for i, txout := range tx.MsgTx().TxOut { _, txaddrhash, err := btcscript.ScriptToAddrHash(txout.PkScript) if err != nil { log.Debug("Error getting payment address from tx; dropping any Tx notifications.") break } for addr, id := range cxt.txRequests { if !bytes.Equal(addr[:], txaddrhash) { continue } blkhash, err := block.Sha() if err != nil { log.Error("Error getting block sha; dropping Tx notification.") break } txaddr, err := btcutil.EncodeAddress(txaddrhash, s.server.btcnet) if err != nil { log.Error("Error encoding address; dropping Tx notification.") break } reply := &btcjson.Reply{ Result: struct { Sender string `json:"sender"` Receiver string `json:"receiver"` BlockHash string `json:"blockhash"` Height int64 `json:"height"` TxHash string `json:"txhash"` Index uint32 `json:"index"` Amount int64 `json:"amount"` PkScript string `json:"pkscript"` Spent bool `json:"spent"` }{ Sender: "Unknown", // TODO(jrick) Receiver: txaddr, BlockHash: blkhash.String(), Height: block.Height(), TxHash: tx.Sha().String(), Index: uint32(i), Amount: txout.Value, PkScript: btcutil.Base58Encode(txout.PkScript), Spent: spent[i], }, Error: nil, Id: &id, } replyBytes, err := json.Marshal(reply) if err != nil { log.Errorf("RPCS: Unable to marshal tx notification: %v", err) continue } wltNtfn <- replyBytes } } } }
func jsonWSRead(walletNotification chan []byte, replychan chan *btcjson.Reply, body []byte, s *rpcServer) error { var message btcjson.Message err := json.Unmarshal(body, &message) if err != nil { reply := btcjson.Reply{ Result: nil, Error: &btcjson.ErrParse, Id: nil, } log.Tracef("RPCS: reply: %v", reply) replychan <- &reply return fmt.Errorf("RPCS: Error unmarshalling json message: %v", err) } log.Tracef("RPCS: received: %v", message) var rawReply btcjson.Reply defer func() { replychan <- &rawReply close(replychan) }() // Deal with commands switch message.Method { case "getcurrentnet": var net btcwire.BitcoinNet if cfg.TestNet3 { net = btcwire.TestNet3 } else { net = btcwire.MainNet } rawReply = btcjson.Reply{ Result: float64(net), Id: &message.Id, } case "getbestblock": // 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 { log.Errorf("RPCS: Error getting newest block: %v", err) rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrBestBlockHash, Id: &message.Id, } return err } rawReply = btcjson.Reply{ Result: map[string]interface{}{ "hash": sha.String(), "height": height, }, Id: &message.Id, } case "rescan": minblock, maxblock := int64(0), btcdb.AllShas params, ok := message.Params.([]interface{}) if !ok || len(params) < 2 { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } fminblock, ok := params[0].(float64) if !ok { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } minblock = int64(fminblock) iaddrs, ok := params[1].([]interface{}) if !ok { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } // addrHashes holds a set of string-ified address hashes. addrHashes := make(map[string]bool, len(iaddrs)) for i := range iaddrs { addr, ok := iaddrs[i].(string) if !ok { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } addrhash, _, err := btcutil.DecodeAddress(addr) if err != nil { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } addrHashes[string(addrhash)] = true } if len(params) > 2 { fmaxblock, ok := params[2].(float64) if !ok { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } maxblock = int64(fmaxblock) } log.Debugf("RPCS: Begining rescan") // 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 { return err } txShaList, err := blk.TxShas() if err != nil { return err } txList := s.server.db.FetchTxByShaList(txShaList) for _, txReply := range txList { if txReply.Err != nil || txReply.Tx == nil { continue } for txOutIdx, txout := range txReply.Tx.TxOut { st, txaddrhash, err := btcscript.ScriptToAddrHash(txout.PkScript) if st != btcscript.ScriptAddr || err != nil { continue } txaddr, err := btcutil.EncodeAddress(txaddrhash, s.server.btcnet) if err != nil { log.Errorf("Error encoding address: %v", err) return err } if ok := addrHashes[string(txaddrhash)]; ok { reply := btcjson.Reply{ Result: struct { Sender string `json:"sender"` Receiver string `json:"receiver"` BlockHash string `json:"blockhash"` Height int64 `json:"height"` TxHash string `json:"txhash"` Index uint32 `json:"index"` Amount int64 `json:"amount"` PkScript string `json:"pkscript"` Spent bool `json:"spent"` }{ Sender: "Unknown", // TODO(jrick) Receiver: txaddr, BlockHash: blkshalist[i].String(), Height: blk.Height(), TxHash: txReply.Sha.String(), Index: uint32(txOutIdx), Amount: txout.Value, PkScript: btcutil.Base58Encode(txout.PkScript), Spent: txReply.TxSpent[txOutIdx], }, Error: nil, Id: &message.Id, } replychan <- &reply } } } } if maxblock-minblock > int64(len(blkshalist)) { minblock += int64(len(blkshalist)) } else { break } } rawReply = btcjson.Reply{ Result: nil, Error: nil, Id: &message.Id, } log.Debug("RPCS: Finished rescan") case "notifynewtxs": params, ok := message.Params.([]interface{}) if !ok || len(params) != 1 { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } addr, ok := params[0].(string) if !ok { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } addrhash, _, err := btcutil.DecodeAddress(addr) if err != nil { jsonError := btcjson.Error{ Code: btcjson.ErrInvalidParams.Code, Message: "Cannot decode address", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } return ErrBadParamsField } var hash addressHash copy(hash[:], addrhash) s.ws.requests.AddTxRequest(walletNotification, hash, message.Id) rawReply = btcjson.Reply{ Result: nil, Error: nil, Id: &message.Id, } case "notifyspent": params, ok := message.Params.([]interface{}) if !ok || len(params) != 2 { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } hashBE, ok1 := params[0].(string) index, ok2 := params[1].(float64) if !ok1 || !ok2 { rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrInvalidParams, Id: &message.Id, } return ErrBadParamsField } hash, err := btcwire.NewShaHashFromStr(hashBE) if err != nil { jsonError := btcjson.Error{ Code: btcjson.ErrInvalidParams.Code, Message: "Hash string cannot be parsed.", } rawReply = btcjson.Reply{ Result: nil, Error: &jsonError, Id: &message.Id, } return ErrBadParamsField } op := btcwire.NewOutPoint(hash, uint32(index)) s.ws.requests.AddSpentRequest(walletNotification, op, message.Id) rawReply = btcjson.Reply{ Result: nil, Error: nil, Id: &message.Id, } default: rawReply = btcjson.Reply{ Result: nil, Error: &btcjson.ErrMethodNotFound, Id: &message.Id, } } return btcjson.ErrMethodNotFound }
// handleGetRawTransaction implements the getrawtransaction command. func handleGetRawTransaction(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) { c := cmd.(*btcjson.GetRawTransactionCmd) if c.Verbose { // TODO: check error code. tx is not checked before // this point. txSha, _ := btcwire.NewShaHashFromStr(c.Txid) txList, err := s.server.db.FetchTxBySha(txSha) if err != nil { log.Errorf("RPCS: Error fetching tx: %v", err) return nil, btcjson.ErrNoTxInfo } 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) return nil, btcjson.ErrBlockNotFound } 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") _, addrhash, err := btcscript.ScriptToAddrHash(v.PkScript) if err != nil { // TODO: set and return error? log.Errorf("RPCS: Error getting address hash for %v: %v", txSha, err) } if addr, err := btcutil.EncodeAddress(addrhash, s.server.btcnet); err != nil { // TODO: set and return error? 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 nil, btcjson.ErrNoNewestBlockInfo } confirmations := uint64(1 + maxidx - idx) blockHeader := &blk.MsgBlock().Header txReply := btcjson.TxRawResult{ Txid: c.Txid, 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, } return txReply, nil } else { // Don't return details // not used yet return nil, nil } }