Example #1
0
// 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
			}
		}
	}
}
Example #2
0
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
}
Example #3
0
// 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
	}
}