dcrd/rpcserver.go
Donald Adu-Poku 1acb88472e multi: add amount field to TransactionInput.
This adds an amount field to TransactionInput intended for specifying the prev out amount when creating a transaction. createraw/ssrtx/sgentx/transaction rpcs are also updated accordingly as well as associated tests.

A typo in assignField is also corrected.
2018-06-21 23:54:42 +00:00

6555 lines
203 KiB
Go

// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2018 The Decred 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/elliptic"
"crypto/sha256"
"crypto/subtle"
"crypto/tls"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"math/big"
"math/rand"
"net"
"net/http"
"os"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/websocket"
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/certgen"
"github.com/decred/dcrd/chaincfg"
"github.com/decred/dcrd/chaincfg/chainec"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database"
"github.com/decred/dcrd/dcrjson"
"github.com/decred/dcrd/dcrutil"
"github.com/decred/dcrd/mempool"
"github.com/decred/dcrd/mining"
"github.com/decred/dcrd/txscript"
"github.com/decred/dcrd/wire"
"github.com/jrick/bitset"
)
// API version constants
const (
jsonrpcSemverString = "3.3.0"
jsonrpcSemverMajor = 3
jsonrpcSemverMinor = 3
jsonrpcSemverPatch = 0
)
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 blake256
// padding. The internal blake256 padding consists of a single 1 bit
// followed by zeros and a final 1 bit in order 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 blake256 block size (64 bytes). Given the padding
// requires at least a 1 bit and 64 bits for the padding, the following
// converts the block header length and hash block size to bits in order
// to ensure the correct number of hash blocks are calculated and then
// multiplies the result by the block hash block size in bytes.
getworkDataLen = (1 + ((wire.MaxBlockHeaderPayload*8 + 65) /
(chainhash.HashBlockSize * 8))) * chainhash.HashBlockSize
// getworkExpirationDiff is the number of blocks below the current
// best block in height to begin pruning out old block work from
// the template pool.
getworkExpirationDiff = 3
// 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
// merkleRootPairSize
merkleRootPairSize = 64
// sstxCommitmentString is the string to insert when a verbose
// transaction output's pkscript type is a ticket commitment.
sstxCommitmentString = "sstxcommitment"
)
var (
// blake256Pad is the extra blake256 internal padding needed for the
// data of the getwork RPC. It is set in the init routine since it is
// based on the size of the block header and requires a bit of
// calculation.
blake256Pad []byte
// 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 = &dcrjson.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"}
// JSON 2.0 batched request prefix
batchedRequestPrefix = []byte("[")
)
// Errors
var (
// ErrRPCUnimplemented is an error returned to RPC clients when the
// provided command is recognized, but not implemented.
ErrRPCUnimplemented = &dcrjson.RPCError{
Code: dcrjson.ErrRPCUnimplemented,
Message: "Command unimplemented",
}
// ErrRPCNoWallet is an error returned to RPC clients when the provided
// command is recognized as a wallet command.
ErrRPCNoWallet = &dcrjson.RPCError{
Code: dcrjson.ErrRPCNoWallet,
Message: "This implementation does not implement wallet commands",
}
// ErrInvalidLongPoll is an internal error code to indicate that
// longpollid is not formated properly.
ErrInvalidLongPoll = errors.New("invalid longpollid format")
)
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,
"createrawsstx": handleCreateRawSStx,
"createrawssgentx": handleCreateRawSSGenTx,
"createrawssrtx": handleCreateRawSSRtx,
"createrawtransaction": handleCreateRawTransaction,
"debuglevel": handleDebugLevel,
"decoderawtransaction": handleDecodeRawTransaction,
"decodescript": handleDecodeScript,
"estimatefee": handleEstimateFee,
"estimatestakediff": handleEstimateStakeDiff,
"existsaddress": handleExistsAddress,
"existsaddresses": handleExistsAddresses,
"existsmissedtickets": handleExistsMissedTickets,
"existsexpiredtickets": handleExistsExpiredTickets,
"existsliveticket": handleExistsLiveTicket,
"existslivetickets": handleExistsLiveTickets,
"existsmempooltxs": handleExistsMempoolTxs,
"generate": handleGenerate,
"getaddednodeinfo": handleGetAddedNodeInfo,
"getbestblock": handleGetBestBlock,
"getbestblockhash": handleGetBestBlockHash,
"getblock": handleGetBlock,
"getblockcount": handleGetBlockCount,
"getblockhash": handleGetBlockHash,
"getblockheader": handleGetBlockHeader,
"getblocksubsidy": handleGetBlockSubsidy,
"getchaintips": handleGetChainTips,
"getcoinsupply": handleGetCoinSupply,
"getconnectioncount": handleGetConnectionCount,
"getcurrentnet": handleGetCurrentNet,
"getdifficulty": handleGetDifficulty,
"getgenerate": handleGetGenerate,
"gethashespersec": handleGetHashesPerSec,
"getcfilter": handleGetCFilter,
"getcfilterheader": handleGetCFilterHeader,
"getheaders": handleGetHeaders,
"getinfo": handleGetInfo,
"getmempoolinfo": handleGetMempoolInfo,
"getmininginfo": handleGetMiningInfo,
"getnettotals": handleGetNetTotals,
"getnetworkhashps": handleGetNetworkHashPS,
"getpeerinfo": handleGetPeerInfo,
"getrawmempool": handleGetRawMempool,
"getrawtransaction": handleGetRawTransaction,
"getstakedifficulty": handleGetStakeDifficulty,
"getstakeversioninfo": handleGetStakeVersionInfo,
"getstakeversions": handleGetStakeVersions,
"getticketpoolvalue": handleGetTicketPoolValue,
"getvoteinfo": handleGetVoteInfo,
"gettxout": handleGetTxOut,
"getwork": handleGetWork,
"help": handleHelp,
"livetickets": handleLiveTickets,
"missedtickets": handleMissedTickets,
"node": handleNode,
"ping": handlePing,
"searchrawtransactions": handleSearchRawTransactions,
"rebroadcastmissed": handleRebroadcastMissed,
"rebroadcastwinners": handleRebroadcastWinners,
"sendrawtransaction": handleSendRawTransaction,
"setgenerate": handleSetGenerate,
"stop": handleStop,
"submitblock": handleSubmitBlock,
"ticketfeeinfo": handleTicketFeeInfo,
"ticketsforaddress": handleTicketsForAddress,
"ticketvwap": handleTicketVWAP,
"txfeeinfo": handleTxFeeInfo,
"validateaddress": handleValidateAddress,
"verifychain": handleVerifyChain,
"verifymessage": handleVerifyMessage,
"version": handleVersion,
}
// list of commands that we recognize, but for which dcrd has no support because
// it lacks support for wallet functionality. For these commands the user
// should ask a connected instance of dcrwallet.
var rpcAskWallet = map[string]struct{}{
"accountaddressindex": {},
"accountsyncaddressindex": {},
"addmultisigaddress": {},
"addticket": {},
"createencryptedwallet": {},
"createmultisig": {},
"dumpprivkey": {},
"getaccount": {},
"getaccountaddress": {},
"getaddressesbyaccount": {},
"getbalance": {},
"getnewaddress": {},
"getrawchangeaddress": {},
"getreceivedbyaccount": {},
"getreceivedbyaddress": {},
"getstakeinfo": {},
"getvotechoices": {},
"gettransaction": {},
"gettxoutsetinfo": {},
"getunconfirmedbalance": {},
"importprivkey": {},
"keypoolrefill": {},
"listaccounts": {},
"listlockunspent": {},
"listreceivedbyaccount": {},
"listreceivedbyaddress": {},
"listsinceblock": {},
"listtransactions": {},
"listunspent": {},
"lockunspent": {},
"rescanwallet": {},
"revoketickets": {},
"sendfrom": {},
"sendmany": {},
"sendtoaddress": {},
"setvotechoice": {},
"settxfee": {},
"signmessage": {},
"signrawtransaction": {},
"sweepaccount": {},
"stakepooluserinfo": {},
"verifyseed": {},
"walletinfo": {},
"walletlock": {},
"walletpassphrase": {},
"walletpassphrasechange": {},
}
// Commands that are currently unimplemented, but should ultimately be.
var rpcUnimplemented = map[string]struct{}{
"estimatefee": {},
"estimatepriority": {},
"getblocktemplate": {},
"getblockchaininfo": {},
"getnetworkinfo": {},
}
// Commands that are available to a limited user
var rpcLimited = map[string]struct{}{
// Websockets commands
"notifyblocks": {},
"notifynewtransactions": {},
"notifyreceived": {},
"notifyspent": {},
"rescan": {},
"session": {},
// Websockets AND HTTP/S commands
"help": {},
// HTTP/S-only commands
"createrawtransaction": {},
"decoderawtransaction": {},
"decodescript": {},
"getbestblock": {},
"getbestblockhash": {},
"getblock": {},
"getblockcount": {},
"getblockhash": {},
"getchaintips": {},
"getcurrentnet": {},
"getdifficulty": {},
"getinfo": {},
"getnettotals": {},
"getnetworkhashps": {},
"getrawmempool": {},
"getrawtransaction": {},
"gettxout": {},
"searchrawtransactions": {},
"sendrawtransaction": {},
"submitblock": {},
"validateaddress": {},
"verifymessage": {},
"version": {},
}
// 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
}
// rpcInternalError 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 rpcInternalError(errStr, context string) *dcrjson.RPCError {
logStr := errStr
if context != "" {
logStr = context + ": " + errStr
}
rpcsLog.Error(logStr)
return dcrjson.NewRPCError(dcrjson.ErrRPCInternal.Code, errStr)
}
// rpcInvalidError is a convenience function to convert an invalid parameter
// error to an RPC error with the appropriate code set.
func rpcInvalidError(fmtStr string, args ...interface{}) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCInvalidParameter,
fmt.Sprintf(fmtStr, args...))
}
// rpcDeserializetionError is a convenience function to convert a
// deserialization error to an RPC error with the appropriate code set.
func rpcDeserializationError(fmtStr string, args ...interface{}) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCDeserialization,
fmt.Sprintf(fmtStr, args...))
}
// rpcRuleError is a convenience function to convert a
// rule error to an RPC error with the appropriate code set.
func rpcRuleError(fmtStr string, args ...interface{}) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCMisc,
fmt.Sprintf(fmtStr, args...))
}
// rpcDuplicateTxError is a convenience function to convert a
// rejected duplicate tx error to an RPC error with the appropriate code set.
func rpcDuplicateTxError(fmtStr string, args ...interface{}) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCDuplicateTx,
fmt.Sprintf(fmtStr, args...))
}
// rpcAddressKeyError is a convenience function to convert an address/key 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 rpcAddressKeyError(fmtStr string, args ...interface{}) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCInvalidAddressOrKey,
fmt.Sprintf(fmtStr, args...))
}
// 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) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCDecodeHexString,
fmt.Sprintf("Argument must be hexadecimal string (not %q)",
gotHex))
}
// rpcNoTxInfoError is a convenience function for returning a nicely formatted
// RPC error which indicates there is no information available for the provided
// transaction hash.
func rpcNoTxInfoError(txHash *chainhash.Hash) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCNoTxInfo,
fmt.Sprintf("No information available about transaction %v",
txHash))
}
// rpcMiscError is a convenience function for returning a nicely formatted RPC
// error which indicates there is a unquantifiable error. Use this sparingly;
// misc return codes are a cop out.
func rpcMiscError(message string) *dcrjson.RPCError {
return dcrjson.NewRPCError(dcrjson.ErrRPCMisc, message)
}
// workStateBlockInfo houses information about how to reconstruct a block given
// its template and signature script.
type workStateBlockInfo struct {
msgBlock *wire.MsgBlock
pkScript []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 *chainhash.Hash
msgBlock *wire.MsgBlock
extraNonce uint64
}
// newWorkState returns a new instance of a workState with all internal fields
// initialized and ready to use.
func newWorkState() *workState {
return &workState{}
}
// 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 *chainhash.Hash
minTimestamp time.Time
template *BlockTemplate
notifyMap map[chainhash.Hash]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[chainhash.Hash]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 dcrwallet.
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.(*dcrjson.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, rpcInvalidError("Invalid subcommand for addnode")
}
if err != nil {
return nil, rpcInvalidError("%v: %v", c.SubCmd, err)
}
// 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.(*dcrjson.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, rpcInvalidError("%v: Invalid "+
"address or node ID", c.SubCmd)
}
}
if err != nil &&
peerExists(s.server.Peers(), addr, int32(nodeID)) {
return nil, rpcMiscError("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, rpcInvalidError("%v: invalid "+
"address or node ID", c.SubCmd)
}
}
if err != nil &&
peerExists(s.server.Peers(), addr, int32(nodeID)) {
return nil, rpcMiscError("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, rpcInvalidError("%v: invalid subcommand "+
"for node connect", subCmd)
}
default:
return nil, rpcInvalidError("%v: invalid subcommand for node",
c.SubCmd)
}
if err != nil {
return nil, rpcInvalidError("%v: %v", c.SubCmd, err)
}
// 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(peers []*serverPeer, addr string, nodeID int32) bool {
for _, p := range peers {
if p.ID() == nodeID || p.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 "", rpcInternalError(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.(*dcrjson.CreateRawTransactionCmd)
// Validate expiry, if given.
if c.Expiry != nil && *c.Expiry < 0 {
return nil, rpcInvalidError("Expiry out of range")
}
// Validate the locktime, if given.
if c.LockTime != nil &&
(*c.LockTime < 0 ||
*c.LockTime > int64(wire.MaxTxInSequenceNum)) {
return nil, rpcInvalidError("Locktime out of range")
}
// Add all transaction inputs to a new transaction after performing
// some validity checks.
mtx := wire.NewMsgTx()
for _, input := range c.Inputs {
txHash, err := chainhash.NewHashFromStr(input.Txid)
if err != nil {
return nil, rpcDecodeHexError(input.Txid)
}
if !(input.Tree == wire.TxTreeRegular ||
input.Tree == wire.TxTreeStake) {
return nil, rpcInvalidError("Tx tree must be regular " +
"or stake")
}
prevOutV := wire.NullValueIn
if input.Amount > 0 {
amt, err := dcrutil.NewAmount(input.Amount)
if err != nil {
return nil, rpcInvalidError(err.Error())
}
prevOutV = int64(amt)
}
prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree)
txIn := wire.NewTxIn(prevOut, prevOutV, []byte{})
if c.LockTime != nil && *c.LockTime != 0 {
txIn.Sequence = wire.MaxTxInSequenceNum - 1
}
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 > dcrutil.MaxAmount {
return nil, rpcInvalidError("Invalid amount: 0 >= %v "+
"> %v", amount, dcrutil.MaxAmount)
}
// Decode the provided address.
addr, err := dcrutil.DecodeAddress(encodedAddr)
if err != nil {
return nil, rpcAddressKeyError("Could not decode "+
"address: %v", err)
}
// 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 *dcrutil.AddressPubKeyHash:
case *dcrutil.AddressScriptHash:
default:
return nil, rpcAddressKeyError("Invalid type: %T", addr)
}
if !addr.IsForNet(s.server.chainParams) {
return nil, rpcAddressKeyError("Wrong network: %v",
addr)
}
// Create a new script which pays to the provided address.
pkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Pay to address script")
}
atomic, err := dcrutil.NewAmount(amount)
if err != nil {
return nil, rpcInternalError(err.Error(),
"New amount")
}
txOut := wire.NewTxOut(int64(atomic), pkScript)
mtx.AddTxOut(txOut)
}
// Set the Locktime, if given.
if c.LockTime != nil {
mtx.LockTime = uint32(*c.LockTime)
}
// Set the Expiry, if given.
if c.Expiry != nil {
mtx.Expiry = uint32(*c.Expiry)
}
// 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
}
// handleCreateRawSStx handles createrawsstx commands.
func handleCreateRawSStx(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.CreateRawSStxCmd)
// Basic sanity checks for the information coming from the cmd.
if len(c.Inputs) != len(c.COuts) {
return nil, rpcInvalidError("Number of inputs should be equal "+
"to the number of future commitment/change outs for "+
"any sstx; %v inputs given, but %v COuts",
len(c.Inputs), len(c.COuts))
}
if len(c.Amount) != 1 {
return nil, rpcInvalidError("Only one SSGen tagged output is "+
"allowed per sstx; len ssgenout %v", len(c.Amount))
}
// Add all transaction inputs to a new transaction after performing
// some validity checks.
mtx := wire.NewMsgTx()
for _, input := range c.Inputs {
txHash, err := chainhash.NewHashFromStr(input.Txid)
if err != nil {
return nil, rpcDecodeHexError(input.Txid)
}
if input.Vout < 0 {
return nil, rpcInvalidError("Vout must be positive")
}
if !(input.Tree == wire.TxTreeRegular ||
input.Tree == wire.TxTreeStake) {
rpcInvalidError("Tx tree must be regular or stake")
}
prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree)
txIn := wire.NewTxIn(prevOut, wire.NullValueIn, []byte{})
mtx.AddTxIn(txIn)
}
// Add all transaction outputs to the transaction after performing
// some validity checks.
amtTicket := int64(0)
for encodedAddr, amount := range c.Amount {
// Ensure amount is in the valid range for monetary amounts.
if amount <= 0 || amount > dcrutil.MaxAmount {
return nil, rpcInvalidError("Invalid SSTx commitment "+
"amount: 0 >= %v > %v", amount,
dcrutil.MaxAmount)
}
// Decode the provided address.
addr, err := dcrutil.DecodeAddress(encodedAddr)
if err != nil {
return nil, rpcAddressKeyError("Could not decode "+
"address: %v", err)
}
// 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 *dcrutil.AddressPubKeyHash:
default:
return nil, rpcAddressKeyError("Invalid address type: "+
"%T", addr)
}
if !addr.IsForNet(s.server.chainParams) {
return nil, rpcAddressKeyError("Wrong network: %v",
addr)
}
// Create a new script which pays to the provided address with an
// SStx tagged output.
pkScript, err := txscript.PayToSStx(addr)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not create TX script")
}
txOut := wire.NewTxOut(amount, pkScript)
mtx.AddTxOut(txOut)
amtTicket += amount
}
// Calculated the commitment amounts, then create the
// addresses and payout proportions as null data
// outputs.
inputAmts := make([]int64, len(c.Inputs))
for i, input := range c.Inputs {
inputAmts[i] = input.Amt
}
changeAmts := make([]int64, len(c.COuts))
for i, cout := range c.COuts {
changeAmts[i] = cout.ChangeAmt
}
// Check and make sure none of the change overflows
// the input amounts.
for i, amt := range inputAmts {
if changeAmts[i] >= amt {
return nil, rpcInvalidError("input %v >= amount %v",
changeAmts[i], amt)
}
}
// Obtain the commitment amounts.
_, amountsCommitted, err := stake.SStxNullOutputAmounts(inputAmts,
changeAmts, amtTicket)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Invalid SSTx output amounts")
}
for i, cout := range c.COuts {
// 1. Append future commitment output.
addr, err := dcrutil.DecodeAddress(cout.Addr)
if err != nil {
return nil, rpcAddressKeyError("Could not decode "+
"address: %v", err)
}
// 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 *dcrutil.AddressPubKeyHash:
break
case *dcrutil.AddressScriptHash:
break
default:
return nil, rpcAddressKeyError("Invalid type: %T", addr)
}
if !addr.IsForNet(s.server.chainParams) {
return nil, rpcAddressKeyError("Wrong network: %v",
addr)
}
// Create an OP_RETURN push containing the pubkeyhash to send
// rewards to. TODO Replace 0x0000 fee limits with an argument
// passed to the RPC call.
pkScript, err := txscript.GenerateSStxAddrPush(addr,
dcrutil.Amount(amountsCommitted[i]), 0x0000)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not create SStx script")
}
txout := wire.NewTxOut(int64(0), pkScript)
mtx.AddTxOut(txout)
// 2. Append change output.
// Ensure amount is in the valid range for monetary amounts.
if cout.ChangeAmt < 0 || cout.ChangeAmt > dcrutil.MaxAmount {
return nil, rpcInvalidError("Invalid change amount: 0 "+
"> %v > %v", cout.ChangeAmt, dcrutil.MaxAmount)
}
// Decode the provided address.
addr, err = dcrutil.DecodeAddress(cout.ChangeAddr)
if err != nil {
return nil, rpcAddressKeyError("Wrong network: %v",
addr)
}
// 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 *dcrutil.AddressPubKeyHash:
break
case *dcrutil.AddressScriptHash:
break
default:
return nil, rpcAddressKeyError("Invalid type: %T", addr)
}
if !addr.IsForNet(s.server.chainParams) {
return nil, rpcAddressKeyError("Wrong network: %v",
addr)
}
// Create a new script which pays to the provided address with
// an SStx change tagged output.
pkScript, err = txscript.PayToSStxChange(addr)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not create SStx change script")
}
txOut := wire.NewTxOut(cout.ChangeAmt, pkScript)
mtx.AddTxOut(txOut)
}
// Make sure we generated a valid SStx.
if err := stake.CheckSStx(mtx); err != nil {
return nil, rpcInternalError(err.Error(),
"Invalid SStx")
}
// Return the serialized and hex-encoded transaction.
mtxHex, err := messageToHex(mtx)
if err != nil {
return nil, err
}
return mtxHex, nil
}
// handleCreateRawSSGenTx handles createrawssgentx commands.
func handleCreateRawSSGenTx(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.CreateRawSSGenTxCmd)
// Only a single SStx should be given
if len(c.Inputs) != 1 {
return nil, rpcInvalidError("SSGen Tx can only have one valid" +
" input")
}
// 1. Fetch the SStx, then calculate all the values we'll need later for
// the generation of the SSGen tx outputs.
//
// Convert the provided transaction hash hex to a chainhash.Hash.
txHash, err := chainhash.NewHashFromStr(c.Inputs[0].Txid)
if err != nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: fmt.Sprintf("Inputs 0 Txid: %v", err),
}
}
// Try to fetch the ticket from the block database.
ticketUtx, err := s.chain.FetchUtxoEntry(txHash)
if ticketUtx == nil || err != nil {
return nil, rpcNoTxInfoError(txHash)
}
if t := ticketUtx.TransactionType(); t != stake.TxTypeSStx {
return nil, rpcDeserializationError("Invalid Tx type: %v", t)
}
// Store the sstx pubkeyhashes and amounts as found in the transaction
// outputs.
minimalOutputs := blockchain.ConvertUtxosToMinimalOutputs(ticketUtx)
ssgenPayTypes, ssgenPkhs, sstxAmts, _, _, _ :=
stake.SStxStakeOutputInfo(minimalOutputs)
// Get the current reward.
blockHash, curHeight := s.server.blockManager.chainState.Best()
stakeVoteSubsidy := blockchain.CalcStakeVoteSubsidy(
s.chain.FetchSubsidyCache(), curHeight, activeNetParams.Params)
// Calculate the output values from this data.
ssgenCalcAmts := stake.CalculateRewards(sstxAmts,
minimalOutputs[0].Value,
stakeVoteSubsidy)
// 2. Add all transaction inputs to a new transaction after performing
// some validity checks. First, add the stake base, then the OP_SSTX
// tagged output.
mtx := wire.NewMsgTx()
stakeBaseOutPoint := wire.NewOutPoint(&chainhash.Hash{},
uint32(0xFFFFFFFF), int8(0x01))
txInStakeBase := wire.NewTxIn(stakeBaseOutPoint, stakeVoteSubsidy, []byte{})
mtx.AddTxIn(txInStakeBase)
for _, input := range c.Inputs {
txHash, err := chainhash.NewHashFromStr(input.Txid)
if err != nil {
return nil, rpcDecodeHexError(input.Txid)
}
if input.Vout < 0 {
return nil, rpcInvalidError("Vout must be positive")
}
if !(input.Tree == wire.TxTreeStake) {
return nil, rpcInvalidError("Input tree is not " +
"TxTreeStake type")
}
prevOutV := wire.NullValueIn
if input.Amount > 0 {
amt, err := dcrutil.NewAmount(input.Amount)
if err != nil {
return nil, rpcInvalidError(err.Error())
}
prevOutV = int64(amt)
}
prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree)
txIn := wire.NewTxIn(prevOut, prevOutV, []byte{})
mtx.AddTxIn(txIn)
}
// 3. Add the OP_RETURN null data pushes of the block header hash, the
// block height, and votebits, then add all the OP_SSGEN tagged
// outputs.
//
// Block reference output.
blockRefScript, err := txscript.GenerateSSGenBlockRef(*blockHash,
uint32(curHeight))
if err != nil {
return nil, rpcInvalidError("Could not generate SSGen block "+
"reference: %v", err)
}
blockRefOut := wire.NewTxOut(0, blockRefScript)
mtx.AddTxOut(blockRefOut)
// Votebits output.
blockVBScript, err := txscript.GenerateSSGenVotes(c.VoteBits)
if err != nil {
return nil, rpcInvalidError("Could not generate SSGen votes: "+
"%v", err)
}
blockVBOut := wire.NewTxOut(0, blockVBScript)
mtx.AddTxOut(blockVBOut)
// Add all the SSGen-tagged transaction outputs to the transaction
// after performing some validity checks.
for i, ssgenPkh := range ssgenPkhs {
// Ensure amount is in the valid range for monetary amounts.
if ssgenCalcAmts[i] <= 0 ||
ssgenCalcAmts[i] > dcrutil.MaxAmount {
return nil, rpcInvalidError("Invalid SSGen amounts: "+
"0 >= %v > %v", ssgenCalcAmts[i],
dcrutil.MaxAmount)
}
// Create a new script which pays to the provided address
// specified in the original ticket tx.
var ssgenOut []byte
switch ssgenPayTypes[i] {
case false: // P2PKH
ssgenOut, err = txscript.PayToSSGenPKHDirect(ssgenPkh)
if err != nil {
return nil,
rpcInvalidError("Could not generate "+
"PKH script: %v", err)
}
case true: // P2SH
ssgenOut, err = txscript.PayToSSGenSHDirect(ssgenPkh)
if err != nil {
return nil,
rpcInvalidError("Could not generate "+
"SHD script: %v", err)
}
}
// Add the txout to our SSGen tx.
txOut := wire.NewTxOut(ssgenCalcAmts[i], ssgenOut)
mtx.AddTxOut(txOut)
}
// Check to make sure our SSGen was created correctly.
err = stake.CheckSSGen(mtx)
if err != nil {
return nil, rpcInternalError(err.Error(), "Invalid SSGen")
}
// Return the serialized and hex-encoded transaction.
mtxHex, err := messageToHex(mtx)
if err != nil {
return nil, err
}
return mtxHex, nil
}
// handleCreateRawSSRtx handles createrawssrtx commands.
func handleCreateRawSSRtx(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.CreateRawSSRtxCmd)
// Only a single SStx should be given
if len(c.Inputs) != 1 {
return nil, rpcInvalidError("SSRtx invalid number of inputs")
}
// Decode the fee as coins.
var feeAmt dcrutil.Amount
if c.Fee != nil {
var err error
feeAmt, err = dcrutil.NewAmount(*c.Fee)
if err != nil {
return nil, rpcInvalidError("Invalid fee amount: %v",
err)
}
}
// 1. Fetch the SStx, then calculate all the values we'll need later
// for the generation of the SSGen tx outputs.
//
// Convert the provided transaction hash hex to a chainhash.Hash.
txHash, err := chainhash.NewHashFromStr(c.Inputs[0].Txid)
if err != nil {
return nil, rpcDecodeHexError(c.Inputs[0].Txid)
}
// Try to fetch the ticket from the block database.
ticketUtx, err := s.chain.FetchUtxoEntry(txHash)
if ticketUtx == nil || err != nil {
return nil, rpcNoTxInfoError(txHash)
}
if t := ticketUtx.TransactionType(); t != stake.TxTypeSStx {
return nil, rpcDeserializationError("Invalid Tx type: %v", t)
}
// Store the sstx pubkeyhashes and amounts as found in the transaction
// outputs.
minimalOutputs := blockchain.ConvertUtxosToMinimalOutputs(ticketUtx)
ssrtxPayTypes, ssrtxPkhs, sstxAmts, _, _, _ :=
stake.SStxStakeOutputInfo(minimalOutputs)
// 2. Add all transaction inputs to a new transaction after performing
// some validity checks; the only input for an SSRtx is an OP_SSTX tagged
// output.
mtx := wire.NewMsgTx()
for _, input := range c.Inputs {
txHash, err := chainhash.NewHashFromStr(input.Txid)
if err != nil {
return nil, rpcDecodeHexError(input.Txid)
}
if input.Vout < 0 {
return nil, rpcInvalidError("Vout must be positive")
}
if !(input.Tree == wire.TxTreeStake) {
return nil, rpcInvalidError("Input tree is not " +
"TxTreeStake type")
}
prevOutV := wire.NullValueIn
if input.Amount > 0 {
amt, err := dcrutil.NewAmount(input.Amount)
if err != nil {
return nil, rpcInvalidError(err.Error())
}
prevOutV = int64(amt)
}
prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree)
txIn := wire.NewTxIn(prevOut, prevOutV, []byte{})
mtx.AddTxIn(txIn)
}
// 3. Add all the OP_SSRTX tagged outputs.
// Calculate the output values from this data.
ssrtxCalcAmts := stake.CalculateRewards(sstxAmts,
minimalOutputs[0].Value, 0) // No subsidy for a revocation
// Add all the SSRtx-tagged transaction outputs to the transaction after
// performing some validity checks.
feeApplied := false
for i, ssrtxPkh := range ssrtxPkhs {
// Ensure amount is in the valid range for monetary amounts.
if sstxAmts[i] <= 0 || sstxAmts[i] > dcrutil.MaxAmount {
return nil, rpcInvalidError("Invalid SSTx amount: 0 >="+
" %v > %v", sstxAmts[i] <= 0, dcrutil.MaxAmount)
}
// Create a new script which pays to the provided address specified in
// the original ticket tx.
var ssrtxOutScript []byte
switch ssrtxPayTypes[i] {
case false: // P2PKH
ssrtxOutScript, err = txscript.PayToSSRtxPKHDirect(ssrtxPkh)
if err != nil {
return nil, rpcInvalidError("Could not "+
"generate PKH script: %v", err)
}
case true: // P2SH
ssrtxOutScript, err = txscript.PayToSSRtxSHDirect(ssrtxPkh)
if err != nil {
return nil, rpcInvalidError("Could not "+
"generate SHD script: %v", err)
}
}
// Add the txout to our SSGen tx.
amt := ssrtxCalcAmts[i]
if !feeApplied && int64(feeAmt) < amt {
amt -= int64(feeAmt)
feeApplied = true
}
txOut := wire.NewTxOut(amt, ssrtxOutScript)
mtx.AddTxOut(txOut)
}
// Check to make sure our SSRtx was created correctly.
err = stake.CheckSSRtx(mtx)
if err != nil {
return nil, rpcInternalError(err.Error(), "Invalid SSRtx")
}
// Return the serialized and hex-encoded transaction.
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.(*dcrjson.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, rpcInvalidError("Invalid debug level %v: %v",
c.LevelSpec, err)
}
return "Done.", nil
}
// createVinList returns a slice of JSON objects for the inputs of the passed
// transaction.
func createVinList(mtx *wire.MsgTx) []dcrjson.Vin {
// Coinbase transactions only have a single txin by definition.
vinList := make([]dcrjson.Vin, len(mtx.TxIn))
if blockchain.IsCoinBaseTx(mtx) {
txIn := mtx.TxIn[0]
vinEntry := &vinList[0]
vinEntry.Coinbase = hex.EncodeToString(txIn.SignatureScript)
vinEntry.Sequence = txIn.Sequence
vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin()
vinEntry.BlockHeight = txIn.BlockHeight
vinEntry.BlockIndex = txIn.BlockIndex
return vinList
}
// Stakebase transactions (votes) have two inputs: a null stake base
// followed by an input consuming a ticket's stakesubmission.
isSSGen := stake.IsSSGen(mtx)
for i, txIn := range mtx.TxIn {
// Handle only the null input of a stakebase differently.
if isSSGen && i == 0 {
vinEntry := &vinList[0]
vinEntry.Stakebase = hex.EncodeToString(txIn.SignatureScript)
vinEntry.Sequence = txIn.Sequence
vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin()
vinEntry.BlockHeight = txIn.BlockHeight
vinEntry.BlockIndex = txIn.BlockIndex
continue
}
// The disassembled string will contain [error] inline
// if the script doesn't fully parse, so ignore the
// error here.
disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
vinEntry := &vinList[i]
vinEntry.Txid = txIn.PreviousOutPoint.Hash.String()
vinEntry.Vout = txIn.PreviousOutPoint.Index
vinEntry.Tree = txIn.PreviousOutPoint.Tree
vinEntry.Sequence = txIn.Sequence
vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin()
vinEntry.BlockHeight = txIn.BlockHeight
vinEntry.BlockIndex = txIn.BlockIndex
vinEntry.ScriptSig = &dcrjson.ScriptSig{
Asm: disbuf,
Hex: hex.EncodeToString(txIn.SignatureScript),
}
}
return vinList
}
// createVoutList returns a slice of JSON objects for the outputs of the passed
// transaction.
func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap map[string]struct{}) []dcrjson.Vout {
txType := stake.DetermineTxType(mtx)
voutList := make([]dcrjson.Vout, 0, len(mtx.TxOut))
for i, v := range mtx.TxOut {
// The disassembled string will contain [error] inline if the
// script doesn't fully parse, so ignore the error here.
disbuf, _ := txscript.DisasmString(v.PkScript)
// Attempt to extract addresses from the public key script. In
// the case of stake submission transactions, the odd outputs
// contain a commitment address, so detect that case
// accordingly.
var addrs []dcrutil.Address
var scriptClass string
var reqSigs int
var commitAmt *dcrutil.Amount
if txType == stake.TxTypeSStx && (i%2 != 0) {
scriptClass = sstxCommitmentString
addr, err := stake.AddrFromSStxPkScrCommitment(v.PkScript,
chainParams)
if err != nil {
rpcsLog.Warnf("failed to decode ticket "+
"commitment addr output for tx hash "+
"%v, output idx %v", mtx.TxHash(), i)
} else {
addrs = []dcrutil.Address{addr}
}
amt, err := stake.AmountFromSStxPkScrCommitment(v.PkScript)
if err != nil {
rpcsLog.Warnf("failed to decode ticket "+
"commitment amt output for tx hash %v"+
", output idx %v", mtx.TxHash(), i)
} else {
commitAmt = &amt
}
} else {
// Ignore the error here since an error means the script
// couldn't parse and there is no additional information
// about it anyways.
var sc txscript.ScriptClass
sc, addrs, reqSigs, _ = txscript.ExtractPkScriptAddrs(
v.Version, v.PkScript, chainParams)
scriptClass = sc.String()
}
// Encode the addresses while checking if the address passes the
// filter when needed.
passesFilter := len(filterAddrMap) == 0
encodedAddrs := make([]string, len(addrs))
for j, addr := range addrs {
encodedAddr := addr.EncodeAddress()
encodedAddrs[j] = encodedAddr
// No need to check the map again if the filter already
// passes.
if passesFilter {
continue
}
if _, exists := filterAddrMap[encodedAddr]; exists {
passesFilter = true
}
}
if !passesFilter {
continue
}
var vout dcrjson.Vout
voutSPK := &vout.ScriptPubKey
vout.N = uint32(i)
vout.Value = dcrutil.Amount(v.Value).ToCoin()
vout.Version = v.Version
voutSPK.Addresses = encodedAddrs
voutSPK.Asm = disbuf
voutSPK.Hex = hex.EncodeToString(v.PkScript)
voutSPK.Type = scriptClass
voutSPK.ReqSigs = int32(reqSigs)
if commitAmt != nil {
voutSPK.CommitAmt = dcrjson.Float64(commitAmt.ToCoin())
}
voutList = append(voutList, vout)
}
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, blkIdx uint32, blkHeader *wire.BlockHeader, blkHash string, blkHeight int64, confirmations int64) (*dcrjson.TxRawResult, error) {
mtxHex, err := messageToHex(mtx)
if err != nil {
return nil, err
}
if txHash != mtx.TxHash().String() {
return nil, rpcInvalidError("Tx hash does not match: got %v "+
"expected %v", txHash, mtx.TxHash())
}
txReply := &dcrjson.TxRawResult{
Hex: mtxHex,
Txid: txHash,
Vin: createVinList(mtx),
Vout: createVoutList(mtx, chainParams, nil),
Version: int32(mtx.Version),
LockTime: mtx.LockTime,
Expiry: mtx.Expiry,
BlockHeight: blkHeight,
BlockIndex: blkIdx,
}
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 = confirmations
}
return txReply, nil
}
// handleDecodeRawTransaction handles decoderawtransaction commands.
func handleDecodeRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.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, rpcDeserializationError("Could not decode Tx: %v",
err)
}
// Create and return the result.
txReply := dcrjson.TxRawDecodeResult{
Txid: mtx.TxHash().String(),
Version: int32(mtx.Version),
Locktime: mtx.LockTime,
Expiry: mtx.Expiry,
Vin: createVinList(&mtx),
Vout: createVoutList(&mtx, s.server.chainParams, nil),
}
return txReply, nil
}
// handleDecodeScript handles decodescript commands.
func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.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.
// TODO Replace magic version with argument passed to RPC call
scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(
txscript.DefaultScriptVersion, 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 := dcrutil.NewAddressScriptHash(script, s.server.chainParams)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Failed to convert script to pay-to-script-hash")
}
// Generate and return the reply.
reply := dcrjson.DecodeScriptResult{
Asm: disbuf,
ReqSigs: int32(reqSigs),
Type: scriptClass.String(),
Addresses: addresses,
}
if scriptClass != txscript.ScriptHashTy {
reply.P2sh = p2sh.EncodeAddress()
}
return reply, nil
}
// handleEstimateFee implenents the estimatefee command.
// TODO this is a very basic implementation. It should be
// modified to match the bitcoin-core one.
func handleEstimateFee(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
return cfg.minRelayTxFee.ToCoin(), nil
}
// handleEstimateStakeDiff implements the estimatestakediff command.
func handleEstimateStakeDiff(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.EstimateStakeDiffCmd)
// Minimum possible stake difficulty.
chain := s.server.blockManager.chain
min, err := chain.EstimateNextStakeDifficulty(0, false)
if err != nil {
return nil, rpcInternalError(err.Error(), "Could not "+
"estimate next minimum stake difficulty")
}
// Maximum possible stake difficulty.
max, err := chain.EstimateNextStakeDifficulty(0, true)
if err != nil {
return nil, rpcInternalError(err.Error(), "Could not "+
"estimate next maximum stake difficulty")
}
// The expected stake difficulty. Average the number of fresh stake
// since the last retarget to get the number of tickets per block,
// then use that to estimate the next stake difficulty.
_, bestHeight := s.server.blockManager.chainState.Best()
lastAdjustment := (bestHeight / activeNetParams.StakeDiffWindowSize) *
activeNetParams.StakeDiffWindowSize
nextAdjustment := ((bestHeight / activeNetParams.StakeDiffWindowSize) +
1) * activeNetParams.StakeDiffWindowSize
totalTickets := 0
err = s.server.db.View(func(dbTx database.Tx) error {
for i := lastAdjustment; i <= bestHeight; i++ {
bh, err := blockchain.DBFetchHeaderByHeight(dbTx, i)
if err != nil {
return err
}
totalTickets += int(bh.FreshStake)
}
return nil
})
blocksSince := float64(bestHeight - lastAdjustment + 1)
remaining := float64(nextAdjustment - bestHeight - 1)
averagePerBlock := float64(totalTickets) / blocksSince
expectedTickets := int64(math.Floor(averagePerBlock * remaining))
expected, err := chain.EstimateNextStakeDifficulty(expectedTickets,
false)
if err != nil {
return nil, rpcInternalError(err.Error(), "Could not "+
"estimate next stake difficulty")
}
// User-specified stake difficulty, if they asked for one.
var userEstFltPtr *float64
if c.Tickets != nil {
userEst, err := chain.EstimateNextStakeDifficulty(int64(*c.Tickets),
false)
if err != nil {
return nil, rpcInternalError(err.Error(), "Could not "+
"estimate next user specified stake difficulty")
}
userEstFlt := dcrutil.Amount(userEst).ToCoin()
userEstFltPtr = &userEstFlt
}
return &dcrjson.EstimateStakeDiffResult{
Min: dcrutil.Amount(min).ToCoin(),
Max: dcrutil.Amount(max).ToCoin(),
Expected: dcrutil.Amount(expected).ToCoin(),
User: userEstFltPtr,
}, nil
}
// handleExistsAddress implements the existsaddress command.
func handleExistsAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
existsAddrIndex := s.server.existsAddrIndex
if existsAddrIndex == nil {
return nil, rpcInternalError("Exists address index disabled",
"Configuration")
}
c := cmd.(*dcrjson.ExistsAddressCmd)
// Attempt to decode the supplied address.
addr, err := dcrutil.DecodeAddress(c.Address)
if err != nil {
return nil, rpcAddressKeyError("Could not decode address: %v",
err)
}
exists, err := existsAddrIndex.ExistsAddress(addr)
if err != nil {
return nil, rpcInvalidError("Could not query address: %v", err)
}
return exists, nil
}
// handleExistsAddresses implements the existsaddresses command.
func handleExistsAddresses(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
existsAddrIndex := s.server.existsAddrIndex
if existsAddrIndex == nil {
return nil, rpcInternalError("Exists address index disabled",
"Configuration")
}
c := cmd.(*dcrjson.ExistsAddressesCmd)
addresses := make([]dcrutil.Address, len(c.Addresses))
for i := range c.Addresses {
// Attempt to decode the supplied address.
addr, err := dcrutil.DecodeAddress(c.Addresses[i])
if err != nil {
return nil, rpcAddressKeyError("Could not decode "+
"address: %v", err)
}
addresses[i] = addr
}
exists, err := existsAddrIndex.ExistsAddresses(addresses)
if err != nil {
return nil, rpcInvalidError("Could not query address: %v", err)
}
// Convert the slice of bools into a compacted set of bit flags.
set := bitset.NewBytes(len(c.Addresses))
for i := range exists {
if exists[i] {
set.Set(i)
}
}
return hex.EncodeToString([]byte(set)), nil
}
// handleExistsMissedTickets implements the existsmissedtickets command.
func handleExistsMissedTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.ExistsMissedTicketsCmd)
hashes, err := dcrjson.DecodeConcatenatedHashes(c.TxHashBlob)
if err != nil {
return nil, err
}
exists := s.server.blockManager.chain.CheckMissedTickets(hashes)
if len(exists) != len(hashes) {
return nil, rpcInvalidError("Invalid missed ticket count "+
"got %v, want %v", len(exists), len(hashes))
}
// Convert the slice of bools into a compacted set of bit flags.
set := bitset.NewBytes(len(hashes))
for i := range exists {
if exists[i] {
set.Set(i)
}
}
return hex.EncodeToString([]byte(set)), nil
}
// handleExistsExpiredTickets implements the existsexpiredtickets command.
func handleExistsExpiredTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.ExistsExpiredTicketsCmd)
hashes, err := dcrjson.DecodeConcatenatedHashes(c.TxHashBlob)
if err != nil {
return nil, err
}
exists := s.server.blockManager.chain.CheckExpiredTickets(hashes)
if len(exists) != len(hashes) {
return nil, rpcInvalidError("Invalid expired ticket count "+
"got %v, want %v", len(exists), len(hashes))
}
// Convert the slice of bools into a compacted set of bit flags.
set := bitset.NewBytes(len(hashes))
for i := range exists {
if exists[i] {
set.Set(i)
}
}
return hex.EncodeToString([]byte(set)), nil
}
// handleExistsLiveTicket implements the existsliveticket command.
func handleExistsLiveTicket(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.ExistsLiveTicketCmd)
hash, err := chainhash.NewHashFromStr(c.TxHash)
if err != nil {
return nil, rpcDecodeHexError(c.TxHash)
}
return s.server.blockManager.chain.CheckLiveTicket(*hash), nil
}
// handleExistsLiveTickets implements the existslivetickets command.
func handleExistsLiveTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.ExistsLiveTicketsCmd)
hashes, err := dcrjson.DecodeConcatenatedHashes(c.TxHashBlob)
if err != nil {
return nil, err
}
exists := s.server.blockManager.chain.CheckLiveTickets(hashes)
if len(exists) != len(hashes) {
return nil, rpcInvalidError("Invalid live ticket count got "+
"%v, want %v", len(exists), len(hashes))
}
// Convert the slice of bools into a compacted set of bit flags.
set := bitset.NewBytes(len(hashes))
for i := range exists {
if exists[i] {
set.Set(i)
}
}
return hex.EncodeToString([]byte(set)), nil
}
// handleExistsMempoolTxs implements the existsmempooltxs command.
func handleExistsMempoolTxs(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.ExistsMempoolTxsCmd)
txHashBlob, err := hex.DecodeString(c.TxHashBlob)
if err != nil {
return nil, rpcDecodeHexError(c.TxHashBlob)
}
// It needs to be an exact number of hashes.
if len(txHashBlob)%32 != 0 {
return nil, rpcInvalidError("Invalid hash blob length")
}
hashesLen := len(txHashBlob) / 32
hashes := make([]*chainhash.Hash, hashesLen)
for i := 0; i < hashesLen; i++ {
hashes[i], err = chainhash.NewHash(
txHashBlob[i*chainhash.HashSize : (i+1)*chainhash.HashSize])
if err != nil {
return nil, rpcInternalError(err.Error(), "New hash")
}
}
exists := s.server.txMemPool.HaveTransactions(hashes)
if len(exists) != hashesLen {
return nil, rpcInternalError(fmt.Sprintf("got %v, want %v",
len(exists), hashesLen),
"Invalid mempool Tx ticket count")
}
// Convert the slice of bools into a compacted set of bit flags.
set := bitset.NewBytes(hashesLen)
for i := range exists {
if exists[i] {
set.Set(i)
}
}
return hex.EncodeToString([]byte(set)), 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, rpcInternalError("No payment addresses specified "+
"via --miningaddr", "Configuration")
}
c := cmd.(*dcrjson.GenerateCmd)
// Respond with an error if the client is requesting 0 blocks to be generated.
if c.NumBlocks == 0 {
return nil, rpcInternalError("Invalid number of blocks",
"Configuration")
}
// Create a reply
reply := make([]string, c.NumBlocks)
blockHashes, err := s.server.cpuMiner.GenerateNBlocks(c.NumBlocks)
if err != nil {
return nil, rpcInternalError("Could not generate blocks",
"Configuration")
}
// 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.(*dcrjson.GetAddedNodeInfoCmd)
// Retrieve a list of persistent (added) peers from the Decred server
// and filter the list of peers per the specified address (if any).
peers := s.server.AddedNodeInfo()
if c.Node != nil {
found := false
for i, peer := range peers {
if peer.Addr() == *c.Node {
peers = peers[i : i+1]
found = true
}
}
if !found {
return nil, rpcInternalError("Node not found", "")
}
}
// 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([]*dcrjson.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 dcrjson.GetAddedNodeInfoResult
result.AddedNode = peer.Addr()
result.Connected = dcrjson.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 := dcrdLookup(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([]dcrjson.GetAddedNodeInfoResultAddr, 0,
len(ipList))
for _, ip := range ipList {
var addr dcrjson.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.
best := s.chain.BestSnapshot()
result := &dcrjson.GetBestBlockResult{
Hash: best.Hash.String(),
Height: best.Height,
}
return result, nil
}
// handleGetBestBlockHash implements the getbestblockhash command.
func handleGetBestBlockHash(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
best := s.chain.BestSnapshot()
return best.Hash.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 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.(*dcrjson.GetBlockCmd)
// Load the raw block bytes from the database.
hash, err := chainhash.NewHashFromStr(c.Hash)
if err != nil {
return nil, rpcDecodeHexError(c.Hash)
}
blk, err := s.server.blockManager.chain.FetchBlockByHash(hash)
if err != nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: fmt.Sprintf("Block not found: %v", hash),
}
}
// When the verbose flag isn't set, simply return the
// network-serialized block as a hex-encoded string.
if c.Verbose != nil && !*c.Verbose {
blkBytes, err := blk.Bytes()
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not serialize block")
}
return hex.EncodeToString(blkBytes), nil
}
best := s.chain.BestSnapshot()
// See if this block is an orphan and adjust Confirmations accordingly.
onMainChain, _ := s.chain.MainChainHasBlock(hash)
// Get next block hash unless there are none.
var nextHashString string
blockHeader := &blk.MsgBlock().Header
confirmations := int64(-1)
if onMainChain {
if int64(blockHeader.Height) < best.Height {
nextHash, err := s.chain.BlockHashByHeight(int64(blockHeader.Height + 1))
if err != nil {
context := "No next block"
return nil, rpcInternalError(err.Error(),
context)
}
nextHashString = nextHash.String()
}
confirmations = 1 + best.Height - int64(blockHeader.Height)
}
sbitsFloat := float64(blockHeader.SBits) / dcrutil.AtomsPerCoin
blockReply := dcrjson.GetBlockVerboseResult{
Hash: c.Hash,
Version: blockHeader.Version,
MerkleRoot: blockHeader.MerkleRoot.String(),
StakeRoot: blockHeader.StakeRoot.String(),
PreviousHash: blockHeader.PrevBlock.String(),
Nonce: blockHeader.Nonce,
VoteBits: blockHeader.VoteBits,
FinalState: hex.EncodeToString(blockHeader.FinalState[:]),
Voters: blockHeader.Voters,
FreshStake: blockHeader.FreshStake,
Revocations: blockHeader.Revocations,
PoolSize: blockHeader.PoolSize,
Time: blockHeader.Timestamp.Unix(),
StakeVersion: blockHeader.StakeVersion,
Confirmations: confirmations,
Height: int64(blockHeader.Height),
Size: int32(blk.MsgBlock().Header.Size),
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
SBits: sbitsFloat,
Difficulty: getDifficultyRatio(blockHeader.Bits),
ExtraData: hex.EncodeToString(blockHeader.ExtraData[:]),
NextHash: nextHashString,
}
if c.VerboseTx == nil || !*c.VerboseTx {
transactions := blk.Transactions()
txNames := make([]string, len(transactions))
for i, tx := range transactions {
txNames[i] = tx.Hash().String()
}
blockReply.Tx = txNames
stransactions := blk.STransactions()
stxNames := make([]string, len(stransactions))
for i, tx := range stransactions {
stxNames[i] = tx.Hash().String()
}
blockReply.STx = stxNames
} else {
txns := blk.Transactions()
rawTxns := make([]dcrjson.TxRawResult, len(txns))
for i, tx := range txns {
rawTxn, err := createTxRawResult(s.server.chainParams,
tx.MsgTx(), tx.Hash().String(), uint32(i),
blockHeader, blk.Hash().String(),
int64(blockHeader.Height), confirmations)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not create transaction")
}
rawTxns[i] = *rawTxn
}
blockReply.RawTx = rawTxns
stxns := blk.STransactions()
rawSTxns := make([]dcrjson.TxRawResult, len(stxns))
for i, tx := range stxns {
rawSTxn, err := createTxRawResult(s.server.chainParams,
tx.MsgTx(), tx.Hash().String(), uint32(i),
blockHeader, blk.Hash().String(),
int64(blockHeader.Height), confirmations)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not create stake transaction")
}
rawSTxns[i] = *rawSTxn
}
blockReply.RawSTx = rawSTxns
}
return blockReply, nil
}
// handleGetBlockCount implements the getblockcount command.
func handleGetBlockCount(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
best := s.chain.BestSnapshot()
return best.Height, nil
}
// handleGetBlockHash implements the getblockhash command.
func handleGetBlockHash(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetBlockHashCmd)
hash, err := s.chain.BlockHashByHeight(c.Index)
if err != nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCOutOfRange,
Message: fmt.Sprintf("Block number out of range: %v",
c.Index),
}
}
return hash.String(), nil
}
// handleGetBlockHeader implements the getblockheader command.
func handleGetBlockHeader(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetBlockHeaderCmd)
// Fetch the header from chain.
hash, err := chainhash.NewHashFromStr(c.Hash)
if err != nil {
return nil, rpcDecodeHexError(c.Hash)
}
blockHeader, err := s.chain.HeaderByHash(hash)
if err != nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: fmt.Sprintf("Block not found: %v", c.Hash),
}
}
// When the verbose flag isn't set, simply return the serialized block
// header as a hex-encoded string.
if c.Verbose != nil && !*c.Verbose {
var headerBuf bytes.Buffer
err := blockHeader.Serialize(&headerBuf)
if err != nil {
context := "Failed to serialize block header"
return nil, rpcInternalError(err.Error(), context)
}
return hex.EncodeToString(headerBuf.Bytes()), nil
}
// The verbose flag is set, so generate the JSON object and return it.
best := s.chain.BestSnapshot()
// See if this block is an orphan and adjust Confirmations accordingly.
onMainChain, _ := s.chain.MainChainHasBlock(hash)
// Get next block hash unless there are none.
var nextHashString string
confirmations := int64(-1)
height := int64(blockHeader.Height)
if onMainChain {
if height < best.Height {
nextHash, err := s.chain.BlockHashByHeight(height + 1)
if err != nil {
context := "No next block"
return nil, rpcInternalError(err.Error(),
context)
}
nextHashString = nextHash.String()
}
confirmations = 1 + best.Height - height
}
blockHeaderReply := dcrjson.GetBlockHeaderVerboseResult{
Hash: c.Hash,
Confirmations: confirmations,
Version: blockHeader.Version,
PreviousHash: blockHeader.PrevBlock.String(),
MerkleRoot: blockHeader.MerkleRoot.String(),
StakeRoot: blockHeader.StakeRoot.String(),
VoteBits: blockHeader.VoteBits,
FinalState: hex.EncodeToString(blockHeader.FinalState[:]),
Voters: blockHeader.Voters,
FreshStake: blockHeader.FreshStake,
Revocations: blockHeader.Revocations,
PoolSize: blockHeader.PoolSize,
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
SBits: dcrutil.Amount(blockHeader.SBits).ToCoin(),
Height: uint32(height),
Size: blockHeader.Size,
Time: blockHeader.Timestamp.Unix(),
Nonce: blockHeader.Nonce,
StakeVersion: blockHeader.StakeVersion,
Difficulty: getDifficultyRatio(blockHeader.Bits),
NextHash: nextHashString,
}
return blockHeaderReply, nil
}
// handleGetBlockSubsidy implements the getblocksubsidy command.
func handleGetBlockSubsidy(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetBlockSubsidyCmd)
height := c.Height
voters := c.Voters
cache := s.chain.FetchSubsidyCache()
if cache == nil {
return nil, rpcInternalError("empty subsidy cache", "")
}
dev := blockchain.CalcBlockTaxSubsidy(cache, height, voters,
s.server.chainParams)
pos := blockchain.CalcStakeVoteSubsidy(cache, height,
s.server.chainParams) * int64(voters)
pow := blockchain.CalcBlockWorkSubsidy(cache, height, voters,
s.server.chainParams)
total := dev + pos + pow
rep := dcrjson.GetBlockSubsidyResult{
Developer: dev,
PoS: pos,
PoW: pow,
Total: total,
}
return rep, nil
}
// encodeTemplateID encodes the passed details into an ID that can be used to
// uniquely identify a block template.
func encodeTemplateID(prevHash *chainhash.Hash, 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) (*chainhash.Hash, int64, error) {
fields := strings.Split(templateID, "-")
if len(fields) != 2 {
return nil, 0, ErrInvalidLongPoll
}
prevHash, err := chainhash.NewHashFromStr(fields[0])
if err != nil {
return nil, 0, ErrInvalidLongPoll
}
lastGenerated, err := strconv.ParseInt(fields[1], 10, 64)
if err != nil {
return nil, 0, ErrInvalidLongPoll
}
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 *chainhash.Hash, 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(blockHash *chainhash.Hash) {
go func() {
state.Lock()
defer state.Unlock()
state.notifyLongPollers(blockHash, 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 *chainhash.Hash, 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 long enough 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 false 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 dcrutil.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.policy, s.server, payAddr)
if err != nil {
return rpcInternalError("Failed to create new block "+
"template: "+err.Error(), "")
}
if blkTemplate == nil {
return rpcInternalError("Failed to create new block "+
"template: not enough voters on parent and no "+
"suitable cached template", "")
}
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 rpcInternalError(err.Error(), context)
}
// Update work state to ensure another block template isn't
// generated until needed.
state.template = deepCopyBlockTemplate(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 rpcInternalError(err.Error(), context)
}
template.Block.Transactions[0].TxOut[0].PkScript = pkScript
template.ValidPayAddress = true
// Update the merkle root.
block := dcrutil.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.
err := UpdateBlockTime(msgBlock, s.server.blockManager)
if err != nil {
context := "Failed to update timestamp"
return rpcInternalError(err.Error(), context)
}
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 dcrjson.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(bm *blockManager, useCoinbaseValue bool, submitOld *bool) (*dcrjson.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 := deepCopyBlockTemplate(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, &dcrjson.RPCError{
Code: dcrjson.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([]dcrjson.GetBlockTemplateResultTx, 0, numTx-1)
txIndex := make(map[chainhash.Hash]int64, numTx)
for i, tx := range msgBlock.Transactions {
txHash := tx.TxHashFull()
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 multiple 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, rpcInternalError(err.Error(), context)
}
var txTypeStr string
txType := stake.DetermineTxType(tx)
switch txType {
case stake.TxTypeRegular:
txTypeStr = "regular"
case stake.TxTypeSStx:
txTypeStr = "error"
case stake.TxTypeSSGen:
txTypeStr = "error"
case stake.TxTypeSSRtx:
txTypeStr = "error"
}
fee := template.Fees[i]
sigOps := template.SigOpCounts[i]
resultTx := dcrjson.GetBlockTemplateResultTx{
Data: hex.EncodeToString(txBuf.Bytes()),
Hash: txHash.String(),
Depends: depends,
Fee: fee,
SigOps: sigOps,
TxType: txTypeStr,
}
transactions = append(transactions, resultTx)
}
// Convert each stake transaction in the block template to a template
// result transaction.
numSTx := len(msgBlock.STransactions)
stransactions := make([]dcrjson.GetBlockTemplateResultTx, 0, numSTx)
stxIndex := make(map[chainhash.Hash]int64, numSTx)
for i, stx := range msgBlock.STransactions {
stxHash := stx.TxHashFull()
stxIndex[stxHash] = int64(i)
// 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 stx.TxIn {
if idx, ok := stxIndex[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, stx.SerializeSize()))
if err := stx.Serialize(txBuf); err != nil {
return nil, err
}
var txTypeStr string
txType := stake.DetermineTxType(stx)
switch txType {
case stake.TxTypeRegular:
txTypeStr = "error"
case stake.TxTypeSStx:
txTypeStr = "ticket"
case stake.TxTypeSSGen:
txTypeStr = "vote"
case stake.TxTypeSSRtx:
txTypeStr = "revocation"
}
fee := template.Fees[i+len(msgBlock.Transactions)]
sigOps := template.SigOpCounts[i+len(msgBlock.Transactions)]
resultTx := dcrjson.GetBlockTemplateResultTx{
Data: hex.EncodeToString(txBuf.Bytes()),
Hash: stxHash.String(),
Depends: depends,
Fee: fee,
SigOps: sigOps,
TxType: txTypeStr,
}
stransactions = append(stransactions, resultTx)
}
headerBytes, err := header.Bytes()
if err != nil {
context := "Could not obtain header"
return nil, rpcInternalError(err.Error(), context)
}
// Choose the correct maximum block size as defined by the network
// parameters and the current status of any hard fork votes to change
// it when serialized.
maxBlockSize, err := bm.chain.MaxBlockSize()
if err != nil {
context := "Invalid blocksize"
return nil, rpcInternalError(err.Error(), context)
}
// 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 := dcrjson.GetBlockTemplateResult{
Header: hex.EncodeToString(headerBytes),
SigOpLimit: blockchain.MaxSigOpsPerBlock,
SizeLimit: maxBlockSize,
Transactions: transactions,
STransactions: stransactions,
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 {
context := "Invalid blocksize"
errStr := fmt.Sprintf("A coinbase transaction has " +
"been requested, but the server has not " +
"been configured with any payment " +
"addresses via --miningaddr")
return nil, rpcInternalError(errStr, context)
}
// 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 := "Could not serialize"
return nil, rpcInternalError(err.Error(), context)
}
resultTx := dcrjson.GetBlockTemplateResultTx{
Data: hex.EncodeToString(txBuf.Bytes()),
Hash: tx.TxHash().String(),
Depends: []int64{},
Fee: template.Fees[0],
SigOps: template.SigOpCounts[0],
}
reply.CoinbaseTxn = &resultTx
}
return &reply, nil
}
// handleGetBlockTemplateLongPoll is 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 long poll ID provided by
// the caller is invalid.
prevHash, lastGenerated, err := decodeTemplateID(longPollID)
if err != nil {
result, err := state.blockTemplateResult(s.server.blockManager,
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(s.server.blockManager,
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 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(s.server.blockManager,
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 *dcrjson.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, rpcInternalError("A coinbase transaction has "+
"been requested, but the server has not been "+
"configured with any payment addresses via "+
"--miningaddr", "Configuration")
}
// 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.SimNet && s.server.ConnectedCount() == 0 {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCClientNotConnected,
Message: "Decred 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, &dcrjson.RPCError{
Code: dcrjson.ErrRPCClientInInitialDownload,
Message: "Decred 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(s.server.blockManager, useCoinbaseValue, nil)
}
// chainErrToGBTErrString converts an error returned from chain to a string
// which matches the reasons and format described in BIP0022 for rejection
// reasons.
// TODO Decred pop in the new errors from blockchain cj
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.ErrMissingTxOut:
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.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.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 *dcrjson.TemplateRequest) (interface{}, error) {
hexData := request.Data
if hexData == "" {
return false, rpcInvalidError("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, rpcDecodeHexError(hexData)
}
var msgBlock wire.MsgBlock
if err := msgBlock.Deserialize(bytes.NewReader(dataBytes)); err != nil {
return nil, rpcDeserializationError("Could not decode block: "+
"%v", err)
}
block := dcrutil.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
}
err = s.server.blockManager.chain.CheckConnectBlockTemplate(block)
if err != nil {
if _, ok := err.(blockchain.RuleError); !ok {
errStr := fmt.Sprintf("Failed to process block "+
"proposal: %v", err)
rpcsLog.Error(errStr)
return nil, rpcInternalError(err.Error(),
"Could not process block")
}
rpcsLog.Infof("Rejected block proposal: %v", err)
return chainErrToGBTErrString(err), 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) {
if s.server.cpuMiner.IsMining() {
return nil, rpcMiscError("Block template production is " +
"disallowed while CPU mining is enabled. " +
"Please disable CPU mining and try again.")
}
// Respond with an error if there are no addresses to pay the created
// blocks to.
if len(cfg.miningAddrs) == 0 {
return nil, rpcInternalError("No payment addresses specified "+
"via --miningaddr", "Configuration")
}
c := cmd.(*dcrjson.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, rpcInvalidError("Invalid mode: %v", mode)
}
// handleGetChainTips implements the getchaintips command.
func handleGetChainTips(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
return s.chain.ChainTips(), nil
}
// handleGetCoinSupply implements the getcoinsupply command.
func handleGetCoinSupply(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
return s.chain.TotalSubsidy(), nil
}
// 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) {
best := s.chain.BestSnapshot()
return getDifficultyRatio(best.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
}
// handleGetCFilter implements the getcfilter command.
func handleGetCFilter(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
if s.server.cfIndex == nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCNoCFIndex,
Message: "Compact filters must be enabled for this command",
}
}
c := cmd.(*dcrjson.GetCFilterCmd)
hash, err := chainhash.NewHashFromStr(c.Hash)
if err != nil {
return nil, rpcDecodeHexError(c.Hash)
}
var filterType wire.FilterType
switch c.FilterType {
case "regular":
filterType = wire.GCSFilterRegular
case "extended":
filterType = wire.GCSFilterExtended
default:
return nil, rpcMiscError("unknown filter type " + c.FilterType)
}
filterBytes, err := s.server.cfIndex.FilterByBlockHash(hash, filterType)
if err != nil {
rpcsLog.Debugf("Could not find committed filter for %v: %v",
hash, err)
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: "Block not found",
}
}
rpcsLog.Debugf("Found committed filter for %v", hash)
return hex.EncodeToString(filterBytes), nil
}
// handleGetCFilterHeader implements the getcfilterheader command.
func handleGetCFilterHeader(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
if s.server.cfIndex == nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCNoCFIndex,
Message: "The CF index must be enabled for this command",
}
}
c := cmd.(*dcrjson.GetCFilterHeaderCmd)
hash, err := chainhash.NewHashFromStr(c.Hash)
if err != nil {
return nil, rpcDecodeHexError(c.Hash)
}
var filterType wire.FilterType
switch c.FilterType {
case "regular":
filterType = wire.GCSFilterRegular
case "extended":
filterType = wire.GCSFilterExtended
default:
return nil, rpcMiscError("unknown filter type " + c.FilterType)
}
headerBytes, err := s.server.cfIndex.FilterHeaderByBlockHash(hash, filterType)
if len(headerBytes) > 0 {
rpcsLog.Debugf("Found header of committed filter for %v", hash)
} else {
rpcsLog.Debugf("Could not find header of committed filter for %v: %v",
hash, err)
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: "Block not found",
}
}
hash.SetBytes(headerBytes)
return hash.String(), nil
}
// handleGetHeaders implements the getheaders command.
func handleGetHeaders(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetHeadersCmd)
blockLocators, err := dcrjson.DecodeConcatenatedHashes(c.BlockLocators)
if err != nil {
// Already a *dcrjson.RPCError
return nil, err
}
var hashStop chainhash.Hash
if c.HashStop != "" {
err := chainhash.Decode(&hashStop, c.HashStop)
if err != nil {
return nil, rpcInvalidError("Failed to decode "+
"hashstop: %v", err)
}
}
// Until wire.MsgGetHeaders uses []Hash instead of the []*Hash, this
// conversion is necessary. The wire protocol getheaders is (probably)
// called much more often than this RPC, so chain.LocateHeaders is
// optimized for that and this is given the performance penality.
locators := make(blockchain.BlockLocator, len(blockLocators))
for i := range blockLocators {
locators[i] = &blockLocators[i]
}
chain := s.server.blockManager.chain
headers := chain.LocateHeaders(locators, &hashStop)
// Return the serialized block headers as hex-encoded strings.
hexBlockHeaders := make([]string, len(headers))
var buf bytes.Buffer
buf.Grow(wire.MaxBlockHeaderPayload)
for i, h := range headers {
err := h.Serialize(&buf)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Failed to serialize block header")
}
hexBlockHeaders[i] = hex.EncodeToString(buf.Bytes())
buf.Reset()
}
return &dcrjson.GetHeadersResult{Headers: hexBlockHeaders}, 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) {
best := s.chain.BestSnapshot()
ret := &dcrjson.InfoChainResult{
Version: int32(1000000*appMajor + 10000*appMinor +
100*appPatch),
ProtocolVersion: int32(maxProtocolVersion),
Blocks: best.Height,
TimeOffset: int64(s.server.timeSource.Offset().Seconds()),
Connections: s.server.ConnectedCount(),
Proxy: cfg.Proxy,
Difficulty: getDifficultyRatio(best.Bits),
TestNet: cfg.TestNet,
RelayFee: cfg.minRelayTxFee.ToCoin(),
}
return ret, nil
}
// handleGetMempoolInfo implements the getmempoolinfo command.
func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
mempoolTxns := s.server.txMemPool.TxDescs()
var numBytes int64
for _, txD := range mempoolTxns {
numBytes += int64(txD.Tx.MsgTx().SerializeSize())
}
ret := &dcrjson.GetMempoolInfoResult{
Size: int64(len(mempoolTxns)),
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) {
// Create a default getnetworkhashps command to use defaults and make
// use of the existing getnetworkhashps handler.
gnhpsCmd := dcrjson.NewGetNetworkHashPSCmd(nil, nil)
networkHashesPerSecIface, err := handleGetNetworkHashPS(s, gnhpsCmd,
closeChan)
if err != nil {
return nil, err
}
networkHashesPerSec, ok := networkHashesPerSecIface.(int64)
if !ok {
return nil, rpcInternalError(err.Error(),
fmt.Sprintf("Invalid type: %q",
networkHashesPerSecIface))
}
best := s.chain.BestSnapshot()
nextStakeDiff, err := s.chain.CalcNextRequiredStakeDifficulty()
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not calculate next stake difficulty")
}
result := dcrjson.GetMiningInfoResult{
Blocks: best.Height,
CurrentBlockSize: best.BlockSize,
CurrentBlockTx: best.NumTxns,
Difficulty: getDifficultyRatio(best.Bits),
StakeDifficulty: nextStakeDiff,
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.TestNet,
}
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 := &dcrjson.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.(*dcrjson.GetNetworkHashPSCmd)
// 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.
best := s.chain.BestSnapshot()
endHeight := int64(-1)
if c.Height != nil {
endHeight = int64(*c.Height)
}
if endHeight > best.Height || endHeight == 0 {
return int64(0), nil
}
if endHeight < 0 {
endHeight = best.Height
}
// Calculate the number of blocks per retarget interval based on the
// chain parameters.
blocksPerRetarget := int64(s.server.chainParams.TargetTimespan /
s.server.chainParams.TargetTimePerBlock)
// 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 % 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.chain.BlockHashByHeight(curHeight)
if err != nil {
context := "Failed to fetch block hash"
return nil, rpcInternalError(err.Error(), context)
}
// Fetch the header from chain.
header, err := s.chain.HeaderByHash(hash)
if err != nil {
context := "Failed to fetch block header"
return nil, rpcInternalError(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) {
peers := s.server.Peers()
syncPeer := s.server.blockManager.SyncPeer()
infos := make([]*dcrjson.GetPeerInfoResult, 0, len(peers))
for _, p := range peers {
statsSnap := p.StatsSnapshot()
info := &dcrjson.GetPeerInfoResult{
ID: statsSnap.ID,
Addr: statsSnap.Addr,
AddrLocal: p.LocalAddr().String(),
Services: fmt.Sprintf("%08d", uint64(statsSnap.Services)),
RelayTxes: !p.disableRelayTx,
LastSend: statsSnap.LastSend.Unix(),
LastRecv: statsSnap.LastRecv.Unix(),
BytesSent: statsSnap.BytesSent,
BytesRecv: statsSnap.BytesRecv,
ConnTime: statsSnap.ConnTime.Unix(),
PingTime: float64(statsSnap.LastPingMicros),
TimeOffset: statsSnap.TimeOffset,
Version: statsSnap.Version,
SubVer: statsSnap.UserAgent,
Inbound: statsSnap.Inbound,
StartingHeight: statsSnap.StartingHeight,
CurrentHeight: statsSnap.LastBlock,
BanScore: int32(p.banScore.Int()),
SyncNode: p == syncPeer,
}
if p.LastPingNonce() != 0 {
wait := float64(time.Since(statsSnap.LastPingTime).Nanoseconds())
// We actually want microseconds.
info.PingWait = wait / 1000
}
infos = append(infos, info)
}
return infos, nil
}
// handleGetRawMempool implements the getrawmempool command.
func handleGetRawMempool(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetRawMempoolCmd)
// Choose the type to filter the results by based on the provided param.
// A filter type of nil means no filtering.
var filterType *stake.TxType
if c.TxType != nil {
switch dcrjson.GetRawMempoolTxTypeCmd(*c.TxType) {
case dcrjson.GRMRegular:
filterType = new(stake.TxType)
*filterType = stake.TxTypeRegular
case dcrjson.GRMTickets:
filterType = new(stake.TxType)
*filterType = stake.TxTypeSStx
case dcrjson.GRMVotes:
filterType = new(stake.TxType)
*filterType = stake.TxTypeSSGen
case dcrjson.GRMRevocations:
filterType = new(stake.TxType)
*filterType = stake.TxTypeSSRtx
case dcrjson.GRMAll:
// Nothing to do
default:
return nil, rpcInvalidError("Invalid transaction "+
"type: %T", *c.TxType)
}
}
// Return verbose results if requested.
mp := s.server.txMemPool
if c.Verbose != nil && *c.Verbose {
return mp.RawMempoolVerbose(filterType), nil
}
// The response is simply an array of the transaction hashes if the
// verbose flag is not set.
descs := mp.TxDescs()
hashStrings := make([]string, 0, len(descs))
for i := range descs {
if filterType != nil && descs[i].Type != *filterType {
continue
}
hashStrings = append(hashStrings, descs[i].Tx.Hash().String())
}
return hashStrings, nil
}
// handleGetRawTransaction implements the getrawtransaction command.
func handleGetRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetRawTransactionCmd)
// Convert the provided transaction hash hex to a Hash.
txHash, err := chainhash.NewHashFromStr(c.Txid)
if err != nil {
return nil, rpcDecodeHexError(c.Txid)
}
verbose := false
if c.Verbose != nil {
verbose = *c.Verbose != 0
}
// Try to fetch the transaction from the memory pool and if that fails,
// try the block database.
var mtx *wire.MsgTx
var blkHash *chainhash.Hash
var blkHeight int64
tx, err := s.server.txMemPool.FetchTransaction(txHash, true)
if err != nil {
txIndex := s.server.txIndex
if txIndex == nil {
return nil, rpcInternalError("The transaction index "+
"must be enabled to query the blockchain "+
"(specify --txindex)", "Configuration")
}
// Look up the location of the transaction.
blockRegion, err := txIndex.TxBlockRegion(*txHash)
if err != nil {
context := "Failed to retrieve transaction location"
return nil, rpcInternalError(err.Error(), context)
}
if blockRegion == nil {
return nil, rpcNoTxInfoError(txHash)
}
// Load the raw transaction bytes from the database.
var txBytes []byte
err = s.server.db.View(func(dbTx database.Tx) error {
var err error
txBytes, err = dbTx.FetchBlockRegion(blockRegion)
return err
})
if err != nil {
return nil, rpcNoTxInfoError(txHash)
}
// When the verbose flag isn't set, simply return the serialized
// transaction as a hex-encoded string. This is done here to
// avoid deserializing it only to reserialize it again later.
if !verbose {
return hex.EncodeToString(txBytes), nil
}
// Grab the block height.
blkHash = blockRegion.Hash
blkHeight, err = s.chain.BlockHeightByHash(blkHash)
if err != nil {
context := "Failed to retrieve block height"
return nil, rpcInternalError(err.Error(), context)
}
// Deserialize the transaction
var msgTx wire.MsgTx
err = msgTx.Deserialize(bytes.NewReader(txBytes))
if err != nil {
context := "Failed to deserialize transaction"
return nil, rpcInternalError(err.Error(), context)
}
mtx = &msgTx
} else {
// When the verbose flag isn't set, simply return the
// network-serialized transaction as a hex-encoded string.
if !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.
mtxHex, err := messageToHex(tx.MsgTx())
if err != nil {
return nil, err
}
return mtxHex, nil
}
mtx = tx.MsgTx()
}
// The verbose flag is set, so generate the JSON object and return it.
var (
blkHeader *wire.BlockHeader
blkHashStr string
confirmations int64
)
if blkHash != nil {
// Fetch the header from chain.
header, err := s.chain.HeaderByHash(blkHash)
if err != nil {
context := "Failed to fetch block header"
return nil, rpcInternalError(err.Error(), context)
}
blkHeader = &header
blkHashStr = blkHash.String()
confirmations = 1 + s.chain.BestSnapshot().Height - blkHeight
}
rawTxn, err := createTxRawResult(s.server.chainParams, mtx,
txHash.String(), 0, blkHeader, blkHashStr, blkHeight,
confirmations)
if err != nil {
return nil, err
}
return *rawTxn, nil
}
// handleGetStakeDifficulty implements the getstakedifficulty command.
func handleGetStakeDifficulty(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
best := s.chain.BestSnapshot()
blockHeader, err := s.chain.HeaderByHeight(best.Height)
if err != nil {
rpcsLog.Errorf("Error getting block: %v", err)
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCDifficulty,
Message: "Error getting stake difficulty: " + err.Error(),
}
}
currentSdiff := dcrutil.Amount(blockHeader.SBits)
nextSdiff, err := s.server.blockManager.CalcNextRequiredStakeDifficulty()
if err != nil {
return nil, rpcInternalError("Could not calculate next stake "+
"difficulty "+err.Error(), "")
}
nextSdiffAmount := dcrutil.Amount(nextSdiff)
sDiffResult := &dcrjson.GetStakeDifficultyResult{
CurrentStakeDifficulty: currentSdiff.ToCoin(),
NextStakeDifficulty: nextSdiffAmount.ToCoin(),
}
return sDiffResult, nil
}
// convertVersionMap translates a map[int]int into a sorted array of
// VersionCount that contains the same information.
func convertVersionMap(m map[int]int) []dcrjson.VersionCount {
sorted := make([]dcrjson.VersionCount, 0, len(m))
order := make([]int, 0, len(m))
for k := range m {
order = append(order, k)
}
sort.Ints(order)
for _, v := range order {
sorted = append(sorted, dcrjson.VersionCount{Version: uint32(v),
Count: uint32(m[v])})
}
return sorted
}
// handleGetStakeVersionInfo implements the getstakeversioninfo command.
func handleGetStakeVersionInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
count := int32(1)
c, ok := cmd.(*dcrjson.GetStakeVersionInfoCmd)
if !ok {
return nil, rpcInvalidError("Invalid type: %T", c)
}
if c.Count != nil {
count = *c.Count
if count <= 0 {
return nil, rpcInvalidError("Count must be positive")
}
}
snapshot := s.chain.BestSnapshot()
interval := s.server.chainParams.StakeVersionInterval
// Assemble JSON result.
result := dcrjson.GetStakeVersionInfoResult{
CurrentHeight: snapshot.Height,
Hash: snapshot.Hash.String(),
Intervals: make([]dcrjson.VersionInterval, 0, count),
}
startHeight := snapshot.Height
endHeight := s.chain.CalcWantHeight(interval,
snapshot.Height) + 1
hash := &snapshot.Hash
adjust := int32(1) // We are off by one on the initial iteration.
for i := int32(0); i < count; i++ {
numBlocks := int32(startHeight - endHeight)
if numBlocks <= 0 {
// Just return what we got.
break
}
sv, err := s.chain.GetStakeVersions(hash, numBlocks+adjust)
if err != nil {
return nil, rpcInternalError(err.Error(),
"handleGetStakeVersionInfo")
}
posVersions := make(map[int]int)
voteVersions := make(map[int]int)
for _, v := range sv {
posVersions[int(v.StakeVersion)]++
for _, vote := range v.Votes {
voteVersions[int(vote.Version)]++
}
}
versionInterval := dcrjson.VersionInterval{
StartHeight: endHeight,
EndHeight: startHeight,
PoSVersions: convertVersionMap(posVersions),
VoteVersions: convertVersionMap(voteVersions),
}
result.Intervals = append(result.Intervals, versionInterval)
// Adjust interval.
endHeight -= interval
startHeight = endHeight + interval
adjust = 0
// Get prior block hash.
hash, err = s.chain.BlockHashByHeight(startHeight - 1)
if err != nil {
return nil, rpcInternalError(err.Error(),
"handleGetStakeVersionInfo")
}
}
return result, nil
}
// handleGetStakeVersions implements the getstakeversions command.
func handleGetStakeVersions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetStakeVersionsCmd)
hash, err := chainhash.NewHashFromStr(c.Hash)
if err != nil {
return nil, rpcDecodeHexError(c.Hash)
}
if c.Count <= 0 {
return nil, rpcInvalidError("Invalid parameter, count must " +
"be > 0")
}
sv, err := s.chain.GetStakeVersions(hash, c.Count)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain stake versions")
}
result := dcrjson.GetStakeVersionsResult{
StakeVersions: make([]dcrjson.StakeVersions, 0, len(sv)),
}
for _, v := range sv {
nsv := dcrjson.StakeVersions{
Hash: v.Hash.String(),
Height: v.Height,
BlockVersion: v.BlockVersion,
StakeVersion: v.StakeVersion,
Votes: make([]dcrjson.VersionBits, 0,
len(v.Votes)),
}
for _, vote := range v.Votes {
nsv.Votes = append(nsv.Votes,
dcrjson.VersionBits{Version: vote.Version,
Bits: vote.Bits})
}
result.StakeVersions = append(result.StakeVersions, nsv)
}
return result, nil
}
// handleGetTicketPoolValue implements the getticketpoolvalue command.
func handleGetTicketPoolValue(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
amt, err := s.server.blockManager.TicketPoolValue()
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain ticket pool value")
}
return amt.ToCoin(), nil
}
// handleGetVoteInfo implements the getvoteinfo command.
func handleGetVoteInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c, ok := cmd.(*dcrjson.GetVoteInfoCmd)
if !ok {
return nil, rpcInvalidError("Invalid type: %T", c)
}
snapshot := s.chain.BestSnapshot()
interval := int64(s.server.chainParams.RuleChangeActivationInterval)
quorum := s.server.chainParams.RuleChangeActivationQuorum
// Assemble JSON result.
result := dcrjson.GetVoteInfoResult{
CurrentHeight: snapshot.Height,
StartHeight: s.chain.CalcWantHeight(interval,
snapshot.Height) + 1,
EndHeight: s.chain.CalcWantHeight(interval,
snapshot.Height) + interval,
Hash: snapshot.Hash.String(),
VoteVersion: c.Version,
Quorum: quorum,
}
// We don't fail, we try to return the totals for this version.
var err error
result.TotalVotes, err = s.chain.CountVoteVersion(c.Version)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not count voter versions")
}
vi, err := s.chain.GetVoteInfo(&snapshot.Hash, c.Version)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain vote info")
}
result.Agendas = make([]dcrjson.Agenda, 0, len(vi.Agendas))
for _, agenda := range vi.Agendas {
a := dcrjson.Agenda{
Id: agenda.Vote.Id,
Description: agenda.Vote.Description,
Mask: agenda.Vote.Mask,
Choices: make([]dcrjson.Choice, 0,
len(agenda.Vote.Choices)),
StartTime: agenda.StartTime,
ExpireTime: agenda.ExpireTime,
}
// Handle choices.
for _, choice := range agenda.Vote.Choices {
c := dcrjson.Choice{
Id: choice.Id,
Description: choice.Description,
Bits: choice.Bits,
IsAbstain: choice.IsAbstain,
IsNo: choice.IsNo,
}
a.Choices = append(a.Choices, c)
}
// Obtain status of agenda.
state, err := s.chain.ThresholdState(&snapshot.Hash, c.Version,
agenda.Vote.Id)
if err != nil {
return nil, err
}
// Save off status.
a.Status = state.String()
if state.State != blockchain.ThresholdStarted {
// Append transformed agenda without progress.
result.Agendas = append(result.Agendas, a)
continue
}
counts, err := s.chain.GetVoteCounts(c.Version, agenda.Vote.Id)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain vote count")
}
// Calculate quorum.
qmin := quorum
totalNonAbstain := counts.Total - counts.TotalAbstain
if totalNonAbstain < quorum {
qmin = totalNonAbstain
}
a.QuorumProgress = float64(qmin) / float64(quorum)
// Calculate choice progress.
for k := range a.Choices {
a.Choices[k].Count = counts.VoteChoices[k]
a.Choices[k].Progress = float64(counts.VoteChoices[k]) /
float64(counts.Total)
}
// Append transformed agenda.
result.Agendas = append(result.Agendas, a)
}
return result, 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
}
// handleGetTxOut handles gettxout commands.
func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.GetTxOutCmd)
// Convert the provided transaction hash hex to a Hash.
txHash, err := chainhash.NewHashFromStr(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 bestBlockHash string
var confirmations int64
var txVersion uint16
var value int64
var scriptVersion uint16
var pkScript []byte
var isCoinbase bool
includeMempool := true
if c.IncludeMempool != nil {
includeMempool = *c.IncludeMempool
}
var txFromMempool *dcrutil.Tx
if includeMempool {
txFromMempool, _ = s.server.txMemPool.FetchTransaction(txHash,
true)
}
if txFromMempool != nil {
mtx := txFromMempool.MsgTx()
if c.Vout > uint32(len(mtx.TxOut)-1) {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCInvalidTxVout,
Message: "Output 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, txHash)
return nil, rpcInternalError(errStr, "")
}
best := s.chain.BestSnapshot()
bestBlockHash = best.Hash.String()
confirmations = 0
txVersion = mtx.Version
value = txOut.Value
scriptVersion = txOut.Version
pkScript = txOut.PkScript
isCoinbase = blockchain.IsCoinBaseTx(mtx)
} else {
entry, err := s.chain.FetchUtxoEntry(txHash)
if err != nil {
return nil, rpcNoTxInfoError(txHash)
}
// To match the behavior of the reference client, return nil
// (JSON null) if the transaction output is spent by another
// transaction already in the main chain. Mined transactions
// that are spent by a mempool transaction are not affected by
// this.
if entry == nil || entry.IsOutputSpent(c.Vout) {
return nil, nil
}
best := s.chain.BestSnapshot()
bestBlockHash = best.Hash.String()
confirmations = 1 + best.Height - entry.BlockHeight()
txVersion = entry.TxVersion()
value = entry.AmountByIndex(c.Vout)
scriptVersion = entry.ScriptVersionByIndex(c.Vout)
pkScript = entry.PkScriptByIndex(c.Vout)
isCoinbase = entry.IsCoinBase()
}
// 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 := 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(scriptVersion,
script, s.server.chainParams)
addresses := make([]string, len(addrs))
for i, addr := range addrs {
addresses[i] = addr.EncodeAddress()
}
txOutReply := &dcrjson.GetTxOutResult{
BestBlock: bestBlockHash,
Confirmations: confirmations,
Value: dcrutil.Amount(value).ToUnit(dcrutil.AmountCoin),
Version: int32(txVersion),
ScriptPubKey: dcrjson.ScriptPubKeyResult{
Asm: disbuf,
Hex: hex.EncodeToString(pkScript),
ReqSigs: int32(reqSigs),
Type: scriptClass.String(),
Addresses: addresses,
},
Coinbase: isCoinbase,
}
return txOutReply, nil
}
// pruneOldBlockTemplates prunes all old block templates from the templatePool
// map. Must be called with the RPC workstate locked to avoid races to the map.
func pruneOldBlockTemplates(s *rpcServer, bestHeight int64) {
pool := s.templatePool
for rootHash, blkData := range pool {
height := int64(blkData.msgBlock.Header.Height)
if height < bestHeight-getworkExpirationDiff {
delete(pool, rootHash)
}
}
}
// 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
// The current code pulls down a new template every second, however
// with a large mempool this will be pretty excruciating sometimes. It
// should examine whether or not a new template needs to be created
// based on the votes present every second or so, and then, if needed,
// generate a new block template. TODO cj
if msgBlock == nil || state.prevHash == nil ||
!state.prevHash.IsEqual(latestHash) ||
(state.lastTxUpdate != lastTxUpdate &&
time.Now().After(state.lastGenerated.Add(time.Second))) {
// Reset the extra nonce and clear all expired cached template
// variations if the best block changed.
if state.prevHash != nil && !state.prevHash.IsEqual(latestHash) {
state.extraNonce = 0
pruneOldBlockTemplates(s, latestHeight)
}
// 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.policy, s.server, payToAddr)
if err != nil {
context := "Failed to create new block template"
return nil, rpcInternalError(err.Error(), context)
}
if template == nil {
// This happens if the template is returned nil because
// there are not enough voters on HEAD and there is
// currently an unsuitable parent cached template to
// try building off of.
context := "Failed to create new block template: not " +
"enough voters and failed to find a suitable " +
"parent template to build from"
return nil, rpcInternalError("internal error", context)
}
templateCopy := deepCopyBlockTemplate(template)
msgBlock = templateCopy.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)",
msgBlock.Header.Timestamp, state.extraNonce,
blockchain.CompactToBig(msgBlock.Header.Bits),
msgBlock.Header.MerkleRoot)
} else {
if msgBlock == nil {
context := "Failed to create new block template, " +
"no previous state"
return nil, rpcInternalError("internal error", context)
}
// 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.
templateCopy := deepCopyBlockTemplate(&BlockTemplate{
Block: msgBlock,
})
msgBlock = templateCopy.Block
// 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.
err := UpdateBlockTime(msgBlock, s.server.blockManager)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Failed to update block time")
}
if templateCopy.Height > 1 {
// 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.
en := extractCoinbaseExtraNonce(msgBlock) + 1
state.extraNonce++
err := UpdateExtraNonce(msgBlock, latestHeight+1, en)
if err != nil {
errStr := fmt.Sprintf("Failed to update extra nonce: "+
"%v", err)
return nil, rpcInternalError(errStr, "")
}
}
rpcsLog.Debugf("Updated block template (timestamp %v, extra "+
"nonce %d, target %064x, merkle root %s)",
msgBlock.Header.Timestamp,
state.extraNonce,
blockchain.CompactToBig(msgBlock.Header.Bits),
msgBlock.Header.MerkleRoot)
}
// 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]
// Create the new merkleRootPair key which is MerkleRoot + StakeRoot
var merkleRootPair [merkleRootPairSize]byte
copy(merkleRootPair[:chainhash.HashSize], msgBlock.Header.MerkleRoot[:])
copy(merkleRootPair[chainhash.HashSize:], msgBlock.Header.StakeRoot[:])
if msgBlock.Header.Height > 1 {
s.templatePool[merkleRootPair] = &workStateBlockInfo{
msgBlock: msgBlock,
pkScript: coinbaseTx.TxOut[1].PkScript,
}
} else {
s.templatePool[merkleRootPair] = &workStateBlockInfo{
msgBlock: msgBlock,
}
}
// Serialize the block header into a buffer large enough to hold the
// the block header and the internal blake256 padding that is added and
// retuned as part of the data below. For reference:
// data[116] --> nBits
// data[136] --> Timestamp
// data[140] --> nonce
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, rpcInternalError(errStr, "")
}
// Expand the data slice to include the full data buffer and apply the
// internal blake256 padding. This makes the data ready for callers to
// make use of only the final chunk along with the midstate for the
// rest.
data = data[:getworkDataLen]
copy(data[wire.MaxBlockHeaderPayload:], blake256Pad)
// The final result reverses 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.
target := bigToLEUint256(blockchain.CompactToBig(msgBlock.Header.Bits))
reply := &dcrjson.GetWorkResult{
Data: hex.EncodeToString(data),
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 nil, rpcInvalidError("Argument must be %d bytes (not "+
"%d)", getworkDataLen, len(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, rpcInvalidError("Invalid block header: %v", err)
}
// Create the new merkleRootPair key which is MerkleRoot + StakeRoot
var merkleRootPair [merkleRootPairSize]byte
copy(merkleRootPair[:chainhash.HashSize], submittedHeader.MerkleRoot[:])
copy(merkleRootPair[chainhash.HashSize:], submittedHeader.StakeRoot[:])
// 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.
blockInfo, ok := s.templatePool[merkleRootPair]
if !ok {
rpcsLog.Errorf("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.
// A temporary block is used because we will be mutating the contents
// for the construction of the correct regular merkle tree. You must
// also deep copy the block itself because it could be accessed outside
// of the GW workstate mutexes once it gets submitted to the
// blockchain.
tempBlock := dcrutil.NewBlockDeepCopy(blockInfo.msgBlock)
msgBlock := tempBlock.MsgBlock()
msgBlock.Header = submittedHeader
if msgBlock.Header.Height > 1 {
pkScriptCopy := make([]byte, len(blockInfo.pkScript))
copy(pkScriptCopy, blockInfo.pkScript)
msgBlock.Transactions[0].TxOut[1].PkScript = blockInfo.pkScript
merkles := blockchain.BuildMerkleTreeStore(tempBlock.Transactions())
msgBlock.Header.MerkleRoot = *merkles[len(merkles)-1]
}
// The real block to submit, with a proper nonce and extraNonce.
block := dcrutil.NewBlockDeepCopyCoinbase(msgBlock)
// Ensure the submitted block hash is less than the target difficulty.
err = blockchain.CheckProofOfWork(&block.MsgBlock().Header,
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, rpcInternalError("Unexpected error "+
"while checking proof of work: "+err.Error(),
"")
}
rpcsLog.Errorf("Block submitted via getwork does not meet "+
"the required proof of work: %v", err)
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 {
// 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, rpcInternalError("Unexpected error "+
"while processing block: "+err.Error(), "")
}
rpcsLog.Infof("Block submitted via getwork rejected: %v", err)
return false, nil
}
if isOrphan {
rpcsLog.Infof("Block submitted via getwork rejected: an orphan building "+
"on parent %v", block.MsgBlock().Header.PrevBlock)
return false, nil
}
// The block was accepted.
rpcsLog.Infof("Block submitted via getwork accepted: %s", block.Hash())
return true, nil
}
// handleGetWork implements the getwork command.
func handleGetWork(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
if s.server.cpuMiner.IsMining() {
return nil, rpcMiscError("getwork polling is disallowed " +
"while CPU mining is enabled. Please disable CPU " +
"mining and try again.")
}
// Respond with an error if there are no addresses to pay the created
// blocks to.
if len(cfg.miningAddrs) == 0 {
return nil, rpcInternalError("No payment addresses specified "+
"via --miningaddr", "Configuration")
}
// 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.SimNet && s.server.ConnectedCount() == 0 {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCClientNotConnected,
Message: "Decred 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, &dcrjson.RPCError{
Code: dcrjson.ErrRPCClientInInitialDownload,
Message: "Decred is downloading blocks...",
}
}
c := cmd.(*dcrjson.GetWorkCmd)
// 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.(*dcrjson.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, rpcInternalError(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, rpcInvalidError("Unknown command: %v", command)
}
// Get the help for the command.
help, err := s.helpCacher.rpcMethodHelp(command)
if err != nil {
context := "Failed to generate help"
return nil, rpcInternalError(err.Error(), context)
}
return help, nil
}
// handleLiveTickets implements the livetickets command.
func handleLiveTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
lt, err := s.server.blockManager.chain.LiveTickets()
if err != nil {
return nil, rpcInternalError("Could not get live tickets "+
err.Error(), "")
}
ltString := make([]string, len(lt))
for i := range lt {
ltString[i] = lt[i].String()
}
return dcrjson.LiveTicketsResult{Tickets: ltString}, nil
}
// handleMissedTickets implements the missedtickets command.
func handleMissedTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
mt, err := s.server.blockManager.chain.MissedTickets()
if err != nil {
return nil, rpcInternalError("Could not get missed tickets "+
err.Error(), "")
}
mtString := make([]string, len(mt))
for i, hash := range mt {
mtString[i] = hash.String()
}
return dcrjson.MissedTicketsResult{Tickets: mtString}, 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, rpcInternalError("Not sending ping - failed to "+
"generate nonce: "+err.Error(), "")
}
s.server.BroadcastMessage(wire.NewMsgPing(nonce))
return nil, nil
}
// handleRebroadcastMissed implements the rebroadcastmissed command.
func handleRebroadcastMissed(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
hash, height := s.server.blockManager.chainState.Best()
mt, err := s.server.blockManager.chain.MissedTickets()
if err != nil {
return nil, rpcInternalError("Could not get missed tickets "+
err.Error(), "")
}
stakeDiff, err := s.server.blockManager.CalcNextRequiredStakeDifficulty()
if err != nil {
return nil, rpcInternalError("Could not calculate next stake "+
"difficulty "+err.Error(), "")
}
missedTicketsNtfn := &blockchain.TicketNotificationsData{
Hash: *hash,
Height: height,
StakeDifficulty: stakeDiff,
TicketsSpent: []chainhash.Hash{},
TicketsMissed: mt,
TicketsNew: []chainhash.Hash{},
}
s.ntfnMgr.NotifySpentAndMissedTickets(missedTicketsNtfn)
return nil, nil
}
// handleRebroadcastWinners implements the rebroadcastwinners command.
func handleRebroadcastWinners(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
_, height := s.server.blockManager.chainState.Best()
blocks, err := s.server.blockManager.TipGeneration()
if err != nil {
return nil, rpcInternalError("Could not get generation "+
err.Error(), "")
}
for i := range blocks {
winningTickets, _, _, err :=
s.server.blockManager.chain.LotteryDataForBlock(&blocks[i])
if err != nil {
return nil, rpcInternalError("Lottery data for block "+
"failed: "+err.Error(), "")
}
ntfnData := &WinningTicketsNtfnData{
BlockHash: blocks[i],
BlockHeight: height,
Tickets: winningTickets,
}
s.ntfnMgr.NotifyWinningTickets(ntfnData)
}
return nil, nil
}
// retrievedTx represents a transaction that was either loaded from the
// transaction memory pool or from the database. When a transaction is loaded
// from the database, it is loaded with the raw serialized bytes while the
// mempool has the fully deserialized structure. This structure therefore will
// have one of the two fields set depending on where is was retrieved from.
// This is mainly done for efficiency to avoid extra serialization steps when
// possible.
type retrievedTx struct {
txBytes []byte
blkHash *chainhash.Hash // Only set when transaction is in a block.
tx *dcrutil.Tx
}
// fetchInputTxos fetches the outpoints from all transactions referenced by the
// inputs to the passed transaction by checking the transaction mempool first
// then the transaction index for those already mined into blocks.
func fetchInputTxos(s *rpcServer, tx *wire.MsgTx) (map[wire.OutPoint]wire.TxOut, error) {
mp := s.server.txMemPool
originOutputs := make(map[wire.OutPoint]wire.TxOut)
voteTx := stake.IsSSGen(tx)
for txInIndex, txIn := range tx.TxIn {
// vote tx have null input for vin[0],
// skip since it resolvces to an invalid transaction
if voteTx && txInIndex == 0 {
continue
}
// Attempt to fetch and use the referenced transaction from the
// memory pool.
origin := &txIn.PreviousOutPoint
originTx, err := mp.FetchTransaction(&origin.Hash, true)
if err == nil {
txOuts := originTx.MsgTx().TxOut
if origin.Index >= uint32(len(txOuts)) {
errStr := fmt.Sprintf("unable to find output "+
"%v referenced from transaction %s:%d",
origin, tx.TxHash(), txInIndex)
return nil, rpcInternalError(errStr, "")
}
originOutputs[*origin] = *txOuts[origin.Index]
continue
}
// Look up the location of the transaction.
blockRegion, err := s.server.txIndex.TxBlockRegion(origin.Hash)
if err != nil {
context := "Failed to retrieve transaction location"
return nil, rpcInternalError(err.Error(), context)
}
if blockRegion == nil {
return nil, rpcNoTxInfoError(&origin.Hash)
}
// Load the raw transaction bytes from the database.
var txBytes []byte
err = s.server.db.View(func(dbTx database.Tx) error {
var err error
txBytes, err = dbTx.FetchBlockRegion(blockRegion)
return err
})
if err != nil {
return nil, rpcNoTxInfoError(&origin.Hash)
}
// Deserialize the transaction
var msgTx wire.MsgTx
err = msgTx.Deserialize(bytes.NewReader(txBytes))
if err != nil {
context := "Failed to deserialize transaction"
return nil, rpcInternalError(err.Error(), context)
}
// Add the referenced output to the map.
if origin.Index >= uint32(len(msgTx.TxOut)) {
errStr := fmt.Sprintf("unable to find output %v "+
"referenced from transaction %s:%d", origin,
tx.TxHash(), txInIndex)
return nil, rpcInternalError(errStr, "")
}
originOutputs[*origin] = *msgTx.TxOut[origin.Index]
}
return originOutputs, nil
}
// createVinListPrevOut returns a slice of JSON objects for the inputs of the
// passed transaction.
func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.Params, vinExtra bool, filterAddrMap map[string]struct{}) ([]dcrjson.VinPrevOut, error) {
// Coinbase transactions only have a single txin by definition.
if blockchain.IsCoinBaseTx(mtx) {
// Only include the transaction if the filter map is empty
// because a coinbase input has no addresses and so would never
// match a non-empty filter.
if len(filterAddrMap) != 0 {
return nil, nil
}
txIn := mtx.TxIn[0]
vinList := make([]dcrjson.VinPrevOut, 1)
vinList[0].Coinbase = hex.EncodeToString(txIn.SignatureScript)
amountIn := dcrutil.Amount(txIn.ValueIn).ToCoin()
vinList[0].AmountIn = &amountIn
vinList[0].Sequence = txIn.Sequence
return vinList, nil
}
// Use a dynamically sized list to accommodate the address filter.
vinList := make([]dcrjson.VinPrevOut, 0, len(mtx.TxIn))
// Lookup all of the referenced transaction outputs needed to populate
// the previous output information if requested.
var originOutputs map[wire.OutPoint]wire.TxOut
if vinExtra || len(filterAddrMap) > 0 {
var err error
originOutputs, err = fetchInputTxos(s, mtx)
if err != nil {
return nil, err
}
}
// Stakebase transactions (votes) have two inputs: a null stake base
// followed by an input consuming a ticket's stakesubmission.
isSSGen := stake.IsSSGen(mtx)
for i, txIn := range mtx.TxIn {
// Handle only the null input of a stakebase differently.
if isSSGen && i == 0 {
amountIn := dcrutil.Amount(txIn.ValueIn).ToCoin()
vinEntry := dcrjson.VinPrevOut{
Stakebase: hex.EncodeToString(txIn.SignatureScript),
AmountIn: &amountIn,
Sequence: txIn.Sequence,
}
vinList = append(vinList, vinEntry)
// No previous outpoints to check against the address filter.
continue
}
// The disassembled string will contain [error] inline if the
// script doesn't fully parse, so ignore the error here.
disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
// Create the basic input entry without the additional optional
// previous output details which will be added later if
// requested and available.
prevOut := &txIn.PreviousOutPoint
amountIn := dcrutil.Amount(txIn.ValueIn).ToCoin()
vinEntry := dcrjson.VinPrevOut{
Txid: prevOut.Hash.String(),
Vout: prevOut.Index,
Tree: prevOut.Tree,
AmountIn: &amountIn,
BlockHeight: &txIn.BlockHeight,
BlockIndex: &txIn.BlockIndex,
Sequence: txIn.Sequence,
ScriptSig: &dcrjson.ScriptSig{
Asm: disbuf,
Hex: hex.EncodeToString(txIn.SignatureScript),
},
}
// Add the entry to the list now if it already passed the
// filter since the previous output might not be available.
passesFilter := len(filterAddrMap) == 0
if passesFilter {
vinList = append(vinList, vinEntry)
}
// Only populate previous output information if requested and
// available.
if len(originOutputs) == 0 {
continue
}
originTxOut, ok := originOutputs[*prevOut]
if !ok {
continue
}
// Ignore the error here since an error means the script
// couldn't parse and there is no additional information about
// it anyways.
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(originTxOut.Version,
originTxOut.PkScript, chainParams)
// Encode the addresses while checking if the address passes
// the filter when needed.
encodedAddrs := make([]string, len(addrs))
for j, addr := range addrs {
encodedAddr := addr.EncodeAddress()
encodedAddrs[j] = encodedAddr
// No need to check the map again if the filter already
// passes.
if passesFilter {
continue
}
if _, exists := filterAddrMap[encodedAddr]; exists {
passesFilter = true
}
}
// Ignore the entry if it doesn't pass the filter.
if !passesFilter {
continue
}
// Add entry to the list if it wasn't already done above.
if len(filterAddrMap) != 0 {
vinList = append(vinList, vinEntry)
}
// Update the entry with previous output information if
// requested.
if vinExtra {
vinListEntry := &vinList[len(vinList)-1]
vinListEntry.PrevOut = &dcrjson.PrevOut{
Addresses: encodedAddrs,
Value: dcrutil.Amount(originTxOut.Value).ToCoin(),
}
}
}
return vinList, nil
}
// fetchMempoolTxnsForAddress queries the address index for all unconfirmed
// transactions that involve the provided address. The results will be limited
// by the number to skip and the number requested.
func fetchMempoolTxnsForAddress(s *rpcServer, addr dcrutil.Address, numToSkip, numRequested uint32) ([]*dcrutil.Tx, uint32) {
// There are no entries to return when there are less available than
// the number being skipped.
mpTxns := s.server.addrIndex.UnconfirmedTxnsForAddress(addr)
numAvailable := uint32(len(mpTxns))
if numToSkip > numAvailable {
return nil, numAvailable
}
// Filter the available entries based on the number to skip and number
// requested.
rangeEnd := numToSkip + numRequested
if rangeEnd > numAvailable {
rangeEnd = numAvailable
}
return mpTxns[numToSkip:rangeEnd], numToSkip
}
// handleSearchRawTransactions implements the searchrawtransactions command.
func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
// Respond with an error if the address index is not enabled.
addrIndex := s.server.addrIndex
if addrIndex == nil {
return nil, rpcInternalError("Address index must be "+
"enabled (--addrindex)", "Configuration")
}
// Override the flag for including extra previous output information in
// each input if needed.
c := cmd.(*dcrjson.SearchRawTransactionsCmd)
vinExtra := false
if c.VinExtra != nil {
vinExtra = *c.VinExtra != 0
}
// Including the extra previous output information requires the
// transaction index. Currently the address index relies on the
// transaction index, so this check is redundant, but it's better to be
// safe in case the address index is ever changed to not rely on it.
if vinExtra && s.server.txIndex == nil {
return nil, rpcInternalError("Transaction index must be "+
"enabled (--txindex)", "Configuration")
}
// Attempt to decode the supplied address.
addr, err := dcrutil.DecodeAddress(c.Address)
if err != nil {
return nil, rpcAddressKeyError("Could not decode address: %v",
err)
}
// Override the default number of requested entries if needed. Also,
// just return now if the number of requested entries is zero to avoid
// extra work.
numRequested := 100
if c.Count != nil {
numRequested = *c.Count
if numRequested < 0 {
numRequested = 1
}
}
if numRequested == 0 {
return nil, nil
}
// Override the default number of entries to skip if needed.
var numToSkip int
if c.Skip != nil {
numToSkip = *c.Skip
if numToSkip < 0 {
numToSkip = 0
}
}
// Override the reverse flag if needed.
var reverse bool
if c.Reverse != nil {
reverse = *c.Reverse
}
// Add transactions from mempool first if client asked for reverse
// order. Otherwise, they will be added last (as needed depending on
// the requested counts).
//
// NOTE: This code doesn't sort by dependency. This might be something
// to do in the future for the client's convenience, or leave it to the
// client.
numSkipped := uint32(0)
addressTxns := make([]retrievedTx, 0, numRequested)
if reverse {
// Transactions in the mempool are not in a block header yet,
// so the block header field in the retieved transaction struct
// is left nil.
mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr,
uint32(numToSkip), uint32(numRequested))
numSkipped += mpSkipped
for _, tx := range mpTxns {
addressTxns = append(addressTxns, retrievedTx{tx: tx})
}
}
// Fetch transactions from the database in the desired order if more
// are needed.
if len(addressTxns) < numRequested {
err = s.server.db.View(func(dbTx database.Tx) error {
regions, dbSkipped, err := addrIndex.TxRegionsForAddress(
dbTx, addr, uint32(numToSkip)-numSkipped,
uint32(numRequested-len(addressTxns)), reverse)
if err != nil {
return err
}
// Load the raw transaction bytes from the database.
serializedTxns, err := dbTx.FetchBlockRegions(regions)
if err != nil {
return err
}
// Add the transaction and the hash of the block it is
// contained in to the list. Note that the transaction
// is left serialized here since the caller might have
// requested non-verbose output and hence there would
// be no point in deserializing it just to reserialize
// it later.
for i, serializedTx := range serializedTxns {
addressTxns = append(addressTxns, retrievedTx{
txBytes: serializedTx,
blkHash: regions[i].Hash,
})
}
numSkipped += dbSkipped
return nil
})
if err != nil {
context := "Failed to load address index entries"
return nil, rpcInternalError(err.Error(), context)
}
}
// Add transactions from mempool last if client did not request reverse
// order and the number of results is still under the number requested.
if !reverse && len(addressTxns) < numRequested {
// Transactions in the mempool are not in a block header yet,
// so the block header field in the retieved transaction struct
// is left nil.
mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr,
uint32(numToSkip)-numSkipped, uint32(numRequested-
len(addressTxns)))
numSkipped += mpSkipped
for _, tx := range mpTxns {
addressTxns = append(addressTxns, retrievedTx{tx: tx})
}
}
// Address has never been used if neither source yielded any results.
if len(addressTxns) == 0 {
return nil, rpcInternalError("No Txns available", "")
}
// Serialize all of the transactions to hex.
hexTxns := make([]string, len(addressTxns))
for i := range addressTxns {
// Simply encode the raw bytes to hex when the retrieved
// transaction is already in serialized form.
rtx := &addressTxns[i]
if rtx.txBytes != nil {
hexTxns[i] = hex.EncodeToString(rtx.txBytes)
continue
}
// Serialize the transaction first and convert to hex when the
// retrieved transaction is the deserialized structure.
hexTxns[i], err = messageToHex(rtx.tx.MsgTx())
if err != nil {
return nil, err
}
}
// When not in verbose mode, simply return a list of serialized txns.
if c.Verbose != nil && *c.Verbose == 0 {
return hexTxns, nil
}
// Normalize the provided filter addresses (if any) to ensure there are
// no duplicates.
filterAddrMap := make(map[string]struct{})
if c.FilterAddrs != nil && len(*c.FilterAddrs) > 0 {
for _, addr := range *c.FilterAddrs {
filterAddrMap[addr] = struct{}{}
}
}
// The verbose flag is set, so generate the JSON object and return it.
best := s.chain.BestSnapshot()
chainParams := s.server.chainParams
srtList := make([]dcrjson.SearchRawTransactionsResult, len(addressTxns))
for i := range addressTxns {
// The deserialized transaction is needed, so deserialize the
// retrieved transaction if it's in serialized form (which will
// be the case when it was lookup up from the database).
// Otherwise, use the existing deserialized transaction.
rtx := &addressTxns[i]
var mtx *wire.MsgTx
if rtx.tx == nil {
// Deserialize the transaction.
mtx = new(wire.MsgTx)
err := mtx.Deserialize(bytes.NewReader(rtx.txBytes))
if err != nil {
context := "Failed to deserialize transaction"
return nil, rpcInternalError(err.Error(),
context)
}
} else {
mtx = rtx.tx.MsgTx()
}
result := &srtList[i]
result.Hex = hexTxns[i]
result.Txid = mtx.TxHash().String()
result.Vin, err = createVinListPrevOut(s, mtx, chainParams,
vinExtra, filterAddrMap)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not create vin list")
}
result.Vout = createVoutList(mtx, chainParams, filterAddrMap)
result.Version = int32(mtx.Version)
result.LockTime = mtx.LockTime
// Transactions grabbed from the mempool aren't yet in a block,
// so conditionally fetch block details here. This will be
// reflected in the final JSON output (mempool won't have
// confirmations or block information).
var blkHeader *wire.BlockHeader
var blkHashStr string
var blkHeight int64
if blkHash := rtx.blkHash; blkHash != nil {
// Fetch the header from chain.
header, err := s.chain.HeaderByHash(blkHash)
if err != nil {
return nil, &dcrjson.RPCError{
Code: dcrjson.ErrRPCBlockNotFound,
Message: "Block not found",
}
}
// Get the block height from chain.
height, err := s.chain.BlockHeightByHash(blkHash)
if err != nil {
context := "Failed to obtain block height"
return nil, rpcInternalError(err.Error(), context)
}
blkHeader = &header
blkHashStr = blkHash.String()
blkHeight = height
}
// Add the block information to the result if there is any.
if blkHeader != nil {
// This is not a typo, they are identical in Bitcoin
// Core as well.
result.Time = blkHeader.Timestamp.Unix()
result.Blocktime = blkHeader.Timestamp.Unix()
result.BlockHash = blkHashStr
result.Confirmations = uint64(1 + best.Height - blkHeight)
}
}
return srtList, nil
}
// handleSendRawTransaction implements the sendrawtransaction command.
func handleSendRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.SendRawTransactionCmd)
// Deserialize and send off to tx relay
allowHighFees := *c.AllowHighFees
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, rpcDeserializationError("Could not decode Tx: %v",
err)
}
tx := dcrutil.NewTx(msgtx)
acceptedTxs, err := s.server.blockManager.ProcessTransaction(tx, false,
false, allowHighFees)
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.(mempool.RuleError); ok {
err = fmt.Errorf("Rejected transaction %v: %v", tx.Hash(),
err)
rpcsLog.Debugf("%v", err)
txRuleErr, ok := err.(mempool.TxRuleError)
if ok {
if txRuleErr.RejectCode == wire.RejectDuplicate {
// return a dublicate tx error
return nil, rpcDuplicateTxError("%v", err)
}
}
// return a generic rule error
return nil, rpcRuleError("%v", err)
}
err = fmt.Errorf("failed to process transaction %v: %v",
tx.Hash(), err)
rpcsLog.Errorf("%v", err)
return nil, rpcDeserializationError("rejected: %v", err)
}
s.server.AnnounceNewTransactions(acceptedTxs)
// Keep track of all the regular sendrawtransaction request txns so that
// they can be rebroadcast if they don't make their way into a block.
//
// Note that votes are only valid for a specific block and are time
// sensitive, so they should not be added to the rebroadcast logic.
//
// TODO: Ideally ticket purchases and revocations could be added to the
// rebroadcast logic as well, however, they would need to be removed under
// certain circumstances such as when the stake difficulty interval changes
// and if a revocation is for a ticket that was missed, but then becomes
// live again due to a reorg. All stake transactions are ignored here since
// there is no clean infrastructure in place currently to handle those
// removals and perpetually broadcasting transactions which are no longer
// valid is not desirable.
if txType := stake.DetermineTxType(msgtx); txType == stake.TxTypeRegular {
iv := wire.NewInvVect(wire.InvTypeTx, tx.Hash())
s.server.AddRebroadcastInventory(iv, tx)
}
return tx.Hash().String(), nil
}
// handleSetGenerate implements the setgenerate command.
func handleSetGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.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, rpcInternalError("No payment addresses "+
"specified via --miningaddr", "Configuration")
}
// 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) {
select {
case s.requestProcessShutdown <- struct{}{}:
default:
}
return "dcrd stopping.", nil
}
// handleSubmitBlock implements the submitblock command.
func handleSubmitBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.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, rpcInternalError(err.Error(), "Block decode")
}
block, err := dcrutil.NewBlockFromBytes(serializedBlock)
if err != nil {
return nil, rpcInternalError(err.Error(), "Block decode")
}
_, err = s.server.blockManager.ProcessBlock(block, blockchain.BFNone)
if err != nil {
return fmt.Sprintf("rejected: %v", err), nil
}
rpcsLog.Infof("Accepted block %s via submitblock", block.Hash())
return nil, nil
}
// min gets the minimum amount from a slice of amounts.
func min(s []dcrutil.Amount) dcrutil.Amount {
if len(s) == 0 {
return 0
}
min := s[0]
for i := range s {
if s[i] < min {
min = s[i]
}
}
return min
}
// max gets the maximum amount from a slice of amounts.
func max(s []dcrutil.Amount) dcrutil.Amount {
max := dcrutil.Amount(0)
for i := range s {
if s[i] > max {
max = s[i]
}
}
return max
}
// mean gets the mean amount from a slice of amounts.
func mean(s []dcrutil.Amount) dcrutil.Amount {
sum := dcrutil.Amount(0)
for i := range s {
sum += s[i]
}
if len(s) == 0 {
return 0
}
return sum / dcrutil.Amount(len(s))
}
// median gets the median amount from a slice of amounts.
func median(s []dcrutil.Amount) dcrutil.Amount {
if len(s) == 0 {
return 0
}
sort.Sort(dcrutil.AmountSorter(s))
middle := len(s) / 2
if len(s) == 0 {
return 0
} else if (len(s) % 2) != 0 {
return s[middle]
}
return (s[middle] + s[middle-1]) / 2
}
// stdDev gets the standard deviation amount from a slice of amounts.
func stdDev(s []dcrutil.Amount) dcrutil.Amount {
var total float64
mean := mean(s)
for i := range s {
total += math.Pow(s[i].ToCoin()-mean.ToCoin(), 2)
}
if len(s)-1 == 0 {
return 0
}
v := total / float64(len(s)-1)
// Not concerned with an error here, it'll return
// zero if the amount is too small.
amt, _ := dcrutil.NewAmount(math.Sqrt(v))
return amt
}
// feeInfoForMempool returns the fee information for the passed tx type in the
// memory pool.
func feeInfoForMempool(s *rpcServer, txType stake.TxType) *dcrjson.FeeInfoMempool {
txDs := s.server.txMemPool.TxDescs()
ticketFees := make([]dcrutil.Amount, 0, len(txDs))
for _, txD := range txDs {
if txD.Type == txType {
feePerKb := (dcrutil.Amount(txD.Fee)) * 1000 /
dcrutil.Amount(txD.Tx.MsgTx().SerializeSize())
ticketFees = append(ticketFees, feePerKb)
}
}
return &dcrjson.FeeInfoMempool{
Number: uint32(len(ticketFees)),
Min: min(ticketFees).ToCoin(),
Max: max(ticketFees).ToCoin(),
Mean: mean(ticketFees).ToCoin(),
Median: median(ticketFees).ToCoin(),
StdDev: stdDev(ticketFees).ToCoin(),
}
}
// calcFee calculates the fee of a transaction that has its fraud proofs
// properly set.
func calcFeePerKb(tx *dcrutil.Tx) dcrutil.Amount {
var in dcrutil.Amount
for _, txIn := range tx.MsgTx().TxIn {
in += dcrutil.Amount(txIn.ValueIn)
}
var out dcrutil.Amount
for _, txOut := range tx.MsgTx().TxOut {
out += dcrutil.Amount(txOut.Value)
}
return ((in - out) * 1000) / dcrutil.Amount(tx.MsgTx().SerializeSize())
}
// feeInfoForBlock fetches the ticket fee information for a given tx type in a
// block.
func ticketFeeInfoForBlock(s *rpcServer, height int64, txType stake.TxType) (*dcrjson.FeeInfoBlock, error) {
bl, err := s.chain.BlockByHeight(height)
if err != nil {
return nil, err
}
txNum := 0
switch txType {
case stake.TxTypeRegular:
txNum = len(bl.MsgBlock().Transactions) - 1
case stake.TxTypeSStx:
txNum = int(bl.MsgBlock().Header.FreshStake)
case stake.TxTypeSSGen:
txNum = int(bl.MsgBlock().Header.Voters)
case stake.TxTypeSSRtx:
txNum = int(bl.MsgBlock().Header.Revocations)
}
txFees := make([]dcrutil.Amount, txNum)
itr := 0
if txType == stake.TxTypeRegular {
for i, tx := range bl.Transactions() {
// Skip the coin base.
if i == 0 {
continue
}
txFees[itr] = calcFeePerKb(tx)
itr++
}
} else {
for _, stx := range bl.STransactions() {
thisTxType := stake.DetermineTxType(stx.MsgTx())
if thisTxType == txType {
txFees[itr] = calcFeePerKb(stx)
itr++
}
}
}
return &dcrjson.FeeInfoBlock{
Height: uint32(height),
Number: uint32(txNum),
Min: min(txFees).ToCoin(),
Max: max(txFees).ToCoin(),
Mean: mean(txFees).ToCoin(),
Median: median(txFees).ToCoin(),
StdDev: stdDev(txFees).ToCoin(),
}, nil
}
// ticketFeeInfoForRange fetches the ticket fee information for a given range
// from [start, end).
func ticketFeeInfoForRange(s *rpcServer, start int64, end int64, txType stake.TxType) (*dcrjson.FeeInfoWindow, error) {
hashes, err := s.chain.HeightRange(start, end)
if err != nil {
return nil, err
}
var txFees []dcrutil.Amount
for i := range hashes {
bl, err := s.chain.BlockByHash(&hashes[i])
if err != nil {
return nil, err
}
if txType == stake.TxTypeRegular {
for i, tx := range bl.Transactions() {
// Skip the coin base.
if i == 0 {
continue
}
txFees = append(txFees, calcFeePerKb(tx))
}
} else {
for _, stx := range bl.STransactions() {
thisTxType := stake.DetermineTxType(stx.MsgTx())
if thisTxType == txType {
txFees = append(txFees, calcFeePerKb(stx))
}
}
}
}
return &dcrjson.FeeInfoWindow{
StartHeight: uint32(start),
EndHeight: uint32(end),
Number: uint32(len(txFees)),
Min: min(txFees).ToCoin(),
Max: max(txFees).ToCoin(),
Mean: mean(txFees).ToCoin(),
Median: median(txFees).ToCoin(),
StdDev: stdDev(txFees).ToCoin(),
}, nil
}
// handleTicketFeeInfo implements the ticketfeeinfo command.
func handleTicketFeeInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.TicketFeeInfoCmd)
s.server.blockManager.chainState.Lock()
bestHeight := s.server.blockManager.chainState.newestHeight
s.server.blockManager.chainState.Unlock()
// Memory pool first.
feeInfoMempool := feeInfoForMempool(s, stake.TxTypeSStx)
// Blocks requested, descending from the chain tip.
var feeInfoBlocks []dcrjson.FeeInfoBlock
blocks := uint32(0)
if c.Blocks != nil {
blocks = *c.Blocks
}
if blocks > 0 {
start := bestHeight
end := bestHeight - int64(blocks)
for i := start; i > end; i-- {
feeInfo, err := ticketFeeInfoForBlock(s, i, stake.TxTypeSStx)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain ticket fee info")
}
feeInfoBlocks = append(feeInfoBlocks, *feeInfo)
}
}
var feeInfoWindows []dcrjson.FeeInfoWindow
windows := uint32(0)
if c.Windows != nil {
windows = *c.Windows
}
if windows > 0 {
// The first window is special because it may not be finished.
// Perform this first and return if it's the only window the
// user wants. Otherwise, append and continue.
winLen := s.server.chainParams.StakeDiffWindowSize
lastChange := (bestHeight / winLen) * winLen
feeInfo, err := ticketFeeInfoForRange(s, lastChange, bestHeight+1,
stake.TxTypeSStx)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain ticket fee info")
}
feeInfoWindows = append(feeInfoWindows, *feeInfo)
// We need data on windows from before this. Start from
// the last adjustment and move backwards through window
// lengths, calulating the fees data and appending it
// each time.
if windows > 1 {
// Go down to the last height requested, except
// in the case that the user has specified to
// many windows. In that case, just proceed to the
// first block.
end := int64(-1)
if lastChange-int64(windows)*winLen > end {
end = lastChange - int64(windows)*winLen
}
for i := lastChange; i > end+winLen; i -= winLen {
feeInfo, err := ticketFeeInfoForRange(s, i-winLen, i,
stake.TxTypeSStx)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain ticket fee info")
}
feeInfoWindows = append(feeInfoWindows, *feeInfo)
}
}
}
return &dcrjson.TicketFeeInfoResult{
FeeInfoMempool: *feeInfoMempool,
FeeInfoBlocks: feeInfoBlocks,
FeeInfoWindows: feeInfoWindows,
}, nil
}
// handleTicketsForAddress implements the ticketsforaddress command.
func handleTicketsForAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.TicketsForAddressCmd)
addr, err := dcrutil.DecodeAddress(c.Address)
if err != nil {
return nil, rpcInvalidError("Invalid address: %v", err)
}
tickets, err := s.server.blockManager.chain.TicketsWithAddress(addr)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain tickets")
}
ticketStrings := make([]string, len(tickets))
itr := 0
for _, ticket := range tickets {
ticketStrings[itr] = ticket.String()
itr++
}
reply := &dcrjson.TicketsForAddressResult{
Tickets: ticketStrings,
}
return reply, nil
}
// handleTicketVWAP implements the ticketvwap command.
func handleTicketVWAP(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.TicketVWAPCmd)
// The default VWAP is for the past WorkDiffWindows * WorkDiffWindowSize
// many blocks.
_, bestHeight := s.server.blockManager.chainState.Best()
var start uint32
if c.Start == nil {
toEval := activeNetParams.WorkDiffWindows *
activeNetParams.WorkDiffWindowSize
startI64 := bestHeight - toEval
// Use 1 as the first block if there aren't
// enough blocks.
if startI64 <= 0 {
start = 1
} else {
start = uint32(startI64)
}
} else {
start = *c.Start
}
end := uint32(bestHeight)
if c.End != nil {
end = *c.End
}
if start > end {
return nil, rpcInvalidError("Start height %v is beyond end "+
"height %v", start, end)
}
if end > uint32(bestHeight) {
return nil, rpcInvalidError("End height %v is beyond "+
"blockchain tip height %v", end, bestHeight)
}
// Calculate the volume weighted average price of a ticket for the
// given range.
ticketNum := int64(0)
totalValue := int64(0)
for i := start; i <= end; i++ {
blockHeader, err := s.chain.HeaderByHeight(int64(i))
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain header")
}
ticketNum += int64(blockHeader.FreshStake)
totalValue += blockHeader.SBits * int64(blockHeader.FreshStake)
}
vwap := int64(0)
if ticketNum > 0 {
vwap = totalValue / ticketNum
}
return dcrutil.Amount(vwap).ToCoin(), nil
}
// handleTxFeeInfo implements the txfeeinfo command.
func handleTxFeeInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.TxFeeInfoCmd)
s.server.blockManager.chainState.Lock()
bestHeight := s.server.blockManager.chainState.newestHeight
s.server.blockManager.chainState.Unlock()
// Memory pool first.
feeInfoMempool := feeInfoForMempool(s, stake.TxTypeRegular)
// Blocks requested, descending from the chain tip.
var feeInfoBlocks []dcrjson.FeeInfoBlock
blocks := uint32(0)
if c.Blocks != nil {
blocks = *c.Blocks
}
if blocks > 0 {
start := bestHeight
end := bestHeight - int64(blocks)
for i := start; i > end; i-- {
feeInfo, err := ticketFeeInfoForBlock(s, i,
stake.TxTypeRegular)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain ticket fee info")
}
feeInfoBlocks = append(feeInfoBlocks, *feeInfo)
}
}
// Get the fee info for the range requested, unless none is given. The
// default range is for the past WorkDiffWindowSize many blocks.
var feeInfoRange dcrjson.FeeInfoRange
var start uint32
if c.RangeStart == nil {
toEval := activeNetParams.WorkDiffWindowSize
startI64 := bestHeight - toEval
// Use 1 as the first block if there aren't enough blocks.
if startI64 <= 0 {
start = 1
} else {
start = uint32(startI64)
}
} else {
start = *c.RangeStart
}
end := uint32(bestHeight)
if c.RangeEnd != nil {
end = *c.RangeEnd
}
if start > end {
return nil, rpcInvalidError("Start height %v is beyond end "+
"height %v", start, end)
}
if end > uint32(bestHeight) {
return nil, rpcInvalidError("End height %v is beyond "+
"blockchain tip height %v", end, bestHeight)
}
feeInfo, err := ticketFeeInfoForRange(s, int64(start), int64(end+1),
stake.TxTypeRegular)
if err != nil {
return nil, rpcInternalError(err.Error(),
"Could not obtain ticket fee info")
}
feeInfoRange = dcrjson.FeeInfoRange{
Number: feeInfo.Number,
Min: feeInfo.Min,
Max: feeInfo.Max,
Mean: feeInfo.Mean,
Median: feeInfo.Median,
StdDev: feeInfo.StdDev,
}
return &dcrjson.TxFeeInfoResult{
FeeInfoMempool: *feeInfoMempool,
FeeInfoBlocks: feeInfoBlocks,
FeeInfoRange: feeInfoRange,
}, nil
}
// handleValidateAddress implements the validateaddress command.
func handleValidateAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.ValidateAddressCmd)
result := dcrjson.ValidateAddressChainResult{}
addr, err := dcrutil.DecodeAddress(c.Address)
if err != nil || !addr.IsForNet(s.server.chainParams) {
// Return the default value (false) for IsValid.
return result, nil
}
result.Address = addr.EncodeAddress()
result.IsValid = true
return result, nil
}
func verifyChain(s *rpcServer, level, depth int64) error {
best := s.chain.BestSnapshot()
finishHeight := best.Height - depth
if finishHeight < 0 {
finishHeight = 0
}
rpcsLog.Infof("Verifying chain for %d blocks at level %d",
best.Height-finishHeight, level)
for height := best.Height; height > finishHeight; height-- {
// Level 0 just looks up the block.
block, err := s.chain.BlockByHeight(height)
if err != nil {
rpcsLog.Errorf("Verify is unable to fetch block at "+
"height %d: %v", height, err)
return err
}
// Level 1 does basic chain sanity checks.
if level > 0 {
err := blockchain.CheckBlockSanity(block,
s.server.timeSource,
activeNetParams.Params)
if err != nil {
rpcsLog.Errorf("Verify is unable to validate "+
"block at hash %v height %d: %v",
block.Hash(), 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.(*dcrjson.VerifyChainCmd)
var checkLevel, checkDepth int64
if c.CheckLevel != nil {
checkLevel = *c.CheckLevel
}
if c.CheckDepth != nil {
checkDepth = *c.CheckDepth
}
err := verifyChain(s, checkLevel, checkDepth)
return err == nil, nil
}
// handleVerifyMessage implements the verifymessage command.
func handleVerifyMessage(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*dcrjson.VerifyMessageCmd)
// Decode the provided address.
addr, err := dcrutil.DecodeAddress(c.Address)
if err != nil {
return nil, rpcAddressKeyError("Could not decode address: %v",
err)
}
// Only P2PKH addresses are valid for signing.
if _, ok := addr.(*dcrutil.AddressPubKeyHash); !ok {
return nil, &dcrjson.RPCError{
Code: dcrjson.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, &dcrjson.RPCError{
Code: dcrjson.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.
var buf bytes.Buffer
wire.WriteVarString(&buf, 0, "Decred Signed Message:\n")
wire.WriteVarString(&buf, 0, c.Message)
expectedMessageHash := chainhash.HashB(buf.Bytes())
pk, wasCompressed, err := chainec.Secp256k1.RecoverCompact(sig,
expectedMessageHash)
if err != nil {
// Mirror Bitcoin Core behavior, which treats error in
// RecoverCompact as invalid signature.
return false, nil
}
// Reconstruct the pubkey hash.
dcrPK := pk
var serializedPK []byte
if wasCompressed {
serializedPK = dcrPK.SerializeCompressed()
} else {
serializedPK = dcrPK.SerializeUncompressed()
}
address, err := dcrutil.NewAddressSecpPubKey(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
}
// handleVersion implements the version command.
func handleVersion(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
runtimeVer := strings.Replace(runtime.Version(), ".", "-", -1)
buildMeta := normalizeBuildString(runtimeVer)
if build := normalizeBuildString(appBuild); build != "" {
buildMeta = fmt.Sprintf("%s.%s", build, buildMeta)
}
result := map[string]dcrjson.VersionResult{
"dcrdjsonrpcapi": {
VersionString: jsonrpcSemverString,
Major: jsonrpcSemverMajor,
Minor: jsonrpcSemverMinor,
Patch: jsonrpcSemverPatch,
},
"dcrd": {
VersionString: version(),
Major: uint32(appMajor),
Minor: uint32(appMinor),
Patch: uint32(appPatch),
Prerelease: normalizePreRelString(appPreRelease),
BuildMetadata: buildMeta,
},
}
return result, nil
}
// rpcServer holds the items the rpc server may need to access (config,
// shutdown, main server, etc.)
type rpcServer struct {
started int32
shutdown int32
policy *mining.Policy
server *server
chain *blockchain.BlockChain
authsha [sha256.Size]byte
limitauthsha [sha256.Size]byte
ntfnMgr *wsNotificationManager
numClients int32
statusLines map[int]string
statusLock sync.RWMutex
wg sync.WaitGroup
listeners []net.Listener
workState *workState
gbtWorkState *gbtWorkState
templatePool map[[merkleRootPairSize]byte]*workStateBlockInfo
helpCacher *helpCacher
requestProcessShutdown chan struct{}
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")
return err
}
// 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
}
// RequestedProcessShutdown returns a channel that is sent to when an
// authorized RPC client requests the process to shutdown. If the request can
// not be read immediately, it is dropped.
func (s *rpcServer) RequestedProcessShutdown() <-chan struct{} {
return s.requestProcessShutdown
}
// 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 := sha256.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 {
jsonrpc string
id interface{}
method string
cmd interface{}
err *dcrjson.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, dcrjson.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 *dcrjson.Request) *parsedRPCCmd {
parsedCmd := parsedRPCCmd{
jsonrpc: request.Jsonrpc,
id: request.ID,
method: request.Method,
}
cmd, err := dcrjson.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.(dcrjson.Error); ok &&
jerr.Code == dcrjson.ErrUnregisteredMethod {
parsedCmd.err = dcrjson.ErrRPCMethodNotFound
return &parsedCmd
}
// Otherwise, some type of invalid parameters is the cause, so
// produce the equivalent RPC error.
parsedCmd.err = rpcInvalidError("Failed to parse request: %v", err)
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 *dcrjson.RPCError to the appropriate type as needed.
func createMarshalledReply(rpcVersion string, id interface{}, result interface{}, replyErr error) ([]byte, error) {
var jsonErr *dcrjson.RPCError
if replyErr != nil {
if jErr, ok := replyErr.(*dcrjson.RPCError); ok {
jsonErr = jErr
} else {
jsonErr = rpcInternalError(replyErr.Error(), "")
}
}
return dcrjson.MarshalResponse(rpcVersion, id, result, jsonErr)
}
// processRequest determines the incoming request type (single or batched),
// parses it and returns a marshalled response.
func (s *rpcServer) processRequest(request *dcrjson.Request, isAdmin bool, closeChan <-chan struct{}) []byte {
var result interface{}
var jsonErr error
if !isAdmin {
if _, ok := rpcLimited[request.Method]; !ok {
jsonErr = rpcInvalidError("limited user not " +
"authorized for this method")
}
}
if jsonErr == nil {
if request.Method == "" || request.Params == nil {
jsonErr = &dcrjson.RPCError{
Code: dcrjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprintf("Invalid request: malformed"),
}
msg, err := createMarshalledReply(request.Jsonrpc, request.ID, result, jsonErr)
if err != nil {
rpcsLog.Errorf("Failed to marshal reply: %v", err)
return nil
}
return msg
}
// Valid requests with no ID (notifications) must not have a response
// per the JSON-RPC spec.
if request.ID == nil {
return 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(request.Jsonrpc, request.ID, result, jsonErr)
if err != nil {
rpcsLog.Errorf("Failed to marshal reply: %v", err)
return nil
}
return msg
}
// 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.Itoa(errCode)+" "+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.Itoa(errCode)+" "+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.Itoa(errCode)+" "+
err.Error(), errCode)
return
}
defer conn.Close()
defer buf.Flush()
conn.SetReadDeadline(timeZeroVal)
// 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)
}
}()
var results []json.RawMessage
var batchSize int
var batchedRequest bool
// Determine request type
if bytes.HasPrefix(body, batchedRequestPrefix) {
batchedRequest = true
}
// Process a single request
if !batchedRequest {
var req dcrjson.Request
var resp json.RawMessage
err = json.Unmarshal(body, &req)
if err != nil {
jsonErr := &dcrjson.RPCError{
Code: dcrjson.ErrRPCParse.Code,
Message: fmt.Sprintf("Failed to parse request: %v",
err),
}
resp, err = dcrjson.MarshalResponse("1.0", nil, nil, jsonErr)
if err != nil {
rpcsLog.Errorf("Failed to create reply: %v", err)
}
}
if err == nil {
resp = s.processRequest(&req, isAdmin, closeChan)
}
if resp != nil {
results = append(results, resp)
}
}
// Process a batched request
if batchedRequest {
var batchedRequests []interface{}
var resp json.RawMessage
err = json.Unmarshal(body, &batchedRequests)
if err != nil {
jsonErr := &dcrjson.RPCError{
Code: dcrjson.ErrRPCParse.Code,
Message: fmt.Sprintf("Failed to parse request: %v",
err),
}
resp, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
if err != nil {
rpcsLog.Errorf("Failed to create reply: %v", err)
}
if resp != nil {
results = append(results, resp)
}
}
if err == nil {
// Response with an empty batch error if the batch size is zero
if len(batchedRequests) == 0 {
jsonErr := &dcrjson.RPCError{
Code: dcrjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprint("Invalid request: empty batch"),
}
resp, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
if err != nil {
rpcsLog.Errorf("Failed to marshal reply: %v", err)
}
if resp != nil {
results = append(results, resp)
}
}
// Process each batch entry individually
if len(batchedRequests) > 0 {
batchSize = len(batchedRequests)
for _, entry := range batchedRequests {
var reqBytes []byte
reqBytes, err = json.Marshal(entry)
if err != nil {
jsonErr := &dcrjson.RPCError{
Code: dcrjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprintf("Invalid request: %v",
err),
}
resp, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
if err != nil {
rpcsLog.Errorf("Failed to create reply: %v", err)
}
if resp != nil {
results = append(results, resp)
}
continue
}
var req dcrjson.Request
err := json.Unmarshal(reqBytes, &req)
if err != nil {
jsonErr := &dcrjson.RPCError{
Code: dcrjson.ErrRPCInvalidRequest.Code,
Message: fmt.Sprintf("Invalid request: %v",
err),
}
resp, err = dcrjson.MarshalResponse("", nil, nil, jsonErr)
if err != nil {
rpcsLog.Errorf("Failed to create reply: %v", err)
}
if resp != nil {
results = append(results, resp)
}
continue
}
resp = s.processRequest(&req, isAdmin, closeChan)
if resp != nil {
results = append(results, resp)
}
}
}
}
}
var msg = []byte{}
if batchedRequest && batchSize > 0 {
if len(results) > 0 {
// Form the batched response json
var buffer bytes.Buffer
buffer.WriteByte('[')
for idx, reply := range results {
if idx == len(results)-1 {
buffer.Write(reply)
buffer.WriteByte(']')
break
}
buffer.Write(reply)
buffer.WriteByte(',')
}
msg = buffer.Bytes()
}
}
if !batchedRequest || batchSize == 0 {
// Respond with the first results entry for single requests
if len(results) > 0 {
msg = results[0]
}
}
// 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)
}
// Terminate with newline to maintain compatibility with Bitcoin Core.
if err := buf.WriteByte('\n'); err != nil {
rpcsLog.Errorf("Failed to append terminating newline to 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="dcrd 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 := "dcrd autogenerated cert"
validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
cert, key, err := certgen.NewTLSCertPair(elliptic.P521(), org,
validUntil, nil)
if err != nil {
return err
}
// Write cert and key files.
if err = ioutil.WriteFile(certFile, cert, 0644); 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, policy *mining.Policy, s *server) (*rpcServer, error) {
rpc := rpcServer{
policy: policy,
server: s,
chain: s.blockManager.chain,
statusLines: make(map[int]string),
workState: newWorkState(),
templatePool: make(map[[merkleRootPairSize]byte]*workStateBlockInfo),
gbtWorkState: newGbtWorkState(s.timeSource),
helpCacher: newHelpCacher(),
requestProcessShutdown: make(chan struct{}),
quit: make(chan int),
}
if cfg.RPCUser != "" && cfg.RPCPass != "" {
login := cfg.RPCUser + ":" + cfg.RPCPass
auth := "Basic " +
base64.StdEncoding.EncodeToString([]byte(login))
rpc.authsha = sha256.Sum256([]byte(auth))
}
if cfg.RPCLimitUser != "" && cfg.RPCLimitPass != "" {
login := cfg.RPCLimitUser + ":" + cfg.RPCLimitPass
auth := "Basic " +
base64.StdEncoding.EncodeToString([]byte(login))
rpc.limitauthsha = sha256.Sum256([]byte(auth))
}
rpc.ntfnMgr = newWsNotificationManager(&rpc)
// Setup TLS if not disabled.
listenFunc := net.Listen
if !cfg.DisableRPC && !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())
// blake256Pad is the extra blake256 internal padding needed for the
// data of the getwork RPC. The internal blake256 padding consists of
// a single 1 bit followed by zeros and a final 1 bit in order 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 blake256 block size (64 bytes). Since
// the block header is a fixed size, it only needs to be calculated
// once.
blake256Pad = make([]byte, getworkDataLen-wire.MaxBlockHeaderPayload)
blake256Pad[0] = 0x80
blake256Pad[len(blake256Pad)-9] |= 0x01
binary.BigEndian.PutUint64(blake256Pad[len(blake256Pad)-8:],
wire.MaxBlockHeaderPayload*8)
}