You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3806 lines
122 KiB
3806 lines
122 KiB
// Copyright (c) 2013-2015 The btcsuite developers |
|
// Use of this source code is governed by an ISC |
|
// license that can be found in the LICENSE file. |
|
|
|
package main |
|
|
|
import ( |
|
"bytes" |
|
"crypto/subtle" |
|
"crypto/tls" |
|
"encoding/base64" |
|
"encoding/binary" |
|
"encoding/hex" |
|
"encoding/json" |
|
"errors" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"math/big" |
|
"math/rand" |
|
"net" |
|
"net/http" |
|
"os" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"sync/atomic" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/blockchain" |
|
"github.com/btcsuite/btcd/btcec" |
|
"github.com/btcsuite/btcd/btcjson" |
|
"github.com/btcsuite/btcd/chaincfg" |
|
"github.com/btcsuite/btcd/database" |
|
"github.com/btcsuite/btcd/txscript" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/btcsuite/fastsha256" |
|
"github.com/btcsuite/websocket" |
|
) |
|
|
|
const ( |
|
// rpcAuthTimeoutSeconds is the number of seconds a connection to the |
|
// RPC server is allowed to stay open without authenticating before it |
|
// is closed. |
|
rpcAuthTimeoutSeconds = 10 |
|
|
|
// uint256Size is the number of bytes needed to represent an unsigned |
|
// 256-bit integer. |
|
uint256Size = 32 |
|
|
|
// getworkDataLen is the length of the data field of the getwork RPC. |
|
// It consists of the serialized block header plus the internal sha256 |
|
// padding. The internal sha256 padding consists of a single 1 bit |
|
// followed by enough zeros to pad the message out to 56 bytes followed |
|
// by length of the message in bits encoded as a big-endian uint64 |
|
// (8 bytes). Thus, the resulting length is a multiple of the sha256 |
|
// block size (64 bytes). |
|
getworkDataLen = (1 + ((wire.MaxBlockHeaderPayload + 8) / |
|
fastsha256.BlockSize)) * fastsha256.BlockSize |
|
|
|
// hash1Len is the length of the hash1 field of the getwork RPC. It |
|
// consists of a zero hash plus the internal sha256 padding. See |
|
// the getworkDataLen comment for details about the internal sha256 |
|
// padding format. |
|
hash1Len = (1 + ((wire.HashSize + 8) / fastsha256.BlockSize)) * |
|
fastsha256.BlockSize |
|
|
|
// gbtNonceRange is two 32-bit big-endian hexadecimal integers which |
|
// represent the valid ranges of nonces returned by the getblocktemplate |
|
// RPC. |
|
gbtNonceRange = "00000000ffffffff" |
|
|
|
// gbtRegenerateSeconds is the number of seconds that must pass before |
|
// a new template is generated when the previous block hash has not |
|
// changed and there have been changes to the available transactions |
|
// in the memory pool. |
|
gbtRegenerateSeconds = 60 |
|
) |
|
|
|
var ( |
|
// gbtMutableFields are the manipulations the server allows to be made |
|
// to block templates generated by the getblocktemplate RPC. It is |
|
// declared here to avoid the overhead of creating the slice on every |
|
// invocation for constant data. |
|
gbtMutableFields = []string{ |
|
"time", "transactions/add", "prevblock", "coinbase/append", |
|
} |
|
|
|
// gbtCoinbaseAux describes additional data that miners should include |
|
// in the coinbase signature script. It is declared here to avoid the |
|
// overhead of creating a new object on every invocation for constant |
|
// data. |
|
gbtCoinbaseAux = &btcjson.GetBlockTemplateResultAux{ |
|
Flags: hex.EncodeToString(builderScript(txscript. |
|
NewScriptBuilder().AddData([]byte(coinbaseFlags)))), |
|
} |
|
|
|
// gbtCapabilities describes additional capabilities returned with a |
|
// block template generated by the getblocktemplate RPC. It is |
|
// declared here to avoid the overhead of creating the slice on every |
|
// invocation for constant data. |
|
gbtCapabilities = []string{"proposal"} |
|
) |
|
|
|
// Errors |
|
var ( |
|
// ErrRPCUnimplemented is an error returned to RPC clients when the |
|
// provided command is recognized, but not implemented. |
|
ErrRPCUnimplemented = &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCUnimplemented, |
|
Message: "Command unimplemented", |
|
} |
|
|
|
// ErrRPCNoWallet is an error returned to RPC clients when the provided |
|
// command is recognized as a wallet command. |
|
ErrRPCNoWallet = &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCNoWallet, |
|
Message: "This implementation does not implement wallet commands", |
|
} |
|
) |
|
|
|
type commandHandler func(*rpcServer, interface{}, <-chan struct{}) (interface{}, error) |
|
|
|
// rpcHandlers maps RPC command strings to appropriate handler functions. |
|
// This is set by init because help references rpcHandlers and thus causes |
|
// a dependency loop. |
|
var rpcHandlers map[string]commandHandler |
|
var rpcHandlersBeforeInit = map[string]commandHandler{ |
|
"addnode": handleAddNode, |
|
"createrawtransaction": handleCreateRawTransaction, |
|
"debuglevel": handleDebugLevel, |
|
"decoderawtransaction": handleDecodeRawTransaction, |
|
"decodescript": handleDecodeScript, |
|
"generate": handleGenerate, |
|
"getaddednodeinfo": handleGetAddedNodeInfo, |
|
"getbestblock": handleGetBestBlock, |
|
"getbestblockhash": handleGetBestBlockHash, |
|
"getblock": handleGetBlock, |
|
"getblockcount": handleGetBlockCount, |
|
"getblockhash": handleGetBlockHash, |
|
"getblockheader": handleGetBlockHeader, |
|
"getblocktemplate": handleGetBlockTemplate, |
|
"getconnectioncount": handleGetConnectionCount, |
|
"getcurrentnet": handleGetCurrentNet, |
|
"getdifficulty": handleGetDifficulty, |
|
"getgenerate": handleGetGenerate, |
|
"gethashespersec": handleGetHashesPerSec, |
|
"getinfo": handleGetInfo, |
|
"getmempoolinfo": handleGetMempoolInfo, |
|
"getmininginfo": handleGetMiningInfo, |
|
"getnettotals": handleGetNetTotals, |
|
"getnetworkhashps": handleGetNetworkHashPS, |
|
"getpeerinfo": handleGetPeerInfo, |
|
"getrawmempool": handleGetRawMempool, |
|
"getrawtransaction": handleGetRawTransaction, |
|
"gettxout": handleGetTxOut, |
|
"getwork": handleGetWork, |
|
"help": handleHelp, |
|
"node": handleNode, |
|
"ping": handlePing, |
|
"searchrawtransactions": handleSearchRawTransactions, |
|
"sendrawtransaction": handleSendRawTransaction, |
|
"setgenerate": handleSetGenerate, |
|
"stop": handleStop, |
|
"submitblock": handleSubmitBlock, |
|
"validateaddress": handleValidateAddress, |
|
"verifychain": handleVerifyChain, |
|
"verifymessage": handleVerifyMessage, |
|
} |
|
|
|
// list of commands that we recognise, but for which btcd has no support because |
|
// it lacks support for wallet functionality. For these commands the user |
|
// should ask a connected instance of btcwallet. |
|
var rpcAskWallet = map[string]struct{}{ |
|
"addmultisigaddress": struct{}{}, |
|
"backupwallet": struct{}{}, |
|
"createencryptedwallet": struct{}{}, |
|
"createmultisig": struct{}{}, |
|
"dumpprivkey": struct{}{}, |
|
"dumpwallet": struct{}{}, |
|
"encryptwallet": struct{}{}, |
|
"getaccount": struct{}{}, |
|
"getaccountaddress": struct{}{}, |
|
"getaddressesbyaccount": struct{}{}, |
|
"getbalance": struct{}{}, |
|
"getnewaddress": struct{}{}, |
|
"getrawchangeaddress": struct{}{}, |
|
"getreceivedbyaccount": struct{}{}, |
|
"getreceivedbyaddress": struct{}{}, |
|
"gettransaction": struct{}{}, |
|
"gettxoutsetinfo": struct{}{}, |
|
"getunconfirmedbalance": struct{}{}, |
|
"getwalletinfo": struct{}{}, |
|
"importprivkey": struct{}{}, |
|
"importwallet": struct{}{}, |
|
"keypoolrefill": struct{}{}, |
|
"listaccounts": struct{}{}, |
|
"listaddressgroupings": struct{}{}, |
|
"listlockunspent": struct{}{}, |
|
"listreceivedbyaccount": struct{}{}, |
|
"listreceivedbyaddress": struct{}{}, |
|
"listsinceblock": struct{}{}, |
|
"listtransactions": struct{}{}, |
|
"listunspent": struct{}{}, |
|
"lockunspent": struct{}{}, |
|
"move": struct{}{}, |
|
"sendfrom": struct{}{}, |
|
"sendmany": struct{}{}, |
|
"sendtoaddress": struct{}{}, |
|
"setaccount": struct{}{}, |
|
"settxfee": struct{}{}, |
|
"signmessage": struct{}{}, |
|
"signrawtransaction": struct{}{}, |
|
"walletlock": struct{}{}, |
|
"walletpassphrase": struct{}{}, |
|
"walletpassphrasechange": struct{}{}, |
|
} |
|
|
|
// Commands that are currently unimplemented, but should ultimately be. |
|
var rpcUnimplemented = map[string]struct{}{ |
|
"estimatefee": struct{}{}, |
|
"estimatepriority": struct{}{}, |
|
"getblockchaininfo": struct{}{}, |
|
"getchaintips": struct{}{}, |
|
"getnetworkinfo": struct{}{}, |
|
} |
|
|
|
// Commands that are available to a limited user |
|
var rpcLimited = map[string]struct{}{ |
|
// Websockets commands |
|
"notifyblocks": struct{}{}, |
|
"notifynewtransactions": struct{}{}, |
|
"notifyreceived": struct{}{}, |
|
"notifyspent": struct{}{}, |
|
"rescan": struct{}{}, |
|
|
|
// Websockets AND HTTP/S commands |
|
"help": struct{}{}, |
|
|
|
// HTTP/S-only commands |
|
"createrawtransaction": struct{}{}, |
|
"decoderawtransaction": struct{}{}, |
|
"decodescript": struct{}{}, |
|
"getbestblock": struct{}{}, |
|
"getbestblockhash": struct{}{}, |
|
"getblock": struct{}{}, |
|
"getblockcount": struct{}{}, |
|
"getblockhash": struct{}{}, |
|
"getcurrentnet": struct{}{}, |
|
"getdifficulty": struct{}{}, |
|
"getinfo": struct{}{}, |
|
"getnettotals": struct{}{}, |
|
"getnetworkhashps": struct{}{}, |
|
"getrawmempool": struct{}{}, |
|
"getrawtransaction": struct{}{}, |
|
"gettxout": struct{}{}, |
|
"searchrawtransactions": struct{}{}, |
|
"sendrawtransaction": struct{}{}, |
|
"submitblock": struct{}{}, |
|
"validateaddress": struct{}{}, |
|
"verifymessage": struct{}{}, |
|
} |
|
|
|
// builderScript is a convenience function which is used for hard-coded scripts |
|
// built with the script builder. Any errors are converted to a panic since it |
|
// is only, and must only, be used with hard-coded, and therefore, known good, |
|
// scripts. |
|
func builderScript(builder *txscript.ScriptBuilder) []byte { |
|
script, err := builder.Script() |
|
if err != nil { |
|
panic(err) |
|
} |
|
return script |
|
} |
|
|
|
// internalRPCError is a convenience function to convert an internal error to |
|
// an RPC error with the appropriate code set. It also logs the error to the |
|
// RPC server subsystem since internal errors really should not occur. The |
|
// context parameter is only used in the log message and may be empty if it's |
|
// not needed. |
|
func internalRPCError(errStr, context string) *btcjson.RPCError { |
|
logStr := errStr |
|
if context != "" { |
|
logStr = context + ": " + errStr |
|
} |
|
rpcsLog.Error(logStr) |
|
return btcjson.NewRPCError(btcjson.ErrRPCInternal.Code, errStr) |
|
} |
|
|
|
// rpcDecodeHexError is a convenience function for returning a nicely formatted |
|
// RPC error which indicates the provided hex string failed to decode. |
|
func rpcDecodeHexError(gotHex string) *btcjson.RPCError { |
|
return btcjson.NewRPCError(btcjson.ErrRPCDecodeHexString, |
|
fmt.Sprintf("Argument must be hexadecimal string (not %q)", |
|
gotHex)) |
|
} |
|
|
|
// workStateBlockInfo houses information about how to reconstruct a block given |
|
// its template and signature script. |
|
type workStateBlockInfo struct { |
|
msgBlock *wire.MsgBlock |
|
signatureScript []byte |
|
} |
|
|
|
// workState houses state that is used in between multiple RPC invocations to |
|
// getwork. |
|
type workState struct { |
|
sync.Mutex |
|
lastTxUpdate time.Time |
|
lastGenerated time.Time |
|
prevHash *wire.ShaHash |
|
msgBlock *wire.MsgBlock |
|
extraNonce uint64 |
|
blockInfo map[wire.ShaHash]*workStateBlockInfo |
|
} |
|
|
|
// newWorkState returns a new instance of a workState with all internal fields |
|
// initialized and ready to use. |
|
func newWorkState() *workState { |
|
return &workState{ |
|
blockInfo: make(map[wire.ShaHash]*workStateBlockInfo), |
|
} |
|
} |
|
|
|
// gbtWorkState houses state that is used in between multiple RPC invocations to |
|
// getblocktemplate. |
|
type gbtWorkState struct { |
|
sync.Mutex |
|
lastTxUpdate time.Time |
|
lastGenerated time.Time |
|
prevHash *wire.ShaHash |
|
minTimestamp time.Time |
|
template *BlockTemplate |
|
notifyMap map[wire.ShaHash]map[int64]chan struct{} |
|
timeSource blockchain.MedianTimeSource |
|
} |
|
|
|
// newGbtWorkState returns a new instance of a gbtWorkState with all internal |
|
// fields initialized and ready to use. |
|
func newGbtWorkState(timeSource blockchain.MedianTimeSource) *gbtWorkState { |
|
return &gbtWorkState{ |
|
notifyMap: make(map[wire.ShaHash]map[int64]chan struct{}), |
|
timeSource: timeSource, |
|
} |
|
} |
|
|
|
// handleUnimplemented is the handler for commands that should ultimately be |
|
// supported but are not yet implemented. |
|
func handleUnimplemented(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
return nil, ErrRPCUnimplemented |
|
} |
|
|
|
// handleAskWallet is the handler for commands that are recognized as valid, but |
|
// are unable to answer correctly since it involves wallet state. |
|
// These commands will be implemented in btcwallet. |
|
func handleAskWallet(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
return nil, ErrRPCNoWallet |
|
} |
|
|
|
// handleAddNode handles addnode commands. |
|
func handleAddNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.AddNodeCmd) |
|
|
|
addr := normalizeAddress(c.Addr, activeNetParams.DefaultPort) |
|
var err error |
|
switch c.SubCmd { |
|
case "add": |
|
err = s.server.ConnectNode(addr, true) |
|
case "remove": |
|
err = s.server.RemoveNodeByAddr(addr) |
|
case "onetry": |
|
err = s.server.ConnectNode(addr, false) |
|
default: |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: "invalid subcommand for addnode", |
|
} |
|
} |
|
|
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: err.Error(), |
|
} |
|
} |
|
|
|
// no data returned unless an error. |
|
return nil, nil |
|
} |
|
|
|
// handleNode handles node commands. |
|
func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.NodeCmd) |
|
|
|
var addr string |
|
var nodeId uint64 |
|
var errN, err error |
|
switch c.SubCmd { |
|
case "disconnect": |
|
// If we have a valid uint disconnect by node id. Otherwise, |
|
// attempt to disconnect by address, returning an error if a |
|
// valid IP address is not supplied. |
|
if nodeId, errN = strconv.ParseUint(c.Target, 10, 32); errN == nil { |
|
err = s.server.DisconnectNodeById(int32(nodeId)) |
|
} else { |
|
if _, _, errP := net.SplitHostPort(c.Target); errP == nil || net.ParseIP(c.Target) != nil { |
|
addr = normalizeAddress(c.Target, activeNetParams.DefaultPort) |
|
err = s.server.DisconnectNodeByAddr(addr) |
|
} else { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: "invalid address or node ID", |
|
} |
|
} |
|
} |
|
if err != nil && peerExists(s.server.PeerInfo(), addr, int32(nodeId)) { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCMisc, |
|
Message: "can't disconnect a permanent peer, use remove", |
|
} |
|
} |
|
case "remove": |
|
// If we have a valid uint disconnect by node id. Otherwise, |
|
// attempt to disconnect by address, returning an error if a |
|
// valid IP address is not supplied. |
|
if nodeId, errN = strconv.ParseUint(c.Target, 10, 32); errN == nil { |
|
err = s.server.RemoveNodeById(int32(nodeId)) |
|
} else { |
|
if _, _, errP := net.SplitHostPort(c.Target); errP == nil || net.ParseIP(c.Target) != nil { |
|
addr = normalizeAddress(c.Target, activeNetParams.DefaultPort) |
|
err = s.server.RemoveNodeByAddr(addr) |
|
} else { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: "invalid address or node ID", |
|
} |
|
} |
|
} |
|
if err != nil && peerExists(s.server.PeerInfo(), addr, int32(nodeId)) { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCMisc, |
|
Message: "can't remove a temporary peer, use disconnect", |
|
} |
|
} |
|
case "connect": |
|
addr = normalizeAddress(c.Target, activeNetParams.DefaultPort) |
|
|
|
// Default to temporary connections. |
|
subCmd := "temp" |
|
if c.ConnectSubCmd != nil { |
|
subCmd = *c.ConnectSubCmd |
|
} |
|
|
|
switch subCmd { |
|
case "perm", "temp": |
|
err = s.server.ConnectNode(addr, subCmd == "perm") |
|
default: |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: "invalid subcommand for node connect", |
|
} |
|
} |
|
default: |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: "invalid subcommand for node", |
|
} |
|
} |
|
|
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: err.Error(), |
|
} |
|
} |
|
|
|
// no data returned unless an error. |
|
return nil, nil |
|
} |
|
|
|
// peerExists determines if a certain peer is currently connected given |
|
// information about all currently connected peers. Peer existence is |
|
// determined using either a target address or node id. |
|
func peerExists(peerInfos []*btcjson.GetPeerInfoResult, addr string, nodeId int32) bool { |
|
for _, peerInfo := range peerInfos { |
|
if peerInfo.ID == nodeId || peerInfo.Addr == addr { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// 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 wire.Message) (string, error) { |
|
var buf bytes.Buffer |
|
if err := msg.BtcEncode(&buf, maxProtocolVersion); err != nil { |
|
context := fmt.Sprintf("Failed to encode msg of type %T", msg) |
|
return "", internalRPCError(err.Error(), context) |
|
} |
|
|
|
return hex.EncodeToString(buf.Bytes()), nil |
|
} |
|
|
|
// handleCreateRawTransaction handles createrawtransaction commands. |
|
func handleCreateRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.CreateRawTransactionCmd) |
|
|
|
// Add all transaction inputs to a new transaction after performing |
|
// some validity checks. |
|
mtx := wire.NewMsgTx() |
|
for _, input := range c.Inputs { |
|
txHash, err := wire.NewShaHashFromStr(input.Txid) |
|
if err != nil { |
|
return nil, rpcDecodeHexError(input.Txid) |
|
} |
|
|
|
prevOut := wire.NewOutPoint(txHash, uint32(input.Vout)) |
|
txIn := wire.NewTxIn(prevOut, []byte{}) |
|
mtx.AddTxIn(txIn) |
|
} |
|
|
|
// Add all transaction outputs to the transaction after performing |
|
// some validity 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.RPCError{ |
|
Code: btcjson.ErrRPCType, |
|
Message: "Invalid amount", |
|
} |
|
} |
|
|
|
// Decode the provided address. |
|
addr, err := btcutil.DecodeAddress(encodedAddr, |
|
activeNetParams.Params) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidAddressOrKey, |
|
Message: "Invalid address or key: " + 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. |
|
switch addr.(type) { |
|
case *btcutil.AddressPubKeyHash: |
|
case *btcutil.AddressScriptHash: |
|
default: |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidAddressOrKey, |
|
Message: "Invalid address or key", |
|
} |
|
} |
|
if !addr.IsForNet(s.server.chainParams) { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidAddressOrKey, |
|
Message: "Invalid address: " + encodedAddr + |
|
" is for the wrong network", |
|
} |
|
} |
|
|
|
// Create a new script which pays to the provided address. |
|
pkScript, err := txscript.PayToAddrScript(addr) |
|
if err != nil { |
|
context := "Failed to generate pay-to-address script" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
// Convert the amount to satoshi. |
|
satoshi, err := btcutil.NewAmount(amount) |
|
if err != nil { |
|
context := "Failed to convert amount" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
txOut := wire.NewTxOut(int64(satoshi), pkScript) |
|
mtx.AddTxOut(txOut) |
|
} |
|
|
|
// Return the serialized and hex-encoded transaction. Note that this |
|
// is intentionally not directly returning because the first return |
|
// value is a string and it would result in returning an empty string to |
|
// the client instead of nothing (nil) in the case of an error. |
|
mtxHex, err := messageToHex(mtx) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return mtxHex, nil |
|
} |
|
|
|
// handleDebugLevel handles debuglevel commands. |
|
func handleDebugLevel(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
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 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParams.Code, |
|
Message: err.Error(), |
|
} |
|
} |
|
|
|
return "Done.", nil |
|
} |
|
|
|
// createVinList returns a slice of JSON objects for the inputs of the passed |
|
// transaction. |
|
func createVinList(mtx *wire.MsgTx) []btcjson.Vin { |
|
vinList := make([]btcjson.Vin, len(mtx.TxIn)) |
|
for i, v := range mtx.TxIn { |
|
if blockchain.IsCoinBaseTx(mtx) { |
|
vinList[i].Coinbase = hex.EncodeToString(v.SignatureScript) |
|
} else { |
|
vinList[i].Txid = v.PreviousOutPoint.Hash.String() |
|
vinList[i].Vout = v.PreviousOutPoint.Index |
|
|
|
// The disassembled string will contain [error] inline |
|
// if the script doesn't fully parse, so ignore the |
|
// error here. |
|
disbuf, _ := txscript.DisasmString(v.SignatureScript) |
|
vinList[i].ScriptSig = new(btcjson.ScriptSig) |
|
vinList[i].ScriptSig.Asm = disbuf |
|
vinList[i].ScriptSig.Hex = hex.EncodeToString(v.SignatureScript) |
|
} |
|
vinList[i].Sequence = v.Sequence |
|
} |
|
|
|
return vinList |
|
} |
|
|
|
// createVoutList returns a slice of JSON objects for the outputs of the passed |
|
// transaction. |
|
func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params) []btcjson.Vout { |
|
voutList := make([]btcjson.Vout, len(mtx.TxOut)) |
|
for i, v := range mtx.TxOut { |
|
voutList[i].N = uint32(i) |
|
voutList[i].Value = btcutil.Amount(v.Value).ToBTC() |
|
|
|
// The disassembled string will contain [error] inline if the |
|
// script doesn't fully parse, so ignore the error here. |
|
disbuf, _ := txscript.DisasmString(v.PkScript) |
|
voutList[i].ScriptPubKey.Asm = disbuf |
|
voutList[i].ScriptPubKey.Hex = hex.EncodeToString(v.PkScript) |
|
|
|
// 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, _ := txscript.ExtractPkScriptAddrs( |
|
v.PkScript, chainParams) |
|
voutList[i].ScriptPubKey.Type = scriptClass.String() |
|
voutList[i].ScriptPubKey.ReqSigs = int32(reqSigs) |
|
|
|
if addrs == nil { |
|
voutList[i].ScriptPubKey.Addresses = nil |
|
} else { |
|
voutList[i].ScriptPubKey.Addresses = make([]string, len(addrs)) |
|
for j, addr := range addrs { |
|
voutList[i].ScriptPubKey.Addresses[j] = addr.EncodeAddress() |
|
} |
|
} |
|
} |
|
|
|
return voutList |
|
} |
|
|
|
// createTxRawResult converts the passed transaction and associated parameters |
|
// to a raw transaction JSON object. |
|
func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, |
|
txHash string, blkHeader *wire.BlockHeader, blkHash string, |
|
blkHeight int64, chainHeight int64) (*btcjson.TxRawResult, error) { |
|
|
|
mtxHex, err := messageToHex(mtx) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
txReply := &btcjson.TxRawResult{ |
|
Hex: mtxHex, |
|
Txid: txHash, |
|
Vout: createVoutList(mtx, chainParams), |
|
Vin: createVinList(mtx), |
|
Version: mtx.Version, |
|
LockTime: mtx.LockTime, |
|
} |
|
|
|
if blkHeader != nil { |
|
// This is not a typo, they are identical in bitcoind as well. |
|
txReply.Time = blkHeader.Timestamp.Unix() |
|
txReply.Blocktime = blkHeader.Timestamp.Unix() |
|
txReply.BlockHash = blkHash |
|
txReply.Confirmations = uint64(1 + chainHeight - blkHeight) |
|
} |
|
|
|
return txReply, nil |
|
} |
|
|
|
// handleDecodeRawTransaction handles decoderawtransaction commands. |
|
func handleDecodeRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
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, rpcDecodeHexError(hexStr) |
|
} |
|
var mtx wire.MsgTx |
|
err = mtx.Deserialize(bytes.NewReader(serializedTx)) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDeserialization, |
|
Message: "TX decode failed: " + err.Error(), |
|
} |
|
} |
|
|
|
// Create and return the result. |
|
txReply := btcjson.TxRawDecodeResult{ |
|
Txid: mtx.TxSha().String(), |
|
Version: mtx.Version, |
|
Locktime: mtx.LockTime, |
|
Vin: createVinList(&mtx), |
|
Vout: createVoutList(&mtx, s.server.chainParams), |
|
} |
|
return txReply, nil |
|
} |
|
|
|
// handleDecodeScript handles decodescript commands. |
|
func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.DecodeScriptCmd) |
|
|
|
// Convert the hex script to bytes. |
|
hexStr := c.HexScript |
|
if len(hexStr)%2 != 0 { |
|
hexStr = "0" + hexStr |
|
} |
|
script, err := hex.DecodeString(hexStr) |
|
if err != nil { |
|
return nil, rpcDecodeHexError(hexStr) |
|
} |
|
|
|
// The disassembled string will contain [error] inline if the script |
|
// doesn't fully parse, so ignore the error here. |
|
disbuf, _ := txscript.DisasmString(script) |
|
|
|
// Get information about the script. |
|
// Ignore the error here since an error means the script couldn't parse |
|
// and there is no additinal information about it anyways. |
|
scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(script, |
|
s.server.chainParams) |
|
addresses := make([]string, len(addrs)) |
|
for i, addr := range addrs { |
|
addresses[i] = addr.EncodeAddress() |
|
} |
|
|
|
// Convert the script itself to a pay-to-script-hash address. |
|
p2sh, err := btcutil.NewAddressScriptHash(script, s.server.chainParams) |
|
if err != nil { |
|
context := "Failed to convert script to pay-to-script-hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
// Generate and return the reply. |
|
reply := btcjson.DecodeScriptResult{ |
|
Asm: disbuf, |
|
ReqSigs: int32(reqSigs), |
|
Type: scriptClass.String(), |
|
Addresses: addresses, |
|
P2sh: p2sh.EncodeAddress(), |
|
} |
|
return reply, nil |
|
} |
|
|
|
// handleGenerate handles generate commands. |
|
func handleGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
// Respond with an error if there are no addresses to pay the |
|
// created blocks to. |
|
if len(cfg.miningAddrs) == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: "No payment addresses specified " + |
|
"via --miningaddr", |
|
} |
|
} |
|
|
|
c := cmd.(*btcjson.GenerateCmd) |
|
|
|
// Respond with an error if the client is requesting 0 blocks to be generated. |
|
if c.NumBlocks == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: "Please request a nonzero number of blocks to generate.", |
|
} |
|
} |
|
|
|
// Create a reply |
|
reply := make([]string, c.NumBlocks) |
|
|
|
blockHashes, err := s.server.cpuMiner.GenerateNBlocks(c.NumBlocks) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: err.Error(), |
|
} |
|
} |
|
|
|
// Mine the correct number of blocks, assigning the hex representation of the |
|
// hash of each one to its place in the reply. |
|
for i, hash := range blockHashes { |
|
reply[i] = hash.String() |
|
} |
|
|
|
return reply, nil |
|
} |
|
|
|
// handleGetAddedNodeInfo handles getaddednodeinfo commands. |
|
func handleGetAddedNodeInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetAddedNodeInfoCmd) |
|
|
|
// Retrieve a list of persistent (added) peers from the bitcoin server |
|
// and filter the list of peer per the specified address (if any). |
|
peers := s.server.AddedNodeInfo() |
|
if c.Node != nil { |
|
node := *c.Node |
|
found := false |
|
for i, peer := range peers { |
|
if peer.addr == node { |
|
peers = peers[i : i+1] |
|
found = true |
|
} |
|
} |
|
if !found { |
|
return nil, &btcjson.RPCError{ |
|
Code: -24, // TODO: ErrRPCClientNodeNotAdded |
|
Message: "Node has not been added", |
|
} |
|
} |
|
} |
|
|
|
// Without the dns flag, the result is just a slice of the addresses as |
|
// strings. |
|
if !c.DNS { |
|
results := make([]string, 0, len(peers)) |
|
for _, peer := range peers { |
|
results = append(results, peer.addr) |
|
} |
|
return results, nil |
|
} |
|
|
|
// With the dns flag, the result is an array of JSON objects which |
|
// include the result of DNS lookups for each peer. |
|
results := make([]*btcjson.GetAddedNodeInfoResult, 0, len(peers)) |
|
for _, peer := range peers { |
|
// Set the "address" of the peer which could be an ip address |
|
// or a domain name. |
|
var result btcjson.GetAddedNodeInfoResult |
|
result.AddedNode = peer.addr |
|
result.Connected = btcjson.Bool(peer.Connected()) |
|
|
|
// Split the address into host and port portions so we can do |
|
// a DNS lookup against the host. When no port is specified in |
|
// the address, just use the address as the host. |
|
host, _, err := net.SplitHostPort(peer.addr) |
|
if err != nil { |
|
host = peer.addr |
|
} |
|
|
|
// Do a DNS lookup for the address. If the lookup fails, just |
|
// use the host. |
|
var ipList []string |
|
ips, err := btcdLookup(host) |
|
if err == nil { |
|
ipList = make([]string, 0, len(ips)) |
|
for _, ip := range ips { |
|
ipList = append(ipList, ip.String()) |
|
} |
|
} else { |
|
ipList = make([]string, 1) |
|
ipList[0] = host |
|
} |
|
|
|
// Add the addresses and connection info to the result. |
|
addrs := make([]btcjson.GetAddedNodeInfoResultAddr, 0, len(ipList)) |
|
for _, ip := range ipList { |
|
var addr btcjson.GetAddedNodeInfoResultAddr |
|
addr.Address = ip |
|
addr.Connected = "false" |
|
if ip == host && peer.Connected() { |
|
addr.Connected = directionString(peer.inbound) |
|
} |
|
addrs = append(addrs, addr) |
|
} |
|
result.Addresses = &addrs |
|
results = append(results, &result) |
|
} |
|
return results, nil |
|
} |
|
|
|
// handleGetBestBlock implements the getbestblock command. |
|
func handleGetBestBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
// All other "get block" commands give either the height, the |
|
// hash, or both but require the block SHA. This gets both for |
|
// the best block. |
|
sha, height, err := s.server.db.NewestSha() |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCBestBlockHash, |
|
Message: "Error getting best block hash", |
|
} |
|
} |
|
|
|
result := &btcjson.GetBestBlockResult{ |
|
Hash: sha.String(), |
|
Height: int32(height), |
|
} |
|
return result, nil |
|
} |
|
|
|
// handleGetBestBlockHash implements the getbestblockhash command. |
|
func handleGetBestBlockHash(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
sha, _, err := s.server.db.NewestSha() |
|
if err != nil { |
|
rpcsLog.Errorf("Error getting newest sha: %v", err) |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCBestBlockHash, |
|
Message: "Error getting best block hash", |
|
} |
|
} |
|
|
|
return sha.String(), nil |
|
} |
|
|
|
// 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 := blockchain.CompactToBig(activeNetParams.PowLimitBits) |
|
target := blockchain.CompactToBig(bits) |
|
|
|
difficulty := new(big.Rat).SetFrac(max, target) |
|
outString := difficulty.FloatString(8) |
|
diff, err := strconv.ParseFloat(outString, 64) |
|
if err != nil { |
|
rpcsLog.Errorf("Cannot get difficulty: %v", err) |
|
return 0 |
|
} |
|
return diff |
|
} |
|
|
|
// handleGetBlock implements the getblock command. |
|
func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetBlockCmd) |
|
|
|
sha, err := wire.NewShaHashFromStr(c.Hash) |
|
if err != nil { |
|
return nil, rpcDecodeHexError(c.Hash) |
|
} |
|
blk, err := s.server.db.FetchBlockBySha(sha) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCBlockNotFound, |
|
Message: "Block not found", |
|
} |
|
} |
|
|
|
// When the verbose flag isn't set, simply return the network-serialized |
|
// block as a hex-encoded string. |
|
if c.Verbose != nil && !*c.Verbose { |
|
// Note that this is intentionally not directly returning |
|
// because the first return value is a string and it would |
|
// result in returning an empty string to the client instead of |
|
// nothing (nil) in the case of an error. |
|
blkHex, err := messageToHex(blk.MsgBlock()) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return blkHex, nil |
|
} |
|
|
|
// The verbose flag is set, so generate the JSON object and return it. |
|
buf, err := blk.Bytes() |
|
if err != nil { |
|
context := "Failed to get block bytes" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
idx := blk.Height() |
|
_, maxIdx, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
blockHeader := &blk.MsgBlock().Header |
|
blockReply := btcjson.GetBlockVerboseResult{ |
|
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: int32(len(buf)), |
|
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), |
|
Difficulty: getDifficultyRatio(blockHeader.Bits), |
|
} |
|
|
|
if c.VerboseTx == nil || !*c.VerboseTx { |
|
transactions := blk.Transactions() |
|
txNames := make([]string, len(transactions)) |
|
for i, tx := range transactions { |
|
txNames[i] = tx.Sha().String() |
|
} |
|
|
|
blockReply.Tx = txNames |
|
} else { |
|
txns := blk.Transactions() |
|
rawTxns := make([]btcjson.TxRawResult, len(txns)) |
|
for i, tx := range txns { |
|
rawTxn, err := createTxRawResult(s.server.chainParams, |
|
tx.MsgTx(), tx.Sha().String(), blockHeader, |
|
sha.String(), idx, maxIdx) |
|
if err != nil { |
|
return nil, err |
|
} |
|
rawTxns[i] = *rawTxn |
|
} |
|
blockReply.RawTx = rawTxns |
|
} |
|
|
|
// Get next block unless we are already at the top. |
|
if idx < maxIdx { |
|
var shaNext *wire.ShaHash |
|
shaNext, err = s.server.db.FetchBlockShaByHeight(int64(idx + 1)) |
|
if err != nil { |
|
context := "No next block" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
blockReply.NextHash = shaNext.String() |
|
} |
|
|
|
return blockReply, nil |
|
} |
|
|
|
// handleGetBlockCount implements the getblockcount command. |
|
func handleGetBlockCount(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
_, maxIdx, err := s.server.db.NewestSha() |
|
if err != nil { |
|
rpcsLog.Errorf("Error getting newest sha: %v", err) |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCBlockCount, |
|
Message: "Error getting block count: " + err.Error(), |
|
} |
|
} |
|
|
|
return maxIdx, nil |
|
} |
|
|
|
// handleGetBlockHash implements the getblockhash command. |
|
func handleGetBlockHash(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetBlockHashCmd) |
|
sha, err := s.server.db.FetchBlockShaByHeight(c.Index) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCOutOfRange, |
|
Message: "Block number out of range", |
|
} |
|
} |
|
|
|
return sha.String(), nil |
|
} |
|
|
|
// handleGetBlockHeader implements the getblockheader command. |
|
func handleGetBlockHeader(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetBlockHeaderCmd) |
|
|
|
sha, err := wire.NewShaHashFromStr(c.Hash) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if c.Verbose == nil || *c.Verbose { |
|
blk, err := s.server.db.FetchBlockBySha(sha) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidAddressOrKey, |
|
Message: "Invalid address or key: " + err.Error(), |
|
} |
|
} |
|
|
|
_, maxIdx, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
var shaNextStr string |
|
shaNext, err := s.server.db.FetchBlockShaByHeight(int64(blk.Height() + 1)) |
|
if err == nil { |
|
shaNextStr = shaNext.String() |
|
} |
|
|
|
msgBlock := blk.MsgBlock() |
|
blockHeaderReply := btcjson.GetBlockHeaderVerboseResult{ |
|
Hash: c.Hash, |
|
Confirmations: uint64(1 + maxIdx - blk.Height()), |
|
Height: int32(blk.Height()), |
|
Version: msgBlock.Header.Version, |
|
MerkleRoot: msgBlock.Header.MerkleRoot.String(), |
|
NextHash: shaNextStr, |
|
PreviousHash: msgBlock.Header.PrevBlock.String(), |
|
Nonce: uint64(msgBlock.Header.Nonce), |
|
Time: msgBlock.Header.Timestamp.Unix(), |
|
Bits: strconv.FormatInt(int64(msgBlock.Header.Bits), 16), |
|
Difficulty: getDifficultyRatio(msgBlock.Header.Bits), |
|
} |
|
return blockHeaderReply, nil |
|
} |
|
|
|
// Verbose disabled |
|
blkHeader, err := s.server.db.FetchBlockHeaderBySha(sha) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidAddressOrKey, |
|
Message: "Invalid address or key: " + err.Error(), |
|
} |
|
} |
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload)) |
|
if err = blkHeader.BtcEncode(buf, maxProtocolVersion); err != nil { |
|
errStr := fmt.Sprintf("Failed to serialize data: %v", err) |
|
return nil, internalRPCError(errStr, "") |
|
} |
|
|
|
return hex.EncodeToString(buf.Bytes()), nil |
|
} |
|
|
|
// encodeTemplateID encodes the passed details into an ID that can be used to |
|
// uniquely identify a block template. |
|
func encodeTemplateID(prevHash *wire.ShaHash, lastGenerated time.Time) string { |
|
return fmt.Sprintf("%s-%d", prevHash.String(), lastGenerated.Unix()) |
|
} |
|
|
|
// decodeTemplateID decodes an ID that is used to uniquely identify a block |
|
// template. This is mainly used as a mechanism to track when to update clients |
|
// that are using long polling for block templates. The ID consists of the |
|
// previous block hash for the associated template and the time the associated |
|
// template was generated. |
|
func decodeTemplateID(templateID string) (*wire.ShaHash, int64, error) { |
|
fields := strings.Split(templateID, "-") |
|
if len(fields) != 2 { |
|
return nil, 0, errors.New("invalid longpollid format") |
|
} |
|
|
|
prevHash, err := wire.NewShaHashFromStr(fields[0]) |
|
if err != nil { |
|
return nil, 0, errors.New("invalid longpollid format") |
|
} |
|
lastGenerated, err := strconv.ParseInt(fields[1], 10, 64) |
|
if err != nil { |
|
return nil, 0, errors.New("invalid longpollid format") |
|
} |
|
|
|
return prevHash, lastGenerated, nil |
|
} |
|
|
|
// notifyLongPollers notifies any channels that have been registered to be |
|
// notified when block templates are stale. |
|
// |
|
// This function MUST be called with the state locked. |
|
func (state *gbtWorkState) notifyLongPollers(latestHash *wire.ShaHash, lastGenerated time.Time) { |
|
// Notify anything that is waiting for a block template update from a |
|
// hash which is not the hash of the tip of the best chain since their |
|
// work is now invalid. |
|
for hash, channels := range state.notifyMap { |
|
if !hash.IsEqual(latestHash) { |
|
for _, c := range channels { |
|
close(c) |
|
} |
|
delete(state.notifyMap, hash) |
|
} |
|
} |
|
|
|
// Return now if the provided last generated timestamp has not been |
|
// initialized. |
|
if lastGenerated.IsZero() { |
|
return |
|
} |
|
|
|
// Return now if there is nothing registered for updates to the current |
|
// best block hash. |
|
channels, ok := state.notifyMap[*latestHash] |
|
if !ok { |
|
return |
|
} |
|
|
|
// Notify anything that is waiting for a block template update from a |
|
// block template generated before the most recently generated block |
|
// template. |
|
lastGeneratedUnix := lastGenerated.Unix() |
|
for lastGen, c := range channels { |
|
if lastGen < lastGeneratedUnix { |
|
close(c) |
|
delete(channels, lastGen) |
|
} |
|
} |
|
|
|
// Remove the entry altogether if there are no more registered |
|
// channels. |
|
if len(channels) == 0 { |
|
delete(state.notifyMap, *latestHash) |
|
} |
|
} |
|
|
|
// NotifyBlockConnected uses the newly-connected block to notify any long poll |
|
// clients with a new block template when their existing block template is |
|
// stale due to the newly connected block. |
|
func (state *gbtWorkState) NotifyBlockConnected(blockSha *wire.ShaHash) { |
|
go func() { |
|
state.Lock() |
|
defer state.Unlock() |
|
|
|
state.notifyLongPollers(blockSha, state.lastTxUpdate) |
|
}() |
|
} |
|
|
|
// NotifyMempoolTx uses the new last updated time for the transaction memory |
|
// pool to notify any long poll clients with a new block template when their |
|
// existing block template is stale due to enough time passing and the contents |
|
// of the memory pool changing. |
|
func (state *gbtWorkState) NotifyMempoolTx(lastUpdated time.Time) { |
|
go func() { |
|
state.Lock() |
|
defer state.Unlock() |
|
|
|
// No need to notify anything if no block templates have been generated |
|
// yet. |
|
if state.prevHash == nil || state.lastGenerated.IsZero() { |
|
return |
|
} |
|
|
|
if time.Now().After(state.lastGenerated.Add(time.Second * |
|
gbtRegenerateSeconds)) { |
|
|
|
state.notifyLongPollers(state.prevHash, lastUpdated) |
|
} |
|
}() |
|
} |
|
|
|
// templateUpdateChan returns a channel that will be closed once the block |
|
// template associated with the passed previous hash and last generated time |
|
// is stale. The function will return existing channels for duplicate |
|
// parameters which allows multiple clients to wait for the same block template |
|
// without requiring a different channel for each client. |
|
// |
|
// This function MUST be called with the state locked. |
|
func (state *gbtWorkState) templateUpdateChan(prevHash *wire.ShaHash, lastGenerated int64) chan struct{} { |
|
// Either get the current list of channels waiting for updates about |
|
// changes to block template for the previous hash or create a new one. |
|
channels, ok := state.notifyMap[*prevHash] |
|
if !ok { |
|
m := make(map[int64]chan struct{}) |
|
state.notifyMap[*prevHash] = m |
|
channels = m |
|
} |
|
|
|
// Get the current channel associated with the time the block template |
|
// was last generated or create a new one. |
|
c, ok := channels[lastGenerated] |
|
if !ok { |
|
c = make(chan struct{}) |
|
channels[lastGenerated] = c |
|
} |
|
|
|
return c |
|
} |
|
|
|
// updateBlockTemplate creates or updates a block template for the work state. |
|
// A new block template will be generated when the current best block has |
|
// changed or the transactions in the memory pool have been updated and it has |
|
// been some time has passed since the last template was generated. Otherwise, |
|
// the timestamp for the existing block template is updated (and possibly the |
|
// difficulty on testnet per the consesus rules). Finally, if the |
|
// useCoinbaseValue flag is flase and the existing block template does not |
|
// already contain a valid payment address, the block template will be updated |
|
// with a randomly selected payment address from the list of configured |
|
// addresses. |
|
// |
|
// This function MUST be called with the state locked. |
|
func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bool) error { |
|
lastTxUpdate := s.server.txMemPool.LastUpdated() |
|
if lastTxUpdate.IsZero() { |
|
lastTxUpdate = time.Now() |
|
} |
|
|
|
// Generate a new block template when the current best block has |
|
// changed or the transactions in the memory pool have been updated and |
|
// it has been at least gbtRegenerateSecond since the last template was |
|
// generated. |
|
var msgBlock *wire.MsgBlock |
|
var targetDifficulty string |
|
latestHash, _ := s.server.blockManager.chainState.Best() |
|
template := state.template |
|
if template == nil || state.prevHash == nil || |
|
!state.prevHash.IsEqual(latestHash) || |
|
(state.lastTxUpdate != lastTxUpdate && |
|
time.Now().After(state.lastGenerated.Add(time.Second* |
|
gbtRegenerateSeconds))) { |
|
|
|
// Reset the previous best hash the block template was generated |
|
// against so any errors below cause the next invocation to try |
|
// again. |
|
state.prevHash = nil |
|
|
|
// Choose a payment address at random if the caller requests a |
|
// full coinbase as opposed to only the pertinent details needed |
|
// to create their own coinbase. |
|
var payAddr btcutil.Address |
|
if !useCoinbaseValue { |
|
payAddr = cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] |
|
} |
|
|
|
// Create a new block template that has a coinbase which anyone |
|
// can redeem. This is only acceptable because the returned |
|
// block template doesn't include the coinbase, so the caller |
|
// will ultimately create their own coinbase which pays to the |
|
// appropriate address(es). |
|
blkTemplate, err := NewBlockTemplate(s.server.txMemPool, payAddr) |
|
if err != nil { |
|
return internalRPCError("Failed to create new block "+ |
|
"template: "+err.Error(), "") |
|
} |
|
template = blkTemplate |
|
msgBlock = template.block |
|
targetDifficulty = fmt.Sprintf("%064x", |
|
blockchain.CompactToBig(msgBlock.Header.Bits)) |
|
|
|
// Find the minimum allowed timestamp for the block based on the |
|
// median timestamp of the last several blocks per the chain |
|
// consensus rules. |
|
chainState := &s.server.blockManager.chainState |
|
minTimestamp, err := minimumMedianTime(chainState) |
|
if err != nil { |
|
context := "Failed to get minimum median time" |
|
return internalRPCError(err.Error(), context) |
|
} |
|
|
|
// Update work state to ensure another block template isn't |
|
// generated until needed. |
|
state.template = template |
|
state.lastGenerated = time.Now() |
|
state.lastTxUpdate = lastTxUpdate |
|
state.prevHash = latestHash |
|
state.minTimestamp = minTimestamp |
|
|
|
rpcsLog.Debugf("Generated block template (timestamp %v, "+ |
|
"target %s, merkle root %s)", |
|
msgBlock.Header.Timestamp, targetDifficulty, |
|
msgBlock.Header.MerkleRoot) |
|
|
|
// Notify any clients that are long polling about the new |
|
// template. |
|
state.notifyLongPollers(latestHash, lastTxUpdate) |
|
} else { |
|
// At this point, there is a saved block template and another |
|
// request for a template was made, but either the available |
|
// transactions haven't change or it hasn't been long enough to |
|
// trigger a new block template to be generated. So, update the |
|
// existing block template. |
|
|
|
// When the caller requires a full coinbase as opposed to only |
|
// the pertinent details needed to create their own coinbase, |
|
// add a payment address to the output of the coinbase of the |
|
// template if it doesn't already have one. Since this requires |
|
// mining addresses to be specified via the config, an error is |
|
// returned if none have been specified. |
|
if !useCoinbaseValue && !template.validPayAddress { |
|
// Choose a payment address at random. |
|
payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] |
|
|
|
// Update the block coinbase output of the template to |
|
// pay to the randomly selected payment address. |
|
pkScript, err := txscript.PayToAddrScript(payToAddr) |
|
if err != nil { |
|
context := "Failed to create pay-to-addr script" |
|
return internalRPCError(err.Error(), context) |
|
} |
|
template.block.Transactions[0].TxOut[0].PkScript = pkScript |
|
template.validPayAddress = true |
|
|
|
// Update the merkle root. |
|
block := btcutil.NewBlock(template.block) |
|
merkles := blockchain.BuildMerkleTreeStore(block.Transactions()) |
|
template.block.Header.MerkleRoot = *merkles[len(merkles)-1] |
|
} |
|
|
|
// Set locals for convenience. |
|
msgBlock = template.block |
|
targetDifficulty = fmt.Sprintf("%064x", |
|
blockchain.CompactToBig(msgBlock.Header.Bits)) |
|
|
|
// Update the time of the block template to the current time |
|
// while accounting for the median time of the past several |
|
// blocks per the chain consensus rules. |
|
UpdateBlockTime(msgBlock, s.server.blockManager) |
|
msgBlock.Header.Nonce = 0 |
|
|
|
rpcsLog.Debugf("Updated block template (timestamp %v, "+ |
|
"target %s)", msgBlock.Header.Timestamp, |
|
targetDifficulty) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// blockTemplateResult returns the current block template associated with the |
|
// state as a btcjson.GetBlockTemplateResult that is ready to be encoded to JSON |
|
// and returned to the caller. |
|
// |
|
// This function MUST be called with the state locked. |
|
func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld *bool) (*btcjson.GetBlockTemplateResult, error) { |
|
// Ensure the timestamps are still in valid range for the template. |
|
// This should really only ever happen if the local clock is changed |
|
// after the template is generated, but it's important to avoid serving |
|
// invalid block templates. |
|
template := state.template |
|
msgBlock := template.block |
|
header := &msgBlock.Header |
|
adjustedTime := state.timeSource.AdjustedTime() |
|
maxTime := adjustedTime.Add(time.Second * blockchain.MaxTimeOffsetSeconds) |
|
if header.Timestamp.After(maxTime) { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCOutOfRange, |
|
Message: fmt.Sprintf("The template time is after the "+ |
|
"maximum allowed time for a block - template "+ |
|
"time %v, maximum time %v", adjustedTime, |
|
maxTime), |
|
} |
|
} |
|
|
|
// Convert each transaction in the block template to a template result |
|
// transaction. The result does not include the coinbase, so notice |
|
// the adjustments to the various lengths and indices. |
|
numTx := len(msgBlock.Transactions) |
|
transactions := make([]btcjson.GetBlockTemplateResultTx, 0, numTx-1) |
|
txIndex := make(map[wire.ShaHash]int64, numTx) |
|
for i, tx := range msgBlock.Transactions { |
|
txHash := tx.TxSha() |
|
txIndex[txHash] = int64(i) |
|
|
|
// Skip the coinbase transaction. |
|
if i == 0 { |
|
continue |
|
} |
|
|
|
// Create an array of 1-based indices to transactions that come |
|
// before this one in the transactions list which this one |
|
// depends on. This is necessary since the created block must |
|
// ensure proper ordering of the dependencies. A map is used |
|
// before creating the final array to prevent duplicate entries |
|
// when mutiple inputs reference the same transaction. |
|
dependsMap := make(map[int64]struct{}) |
|
for _, txIn := range tx.TxIn { |
|
if idx, ok := txIndex[txIn.PreviousOutPoint.Hash]; ok { |
|
dependsMap[idx] = struct{}{} |
|
} |
|
} |
|
depends := make([]int64, 0, len(dependsMap)) |
|
for idx := range dependsMap { |
|
depends = append(depends, idx) |
|
} |
|
|
|
// Serialize the transaction for later conversion to hex. |
|
txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) |
|
if err := tx.Serialize(txBuf); err != nil { |
|
context := "Failed to serialize transaction" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
resultTx := btcjson.GetBlockTemplateResultTx{ |
|
Data: hex.EncodeToString(txBuf.Bytes()), |
|
Hash: txHash.String(), |
|
Depends: depends, |
|
Fee: template.fees[i], |
|
SigOps: template.sigOpCounts[i], |
|
} |
|
transactions = append(transactions, resultTx) |
|
} |
|
|
|
// Generate the block template reply. Note that following mutations are |
|
// implied by the included or omission of fields: |
|
// Including MinTime -> time/decrement |
|
// Omitting CoinbaseTxn -> coinbase, generation |
|
targetDifficulty := fmt.Sprintf("%064x", blockchain.CompactToBig(header.Bits)) |
|
templateID := encodeTemplateID(state.prevHash, state.lastGenerated) |
|
reply := btcjson.GetBlockTemplateResult{ |
|
Bits: strconv.FormatInt(int64(header.Bits), 16), |
|
CurTime: header.Timestamp.Unix(), |
|
Height: template.height, |
|
PreviousHash: header.PrevBlock.String(), |
|
SigOpLimit: blockchain.MaxSigOpsPerBlock, |
|
SizeLimit: wire.MaxBlockPayload, |
|
Transactions: transactions, |
|
Version: header.Version, |
|
LongPollID: templateID, |
|
SubmitOld: submitOld, |
|
Target: targetDifficulty, |
|
MinTime: state.minTimestamp.Unix(), |
|
MaxTime: maxTime.Unix(), |
|
Mutable: gbtMutableFields, |
|
NonceRange: gbtNonceRange, |
|
Capabilities: gbtCapabilities, |
|
} |
|
if useCoinbaseValue { |
|
reply.CoinbaseAux = gbtCoinbaseAux |
|
reply.CoinbaseValue = &msgBlock.Transactions[0].TxOut[0].Value |
|
} else { |
|
// Ensure the template has a valid payment address associated |
|
// with it when a full coinbase is requested. |
|
if !template.validPayAddress { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: "A coinbase transaction has been " + |
|
"requested, but the server has not " + |
|
"been configured with any payment " + |
|
"addresses via --miningaddr", |
|
} |
|
} |
|
|
|
// Serialize the transaction for conversion to hex. |
|
tx := msgBlock.Transactions[0] |
|
txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) |
|
if err := tx.Serialize(txBuf); err != nil { |
|
context := "Failed to serialize transaction" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
resultTx := btcjson.GetBlockTemplateResultTx{ |
|
Data: hex.EncodeToString(txBuf.Bytes()), |
|
Hash: tx.TxSha().String(), |
|
Depends: []int64{}, |
|
Fee: template.fees[0], |
|
SigOps: template.sigOpCounts[0], |
|
} |
|
|
|
reply.CoinbaseTxn = &resultTx |
|
} |
|
|
|
return &reply, nil |
|
} |
|
|
|
// handleGetBlockTemplateLongPoll a helper for handleGetBlockTemplateRequest |
|
// which deals with handling long polling for block templates. When a caller |
|
// sends a request with a long poll ID that was previously returned, a response |
|
// is not sent until the caller should stop working on the previous block |
|
// template in favor of the new one. In particular, this is the case when the |
|
// old block template is no longer valid due to a solution already being found |
|
// and added to the block chain, or new transactions have shown up and some time |
|
// has passed without finding a solution. |
|
// |
|
// See https://en.bitcoin.it/wiki/BIP_0022 for more details. |
|
func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbaseValue bool, closeChan <-chan struct{}) (interface{}, error) { |
|
state := s.gbtWorkState |
|
state.Lock() |
|
// The state unlock is intentionally not deferred here since it needs to |
|
// be manually unlocked before waiting for a notification about block |
|
// template changes. |
|
|
|
if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { |
|
state.Unlock() |
|
return nil, err |
|
} |
|
|
|
// Just return the current block template if the the long poll ID |
|
// provided by the caller is invalid. |
|
prevHash, lastGenerated, err := decodeTemplateID(longPollID) |
|
if err != nil { |
|
result, err := state.blockTemplateResult(useCoinbaseValue, nil) |
|
if err != nil { |
|
state.Unlock() |
|
return nil, err |
|
} |
|
|
|
state.Unlock() |
|
return result, nil |
|
} |
|
|
|
// Return the block template now if the specific block template |
|
// identified by the long poll ID no longer matches the current block |
|
// template as this means the provided template is stale. |
|
prevTemplateHash := &state.template.block.Header.PrevBlock |
|
if !prevHash.IsEqual(prevTemplateHash) || |
|
lastGenerated != state.lastGenerated.Unix() { |
|
|
|
// Include whether or not it is valid to submit work against the |
|
// old block template depending on whether or not a solution has |
|
// already been found and added to the block chain. |
|
submitOld := prevHash.IsEqual(prevTemplateHash) |
|
result, err := state.blockTemplateResult(useCoinbaseValue, |
|
&submitOld) |
|
if err != nil { |
|
state.Unlock() |
|
return nil, err |
|
} |
|
|
|
state.Unlock() |
|
return result, nil |
|
} |
|
|
|
// Register the previous hash and last generated time for notifications |
|
// Get a channel that will be notified when the template associated with |
|
// the provided ID is is stale and a new block template should be |
|
// returned to the caller. |
|
longPollChan := state.templateUpdateChan(prevHash, lastGenerated) |
|
state.Unlock() |
|
|
|
select { |
|
// When the client closes before it's time to send a reply, just return |
|
// now so the goroutine doesn't hang around. |
|
case <-closeChan: |
|
return nil, ErrClientQuit |
|
|
|
// Wait until signal received to send the reply. |
|
case <-longPollChan: |
|
// Fallthrough |
|
} |
|
|
|
// Get the lastest block template |
|
state.Lock() |
|
defer state.Unlock() |
|
|
|
if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { |
|
return nil, err |
|
} |
|
|
|
// Include whether or not it is valid to submit work against the old |
|
// block template depending on whether or not a solution has already |
|
// been found and added to the block chain. |
|
submitOld := prevHash.IsEqual(&state.template.block.Header.PrevBlock) |
|
result, err := state.blockTemplateResult(useCoinbaseValue, &submitOld) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return result, nil |
|
} |
|
|
|
// handleGetBlockTemplateRequest is a helper for handleGetBlockTemplate which |
|
// deals with generating and returning block templates to the caller. It |
|
// handles both long poll requests as specified by BIP 0022 as well as regular |
|
// requests. In addition, it detects the capabilities reported by the caller |
|
// in regards to whether or not it supports creating its own coinbase (the |
|
// coinbasetxn and coinbasevalue capabilities) and modifies the returned block |
|
// template accordingly. |
|
func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateRequest, closeChan <-chan struct{}) (interface{}, error) { |
|
// Extract the relevant passed capabilities and restrict the result to |
|
// either a coinbase value or a coinbase transaction object depending on |
|
// the request. Default to only providing a coinbase value. |
|
useCoinbaseValue := true |
|
if request != nil { |
|
var hasCoinbaseValue, hasCoinbaseTxn bool |
|
for _, capability := range request.Capabilities { |
|
switch capability { |
|
case "coinbasetxn": |
|
hasCoinbaseTxn = true |
|
case "coinbasevalue": |
|
hasCoinbaseValue = true |
|
} |
|
} |
|
|
|
if hasCoinbaseTxn && !hasCoinbaseValue { |
|
useCoinbaseValue = false |
|
} |
|
} |
|
|
|
// When a coinbase transaction has been requested, respond with an error |
|
// if there are no addresses to pay the created block template to. |
|
if !useCoinbaseValue && len(cfg.miningAddrs) == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: "A coinbase transaction has been requested, " + |
|
"but the server has not been configured with " + |
|
"any payment addresses via --miningaddr", |
|
} |
|
} |
|
|
|
// Return an error if there are no peers connected since there is no |
|
// way to relay a found block or receive transactions to work on. |
|
// However, allow this state when running in the regression test or |
|
// simulation test mode. |
|
if !(cfg.RegressionTest || cfg.SimNet) && s.server.ConnectedCount() == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCClientNotConnected, |
|
Message: "Bitcoin is not connected", |
|
} |
|
} |
|
|
|
// No point in generating or accepting work before the chain is synced. |
|
_, currentHeight := s.server.blockManager.chainState.Best() |
|
if currentHeight != 0 && !s.server.blockManager.IsCurrent() { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCClientInInitialDownload, |
|
Message: "Bitcoin is downloading blocks...", |
|
} |
|
} |
|
|
|
// When a long poll ID was provided, this is a long poll request by the |
|
// client to be notified when block template referenced by the ID should |
|
// be replaced with a new one. |
|
if request != nil && request.LongPollID != "" { |
|
return handleGetBlockTemplateLongPoll(s, request.LongPollID, |
|
useCoinbaseValue, closeChan) |
|
} |
|
|
|
// Protect concurrent access when updating block templates. |
|
state := s.gbtWorkState |
|
state.Lock() |
|
defer state.Unlock() |
|
|
|
// Get and return a block template. A new block template will be |
|
// generated when the current best block has changed or the transactions |
|
// in the memory pool have been updated and it has been at least five |
|
// seconds since the last template was generated. Otherwise, the |
|
// timestamp for the existing block template is updated (and possibly |
|
// the difficulty on testnet per the consesus rules). |
|
if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { |
|
return nil, err |
|
} |
|
return state.blockTemplateResult(useCoinbaseValue, nil) |
|
} |
|
|
|
// chainErrToGBTErrString converts an error returned from btcchain to a string |
|
// which matches the reasons and format described in BIP0022 for rejection |
|
// reasons. |
|
func chainErrToGBTErrString(err error) string { |
|
// When the passed error is not a RuleError, just return a generic |
|
// rejected string with the error text. |
|
ruleErr, ok := err.(blockchain.RuleError) |
|
if !ok { |
|
return "rejected: " + err.Error() |
|
} |
|
|
|
switch ruleErr.ErrorCode { |
|
case blockchain.ErrDuplicateBlock: |
|
return "duplicate" |
|
case blockchain.ErrBlockTooBig: |
|
return "bad-block-size" |
|
case blockchain.ErrBlockVersionTooOld: |
|
return "bad-version" |
|
case blockchain.ErrInvalidTime: |
|
return "bad-time" |
|
case blockchain.ErrTimeTooOld: |
|
return "time-too-old" |
|
case blockchain.ErrTimeTooNew: |
|
return "time-too-new" |
|
case blockchain.ErrDifficultyTooLow: |
|
return "bad-diffbits" |
|
case blockchain.ErrUnexpectedDifficulty: |
|
return "bad-diffbits" |
|
case blockchain.ErrHighHash: |
|
return "high-hash" |
|
case blockchain.ErrBadMerkleRoot: |
|
return "bad-txnmrklroot" |
|
case blockchain.ErrBadCheckpoint: |
|
return "bad-checkpoint" |
|
case blockchain.ErrForkTooOld: |
|
return "fork-too-old" |
|
case blockchain.ErrCheckpointTimeTooOld: |
|
return "checkpoint-time-too-old" |
|
case blockchain.ErrNoTransactions: |
|
return "bad-txns-none" |
|
case blockchain.ErrTooManyTransactions: |
|
return "bad-txns-toomany" |
|
case blockchain.ErrNoTxInputs: |
|
return "bad-txns-noinputs" |
|
case blockchain.ErrNoTxOutputs: |
|
return "bad-txns-nooutputs" |
|
case blockchain.ErrTxTooBig: |
|
return "bad-txns-size" |
|
case blockchain.ErrBadTxOutValue: |
|
return "bad-txns-outputvalue" |
|
case blockchain.ErrDuplicateTxInputs: |
|
return "bad-txns-dupinputs" |
|
case blockchain.ErrBadTxInput: |
|
return "bad-txns-badinput" |
|
case blockchain.ErrMissingTx: |
|
return "bad-txns-missinginput" |
|
case blockchain.ErrUnfinalizedTx: |
|
return "bad-txns-unfinalizedtx" |
|
case blockchain.ErrDuplicateTx: |
|
return "bad-txns-duplicate" |
|
case blockchain.ErrOverwriteTx: |
|
return "bad-txns-overwrite" |
|
case blockchain.ErrImmatureSpend: |
|
return "bad-txns-maturity" |
|
case blockchain.ErrDoubleSpend: |
|
return "bad-txns-dblspend" |
|
case blockchain.ErrSpendTooHigh: |
|
return "bad-txns-highspend" |
|
case blockchain.ErrBadFees: |
|
return "bad-txns-fees" |
|
case blockchain.ErrTooManySigOps: |
|
return "high-sigops" |
|
case blockchain.ErrFirstTxNotCoinbase: |
|
return "bad-txns-nocoinbase" |
|
case blockchain.ErrMultipleCoinbases: |
|
return "bad-txns-multicoinbase" |
|
case blockchain.ErrBadCoinbaseScriptLen: |
|
return "bad-cb-length" |
|
case blockchain.ErrBadCoinbaseValue: |
|
return "bad-cb-value" |
|
case blockchain.ErrMissingCoinbaseHeight: |
|
return "bad-cb-height" |
|
case blockchain.ErrBadCoinbaseHeight: |
|
return "bad-cb-height" |
|
case blockchain.ErrScriptMalformed: |
|
return "bad-script-malformed" |
|
case blockchain.ErrScriptValidation: |
|
return "bad-script-validate" |
|
} |
|
|
|
return "rejected: " + err.Error() |
|
} |
|
|
|
// handleGetBlockTemplateProposal is a helper for handleGetBlockTemplate which |
|
// deals with block proposals. |
|
// |
|
// See https://en.bitcoin.it/wiki/BIP_0023 for more details. |
|
func handleGetBlockTemplateProposal(s *rpcServer, request *btcjson.TemplateRequest) (interface{}, error) { |
|
hexData := request.Data |
|
if hexData == "" { |
|
return false, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCType, |
|
Message: fmt.Sprintf("Data must contain the " + |
|
"hex-encoded serialized block that is being " + |
|
"proposed"), |
|
} |
|
} |
|
|
|
// Ensure the provided data is sane and deserialize the proposed block. |
|
if len(hexData)%2 != 0 { |
|
hexData = "0" + hexData |
|
} |
|
dataBytes, err := hex.DecodeString(hexData) |
|
if err != nil { |
|
return false, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDeserialization, |
|
Message: fmt.Sprintf("Data must be "+ |
|
"hexadecimal string (not %q)", hexData), |
|
} |
|
} |
|
var msgBlock wire.MsgBlock |
|
if err := msgBlock.Deserialize(bytes.NewReader(dataBytes)); err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDeserialization, |
|
Message: "Block decode failed: " + err.Error(), |
|
} |
|
} |
|
block := btcutil.NewBlock(&msgBlock) |
|
|
|
// Ensure the block is building from the expected previous block. |
|
expectedPrevHash, _ := s.server.blockManager.chainState.Best() |
|
prevHash := &block.MsgBlock().Header.PrevBlock |
|
if expectedPrevHash == nil || !expectedPrevHash.IsEqual(prevHash) { |
|
return "bad-prevblk", nil |
|
} |
|
|
|
flags := blockchain.BFDryRun | blockchain.BFNoPoWCheck |
|
isOrphan, err := s.server.blockManager.ProcessBlock(block, flags) |
|
if err != nil { |
|
if _, ok := err.(blockchain.RuleError); !ok { |
|
err := rpcsLog.Errorf("Failed to process block "+ |
|
"proposal: %v", err) |
|
return nil, &btcjson.RPCError{ |
|
Code: -25, // TODO: ErrRpcVerify |
|
Message: err.Error(), |
|
} |
|
} |
|
|
|
rpcsLog.Infof("Rejected block proposal: %v", err) |
|
return chainErrToGBTErrString(err), nil |
|
} |
|
if isOrphan { |
|
return "orphan", nil |
|
} |
|
|
|
return nil, nil |
|
} |
|
|
|
// handleGetBlockTemplate implements the getblocktemplate command. |
|
// |
|
// See https://en.bitcoin.it/wiki/BIP_0022 and |
|
// https://en.bitcoin.it/wiki/BIP_0023 for more details. |
|
func handleGetBlockTemplate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetBlockTemplateCmd) |
|
request := c.Request |
|
|
|
// Set the default mode and override it if supplied. |
|
mode := "template" |
|
if request != nil && request.Mode != "" { |
|
mode = request.Mode |
|
} |
|
|
|
switch mode { |
|
case "template": |
|
return handleGetBlockTemplateRequest(s, request, closeChan) |
|
case "proposal": |
|
return handleGetBlockTemplateProposal(s, request) |
|
} |
|
|
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: "Invalid mode", |
|
} |
|
} |
|
|
|
// handleGetConnectionCount implements the getconnectioncount command. |
|
func handleGetConnectionCount(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
return s.server.ConnectedCount(), nil |
|
} |
|
|
|
// handleGetCurrentNet implements the getcurrentnet command. |
|
func handleGetCurrentNet(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
return s.server.chainParams.Net, nil |
|
} |
|
|
|
// handleGetDifficulty implements the getdifficulty command. |
|
func handleGetDifficulty(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
sha, _, err := s.server.db.NewestSha() |
|
if err != nil { |
|
rpcsLog.Errorf("Error getting sha: %v", err) |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDifficulty, |
|
Message: "Error getting difficulty: " + err.Error(), |
|
} |
|
} |
|
blockHeader, err := s.server.db.FetchBlockHeaderBySha(sha) |
|
if err != nil { |
|
rpcsLog.Errorf("Error getting block: %v", err) |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDifficulty, |
|
Message: "Error getting difficulty: " + err.Error(), |
|
} |
|
} |
|
return getDifficultyRatio(blockHeader.Bits), nil |
|
} |
|
|
|
// handleGetGenerate implements the getgenerate command. |
|
func handleGetGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
return s.server.cpuMiner.IsMining(), nil |
|
} |
|
|
|
// handleGetHashesPerSec implements the gethashespersec command. |
|
func handleGetHashesPerSec(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
return int64(s.server.cpuMiner.HashesPerSecond()), nil |
|
} |
|
|
|
// handleGetInfo implements the getinfo command. We only return the fields |
|
// that are not related to wallet functionality. |
|
func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
// We require the current block height and sha. |
|
sha, height, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
blkHeader, err := s.server.db.FetchBlockHeaderBySha(sha) |
|
if err != nil { |
|
context := "Failed to get block" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
ret := &btcjson.InfoChainResult{ |
|
Version: int32(1000000*appMajor + 10000*appMinor + 100*appPatch), |
|
ProtocolVersion: int32(maxProtocolVersion), |
|
Blocks: int32(height), |
|
TimeOffset: int64(s.server.timeSource.Offset().Seconds()), |
|
Connections: s.server.ConnectedCount(), |
|
Proxy: cfg.Proxy, |
|
Difficulty: getDifficultyRatio(blkHeader.Bits), |
|
TestNet: cfg.TestNet3, |
|
RelayFee: float64(minTxRelayFee) / btcutil.SatoshiPerBitcoin, |
|
} |
|
|
|
return ret, nil |
|
} |
|
|
|
// handleGetMempoolInfo implements the getmempoolinfo command. |
|
func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
txD := s.server.txMemPool.TxDescs() |
|
|
|
var numBytes int64 |
|
for _, desc := range txD { |
|
numBytes += int64(desc.Tx.MsgTx().SerializeSize()) |
|
} |
|
|
|
ret := &btcjson.GetMempoolInfoResult{ |
|
Size: int64(len(txD)), |
|
Bytes: numBytes, |
|
} |
|
|
|
return ret, nil |
|
} |
|
|
|
// handleGetMiningInfo implements the getmininginfo command. We only return the |
|
// fields that are not related to wallet functionality. |
|
func handleGetMiningInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
sha, height, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
block, err := s.server.db.FetchBlockBySha(sha) |
|
if err != nil { |
|
context := "Failed to get block" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
blockBytes, err := block.Bytes() |
|
if err != nil { |
|
context := "Failed to get block bytes" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
// Create a default getnetworkhashps command to use defaults and make |
|
// use of the existing getnetworkhashps handler. |
|
gnhpsCmd := btcjson.NewGetNetworkHashPSCmd(nil, nil) |
|
networkHashesPerSecIface, err := handleGetNetworkHashPS(s, gnhpsCmd, |
|
closeChan) |
|
if err != nil { |
|
return nil, err |
|
} |
|
networkHashesPerSec, ok := networkHashesPerSecIface.(int64) |
|
if !ok { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: "networkHashesPerSec is not an int64", |
|
} |
|
} |
|
|
|
result := btcjson.GetMiningInfoResult{ |
|
Blocks: height, |
|
CurrentBlockSize: uint64(len(blockBytes)), |
|
CurrentBlockTx: uint64(len(block.MsgBlock().Transactions)), |
|
Difficulty: getDifficultyRatio(block.MsgBlock().Header.Bits), |
|
Generate: s.server.cpuMiner.IsMining(), |
|
GenProcLimit: s.server.cpuMiner.NumWorkers(), |
|
HashesPerSec: int64(s.server.cpuMiner.HashesPerSecond()), |
|
NetworkHashPS: networkHashesPerSec, |
|
PooledTx: uint64(s.server.txMemPool.Count()), |
|
TestNet: cfg.TestNet3, |
|
} |
|
return &result, nil |
|
} |
|
|
|
// handleGetNetTotals implements the getnettotals command. |
|
func handleGetNetTotals(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
totalBytesRecv, totalBytesSent := s.server.NetTotals() |
|
reply := &btcjson.GetNetTotalsResult{ |
|
TotalBytesRecv: totalBytesRecv, |
|
TotalBytesSent: totalBytesSent, |
|
TimeMillis: time.Now().UTC().UnixNano() / int64(time.Millisecond), |
|
} |
|
return reply, nil |
|
} |
|
|
|
// handleGetNetworkHashPS implements the getnetworkhashps command. |
|
func handleGetNetworkHashPS(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
// Note: All valid error return paths should return an int64. |
|
// Literal zeros are inferred as int, and won't coerce to int64 |
|
// because the return value is an interface{}. |
|
|
|
c := cmd.(*btcjson.GetNetworkHashPSCmd) |
|
|
|
_, newestHeight, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
// When the passed height is too high or zero, just return 0 now |
|
// since we can't reasonably calculate the number of network hashes |
|
// per second from invalid values. When it's negative, use the current |
|
// best block height. |
|
endHeight := int64(-1) |
|
if c.Height != nil { |
|
endHeight = int64(*c.Height) |
|
} |
|
if endHeight > newestHeight || endHeight == 0 { |
|
return int64(0), nil |
|
} |
|
if endHeight < 0 { |
|
endHeight = newestHeight |
|
} |
|
|
|
// Calculate the starting block height based on the passed number of |
|
// blocks. When the passed value is negative, use the last block the |
|
// difficulty changed as the starting height. Also make sure the |
|
// starting height is not before the beginning of the chain. |
|
numBlocks := int64(120) |
|
if c.Blocks != nil { |
|
numBlocks = int64(*c.Blocks) |
|
} |
|
var startHeight int64 |
|
if numBlocks <= 0 { |
|
startHeight = endHeight - ((endHeight % blockchain.BlocksPerRetarget) + 1) |
|
} else { |
|
startHeight = endHeight - numBlocks |
|
} |
|
if startHeight < 0 { |
|
startHeight = 0 |
|
} |
|
rpcsLog.Debugf("Calculating network hashes per second from %d to %d", |
|
startHeight, endHeight) |
|
|
|
// Find the min and max block timestamps as well as calculate the total |
|
// amount of work that happened between the start and end blocks. |
|
var minTimestamp, maxTimestamp time.Time |
|
totalWork := big.NewInt(0) |
|
for curHeight := startHeight; curHeight <= endHeight; curHeight++ { |
|
hash, err := s.server.db.FetchBlockShaByHeight(curHeight) |
|
if err != nil { |
|
context := "Failed to fetch block hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
header, err := s.server.db.FetchBlockHeaderBySha(hash) |
|
if err != nil { |
|
context := "Failed to fetch block header" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
if curHeight == startHeight { |
|
minTimestamp = header.Timestamp |
|
maxTimestamp = minTimestamp |
|
} else { |
|
totalWork.Add(totalWork, blockchain.CalcWork(header.Bits)) |
|
|
|
if minTimestamp.After(header.Timestamp) { |
|
minTimestamp = header.Timestamp |
|
} |
|
if maxTimestamp.Before(header.Timestamp) { |
|
maxTimestamp = header.Timestamp |
|
} |
|
} |
|
} |
|
|
|
// Calculate the difference in seconds between the min and max block |
|
// timestamps and avoid division by zero in the case where there is no |
|
// time difference. |
|
timeDiff := int64(maxTimestamp.Sub(minTimestamp) / time.Second) |
|
if timeDiff == 0 { |
|
return int64(0), nil |
|
} |
|
|
|
hashesPerSec := new(big.Int).Div(totalWork, big.NewInt(timeDiff)) |
|
return hashesPerSec.Int64(), nil |
|
} |
|
|
|
// handleGetPeerInfo implements the getpeerinfo command. |
|
func handleGetPeerInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
return s.server.PeerInfo(), nil |
|
} |
|
|
|
// handleGetRawMempool implements the getrawmempool command. |
|
func handleGetRawMempool(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetRawMempoolCmd) |
|
mp := s.server.txMemPool |
|
descs := mp.TxDescs() |
|
|
|
if c.Verbose != nil && *c.Verbose { |
|
result := make(map[string]*btcjson.GetRawMempoolVerboseResult, |
|
len(descs)) |
|
|
|
_, newestHeight, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
mp.RLock() |
|
defer mp.RUnlock() |
|
for _, desc := range descs { |
|
// Calculate the starting and current priority from the |
|
// the tx's inputs. Use zeros if one or more of the |
|
// input transactions can't be found for some reason. |
|
var startingPriority, currentPriority float64 |
|
inputTxs, err := mp.fetchInputTransactions(desc.Tx) |
|
if err == nil { |
|
startingPriority = desc.StartingPriority(inputTxs) |
|
currentPriority = desc.CurrentPriority(inputTxs, |
|
newestHeight+1) |
|
} |
|
|
|
mpd := &btcjson.GetRawMempoolVerboseResult{ |
|
Size: int32(desc.Tx.MsgTx().SerializeSize()), |
|
Fee: btcutil.Amount(desc.Fee).ToBTC(), |
|
Time: desc.Added.Unix(), |
|
Height: desc.Height, |
|
StartingPriority: startingPriority, |
|
CurrentPriority: currentPriority, |
|
Depends: make([]string, 0), |
|
} |
|
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 |
|
} |
|
|
|
return result, nil |
|
} |
|
|
|
// The response is simply an array of the transaction hashes if the |
|
// verbose flag is not set. |
|
hashStrings := make([]string, len(descs)) |
|
for i := range hashStrings { |
|
hashStrings[i] = descs[i].Tx.Sha().String() |
|
} |
|
|
|
return hashStrings, nil |
|
} |
|
|
|
// handleGetRawTransaction implements the getrawtransaction command. |
|
func handleGetRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetRawTransactionCmd) |
|
|
|
// Convert the provided transaction hash hex to a ShaHash. |
|
txHash, err := wire.NewShaHashFromStr(c.Txid) |
|
if err != nil { |
|
return nil, rpcDecodeHexError(c.Txid) |
|
} |
|
|
|
// Try to fetch the transaction from the memory pool and if that fails, |
|
// try the block database. |
|
var mtx *wire.MsgTx |
|
var blkHash *wire.ShaHash |
|
var blkHeight int64 |
|
tx, err := s.server.txMemPool.FetchTransaction(txHash) |
|
if err != nil { |
|
txList, err := s.server.db.FetchTxBySha(txHash) |
|
if err != nil || len(txList) == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCNoTxInfo, |
|
Message: "No information available about transaction", |
|
} |
|
} |
|
|
|
lastTx := txList[len(txList)-1] |
|
mtx = lastTx.Tx |
|
blkHash = lastTx.BlkSha |
|
blkHeight = lastTx.Height |
|
} else { |
|
mtx = tx.MsgTx() |
|
} |
|
|
|
// When the verbose flag isn't set, simply return the network-serialized |
|
// transaction as a hex-encoded string. |
|
if c.Verbose == nil || *c.Verbose == 0 { |
|
// Note that this is intentionally not directly returning |
|
// because the first return value is a string and it would |
|
// result in returning an empty string to the client instead of |
|
// nothing (nil) in the case of an error. |
|
mtxHex, err := messageToHex(mtx) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return mtxHex, nil |
|
} |
|
|
|
var blkHeader *wire.BlockHeader |
|
var blkHashStr string |
|
var chainHeight int64 |
|
if blkHash != nil { |
|
blkHeader, err = s.server.db.FetchBlockHeaderBySha(blkHash) |
|
if err != nil { |
|
rpcsLog.Errorf("Error fetching sha: %v", err) |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCBlockNotFound, |
|
Message: "Block not found: " + err.Error(), |
|
} |
|
} |
|
|
|
_, chainHeight, err = s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
blkHashStr = blkHash.String() |
|
} |
|
|
|
rawTxn, err := createTxRawResult(s.server.chainParams, mtx, |
|
txHash.String(), blkHeader, blkHashStr, blkHeight, chainHeight) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return *rawTxn, nil |
|
} |
|
|
|
// bigToLEUint256 returns the passed big integer as an unsigned 256-bit integer |
|
// encoded as little-endian bytes. Numbers which are larger than the max |
|
// unsigned 256-bit integer are truncated. |
|
func bigToLEUint256(n *big.Int) [uint256Size]byte { |
|
// Pad or truncate the big-endian big int to correct number of bytes. |
|
nBytes := n.Bytes() |
|
nlen := len(nBytes) |
|
pad := 0 |
|
start := 0 |
|
if nlen <= uint256Size { |
|
pad = uint256Size - nlen |
|
} else { |
|
start = nlen - uint256Size |
|
} |
|
var buf [uint256Size]byte |
|
copy(buf[pad:], nBytes[start:]) |
|
|
|
// Reverse the bytes to little endian and return them. |
|
for i := 0; i < uint256Size/2; i++ { |
|
buf[i], buf[uint256Size-1-i] = buf[uint256Size-1-i], buf[i] |
|
} |
|
return buf |
|
} |
|
|
|
// reverseUint32Array treats the passed bytes as a series of uint32s and |
|
// reverses the byte order of each uint32. The passed byte slice must be a |
|
// multiple of 4 for a correct result. The passed bytes slice is modified. |
|
func reverseUint32Array(b []byte) { |
|
blen := len(b) |
|
for i := 0; i < blen; i += 4 { |
|
b[i], b[i+3] = b[i+3], b[i] |
|
b[i+1], b[i+2] = b[i+2], b[i+1] |
|
} |
|
} |
|
|
|
// handleGetTxOut handles gettxout commands. |
|
func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetTxOutCmd) |
|
|
|
// Convert the provided transaction hash hex to a ShaHash. |
|
txHash, err := wire.NewShaHashFromStr(c.Txid) |
|
if err != nil { |
|
return nil, rpcDecodeHexError(c.Txid) |
|
} |
|
|
|
// If requested and the tx is available in the mempool try to fetch it |
|
// from there, otherwise attempt to fetch from the block database. |
|
var mtx *wire.MsgTx |
|
var bestBlockSha string |
|
var confirmations int64 |
|
var dbSpentInfo []bool |
|
includeMempool := true |
|
if c.IncludeMempool != nil { |
|
includeMempool = *c.IncludeMempool |
|
} |
|
// TODO: This is racy. It should attempt to fetch it directly and check |
|
// the error. |
|
if includeMempool && s.server.txMemPool.HaveTransaction(txHash) { |
|
tx, err := s.server.txMemPool.FetchTransaction(txHash) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCNoTxInfo, |
|
Message: "No information available about transaction", |
|
} |
|
} |
|
mtx = tx.MsgTx() |
|
confirmations = 0 |
|
bestBlockSha = "" |
|
} else { |
|
txList, err := s.server.db.FetchTxBySha(txHash) |
|
if err != nil || len(txList) == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCNoTxInfo, |
|
Message: "No information available about transaction", |
|
} |
|
} |
|
|
|
lastTx := txList[len(txList)-1] |
|
mtx = lastTx.Tx |
|
blkHash := lastTx.BlkSha |
|
txHeight := lastTx.Height |
|
dbSpentInfo = lastTx.TxSpent |
|
|
|
_, bestHeight, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
confirmations = 1 + bestHeight - txHeight |
|
bestBlockSha = blkHash.String() |
|
} |
|
|
|
if c.Vout > uint32(len(mtx.TxOut)-1) { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidTxVout, |
|
Message: "Ouput index number (vout) does not exist " + |
|
"for transaction.", |
|
} |
|
} |
|
|
|
txOut := mtx.TxOut[c.Vout] |
|
if txOut == nil { |
|
errStr := fmt.Sprintf("Output index: %d for txid: %s does "+ |
|
"not exist", c.Vout, c.Txid) |
|
return nil, internalRPCError(errStr, "") |
|
} |
|
|
|
// To match the behavior of the reference client, this handler returns |
|
// nil (JSON null) if the transaction output is spent by another |
|
// transaction already in the database. Unspent transaction outputs |
|
// from transactions in mempool, as well as mined transactions that are |
|
// spent by a mempool transaction, are not affected by this. |
|
if dbSpentInfo != nil && dbSpentInfo[c.Vout] { |
|
return nil, nil |
|
} |
|
|
|
// Disassemble script into single line printable format. |
|
// The disassembled string will contain [error] inline if the script |
|
// doesn't fully parse, so ignore the error here. |
|
script := txOut.PkScript |
|
disbuf, _ := txscript.DisasmString(script) |
|
|
|
// Get further info about the script. |
|
// 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, _ := txscript.ExtractPkScriptAddrs(script, |
|
s.server.chainParams) |
|
addresses := make([]string, len(addrs)) |
|
for i, addr := range addrs { |
|
addresses[i] = addr.EncodeAddress() |
|
} |
|
|
|
txOutReply := &btcjson.GetTxOutResult{ |
|
BestBlock: bestBlockSha, |
|
Confirmations: confirmations, |
|
Value: btcutil.Amount(txOut.Value).ToUnit(btcutil.AmountBTC), |
|
Version: mtx.Version, |
|
ScriptPubKey: btcjson.ScriptPubKeyResult{ |
|
Asm: disbuf, |
|
Hex: hex.EncodeToString(script), |
|
ReqSigs: int32(reqSigs), |
|
Type: scriptClass.String(), |
|
Addresses: addresses, |
|
}, |
|
Coinbase: blockchain.IsCoinBase(btcutil.NewTx(mtx)), |
|
} |
|
return txOutReply, nil |
|
} |
|
|
|
// handleGetWorkRequest is a helper for handleGetWork which deals with |
|
// generating and returning work to the caller. |
|
// |
|
// This function MUST be called with the RPC workstate locked. |
|
func handleGetWorkRequest(s *rpcServer) (interface{}, error) { |
|
state := s.workState |
|
|
|
// Generate a new block template when the current best block has |
|
// changed or the transactions in the memory pool have been updated |
|
// and it has been at least one minute since the last template was |
|
// generated. |
|
lastTxUpdate := s.server.txMemPool.LastUpdated() |
|
latestHash, latestHeight := s.server.blockManager.chainState.Best() |
|
msgBlock := state.msgBlock |
|
if msgBlock == nil || state.prevHash == nil || |
|
!state.prevHash.IsEqual(latestHash) || |
|
(state.lastTxUpdate != lastTxUpdate && |
|
time.Now().After(state.lastGenerated.Add(time.Minute))) { |
|
|
|
// Reset the extra nonce and clear all cached template |
|
// variations if the best block changed. |
|
if state.prevHash != nil && !state.prevHash.IsEqual(latestHash) { |
|
state.extraNonce = 0 |
|
state.blockInfo = make(map[wire.ShaHash]*workStateBlockInfo) |
|
} |
|
|
|
// Reset the previous best hash the block template was generated |
|
// against so any errors below cause the next invocation to try |
|
// again. |
|
state.prevHash = nil |
|
|
|
// Choose a payment address at random. |
|
payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] |
|
|
|
template, err := NewBlockTemplate(s.server.txMemPool, payToAddr) |
|
if err != nil { |
|
context := "Failed to create new block template" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
msgBlock = template.block |
|
|
|
// Update work state to ensure another block template isn't |
|
// generated until needed. |
|
state.msgBlock = msgBlock |
|
state.lastGenerated = time.Now() |
|
state.lastTxUpdate = lastTxUpdate |
|
state.prevHash = latestHash |
|
|
|
rpcsLog.Debugf("Generated block template (timestamp %v, extra "+ |
|
"nonce %d, target %064x, merkle root %s, signature "+ |
|
"script %x)", msgBlock.Header.Timestamp, |
|
state.extraNonce, |
|
blockchain.CompactToBig(msgBlock.Header.Bits), |
|
msgBlock.Header.MerkleRoot, |
|
msgBlock.Transactions[0].TxIn[0].SignatureScript) |
|
} else { |
|
// At this point, there is a saved block template and a new |
|
// request for work was made, but either the available |
|
// transactions haven't change or it hasn't been long enough to |
|
// trigger a new block template to be generated. So, update the |
|
// existing block template and track the variations so each |
|
// variation can be regenerated if a caller finds an answer and |
|
// makes a submission against it. |
|
|
|
// Update the time of the block template to the current time |
|
// while accounting for the median time of the past several |
|
// blocks per the chain consensus rules. |
|
UpdateBlockTime(msgBlock, s.server.blockManager) |
|
|
|
// Increment the extra nonce and update the block template |
|
// with the new value by regenerating the coinbase script and |
|
// setting the merkle root to the new value. |
|
state.extraNonce++ |
|
err := UpdateExtraNonce(msgBlock, latestHeight+1, state.extraNonce) |
|
if err != nil { |
|
errStr := fmt.Sprintf("Failed to update extra nonce: "+ |
|
"%v", err) |
|
return nil, internalRPCError(errStr, "") |
|
} |
|
|
|
rpcsLog.Debugf("Updated block template (timestamp %v, extra "+ |
|
"nonce %d, target %064x, merkle root %s, signature "+ |
|
"script %x)", msgBlock.Header.Timestamp, |
|
state.extraNonce, |
|
blockchain.CompactToBig(msgBlock.Header.Bits), |
|
msgBlock.Header.MerkleRoot, |
|
msgBlock.Transactions[0].TxIn[0].SignatureScript) |
|
} |
|
|
|
// In order to efficiently store the variations of block templates that |
|
// have been provided to callers, save a pointer to the block as well as |
|
// the modified signature script keyed by the merkle root. This |
|
// information, along with the data that is included in a work |
|
// submission, is used to rebuild the block before checking the |
|
// submitted solution. |
|
coinbaseTx := msgBlock.Transactions[0] |
|
state.blockInfo[msgBlock.Header.MerkleRoot] = &workStateBlockInfo{ |
|
msgBlock: msgBlock, |
|
signatureScript: coinbaseTx.TxIn[0].SignatureScript, |
|
} |
|
|
|
// Serialize the block header into a buffer large enough to hold the |
|
// the block header and the internal sha256 padding that is added and |
|
// retuned as part of the data below. |
|
data := make([]byte, 0, getworkDataLen) |
|
buf := bytes.NewBuffer(data) |
|
err := msgBlock.Header.Serialize(buf) |
|
if err != nil { |
|
errStr := fmt.Sprintf("Failed to serialize data: %v", err) |
|
return nil, internalRPCError(errStr, "") |
|
} |
|
|
|
// Calculate the midstate for the block header. The midstate here is |
|
// the internal state of the sha256 algorithm for the first chunk of the |
|
// block header (sha256 operates on 64-byte chunks) which is before the |
|
// nonce. This allows sophisticated callers to avoid hashing the first |
|
// chunk over and over while iterating the nonce range. |
|
data = data[:buf.Len()] |
|
midstate := fastsha256.MidState256(data) |
|
|
|
// Expand the data slice to include the full data buffer and apply the |
|
// internal sha256 padding which consists of a single 1 bit followed |
|
// by enough zeros to pad the message out to 56 bytes followed by the |
|
// length of the message in bits encoded as a big-endian uint64 |
|
// (8 bytes). Thus, the resulting length is a multiple of the sha256 |
|
// block size (64 bytes). This makes the data ready for sophisticated |
|
// caller to make use of only the second chunk along with the midstate |
|
// for the first chunk. |
|
data = data[:getworkDataLen] |
|
data[wire.MaxBlockHeaderPayload] = 0x80 |
|
binary.BigEndian.PutUint64(data[len(data)-8:], |
|
wire.MaxBlockHeaderPayload*8) |
|
|
|
// Create the hash1 field which is a zero hash along with the internal |
|
// sha256 padding as described above. This field is really quite |
|
// useless, but it is required for compatibility with the reference |
|
// implementation. |
|
var hash1 [hash1Len]byte |
|
hash1[wire.HashSize] = 0x80 |
|
binary.BigEndian.PutUint64(hash1[len(hash1)-8:], wire.HashSize*8) |
|
|
|
// The final result reverses the each of the fields to little endian. |
|
// In particular, the data, hash1, and midstate fields are treated as |
|
// arrays of uint32s (per the internal sha256 hashing state) which are |
|
// in big endian, and thus each 4 bytes is byte swapped. The target is |
|
// also in big endian, but it is treated as a uint256 and byte swapped |
|
// to little endian accordingly. |
|
// |
|
// The fact the fields are reversed in this way is rather odd and likey |
|
// an artifact of some legacy internal state in the reference |
|
// implementation, but it is required for compatibility. |
|
reverseUint32Array(data) |
|
reverseUint32Array(hash1[:]) |
|
reverseUint32Array(midstate[:]) |
|
target := bigToLEUint256(blockchain.CompactToBig(msgBlock.Header.Bits)) |
|
reply := &btcjson.GetWorkResult{ |
|
Data: hex.EncodeToString(data), |
|
Hash1: hex.EncodeToString(hash1[:]), |
|
Midstate: hex.EncodeToString(midstate[:]), |
|
Target: hex.EncodeToString(target[:]), |
|
} |
|
return reply, nil |
|
} |
|
|
|
// handleGetWorkSubmission is a helper for handleGetWork which deals with |
|
// the calling submitting work to be verified and processed. |
|
// |
|
// This function MUST be called with the RPC workstate locked. |
|
func handleGetWorkSubmission(s *rpcServer, hexData string) (interface{}, error) { |
|
// Ensure the provided data is sane. |
|
if len(hexData)%2 != 0 { |
|
hexData = "0" + hexData |
|
} |
|
data, err := hex.DecodeString(hexData) |
|
if err != nil { |
|
return false, rpcDecodeHexError(hexData) |
|
} |
|
if len(data) != getworkDataLen { |
|
return false, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: fmt.Sprintf("Argument must be "+ |
|
"%d bytes (not %d)", getworkDataLen, |
|
len(data)), |
|
} |
|
} |
|
|
|
// Reverse the data as if it were an array of 32-bit unsigned integers. |
|
// The fact the getwork request and submission data is reversed in this |
|
// way is rather odd and likey an artifact of some legacy internal state |
|
// in the reference implementation, but it is required for |
|
// compatibility. |
|
reverseUint32Array(data) |
|
|
|
// Deserialize the block header from the data. |
|
var submittedHeader wire.BlockHeader |
|
bhBuf := bytes.NewReader(data[0:wire.MaxBlockHeaderPayload]) |
|
err = submittedHeader.Deserialize(bhBuf) |
|
if err != nil { |
|
return false, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: fmt.Sprintf("Argument does not "+ |
|
"contain a valid block header: %v", err), |
|
} |
|
} |
|
|
|
// Look up the full block for the provided data based on the |
|
// merkle root. Return false to indicate the solve failed if |
|
// it's not available. |
|
state := s.workState |
|
blockInfo, ok := state.blockInfo[submittedHeader.MerkleRoot] |
|
if !ok { |
|
rpcsLog.Debugf("Block submitted via getwork has no matching "+ |
|
"template for merkle root %s", |
|
submittedHeader.MerkleRoot) |
|
return false, nil |
|
} |
|
|
|
// Reconstruct the block using the submitted header stored block info. |
|
msgBlock := blockInfo.msgBlock |
|
block := btcutil.NewBlock(msgBlock) |
|
msgBlock.Header.Timestamp = submittedHeader.Timestamp |
|
msgBlock.Header.Nonce = submittedHeader.Nonce |
|
msgBlock.Transactions[0].TxIn[0].SignatureScript = blockInfo.signatureScript |
|
merkles := blockchain.BuildMerkleTreeStore(block.Transactions()) |
|
msgBlock.Header.MerkleRoot = *merkles[len(merkles)-1] |
|
|
|
// Ensure the submitted block hash is less than the target difficulty. |
|
err = blockchain.CheckProofOfWork(block, activeNetParams.PowLimit) |
|
if err != nil { |
|
// Anything other than a rule violation is an unexpected error, |
|
// so return that error as an internal error. |
|
if _, ok := err.(blockchain.RuleError); !ok { |
|
return false, internalRPCError("Unexpected error "+ |
|
"while checking proof of work: "+err.Error(), |
|
"") |
|
} |
|
|
|
rpcsLog.Debugf("Block submitted via getwork does not meet "+ |
|
"the required proof of work: %v", err) |
|
return false, nil |
|
} |
|
|
|
latestHash, _ := s.server.blockManager.chainState.Best() |
|
if !msgBlock.Header.PrevBlock.IsEqual(latestHash) { |
|
rpcsLog.Debugf("Block submitted via getwork with previous "+ |
|
"block %s is stale", msgBlock.Header.PrevBlock) |
|
return false, nil |
|
} |
|
|
|
// Process this block using the same rules as blocks coming from other |
|
// nodes. This will in turn relay it to the network like normal. |
|
isOrphan, err := s.server.blockManager.ProcessBlock(block, blockchain.BFNone) |
|
if err != nil || isOrphan { |
|
// Anything other than a rule violation is an unexpected error, |
|
// so return that error as an internal error. |
|
if _, ok := err.(blockchain.RuleError); !ok { |
|
return false, internalRPCError("Unexpected error "+ |
|
"while processing block: "+err.Error(), "") |
|
} |
|
|
|
rpcsLog.Infof("Block submitted via getwork rejected: %v", err) |
|
return false, nil |
|
} |
|
|
|
// The block was accepted. |
|
rpcsLog.Infof("Block submitted via getwork accepted: %s", block.Sha()) |
|
return true, nil |
|
} |
|
|
|
// handleGetWork implements the getwork command. |
|
func handleGetWork(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.GetWorkCmd) |
|
|
|
// Respond with an error if there are no addresses to pay the created |
|
// blocks to. |
|
if len(cfg.miningAddrs) == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: "No payment addresses specified via --miningaddr", |
|
} |
|
} |
|
|
|
// Return an error if there are no peers connected since there is no |
|
// way to relay a found block or receive transactions to work on. |
|
// However, allow this state when running in the regression test or |
|
// simulation test mode. |
|
if !(cfg.RegressionTest || cfg.SimNet) && s.server.ConnectedCount() == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCClientNotConnected, |
|
Message: "Bitcoin is not connected", |
|
} |
|
} |
|
|
|
// No point in generating or accepting work before the chain is synced. |
|
_, currentHeight := s.server.blockManager.chainState.Best() |
|
if currentHeight != 0 && !s.server.blockManager.IsCurrent() { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCClientInInitialDownload, |
|
Message: "Bitcoin is downloading blocks...", |
|
} |
|
} |
|
|
|
// Protect concurrent access from multiple RPC invocations for work |
|
// requests and submission. |
|
s.workState.Lock() |
|
defer s.workState.Unlock() |
|
|
|
// When the caller provides data, it is a submission of a supposedly |
|
// solved block that needs to be checked and submitted to the network |
|
// if valid. |
|
if c.Data != nil && *c.Data != "" { |
|
return handleGetWorkSubmission(s, *c.Data) |
|
} |
|
|
|
// No data was provided, so the caller is requesting work. |
|
return handleGetWorkRequest(s) |
|
} |
|
|
|
// handleHelp implements the help command. |
|
func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.HelpCmd) |
|
|
|
// Provide a usage overview of all commands when no specific command |
|
// was specified. |
|
var command string |
|
if c.Command != nil { |
|
command = *c.Command |
|
} |
|
if command == "" { |
|
usage, err := s.helpCacher.rpcUsage(false) |
|
if err != nil { |
|
context := "Failed to generate RPC usage" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
return usage, nil |
|
} |
|
|
|
// Check that the command asked for is supported and implemented. Only |
|
// search the main list of handlers since help should not be provided |
|
// for commands that are unimplemented or related to wallet |
|
// functionality. |
|
if _, ok := rpcHandlers[command]; !ok { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParameter, |
|
Message: "Unknown command: " + command, |
|
} |
|
} |
|
|
|
// Get the help for the command. |
|
help, err := s.helpCacher.rpcMethodHelp(command) |
|
if err != nil { |
|
context := "Failed to generate help" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
return help, nil |
|
} |
|
|
|
// handlePing implements the ping command. |
|
func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
// Ask server to ping \o_ |
|
nonce, err := wire.RandomUint64() |
|
if err != nil { |
|
return nil, internalRPCError("Not sending ping - failed to "+ |
|
"generate nonce: "+err.Error(), "") |
|
} |
|
s.server.BroadcastMessage(wire.NewMsgPing(nonce)) |
|
|
|
return nil, nil |
|
} |
|
|
|
// handleSearchRawTransaction implements the searchrawtransactions command. |
|
func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
if !cfg.AddrIndex { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCMisc, |
|
Message: "Address index must be enabled (--addrindex)", |
|
} |
|
} |
|
if !s.server.addrIndexer.IsCaughtUp() { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCMisc, |
|
Message: "Address index has not yet caught up to the " + |
|
"current best height", |
|
} |
|
} |
|
|
|
c := cmd.(*btcjson.SearchRawTransactionsCmd) |
|
|
|
// Attempt to decode the supplied address. |
|
addr, err := btcutil.DecodeAddress(c.Address, s.server.chainParams) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidAddressOrKey, |
|
Message: "Invalid address or key: " + err.Error(), |
|
} |
|
} |
|
|
|
var addressTxs []*database.TxListReply |
|
|
|
var numRequested, numToSkip int |
|
if c.Count != nil { |
|
numRequested = *c.Count |
|
if numRequested < 0 { |
|
numRequested = 1 |
|
} |
|
} |
|
if c.Skip != nil { |
|
numToSkip = *c.Skip |
|
if numToSkip < 0 { |
|
numToSkip = 0 |
|
} |
|
} |
|
|
|
// While it's more efficient to check the mempool for relevant transactions |
|
// first, we want to return results in order of occurrence/dependency so |
|
// we'll check the mempool only if there aren't enough results returned |
|
// by the database. |
|
dbTxs, err := s.server.db.FetchTxsForAddr(addr, numToSkip, |
|
numRequested-len(addressTxs)) |
|
if err == nil { |
|
for _, txReply := range dbTxs { |
|
addressTxs = append(addressTxs, txReply) |
|
} |
|
} |
|
|
|
// This code (and txMemPool.FilterTransactionsByAddress()) doesn't sort by |
|
// dependency. This might be something we want to do in the future when we |
|
// return results for the client's convenience, or leave it to the client. |
|
if len(addressTxs) < numRequested { |
|
memPoolTxs, err := s.server.txMemPool.FilterTransactionsByAddress(addr) |
|
if err == nil { |
|
for _, tx := range memPoolTxs { |
|
txReply := &database.TxListReply{Tx: tx.MsgTx(), Sha: tx.Sha()} |
|
addressTxs = append(addressTxs, txReply) |
|
if len(addressTxs) == numRequested { |
|
break |
|
} |
|
} |
|
} |
|
} |
|
|
|
// If neither source yielded any results, then the address has never |
|
// been used. |
|
if len(addressTxs) == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCNoTxInfo, |
|
Message: "No information available about transaction", |
|
} |
|
} |
|
|
|
// When not in verbose mode, simply return a list of serialized txs. |
|
if c.Verbose != nil && *c.Verbose == 0 { |
|
serializedTxs := make([]string, len(addressTxs), len(addressTxs)) |
|
for i, txReply := range addressTxs { |
|
serializedTxs[i], err = messageToHex(txReply.Tx) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
return serializedTxs, nil |
|
} |
|
|
|
// Otherwise, we'll need to populate raw tx results. |
|
// Grab the current best height for tx confirmation calculation. |
|
_, maxIdx, err := s.server.db.NewestSha() |
|
if err != nil { |
|
context := "Failed to get newest hash" |
|
return nil, internalRPCError(err.Error(), context) |
|
} |
|
|
|
rawTxns := make([]btcjson.TxRawResult, len(addressTxs), len(addressTxs)) |
|
for i, txReply := range addressTxs { |
|
txHash := txReply.Sha.String() |
|
mtx := txReply.Tx |
|
|
|
// Transactions grabbed from the mempool aren't yet |
|
// within a block. So we conditionally fetch a txs |
|
// embedded block here. This will be reflected in the |
|
// final JSON output (mempool won't have confirmations). |
|
var blkHeader *wire.BlockHeader |
|
var blkHashStr string |
|
var blkHeight int64 |
|
if txReply.BlkSha != nil { |
|
blkHeader, err = s.server.db.FetchBlockHeaderBySha(txReply.BlkSha) |
|
if err != nil { |
|
rpcsLog.Errorf("Error fetching sha: %v", err) |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCBlockNotFound, |
|
Message: "Block not found", |
|
} |
|
} |
|
blkHashStr = txReply.BlkSha.String() |
|
blkHeight = txReply.Height |
|
} |
|
|
|
rawTxn, err := createTxRawResult(s.server.chainParams, mtx, |
|
txHash, blkHeader, blkHashStr, blkHeight, maxIdx) |
|
if err != nil { |
|
return nil, err |
|
} |
|
rawTxns[i] = *rawTxn |
|
} |
|
return rawTxns, nil |
|
} |
|
|
|
// handleSendRawTransaction implements the sendrawtransaction command. |
|
func handleSendRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.SendRawTransactionCmd) |
|
// Deserialize and send off to tx relay |
|
hexStr := c.HexTx |
|
if len(hexStr)%2 != 0 { |
|
hexStr = "0" + hexStr |
|
} |
|
serializedTx, err := hex.DecodeString(hexStr) |
|
if err != nil { |
|
return nil, rpcDecodeHexError(hexStr) |
|
} |
|
msgtx := wire.NewMsgTx() |
|
err = msgtx.Deserialize(bytes.NewReader(serializedTx)) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDeserialization, |
|
Message: "TX decode failed: " + err.Error(), |
|
} |
|
} |
|
|
|
tx := btcutil.NewTx(msgtx) |
|
err = s.server.txMemPool.ProcessTransaction(tx, false, false) |
|
if err != nil { |
|
// 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. In both cases, a JSON-RPC |
|
// error is returned to the client with the deserialization |
|
// error code (to match bitcoind behavior). |
|
if _, ok := err.(RuleError); ok { |
|
rpcsLog.Debugf("Rejected transaction %v: %v", tx.Sha(), |
|
err) |
|
} else { |
|
rpcsLog.Errorf("Failed to process transaction %v: %v", |
|
tx.Sha(), err) |
|
} |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDeserialization, |
|
Message: "TX rejected: " + err.Error(), |
|
} |
|
} |
|
|
|
// Keep track of all the sendrawtransaction request txns so that they |
|
// can be rebroadcast if they don't make their way into a block. |
|
iv := wire.NewInvVect(wire.InvTypeTx, tx.Sha()) |
|
s.server.AddRebroadcastInventory(iv, tx) |
|
|
|
return tx.Sha().String(), nil |
|
} |
|
|
|
// handleSetGenerate implements the setgenerate command. |
|
func handleSetGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.SetGenerateCmd) |
|
|
|
// Disable generation regardless of the provided generate flag if the |
|
// maximum number of threads (goroutines for our purposes) is 0. |
|
// Otherwise enable or disable it depending on the provided flag. |
|
generate := c.Generate |
|
genProcLimit := -1 |
|
if c.GenProcLimit != nil { |
|
genProcLimit = *c.GenProcLimit |
|
} |
|
if genProcLimit == 0 { |
|
generate = false |
|
} |
|
|
|
if !generate { |
|
s.server.cpuMiner.Stop() |
|
} else { |
|
// Respond with an error if there are no addresses to pay the |
|
// created blocks to. |
|
if len(cfg.miningAddrs) == 0 { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInternal.Code, |
|
Message: "No payment addresses specified " + |
|
"via --miningaddr", |
|
} |
|
} |
|
|
|
// It's safe to call start even if it's already started. |
|
s.server.cpuMiner.SetNumWorkers(int32(genProcLimit)) |
|
s.server.cpuMiner.Start() |
|
} |
|
return nil, nil |
|
} |
|
|
|
// handleStop implements the stop command. |
|
func handleStop(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
s.server.Stop() |
|
return "btcd stopping.", nil |
|
} |
|
|
|
// handleSubmitBlock implements the submitblock command. |
|
func handleSubmitBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.SubmitBlockCmd) |
|
|
|
// Deserialize the submitted block. |
|
hexStr := c.HexBlock |
|
if len(hexStr)%2 != 0 { |
|
hexStr = "0" + c.HexBlock |
|
} |
|
serializedBlock, err := hex.DecodeString(hexStr) |
|
if err != nil { |
|
return nil, rpcDecodeHexError(hexStr) |
|
} |
|
|
|
block, err := btcutil.NewBlockFromBytes(serializedBlock) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCDeserialization, |
|
Message: "Block decode failed: " + err.Error(), |
|
} |
|
} |
|
|
|
_, err = s.server.blockManager.ProcessBlock(block, blockchain.BFNone) |
|
if err != nil { |
|
return fmt.Sprintf("rejected: %s", err.Error()), nil |
|
} |
|
|
|
rpcsLog.Infof("Accepted block %s via submitblock", block.Sha()) |
|
return nil, nil |
|
} |
|
|
|
// handleValidateAddress implements the validateaddress command. |
|
func handleValidateAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.ValidateAddressCmd) |
|
|
|
result := btcjson.ValidateAddressChainResult{} |
|
addr, err := btcutil.DecodeAddress(c.Address, activeNetParams.Params) |
|
if err != nil { |
|
// Return the default value (false) for IsValid. |
|
return result, nil |
|
} |
|
|
|
result.Address = addr.EncodeAddress() |
|
result.IsValid = true |
|
|
|
return result, nil |
|
} |
|
|
|
func verifyChain(db database.Db, level, depth int32, timeSource blockchain.MedianTimeSource) error { |
|
_, curHeight64, err := db.NewestSha() |
|
if err != nil { |
|
rpcsLog.Errorf("Verify is unable to fetch current block "+ |
|
"height: %v", err) |
|
} |
|
curHeight := int32(curHeight64) |
|
|
|
finishHeight := curHeight - depth |
|
if finishHeight < 0 { |
|
finishHeight = 0 |
|
} |
|
rpcsLog.Infof("Verifying chain for %d blocks at level %d", |
|
curHeight-finishHeight, level) |
|
|
|
for height := curHeight; height > finishHeight; height-- { |
|
// Level 0 just looks up the block. |
|
sha, err := db.FetchBlockShaByHeight(int64(height)) |
|
if err != nil { |
|
rpcsLog.Errorf("Verify is unable to fetch block at "+ |
|
"height %d: %v", height, err) |
|
return err |
|
} |
|
|
|
block, err := db.FetchBlockBySha(sha) |
|
if err != nil { |
|
rpcsLog.Errorf("Verify is unable to fetch block at "+ |
|
"sha %v height %d: %v", sha, height, err) |
|
return err |
|
} |
|
|
|
// Level 1 does basic chain sanity checks. |
|
if level > 0 { |
|
err := blockchain.CheckBlockSanity(block, |
|
activeNetParams.PowLimit, timeSource) |
|
if err != nil { |
|
rpcsLog.Errorf("Verify is unable to "+ |
|
"validate block at sha %v height "+ |
|
"%d: %v", sha, height, err) |
|
return err |
|
} |
|
} |
|
} |
|
rpcsLog.Infof("Chain verify completed successfully") |
|
|
|
return nil |
|
} |
|
|
|
// handleVerifyChain implements the verifychain command. |
|
func handleVerifyChain(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.VerifyChainCmd) |
|
|
|
var checkLevel, checkDepth int32 |
|
if c.CheckLevel != nil { |
|
checkLevel = *c.CheckLevel |
|
} |
|
if c.CheckDepth != nil { |
|
checkDepth = *c.CheckDepth |
|
} |
|
|
|
err := verifyChain(s.server.db, checkLevel, checkDepth, |
|
s.server.timeSource) |
|
return err == nil, nil |
|
} |
|
|
|
// handleVerifyMessage implements the verifymessage command. |
|
func handleVerifyMessage(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { |
|
c := cmd.(*btcjson.VerifyMessageCmd) |
|
|
|
// Decode the provided address. |
|
addr, err := btcutil.DecodeAddress(c.Address, activeNetParams.Params) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidAddressOrKey, |
|
Message: "Invalid address or key: " + err.Error(), |
|
} |
|
} |
|
|
|
// Only P2PKH addresses are valid for signing. |
|
if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCType, |
|
Message: "Address is not a pay-to-pubkey-hash address", |
|
} |
|
} |
|
|
|
// Decode base64 signature. |
|
sig, err := base64.StdEncoding.DecodeString(c.Signature) |
|
if err != nil { |
|
return nil, &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCParse.Code, |
|
Message: "Malformed base64 encoding: " + err.Error(), |
|
} |
|
} |
|
|
|
// Validate the signature - this just shows that it was valid at all. |
|
// we will compare it with the key next. |
|
pk, wasCompressed, err := btcec.RecoverCompact(btcec.S256(), sig, |
|
wire.DoubleSha256([]byte("Bitcoin Signed Message:\n"+c.Message))) |
|
if err != nil { |
|
// Mirror Bitcoin Core behavior, which treats error in |
|
// RecoverCompact as invalid signature. |
|
return false, nil |
|
} |
|
|
|
// Reconstruct the pubkey hash. |
|
btcPK := (*btcec.PublicKey)(pk) |
|
var serializedPK []byte |
|
if wasCompressed { |
|
serializedPK = btcPK.SerializeCompressed() |
|
} else { |
|
serializedPK = btcPK.SerializeUncompressed() |
|
} |
|
address, err := btcutil.NewAddressPubKey(serializedPK, |
|
activeNetParams.Params) |
|
if err != nil { |
|
// Again mirror Bitcoin Core behavior, which treats error in public key |
|
// reconstruction as invalid signature. |
|
return false, nil |
|
} |
|
|
|
// Return boolean if addresses match. |
|
return address.EncodeAddress() == c.Address, nil |
|
} |
|
|
|
// rpcServer holds the items the rpc server may need to access (config, |
|
// shutdown, main server, etc.) |
|
type rpcServer struct { |
|
started int32 |
|
shutdown int32 |
|
server *server |
|
authsha [fastsha256.Size]byte |
|
limitauthsha [fastsha256.Size]byte |
|
ntfnMgr *wsNotificationManager |
|
numClients int32 |
|
statusLines map[int]string |
|
statusLock sync.RWMutex |
|
wg sync.WaitGroup |
|
listeners []net.Listener |
|
workState *workState |
|
gbtWorkState *gbtWorkState |
|
helpCacher *helpCacher |
|
quit chan int |
|
} |
|
|
|
// httpStatusLine returns a response Status-Line (RFC 2616 Section 6.1) |
|
// for the given request and response status code. This function was lifted and |
|
// adapted from the standard library HTTP server code since it's not exported. |
|
func (s *rpcServer) httpStatusLine(req *http.Request, code int) string { |
|
// Fast path: |
|
key := code |
|
proto11 := req.ProtoAtLeast(1, 1) |
|
if !proto11 { |
|
key = -key |
|
} |
|
s.statusLock.RLock() |
|
line, ok := s.statusLines[key] |
|
s.statusLock.RUnlock() |
|
if ok { |
|
return line |
|
} |
|
|
|
// Slow path: |
|
proto := "HTTP/1.0" |
|
if proto11 { |
|
proto = "HTTP/1.1" |
|
} |
|
codeStr := strconv.Itoa(code) |
|
text := http.StatusText(code) |
|
if text != "" { |
|
line = proto + " " + codeStr + " " + text + "\r\n" |
|
s.statusLock.Lock() |
|
s.statusLines[key] = line |
|
s.statusLock.Unlock() |
|
} else { |
|
text = "status code " + codeStr |
|
line = proto + " " + codeStr + " " + text + "\r\n" |
|
} |
|
|
|
return line |
|
} |
|
|
|
// writeHTTPResponseHeaders writes the necessary response headers prior to |
|
// writing an HTTP body given a request to use for protocol negotiation, headers |
|
// to write, a status code, and a writer. |
|
func (s *rpcServer) writeHTTPResponseHeaders(req *http.Request, headers http.Header, code int, w io.Writer) error { |
|
_, err := io.WriteString(w, s.httpStatusLine(req, code)) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err = headers.Write(w) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
_, err = io.WriteString(w, "\r\n") |
|
if err != nil { |
|
return err |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// Stop is used by server.go to stop the rpc listener. |
|
func (s *rpcServer) Stop() error { |
|
if atomic.AddInt32(&s.shutdown, 1) != 1 { |
|
rpcsLog.Infof("RPC server is already in the process of shutting down") |
|
return nil |
|
} |
|
rpcsLog.Warnf("RPC server shutting down") |
|
for _, listener := range s.listeners { |
|
err := listener.Close() |
|
if err != nil { |
|
rpcsLog.Errorf("Problem shutting down rpc: %v", err) |
|
return err |
|
} |
|
} |
|
s.ntfnMgr.Shutdown() |
|
s.ntfnMgr.WaitForShutdown() |
|
close(s.quit) |
|
s.wg.Wait() |
|
rpcsLog.Infof("RPC server shutdown complete") |
|
return nil |
|
} |
|
|
|
// limitConnections responds with a 503 service unavailable and returns true if |
|
// adding another client would exceed the maximum allow RPC clients. |
|
// |
|
// This function is safe for concurrent access. |
|
func (s *rpcServer) limitConnections(w http.ResponseWriter, remoteAddr string) bool { |
|
if int(atomic.LoadInt32(&s.numClients)+1) > cfg.RPCMaxClients { |
|
rpcsLog.Infof("Max RPC clients exceeded [%d] - "+ |
|
"disconnecting client %s", cfg.RPCMaxClients, |
|
remoteAddr) |
|
http.Error(w, "503 Too busy. Try again later.", |
|
http.StatusServiceUnavailable) |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
// incrementClients adds one to the number of connected RPC clients. Note |
|
// this only applies to standard clients. Websocket clients have their own |
|
// limits and are tracked separately. |
|
// |
|
// This function is safe for concurrent access. |
|
func (s *rpcServer) incrementClients() { |
|
atomic.AddInt32(&s.numClients, 1) |
|
} |
|
|
|
// decrementClients subtracts one from the number of connected RPC clients. |
|
// Note this only applies to standard clients. Websocket clients have their own |
|
// limits and are tracked separately. |
|
// |
|
// This function is safe for concurrent access. |
|
func (s *rpcServer) decrementClients() { |
|
atomic.AddInt32(&s.numClients, -1) |
|
} |
|
|
|
// 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. |
|
// |
|
// The first bool return value signifies auth success (true if successful) and |
|
// the second bool return value specifies whether the user can change the state |
|
// of the server (true) or whether the user is limited (false). The second is |
|
// always false if the first is. |
|
func (s *rpcServer) checkAuth(r *http.Request, require bool) (bool, bool, |
|
error) { |
|
authhdr := r.Header["Authorization"] |
|
if len(authhdr) <= 0 { |
|
if require { |
|
rpcsLog.Warnf("RPC authentication failure from %s", |
|
r.RemoteAddr) |
|
return false, false, errors.New("auth failure") |
|
} |
|
|
|
return false, false, nil |
|
} |
|
|
|
authsha := fastsha256.Sum256([]byte(authhdr[0])) |
|
|
|
// Check for limited auth first as in environments with limited users, those |
|
// are probably expected to have a higher volume of calls |
|
limitcmp := subtle.ConstantTimeCompare(authsha[:], s.limitauthsha[:]) |
|
if limitcmp == 1 { |
|
return true, false, nil |
|
} |
|
|
|
// Check for admin-level auth |
|
cmp := subtle.ConstantTimeCompare(authsha[:], s.authsha[:]) |
|
if cmp == 1 { |
|
return true, true, nil |
|
} |
|
|
|
// Request's auth doesn't match either user |
|
rpcsLog.Warnf("RPC authentication failure from %s", r.RemoteAddr) |
|
return false, false, errors.New("auth failure") |
|
} |
|
|
|
// parsedRPCCmd represents a JSON-RPC request object that has been parsed into |
|
// a known concrete command along with any error that might have happened while |
|
// parsing it. |
|
type parsedRPCCmd struct { |
|
id interface{} |
|
method string |
|
cmd interface{} |
|
err *btcjson.RPCError |
|
} |
|
|
|
// standardCmdResult checks that a parsed command is a standard Bitcoin JSON-RPC |
|
// command and runs the appropriate handler to reply to the command. Any |
|
// commands which are not recognized or not implemented will return an error |
|
// suitable for use in replies. |
|
func (s *rpcServer) standardCmdResult(cmd *parsedRPCCmd, closeChan <-chan struct{}) (interface{}, error) { |
|
handler, ok := rpcHandlers[cmd.method] |
|
if ok { |
|
goto handled |
|
} |
|
_, ok = rpcAskWallet[cmd.method] |
|
if ok { |
|
handler = handleAskWallet |
|
goto handled |
|
} |
|
_, ok = rpcUnimplemented[cmd.method] |
|
if ok { |
|
handler = handleUnimplemented |
|
goto handled |
|
} |
|
return nil, btcjson.ErrRPCMethodNotFound |
|
handled: |
|
|
|
return handler(s, cmd.cmd, closeChan) |
|
} |
|
|
|
// parseCmd parses a JSON-RPC request object into known concrete command. The |
|
// err field of the returned parsedRPCCmd struct will contain an RPC error that |
|
// is suitable for use in replies if the command is invalid in some way such as |
|
// an unregistered command or invalid parameters. |
|
func parseCmd(request *btcjson.Request) *parsedRPCCmd { |
|
var parsedCmd parsedRPCCmd |
|
parsedCmd.id = request.ID |
|
parsedCmd.method = request.Method |
|
|
|
cmd, err := btcjson.UnmarshalCmd(request) |
|
if err != nil { |
|
// When the error is because the method is not registered, |
|
// produce a method not found RPC error. |
|
if jerr, ok := err.(btcjson.Error); ok && |
|
jerr.ErrorCode == btcjson.ErrUnregisteredMethod { |
|
|
|
parsedCmd.err = btcjson.ErrRPCMethodNotFound |
|
return &parsedCmd |
|
} |
|
|
|
// Otherwise, some type of invalid parameters is the |
|
// cause, so produce the equivalent RPC error. |
|
parsedCmd.err = btcjson.NewRPCError( |
|
btcjson.ErrRPCInvalidParams.Code, err.Error()) |
|
return &parsedCmd |
|
} |
|
|
|
parsedCmd.cmd = cmd |
|
return &parsedCmd |
|
} |
|
|
|
// createMarshalledReply returns a new marshalled JSON-RPC response given the |
|
// passed parameters. It will automatically convert errors that are not of |
|
// the type *btcjson.RPCError to the appropriate type as needed. |
|
func createMarshalledReply(id, result interface{}, replyErr error) ([]byte, error) { |
|
var jsonErr *btcjson.RPCError |
|
if replyErr != nil { |
|
if jErr, ok := replyErr.(*btcjson.RPCError); ok { |
|
jsonErr = jErr |
|
} else { |
|
jsonErr = internalRPCError(replyErr.Error(), "") |
|
} |
|
} |
|
|
|
return btcjson.MarshalResponse(id, result, jsonErr) |
|
} |
|
|
|
// jsonRPCRead handles reading and responding to RPC messages. |
|
func (s *rpcServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, |
|
isAdmin bool) { |
|
if atomic.LoadInt32(&s.shutdown) != 0 { |
|
return |
|
} |
|
|
|
// Read and close the JSON-RPC request body from the caller. |
|
body, err := ioutil.ReadAll(r.Body) |
|
r.Body.Close() |
|
if err != nil { |
|
errMsg := fmt.Sprintf("error reading JSON message: %v", err) |
|
errCode := http.StatusBadRequest |
|
http.Error(w, strconv.FormatInt(int64(errCode), 10)+" "+errMsg, |
|
errCode) |
|
return |
|
} |
|
|
|
// Unfortunately, the http server doesn't provide the ability to |
|
// change the read deadline for the new connection and having one breaks |
|
// long polling. However, not having a read deadline on the initial |
|
// connection would mean clients can connect and idle forever. Thus, |
|
// hijack the connecton from the HTTP server, clear the read deadline, |
|
// and handle writing the response manually. |
|
hj, ok := w.(http.Hijacker) |
|
if !ok { |
|
errMsg := "webserver doesn't support hijacking" |
|
rpcsLog.Warnf(errMsg) |
|
errCode := http.StatusInternalServerError |
|
http.Error(w, strconv.FormatInt(int64(errCode), 10)+" "+errMsg, |
|
errCode) |
|
return |
|
} |
|
conn, buf, err := hj.Hijack() |
|
if err != nil { |
|
rpcsLog.Warnf("Failed to hijack HTTP connection: %v", err) |
|
errCode := http.StatusInternalServerError |
|
http.Error(w, strconv.FormatInt(int64(errCode), 10)+" "+ |
|
err.Error(), errCode) |
|
return |
|
} |
|
defer conn.Close() |
|
defer buf.Flush() |
|
conn.SetReadDeadline(timeZeroVal) |
|
|
|
// Attempt to parse the raw body into a JSON-RPC request. |
|
var responseID interface{} |
|
var jsonErr error |
|
var result interface{} |
|
var request btcjson.Request |
|
if err := json.Unmarshal(body, &request); err != nil { |
|
jsonErr = &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCParse.Code, |
|
Message: "Failed to parse request: " + err.Error(), |
|
} |
|
} |
|
if jsonErr == nil { |
|
// Requests with no ID (notifications) must not have a response |
|
// per the JSON-RPC spec. |
|
if request.ID == nil { |
|
return |
|
} |
|
|
|
// The parse was at least successful enough to have an ID so |
|
// set it for the response. |
|
responseID = request.ID |
|
|
|
// Setup a close notifier. Since the connection is hijacked, |
|
// the CloseNotifer on the ResponseWriter is not available. |
|
closeChan := make(chan struct{}, 1) |
|
go func() { |
|
_, err := conn.Read(make([]byte, 1)) |
|
if err != nil { |
|
close(closeChan) |
|
} |
|
}() |
|
|
|
// Check if the user is limited and set error if method unauthorized |
|
if !isAdmin { |
|
if _, ok := rpcLimited[request.Method]; !ok { |
|
jsonErr = &btcjson.RPCError{ |
|
Code: btcjson.ErrRPCInvalidParams.Code, |
|
Message: "limited user not authorized for this method", |
|
} |
|
} |
|
} |
|
|
|
if jsonErr == nil { |
|
// Attempt to parse the JSON-RPC request into a known concrete |
|
// command. |
|
parsedCmd := parseCmd(&request) |
|
if parsedCmd.err != nil { |
|
jsonErr = parsedCmd.err |
|
} else { |
|
result, jsonErr = s.standardCmdResult(parsedCmd, closeChan) |
|
} |
|
} |
|
} |
|
|
|
// Marshal the response. |
|
msg, err := createMarshalledReply(responseID, result, jsonErr) |
|
if err != nil { |
|
rpcsLog.Errorf("Failed to marshal reply: %v", err) |
|
return |
|
} |
|
|
|
// Write the response. |
|
err = s.writeHTTPResponseHeaders(r, w.Header(), http.StatusOK, buf) |
|
if err != nil { |
|
rpcsLog.Error(err) |
|
return |
|
} |
|
if _, err := buf.Write(msg); err != nil { |
|
rpcsLog.Errorf("Failed to write marshalled reply: %v", err) |
|
} |
|
} |
|
|
|
// jsonAuthFail sends a message back to the client if the http auth is rejected. |
|
func jsonAuthFail(w http.ResponseWriter) { |
|
w.Header().Add("WWW-Authenticate", `Basic realm="btcd RPC"`) |
|
http.Error(w, "401 Unauthorized.", http.StatusUnauthorized) |
|
} |
|
|
|
// Start is used by server.go to start the rpc listener. |
|
func (s *rpcServer) Start() { |
|
if atomic.AddInt32(&s.started, 1) != 1 { |
|
return |
|
} |
|
|
|
rpcsLog.Trace("Starting RPC server") |
|
rpcServeMux := http.NewServeMux() |
|
httpServer := &http.Server{ |
|
Handler: rpcServeMux, |
|
|
|
// Timeout connections which don't complete the initial |
|
// handshake within the allowed timeframe. |
|
ReadTimeout: time.Second * rpcAuthTimeoutSeconds, |
|
} |
|
rpcServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
|
w.Header().Set("Connection", "close") |
|
w.Header().Set("Content-Type", "application/json") |
|
r.Close = true |
|
|
|
// Limit the number of connections to max allowed. |
|
if s.limitConnections(w, r.RemoteAddr) { |
|
return |
|
} |
|
|
|
// Keep track of the number of connected clients. |
|
s.incrementClients() |
|
defer s.decrementClients() |
|
_, isAdmin, err := s.checkAuth(r, true) |
|
if err != nil { |
|
jsonAuthFail(w) |
|
return |
|
} |
|
|
|
// Read and respond to the request. |
|
s.jsonRPCRead(w, r, isAdmin) |
|
}) |
|
|
|
// Websocket endpoint. |
|
rpcServeMux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { |
|
authenticated, isAdmin, err := s.checkAuth(r, false) |
|
if err != nil { |
|
jsonAuthFail(w) |
|
return |
|
} |
|
|
|
// Attempt to upgrade the connection to a websocket connection |
|
// using the default size for read/write buffers. |
|
ws, err := websocket.Upgrade(w, r, nil, 0, 0) |
|
if err != nil { |
|
if _, ok := err.(websocket.HandshakeError); !ok { |
|
rpcsLog.Errorf("Unexpected websocket error: %v", |
|
err) |
|
} |
|
http.Error(w, "400 Bad Request.", http.StatusBadRequest) |
|
return |
|
} |
|
s.WebsocketHandler(ws, r.RemoteAddr, authenticated, isAdmin) |
|
}) |
|
|
|
for _, listener := range s.listeners { |
|
s.wg.Add(1) |
|
go func(listener net.Listener) { |
|
rpcsLog.Infof("RPC server listening on %s", listener.Addr()) |
|
httpServer.Serve(listener) |
|
rpcsLog.Tracef("RPC listener done for %s", listener.Addr()) |
|
s.wg.Done() |
|
}(listener) |
|
} |
|
|
|
s.ntfnMgr.Start() |
|
} |
|
|
|
// genCertPair generates a key/cert pair to the paths provided. |
|
func genCertPair(certFile, keyFile string) error { |
|
rpcsLog.Infof("Generating TLS certificates...") |
|
|
|
org := "btcd autogenerated cert" |
|
validUntil := time.Now().Add(10 * 365 * 24 * time.Hour) |
|
cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// Write cert and key files. |
|
if err = ioutil.WriteFile(certFile, cert, 0666); err != nil { |
|
return err |
|
} |
|
if err = ioutil.WriteFile(keyFile, key, 0600); err != nil { |
|
os.Remove(certFile) |
|
return err |
|
} |
|
|
|
rpcsLog.Infof("Done generating TLS certificates") |
|
return nil |
|
} |
|
|
|
// newRPCServer returns a new instance of the rpcServer struct. |
|
func newRPCServer(listenAddrs []string, s *server) (*rpcServer, error) { |
|
rpc := rpcServer{ |
|
server: s, |
|
statusLines: make(map[int]string), |
|
workState: newWorkState(), |
|
gbtWorkState: newGbtWorkState(s.timeSource), |
|
helpCacher: newHelpCacher(), |
|
quit: make(chan int), |
|
} |
|
if cfg.RPCUser != "" && cfg.RPCPass != "" { |
|
login := cfg.RPCUser + ":" + cfg.RPCPass |
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login)) |
|
rpc.authsha = fastsha256.Sum256([]byte(auth)) |
|
} |
|
if cfg.RPCLimitUser != "" && cfg.RPCLimitPass != "" { |
|
login := cfg.RPCLimitUser + ":" + cfg.RPCLimitPass |
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login)) |
|
rpc.limitauthsha = fastsha256.Sum256([]byte(auth)) |
|
} |
|
rpc.ntfnMgr = newWsNotificationManager(&rpc) |
|
|
|
// Setup TLS if not disabled. |
|
listenFunc := net.Listen |
|
if !cfg.DisableTLS { |
|
// Generate the TLS cert and key file if both don't already |
|
// exist. |
|
if !fileExists(cfg.RPCKey) && !fileExists(cfg.RPCCert) { |
|
err := genCertPair(cfg.RPCCert, cfg.RPCKey) |
|
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}, |
|
MinVersion: tls.VersionTLS12, |
|
} |
|
|
|
// Change the standard net.Listen function to the tls one. |
|
listenFunc = func(net string, laddr string) (net.Listener, error) { |
|
return tls.Listen(net, laddr, &tlsConfig) |
|
} |
|
} |
|
|
|
// TODO(oga) this code is similar to that in server, should be |
|
// factored into something shared. |
|
ipv4ListenAddrs, ipv6ListenAddrs, _, err := parseListeners(listenAddrs) |
|
if err != nil { |
|
return nil, err |
|
} |
|
listeners := make([]net.Listener, 0, |
|
len(ipv6ListenAddrs)+len(ipv4ListenAddrs)) |
|
for _, addr := range ipv4ListenAddrs { |
|
listener, err := listenFunc("tcp4", addr) |
|
if err != nil { |
|
rpcsLog.Warnf("Can't listen on %s: %v", addr, err) |
|
continue |
|
} |
|
listeners = append(listeners, listener) |
|
} |
|
|
|
for _, addr := range ipv6ListenAddrs { |
|
listener, err := listenFunc("tcp6", addr) |
|
if err != nil { |
|
rpcsLog.Warnf("Can't listen on %s: %v", addr, err) |
|
continue |
|
} |
|
listeners = append(listeners, listener) |
|
} |
|
if len(listeners) == 0 { |
|
return nil, errors.New("RPCS: No valid listen address") |
|
} |
|
|
|
rpc.listeners = listeners |
|
|
|
return &rpc, nil |
|
} |
|
|
|
func init() { |
|
rpcHandlers = rpcHandlersBeforeInit |
|
rand.Seed(time.Now().UnixNano()) |
|
}
|
|
|