lbcd/btcd/rpcserver.go
2013-08-06 19:17:05 -05:00

486 lines
11 KiB
Go

// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"github.com/conformal/btcchain"
"github.com/conformal/btcjson"
"github.com/conformal/btcscript"
"github.com/conformal/btcwire"
"github.com/davecgh/go-spew/spew"
"math/big"
"net"
"net/http"
"strconv"
"strings"
"sync"
)
// rpcServer holds the items the rpc server may need to access (config,
// shutdown, main server, etc.)
type rpcServer struct {
started bool
shutdown bool
server *server
wg sync.WaitGroup
rpcport string
username string
password string
listener net.Listener
}
// Start is used by server.go to start the rpc listener.
func (s *rpcServer) Start() {
if s.started {
return
}
log.Trace("[RPCS] Starting RPC server")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
jsonRpcRead(w, r, s)
})
listenAddr := net.JoinHostPort("", s.rpcport)
httpServer := &http.Server{Addr: listenAddr}
go func() {
log.Infof("[RPCS] RPC server listening on %s", s.listener.Addr())
httpServer.Serve(s.listener)
s.wg.Done()
}()
s.wg.Add(1)
s.started = true
}
// Stop is used by server.go to stop the rpc listener.
func (s *rpcServer) Stop() error {
if s.shutdown {
log.Infof("[RPCS] RPC server is already in the process of shutting down")
return nil
}
log.Warnf("[RPCS] RPC server shutting down")
err := s.listener.Close()
if err != nil {
log.Errorf("[RPCS] Problem shutting down rpc: %v", err)
return err
}
log.Infof("[RPCS] RPC server shutdown complete")
s.wg.Wait()
s.shutdown = true
return nil
}
// newRpcServer returns a new instance of the rpcServer struct.
func newRpcServer(s *server) (*rpcServer, error) {
rpc := rpcServer{
server: s,
}
// Get values from config
rpc.rpcport = cfg.RpcPort
rpc.username = cfg.RpcUser
rpc.password = cfg.RpcPass
listenAddr := net.JoinHostPort("", rpc.rpcport)
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Errorf("[RPCS] Couldn't create listener: %v", err)
return nil, err
}
rpc.listener = listener
return &rpc, 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 s.shutdown == true {
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 "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
txS, _, blksha, 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
}
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
}
// getDifficultyRatio returns the proof-of-work difficulty as a multiple of the
// minimum difficulty using the passed bits field from the header of a block.
func getDifficultyRatio(bits uint32) float64 {
// The minimum difficulty is the max possible proof-of-work limit bits
// converted back to a number. Note this is not the same as the the
// proof of work limit directly because the block difficulty is encoded
// in a block with the compact form which loses precision.
max := btcchain.CompactToBig(activeNetParams.powLimitBits)
target := btcchain.CompactToBig(bits)
difficulty := new(big.Rat).SetFrac(max, target)
outString := difficulty.FloatString(2)
diff, err := strconv.ParseFloat(outString, 64)
if err != nil {
log.Errorf("[RPCS] Cannot get difficulty: %v", err)
return 0
}
return diff
}