2013-08-06 23:55:22 +02:00
|
|
|
// 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 (
|
2013-08-14 22:55:31 +02:00
|
|
|
"bytes"
|
|
|
|
"code.google.com/p/go.net/websocket"
|
2013-12-05 19:21:51 +01:00
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/subtle"
|
2013-11-07 17:25:11 +01:00
|
|
|
"crypto/tls"
|
2013-10-01 22:43:45 +02:00
|
|
|
"encoding/base64"
|
2013-08-14 22:55:31 +02:00
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
2013-10-01 22:43:45 +02:00
|
|
|
"fmt"
|
2013-08-06 23:55:22 +02:00
|
|
|
"github.com/conformal/btcchain"
|
2013-08-14 22:55:31 +02:00
|
|
|
"github.com/conformal/btcdb"
|
2013-08-06 23:55:22 +02:00
|
|
|
"github.com/conformal/btcjson"
|
|
|
|
"github.com/conformal/btcscript"
|
2013-10-08 18:37:06 +02:00
|
|
|
"github.com/conformal/btcutil"
|
2013-08-06 23:55:22 +02:00
|
|
|
"github.com/conformal/btcwire"
|
2014-01-10 20:45:04 +01:00
|
|
|
"io/ioutil"
|
2013-08-06 23:55:22 +02:00
|
|
|
"math/big"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2013-11-07 17:25:11 +01:00
|
|
|
"os"
|
2013-08-06 23:55:22 +02:00
|
|
|
"strconv"
|
|
|
|
"sync"
|
2013-10-03 01:33:42 +02:00
|
|
|
"sync/atomic"
|
2013-11-07 17:25:11 +01:00
|
|
|
"time"
|
2013-08-06 23:55:22 +02:00
|
|
|
)
|
|
|
|
|
2013-08-14 22:55:31 +02:00
|
|
|
// Errors
|
|
|
|
var (
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
// ErrBadParamsField describes an error where the parameters JSON
|
|
|
|
// field cannot be properly parsed.
|
2013-08-14 22:55:31 +02:00
|
|
|
ErrBadParamsField = errors.New("bad params field")
|
|
|
|
)
|
|
|
|
|
2013-12-31 21:48:50 +01:00
|
|
|
type commandHandler func(*rpcServer, btcjson.Cmd) (interface{}, error)
|
|
|
|
|
|
|
|
// handlers maps RPC command strings to appropriate handler functions.
|
2013-12-31 22:53:19 +01:00
|
|
|
var rpcHandlers = map[string]commandHandler{
|
2013-12-31 21:48:50 +01:00
|
|
|
"addmultisigaddress": handleAskWallet,
|
|
|
|
"addnode": handleAddNode,
|
|
|
|
"backupwallet": handleAskWallet,
|
|
|
|
"createmultisig": handleAskWallet,
|
2014-01-01 17:17:40 +01:00
|
|
|
"createrawtransaction": handleCreateRawTransaction,
|
2013-12-31 21:48:50 +01:00
|
|
|
"debuglevel": handleDebugLevel,
|
|
|
|
"decoderawtransaction": handleDecodeRawTransaction,
|
2014-01-04 06:29:24 +01:00
|
|
|
"decodescript": handleDecodeScript,
|
2013-12-31 21:48:50 +01:00
|
|
|
"dumpprivkey": handleAskWallet,
|
|
|
|
"dumpwallet": handleAskWallet,
|
|
|
|
"encryptwallet": handleAskWallet,
|
|
|
|
"getaccount": handleAskWallet,
|
|
|
|
"getaccountaddress": handleAskWallet,
|
|
|
|
"getaddednodeinfo": handleUnimplemented,
|
|
|
|
"getaddressesbyaccount": handleAskWallet,
|
|
|
|
"getbalance": handleAskWallet,
|
|
|
|
"getbestblockhash": handleGetBestBlockHash,
|
|
|
|
"getblock": handleGetBlock,
|
|
|
|
"getblockcount": handleGetBlockCount,
|
|
|
|
"getblockhash": handleGetBlockHash,
|
|
|
|
"getblocktemplate": handleUnimplemented,
|
|
|
|
"getconnectioncount": handleGetConnectionCount,
|
|
|
|
"getdifficulty": handleGetDifficulty,
|
|
|
|
"getgenerate": handleGetGenerate,
|
|
|
|
"gethashespersec": handleGetHashesPerSec,
|
|
|
|
"getinfo": handleUnimplemented,
|
|
|
|
"getmininginfo": handleUnimplemented,
|
|
|
|
"getnettotals": handleUnimplemented,
|
|
|
|
"getnetworkhashps": handleUnimplemented,
|
|
|
|
"getnewaddress": handleUnimplemented,
|
|
|
|
"getpeerinfo": handleGetPeerInfo,
|
|
|
|
"getrawchangeaddress": handleAskWallet,
|
|
|
|
"getrawmempool": handleGetRawMempool,
|
|
|
|
"getrawtransaction": handleGetRawTransaction,
|
|
|
|
"getreceivedbyaccount": handleAskWallet,
|
|
|
|
"getreceivedbyaddress": handleAskWallet,
|
|
|
|
"gettransaction": handleAskWallet,
|
|
|
|
"gettxout": handleAskWallet,
|
|
|
|
"gettxoutsetinfo": handleAskWallet,
|
|
|
|
"getwork": handleUnimplemented,
|
|
|
|
"help": handleUnimplemented,
|
|
|
|
"importprivkey": handleAskWallet,
|
|
|
|
"importwallet": handleAskWallet,
|
|
|
|
"keypoolrefill": handleAskWallet,
|
|
|
|
"listaccounts": handleAskWallet,
|
|
|
|
"listaddressgroupings": handleAskWallet,
|
|
|
|
"listlockunspent": handleAskWallet,
|
|
|
|
"listreceivedbyaccount": handleAskWallet,
|
|
|
|
"listreceivedbyaddress": handleAskWallet,
|
|
|
|
"listsinceblock": handleAskWallet,
|
|
|
|
"listtransactions": handleAskWallet,
|
|
|
|
"listunspent": handleAskWallet,
|
|
|
|
"lockunspent": handleAskWallet,
|
|
|
|
"move": handleAskWallet,
|
|
|
|
"ping": handleUnimplemented,
|
|
|
|
"sendfrom": handleAskWallet,
|
|
|
|
"sendmany": handleAskWallet,
|
|
|
|
"sendrawtransaction": handleSendRawTransaction,
|
|
|
|
"sendtoaddress": handleAskWallet,
|
|
|
|
"setaccount": handleAskWallet,
|
|
|
|
"setgenerate": handleSetGenerate,
|
|
|
|
"settxfee": handleAskWallet,
|
|
|
|
"signmessage": handleAskWallet,
|
|
|
|
"signrawtransaction": handleAskWallet,
|
|
|
|
"stop": handleStop,
|
2014-01-15 16:01:12 +01:00
|
|
|
"submitblock": handleSubmitBlock,
|
2013-12-31 21:48:50 +01:00
|
|
|
"validateaddress": handleAskWallet,
|
|
|
|
"verifychain": handleVerifyChain,
|
|
|
|
"verifymessage": handleAskWallet,
|
|
|
|
"walletlock": handleAskWallet,
|
|
|
|
"walletpassphrase": handleAskWallet,
|
|
|
|
"walletpassphrasechange": handleAskWallet,
|
|
|
|
}
|
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
// rpcServer holds the items the rpc server may need to access (config,
|
|
|
|
// shutdown, main server, etc.)
|
|
|
|
type rpcServer struct {
|
2013-10-03 01:33:42 +02:00
|
|
|
started int32
|
|
|
|
shutdown int32
|
2013-08-07 17:38:39 +02:00
|
|
|
server *server
|
2013-12-05 19:21:51 +01:00
|
|
|
authsha [sha256.Size]byte
|
2014-01-17 20:04:57 +01:00
|
|
|
ws *wsContext
|
2013-08-07 17:38:39 +02:00
|
|
|
wg sync.WaitGroup
|
|
|
|
listeners []net.Listener
|
2013-08-14 22:55:31 +02:00
|
|
|
quit chan int
|
|
|
|
}
|
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
// Start is used by server.go to start the rpc listener.
|
|
|
|
func (s *rpcServer) Start() {
|
2013-10-03 01:33:42 +02:00
|
|
|
if atomic.AddInt32(&s.started, 1) != 1 {
|
2013-08-06 23:55:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Trace("Starting RPC server")
|
2013-10-23 17:45:43 +02:00
|
|
|
rpcServeMux := http.NewServeMux()
|
|
|
|
httpServer := &http.Server{Handler: rpcServeMux}
|
|
|
|
rpcServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
2013-12-05 19:21:51 +01:00
|
|
|
if err := s.checkAuth(r); err != nil {
|
2013-10-01 22:43:45 +02:00
|
|
|
jsonAuthFail(w, r, s)
|
2013-12-05 19:21:51 +01:00
|
|
|
return
|
2013-10-01 22:43:45 +02:00
|
|
|
}
|
2014-01-10 16:57:05 +01:00
|
|
|
w.Header().Set("Connection", "close")
|
2013-12-05 19:21:51 +01:00
|
|
|
jsonRPCRead(w, r, s)
|
2013-08-06 23:55:22 +02:00
|
|
|
})
|
2014-01-15 04:53:07 +01:00
|
|
|
|
2013-12-05 19:21:51 +01:00
|
|
|
rpcServeMux.HandleFunc("/wallet", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if err := s.checkAuth(r); err != nil {
|
|
|
|
http.Error(w, "401 Unauthorized.", http.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
websocket.Handler(s.walletReqsNotifications).ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
|
2013-08-07 17:38:39 +02:00
|
|
|
for _, listener := range s.listeners {
|
2013-09-13 00:24:37 +02:00
|
|
|
s.wg.Add(1)
|
2013-08-07 17:38:39 +02:00
|
|
|
go func(listener net.Listener) {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("RPC server listening on %s", listener.Addr())
|
2013-08-07 17:38:39 +02:00
|
|
|
httpServer.Serve(listener)
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Tracef("RPC listener done for %s", listener.Addr())
|
2013-08-07 17:38:39 +02:00
|
|
|
s.wg.Done()
|
|
|
|
}(listener)
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
|
2013-12-05 19:21:51 +01:00
|
|
|
// checkAuth checks the HTTP Basic authentication supplied by a wallet
|
|
|
|
// or RPC client in the HTTP request r. If the supplied authentication
|
|
|
|
// does not match the username and password expected, a non-nil error is
|
|
|
|
// returned.
|
|
|
|
//
|
|
|
|
// This check is time-constant.
|
|
|
|
func (s *rpcServer) checkAuth(r *http.Request) error {
|
|
|
|
authhdr := r.Header["Authorization"]
|
|
|
|
if len(authhdr) <= 0 {
|
|
|
|
rpcsLog.Warnf("Auth failure.")
|
|
|
|
return errors.New("auth failure")
|
|
|
|
}
|
|
|
|
|
|
|
|
authsha := sha256.Sum256([]byte(authhdr[0]))
|
|
|
|
cmp := subtle.ConstantTimeCompare(authsha[:], s.authsha[:])
|
|
|
|
if cmp != 1 {
|
|
|
|
rpcsLog.Warnf("Auth failure.")
|
|
|
|
return errors.New("auth failure")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
// Stop is used by server.go to stop the rpc listener.
|
|
|
|
func (s *rpcServer) Stop() error {
|
2013-10-03 01:33:42 +02:00
|
|
|
if atomic.AddInt32(&s.shutdown, 1) != 1 {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("RPC server is already in the process of shutting down")
|
2013-08-06 23:55:22 +02:00
|
|
|
return nil
|
|
|
|
}
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("RPC server shutting down")
|
2013-08-07 17:38:39 +02:00
|
|
|
for _, listener := range s.listeners {
|
|
|
|
err := listener.Close()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Problem shutting down rpc: %v", err)
|
2013-08-07 17:38:39 +02:00
|
|
|
return err
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
close(s.quit)
|
2014-01-15 04:53:07 +01:00
|
|
|
s.wg.Wait()
|
|
|
|
rpcsLog.Infof("RPC server shutdown complete")
|
2013-08-06 23:55:22 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:45:04 +01:00
|
|
|
// genCertPair generates a key/cert pair to the paths provided.
|
|
|
|
func genCertPair(certFile, keyFile string) error {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("Generating TLS certificates...")
|
2013-11-07 17:25:11 +01:00
|
|
|
|
2014-01-10 20:45:04 +01:00
|
|
|
org := "btcd autogenerated cert"
|
|
|
|
validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
|
|
|
|
cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil)
|
2013-11-07 17:25:11 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-01-10 20:45:04 +01:00
|
|
|
// Write cert and key files.
|
|
|
|
if err = ioutil.WriteFile(certFile, cert, 0666); err != nil {
|
2013-11-07 17:25:11 +01:00
|
|
|
return err
|
|
|
|
}
|
2014-01-10 20:45:04 +01:00
|
|
|
if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
|
|
|
|
os.Remove(certFile)
|
2013-11-07 17:25:11 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("Done generating TLS certificates")
|
2013-11-07 17:25:11 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-09-18 07:36:40 +02:00
|
|
|
// newRPCServer returns a new instance of the rpcServer struct.
|
2013-11-14 02:51:37 +01:00
|
|
|
func newRPCServer(listenAddrs []string, s *server) (*rpcServer, error) {
|
2013-12-05 19:21:51 +01:00
|
|
|
login := cfg.RPCUser + ":" + cfg.RPCPass
|
|
|
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
2013-08-06 23:55:22 +02:00
|
|
|
rpc := rpcServer{
|
2013-12-05 19:21:51 +01:00
|
|
|
authsha: sha256.Sum256([]byte(auth)),
|
|
|
|
server: s,
|
2014-01-17 20:04:57 +01:00
|
|
|
ws: newWebsocketContext(),
|
2013-12-05 19:21:51 +01:00
|
|
|
quit: make(chan int),
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
|
2013-11-20 22:55:36 +01:00
|
|
|
// check for existence of cert file and key file
|
2013-11-07 17:25:11 +01:00
|
|
|
if !fileExists(cfg.RPCKey) && !fileExists(cfg.RPCCert) {
|
|
|
|
// if both files do not exist, we generate them.
|
2014-01-10 20:45:04 +01:00
|
|
|
err := genCertPair(cfg.RPCCert, cfg.RPCKey)
|
2013-11-07 17:25:11 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
keypair, err := tls.LoadX509KeyPair(cfg.RPCCert, cfg.RPCKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tlsConfig := tls.Config{
|
|
|
|
Certificates: []tls.Certificate{keypair},
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(oga) this code is similar to that in server, should be
|
2013-11-14 02:51:37 +01:00
|
|
|
// factored into something shared.
|
2013-11-26 01:40:16 +01:00
|
|
|
ipv4ListenAddrs, ipv6ListenAddrs, _, err := parseListeners(listenAddrs)
|
2013-11-07 17:25:11 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-11-14 02:51:37 +01:00
|
|
|
listeners := make([]net.Listener, 0,
|
|
|
|
len(ipv6ListenAddrs)+len(ipv4ListenAddrs))
|
|
|
|
for _, addr := range ipv4ListenAddrs {
|
2013-11-07 17:25:11 +01:00
|
|
|
var listener net.Listener
|
|
|
|
listener, err = tls.Listen("tcp4", addr, &tlsConfig)
|
2013-11-14 02:51:37 +01:00
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("Can't listen on %s: %v", addr,
|
2013-11-14 02:51:37 +01:00
|
|
|
err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
listeners = append(listeners, listener)
|
2013-08-07 17:38:39 +02:00
|
|
|
}
|
|
|
|
|
2013-11-14 02:51:37 +01:00
|
|
|
for _, addr := range ipv6ListenAddrs {
|
2013-11-07 17:25:11 +01:00
|
|
|
var listener net.Listener
|
|
|
|
listener, err = tls.Listen("tcp6", addr, &tlsConfig)
|
2013-11-14 02:51:37 +01:00
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("Can't listen on %s: %v", addr,
|
2013-11-14 02:51:37 +01:00
|
|
|
err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
listeners = append(listeners, listener)
|
|
|
|
}
|
|
|
|
if len(listeners) == 0 {
|
|
|
|
return nil, errors.New("RPCS: No valid listen address")
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-08-07 17:38:39 +02:00
|
|
|
|
|
|
|
rpc.listeners = listeners
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
return &rpc, err
|
|
|
|
}
|
|
|
|
|
2013-10-01 22:43:45 +02:00
|
|
|
// jsonAuthFail sends a message back to the client if the http auth is rejected.
|
|
|
|
func jsonAuthFail(w http.ResponseWriter, r *http.Request, s *rpcServer) {
|
|
|
|
fmt.Fprint(w, "401 Unauthorized.\n")
|
|
|
|
}
|
|
|
|
|
2013-12-31 22:53:19 +01:00
|
|
|
// jsonRPCRead is the RPC wrapper around the jsonRead function to handle reading
|
|
|
|
// and responding to RPC messages.
|
2013-09-18 07:36:40 +02:00
|
|
|
func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) {
|
2013-08-06 23:55:22 +02:00
|
|
|
r.Close = true
|
2013-10-03 01:33:42 +02:00
|
|
|
if atomic.LoadInt32(&s.shutdown) != 0 {
|
2013-08-06 23:55:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
body, err := btcjson.GetRaw(r.Body)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting json message: %v", err)
|
2013-08-06 23:55:22 +02:00
|
|
|
return
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-07 18:47:54 +01:00
|
|
|
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 {
|
2013-12-31 21:39:17 +01:00
|
|
|
reply = standardCmdReply(cmd, s)
|
2013-11-07 18:47:54 +01:00
|
|
|
}
|
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Tracef("reply: %v", reply)
|
2013-08-14 22:55:31 +02:00
|
|
|
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
msg, err := btcjson.MarshallAndSend(reply, w)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf(msg)
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
return
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debugf(msg)
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
// handleUnimplemented is a temporary handler for commands that we should
|
|
|
|
// support but do not.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleUnimplemented(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-11-04 19:31:56 +01:00
|
|
|
return nil, btcjson.ErrUnimplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleAskWallet is the handler for commands that we do recognise as valid
|
|
|
|
// but that we can not answer correctly since it involves wallet state.
|
|
|
|
// These commands will be implemented in btcwallet.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleAskWallet(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-11-04 19:31:56 +01:00
|
|
|
return nil, btcjson.ErrNoWallet
|
|
|
|
}
|
|
|
|
|
2013-12-31 01:22:39 +01:00
|
|
|
// handleAddNode handles addnode commands.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleAddNode(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 18:18:53 +01:00
|
|
|
c := cmd.(*btcjson.AddNodeCmd)
|
|
|
|
|
2013-09-19 17:46:33 +02:00
|
|
|
addr := normalizeAddress(c.Addr, activeNetParams.peerPort)
|
2013-10-29 18:18:53 +01:00
|
|
|
var err error
|
|
|
|
switch c.SubCmd {
|
|
|
|
case "add":
|
|
|
|
err = s.server.AddAddr(addr, true)
|
|
|
|
case "remove":
|
|
|
|
err = s.server.RemoveAddr(addr)
|
|
|
|
case "onetry":
|
|
|
|
err = s.server.AddAddr(addr, false)
|
|
|
|
default:
|
|
|
|
err = errors.New("Invalid subcommand for addnode")
|
|
|
|
}
|
|
|
|
|
2013-12-31 20:15:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-29 18:18:53 +01:00
|
|
|
// no data returned unless an error.
|
2013-12-31 20:15:44 +01:00
|
|
|
return nil, nil
|
2013-10-29 18:18:53 +01:00
|
|
|
}
|
|
|
|
|
2014-01-01 17:17:40 +01:00
|
|
|
// messageToHex serializes a message to the wire protocol encoding using the
|
|
|
|
// latest protocol version and returns a hex-encoded string of the result.
|
|
|
|
func messageToHex(msg btcwire.Message) (string, error) {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err := msg.BtcEncode(&buf, btcwire.ProtocolVersion)
|
|
|
|
if err != nil {
|
|
|
|
return "", btcjson.Error{
|
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hex.EncodeToString(buf.Bytes()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleCreateRawTransaction handles createrawtransaction commands.
|
|
|
|
func handleCreateRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.CreateRawTransactionCmd)
|
|
|
|
|
|
|
|
// Add all transaction inputs to a new transaction after performing
|
|
|
|
// some validty checks.
|
|
|
|
mtx := btcwire.NewMsgTx()
|
|
|
|
for _, input := range c.Inputs {
|
|
|
|
txHash, err := btcwire.NewShaHashFromStr(input.Txid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.ErrDecodeHexString
|
|
|
|
}
|
|
|
|
|
|
|
|
if input.Vout < 0 {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: "Invalid parameter, vout must be positive",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prevOut := btcwire.NewOutPoint(txHash, uint32(input.Vout))
|
|
|
|
txIn := btcwire.NewTxIn(prevOut, []byte{})
|
|
|
|
mtx.AddTxIn(txIn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add all transaction outputs to the transaction after performing
|
|
|
|
// some validty checks.
|
|
|
|
for encodedAddr, amount := range c.Amounts {
|
|
|
|
// Ensure amount is in the valid range for monetary amounts.
|
|
|
|
if amount <= 0 || amount > btcutil.MaxSatoshi {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrType.Code,
|
|
|
|
Message: "Invalid amount",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the provided address.
|
|
|
|
addr, err := btcutil.DecodeAddr(encodedAddr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidAddressOrKey.Code,
|
|
|
|
Message: btcjson.ErrInvalidAddressOrKey.Message +
|
|
|
|
": " + err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the address is one of the supported types and that
|
|
|
|
// the network encoded with the address matches the network the
|
|
|
|
// server is currently on.
|
|
|
|
net := s.server.btcnet
|
|
|
|
switch addr := addr.(type) {
|
|
|
|
case *btcutil.AddressPubKeyHash:
|
|
|
|
net = addr.Net()
|
|
|
|
case *btcutil.AddressScriptHash:
|
|
|
|
net = addr.Net()
|
|
|
|
default:
|
|
|
|
return nil, btcjson.ErrInvalidAddressOrKey
|
|
|
|
}
|
|
|
|
if net != s.server.btcnet {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidAddressOrKey.Code,
|
|
|
|
Message: fmt.Sprintf("%s: %q",
|
|
|
|
btcjson.ErrInvalidAddressOrKey.Message,
|
|
|
|
encodedAddr),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new script which pays to the provided address.
|
|
|
|
pkScript, err := btcscript.PayToAddrScript(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
txOut := btcwire.NewTxOut(amount, pkScript)
|
|
|
|
mtx.AddTxOut(txOut)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the serialized and hex-encoded transaction.
|
|
|
|
mtxHex, err := messageToHex(mtx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return mtxHex, nil
|
|
|
|
}
|
|
|
|
|
2013-11-22 17:46:56 +01:00
|
|
|
// handleDebugLevel handles debuglevel commands.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleDebugLevel(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-11-22 17:46:56 +01:00
|
|
|
c := cmd.(*btcjson.DebugLevelCmd)
|
|
|
|
|
|
|
|
// Special show command to list supported subsystems.
|
|
|
|
if c.LevelSpec == "show" {
|
|
|
|
return fmt.Sprintf("Supported subsystems %v",
|
|
|
|
supportedSubsystems()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := parseAndSetDebugLevels(c.LevelSpec)
|
|
|
|
if err != nil {
|
2013-12-31 20:15:44 +01:00
|
|
|
return nil, btcjson.Error{
|
2013-11-22 17:46:56 +01:00
|
|
|
Code: btcjson.ErrInvalidParams.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "Done.", nil
|
|
|
|
}
|
|
|
|
|
2013-12-31 01:22:39 +01:00
|
|
|
// createVinList returns a slice of JSON objects for the inputs of the passed
|
|
|
|
// transaction.
|
2013-12-31 03:27:36 +01:00
|
|
|
func createVinList(mtx *btcwire.MsgTx) ([]btcjson.Vin, error) {
|
2013-12-31 01:22:39 +01:00
|
|
|
tx := btcutil.NewTx(mtx)
|
|
|
|
vinList := make([]btcjson.Vin, len(mtx.TxIn))
|
|
|
|
for i, v := range mtx.TxIn {
|
|
|
|
if btcchain.IsCoinBase(tx) {
|
|
|
|
vinList[i].Coinbase = hex.EncodeToString(v.SignatureScript)
|
|
|
|
} else {
|
|
|
|
vinList[i].Txid = v.PreviousOutpoint.Hash.String()
|
|
|
|
vinList[i].Vout = int(v.PreviousOutpoint.Index)
|
|
|
|
|
2013-12-31 02:06:13 +01:00
|
|
|
disbuf, err := btcscript.DisasmString(v.SignatureScript)
|
|
|
|
if err != nil {
|
2013-12-31 03:27:36 +01:00
|
|
|
return nil, btcjson.Error{
|
2013-12-31 02:06:13 +01:00
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
2013-12-31 01:22:39 +01:00
|
|
|
vinList[i].ScriptSig = new(btcjson.ScriptSig)
|
|
|
|
vinList[i].ScriptSig.Asm = disbuf
|
|
|
|
vinList[i].ScriptSig.Hex = hex.EncodeToString(v.SignatureScript)
|
|
|
|
}
|
|
|
|
vinList[i].Sequence = v.Sequence
|
|
|
|
}
|
|
|
|
|
2013-12-31 02:06:13 +01:00
|
|
|
return vinList, nil
|
2013-12-31 01:22:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// createVoutList returns a slice of JSON objects for the outputs of the passed
|
|
|
|
// transaction.
|
2013-12-31 03:27:36 +01:00
|
|
|
func createVoutList(mtx *btcwire.MsgTx, net btcwire.BitcoinNet) ([]btcjson.Vout, error) {
|
2013-12-31 01:22:39 +01:00
|
|
|
voutList := make([]btcjson.Vout, len(mtx.TxOut))
|
|
|
|
for i, v := range mtx.TxOut {
|
|
|
|
voutList[i].N = i
|
|
|
|
voutList[i].Value = float64(v.Value) / float64(btcutil.SatoshiPerBitcoin)
|
|
|
|
|
2013-12-31 02:06:13 +01:00
|
|
|
disbuf, err := btcscript.DisasmString(v.PkScript)
|
|
|
|
if err != nil {
|
2013-12-31 03:27:36 +01:00
|
|
|
return nil, btcjson.Error{
|
2013-12-31 02:06:13 +01:00
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
voutList[i].ScriptPubKey.Asm = disbuf
|
|
|
|
voutList[i].ScriptPubKey.Hex = hex.EncodeToString(v.PkScript)
|
|
|
|
|
2014-01-08 03:30:01 +01:00
|
|
|
// Ignore the error here since an error means the script
|
|
|
|
// couldn't parse and there is no additional information about
|
|
|
|
// it anyways.
|
|
|
|
scriptClass, addrs, reqSigs, _ := btcscript.ExtractPkScriptAddrs(v.PkScript, net)
|
|
|
|
voutList[i].ScriptPubKey.Type = scriptClass.String()
|
2014-01-02 06:30:00 +01:00
|
|
|
voutList[i].ScriptPubKey.ReqSigs = reqSigs
|
|
|
|
|
2014-01-08 03:30:01 +01:00
|
|
|
if addrs == nil {
|
2014-01-02 06:30:00 +01:00
|
|
|
voutList[i].ScriptPubKey.Addresses = nil
|
|
|
|
} else {
|
2014-01-08 03:30:01 +01:00
|
|
|
voutList[i].ScriptPubKey.Addresses = make([]string, len(addrs))
|
|
|
|
for j, addr := range addrs {
|
2014-01-03 19:22:28 +01:00
|
|
|
voutList[i].ScriptPubKey.Addresses[j] = addr.EncodeAddress()
|
2014-01-02 06:30:00 +01:00
|
|
|
}
|
2013-12-31 01:22:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-31 02:06:13 +01:00
|
|
|
return voutList, nil
|
2013-12-31 01:22:39 +01:00
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleDecodeRawTransaction handles decoderawtransaction commands.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleDecodeRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-12-31 01:22:39 +01:00
|
|
|
c := cmd.(*btcjson.DecodeRawTransactionCmd)
|
|
|
|
|
|
|
|
// Deserialize the transaction.
|
|
|
|
hexStr := c.HexTx
|
|
|
|
if len(hexStr)%2 != 0 {
|
|
|
|
hexStr = "0" + hexStr
|
|
|
|
}
|
|
|
|
serializedTx, err := hex.DecodeString(hexStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: fmt.Sprintf("argument must be hexadecimal "+
|
|
|
|
"string (not %q)", hexStr),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var mtx btcwire.MsgTx
|
|
|
|
err = mtx.Deserialize(bytes.NewBuffer(serializedTx))
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: "TX decode failed",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
txSha, _ := mtx.TxSha()
|
|
|
|
|
2013-12-31 02:06:13 +01:00
|
|
|
vin, err := createVinList(&mtx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
vout, err := createVoutList(&mtx, s.server.btcnet)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2013-12-31 01:22:39 +01:00
|
|
|
// Create and return the result.
|
|
|
|
txReply := btcjson.TxRawDecodeResult{
|
|
|
|
Txid: txSha.String(),
|
|
|
|
Version: mtx.Version,
|
|
|
|
Locktime: mtx.LockTime,
|
2013-12-31 02:06:13 +01:00
|
|
|
Vin: vin,
|
|
|
|
Vout: vout,
|
2013-12-31 01:22:39 +01:00
|
|
|
}
|
|
|
|
return txReply, nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2014-01-04 06:29:24 +01:00
|
|
|
// handleDecodeScript handles decodescript commands.
|
|
|
|
func handleDecodeScript(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.DecodeScriptCmd)
|
|
|
|
|
|
|
|
// Convert the hex script to bytes.
|
|
|
|
script, err := hex.DecodeString(c.HexScript)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: fmt.Sprintf("argument must be hexadecimal "+
|
|
|
|
"string (not %q)", c.HexScript),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The disassembled string will contain [error] inline if the script
|
|
|
|
// doesn't fully parse, so ignore the error here.
|
|
|
|
disbuf, _ := btcscript.DisasmString(script)
|
|
|
|
|
|
|
|
// Get information about the script.
|
2014-01-08 03:30:01 +01:00
|
|
|
// Ignore the error here since an error means the script couldn't parse
|
|
|
|
// and there is no additinal information about it anyways.
|
2014-01-04 06:29:24 +01:00
|
|
|
net := s.server.btcnet
|
2014-01-08 03:30:01 +01:00
|
|
|
scriptClass, addrs, reqSigs, _ := btcscript.ExtractPkScriptAddrs(script, net)
|
|
|
|
addresses := make([]string, len(addrs))
|
|
|
|
for i, addr := range addrs {
|
|
|
|
addresses[i] = addr.EncodeAddress()
|
2014-01-04 06:29:24 +01:00
|
|
|
}
|
|
|
|
|
2014-01-04 19:42:50 +01:00
|
|
|
// Convert the script itself to a pay-to-script-hash address.
|
|
|
|
p2sh, err := btcutil.NewAddressScriptHash(script, net)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-04 06:29:24 +01:00
|
|
|
// Generate and return the reply.
|
|
|
|
reply := btcjson.DecodeScriptResult{
|
|
|
|
Asm: disbuf,
|
|
|
|
ReqSigs: reqSigs,
|
2014-01-08 03:30:01 +01:00
|
|
|
Type: scriptClass.String(),
|
2014-01-04 06:29:24 +01:00
|
|
|
Addresses: addresses,
|
2014-01-04 19:42:50 +01:00
|
|
|
P2sh: p2sh.EncodeAddress(),
|
2014-01-04 06:29:24 +01:00
|
|
|
}
|
|
|
|
return reply, nil
|
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetBestBlockHash implements the getbestblockhash command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetBestBlockHash(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
sha, _, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting newest sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBestBlockHash
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-07 19:53:22 +01:00
|
|
|
return sha.String(), nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetBlock implements the getblock command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetBlock(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
c := cmd.(*btcjson.GetBlockCmd)
|
|
|
|
sha, err := btcwire.NewShaHashFromStr(c.Hash)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error generating sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
|
|
|
}
|
|
|
|
blk, err := s.server.db.FetchBlockBySha(sha)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error fetching sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
|
|
|
}
|
2013-12-26 18:11:12 +01:00
|
|
|
|
|
|
|
// When the verbose flag isn't set, simply return the network-serialized
|
|
|
|
// block as a hex-encoded string.
|
|
|
|
if !c.Verbose {
|
2013-12-27 05:33:31 +01:00
|
|
|
blkHex, err := messageToHex(blk.MsgBlock())
|
2013-12-26 18:11:12 +01:00
|
|
|
if err != nil {
|
2013-12-27 05:33:31 +01:00
|
|
|
return nil, err
|
2013-12-26 18:11:12 +01:00
|
|
|
}
|
|
|
|
return blkHex, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The verbose flag is set, so generate the JSON object and return it.
|
2013-10-29 16:42:34 +01:00
|
|
|
buf, err := blk.Bytes()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error fetching block: %v", err)
|
2013-12-26 18:11:12 +01:00
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-12-26 18:11:12 +01:00
|
|
|
idx := blk.Height()
|
2013-10-29 16:42:34 +01:00
|
|
|
_, maxidx, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Cannot get newest sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
blockHeader := &blk.MsgBlock().Header
|
|
|
|
blockReply := btcjson.BlockResult{
|
|
|
|
Hash: c.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,
|
|
|
|
Size: len(buf),
|
|
|
|
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
|
|
|
|
Difficulty: getDifficultyRatio(blockHeader.Bits),
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-12-08 20:57:14 +01:00
|
|
|
if !c.VerboseTx {
|
|
|
|
txList, _ := blk.TxShas()
|
|
|
|
|
|
|
|
txNames := make([]string, len(txList))
|
|
|
|
for i, v := range txList {
|
|
|
|
txNames[i] = v.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
blockReply.Tx = txNames
|
|
|
|
} else {
|
|
|
|
txns := blk.Transactions()
|
|
|
|
rawTxns := make([]btcjson.TxRawResult, len(txns))
|
|
|
|
for i, tx := range txns {
|
|
|
|
txSha := tx.Sha().String()
|
|
|
|
mtx := tx.MsgTx()
|
|
|
|
|
2013-12-27 06:22:41 +01:00
|
|
|
rawTxn, err := createTxRawResult(s.server.btcnet, txSha,
|
|
|
|
mtx, blk, maxidx, sha)
|
2013-12-08 20:57:14 +01:00
|
|
|
if err != nil {
|
2013-12-27 06:22:41 +01:00
|
|
|
rpcsLog.Errorf("Cannot create TxRawResult for "+
|
|
|
|
"transaction %s: %v", txSha, err)
|
2013-12-08 20:57:14 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rawTxns[i] = *rawTxn
|
|
|
|
}
|
|
|
|
blockReply.RawTx = rawTxns
|
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// Get next block unless we are already at the top.
|
|
|
|
if idx < maxidx {
|
|
|
|
var shaNext *btcwire.ShaHash
|
|
|
|
shaNext, err = s.server.db.FetchBlockShaByHeight(int64(idx + 1))
|
2013-08-06 23:55:22 +02:00
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("No next block: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-10-29 16:42:34 +01:00
|
|
|
blockReply.NextHash = shaNext.String()
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return blockReply, nil
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetBlockCount implements the getblockcount command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetBlockCount(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
_, maxidx, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting newest sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockCount
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return maxidx, nil
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetBlockHash implements the getblockhash command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetBlockHash(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
c := cmd.(*btcjson.GetBlockHashCmd)
|
|
|
|
sha, err := s.server.db.FetchBlockShaByHeight(c.Index)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting block: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrOutOfRange
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return sha.String(), nil
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetConnectionCount implements the getconnectioncount command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetConnectionCount(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-21 19:45:30 +02:00
|
|
|
return s.server.ConnectedCount(), nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetDifficulty implements the getdifficulty command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetDifficulty(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
sha, _, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrDifficulty
|
|
|
|
}
|
|
|
|
blk, err := s.server.db.FetchBlockBySha(sha)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting block: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrDifficulty
|
|
|
|
}
|
|
|
|
blockHeader := &blk.MsgBlock().Header
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return getDifficultyRatio(blockHeader.Bits), nil
|
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetGenerate implements the getgenerate command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetGenerate(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
// btcd does not do mining so we can hardcode replies here.
|
|
|
|
return false, nil
|
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetHashesPerSec implements the gethashespersec command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetHashesPerSec(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
// btcd does not do mining so we can hardcode replies here.
|
|
|
|
return 0, nil
|
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-21 19:45:30 +02:00
|
|
|
// handleGetPeerInfo implements the getpeerinfo command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetPeerInfo(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 18:18:53 +01:00
|
|
|
return s.server.PeerInfo(), nil
|
2013-10-21 19:45:30 +02:00
|
|
|
}
|
|
|
|
|
2013-12-25 19:28:54 +01:00
|
|
|
// mempoolDescriptor describes a JSON object which is returned for each
|
|
|
|
// transaction in the memory pool in response to a getrawmempool command with
|
|
|
|
// the verbose flag set.
|
2013-12-11 18:32:16 +01:00
|
|
|
type mempoolDescriptor struct {
|
2013-12-17 15:02:35 +01:00
|
|
|
Size int `json:"size"`
|
2013-12-25 19:28:54 +01:00
|
|
|
Fee float64 `json:"fee"`
|
2013-12-17 15:02:35 +01:00
|
|
|
Time int64 `json:"time"`
|
|
|
|
Height int64 `json:"height"`
|
|
|
|
StartingPriority int `json:"startingpriority"`
|
|
|
|
CurrentPriority int `json:"currentpriority"`
|
|
|
|
Depends []string `json:"depends"`
|
2013-12-11 18:32:16 +01:00
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetRawMempool implements the getrawmempool command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetRawMempool(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-12-11 18:32:16 +01:00
|
|
|
c := cmd.(*btcjson.GetRawMempoolCmd)
|
|
|
|
descs := s.server.txMemPool.TxDescs()
|
|
|
|
|
|
|
|
if c.Verbose {
|
|
|
|
result := make(map[string]*mempoolDescriptor, len(descs))
|
|
|
|
for _, desc := range descs {
|
|
|
|
mpd := &mempoolDescriptor{
|
2013-12-25 19:28:54 +01:00
|
|
|
Size: desc.Tx.MsgTx().SerializeSize(),
|
|
|
|
Fee: float64(desc.Fee) /
|
|
|
|
float64(btcutil.SatoshiPerBitcoin),
|
2013-12-17 15:02:35 +01:00
|
|
|
Time: desc.Added.Unix(),
|
|
|
|
Height: desc.Height,
|
2013-12-11 18:32:16 +01:00
|
|
|
StartingPriority: 0, // We don't mine.
|
2013-12-17 15:02:35 +01:00
|
|
|
CurrentPriority: 0, // We don't mine.
|
2013-12-11 18:32:16 +01:00
|
|
|
}
|
|
|
|
for _, txIn := range desc.Tx.MsgTx().TxIn {
|
|
|
|
hash := &txIn.PreviousOutpoint.Hash
|
|
|
|
if s.server.txMemPool.HaveTransaction(hash) {
|
|
|
|
mpd.Depends = append(mpd.Depends,
|
|
|
|
hash.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result[desc.Tx.Sha().String()] = mpd
|
|
|
|
}
|
|
|
|
|
2013-12-17 15:02:35 +01:00
|
|
|
return result, nil
|
2013-12-11 18:32:16 +01:00
|
|
|
}
|
2013-12-25 19:28:54 +01:00
|
|
|
|
|
|
|
// The response is simply an array of the transaction hashes if the
|
|
|
|
// verbose flag is not set.
|
2013-12-11 18:32:16 +01:00
|
|
|
hashStrings := make([]string, len(descs))
|
2013-12-17 15:02:35 +01:00
|
|
|
for i := range hashStrings {
|
2013-12-11 18:32:16 +01:00
|
|
|
hashStrings[i] = descs[i].Tx.Sha().String()
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-12-11 18:32:16 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return hashStrings, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetRawTransaction implements the getrawtransaction command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleGetRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
c := cmd.(*btcjson.GetRawTransactionCmd)
|
2013-11-12 20:03:23 +01:00
|
|
|
|
2013-12-27 06:22:41 +01:00
|
|
|
// Convert the provided transaction hash hex to a ShaHash.
|
|
|
|
txSha, err := btcwire.NewShaHashFromStr(c.Txid)
|
|
|
|
if err != nil {
|
|
|
|
rpcsLog.Errorf("Error generating sha: %v", err)
|
|
|
|
return nil, btcjson.Error{
|
|
|
|
Code: btcjson.ErrBlockNotFound.Code,
|
|
|
|
Message: "Parameter 1 must be a hexaecimal string",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to fetch the transaction from the memory pool and if that fails,
|
|
|
|
// try the block database.
|
2013-12-08 20:57:14 +01:00
|
|
|
var mtx *btcwire.MsgTx
|
|
|
|
var blksha *btcwire.ShaHash
|
|
|
|
tx, err := s.server.txMemPool.FetchTransaction(txSha)
|
|
|
|
if err != nil {
|
|
|
|
txList, err := s.server.db.FetchTxBySha(txSha)
|
|
|
|
if err != nil || len(txList) == 0 {
|
|
|
|
rpcsLog.Errorf("Error fetching tx: %v", err)
|
|
|
|
return nil, btcjson.ErrNoTxInfo
|
2013-10-29 01:43:09 +01:00
|
|
|
}
|
|
|
|
|
2013-12-08 20:57:14 +01:00
|
|
|
lastTx := len(txList) - 1
|
|
|
|
mtx = txList[lastTx].Tx
|
|
|
|
|
|
|
|
blksha = txList[lastTx].BlkSha
|
|
|
|
} else {
|
|
|
|
mtx = tx.MsgTx()
|
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-12-27 06:22:41 +01:00
|
|
|
// When the verbose flag isn't set, simply return the network-serialized
|
|
|
|
// transaction as a hex-encoded string.
|
|
|
|
if !c.Verbose {
|
|
|
|
mtxHex, err := messageToHex(mtx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return mtxHex, nil
|
|
|
|
}
|
|
|
|
|
2013-12-08 20:57:14 +01:00
|
|
|
var blk *btcutil.Block
|
|
|
|
var maxidx int64
|
|
|
|
if blksha != nil {
|
|
|
|
blk, err = s.server.db.FetchBlockBySha(blksha)
|
|
|
|
if err != nil {
|
|
|
|
rpcsLog.Errorf("Error fetching sha: %v", err)
|
|
|
|
return nil, btcjson.ErrBlockNotFound
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
|
2013-12-08 20:57:14 +01:00
|
|
|
_, maxidx, err = s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
|
|
|
rpcsLog.Errorf("Cannot get newest sha: %v", err)
|
|
|
|
return nil, btcjson.ErrNoNewestBlockInfo
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-12-08 20:57:14 +01:00
|
|
|
}
|
|
|
|
|
2013-12-27 06:22:41 +01:00
|
|
|
rawTxn, jsonErr := createTxRawResult(s.server.btcnet, c.Txid, mtx, blk, maxidx, blksha)
|
2013-12-08 20:57:14 +01:00
|
|
|
if err != nil {
|
|
|
|
rpcsLog.Errorf("Cannot create TxRawResult for txSha=%s: %v", txSha, err)
|
2013-12-31 03:27:36 +01:00
|
|
|
return nil, jsonErr
|
2013-12-08 20:57:14 +01:00
|
|
|
}
|
|
|
|
return *rawTxn, nil
|
|
|
|
}
|
|
|
|
|
2013-12-27 06:22:41 +01:00
|
|
|
// createTxRawResult converts the passed transaction and associated parameters
|
|
|
|
// to a raw transaction JSON object.
|
2013-12-31 03:27:36 +01:00
|
|
|
func createTxRawResult(net btcwire.BitcoinNet, txSha string, mtx *btcwire.MsgTx, blk *btcutil.Block, maxidx int64, blksha *btcwire.ShaHash) (*btcjson.TxRawResult, error) {
|
2013-12-27 05:33:31 +01:00
|
|
|
mtxHex, err := messageToHex(mtx)
|
2013-12-08 20:57:14 +01:00
|
|
|
if err != nil {
|
2013-12-27 05:33:31 +01:00
|
|
|
return nil, err
|
2013-12-08 20:57:14 +01:00
|
|
|
}
|
|
|
|
|
2013-12-31 02:06:13 +01:00
|
|
|
vin, err := createVinList(mtx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
vout, err := createVoutList(mtx, net)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2013-12-08 20:57:14 +01:00
|
|
|
txReply := &btcjson.TxRawResult{
|
|
|
|
Hex: mtxHex,
|
|
|
|
Txid: txSha,
|
2013-12-31 02:06:13 +01:00
|
|
|
Vout: vout,
|
|
|
|
Vin: vin,
|
2013-12-08 20:57:14 +01:00
|
|
|
Version: mtx.Version,
|
|
|
|
LockTime: mtx.LockTime,
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-12-08 20:57:14 +01:00
|
|
|
|
|
|
|
if blk != nil {
|
|
|
|
blockHeader := &blk.MsgBlock().Header
|
|
|
|
idx := blk.Height()
|
|
|
|
|
2013-12-27 06:22:41 +01:00
|
|
|
// This is not a typo, they are identical in bitcoind as well.
|
2013-12-08 20:57:14 +01:00
|
|
|
txReply.Time = blockHeader.Timestamp.Unix()
|
|
|
|
txReply.Blocktime = blockHeader.Timestamp.Unix()
|
|
|
|
txReply.BlockHash = blksha.String()
|
|
|
|
txReply.Confirmations = uint64(1 + maxidx - idx)
|
|
|
|
}
|
|
|
|
|
|
|
|
return txReply, nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// handleSendRawTransaction implements the sendrawtransaction command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
c := cmd.(*btcjson.SendRawTransactionCmd)
|
|
|
|
// Deserialize and send off to tx relay
|
|
|
|
serializedTx, err := hex.DecodeString(c.HexTx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.ErrDecodeHexString
|
|
|
|
}
|
|
|
|
msgtx := btcwire.NewMsgTx()
|
|
|
|
err = msgtx.Deserialize(bytes.NewBuffer(serializedTx))
|
|
|
|
if err != nil {
|
|
|
|
err := btcjson.Error{
|
2013-10-30 01:41:38 +01:00
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
2013-11-07 16:34:55 +01:00
|
|
|
Message: "TX decode failed",
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
2013-10-31 06:28:37 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
tx := btcutil.NewTx(msgtx)
|
|
|
|
err = s.server.txMemPool.ProcessTransaction(tx)
|
|
|
|
if err != nil {
|
2013-10-30 20:11:11 +01:00
|
|
|
// When the error is a rule error, it means the transaction was
|
|
|
|
// simply rejected as opposed to something actually going wrong,
|
|
|
|
// so log it as such. Otherwise, something really did go wrong,
|
|
|
|
// so log it as an actual error.
|
|
|
|
if _, ok := err.(TxRuleError); ok {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debugf("Rejected transaction %v: %v", tx.Sha(),
|
2013-10-31 00:40:55 +01:00
|
|
|
err)
|
2013-10-30 20:11:11 +01:00
|
|
|
} else {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Failed to process transaction %v: %v",
|
2013-10-31 00:40:55 +01:00
|
|
|
tx.Sha(), err)
|
2013-11-07 16:34:55 +01:00
|
|
|
err = btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: "TX rejected",
|
|
|
|
}
|
|
|
|
return nil, err
|
2013-10-30 20:11:11 +01:00
|
|
|
}
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-10-31 06:28:37 +01:00
|
|
|
return tx.Sha().String(), nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// handleSetGenerate implements the setgenerate command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleSetGenerate(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
// btcd does not do mining so we can hardcode replies here.
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleStop implements the stop command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleStop(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-10-29 16:42:34 +01:00
|
|
|
s.server.Stop()
|
|
|
|
return "btcd stopping.", nil
|
|
|
|
}
|
|
|
|
|
2014-01-15 16:01:12 +01:00
|
|
|
// handleSubmitBlock implements the submitblock command.
|
|
|
|
func handleSubmitBlock(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.SubmitBlockCmd)
|
|
|
|
// Deserialize and send off to block processor.
|
|
|
|
serializedBlock, err := hex.DecodeString(c.HexBlock)
|
|
|
|
if err != nil {
|
|
|
|
err := btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: "Block decode failed",
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
block, err := btcutil.NewBlockFromBytes(serializedBlock)
|
|
|
|
if err != nil {
|
|
|
|
err := btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: "Block decode failed",
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = s.server.blockManager.blockChain.ProcessBlock(block, false)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Sprintf("rejected: %s", err.Error()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2013-11-12 17:39:10 +01:00
|
|
|
func verifyChain(db btcdb.Db, level, depth int32) error {
|
|
|
|
_, curheight64, err := db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to fetch current block "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"height: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
curheight := int32(curheight64)
|
|
|
|
|
|
|
|
if depth > curheight {
|
|
|
|
depth = curheight
|
|
|
|
}
|
|
|
|
|
|
|
|
for height := curheight; height > (curheight - depth); height-- {
|
|
|
|
// Level 0 just looks up the block.
|
|
|
|
sha, err := db.FetchBlockShaByHeight(int64(height))
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to fetch block at "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"height %d: %v", height, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
block, err := db.FetchBlockBySha(sha)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to fetch block at "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"sha %v height %d: %v", sha, height, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Level 1 does basic chain sanity checks.
|
|
|
|
if level > 0 {
|
|
|
|
err := btcchain.CheckBlockSanity(block,
|
|
|
|
activeNetParams.powLimit)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"validate block at sha %v height "+
|
|
|
|
"%s: %v", sha, height, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("Chain verify completed successfully")
|
2013-11-12 17:39:10 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-12-31 21:39:17 +01:00
|
|
|
func handleVerifyChain(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) {
|
2013-11-12 17:39:10 +01:00
|
|
|
c := cmd.(*btcjson.VerifyChainCmd)
|
|
|
|
|
|
|
|
err := verifyChain(s.server.db, c.CheckLevel, c.CheckDepth)
|
2013-12-30 17:59:14 +01:00
|
|
|
return err == nil, nil
|
2013-11-12 17:39:10 +01:00
|
|
|
}
|
|
|
|
|
2013-11-07 18:47:54 +01:00
|
|
|
// parseCmd parses a marshaled known command, returning any errors as a
|
|
|
|
// btcjson.Error that can be used in replies. The returned cmd may still
|
|
|
|
// be non-nil if b is at least a valid marshaled JSON-RPC message.
|
|
|
|
func parseCmd(b []byte) (btcjson.Cmd, *btcjson.Error) {
|
|
|
|
cmd, err := btcjson.ParseMarshaledCmd(b)
|
2013-10-29 16:42:34 +01:00
|
|
|
if err != nil {
|
2013-11-07 18:47:54 +01:00
|
|
|
jsonErr, ok := err.(btcjson.Error)
|
|
|
|
if !ok {
|
|
|
|
jsonErr = btcjson.Error{
|
|
|
|
Code: btcjson.ErrParse.Code,
|
|
|
|
Message: err.Error(),
|
2013-10-30 04:23:22 +01:00
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
}
|
2013-11-07 18:47:54 +01:00
|
|
|
return cmd, &jsonErr
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-11-07 18:47:54 +01:00
|
|
|
return cmd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// standardCmdReply checks that a parsed command is a standard
|
|
|
|
// Bitcoin JSON-RPC command and runs the proper handler to reply to the
|
|
|
|
// command.
|
2013-12-31 21:39:17 +01:00
|
|
|
func standardCmdReply(cmd btcjson.Cmd, s *rpcServer) (reply btcjson.Reply) {
|
2013-10-29 16:42:34 +01:00
|
|
|
id := cmd.Id()
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Id = &id
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-12-31 22:53:19 +01:00
|
|
|
handler, ok := rpcHandlers[cmd.Method()]
|
2013-10-29 16:42:34 +01:00
|
|
|
if !ok {
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Error = &btcjson.ErrMethodNotFound
|
|
|
|
return reply
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
|
2013-12-31 21:39:17 +01:00
|
|
|
result, err := handler(s, cmd)
|
2013-10-29 16:42:34 +01:00
|
|
|
if err != nil {
|
2013-11-07 18:47:54 +01:00
|
|
|
jsonErr, ok := err.(btcjson.Error)
|
|
|
|
if !ok {
|
2013-10-29 16:42:34 +01:00
|
|
|
// 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.
|
2013-11-07 18:47:54 +01:00
|
|
|
jsonErr = btcjson.Error{
|
2013-10-30 01:41:38 +01:00
|
|
|
Code: btcjson.ErrInternal.Code,
|
2013-10-29 16:42:34 +01:00
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Error = &jsonErr
|
2013-10-29 16:42:34 +01:00
|
|
|
} else {
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Result = result
|
|
|
|
}
|
|
|
|
return reply
|
|
|
|
}
|
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
// 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 {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Cannot get difficulty: %v", err)
|
2013-08-06 23:55:22 +02:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return diff
|
|
|
|
}
|