mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
this updates the function signatures of minimumMedianTime and medianAdjustedTime and removes all chainState usage in rpcserver.go in favour of using BestState. Port of upstream commit 2274d36 (subset). In preparation of the removal of chainState.
6550 lines
203 KiB
Go
6550 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/chainhash"
|
|
"github.com/decred/dcrd/database"
|
|
"github.com/decred/dcrd/dcrec/secp256k1"
|
|
"github.com/decred/dcrd/dcrjson"
|
|
"github.com/decred/dcrd/dcrutil"
|
|
"github.com/decred/dcrd/internal/version"
|
|
"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": {},
|
|
"fundrawtransaction": {},
|
|
"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:
|
|
case *dcrutil.AddressScriptHash:
|
|
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)
|
|
|
|
best := s.server.blockManager.chain.BestSnapshot()
|
|
stakeVoteSubsidy := blockchain.CalcStakeVoteSubsidy(
|
|
s.chain.FetchSubsidyCache(), best.Height, 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(best.Hash,
|
|
uint32(best.Height))
|
|
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.chain.BestSnapshot().Height
|
|
lastAdjustment := (bestHeight / activeNetParams.StakeDiffWindowSize) *
|
|
activeNetParams.StakeDiffWindowSize
|
|
nextAdjustment := ((bestHeight / activeNetParams.StakeDiffWindowSize) +
|
|
1) * activeNetParams.StakeDiffWindowSize
|
|
totalTickets := 0
|
|
for i := lastAdjustment; i <= bestHeight; i++ {
|
|
bh, err := chain.HeaderByHeight(i)
|
|
if err != nil {
|
|
return nil, rpcInternalError(err.Error(), "Could not "+
|
|
"estimate next stake difficulty")
|
|
}
|
|
totalTickets += int(bh.FreshStake)
|
|
}
|
|
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.BlockByHash(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()
|
|
|
|
// Get next block hash unless there are none.
|
|
var nextHashString string
|
|
blockHeader := &blk.MsgBlock().Header
|
|
confirmations := int64(-1)
|
|
if s.chain.MainChainHasBlock(hash) {
|
|
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()
|
|
|
|
// Get next block hash unless there are none.
|
|
var nextHashString string
|
|
confirmations := int64(-1)
|
|
height := int64(blockHeader.Height)
|
|
if s.chain.MainChainHasBlock(hash) {
|
|
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
|
|
best := s.server.blockManager.chain.BestSnapshot()
|
|
latestHash := best.Hash
|
|
template := state.template
|
|
if template == nil || state.prevHash == nil ||
|
|
!state.prevHash.IsEqual(&best.Hash) ||
|
|
(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.
|
|
minTimestamp := minimumMedianTime(best)
|
|
|
|
// 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(&best.Hash, 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.
|
|
bestHeight := s.server.blockManager.chain.BestSnapshot().Height
|
|
if bestHeight != 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.chain.BestSnapshot().Hash
|
|
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, rpcInvalidError("Unknown filter type %q",
|
|
c.FilterType)
|
|
}
|
|
|
|
filterBytes, err := s.server.cfIndex.FilterByBlockHash(hash, filterType)
|
|
if err != nil {
|
|
context := fmt.Sprintf("Failed to load %v filter for block %v",
|
|
filterType, hash)
|
|
return "", rpcInternalError(err.Error(), context)
|
|
}
|
|
if len(filterBytes) == 0 {
|
|
return nil, &dcrjson.RPCError{
|
|
Code: dcrjson.ErrRPCBlockNotFound,
|
|
Message: fmt.Sprintf("Block not found: %v", hash),
|
|
}
|
|
}
|
|
|
|
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, rpcInvalidError("Unknown filter type %q",
|
|
c.FilterType)
|
|
}
|
|
|
|
headerBytes, err := s.server.cfIndex.FilterHeaderByBlockHash(hash, filterType)
|
|
if err != nil {
|
|
context := fmt.Sprintf("Failed to load %v filter header for block %v",
|
|
filterType, hash)
|
|
return "", rpcInternalError(err.Error(), context)
|
|
}
|
|
if bytes.Equal(headerBytes, zeroHash[:]) && *hash !=
|
|
*s.server.chainParams.GenesisHash {
|
|
|
|
return nil, &dcrjson.RPCError{
|
|
Code: dcrjson.ErrRPCBlockNotFound,
|
|
Message: fmt.Sprintf("Block not found: %v", hash),
|
|
}
|
|
}
|
|
|
|
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*version.Major + 10000*version.Minor +
|
|
100*version.Patch),
|
|
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
|
|
var blkIndex uint32
|
|
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 details.
|
|
blkHash = blockRegion.Hash
|
|
blkHeight, err = s.chain.BlockHeightByHash(blkHash)
|
|
if err != nil {
|
|
context := "Failed to retrieve block height"
|
|
return nil, rpcInternalError(err.Error(), context)
|
|
}
|
|
blkIndex = wire.NullBlockIndex // TODO: Update txindex to provide.
|
|
|
|
// 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(),
|
|
blkIndex, 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()
|
|
best := s.server.blockManager.chain.BestSnapshot()
|
|
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(&best.Hash) ||
|
|
(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(&best.Hash) {
|
|
state.extraNonce = 0
|
|
pruneOldBlockTemplates(s, best.Height)
|
|
}
|
|
|
|
// 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 = &best.Hash
|
|
|
|
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, best.Height+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.
|
|
bestHeight := s.server.blockManager.chain.BestSnapshot().Height
|
|
if bestHeight != 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) {
|
|
best := s.server.blockManager.chain.BestSnapshot()
|
|
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: best.Hash,
|
|
Height: best.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) {
|
|
bestHeight := s.server.blockManager.chain.BestSnapshot().Height
|
|
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: bestHeight,
|
|
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.
|
|
blkIndex uint32 // 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 and block index fields in the retrieved
|
|
// transaction struct are left unset.
|
|
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.
|
|
//
|
|
// TODO: Update txindex to provide block index.
|
|
for i, serializedTx := range serializedTxns {
|
|
addressTxns = append(addressTxns, retrievedTx{
|
|
txBytes: serializedTx,
|
|
blkHash: regions[i].Hash,
|
|
blkIndex: wire.NullBlockIndex,
|
|
})
|
|
}
|
|
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
|
|
result.Expiry = mtx.Expiry
|
|
|
|
// 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
|
|
var blkIndex uint32
|
|
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
|
|
blkIndex = rtx.blkIndex
|
|
}
|
|
|
|
// 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.BlockHeight = blkHeight
|
|
result.BlockIndex = blkIndex
|
|
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.
|
|
if txType := stake.DetermineTxType(msgtx); txType != stake.TxTypeSSGen {
|
|
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)
|
|
|
|
bestHeight := s.server.blockManager.chain.BestSnapshot().Height
|
|
|
|
// 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.chain.BestSnapshot().Height
|
|
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)
|
|
|
|
bestHeight := s.server.blockManager.chain.BestSnapshot().Height
|
|
|
|
// 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 := 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 := version.NormalizeBuildString(runtimeVer)
|
|
build := version.NormalizeBuildString(version.BuildMetadata)
|
|
if 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.String(),
|
|
Major: uint32(version.Major),
|
|
Minor: uint32(version.Minor),
|
|
Patch: uint32(version.Patch),
|
|
Prerelease: version.NormalizePreRelString(version.PreRelease),
|
|
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)
|
|
}
|