// 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" "sort" "strconv" "strings" "sync" "sync/atomic" "time" "github.com/btcsuite/websocket" "github.com/decred/dcrd/blockchain" "github.com/decred/dcrd/blockchain/stake" "github.com/decred/dcrd/certgen" "github.com/decred/dcrd/chaincfg" "github.com/decred/dcrd/chaincfg/chainec" "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/database" "github.com/decred/dcrd/dcrjson" "github.com/decred/dcrd/dcrutil" "github.com/decred/dcrd/mempool" "github.com/decred/dcrd/mining" "github.com/decred/dcrd/txscript" "github.com/decred/dcrd/wire" "github.com/jrick/bitset" ) // API version constants const ( jsonrpcSemverString = "3.2.0" jsonrpcSemverMajor = 3 jsonrpcSemverMinor = 2 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, "getcoinsupply": handleGetCoinSupply, "getconnectioncount": handleGetConnectionCount, "getcurrentnet": handleGetCurrentNet, "getdifficulty": handleGetDifficulty, "getgenerate": handleGetGenerate, "gethashespersec": handleGetHashesPerSec, "getheaders": handleGetHeaders, "getinfo": handleGetInfo, "getmempoolinfo": handleGetMempoolInfo, "getmininginfo": handleGetMiningInfo, "getnettotals": handleGetNetTotals, "getnetworkhashps": handleGetNetworkHashPS, "getpeerinfo": handleGetPeerInfo, "getrawmempool": handleGetRawMempool, "getrawtransaction": handleGetRawTransaction, "getstakedifficulty": handleGetStakeDifficulty, "getstakeversioninfo": handleGetStakeVersionInfo, "getstakeversions": handleGetStakeVersions, "getticketpoolvalue": handleGetTicketPoolValue, "getvoteinfo": handleGetVoteInfo, "gettxout": handleGetTxOut, "getwork": handleGetWork, "help": handleHelp, "livetickets": handleLiveTickets, "missedtickets": handleMissedTickets, "node": handleNode, "ping": handlePing, "searchrawtransactions": handleSearchRawTransactions, "rebroadcastmissed": handleRebroadcastMissed, "rebroadcastwinners": handleRebroadcastWinners, "sendrawtransaction": handleSendRawTransaction, "setgenerate": handleSetGenerate, "stop": handleStop, "submitblock": handleSubmitBlock, "ticketfeeinfo": handleTicketFeeInfo, "ticketsforaddress": handleTicketsForAddress, "ticketvwap": handleTicketVWAP, "txfeeinfo": handleTxFeeInfo, "validateaddress": handleValidateAddress, "verifychain": handleVerifyChain, "verifymessage": handleVerifyMessage, "version": handleVersion, } // list of commands that we recognize, but for which dcrd has no support because // it lacks support for wallet functionality. For these commands the user // should ask a connected instance of dcrwallet. var rpcAskWallet = map[string]struct{}{ "accountaddressindex": {}, "accountsyncaddressindex": {}, "addmultisigaddress": {}, "addticket": {}, "createencryptedwallet": {}, "createmultisig": {}, "dumpprivkey": {}, "getaccount": {}, "getaccountaddress": {}, "getaddressesbyaccount": {}, "getbalance": {}, "getnewaddress": {}, "getrawchangeaddress": {}, "getreceivedbyaccount": {}, "getreceivedbyaddress": {}, "getstakeinfo": {}, "getvotechoices": {}, "gettransaction": {}, "gettxoutsetinfo": {}, "getunconfirmedbalance": {}, "importprivkey": {}, "keypoolrefill": {}, "listaccounts": {}, "listlockunspent": {}, "listreceivedbyaccount": {}, "listreceivedbyaddress": {}, "listsinceblock": {}, "listtransactions": {}, "listunspent": {}, "lockunspent": {}, "rescanwallet": {}, "revoketickets": {}, "sendfrom": {}, "sendmany": {}, "sendtoaddress": {}, "setvotechoice": {}, "settxfee": {}, "signmessage": {}, "signrawtransaction": {}, "sweepaccount": {}, "stakepooluserinfo": {}, "walletinfo": {}, "walletlock": {}, "walletpassphrase": {}, "walletpassphrasechange": {}, } // Commands that are currently unimplemented, but should ultimately be. var rpcUnimplemented = map[string]struct{}{ "estimatefee": {}, "estimatepriority": {}, "getblocktemplate": {}, "getblockchaininfo": {}, "getchaintips": {}, "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": {}, "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 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") } prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree) txIn := wire.NewTxIn(prevOut, []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) } // 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, []byte{}) mtx.AddTxIn(txIn) } // Add all transaction outputs to the transaction after performing // some validity checks. amtTicket := int64(0) for encodedAddr, amount := range c.Amount { // Ensure amount is in the valid range for monetary amounts. if amount <= 0 || amount > dcrutil.MaxAmount { return nil, rpcInvalidError("Invalid SSTx commitment "+ "amount: 0 >= %v > %v", amount, dcrutil.MaxAmount) } // Decode the provided address. addr, err := dcrutil.DecodeAddress(encodedAddr) if err != nil { return nil, rpcAddressKeyError("Could not decode "+ "address: %v", err) } // Ensure the address is one of the supported types and that // the network encoded with the address matches the network the // server is currently on. switch addr.(type) { case *dcrutil.AddressPubKeyHash: default: return nil, rpcAddressKeyError("Invalid address type: "+ "%T", addr) } if !addr.IsForNet(s.server.chainParams) { return nil, rpcAddressKeyError("Wrong network: %v", addr) } // Create a new script which pays to the provided address with an // SStx tagged output. pkScript, err := txscript.PayToSStx(addr) if err != nil { return nil, rpcInternalError(err.Error(), "Could not create TX script") } txOut := wire.NewTxOut(amount, pkScript) mtx.AddTxOut(txOut) amtTicket += amount } // Calculated the commitment amounts, then create the // addresses and payout proportions as null data // outputs. inputAmts := make([]int64, len(c.Inputs)) for i, input := range c.Inputs { inputAmts[i] = input.Amt } changeAmts := make([]int64, len(c.COuts)) for i, cout := range c.COuts { changeAmts[i] = cout.ChangeAmt } // Check and make sure none of the change overflows // the input amounts. for i, amt := range inputAmts { if changeAmts[i] >= amt { return nil, rpcInvalidError("input %v >= amount %v", changeAmts[i], amt) } } // Obtain the commitment amounts. _, amountsCommitted, err := stake.SStxNullOutputAmounts(inputAmts, changeAmts, amtTicket) if err != nil { return nil, rpcInternalError(err.Error(), "Invalid SSTx output amounts") } for i, cout := range c.COuts { // 1. Append future commitment output. addr, err := dcrutil.DecodeAddress(cout.Addr) if err != nil { return nil, rpcAddressKeyError("Could not decode "+ "address: %v", err) } // Ensure the address is one of the supported types and that // the network encoded with the address matches the network the // server is currently on. switch addr.(type) { case *dcrutil.AddressPubKeyHash: break case *dcrutil.AddressScriptHash: break default: return nil, rpcAddressKeyError("Invalid type: %T", addr) } if !addr.IsForNet(s.server.chainParams) { return nil, rpcAddressKeyError("Wrong network: %v", addr) } // Create an OP_RETURN push containing the pubkeyhash to send // rewards to. TODO Replace 0x0000 fee limits with an argument // passed to the RPC call. pkScript, err := txscript.GenerateSStxAddrPush(addr, dcrutil.Amount(amountsCommitted[i]), 0x0000) if err != nil { return nil, rpcInternalError(err.Error(), "Could not create SStx script") } txout := wire.NewTxOut(int64(0), pkScript) mtx.AddTxOut(txout) // 2. Append change output. // Ensure amount is in the valid range for monetary amounts. if cout.ChangeAmt < 0 || cout.ChangeAmt > dcrutil.MaxAmount { return nil, rpcInvalidError("Invalid change amount: 0 "+ "> %v > %v", cout.ChangeAmt, dcrutil.MaxAmount) } // Decode the provided address. addr, err = dcrutil.DecodeAddress(cout.ChangeAddr) if err != nil { return nil, rpcAddressKeyError("Wrong network: %v", addr) } // Ensure the address is one of the supported types and that // the network encoded with the address matches the network the // server is currently on. switch addr.(type) { case *dcrutil.AddressPubKeyHash: break case *dcrutil.AddressScriptHash: break default: return nil, rpcAddressKeyError("Invalid type: %T", addr) } if !addr.IsForNet(s.server.chainParams) { return nil, rpcAddressKeyError("Wrong network: %v", addr) } // Create a new script which pays to the provided address with // an SStx change tagged output. pkScript, err = txscript.PayToSStxChange(addr) if err != nil { return nil, rpcInternalError(err.Error(), "Could not create SStx change script") } txOut := wire.NewTxOut(cout.ChangeAmt, pkScript) mtx.AddTxOut(txOut) } // Make sure we generated a valid SStx. if err := stake.CheckSStx(mtx); err != nil { return nil, rpcInternalError(err.Error(), "Invalid SStx") } // Return the serialized and hex-encoded transaction. mtxHex, err := messageToHex(mtx) if err != nil { return nil, err } return mtxHex, nil } // handleCreateRawSSGenTx handles createrawssgentx commands. func handleCreateRawSSGenTx(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.CreateRawSSGenTxCmd) // Only a single SStx should be given if len(c.Inputs) != 1 { return nil, rpcInvalidError("SSGen Tx can only have one valid" + " input") } // 1. Fetch the SStx, then calculate all the values we'll need later for // the generation of the SSGen tx outputs. // // Convert the provided transaction hash hex to a chainhash.Hash. txHash, err := chainhash.NewHashFromStr(c.Inputs[0].Txid) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCBlockNotFound, Message: fmt.Sprintf("Inputs 0 Txid: %v", err), } } // Try to fetch the ticket from the block database. ticketUtx, err := s.chain.FetchUtxoEntry(txHash) if ticketUtx == nil || err != nil { return nil, rpcNoTxInfoError(txHash) } if t := ticketUtx.TransactionType(); t != stake.TxTypeSStx { return nil, rpcDeserializationError("Invalid Tx type: %v", t) } // Store the sstx pubkeyhashes and amounts as found in the transaction // outputs. minimalOutputs := blockchain.ConvertUtxosToMinimalOutputs(ticketUtx) ssgenPayTypes, ssgenPkhs, sstxAmts, _, _, _ := stake.SStxStakeOutputInfo(minimalOutputs) // Get the current reward. blockHash, curHeight := s.server.blockManager.chainState.Best() stakeVoteSubsidy := blockchain.CalcStakeVoteSubsidy( s.chain.FetchSubsidyCache(), curHeight, activeNetParams.Params) // Calculate the output values from this data. ssgenCalcAmts := stake.CalculateRewards(sstxAmts, minimalOutputs[0].Value, stakeVoteSubsidy) // 2. Add all transaction inputs to a new transaction after performing // some validity checks. First, add the stake base, then the OP_SSTX // tagged output. mtx := wire.NewMsgTx() stakeBaseOutPoint := wire.NewOutPoint(&chainhash.Hash{}, uint32(0xFFFFFFFF), int8(0x01)) txInStakeBase := wire.NewTxIn(stakeBaseOutPoint, []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") } prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree) txIn := wire.NewTxIn(prevOut, []byte{}) mtx.AddTxIn(txIn) } // 3. Add the OP_RETURN null data pushes of the block header hash, the // block height, and votebits, then add all the OP_SSGEN tagged // outputs. // // Block reference output. blockRefScript, err := txscript.GenerateSSGenBlockRef(*blockHash, uint32(curHeight)) if err != nil { return nil, rpcInvalidError("Could not generate SSGen block "+ "reference: %v", err) } blockRefOut := wire.NewTxOut(0, blockRefScript) mtx.AddTxOut(blockRefOut) // Votebits output. blockVBScript, err := txscript.GenerateSSGenVotes(c.VoteBits) if err != nil { return nil, rpcInvalidError("Could not generate SSGen votes: "+ "%v", err) } blockVBOut := wire.NewTxOut(0, blockVBScript) mtx.AddTxOut(blockVBOut) // Add all the SSGen-tagged transaction outputs to the transaction // after performing some validity checks. for i, ssgenPkh := range ssgenPkhs { // Ensure amount is in the valid range for monetary amounts. if ssgenCalcAmts[i] <= 0 || ssgenCalcAmts[i] > dcrutil.MaxAmount { return nil, rpcInvalidError("Invalid SSGen amounts: "+ "0 >= %v > %v", ssgenCalcAmts[i], dcrutil.MaxAmount) } // Create a new script which pays to the provided address // specified in the original ticket tx. var ssgenOut []byte switch ssgenPayTypes[i] { case false: // P2PKH ssgenOut, err = txscript.PayToSSGenPKHDirect(ssgenPkh) if err != nil { return nil, rpcInvalidError("Could not generate "+ "PKH script: %v", err) } case true: // P2SH ssgenOut, err = txscript.PayToSSGenSHDirect(ssgenPkh) if err != nil { return nil, rpcInvalidError("Could not generate "+ "SHD script: %v", err) } } // Add the txout to our SSGen tx. txOut := wire.NewTxOut(ssgenCalcAmts[i], ssgenOut) mtx.AddTxOut(txOut) } // Check to make sure our SSGen was created correctly. err = stake.CheckSSGen(mtx) if err != nil { return nil, rpcInternalError(err.Error(), "Invalid SSGen") } // Return the serialized and hex-encoded transaction. mtxHex, err := messageToHex(mtx) if err != nil { return nil, err } return mtxHex, nil } // handleCreateRawSSRtx handles createrawssrtx commands. func handleCreateRawSSRtx(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.CreateRawSSRtxCmd) // Only a single SStx should be given if len(c.Inputs) != 1 { return nil, rpcInvalidError("SSRtx invalid number of inputs") } // Decode the fee as coins. var feeAmt dcrutil.Amount if c.Fee != nil { var err error feeAmt, err = dcrutil.NewAmount(*c.Fee) if err != nil { return nil, rpcInvalidError("Invalid fee amount: %v", err) } } // 1. Fetch the SStx, then calculate all the values we'll need later // for the generation of the SSGen tx outputs. // // Convert the provided transaction hash hex to a chainhash.Hash. txHash, err := chainhash.NewHashFromStr(c.Inputs[0].Txid) if err != nil { return nil, rpcDecodeHexError(c.Inputs[0].Txid) } // Try to fetch the ticket from the block database. ticketUtx, err := s.chain.FetchUtxoEntry(txHash) if ticketUtx == nil || err != nil { return nil, rpcNoTxInfoError(txHash) } if t := ticketUtx.TransactionType(); t != stake.TxTypeSStx { return nil, rpcDeserializationError("Invalid Tx type: %v", t) } // Store the sstx pubkeyhashes and amounts as found in the transaction // outputs. minimalOutputs := blockchain.ConvertUtxosToMinimalOutputs(ticketUtx) ssrtxPayTypes, ssrtxPkhs, sstxAmts, _, _, _ := stake.SStxStakeOutputInfo(minimalOutputs) // 2. Add all transaction inputs to a new transaction after performing // some validity checks; the only input for an SSRtx is an OP_SSTX tagged // output. mtx := wire.NewMsgTx() for _, input := range c.Inputs { txHash, err := chainhash.NewHashFromStr(input.Txid) if err != nil { return nil, rpcDecodeHexError(input.Txid) } if input.Vout < 0 { return nil, rpcInvalidError("Vout must be positive") } if !(input.Tree == wire.TxTreeStake) { return nil, rpcInvalidError("Input tree is not " + "TxTreeStake type") } prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree) txIn := wire.NewTxIn(prevOut, []byte{}) mtx.AddTxIn(txIn) } // 3. Add all the OP_SSRTX tagged outputs. // Calculate the output values from this data. ssrtxCalcAmts := stake.CalculateRewards(sstxAmts, minimalOutputs[0].Value, 0) // No subsidy for a revocation // Add all the SSRtx-tagged transaction outputs to the transaction after // performing some validity checks. feeApplied := false for i, ssrtxPkh := range ssrtxPkhs { // Ensure amount is in the valid range for monetary amounts. if sstxAmts[i] <= 0 || sstxAmts[i] > dcrutil.MaxAmount { return nil, rpcInvalidError("Invalid SSTx amount: 0 >="+ " %v > %v", sstxAmts[i] <= 0, dcrutil.MaxAmount) } // Create a new script which pays to the provided address specified in // the original ticket tx. var ssrtxOutScript []byte switch ssrtxPayTypes[i] { case false: // P2PKH ssrtxOutScript, err = txscript.PayToSSRtxPKHDirect(ssrtxPkh) if err != nil { return nil, rpcInvalidError("Could not "+ "generate PKH script: %v", err) } case true: // P2SH ssrtxOutScript, err = txscript.PayToSSRtxSHDirect(ssrtxPkh) if err != nil { return nil, rpcInvalidError("Could not "+ "generate SHD script: %v", err) } } // Add the txout to our SSGen tx. amt := ssrtxCalcAmts[i] if !feeApplied && int64(feeAmt) < amt { amt -= int64(feeAmt) feeApplied = true } txOut := wire.NewTxOut(amt, ssrtxOutScript) mtx.AddTxOut(txOut) } // Check to make sure our SSRtx was created correctly. err = stake.CheckSSRtx(mtx) if err != nil { return nil, rpcInternalError(err.Error(), "Invalid SSRtx") } // Return the serialized and hex-encoded transaction. mtxHex, err := messageToHex(mtx) if err != nil { return nil, err } return mtxHex, nil } // handleDebugLevel handles debuglevel commands. func handleDebugLevel(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.DebugLevelCmd) // Special show command to list supported subsystems. if c.LevelSpec == "show" { return fmt.Sprintf("Supported subsystems %v", supportedSubsystems()), nil } err := parseAndSetDebugLevels(c.LevelSpec) if err != nil { return nil, rpcInvalidError("Invalid debug level %v: %v", c.LevelSpec, err) } return "Done.", nil } // createVinList returns a slice of JSON objects for the inputs of the passed // transaction. func createVinList(mtx *wire.MsgTx) []dcrjson.Vin { // Coinbase transactions only have a single txin by definition. vinList := make([]dcrjson.Vin, len(mtx.TxIn)) if blockchain.IsCoinBaseTx(mtx) { txIn := mtx.TxIn[0] vinEntry := &vinList[0] vinEntry.Coinbase = hex.EncodeToString(txIn.SignatureScript) vinEntry.Sequence = txIn.Sequence vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() vinEntry.BlockHeight = txIn.BlockHeight vinEntry.BlockIndex = txIn.BlockIndex return vinList } // Stakebase transactions (votes) have two inputs: a null stake base // followed by an input consuming a ticket's stakesubmission. isSSGen := stake.IsSSGen(mtx) for i, txIn := range mtx.TxIn { // Handle only the null input of a stakebase differently. if isSSGen && i == 0 { vinEntry := &vinList[0] vinEntry.Stakebase = hex.EncodeToString(txIn.SignatureScript) vinEntry.Sequence = txIn.Sequence vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() vinEntry.BlockHeight = txIn.BlockHeight vinEntry.BlockIndex = txIn.BlockIndex continue } // The disassembled string will contain [error] inline // if the script doesn't fully parse, so ignore the // error here. disbuf, _ := txscript.DisasmString(txIn.SignatureScript) vinEntry := &vinList[i] vinEntry.Txid = txIn.PreviousOutPoint.Hash.String() vinEntry.Vout = txIn.PreviousOutPoint.Index vinEntry.Tree = txIn.PreviousOutPoint.Tree vinEntry.Sequence = txIn.Sequence vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() vinEntry.BlockHeight = txIn.BlockHeight vinEntry.BlockIndex = txIn.BlockIndex vinEntry.ScriptSig = &dcrjson.ScriptSig{ Asm: disbuf, Hex: hex.EncodeToString(txIn.SignatureScript), } } return vinList } // createVoutList returns a slice of JSON objects for the outputs of the passed // transaction. func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap map[string]struct{}) []dcrjson.Vout { txType := stake.DetermineTxType(mtx) voutList := make([]dcrjson.Vout, 0, len(mtx.TxOut)) for i, v := range mtx.TxOut { // The disassembled string will contain [error] inline if the // script doesn't fully parse, so ignore the error here. disbuf, _ := txscript.DisasmString(v.PkScript) // Attempt to extract addresses from the public key script. In // the case of stake submission transactions, the odd outputs // contain a commitment address, so detect that case // accordingly. var addrs []dcrutil.Address var scriptClass string var reqSigs int var commitAmt *dcrutil.Amount if txType == stake.TxTypeSStx && (i%2 != 0) { scriptClass = sstxCommitmentString addr, err := stake.AddrFromSStxPkScrCommitment(v.PkScript, chainParams) if err != nil { rpcsLog.Warnf("failed to decode ticket "+ "commitment addr output for tx hash "+ "%v, output idx %v", mtx.TxHash(), i) } else { addrs = []dcrutil.Address{addr} } amt, err := stake.AmountFromSStxPkScrCommitment(v.PkScript) if err != nil { rpcsLog.Warnf("failed to decode ticket "+ "commitment amt output for tx hash %v"+ ", output idx %v", mtx.TxHash(), i) } else { commitAmt = &amt } } else { // Ignore the error here since an error means the script // couldn't parse and there is no additional information // about it anyways. var sc txscript.ScriptClass sc, addrs, reqSigs, _ = txscript.ExtractPkScriptAddrs( v.Version, v.PkScript, chainParams) scriptClass = sc.String() } // Encode the addresses while checking if the address passes the // filter when needed. passesFilter := len(filterAddrMap) == 0 encodedAddrs := make([]string, len(addrs)) for j, addr := range addrs { encodedAddr := addr.EncodeAddress() encodedAddrs[j] = encodedAddr // No need to check the map again if the filter already // passes. if passesFilter { continue } if _, exists := filterAddrMap[encodedAddr]; exists { passesFilter = true } } if !passesFilter { continue } var vout dcrjson.Vout voutSPK := &vout.ScriptPubKey vout.N = uint32(i) vout.Value = dcrutil.Amount(v.Value).ToCoin() vout.Version = v.Version voutSPK.Addresses = encodedAddrs voutSPK.Asm = disbuf voutSPK.Hex = hex.EncodeToString(v.PkScript) voutSPK.Type = scriptClass voutSPK.ReqSigs = int32(reqSigs) if commitAmt != nil { voutSPK.CommitAmt = dcrjson.Float64(commitAmt.ToCoin()) } voutList = append(voutList, vout) } return voutList } // createTxRawResult converts the passed transaction and associated parameters // to a raw transaction JSON object. func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, txHash string, blkIdx uint32, blkHeader *wire.BlockHeader, blkHash string, blkHeight int64, confirmations int64) (*dcrjson.TxRawResult, error) { mtxHex, err := messageToHex(mtx) if err != nil { return nil, err } if txHash != mtx.TxHash().String() { return nil, rpcInvalidError("Tx hash does not match: got %v "+ "expected %v", txHash, mtx.TxHash()) } txReply := &dcrjson.TxRawResult{ Hex: mtxHex, Txid: txHash, Vin: createVinList(mtx), Vout: createVoutList(mtx, chainParams, nil), Version: int32(mtx.Version), LockTime: mtx.LockTime, Expiry: mtx.Expiry, BlockHeight: blkHeight, BlockIndex: blkIdx, } if blkHeader != nil { // This is not a typo, they are identical in bitcoind as well. txReply.Time = blkHeader.Timestamp.Unix() txReply.Blocktime = blkHeader.Timestamp.Unix() txReply.BlockHash = blkHash txReply.Confirmations = confirmations } return txReply, nil } // handleDecodeRawTransaction handles decoderawtransaction commands. func handleDecodeRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.DecodeRawTransactionCmd) // Deserialize the transaction. hexStr := c.HexTx if len(hexStr)%2 != 0 { hexStr = "0" + hexStr } serializedTx, err := hex.DecodeString(hexStr) if err != nil { return nil, rpcDecodeHexError(hexStr) } var mtx wire.MsgTx err = mtx.Deserialize(bytes.NewReader(serializedTx)) if err != nil { return nil, rpcDeserializationError("Could not decode Tx: %v", err) } // Create and return the result. txReply := dcrjson.TxRawDecodeResult{ Txid: mtx.TxHash().String(), Version: int32(mtx.Version), Locktime: mtx.LockTime, Expiry: mtx.Expiry, Vin: createVinList(&mtx), Vout: createVoutList(&mtx, s.server.chainParams, nil), } return txReply, nil } // handleDecodeScript handles decodescript commands. func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.DecodeScriptCmd) // Convert the hex script to bytes. hexStr := c.HexScript if len(hexStr)%2 != 0 { hexStr = "0" + hexStr } script, err := hex.DecodeString(hexStr) if err != nil { return nil, rpcDecodeHexError(hexStr) } // The disassembled string will contain [error] inline if the script // doesn't fully parse, so ignore the error here. disbuf, _ := txscript.DisasmString(script) // Get information about the script. // Ignore the error here since an error means the script couldn't parse // and there is no additinal information about it anyways. // TODO Replace magic version with argument passed to RPC call scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, script, s.server.chainParams) addresses := make([]string, len(addrs)) for i, addr := range addrs { addresses[i] = addr.EncodeAddress() } // Convert the script itself to a pay-to-script-hash address. p2sh, err := dcrutil.NewAddressScriptHash(script, s.server.chainParams) if err != nil { return nil, rpcInternalError(err.Error(), "Failed to convert script to pay-to-script-hash") } // Generate and return the reply. reply := dcrjson.DecodeScriptResult{ Asm: disbuf, ReqSigs: int32(reqSigs), Type: scriptClass.String(), Addresses: addresses, } if scriptClass != txscript.ScriptHashTy { reply.P2sh = p2sh.EncodeAddress() } return reply, nil } // handleEstimateFee implenents the estimatefee command. // TODO this is a very basic implementation. It should be // modified to match the bitcoin-core one. func handleEstimateFee(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { return cfg.minRelayTxFee.ToCoin(), nil } // handleEstimateStakeDiff implements the estimatestakediff command. func handleEstimateStakeDiff(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.EstimateStakeDiffCmd) // Minimum possible stake difficulty. chain := s.server.blockManager.chain min, err := chain.EstimateNextStakeDifficulty(0, false) if err != nil { return nil, rpcInternalError(err.Error(), "Could not "+ "estimate next minimum stake difficulty") } // Maximum possible stake difficulty. max, err := chain.EstimateNextStakeDifficulty(0, true) if err != nil { return nil, rpcInternalError(err.Error(), "Could not "+ "estimate next maximum stake difficulty") } // The expected stake difficulty. Average the number of fresh stake // since the last retarget to get the number of tickets per block, // then use that to estimate the next stake difficulty. _, bestHeight := s.server.blockManager.chainState.Best() lastAdjustment := (bestHeight / activeNetParams.StakeDiffWindowSize) * activeNetParams.StakeDiffWindowSize nextAdjustment := ((bestHeight / activeNetParams.StakeDiffWindowSize) + 1) * activeNetParams.StakeDiffWindowSize totalTickets := 0 err = s.server.db.View(func(dbTx database.Tx) error { for i := lastAdjustment; i <= bestHeight; i++ { bh, err := blockchain.DBFetchHeaderByHeight(dbTx, i) if err != nil { return err } totalTickets += int(bh.FreshStake) } return nil }) blocksSince := float64(bestHeight - lastAdjustment + 1) remaining := float64(nextAdjustment - bestHeight - 1) averagePerBlock := float64(totalTickets) / blocksSince expectedTickets := int64(math.Floor(averagePerBlock * remaining)) expected, err := chain.EstimateNextStakeDifficulty(expectedTickets, false) if err != nil { return nil, rpcInternalError(err.Error(), "Could not "+ "estimate next stake difficulty") } // User-specified stake difficulty, if they asked for one. var userEstFltPtr *float64 if c.Tickets != nil { userEst, err := chain.EstimateNextStakeDifficulty(int64(*c.Tickets), false) if err != nil { return nil, rpcInternalError(err.Error(), "Could not "+ "estimate next user specified stake difficulty") } userEstFlt := dcrutil.Amount(userEst).ToCoin() userEstFltPtr = &userEstFlt } return &dcrjson.EstimateStakeDiffResult{ Min: dcrutil.Amount(min).ToCoin(), Max: dcrutil.Amount(max).ToCoin(), Expected: dcrutil.Amount(expected).ToCoin(), User: userEstFltPtr, }, nil } // handleExistsAddress implements the existsaddress command. func handleExistsAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { existsAddrIndex := s.server.existsAddrIndex if existsAddrIndex == nil { return nil, rpcInternalError("Exists address index disabled", "Configuration") } c := cmd.(*dcrjson.ExistsAddressCmd) // Attempt to decode the supplied address. addr, err := dcrutil.DecodeAddress(c.Address) if err != nil { return nil, rpcAddressKeyError("Could not decode address: %v", err) } exists, err := existsAddrIndex.ExistsAddress(addr) if err != nil { return nil, rpcInvalidError("Could not query address: %v", err) } return exists, nil } // handleExistsAddresses implements the existsaddresses command. func handleExistsAddresses(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { existsAddrIndex := s.server.existsAddrIndex if existsAddrIndex == nil { return nil, rpcInternalError("Exists address index disabled", "Configuration") } c := cmd.(*dcrjson.ExistsAddressesCmd) addresses := make([]dcrutil.Address, len(c.Addresses)) for i := range c.Addresses { // Attempt to decode the supplied address. addr, err := dcrutil.DecodeAddress(c.Addresses[i]) if err != nil { return nil, rpcAddressKeyError("Could not decode "+ "address: %v", err) } addresses[i] = addr } exists, err := existsAddrIndex.ExistsAddresses(addresses) if err != nil { return nil, rpcInvalidError("Could not query address: %v", err) } // Convert the slice of bools into a compacted set of bit flags. set := bitset.NewBytes(len(c.Addresses)) for i := range exists { if exists[i] { set.Set(i) } } return hex.EncodeToString([]byte(set)), nil } // handleExistsMissedTickets implements the existsmissedtickets command. func handleExistsMissedTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.ExistsMissedTicketsCmd) hashes, err := dcrjson.DecodeConcatenatedHashes(c.TxHashBlob) if err != nil { return nil, err } exists := s.server.blockManager.chain.CheckMissedTickets(hashes) if len(exists) != len(hashes) { return nil, rpcInvalidError("Invalid missed ticket count "+ "got %v, want %v", len(exists), len(hashes)) } // Convert the slice of bools into a compacted set of bit flags. set := bitset.NewBytes(len(hashes)) for i := range exists { if exists[i] { set.Set(i) } } return hex.EncodeToString([]byte(set)), nil } // handleExistsExpiredTickets implements the existsexpiredtickets command. func handleExistsExpiredTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.ExistsExpiredTicketsCmd) hashes, err := dcrjson.DecodeConcatenatedHashes(c.TxHashBlob) if err != nil { return nil, err } exists := s.server.blockManager.chain.CheckExpiredTickets(hashes) if len(exists) != len(hashes) { return nil, rpcInvalidError("Invalid expired ticket count "+ "got %v, want %v", len(exists), len(hashes)) } // Convert the slice of bools into a compacted set of bit flags. set := bitset.NewBytes(len(hashes)) for i := range exists { if exists[i] { set.Set(i) } } return hex.EncodeToString([]byte(set)), nil } // handleExistsLiveTicket implements the existsliveticket command. func handleExistsLiveTicket(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.ExistsLiveTicketCmd) hash, err := chainhash.NewHashFromStr(c.TxHash) if err != nil { return nil, rpcDecodeHexError(c.TxHash) } return s.server.blockManager.chain.CheckLiveTicket(*hash), nil } // handleExistsLiveTickets implements the existslivetickets command. func handleExistsLiveTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.ExistsLiveTicketsCmd) hashes, err := dcrjson.DecodeConcatenatedHashes(c.TxHashBlob) if err != nil { return nil, err } exists := s.server.blockManager.chain.CheckLiveTickets(hashes) if len(exists) != len(hashes) { return nil, rpcInvalidError("Invalid live ticket count got "+ "%v, want %v", len(exists), len(hashes)) } // Convert the slice of bools into a compacted set of bit flags. set := bitset.NewBytes(len(hashes)) for i := range exists { if exists[i] { set.Set(i) } } return hex.EncodeToString([]byte(set)), nil } // handleExistsMempoolTxs implements the existsmempooltxs command. func handleExistsMempoolTxs(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.ExistsMempoolTxsCmd) txHashBlob, err := hex.DecodeString(c.TxHashBlob) if err != nil { return nil, rpcDecodeHexError(c.TxHashBlob) } // It needs to be an exact number of hashes. if len(txHashBlob)%32 != 0 { return nil, rpcInvalidError("Invalid hash blob length") } hashesLen := len(txHashBlob) / 32 hashes := make([]*chainhash.Hash, hashesLen) for i := 0; i < hashesLen; i++ { hashes[i], err = chainhash.NewHash( txHashBlob[i*chainhash.HashSize : (i+1)*chainhash.HashSize]) if err != nil { return nil, rpcInternalError(err.Error(), "New hash") } } exists := s.server.txMemPool.HaveTransactions(hashes) if len(exists) != hashesLen { return nil, rpcInternalError(fmt.Sprintf("got %v, want %v", len(exists), hashesLen), "Invalid mempool Tx ticket count") } // Convert the slice of bools into a compacted set of bit flags. set := bitset.NewBytes(hashesLen) for i := range exists { if exists[i] { set.Set(i) } } return hex.EncodeToString([]byte(set)), nil } // handleGenerate handles generate commands. func handleGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Respond with an error if there are no addresses to pay the // created blocks to. if len(cfg.miningAddrs) == 0 { return nil, rpcInternalError("No payment addresses specified "+ "via --miningaddr", "Configuration") } c := cmd.(*dcrjson.GenerateCmd) // Respond with an error if the client is requesting 0 blocks to be generated. if c.NumBlocks == 0 { return nil, rpcInternalError("Invalid number of blocks", "Configuration") } // Create a reply reply := make([]string, c.NumBlocks) blockHashes, err := s.server.cpuMiner.GenerateNBlocks(c.NumBlocks) if err != nil { return nil, rpcInternalError("Could not generate blocks", "Configuration") } // Mine the correct number of blocks, assigning the hex representation of the // hash of each one to its place in the reply. for i, hash := range blockHashes { reply[i] = hash.String() } return reply, nil } // handleGetAddedNodeInfo handles getaddednodeinfo commands. func handleGetAddedNodeInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetAddedNodeInfoCmd) // Retrieve a list of persistent (added) peers from the decred server // and filter the list of peers per the specified address (if any). peers := s.server.AddedNodeInfo() if c.Node != nil { found := false for i, peer := range peers { if peer.Addr() == *c.Node { peers = peers[i : i+1] found = true } } if !found { return nil, rpcInternalError("Node not found", "") } } // Without the dns flag, the result is just a slice of the addresses as // strings. if !c.DNS { results := make([]string, 0, len(peers)) for _, peer := range peers { results = append(results, peer.Addr()) } return results, nil } // With the dns flag, the result is an array of JSON objects which // include the result of DNS lookups for each peer. results := make([]*dcrjson.GetAddedNodeInfoResult, 0, len(peers)) for _, peer := range peers { // Set the "address" of the peer which could be an ip address // or a domain name. var result dcrjson.GetAddedNodeInfoResult result.AddedNode = peer.Addr() result.Connected = dcrjson.Bool(peer.Connected()) // Split the address into host and port portions so we can do a // DNS lookup against the host. When no port is specified in // the address, just use the address as the host. host, _, err := net.SplitHostPort(peer.Addr()) if err != nil { host = peer.Addr() } // Do a DNS lookup for the address. If the lookup fails, just // use the host. var ipList []string ips, err := dcrdLookup(host) if err == nil { ipList = make([]string, 0, len(ips)) for _, ip := range ips { ipList = append(ipList, ip.String()) } } else { ipList = make([]string, 1) ipList[0] = host } // Add the addresses and connection info to the result. addrs := make([]dcrjson.GetAddedNodeInfoResultAddr, 0, len(ipList)) for _, ip := range ipList { var addr dcrjson.GetAddedNodeInfoResultAddr addr.Address = ip addr.Connected = "false" if ip == host && peer.Connected() { addr.Connected = directionString(peer.Inbound()) } addrs = append(addrs, addr) } result.Addresses = &addrs results = append(results, &result) } return results, nil } // handleGetBestBlock implements the getbestblock command. func handleGetBestBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // All other "get block" commands give either the height, the hash, or // both but require the block SHA. This gets both for the best block. best := s.chain.BestSnapshot() result := &dcrjson.GetBestBlockResult{ Hash: best.Hash.String(), Height: best.Height, } return result, nil } // handleGetBestBlockHash implements the getbestblockhash command. func handleGetBestBlockHash(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { best := s.chain.BestSnapshot() return best.Hash.String(), nil } // getDifficultyRatio returns the proof-of-work difficulty as a multiple of the // minimum difficulty using the passed bits field from the header of a block. func getDifficultyRatio(bits uint32) float64 { // The minimum difficulty is the max possible proof-of-work limit bits // converted back to a number. Note this is not the same as the proof // of work limit directly because the block difficulty is encoded in a // block with the compact form which loses precision. max := blockchain.CompactToBig(activeNetParams.PowLimitBits) target := blockchain.CompactToBig(bits) difficulty := new(big.Rat).SetFrac(max, target) outString := difficulty.FloatString(8) diff, err := strconv.ParseFloat(outString, 64) if err != nil { rpcsLog.Errorf("Cannot get difficulty: %v", err) return 0 } return diff } // handleGetBlock implements the getblock command. func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetBlockCmd) // Load the raw block bytes from the database. hash, err := chainhash.NewHashFromStr(c.Hash) if err != nil { return nil, rpcDecodeHexError(c.Hash) } blk, err := s.server.blockManager.chain.FetchBlockByHash(hash) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCBlockNotFound, Message: fmt.Sprintf("Block not found: %v", hash), } } // When the verbose flag isn't set, simply return the // network-serialized block as a hex-encoded string. if c.Verbose != nil && !*c.Verbose { blkBytes, err := blk.Bytes() if err != nil { return nil, rpcInternalError(err.Error(), "Could not serialize block") } return hex.EncodeToString(blkBytes), nil } best := s.chain.BestSnapshot() // See if this block is an orphan and adjust Confirmations accordingly. onMainChain, _ := s.chain.MainChainHasBlock(hash) // Get next block hash unless there are none. var nextHashString string blockHeader := &blk.MsgBlock().Header confirmations := int64(-1) if onMainChain { if int64(blockHeader.Height) < best.Height { nextHash, err := s.chain.BlockHashByHeight(int64(blockHeader.Height + 1)) if err != nil { context := "No next block" return nil, rpcInternalError(err.Error(), context) } nextHashString = nextHash.String() } confirmations = 1 + best.Height - int64(blockHeader.Height) } sbitsFloat := float64(blockHeader.SBits) / dcrutil.AtomsPerCoin blockReply := dcrjson.GetBlockVerboseResult{ Hash: c.Hash, Version: blockHeader.Version, MerkleRoot: blockHeader.MerkleRoot.String(), StakeRoot: blockHeader.StakeRoot.String(), PreviousHash: blockHeader.PrevBlock.String(), Nonce: blockHeader.Nonce, VoteBits: blockHeader.VoteBits, FinalState: hex.EncodeToString(blockHeader.FinalState[:]), Voters: blockHeader.Voters, FreshStake: blockHeader.FreshStake, Revocations: blockHeader.Revocations, PoolSize: blockHeader.PoolSize, Time: blockHeader.Timestamp.Unix(), StakeVersion: blockHeader.StakeVersion, Confirmations: confirmations, Height: int64(blockHeader.Height), Size: int32(blk.MsgBlock().Header.Size), Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), SBits: sbitsFloat, Difficulty: getDifficultyRatio(blockHeader.Bits), ExtraData: hex.EncodeToString(blockHeader.ExtraData[:]), NextHash: nextHashString, } if c.VerboseTx == nil || !*c.VerboseTx { transactions := blk.Transactions() txNames := make([]string, len(transactions)) for i, tx := range transactions { txNames[i] = tx.Hash().String() } blockReply.Tx = txNames stransactions := blk.STransactions() stxNames := make([]string, len(stransactions)) for i, tx := range stransactions { stxNames[i] = tx.Hash().String() } blockReply.STx = stxNames } else { txns := blk.Transactions() rawTxns := make([]dcrjson.TxRawResult, len(txns)) for i, tx := range txns { rawTxn, err := createTxRawResult(s.server.chainParams, tx.MsgTx(), tx.Hash().String(), uint32(i), blockHeader, blk.Hash().String(), int64(blockHeader.Height), confirmations) if err != nil { return nil, rpcInternalError(err.Error(), "Could not create transaction") } rawTxns[i] = *rawTxn } blockReply.RawTx = rawTxns stxns := blk.STransactions() rawSTxns := make([]dcrjson.TxRawResult, len(stxns)) for i, tx := range stxns { rawSTxn, err := createTxRawResult(s.server.chainParams, tx.MsgTx(), tx.Hash().String(), uint32(i), blockHeader, blk.Hash().String(), int64(blockHeader.Height), confirmations) if err != nil { return nil, rpcInternalError(err.Error(), "Could not create stake transaction") } rawSTxns[i] = *rawSTxn } blockReply.RawSTx = rawSTxns } return blockReply, nil } // handleGetBlockCount implements the getblockcount command. func handleGetBlockCount(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { best := s.chain.BestSnapshot() return best.Height, nil } // handleGetBlockHash implements the getblockhash command. func handleGetBlockHash(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetBlockHashCmd) hash, err := s.chain.BlockHashByHeight(c.Index) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCOutOfRange, Message: fmt.Sprintf("Block number out of range: %v", c.Index), } } return hash.String(), nil } // handleGetBlockHeader implements the getblockheader command. func handleGetBlockHeader(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetBlockHeaderCmd) // Fetch the header from chain. hash, err := chainhash.NewHashFromStr(c.Hash) if err != nil { return nil, rpcDecodeHexError(c.Hash) } blockHeader, err := s.chain.FetchHeader(hash) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCBlockNotFound, Message: fmt.Sprintf("Block not found: %v", c.Hash), } } // When the verbose flag isn't set, simply return the serialized block // header as a hex-encoded string. if c.Verbose != nil && !*c.Verbose { var headerBuf bytes.Buffer err := blockHeader.Serialize(&headerBuf) if err != nil { context := "Failed to serialize block header" return nil, rpcInternalError(err.Error(), context) } return hex.EncodeToString(headerBuf.Bytes()), nil } // The verbose flag is set, so generate the JSON object and return it. best := s.chain.BestSnapshot() // See if this block is an orphan and adjust Confirmations accordingly. onMainChain, _ := s.chain.MainChainHasBlock(hash) // Get next block hash unless there are none. var nextHashString string confirmations := int64(-1) height := int64(blockHeader.Height) if onMainChain { if height < best.Height { nextHash, err := s.chain.BlockHashByHeight(height + 1) if err != nil { context := "No next block" return nil, rpcInternalError(err.Error(), context) } nextHashString = nextHash.String() } confirmations = 1 + best.Height - height } blockHeaderReply := dcrjson.GetBlockHeaderVerboseResult{ Hash: c.Hash, Confirmations: confirmations, Version: blockHeader.Version, PreviousHash: blockHeader.PrevBlock.String(), MerkleRoot: blockHeader.MerkleRoot.String(), StakeRoot: blockHeader.StakeRoot.String(), VoteBits: blockHeader.VoteBits, FinalState: hex.EncodeToString(blockHeader.FinalState[:]), Voters: blockHeader.Voters, FreshStake: blockHeader.FreshStake, Revocations: blockHeader.Revocations, PoolSize: blockHeader.PoolSize, Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), SBits: dcrutil.Amount(blockHeader.SBits).ToCoin(), Height: uint32(height), Size: blockHeader.Size, Time: blockHeader.Timestamp.Unix(), Nonce: blockHeader.Nonce, StakeVersion: blockHeader.StakeVersion, Difficulty: getDifficultyRatio(blockHeader.Bits), NextHash: nextHashString, } return blockHeaderReply, nil } // handleGetBlockSubsidy implements the getblocksubsidy command. func handleGetBlockSubsidy(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetBlockSubsidyCmd) height := c.Height voters := c.Voters cache := s.chain.FetchSubsidyCache() if cache == nil { return nil, rpcInternalError("empty subsidy cache", "") } dev := blockchain.CalcBlockTaxSubsidy(cache, height, voters, s.server.chainParams) pos := blockchain.CalcStakeVoteSubsidy(cache, height, s.server.chainParams) * int64(voters) pow := blockchain.CalcBlockWorkSubsidy(cache, height, voters, s.server.chainParams) total := dev + pos + pow rep := dcrjson.GetBlockSubsidyResult{ Developer: dev, PoS: pos, PoW: pow, Total: total, } return rep, nil } // encodeTemplateID encodes the passed details into an ID that can be used to // uniquely identify a block template. func encodeTemplateID(prevHash *chainhash.Hash, lastGenerated time.Time) string { return fmt.Sprintf("%s-%d", prevHash.String(), lastGenerated.Unix()) } // decodeTemplateID decodes an ID that is used to uniquely identify a block // template. This is mainly used as a mechanism to track when to update // clients that are using long polling for block templates. The ID consists of // the previous block hash for the associated template and the time the // associated template was generated. func decodeTemplateID(templateID string) (*chainhash.Hash, int64, error) { fields := strings.Split(templateID, "-") if len(fields) != 2 { return nil, 0, ErrInvalidLongPoll } prevHash, err := chainhash.NewHashFromStr(fields[0]) if err != nil { return nil, 0, ErrInvalidLongPoll } lastGenerated, err := strconv.ParseInt(fields[1], 10, 64) if err != nil { return nil, 0, ErrInvalidLongPoll } return prevHash, lastGenerated, nil } // notifyLongPollers notifies any channels that have been registered to be // notified when block templates are stale. // // This function MUST be called with the state locked. func (state *gbtWorkState) notifyLongPollers(latestHash *chainhash.Hash, lastGenerated time.Time) { // Notify anything that is waiting for a block template update from a // hash which is not the hash of the tip of the best chain since their // work is now invalid. for hash, channels := range state.notifyMap { if !hash.IsEqual(latestHash) { for _, c := range channels { close(c) } delete(state.notifyMap, hash) } } // Return now if the provided last generated timestamp has not been // initialized. if lastGenerated.IsZero() { return } // Return now if there is nothing registered for updates to the current // best block hash. channels, ok := state.notifyMap[*latestHash] if !ok { return } // Notify anything that is waiting for a block template update from a // block template generated before the most recently generated block // template. lastGeneratedUnix := lastGenerated.Unix() for lastGen, c := range channels { if lastGen < lastGeneratedUnix { close(c) delete(channels, lastGen) } } // Remove the entry altogether if there are no more registered // channels. if len(channels) == 0 { delete(state.notifyMap, *latestHash) } } // NotifyBlockConnected uses the newly-connected block to notify any long poll // clients with a new block template when their existing block template is // stale due to the newly connected block. func (state *gbtWorkState) NotifyBlockConnected(blockHash *chainhash.Hash) { go func() { state.Lock() defer state.Unlock() state.notifyLongPollers(blockHash, state.lastTxUpdate) }() } // NotifyMempoolTx uses the new last updated time for the transaction memory // pool to notify any long poll clients with a new block template when their // existing block template is stale due to enough time passing and the contents // of the memory pool changing. func (state *gbtWorkState) NotifyMempoolTx(lastUpdated time.Time) { go func() { state.Lock() defer state.Unlock() // No need to notify anything if no block templates have been generated // yet. if state.prevHash == nil || state.lastGenerated.IsZero() { return } if time.Now().After(state.lastGenerated.Add(time.Second * gbtRegenerateSeconds)) { state.notifyLongPollers(state.prevHash, lastUpdated) } }() } // templateUpdateChan returns a channel that will be closed once the block // template associated with the passed previous hash and last generated time // is stale. The function will return existing channels for duplicate // parameters which allows multiple clients to wait for the same block template // without requiring a different channel for each client. // // This function MUST be called with the state locked. func (state *gbtWorkState) templateUpdateChan(prevHash *chainhash.Hash, lastGenerated int64) chan struct{} { // Either get the current list of channels waiting for updates about // changes to block template for the previous hash or create a new one. channels, ok := state.notifyMap[*prevHash] if !ok { m := make(map[int64]chan struct{}) state.notifyMap[*prevHash] = m channels = m } // Get the current channel associated with the time the block template // was last generated or create a new one. c, ok := channels[lastGenerated] if !ok { c = make(chan struct{}) channels[lastGenerated] = c } return c } // updateBlockTemplate creates or updates a block template for the work state. // A new block template will be generated when the current best block has // changed or the transactions in the memory pool have been updated and it has // been long enough since the last template was generated. Otherwise, the // timestamp for the existing block template is updated (and possibly the // difficulty on testnet per the consesus rules). Finally, if the // useCoinbaseValue flag is false and the existing block template does not // already contain a valid payment address, the block template will be updated // with a randomly selected payment address from the list of configured // addresses. // // This function MUST be called with the state locked. func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bool) error { lastTxUpdate := s.server.txMemPool.LastUpdated() if lastTxUpdate.IsZero() { lastTxUpdate = time.Now() } // Generate a new block template when the current best block has // changed or the transactions in the memory pool have been updated and // it has been at least gbtRegenerateSecond since the last template was // generated. var msgBlock *wire.MsgBlock var targetDifficulty string latestHash, _ := s.server.blockManager.chainState.Best() template := state.template if template == nil || state.prevHash == nil || !state.prevHash.IsEqual(latestHash) || (state.lastTxUpdate != lastTxUpdate && time.Now().After(state.lastGenerated.Add(time.Second* gbtRegenerateSeconds))) { // Reset the previous best hash the block template was generated // against so any errors below cause the next invocation to try // again. state.prevHash = nil // Choose a payment address at random if the caller requests a // full coinbase as opposed to only the pertinent details needed // to create their own coinbase. var payAddr dcrutil.Address if !useCoinbaseValue { payAddr = cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] } // Create a new block template that has a coinbase which anyone // can redeem. This is only acceptable because the returned // block template doesn't include the coinbase, so the caller // will ultimately create their own coinbase which pays to the // appropriate address(es). blkTemplate, err := NewBlockTemplate(s.policy, s.server, payAddr) if err != nil { return rpcInternalError("Failed to create new block "+ "template: "+err.Error(), "") } if blkTemplate == nil { return rpcInternalError("Failed to create new block "+ "template: not enough voters on parent and no "+ "suitable cached template", "") } template = blkTemplate msgBlock = template.Block targetDifficulty = fmt.Sprintf("%064x", blockchain.CompactToBig(msgBlock.Header.Bits)) // Find the minimum allowed timestamp for the block based on the // median timestamp of the last several blocks per the chain // consensus rules. chainState := &s.server.blockManager.chainState minTimestamp, err := minimumMedianTime(chainState) if err != nil { context := "Failed to get minimum median time" return rpcInternalError(err.Error(), context) } // Update work state to ensure another block template isn't // generated until needed. state.template = deepCopyBlockTemplate(template) state.lastGenerated = time.Now() state.lastTxUpdate = lastTxUpdate state.prevHash = latestHash state.minTimestamp = minTimestamp rpcsLog.Debugf("Generated block template (timestamp %v, "+ "target %s, merkle root %s)", msgBlock.Header.Timestamp, targetDifficulty, msgBlock.Header.MerkleRoot) // Notify any clients that are long polling about the new // template. state.notifyLongPollers(latestHash, lastTxUpdate) } else { // At this point, there is a saved block template and another // request for a template was made, but either the available // transactions haven't change or it hasn't been long enough to // trigger a new block template to be generated. So, update the // existing block template. // When the caller requires a full coinbase as opposed to only // the pertinent details needed to create their own coinbase, // add a payment address to the output of the coinbase of the // template if it doesn't already have one. Since this requires // mining addresses to be specified via the config, an error is // returned if none have been specified. if !useCoinbaseValue && !template.ValidPayAddress { // Choose a payment address at random. payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] // Update the block coinbase output of the template to // pay to the randomly selected payment address. pkScript, err := txscript.PayToAddrScript(payToAddr) if err != nil { context := "Failed to create pay-to-addr script" return rpcInternalError(err.Error(), context) } template.Block.Transactions[0].TxOut[0].PkScript = pkScript template.ValidPayAddress = true // Update the merkle root. block := dcrutil.NewBlock(template.Block) merkles := blockchain.BuildMerkleTreeStore(block.Transactions()) template.Block.Header.MerkleRoot = *merkles[len(merkles)-1] } // Set locals for convenience. msgBlock = template.Block targetDifficulty = fmt.Sprintf("%064x", blockchain.CompactToBig(msgBlock.Header.Bits)) // Update the time of the block template to the current time // while accounting for the median time of the past several // blocks per the chain consensus rules. err := UpdateBlockTime(msgBlock, s.server.blockManager) if err != nil { context := "Failed to update timestamp" return rpcInternalError(err.Error(), context) } msgBlock.Header.Nonce = 0 rpcsLog.Debugf("Updated block template (timestamp %v, "+ "target %s)", msgBlock.Header.Timestamp, targetDifficulty) } return nil } // blockTemplateResult returns the current block template associated with the // state as a dcrjson.GetBlockTemplateResult that is ready to be encoded to // JSON and returned to the caller. // // This function MUST be called with the state locked. func (state *gbtWorkState) blockTemplateResult(bm *blockManager, useCoinbaseValue bool, submitOld *bool) (*dcrjson.GetBlockTemplateResult, error) { // Ensure the timestamps are still in valid range for the template. // This should really only ever happen if the local clock is changed // after the template is generated, but it's important to avoid serving // invalid block templates. template := deepCopyBlockTemplate(state.template) msgBlock := template.Block header := &msgBlock.Header adjustedTime := state.timeSource.AdjustedTime() maxTime := adjustedTime.Add(time.Second * blockchain.MaxTimeOffsetSeconds) if header.Timestamp.After(maxTime) { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCOutOfRange, Message: fmt.Sprintf("The template time is after the "+ "maximum allowed time for a block - template "+ "time %v, maximum time %v", adjustedTime, maxTime), } } // Convert each transaction in the block template to a template result // transaction. The result does not include the coinbase, so notice // the adjustments to the various lengths and indices. numTx := len(msgBlock.Transactions) transactions := make([]dcrjson.GetBlockTemplateResultTx, 0, numTx-1) txIndex := make(map[chainhash.Hash]int64, numTx) for i, tx := range msgBlock.Transactions { txHash := tx.TxHashFull() txIndex[txHash] = int64(i) // Skip the coinbase transaction. if i == 0 { continue } // Create an array of 1-based indices to transactions that come // before this one in the transactions list which this one // depends on. This is necessary since the created block must // ensure proper ordering of the dependencies. A map is used // before creating the final array to prevent duplicate entries // when multiple inputs reference the same transaction. dependsMap := make(map[int64]struct{}) for _, txIn := range tx.TxIn { if idx, ok := txIndex[txIn.PreviousOutPoint.Hash]; ok { dependsMap[idx] = struct{}{} } } depends := make([]int64, 0, len(dependsMap)) for idx := range dependsMap { depends = append(depends, idx) } // Serialize the transaction for later conversion to hex. txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) if err := tx.Serialize(txBuf); err != nil { context := "Failed to serialize transaction" return nil, rpcInternalError(err.Error(), context) } var txTypeStr string txType := stake.DetermineTxType(tx) switch txType { case stake.TxTypeRegular: txTypeStr = "regular" case stake.TxTypeSStx: txTypeStr = "error" case stake.TxTypeSSGen: txTypeStr = "error" case stake.TxTypeSSRtx: txTypeStr = "error" } fee := template.Fees[i] sigOps := template.SigOpCounts[i] resultTx := dcrjson.GetBlockTemplateResultTx{ Data: hex.EncodeToString(txBuf.Bytes()), Hash: txHash.String(), Depends: depends, Fee: fee, SigOps: sigOps, TxType: txTypeStr, } transactions = append(transactions, resultTx) } // Convert each stake transaction in the block template to a template // result transaction. numSTx := len(msgBlock.STransactions) stransactions := make([]dcrjson.GetBlockTemplateResultTx, 0, numSTx) stxIndex := make(map[chainhash.Hash]int64, numSTx) for i, stx := range msgBlock.STransactions { stxHash := stx.TxHashFull() stxIndex[stxHash] = int64(i) // Create an array of 1-based indices to transactions that come // before this one in the transactions list which this one // depends on. This is necessary since the created block must // ensure proper ordering of the dependencies. A map is used // before creating the final array to prevent duplicate entries // when mutiple inputs reference the same transaction. dependsMap := make(map[int64]struct{}) for _, txIn := range stx.TxIn { if idx, ok := stxIndex[txIn.PreviousOutPoint.Hash]; ok { dependsMap[idx] = struct{}{} } } depends := make([]int64, 0, len(dependsMap)) for idx := range dependsMap { depends = append(depends, idx) } // Serialize the transaction for later conversion to hex. txBuf := bytes.NewBuffer(make([]byte, 0, stx.SerializeSize())) if err := stx.Serialize(txBuf); err != nil { return nil, err } var txTypeStr string txType := stake.DetermineTxType(stx) switch txType { case stake.TxTypeRegular: txTypeStr = "error" case stake.TxTypeSStx: txTypeStr = "ticket" case stake.TxTypeSSGen: txTypeStr = "vote" case stake.TxTypeSSRtx: txTypeStr = "revocation" } fee := template.Fees[i+len(msgBlock.Transactions)] sigOps := template.SigOpCounts[i+len(msgBlock.Transactions)] resultTx := dcrjson.GetBlockTemplateResultTx{ Data: hex.EncodeToString(txBuf.Bytes()), Hash: stxHash.String(), Depends: depends, Fee: fee, SigOps: sigOps, TxType: txTypeStr, } stransactions = append(stransactions, resultTx) } headerBytes, err := header.Bytes() if err != nil { context := "Could not obtain header" return nil, rpcInternalError(err.Error(), context) } // Choose the correct maximum block size as defined by the network // parameters and the current status of any hard fork votes to change // it when serialized. maxBlockSize, err := bm.chain.MaxBlockSize() if err != nil { context := "Invalid blocksize" return nil, rpcInternalError(err.Error(), context) } // Generate the block template reply. Note that following mutations // are implied by the included or omission of fields: // Including MinTime -> time/decrement // Omitting CoinbaseTxn -> coinbase, generation targetDifficulty := fmt.Sprintf("%064x", blockchain.CompactToBig(header.Bits)) templateID := encodeTemplateID(state.prevHash, state.lastGenerated) reply := dcrjson.GetBlockTemplateResult{ Header: hex.EncodeToString(headerBytes), SigOpLimit: blockchain.MaxSigOpsPerBlock, SizeLimit: maxBlockSize, Transactions: transactions, STransactions: stransactions, LongPollID: templateID, SubmitOld: submitOld, Target: targetDifficulty, MinTime: state.minTimestamp.Unix(), MaxTime: maxTime.Unix(), Mutable: gbtMutableFields, NonceRange: gbtNonceRange, Capabilities: gbtCapabilities, } if useCoinbaseValue { reply.CoinbaseAux = gbtCoinbaseAux reply.CoinbaseValue = &msgBlock.Transactions[0].TxOut[0].Value } else { // Ensure the template has a valid payment address associated // with it when a full coinbase is requested. if !template.ValidPayAddress { context := "Invalid blocksize" errStr := fmt.Sprintf("A coinbase transaction has " + "been requested, but the server has not " + "been configured with any payment " + "addresses via --miningaddr") return nil, rpcInternalError(errStr, context) } // Serialize the transaction for conversion to hex. tx := msgBlock.Transactions[0] txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) if err := tx.Serialize(txBuf); err != nil { context := "Could not serialize" return nil, rpcInternalError(err.Error(), context) } resultTx := dcrjson.GetBlockTemplateResultTx{ Data: hex.EncodeToString(txBuf.Bytes()), Hash: tx.TxHash().String(), Depends: []int64{}, Fee: template.Fees[0], SigOps: template.SigOpCounts[0], } reply.CoinbaseTxn = &resultTx } return &reply, nil } // handleGetBlockTemplateLongPoll is a helper for handleGetBlockTemplateRequest // which deals with handling long polling for block templates. When a caller // sends a request with a long poll ID that was previously returned, a response // is not sent until the caller should stop working on the previous block // template in favor of the new one. In particular, this is the case when the // old block template is no longer valid due to a solution already being found // and added to the block chain, or new transactions have shown up and some time // has passed without finding a solution. // // See https://en.bitcoin.it/wiki/BIP_0022 for more details. func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbaseValue bool, closeChan <-chan struct{}) (interface{}, error) { state := s.gbtWorkState state.Lock() // The state unlock is intentionally not deferred here since it needs to // be manually unlocked before waiting for a notification about block // template changes. if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { state.Unlock() return nil, err } // Just return the current block template if the long poll ID provided by // the caller is invalid. prevHash, lastGenerated, err := decodeTemplateID(longPollID) if err != nil { result, err := state.blockTemplateResult(s.server.blockManager, useCoinbaseValue, nil) if err != nil { state.Unlock() return nil, err } state.Unlock() return result, nil } // Return the block template now if the specific block template // identified by the long poll ID no longer matches the current block // template as this means the provided template is stale. prevTemplateHash := &state.template.Block.Header.PrevBlock if !prevHash.IsEqual(prevTemplateHash) || lastGenerated != state.lastGenerated.Unix() { // Include whether or not it is valid to submit work against the // old block template depending on whether or not a solution has // already been found and added to the block chain. submitOld := prevHash.IsEqual(prevTemplateHash) result, err := state.blockTemplateResult(s.server.blockManager, useCoinbaseValue, &submitOld) if err != nil { state.Unlock() return nil, err } state.Unlock() return result, nil } // Register the previous hash and last generated time for notifications // Get a channel that will be notified when the template associated with // the provided ID is stale and a new block template should be returned to // the caller. longPollChan := state.templateUpdateChan(prevHash, lastGenerated) state.Unlock() select { // When the client closes before it's time to send a reply, just return // now so the goroutine doesn't hang around. case <-closeChan: return nil, ErrClientQuit // Wait until signal received to send the reply. case <-longPollChan: // Fallthrough } // Get the lastest block template state.Lock() defer state.Unlock() if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { return nil, err } // Include whether or not it is valid to submit work against the old // block template depending on whether or not a solution has already // been found and added to the block chain. submitOld := prevHash.IsEqual(&state.template.Block.Header.PrevBlock) result, err := state.blockTemplateResult(s.server.blockManager, useCoinbaseValue, &submitOld) if err != nil { return nil, err } return result, nil } // handleGetBlockTemplateRequest is a helper for handleGetBlockTemplate which // deals with generating and returning block templates to the caller. It // handles both long poll requests as specified by BIP 0022 as well as regular // requests. In addition, it detects the capabilities reported by the caller // in regards to whether or not it supports creating its own coinbase (the // coinbasetxn and coinbasevalue capabilities) and modifies the returned block // template accordingly. func handleGetBlockTemplateRequest(s *rpcServer, request *dcrjson.TemplateRequest, closeChan <-chan struct{}) (interface{}, error) { // Extract the relevant passed capabilities and restrict the result to // either a coinbase value or a coinbase transaction object depending // on the request. Default to only providing a coinbase value. useCoinbaseValue := true if request != nil { var hasCoinbaseValue, hasCoinbaseTxn bool for _, capability := range request.Capabilities { switch capability { case "coinbasetxn": hasCoinbaseTxn = true case "coinbasevalue": hasCoinbaseValue = true } } if hasCoinbaseTxn && !hasCoinbaseValue { useCoinbaseValue = false } } // When a coinbase transaction has been requested, respond with an // error if there are no addresses to pay the created block template // to. if !useCoinbaseValue && len(cfg.miningAddrs) == 0 { return nil, rpcInternalError("A coinbase transaction has "+ "been requested, but the server has not been "+ "configured with any payment addresses via "+ "--miningaddr", "Configuration") } // Return an error if there are no peers connected since there is no // way to relay a found block or receive transactions to work on. // However, allow this state when running in the regression test or // simulation test mode. if !cfg.SimNet && s.server.ConnectedCount() == 0 { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCClientNotConnected, Message: "Decred is not connected", } } // No point in generating or accepting work before the chain is synced. _, currentHeight := s.server.blockManager.chainState.Best() if currentHeight != 0 && !s.server.blockManager.IsCurrent() { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCClientInInitialDownload, Message: "Decred is downloading blocks...", } } // When a long poll ID was provided, this is a long poll request by the // client to be notified when block template referenced by the ID // should be replaced with a new one. if request != nil && request.LongPollID != "" { return handleGetBlockTemplateLongPoll(s, request.LongPollID, useCoinbaseValue, closeChan) } // Protect concurrent access when updating block templates. state := s.gbtWorkState state.Lock() defer state.Unlock() // Get and return a block template. A new block template will be // generated when the current best block has changed or the // transactions in the memory pool have been updated and it has been at // least five seconds since the last template was generated. // Otherwise, the timestamp for the existing block template is updated // (and possibly the difficulty on testnet per the consesus rules). if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil { return nil, err } return state.blockTemplateResult(s.server.blockManager, useCoinbaseValue, nil) } // chainErrToGBTErrString converts an error returned from chain to a string // which matches the reasons and format described in BIP0022 for rejection // reasons. // TODO Decred pop in the new errors from blockchain cj func chainErrToGBTErrString(err error) string { // When the passed error is not a RuleError, just return a generic // rejected string with the error text. ruleErr, ok := err.(blockchain.RuleError) if !ok { return "rejected: " + err.Error() } switch ruleErr.ErrorCode { case blockchain.ErrDuplicateBlock: return "duplicate" case blockchain.ErrBlockTooBig: return "bad-block-size" case blockchain.ErrBlockVersionTooOld: return "bad-version" case blockchain.ErrInvalidTime: return "bad-time" case blockchain.ErrTimeTooOld: return "time-too-old" case blockchain.ErrTimeTooNew: return "time-too-new" case blockchain.ErrDifficultyTooLow: return "bad-diffbits" case blockchain.ErrUnexpectedDifficulty: return "bad-diffbits" case blockchain.ErrHighHash: return "high-hash" case blockchain.ErrBadMerkleRoot: return "bad-txnmrklroot" case blockchain.ErrBadCheckpoint: return "bad-checkpoint" case blockchain.ErrForkTooOld: return "fork-too-old" case blockchain.ErrCheckpointTimeTooOld: return "checkpoint-time-too-old" case blockchain.ErrNoTransactions: return "bad-txns-none" case blockchain.ErrTooManyTransactions: return "bad-txns-toomany" case blockchain.ErrNoTxInputs: return "bad-txns-noinputs" case blockchain.ErrNoTxOutputs: return "bad-txns-nooutputs" case blockchain.ErrTxTooBig: return "bad-txns-size" case blockchain.ErrBadTxOutValue: return "bad-txns-outputvalue" case blockchain.ErrDuplicateTxInputs: return "bad-txns-dupinputs" case blockchain.ErrBadTxInput: return "bad-txns-badinput" case blockchain.ErrMissingTxOut: return "bad-txns-missinginput" case blockchain.ErrUnfinalizedTx: return "bad-txns-unfinalizedtx" case blockchain.ErrDuplicateTx: return "bad-txns-duplicate" case blockchain.ErrOverwriteTx: return "bad-txns-overwrite" case blockchain.ErrImmatureSpend: return "bad-txns-maturity" case blockchain.ErrSpendTooHigh: return "bad-txns-highspend" case blockchain.ErrBadFees: return "bad-txns-fees" case blockchain.ErrTooManySigOps: return "high-sigops" case blockchain.ErrFirstTxNotCoinbase: return "bad-txns-nocoinbase" case blockchain.ErrMultipleCoinbases: return "bad-txns-multicoinbase" case blockchain.ErrBadCoinbaseScriptLen: return "bad-cb-length" case blockchain.ErrBadCoinbaseValue: return "bad-cb-value" case blockchain.ErrScriptMalformed: return "bad-script-malformed" case blockchain.ErrScriptValidation: return "bad-script-validate" } return "rejected: " + err.Error() } // handleGetBlockTemplateProposal is a helper for handleGetBlockTemplate which // deals with block proposals. // // See https://en.bitcoin.it/wiki/BIP_0023 for more details. func handleGetBlockTemplateProposal(s *rpcServer, request *dcrjson.TemplateRequest) (interface{}, error) { hexData := request.Data if hexData == "" { return false, rpcInvalidError("Data must contain the " + "hex-encoded serialized block that is being " + "proposed") } // Ensure the provided data is sane and deserialize the proposed block. if len(hexData)%2 != 0 { hexData = "0" + hexData } dataBytes, err := hex.DecodeString(hexData) if err != nil { return false, rpcDecodeHexError(hexData) } var msgBlock wire.MsgBlock if err := msgBlock.Deserialize(bytes.NewReader(dataBytes)); err != nil { return nil, rpcDeserializationError("Could not decode block: "+ "%v", err) } block := dcrutil.NewBlock(&msgBlock) // Ensure the block is building from the expected previous block. expectedPrevHash, _ := s.server.blockManager.chainState.Best() prevHash := &block.MsgBlock().Header.PrevBlock if expectedPrevHash == nil || !expectedPrevHash.IsEqual(prevHash) { return "bad-prevblk", nil } flags := blockchain.BFDryRun | blockchain.BFNoPoWCheck isOrphan, err := s.server.blockManager.ProcessBlock(block, flags) 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 } if isOrphan { return "orphan", nil } return nil, nil } // handleGetBlockTemplate implements the getblocktemplate command. // // See https://en.bitcoin.it/wiki/BIP_0022 and // https://en.bitcoin.it/wiki/BIP_0023 for more details. func handleGetBlockTemplate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { 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) } // 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 } // 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 server.locateBlocks is // optimized for that and this is given the performance penality. pBlockLocators := make([]*chainhash.Hash, len(blockLocators)) for i := range blockLocators { pBlockLocators[i] = &blockLocators[i] } blockHashes, err := s.server.locateBlocks(pBlockLocators, &hashStop) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCDatabase, Message: "Failed to fetch hashes of block " + "headers: " + err.Error(), } } blockHeaders, err := fetchHeaders(s.chain, blockHashes) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCDatabase, Message: "Failed to fetch headers of located blocks: " + err.Error(), } } hexBlockHeaders := make([]string, len(blockHeaders)) var buf bytes.Buffer buf.Grow(wire.MaxBlockHeaderPayload) for i, h := range blockHeaders { err := h.Serialize(&buf) if err != nil { return nil, rpcInternalError(err.Error(), "Failed to serialize block header") } hexBlockHeaders[i] = hex.EncodeToString(buf.Bytes()) buf.Reset() } return &dcrjson.GetHeadersResult{Headers: hexBlockHeaders}, nil } // handleGetInfo implements the getinfo command. We only return the fields // that are not related to wallet functionality. func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { best := s.chain.BestSnapshot() ret := &dcrjson.InfoChainResult{ Version: int32(1000000*appMajor + 10000*appMinor + 100*appPatch), ProtocolVersion: int32(maxProtocolVersion), Blocks: best.Height, TimeOffset: int64(s.server.timeSource.Offset().Seconds()), Connections: s.server.ConnectedCount(), Proxy: cfg.Proxy, Difficulty: getDifficultyRatio(best.Bits), TestNet: cfg.TestNet, RelayFee: cfg.minRelayTxFee.ToCoin(), } return ret, nil } // handleGetMempoolInfo implements the getmempoolinfo command. func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { mempoolTxns := s.server.txMemPool.TxDescs() var numBytes int64 for _, txD := range mempoolTxns { numBytes += int64(txD.Tx.MsgTx().SerializeSize()) } ret := &dcrjson.GetMempoolInfoResult{ Size: int64(len(mempoolTxns)), Bytes: numBytes, } return ret, nil } // handleGetMiningInfo implements the getmininginfo command. We only return the // fields that are not related to wallet functionality. func handleGetMiningInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Create a default getnetworkhashps command to use defaults and make // use of the existing getnetworkhashps handler. gnhpsCmd := dcrjson.NewGetNetworkHashPSCmd(nil, nil) networkHashesPerSecIface, err := handleGetNetworkHashPS(s, gnhpsCmd, closeChan) if err != nil { return nil, err } networkHashesPerSec, ok := networkHashesPerSecIface.(int64) if !ok { return nil, rpcInternalError(err.Error(), fmt.Sprintf("Invalid type: %q", networkHashesPerSecIface)) } best := s.chain.BestSnapshot() nextStakeDiff, err := s.chain.CalcNextRequiredStakeDifficulty() if err != nil { return nil, rpcInternalError(err.Error(), "Could not calculate next stake difficulty") } result := dcrjson.GetMiningInfoResult{ Blocks: best.Height, CurrentBlockSize: best.BlockSize, CurrentBlockTx: best.NumTxns, Difficulty: getDifficultyRatio(best.Bits), StakeDifficulty: nextStakeDiff, Generate: s.server.cpuMiner.IsMining(), GenProcLimit: s.server.cpuMiner.NumWorkers(), HashesPerSec: int64(s.server.cpuMiner.HashesPerSecond()), NetworkHashPS: networkHashesPerSec, PooledTx: uint64(s.server.txMemPool.Count()), TestNet: cfg.TestNet, } return &result, nil } // handleGetNetTotals implements the getnettotals command. func handleGetNetTotals(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { totalBytesRecv, totalBytesSent := s.server.NetTotals() reply := &dcrjson.GetNetTotalsResult{ TotalBytesRecv: totalBytesRecv, TotalBytesSent: totalBytesSent, TimeMillis: time.Now().UTC().UnixNano() / int64(time.Millisecond), } return reply, nil } // handleGetNetworkHashPS implements the getnetworkhashps command. func handleGetNetworkHashPS(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Note: All valid error return paths should return an int64. Literal // zeros are inferred as int, and won't coerce to int64 because the // return value is an interface{}. c := cmd.(*dcrjson.GetNetworkHashPSCmd) // When the passed height is too high or zero, just return 0 now since // we can't reasonably calculate the number of network hashes per // second from invalid values. When it's negative, use the current // best block height. best := s.chain.BestSnapshot() endHeight := int64(-1) if c.Height != nil { endHeight = int64(*c.Height) } if endHeight > best.Height || endHeight == 0 { return int64(0), nil } if endHeight < 0 { endHeight = best.Height } // Calculate the number of blocks per retarget interval based on the // chain parameters. blocksPerRetarget := int64(s.server.chainParams.TargetTimespan / s.server.chainParams.TargetTimePerBlock) // Calculate the starting block height based on the passed number of // blocks. When the passed value is negative, use the last block the // difficulty changed as the starting height. Also make sure the // starting height is not before the beginning of the chain. numBlocks := int64(120) if c.Blocks != nil { numBlocks = int64(*c.Blocks) } var startHeight int64 if numBlocks <= 0 { startHeight = endHeight - ((endHeight % blocksPerRetarget) + 1) } else { startHeight = endHeight - numBlocks } if startHeight < 0 { startHeight = 0 } rpcsLog.Debugf("Calculating network hashes per second from %d to %d", startHeight, endHeight) // Find the min and max block timestamps as well as calculate the total // amount of work that happened between the start and end blocks. var minTimestamp, maxTimestamp time.Time totalWork := big.NewInt(0) for curHeight := startHeight; curHeight <= endHeight; curHeight++ { hash, err := s.chain.BlockHashByHeight(curHeight) if err != nil { context := "Failed to fetch block hash" return nil, rpcInternalError(err.Error(), context) } // Fetch the header from chain. header, err := s.chain.FetchHeader(hash) if err != nil { context := "Failed to fetch block header" return nil, rpcInternalError(err.Error(), context) } if curHeight == startHeight { minTimestamp = header.Timestamp maxTimestamp = minTimestamp } else { totalWork.Add(totalWork, blockchain.CalcWork(header.Bits)) if minTimestamp.After(header.Timestamp) { minTimestamp = header.Timestamp } if maxTimestamp.Before(header.Timestamp) { maxTimestamp = header.Timestamp } } } // Calculate the difference in seconds between the min and max block // timestamps and avoid division by zero in the case where there is no // time difference. timeDiff := int64(maxTimestamp.Sub(minTimestamp) / time.Second) if timeDiff == 0 { return int64(0), nil } hashesPerSec := new(big.Int).Div(totalWork, big.NewInt(timeDiff)) return hashesPerSec.Int64(), nil } // handleGetPeerInfo implements the getpeerinfo command. func handleGetPeerInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { peers := s.server.Peers() syncPeer := s.server.blockManager.SyncPeer() infos := make([]*dcrjson.GetPeerInfoResult, 0, len(peers)) for _, p := range peers { statsSnap := p.StatsSnapshot() info := &dcrjson.GetPeerInfoResult{ ID: statsSnap.ID, Addr: statsSnap.Addr, AddrLocal: p.LocalAddr().String(), Services: fmt.Sprintf("%08d", uint64(statsSnap.Services)), RelayTxes: !p.disableRelayTx, LastSend: statsSnap.LastSend.Unix(), LastRecv: statsSnap.LastRecv.Unix(), BytesSent: statsSnap.BytesSent, BytesRecv: statsSnap.BytesRecv, ConnTime: statsSnap.ConnTime.Unix(), PingTime: float64(statsSnap.LastPingMicros), TimeOffset: statsSnap.TimeOffset, Version: statsSnap.Version, SubVer: statsSnap.UserAgent, Inbound: statsSnap.Inbound, StartingHeight: statsSnap.StartingHeight, CurrentHeight: statsSnap.LastBlock, BanScore: int32(p.banScore.Int()), SyncNode: p == syncPeer, } if p.LastPingNonce() != 0 { wait := float64(time.Since(statsSnap.LastPingTime).Nanoseconds()) // We actually want microseconds. info.PingWait = wait / 1000 } infos = append(infos, info) } return infos, nil } // handleGetRawMempool implements the getrawmempool command. func handleGetRawMempool(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetRawMempoolCmd) // Choose the type to filter the results by based on the provided param. // A filter type of nil means no filtering. var filterType *stake.TxType if c.TxType != nil { switch dcrjson.GetRawMempoolTxTypeCmd(*c.TxType) { case dcrjson.GRMRegular: filterType = new(stake.TxType) *filterType = stake.TxTypeRegular case dcrjson.GRMTickets: filterType = new(stake.TxType) *filterType = stake.TxTypeSStx case dcrjson.GRMVotes: filterType = new(stake.TxType) *filterType = stake.TxTypeSSGen case dcrjson.GRMRevocations: filterType = new(stake.TxType) *filterType = stake.TxTypeSSRtx case dcrjson.GRMAll: // Nothing to do default: return nil, rpcInvalidError("Invalid transaction "+ "type: %T", *c.TxType) } } // Return verbose results if requested. mp := s.server.txMemPool if c.Verbose != nil && *c.Verbose { return mp.RawMempoolVerbose(filterType), nil } // The response is simply an array of the transaction hashes if the // verbose flag is not set. descs := mp.TxDescs() hashStrings := make([]string, 0, len(descs)) for i := range descs { if filterType != nil && descs[i].Type != *filterType { continue } hashStrings = append(hashStrings, descs[i].Tx.Hash().String()) } return hashStrings, nil } // handleGetRawTransaction implements the getrawtransaction command. func handleGetRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetRawTransactionCmd) // Convert the provided transaction hash hex to a Hash. txHash, err := chainhash.NewHashFromStr(c.Txid) if err != nil { return nil, rpcDecodeHexError(c.Txid) } verbose := false if c.Verbose != nil { verbose = *c.Verbose != 0 } // Try to fetch the transaction from the memory pool and if that fails, // try the block database. var mtx *wire.MsgTx var blkHash *chainhash.Hash var blkHeight int64 tx, err := s.server.txMemPool.FetchTransaction(txHash, true) if err != nil { txIndex := s.server.txIndex if txIndex == nil { return nil, rpcInternalError("The transaction index "+ "must be enabled to query the blockchain "+ "(specify --txindex)", "Configuration") } // Look up the location of the transaction. blockRegion, err := txIndex.TxBlockRegion(*txHash) if err != nil { context := "Failed to retrieve transaction location" return nil, rpcInternalError(err.Error(), context) } if blockRegion == nil { return nil, rpcNoTxInfoError(txHash) } // Load the raw transaction bytes from the database. var txBytes []byte err = s.server.db.View(func(dbTx database.Tx) error { var err error txBytes, err = dbTx.FetchBlockRegion(blockRegion) return err }) if err != nil { return nil, rpcNoTxInfoError(txHash) } // When the verbose flag isn't set, simply return the serialized // transaction as a hex-encoded string. This is done here to // avoid deserializing it only to reserialize it again later. if !verbose { return hex.EncodeToString(txBytes), nil } // Grab the block height. blkHash = blockRegion.Hash blkHeight, err = s.chain.BlockHeightByHash(blkHash) if err != nil { context := "Failed to retrieve block height" return nil, rpcInternalError(err.Error(), context) } // Deserialize the transaction var msgTx wire.MsgTx err = msgTx.Deserialize(bytes.NewReader(txBytes)) if err != nil { context := "Failed to deserialize transaction" return nil, rpcInternalError(err.Error(), context) } mtx = &msgTx } else { // When the verbose flag isn't set, simply return the // network-serialized transaction as a hex-encoded string. if !verbose { // Note that this is intentionally not directly // returning because the first return value is a // string and it would result in returning an empty // string to the client instead of nothing (nil) in the // case of an error. mtxHex, err := messageToHex(tx.MsgTx()) if err != nil { return nil, err } return mtxHex, nil } mtx = tx.MsgTx() } // The verbose flag is set, so generate the JSON object and return it. var ( blkHeader *wire.BlockHeader blkHashStr string confirmations int64 ) if blkHash != nil { // Fetch the header from chain. header, err := s.chain.FetchHeader(blkHash) if err != nil { context := "Failed to fetch block header" return nil, rpcInternalError(err.Error(), context) } blkHeader = &header blkHashStr = blkHash.String() confirmations = 1 + s.chain.BestSnapshot().Height - blkHeight } rawTxn, err := createTxRawResult(s.server.chainParams, mtx, txHash.String(), 0, blkHeader, blkHashStr, blkHeight, confirmations) if err != nil { return nil, err } return *rawTxn, nil } // handleGetStakeDifficulty implements the getstakedifficulty command. func handleGetStakeDifficulty(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { best := s.chain.BestSnapshot() blockHeader, err := s.chain.HeaderByHeight(best.Height) if err != nil { rpcsLog.Errorf("Error getting block: %v", err) return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCDifficulty, Message: "Error getting stake difficulty: " + err.Error(), } } currentSdiff := dcrutil.Amount(blockHeader.SBits) nextSdiff, err := s.server.blockManager.CalcNextRequiredStakeDifficulty() if err != nil { return nil, rpcInternalError("Could not calculate next stake "+ "difficulty "+err.Error(), "") } nextSdiffAmount := dcrutil.Amount(nextSdiff) sDiffResult := &dcrjson.GetStakeDifficultyResult{ CurrentStakeDifficulty: currentSdiff.ToCoin(), NextStakeDifficulty: nextSdiffAmount.ToCoin(), } return sDiffResult, nil } // convertVersionMap translates a map[int]int into a sorted array of // VersionCount that contains the same information. func convertVersionMap(m map[int]int) []dcrjson.VersionCount { sorted := make([]dcrjson.VersionCount, 0, len(m)) order := make([]int, 0, len(m)) for k := range m { order = append(order, k) } sort.Ints(order) for _, v := range order { sorted = append(sorted, dcrjson.VersionCount{Version: uint32(v), Count: uint32(m[v])}) } return sorted } // handleGetStakeVersionInfo implements the getstakeversioninfo command. func handleGetStakeVersionInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { count := int32(1) c, ok := cmd.(*dcrjson.GetStakeVersionInfoCmd) if !ok { return nil, rpcInvalidError("Invalid type: %T", c) } if c.Count != nil { count = *c.Count if count <= 0 { return nil, rpcInvalidError("Count must be positive") } } snapshot := s.chain.BestSnapshot() interval := s.server.chainParams.StakeVersionInterval // Assemble JSON result. result := dcrjson.GetStakeVersionInfoResult{ CurrentHeight: snapshot.Height, Hash: snapshot.Hash.String(), Intervals: make([]dcrjson.VersionInterval, 0, count), } startHeight := snapshot.Height endHeight := s.chain.CalcWantHeight(interval, snapshot.Height) + 1 hash := &snapshot.Hash adjust := int32(1) // We are off by one on the initial iteration. for i := int32(0); i < count; i++ { numBlocks := int32(startHeight - endHeight) if numBlocks <= 0 { // Just return what we got. break } sv, err := s.chain.GetStakeVersions(hash, numBlocks+adjust) if err != nil { return nil, rpcInternalError(err.Error(), "handleGetStakeVersionInfo") } posVersions := make(map[int]int) voteVersions := make(map[int]int) for _, v := range sv { posVersions[int(v.StakeVersion)]++ for _, vote := range v.Votes { voteVersions[int(vote.Version)]++ } } versionInterval := dcrjson.VersionInterval{ StartHeight: endHeight, EndHeight: startHeight, PoSVersions: convertVersionMap(posVersions), VoteVersions: convertVersionMap(voteVersions), } result.Intervals = append(result.Intervals, versionInterval) // Adjust interval. endHeight -= interval startHeight = endHeight + interval adjust = 0 // Get prior block hash. hash, err = s.chain.BlockHashByHeight(startHeight - 1) if err != nil { return nil, rpcInternalError(err.Error(), "handleGetStakeVersionInfo") } } return result, nil } // handleGetStakeVersions implements the getstakeversions command. func handleGetStakeVersions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetStakeVersionsCmd) hash, err := chainhash.NewHashFromStr(c.Hash) if err != nil { return nil, rpcDecodeHexError(c.Hash) } if c.Count <= 0 { return nil, rpcInvalidError("Invalid parameter, count must " + "be > 0") } sv, err := s.chain.GetStakeVersions(hash, c.Count) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain stake versions") } result := dcrjson.GetStakeVersionsResult{ StakeVersions: make([]dcrjson.StakeVersions, 0, len(sv)), } for _, v := range sv { nsv := dcrjson.StakeVersions{ Hash: v.Hash.String(), Height: v.Height, BlockVersion: v.BlockVersion, StakeVersion: v.StakeVersion, Votes: make([]dcrjson.VersionBits, 0, len(v.Votes)), } for _, vote := range v.Votes { nsv.Votes = append(nsv.Votes, dcrjson.VersionBits{Version: vote.Version, Bits: vote.Bits}) } result.StakeVersions = append(result.StakeVersions, nsv) } return result, nil } // handleGetTicketPoolValue implements the getticketpoolvalue command. func handleGetTicketPoolValue(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { amt, err := s.server.blockManager.TicketPoolValue() if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain ticket pool value") } return amt.ToCoin(), nil } // handleGetVoteInfo implements the getvoteinfo command. func handleGetVoteInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c, ok := cmd.(*dcrjson.GetVoteInfoCmd) if !ok { return nil, rpcInvalidError("Invalid type: %T", c) } snapshot := s.chain.BestSnapshot() interval := int64(s.server.chainParams.RuleChangeActivationInterval) quorum := s.server.chainParams.RuleChangeActivationQuorum // Assemble JSON result. result := dcrjson.GetVoteInfoResult{ CurrentHeight: snapshot.Height, StartHeight: s.chain.CalcWantHeight(interval, snapshot.Height) + 1, EndHeight: s.chain.CalcWantHeight(interval, snapshot.Height) + interval, Hash: snapshot.Hash.String(), VoteVersion: c.Version, Quorum: quorum, } // We don't fail, we try to return the totals for this version. var err error result.TotalVotes, err = s.chain.CountVoteVersion(c.Version) if err != nil { return nil, rpcInternalError(err.Error(), "Could not count voter versions") } vi, err := s.chain.GetVoteInfo(&snapshot.Hash, c.Version) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain vote info") } result.Agendas = make([]dcrjson.Agenda, 0, len(vi.Agendas)) for _, agenda := range vi.Agendas { a := dcrjson.Agenda{ Id: agenda.Vote.Id, Description: agenda.Vote.Description, Mask: agenda.Vote.Mask, Choices: make([]dcrjson.Choice, 0, len(agenda.Vote.Choices)), StartTime: agenda.StartTime, ExpireTime: agenda.ExpireTime, } // Handle choices. for _, choice := range agenda.Vote.Choices { c := dcrjson.Choice{ Id: choice.Id, Description: choice.Description, Bits: choice.Bits, IsAbstain: choice.IsAbstain, IsNo: choice.IsNo, } a.Choices = append(a.Choices, c) } // Obtain status of agenda. state, err := s.chain.ThresholdState(&snapshot.Hash, c.Version, agenda.Vote.Id) if err != nil { return nil, err } // Save off status. a.Status = state.String() if state.State != blockchain.ThresholdStarted { // Append transformed agenda without progress. result.Agendas = append(result.Agendas, a) continue } counts, err := s.chain.GetVoteCounts(c.Version, agenda.Vote.Id) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain vote count") } // Calculate quorum. qmin := quorum totalNonAbstain := counts.Total - counts.TotalAbstain if totalNonAbstain < quorum { qmin = totalNonAbstain } a.QuorumProgress = float64(qmin) / float64(quorum) // Calculate choice progress. for k := range a.Choices { a.Choices[k].Count = counts.VoteChoices[k] a.Choices[k].Progress = float64(counts.VoteChoices[k]) / float64(counts.Total) } // Append transformed agenda. result.Agendas = append(result.Agendas, a) } return result, nil } // bigToLEUint256 returns the passed big integer as an unsigned 256-bit integer // encoded as little-endian bytes. Numbers which are larger than the max // unsigned 256-bit integer are truncated. func bigToLEUint256(n *big.Int) [uint256Size]byte { // Pad or truncate the big-endian big int to correct number of bytes. nBytes := n.Bytes() nlen := len(nBytes) pad := 0 start := 0 if nlen <= uint256Size { pad = uint256Size - nlen } else { start = nlen - uint256Size } var buf [uint256Size]byte copy(buf[pad:], nBytes[start:]) // Reverse the bytes to little endian and return them. for i := 0; i < uint256Size/2; i++ { buf[i], buf[uint256Size-1-i] = buf[uint256Size-1-i], buf[i] } return buf } // handleGetTxOut handles gettxout commands. func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.GetTxOutCmd) // Convert the provided transaction hash hex to a Hash. txHash, err := chainhash.NewHashFromStr(c.Txid) if err != nil { return nil, rpcDecodeHexError(c.Txid) } // If requested and the tx is available in the mempool try to fetch it // from there, otherwise attempt to fetch from the block database. var bestBlockHash string var confirmations int64 var txVersion uint16 var value int64 var scriptVersion uint16 var pkScript []byte var isCoinbase bool includeMempool := true if c.IncludeMempool != nil { includeMempool = *c.IncludeMempool } var txFromMempool *dcrutil.Tx if includeMempool { txFromMempool, _ = s.server.txMemPool.FetchTransaction(txHash, true) } if txFromMempool != nil { mtx := txFromMempool.MsgTx() if c.Vout > uint32(len(mtx.TxOut)-1) { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCInvalidTxVout, Message: "Output index number (vout) does not " + "exist for transaction.", } } txOut := mtx.TxOut[c.Vout] if txOut == nil { errStr := fmt.Sprintf("Output index: %d for txid: %s "+ "does not exist", c.Vout, txHash) return nil, rpcInternalError(errStr, "") } best := s.chain.BestSnapshot() bestBlockHash = best.Hash.String() confirmations = 0 txVersion = mtx.Version value = txOut.Value scriptVersion = txOut.Version pkScript = txOut.PkScript isCoinbase = blockchain.IsCoinBaseTx(mtx) } else { entry, err := s.chain.FetchUtxoEntry(txHash) if err != nil { return nil, rpcNoTxInfoError(txHash) } // To match the behavior of the reference client, return nil // (JSON null) if the transaction output is spent by another // transaction already in the main chain. Mined transactions // that are spent by a mempool transaction are not affected by // this. if entry == nil || entry.IsOutputSpent(c.Vout) { return nil, nil } best := s.chain.BestSnapshot() bestBlockHash = best.Hash.String() confirmations = 1 + best.Height - entry.BlockHeight() txVersion = entry.TxVersion() value = entry.AmountByIndex(c.Vout) scriptVersion = entry.ScriptVersionByIndex(c.Vout) pkScript = entry.PkScriptByIndex(c.Vout) isCoinbase = entry.IsCoinBase() } // Disassemble script into single line printable format. The // disassembled string will contain [error] inline if the script // doesn't fully parse, so ignore the error here. script := pkScript disbuf, _ := txscript.DisasmString(script) // Get further info about the script. Ignore the error here since an // error means the script couldn't parse and there is no additional // information about it anyways. scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(scriptVersion, script, s.server.chainParams) addresses := make([]string, len(addrs)) for i, addr := range addrs { addresses[i] = addr.EncodeAddress() } txOutReply := &dcrjson.GetTxOutResult{ BestBlock: bestBlockHash, Confirmations: confirmations, Value: dcrutil.Amount(value).ToUnit(dcrutil.AmountCoin), Version: int32(txVersion), ScriptPubKey: dcrjson.ScriptPubKeyResult{ Asm: disbuf, Hex: hex.EncodeToString(pkScript), ReqSigs: int32(reqSigs), Type: scriptClass.String(), Addresses: addresses, }, Coinbase: isCoinbase, } return txOutReply, nil } // pruneOldBlockTemplates prunes all old block templates from the templatePool // map. Must be called with the RPC workstate locked to avoid races to the map. func pruneOldBlockTemplates(s *rpcServer, bestHeight int64) { pool := s.templatePool for rootHash, blkData := range pool { height := int64(blkData.msgBlock.Header.Height) if height < bestHeight-getworkExpirationDiff { delete(pool, rootHash) } } } // handleGetWorkRequest is a helper for handleGetWork which deals with // generating and returning work to the caller. // // This function MUST be called with the RPC workstate locked. func handleGetWorkRequest(s *rpcServer) (interface{}, error) { state := s.workState // Generate a new block template when the current best block has // changed or the transactions in the memory pool have been updated and // it has been at least one minute since the last template was // generated. lastTxUpdate := s.server.txMemPool.LastUpdated() latestHash, latestHeight := s.server.blockManager.chainState.Best() msgBlock := state.msgBlock // The current code pulls down a new template every second, however // with a large mempool this will be pretty excruciating sometimes. It // should examine whether or not a new template needs to be created // based on the votes present every second or so, and then, if needed, // generate a new block template. TODO cj if msgBlock == nil || state.prevHash == nil || !state.prevHash.IsEqual(latestHash) || (state.lastTxUpdate != lastTxUpdate && time.Now().After(state.lastGenerated.Add(time.Second))) { // Reset the extra nonce and clear all expired cached template // variations if the best block changed. if state.prevHash != nil && !state.prevHash.IsEqual(latestHash) { state.extraNonce = 0 pruneOldBlockTemplates(s, latestHeight) } // Reset the previous best hash the block template was // generated against so any errors below cause the next // invocation to try again. state.prevHash = nil // Choose a payment address at random. payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] template, err := NewBlockTemplate(s.policy, s.server, payToAddr) if err != nil { context := "Failed to create new block template" return nil, rpcInternalError(err.Error(), context) } if template == nil { // This happens if the template is returned nil because // there are not enough voters on HEAD and there is // currently an unsuitable parent cached template to // try building off of. context := "Failed to create new block template: not " + "enough voters and failed to find a suitable " + "parent template to build from" return nil, rpcInternalError("internal error", context) } templateCopy := deepCopyBlockTemplate(template) msgBlock = templateCopy.Block // Update work state to ensure another block template isn't // generated until needed. state.msgBlock = msgBlock state.lastGenerated = time.Now() state.lastTxUpdate = lastTxUpdate state.prevHash = latestHash rpcsLog.Debugf("Generated block template (timestamp %v, extra "+ "nonce %d, target %064x, merkle root %s)", msgBlock.Header.Timestamp, state.extraNonce, blockchain.CompactToBig(msgBlock.Header.Bits), msgBlock.Header.MerkleRoot) } else { if msgBlock == nil { context := "Failed to create new block template, " + "no previous state" return nil, rpcInternalError("internal error", context) } // At this point, there is a saved block template and a new // request for work was made, but either the available // transactions haven't change or it hasn't been long enough to // trigger a new block template to be generated. So, update the // existing block template and track the variations so each // variation can be regenerated if a caller finds an answer and // makes a submission against it. templateCopy := deepCopyBlockTemplate(&BlockTemplate{ Block: msgBlock, }) msgBlock = templateCopy.Block // Update the time of the block template to the current time // while accounting for the median time of the past several // blocks per the chain consensus rules. err := UpdateBlockTime(msgBlock, s.server.blockManager) if err != nil { return nil, rpcInternalError(err.Error(), "Failed to update block time") } if templateCopy.Height > 1 { // Increment the extra nonce and update the block template // with the new value by regenerating the coinbase script and // setting the merkle root to the new value. ens := getCoinbaseExtranonces(msgBlock) state.extraNonce++ ens[0]++ err := UpdateExtraNonce(msgBlock, latestHeight+1, ens) if err != nil { errStr := fmt.Sprintf("Failed to update extra nonce: "+ "%v", err) return nil, rpcInternalError(errStr, "") } } rpcsLog.Debugf("Updated block template (timestamp %v, extra "+ "nonce %d, target %064x, merkle root %s)", msgBlock.Header.Timestamp, state.extraNonce, blockchain.CompactToBig(msgBlock.Header.Bits), msgBlock.Header.MerkleRoot) } // In order to efficiently store the variations of block templates that // have been provided to callers, save a pointer to the block as well // as the modified signature script keyed by the merkle root. This // information, along with the data that is included in a work // submission, is used to rebuild the block before checking the // submitted solution. coinbaseTx := msgBlock.Transactions[0] // Create the new merkleRootPair key which is MerkleRoot + StakeRoot var merkleRootPair [merkleRootPairSize]byte copy(merkleRootPair[:chainhash.HashSize], msgBlock.Header.MerkleRoot[:]) copy(merkleRootPair[chainhash.HashSize:], msgBlock.Header.StakeRoot[:]) if msgBlock.Header.Height > 1 { s.templatePool[merkleRootPair] = &workStateBlockInfo{ msgBlock: msgBlock, pkScript: coinbaseTx.TxOut[1].PkScript, } } else { s.templatePool[merkleRootPair] = &workStateBlockInfo{ msgBlock: msgBlock, } } // Serialize the block header into a buffer large enough to hold the // the block header and the internal blake256 padding that is added and // retuned as part of the data below. For reference: // data[116] --> nBits // data[136] --> Timestamp // data[140] --> nonce data := make([]byte, 0, getworkDataLen) buf := bytes.NewBuffer(data) err := msgBlock.Header.Serialize(buf) if err != nil { errStr := fmt.Sprintf("Failed to serialize data: %v", err) return nil, rpcInternalError(errStr, "") } // Expand the data slice to include the full data buffer and apply the // internal blake256 padding. This makes the data ready for callers to // make use of only the final chunk along with the midstate for the // rest. data = data[:getworkDataLen] copy(data[wire.MaxBlockHeaderPayload:], blake256Pad) // The final result reverses each of the fields to little endian. In // particular, the data, hash1, and midstate fields are treated as // arrays of uint32s (per the internal sha256 hashing state) which are // in big endian, and thus each 4 bytes is byte swapped. The target is // also in big endian, but it is treated as a uint256 and byte swapped // to little endian accordingly. // // The fact the fields are reversed in this way is rather odd and likey // an artifact of some legacy internal state in the reference // implementation, but it is required for compatibility. target := bigToLEUint256(blockchain.CompactToBig(msgBlock.Header.Bits)) reply := &dcrjson.GetWorkResult{ Data: hex.EncodeToString(data), Target: hex.EncodeToString(target[:]), } return reply, nil } // handleGetWorkSubmission is a helper for handleGetWork which deals with // the calling submitting work to be verified and processed. // // This function MUST be called with the RPC workstate locked. func handleGetWorkSubmission(s *rpcServer, hexData string) (interface{}, error) { // Ensure the provided data is sane. if len(hexData)%2 != 0 { hexData = "0" + hexData } data, err := hex.DecodeString(hexData) if err != nil { return false, rpcDecodeHexError(hexData) } if len(data) != getworkDataLen { return nil, rpcInvalidError("Argument must be %d bytes (not "+ "%d)", getworkDataLen, len(data)) } // Deserialize the block header from the data. var submittedHeader wire.BlockHeader bhBuf := bytes.NewReader(data[0:wire.MaxBlockHeaderPayload]) err = submittedHeader.Deserialize(bhBuf) if err != nil { return false, rpcInvalidError("Invalid block header: %v", err) } // Create the new merkleRootPair key which is MerkleRoot + StakeRoot var merkleRootPair [merkleRootPairSize]byte copy(merkleRootPair[:chainhash.HashSize], submittedHeader.MerkleRoot[:]) copy(merkleRootPair[chainhash.HashSize:], submittedHeader.StakeRoot[:]) // Look up the full block for the provided data based on the merkle // root. Return false to indicate the solve failed if it's not // available. blockInfo, ok := s.templatePool[merkleRootPair] if !ok { rpcsLog.Errorf("Block submitted via getwork has no matching "+ "template for merkle root %s", submittedHeader.MerkleRoot) return false, nil } // Reconstruct the block using the submitted header stored block info. // A temporary block is used because we will be mutating the contents // for the construction of the correct regular merkle tree. You must // also deep copy the block itself because it could be accessed outside // of the GW workstate mutexes once it gets submitted to the // blockchain. tempBlock := dcrutil.NewBlockDeepCopy(blockInfo.msgBlock) msgBlock := tempBlock.MsgBlock() msgBlock.Header = submittedHeader if msgBlock.Header.Height > 1 { pkScriptCopy := make([]byte, len(blockInfo.pkScript)) copy(pkScriptCopy, blockInfo.pkScript) msgBlock.Transactions[0].TxOut[1].PkScript = blockInfo.pkScript merkles := blockchain.BuildMerkleTreeStore(tempBlock.Transactions()) msgBlock.Header.MerkleRoot = *merkles[len(merkles)-1] } // The real block to submit, with a proper nonce and extraNonce. block := dcrutil.NewBlockDeepCopyCoinbase(msgBlock) // Ensure the submitted block hash is less than the target difficulty. err = blockchain.CheckProofOfWork(&block.MsgBlock().Header, activeNetParams.PowLimit) if err != nil { // Anything other than a rule violation is an unexpected error, // so return that error as an internal error. if _, ok := err.(blockchain.RuleError); !ok { return false, rpcInternalError("Unexpected error "+ "while checking proof of work: "+err.Error(), "") } rpcsLog.Errorf("Block submitted via getwork does not meet "+ "the required proof of work: %v", err) return false, nil } // Process this block using the same rules as blocks coming from other // nodes. This will in turn relay it to the network like normal. isOrphan, err := s.server.blockManager.ProcessBlock(block, blockchain.BFNone) if err != nil { // Anything other than a rule violation is an unexpected error, // so return that error as an internal error. if _, ok := err.(blockchain.RuleError); !ok { return false, rpcInternalError("Unexpected error "+ "while processing block: "+err.Error(), "") } rpcsLog.Infof("Block submitted via getwork rejected: %v", err) return false, nil } if isOrphan { rpcsLog.Infof("Block submitted via getwork rejected: an orphan building "+ "on parent %v", block.MsgBlock().Header.PrevBlock) return false, nil } // The block was accepted. rpcsLog.Infof("Block submitted via getwork accepted: %s", block.Hash()) return true, nil } // handleGetWork implements the getwork command. func handleGetWork(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { if s.server.cpuMiner.IsMining() { return nil, rpcMiscError("getwork polling is disallowed " + "while CPU mining is enabled. Please disable CPU " + "mining and try again.") } // Respond with an error if there are no addresses to pay the created // blocks to. if len(cfg.miningAddrs) == 0 { return nil, rpcInternalError("No payment addresses specified "+ "via --miningaddr", "Configuration") } // Return an error if there are no peers connected since there is no // way to relay a found block or receive transactions to work on. // However, allow this state when running in the regression test or // simulation test mode. if !cfg.SimNet && s.server.ConnectedCount() == 0 { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCClientNotConnected, Message: "Decred is not connected", } } // No point in generating or accepting work before the chain is synced. _, currentHeight := s.server.blockManager.chainState.Best() if currentHeight != 0 && !s.server.blockManager.IsCurrent() { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCClientInInitialDownload, Message: "Decred is downloading blocks...", } } c := cmd.(*dcrjson.GetWorkCmd) // Protect concurrent access from multiple RPC invocations for work // requests and submission. s.workState.Lock() defer s.workState.Unlock() // When the caller provides data, it is a submission of a supposedly // solved block that needs to be checked and submitted to the network // if valid. if c.Data != nil && *c.Data != "" { return handleGetWorkSubmission(s, *c.Data) } // No data was provided, so the caller is requesting work. return handleGetWorkRequest(s) } // handleHelp implements the help command. func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.HelpCmd) // Provide a usage overview of all commands when no specific command // was specified. var command string if c.Command != nil { command = *c.Command } if command == "" { usage, err := s.helpCacher.rpcUsage(false) if err != nil { context := "Failed to generate RPC usage" return nil, rpcInternalError(err.Error(), context) } return usage, nil } // Check that the command asked for is supported and implemented. Only // search the main list of handlers since help should not be provided // for commands that are unimplemented or related to wallet // functionality. if _, ok := rpcHandlers[command]; !ok { return nil, rpcInvalidError("Unknown command: %v", command) } // Get the help for the command. help, err := s.helpCacher.rpcMethodHelp(command) if err != nil { context := "Failed to generate help" return nil, rpcInternalError(err.Error(), context) } return help, nil } // handleLiveTickets implements the livetickets command. func handleLiveTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { lt, err := s.server.blockManager.chain.LiveTickets() if err != nil { return nil, rpcInternalError("Could not get live tickets "+ err.Error(), "") } ltString := make([]string, len(lt)) for i := range lt { ltString[i] = lt[i].String() } return dcrjson.LiveTicketsResult{Tickets: ltString}, nil } // handleMissedTickets implements the missedtickets command. func handleMissedTickets(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { mt, err := s.server.blockManager.chain.MissedTickets() if err != nil { return nil, rpcInternalError("Could not get missed tickets "+ err.Error(), "") } mtString := make([]string, len(mt)) for i, hash := range mt { mtString[i] = hash.String() } return dcrjson.MissedTicketsResult{Tickets: mtString}, nil } // handlePing implements the ping command. func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Ask server to ping \o_ nonce, err := wire.RandomUint64() if err != nil { return nil, rpcInternalError("Not sending ping - failed to "+ "generate nonce: "+err.Error(), "") } s.server.BroadcastMessage(wire.NewMsgPing(nonce)) return nil, nil } // handleRebroadcastMissed implements the rebroadcastmissed command. func handleRebroadcastMissed(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { hash, height := s.server.blockManager.chainState.Best() mt, err := s.server.blockManager.chain.MissedTickets() if err != nil { return nil, rpcInternalError("Could not get missed tickets "+ err.Error(), "") } stakeDiff, err := s.server.blockManager.CalcNextRequiredStakeDifficulty() if err != nil { return nil, rpcInternalError("Could not calculate next stake "+ "difficulty "+err.Error(), "") } missedTicketsNtfn := &blockchain.TicketNotificationsData{ Hash: *hash, Height: height, StakeDifficulty: stakeDiff, TicketsSpent: []chainhash.Hash{}, TicketsMissed: mt, TicketsNew: []chainhash.Hash{}, } s.ntfnMgr.NotifySpentAndMissedTickets(missedTicketsNtfn) return nil, nil } // handleRebroadcastWinners implements the rebroadcastwinners command. func handleRebroadcastWinners(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { hash, height := s.server.blockManager.chainState.Best() blocks, err := s.server.blockManager.GetGeneration(*hash) 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: *hash, BlockHeight: height, Tickets: winningTickets, } s.ntfnMgr.NotifyWinningTickets(ntfnData) } return nil, nil } // retrievedTx represents a transaction that was either loaded from the // transaction memory pool or from the database. When a transaction is loaded // from the database, it is loaded with the raw serialized bytes while the // mempool has the fully deserialized structure. This structure therefore will // have one of the two fields set depending on where is was retrieved from. // This is mainly done for efficiency to avoid extra serialization steps when // possible. type retrievedTx struct { txBytes []byte blkHash *chainhash.Hash // Only set when transaction is in a block. tx *dcrutil.Tx } // fetchInputTxos fetches the outpoints from all transactions referenced by the // inputs to the passed transaction by checking the transaction mempool first // then the transaction index for those already mined into blocks. func fetchInputTxos(s *rpcServer, tx *wire.MsgTx) (map[wire.OutPoint]wire.TxOut, error) { mp := s.server.txMemPool originOutputs := make(map[wire.OutPoint]wire.TxOut) voteTx := stake.IsSSGen(tx) for txInIndex, txIn := range tx.TxIn { // vote tx have null input for vin[0], // skip since it resolvces to an invalid transaction if voteTx && txInIndex == 0 { continue } // Attempt to fetch and use the referenced transaction from the // memory pool. origin := &txIn.PreviousOutPoint originTx, err := mp.FetchTransaction(&origin.Hash, true) if err == nil { txOuts := originTx.MsgTx().TxOut if origin.Index >= uint32(len(txOuts)) { errStr := fmt.Sprintf("unable to find output "+ "%v referenced from transaction %s:%d", origin, tx.TxHash(), txInIndex) return nil, rpcInternalError(errStr, "") } originOutputs[*origin] = *txOuts[origin.Index] continue } // Look up the location of the transaction. blockRegion, err := s.server.txIndex.TxBlockRegion(origin.Hash) if err != nil { context := "Failed to retrieve transaction location" return nil, rpcInternalError(err.Error(), context) } if blockRegion == nil { return nil, rpcNoTxInfoError(&origin.Hash) } // Load the raw transaction bytes from the database. var txBytes []byte err = s.server.db.View(func(dbTx database.Tx) error { var err error txBytes, err = dbTx.FetchBlockRegion(blockRegion) return err }) if err != nil { return nil, rpcNoTxInfoError(&origin.Hash) } // Deserialize the transaction var msgTx wire.MsgTx err = msgTx.Deserialize(bytes.NewReader(txBytes)) if err != nil { context := "Failed to deserialize transaction" return nil, rpcInternalError(err.Error(), context) } // Add the referenced output to the map. if origin.Index >= uint32(len(msgTx.TxOut)) { errStr := fmt.Sprintf("unable to find output %v "+ "referenced from transaction %s:%d", origin, tx.TxHash(), txInIndex) return nil, rpcInternalError(errStr, "") } originOutputs[*origin] = *msgTx.TxOut[origin.Index] } return originOutputs, nil } // createVinListPrevOut returns a slice of JSON objects for the inputs of the // passed transaction. func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.Params, vinExtra bool, filterAddrMap map[string]struct{}) ([]dcrjson.VinPrevOut, error) { // Coinbase transactions only have a single txin by definition. if blockchain.IsCoinBaseTx(mtx) { // Only include the transaction if the filter map is empty // because a coinbase input has no addresses and so would never // match a non-empty filter. if len(filterAddrMap) != 0 { return nil, nil } txIn := mtx.TxIn[0] vinList := make([]dcrjson.VinPrevOut, 1) vinList[0].Coinbase = hex.EncodeToString(txIn.SignatureScript) amountIn := dcrutil.Amount(txIn.ValueIn).ToCoin() vinList[0].AmountIn = &amountIn vinList[0].Sequence = txIn.Sequence return vinList, nil } // Use a dynamically sized list to accommodate the address filter. vinList := make([]dcrjson.VinPrevOut, 0, len(mtx.TxIn)) // Lookup all of the referenced transaction outputs needed to populate // the previous output information if requested. var originOutputs map[wire.OutPoint]wire.TxOut if vinExtra || len(filterAddrMap) > 0 { var err error originOutputs, err = fetchInputTxos(s, mtx) if err != nil { return nil, err } } // Stakebase transactions (votes) have two inputs: a null stake base // followed by an input consuming a ticket's stakesubmission. isSSGen := stake.IsSSGen(mtx) for i, txIn := range mtx.TxIn { // Handle only the null input of a stakebase differently. if isSSGen && i == 0 { amountIn := dcrutil.Amount(txIn.ValueIn).ToCoin() vinEntry := dcrjson.VinPrevOut{ Stakebase: hex.EncodeToString(txIn.SignatureScript), AmountIn: &amountIn, Sequence: txIn.Sequence, } vinList = append(vinList, vinEntry) // No previous outpoints to check against the address filter. continue } // The disassembled string will contain [error] inline if the // script doesn't fully parse, so ignore the error here. disbuf, _ := txscript.DisasmString(txIn.SignatureScript) // Create the basic input entry without the additional optional // previous output details which will be added later if // requested and available. prevOut := &txIn.PreviousOutPoint amountIn := dcrutil.Amount(txIn.ValueIn).ToCoin() vinEntry := dcrjson.VinPrevOut{ Txid: prevOut.Hash.String(), Vout: prevOut.Index, Tree: prevOut.Tree, AmountIn: &amountIn, BlockHeight: &txIn.BlockHeight, BlockIndex: &txIn.BlockIndex, Sequence: txIn.Sequence, ScriptSig: &dcrjson.ScriptSig{ Asm: disbuf, Hex: hex.EncodeToString(txIn.SignatureScript), }, } // Add the entry to the list now if it already passed the // filter since the previous output might not be available. passesFilter := len(filterAddrMap) == 0 if passesFilter { vinList = append(vinList, vinEntry) } // Only populate previous output information if requested and // available. if len(originOutputs) == 0 { continue } originTxOut, ok := originOutputs[*prevOut] if !ok { continue } // Ignore the error here since an error means the script // couldn't parse and there is no additional information about // it anyways. _, addrs, _, _ := txscript.ExtractPkScriptAddrs(originTxOut.Version, originTxOut.PkScript, chainParams) // Encode the addresses while checking if the address passes // the filter when needed. encodedAddrs := make([]string, len(addrs)) for j, addr := range addrs { encodedAddr := addr.EncodeAddress() encodedAddrs[j] = encodedAddr // No need to check the map again if the filter already // passes. if passesFilter { continue } if _, exists := filterAddrMap[encodedAddr]; exists { passesFilter = true } } // Ignore the entry if it doesn't pass the filter. if !passesFilter { continue } // Add entry to the list if it wasn't already done above. if len(filterAddrMap) != 0 { vinList = append(vinList, vinEntry) } // Update the entry with previous output information if // requested. if vinExtra { vinListEntry := &vinList[len(vinList)-1] vinListEntry.PrevOut = &dcrjson.PrevOut{ Addresses: encodedAddrs, Value: dcrutil.Amount(originTxOut.Value).ToCoin(), } } } return vinList, nil } // fetchMempoolTxnsForAddress queries the address index for all unconfirmed // transactions that involve the provided address. The results will be limited // by the number to skip and the number requested. func fetchMempoolTxnsForAddress(s *rpcServer, addr dcrutil.Address, numToSkip, numRequested uint32) ([]*dcrutil.Tx, uint32) { // There are no entries to return when there are less available than // the number being skipped. mpTxns := s.server.addrIndex.UnconfirmedTxnsForAddress(addr) numAvailable := uint32(len(mpTxns)) if numToSkip > numAvailable { return nil, numAvailable } // Filter the available entries based on the number to skip and number // requested. rangeEnd := numToSkip + numRequested if rangeEnd > numAvailable { rangeEnd = numAvailable } return mpTxns[numToSkip:rangeEnd], numToSkip } // handleSearchRawTransactions implements the searchrawtransactions command. func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { // Respond with an error if the address index is not enabled. addrIndex := s.server.addrIndex if addrIndex == nil { return nil, rpcInternalError("Address index must be "+ "enabled (--addrindex)", "Configuration") } // Override the flag for including extra previous output information in // each input if needed. c := cmd.(*dcrjson.SearchRawTransactionsCmd) vinExtra := false if c.VinExtra != nil { vinExtra = *c.VinExtra != 0 } // Including the extra previous output information requires the // transaction index. Currently the address index relies on the // transaction index, so this check is redundant, but it's better to be // safe in case the address index is ever changed to not rely on it. if vinExtra && s.server.txIndex == nil { return nil, rpcInternalError("Transaction index must be "+ "enabled (--txindex)", "Configuration") } // Attempt to decode the supplied address. addr, err := dcrutil.DecodeAddress(c.Address) if err != nil { return nil, rpcAddressKeyError("Could not decode address: %v", err) } // Override the default number of requested entries if needed. Also, // just return now if the number of requested entries is zero to avoid // extra work. numRequested := 100 if c.Count != nil { numRequested = *c.Count if numRequested < 0 { numRequested = 1 } } if numRequested == 0 { return nil, nil } // Override the default number of entries to skip if needed. var numToSkip int if c.Skip != nil { numToSkip = *c.Skip if numToSkip < 0 { numToSkip = 0 } } // Override the reverse flag if needed. var reverse bool if c.Reverse != nil { reverse = *c.Reverse } // Add transactions from mempool first if client asked for reverse // order. Otherwise, they will be added last (as needed depending on // the requested counts). // // NOTE: This code doesn't sort by dependency. This might be something // to do in the future for the client's convenience, or leave it to the // client. numSkipped := uint32(0) addressTxns := make([]retrievedTx, 0, numRequested) if reverse { // Transactions in the mempool are not in a block header yet, // so the block header field in the retieved transaction struct // is left nil. mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr, uint32(numToSkip), uint32(numRequested)) numSkipped += mpSkipped for _, tx := range mpTxns { addressTxns = append(addressTxns, retrievedTx{tx: tx}) } } // Fetch transactions from the database in the desired order if more // are needed. if len(addressTxns) < numRequested { err = s.server.db.View(func(dbTx database.Tx) error { regions, dbSkipped, err := addrIndex.TxRegionsForAddress( dbTx, addr, uint32(numToSkip)-numSkipped, uint32(numRequested-len(addressTxns)), reverse) if err != nil { return err } // Load the raw transaction bytes from the database. serializedTxns, err := dbTx.FetchBlockRegions(regions) if err != nil { return err } // Add the transaction and the hash of the block it is // contained in to the list. Note that the transaction // is left serialized here since the caller might have // requested non-verbose output and hence there would // be no point in deserializing it just to reserialize // it later. for i, serializedTx := range serializedTxns { addressTxns = append(addressTxns, retrievedTx{ txBytes: serializedTx, blkHash: regions[i].Hash, }) } numSkipped += dbSkipped return nil }) if err != nil { context := "Failed to load address index entries" return nil, rpcInternalError(err.Error(), context) } } // Add transactions from mempool last if client did not request reverse // order and the number of results is still under the number requested. if !reverse && len(addressTxns) < numRequested { // Transactions in the mempool are not in a block header yet, // so the block header field in the retieved transaction struct // is left nil. mpTxns, mpSkipped := fetchMempoolTxnsForAddress(s, addr, uint32(numToSkip)-numSkipped, uint32(numRequested- len(addressTxns))) numSkipped += mpSkipped for _, tx := range mpTxns { addressTxns = append(addressTxns, retrievedTx{tx: tx}) } } // Address has never been used if neither source yielded any results. if len(addressTxns) == 0 { return nil, rpcInternalError("No Txns available", "") } // Serialize all of the transactions to hex. hexTxns := make([]string, len(addressTxns)) for i := range addressTxns { // Simply encode the raw bytes to hex when the retrieved // transaction is already in serialized form. rtx := &addressTxns[i] if rtx.txBytes != nil { hexTxns[i] = hex.EncodeToString(rtx.txBytes) continue } // Serialize the transaction first and convert to hex when the // retrieved transaction is the deserialized structure. hexTxns[i], err = messageToHex(rtx.tx.MsgTx()) if err != nil { return nil, err } } // When not in verbose mode, simply return a list of serialized txns. if c.Verbose != nil && *c.Verbose == 0 { return hexTxns, nil } // Normalize the provided filter addresses (if any) to ensure there are // no duplicates. filterAddrMap := make(map[string]struct{}) if c.FilterAddrs != nil && len(*c.FilterAddrs) > 0 { for _, addr := range *c.FilterAddrs { filterAddrMap[addr] = struct{}{} } } // The verbose flag is set, so generate the JSON object and return it. best := s.chain.BestSnapshot() chainParams := s.server.chainParams srtList := make([]dcrjson.SearchRawTransactionsResult, len(addressTxns)) for i := range addressTxns { // The deserialized transaction is needed, so deserialize the // retrieved transaction if it's in serialized form (which will // be the case when it was lookup up from the database). // Otherwise, use the existing deserialized transaction. rtx := &addressTxns[i] var mtx *wire.MsgTx if rtx.tx == nil { // Deserialize the transaction. mtx = new(wire.MsgTx) err := mtx.Deserialize(bytes.NewReader(rtx.txBytes)) if err != nil { context := "Failed to deserialize transaction" return nil, rpcInternalError(err.Error(), context) } } else { mtx = rtx.tx.MsgTx() } result := &srtList[i] result.Hex = hexTxns[i] result.Txid = mtx.TxHash().String() result.Vin, err = createVinListPrevOut(s, mtx, chainParams, vinExtra, filterAddrMap) if err != nil { return nil, rpcInternalError(err.Error(), "Could not create vin list") } result.Vout = createVoutList(mtx, chainParams, filterAddrMap) result.Version = int32(mtx.Version) result.LockTime = mtx.LockTime // Transactions grabbed from the mempool aren't yet in a block, // so conditionally fetch block details here. This will be // reflected in the final JSON output (mempool won't have // confirmations or block information). var blkHeader *wire.BlockHeader var blkHashStr string var blkHeight int64 if blkHash := rtx.blkHash; blkHash != nil { // Fetch the header from chain. header, err := s.chain.FetchHeader(blkHash) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCBlockNotFound, Message: "Block not found", } } // Get the block height from chain. height, err := s.chain.BlockHeightByHash(blkHash) if err != nil { context := "Failed to obtain block height" return nil, rpcInternalError(err.Error(), context) } blkHeader = &header blkHashStr = blkHash.String() blkHeight = height } // Add the block information to the result if there is any. if blkHeader != nil { // This is not a typo, they are identical in Bitcoin // Core as well. result.Time = blkHeader.Timestamp.Unix() result.Blocktime = blkHeader.Timestamp.Unix() result.BlockHash = blkHashStr result.Confirmations = uint64(1 + best.Height - blkHeight) } } return srtList, nil } // handleSendRawTransaction implements the sendrawtransaction command. func handleSendRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.SendRawTransactionCmd) // Deserialize and send off to tx relay allowHighFees := *c.AllowHighFees hexStr := c.HexTx if len(hexStr)%2 != 0 { hexStr = "0" + hexStr } serializedTx, err := hex.DecodeString(hexStr) if err != nil { return nil, rpcDecodeHexError(hexStr) } msgtx := wire.NewMsgTx() err = msgtx.Deserialize(bytes.NewReader(serializedTx)) if err != nil { return nil, rpcDeserializationError("Could not decode Tx: %v", err) } tx := dcrutil.NewTx(msgtx) acceptedTxs, err := s.server.blockManager.ProcessTransaction(tx, false, false, allowHighFees) if err != nil { // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going // wrong, so log it as such. Otherwise, something really did // go wrong, so log it as an actual error. In both cases, a // JSON-RPC error is returned to the client with the // deserialization error code (to match bitcoind behavior). if _, ok := err.(mempool.RuleError); ok { err = fmt.Errorf("Rejected transaction %v: %v", tx.Hash(), err) rpcsLog.Debugf("%v", err) txRuleErr, ok := err.(mempool.TxRuleError) if ok { if txRuleErr.RejectCode == wire.RejectDuplicate { // return a dublicate tx error return nil, rpcDuplicateTxError("%v", err) } } // return a generic rule error return nil, rpcRuleError("%v", err) } err = fmt.Errorf("failed to process transaction %v: %v", tx.Hash(), err) rpcsLog.Errorf("%v", err) return nil, rpcDeserializationError("rejected: %v", err) } s.server.AnnounceNewTransactions(acceptedTxs) // Keep track of all the regular sendrawtransaction request txns so that // they can be rebroadcast if they don't make their way into a block. // // Note that votes are only valid for a specific block and are time // sensitive, so they should not be added to the rebroadcast logic. // // TODO: Ideally ticket purchases and revocations could be added to the // rebroadcast logic as well, however, they would need to be removed under // certain circumstances such as when the stake difficulty interval changes // and if a revocation is for a ticket that was missed, but then becomes // live again due to a reorg. All stake transactions are ignored here since // there is no clean infrastructure in place currently to handle those // removals and perpetually broadcasting transactions which are no longer // valid is not desirable. if txType := stake.DetermineTxType(msgtx); txType == stake.TxTypeRegular { iv := wire.NewInvVect(wire.InvTypeTx, tx.Hash()) s.server.AddRebroadcastInventory(iv, tx) } return tx.Hash().String(), nil } // handleSetGenerate implements the setgenerate command. func handleSetGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.SetGenerateCmd) // Disable generation regardless of the provided generate flag if the // maximum number of threads (goroutines for our purposes) is 0. // Otherwise enable or disable it depending on the provided flag. generate := c.Generate genProcLimit := -1 if c.GenProcLimit != nil { genProcLimit = *c.GenProcLimit } if genProcLimit == 0 { generate = false } if !generate { s.server.cpuMiner.Stop() } else { // Respond with an error if there are no addresses to pay the // created blocks to. if len(cfg.miningAddrs) == 0 { return nil, rpcInternalError("No payment addresses "+ "specified via --miningaddr", "Configuration") } // It's safe to call start even if it's already started. s.server.cpuMiner.SetNumWorkers(int32(genProcLimit)) s.server.cpuMiner.Start() } return nil, nil } // handleStop implements the stop command. func handleStop(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { select { case s.requestProcessShutdown <- struct{}{}: default: } return "dcrd stopping.", nil } // handleSubmitBlock implements the submitblock command. func handleSubmitBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.SubmitBlockCmd) // Deserialize the submitted block. hexStr := c.HexBlock if len(hexStr)%2 != 0 { hexStr = "0" + c.HexBlock } serializedBlock, err := hex.DecodeString(hexStr) if err != nil { return nil, rpcInternalError(err.Error(), "Block decode") } block, err := dcrutil.NewBlockFromBytes(serializedBlock) if err != nil { return nil, rpcInternalError(err.Error(), "Block decode") } _, err = s.server.blockManager.ProcessBlock(block, blockchain.BFNone) if err != nil { return fmt.Sprintf("rejected: %v", err), nil } rpcsLog.Infof("Accepted block %s via submitblock", block.Hash()) return nil, nil } // min gets the minimum amount from a slice of amounts. func min(s []dcrutil.Amount) dcrutil.Amount { if len(s) == 0 { return 0 } min := s[0] for i := range s { if s[i] < min { min = s[i] } } return min } // max gets the maximum amount from a slice of amounts. func max(s []dcrutil.Amount) dcrutil.Amount { max := dcrutil.Amount(0) for i := range s { if s[i] > max { max = s[i] } } return max } // mean gets the mean amount from a slice of amounts. func mean(s []dcrutil.Amount) dcrutil.Amount { sum := dcrutil.Amount(0) for i := range s { sum += s[i] } if len(s) == 0 { return 0 } return sum / dcrutil.Amount(len(s)) } // median gets the median amount from a slice of amounts. func median(s []dcrutil.Amount) dcrutil.Amount { if len(s) == 0 { return 0 } sort.Sort(dcrutil.AmountSorter(s)) middle := len(s) / 2 if len(s) == 0 { return 0 } else if (len(s) % 2) != 0 { return s[middle] } return (s[middle] + s[middle-1]) / 2 } // stdDev gets the standard deviation amount from a slice of amounts. func stdDev(s []dcrutil.Amount) dcrutil.Amount { var total float64 mean := mean(s) for i := range s { total += math.Pow(s[i].ToCoin()-mean.ToCoin(), 2) } if len(s)-1 == 0 { return 0 } v := total / float64(len(s)-1) // Not concerned with an error here, it'll return // zero if the amount is too small. amt, _ := dcrutil.NewAmount(math.Sqrt(v)) return amt } // feeInfoForMempool returns the fee information for the passed tx type in the // memory pool. func feeInfoForMempool(s *rpcServer, txType stake.TxType) *dcrjson.FeeInfoMempool { txDs := s.server.txMemPool.TxDescs() ticketFees := make([]dcrutil.Amount, 0, len(txDs)) for _, txD := range txDs { if txD.Type == txType { feePerKb := (dcrutil.Amount(txD.Fee)) * 1000 / dcrutil.Amount(txD.Tx.MsgTx().SerializeSize()) ticketFees = append(ticketFees, feePerKb) } } return &dcrjson.FeeInfoMempool{ Number: uint32(len(ticketFees)), Min: min(ticketFees).ToCoin(), Max: max(ticketFees).ToCoin(), Mean: mean(ticketFees).ToCoin(), Median: median(ticketFees).ToCoin(), StdDev: stdDev(ticketFees).ToCoin(), } } // calcFee calculates the fee of a transaction that has its fraud proofs // properly set. func calcFeePerKb(tx *dcrutil.Tx) dcrutil.Amount { var in dcrutil.Amount for _, txIn := range tx.MsgTx().TxIn { in += dcrutil.Amount(txIn.ValueIn) } var out dcrutil.Amount for _, txOut := range tx.MsgTx().TxOut { out += dcrutil.Amount(txOut.Value) } return ((in - out) * 1000) / dcrutil.Amount(tx.MsgTx().SerializeSize()) } // feeInfoForBlock fetches the ticket fee information for a given tx type in a // block. func ticketFeeInfoForBlock(s *rpcServer, height int64, txType stake.TxType) (*dcrjson.FeeInfoBlock, error) { bl, err := s.chain.BlockByHeight(height) if err != nil { return nil, err } txNum := 0 switch txType { case stake.TxTypeRegular: txNum = len(bl.MsgBlock().Transactions) - 1 case stake.TxTypeSStx: txNum = int(bl.MsgBlock().Header.FreshStake) case stake.TxTypeSSGen: txNum = int(bl.MsgBlock().Header.Voters) case stake.TxTypeSSRtx: txNum = int(bl.MsgBlock().Header.Revocations) } txFees := make([]dcrutil.Amount, txNum) itr := 0 if txType == stake.TxTypeRegular { for i, tx := range bl.Transactions() { // Skip the coin base. if i == 0 { continue } txFees[itr] = calcFeePerKb(tx) itr++ } } else { for _, stx := range bl.STransactions() { thisTxType := stake.DetermineTxType(stx.MsgTx()) if thisTxType == txType { txFees[itr] = calcFeePerKb(stx) itr++ } } } return &dcrjson.FeeInfoBlock{ Height: uint32(height), Number: uint32(txNum), Min: min(txFees).ToCoin(), Max: max(txFees).ToCoin(), Mean: mean(txFees).ToCoin(), Median: median(txFees).ToCoin(), StdDev: stdDev(txFees).ToCoin(), }, nil } // ticketFeeInfoForRange fetches the ticket fee information for a given range // from [start, end). func ticketFeeInfoForRange(s *rpcServer, start int64, end int64, txType stake.TxType) (*dcrjson.FeeInfoWindow, error) { hashes, err := s.chain.HeightRange(start, end) if err != nil { return nil, err } var txFees []dcrutil.Amount for i := range hashes { bl, err := s.chain.BlockByHash(&hashes[i]) if err != nil { return nil, err } if txType == stake.TxTypeRegular { for i, tx := range bl.Transactions() { // Skip the coin base. if i == 0 { continue } txFees = append(txFees, calcFeePerKb(tx)) } } else { for _, stx := range bl.STransactions() { thisTxType := stake.DetermineTxType(stx.MsgTx()) if thisTxType == txType { txFees = append(txFees, calcFeePerKb(stx)) } } } } return &dcrjson.FeeInfoWindow{ StartHeight: uint32(start), EndHeight: uint32(end), Number: uint32(len(txFees)), Min: min(txFees).ToCoin(), Max: max(txFees).ToCoin(), Mean: mean(txFees).ToCoin(), Median: median(txFees).ToCoin(), StdDev: stdDev(txFees).ToCoin(), }, nil } // handleTicketFeeInfo implements the ticketfeeinfo command. func handleTicketFeeInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.TicketFeeInfoCmd) s.server.blockManager.chainState.Lock() bestHeight := s.server.blockManager.chainState.newestHeight s.server.blockManager.chainState.Unlock() // Memory pool first. feeInfoMempool := feeInfoForMempool(s, stake.TxTypeSStx) // Blocks requested, descending from the chain tip. var feeInfoBlocks []dcrjson.FeeInfoBlock blocks := uint32(0) if c.Blocks != nil { blocks = *c.Blocks } if blocks > 0 { start := bestHeight end := bestHeight - int64(blocks) for i := start; i > end; i-- { feeInfo, err := ticketFeeInfoForBlock(s, i, stake.TxTypeSStx) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain ticket fee info") } feeInfoBlocks = append(feeInfoBlocks, *feeInfo) } } var feeInfoWindows []dcrjson.FeeInfoWindow windows := uint32(0) if c.Windows != nil { windows = *c.Windows } if windows > 0 { // The first window is special because it may not be finished. // Perform this first and return if it's the only window the // user wants. Otherwise, append and continue. winLen := s.server.chainParams.StakeDiffWindowSize lastChange := (bestHeight / winLen) * winLen feeInfo, err := ticketFeeInfoForRange(s, lastChange, bestHeight+1, stake.TxTypeSStx) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain ticket fee info") } feeInfoWindows = append(feeInfoWindows, *feeInfo) // We need data on windows from before this. Start from // the last adjustment and move backwards through window // lengths, calulating the fees data and appending it // each time. if windows > 1 { // Go down to the last height requested, except // in the case that the user has specified to // many windows. In that case, just proceed to the // first block. end := int64(-1) if lastChange-int64(windows)*winLen > end { end = lastChange - int64(windows)*winLen } for i := lastChange; i > end+winLen; i -= winLen { feeInfo, err := ticketFeeInfoForRange(s, i-winLen, i, stake.TxTypeSStx) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain ticket fee info") } feeInfoWindows = append(feeInfoWindows, *feeInfo) } } } return &dcrjson.TicketFeeInfoResult{ FeeInfoMempool: *feeInfoMempool, FeeInfoBlocks: feeInfoBlocks, FeeInfoWindows: feeInfoWindows, }, nil } // handleTicketsForAddress implements the ticketsforaddress command. func handleTicketsForAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.TicketsForAddressCmd) addr, err := dcrutil.DecodeAddress(c.Address) if err != nil { return nil, rpcInvalidError("Invalid address: %v", err) } tickets, err := s.server.blockManager.chain.TicketsWithAddress(addr) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain tickets") } ticketStrings := make([]string, len(tickets)) itr := 0 for _, ticket := range tickets { ticketStrings[itr] = ticket.String() itr++ } reply := &dcrjson.TicketsForAddressResult{ Tickets: ticketStrings, } return reply, nil } // handleTicketVWAP implements the ticketvwap command. func handleTicketVWAP(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.TicketVWAPCmd) // The default VWAP is for the past WorkDiffWindows * WorkDiffWindowSize // many blocks. _, bestHeight := s.server.blockManager.chainState.Best() var start uint32 if c.Start == nil { toEval := activeNetParams.WorkDiffWindows * activeNetParams.WorkDiffWindowSize startI64 := bestHeight - toEval // Use 1 as the first block if there aren't // enough blocks. if startI64 <= 0 { start = 1 } else { start = uint32(startI64) } } else { start = *c.Start } end := uint32(bestHeight) if c.End != nil { end = *c.End } if start > end { return nil, rpcInvalidError("Start height %v is beyond end "+ "height %v", start, end) } if end > uint32(bestHeight) { return nil, rpcInvalidError("End height %v is beyond "+ "blockchain tip height %v", end, bestHeight) } // Calculate the volume weighted average price of a ticket for the // given range. ticketNum := int64(0) totalValue := int64(0) for i := start; i <= end; i++ { blockHeader, err := s.chain.HeaderByHeight(int64(i)) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain header") } ticketNum += int64(blockHeader.FreshStake) totalValue += blockHeader.SBits * int64(blockHeader.FreshStake) } vwap := int64(0) if ticketNum > 0 { vwap = totalValue / ticketNum } return dcrutil.Amount(vwap).ToCoin(), nil } // handleTxFeeInfo implements the txfeeinfo command. func handleTxFeeInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.TxFeeInfoCmd) s.server.blockManager.chainState.Lock() bestHeight := s.server.blockManager.chainState.newestHeight s.server.blockManager.chainState.Unlock() // Memory pool first. feeInfoMempool := feeInfoForMempool(s, stake.TxTypeRegular) // Blocks requested, descending from the chain tip. var feeInfoBlocks []dcrjson.FeeInfoBlock blocks := uint32(0) if c.Blocks != nil { blocks = *c.Blocks } if blocks > 0 { start := bestHeight end := bestHeight - int64(blocks) for i := start; i > end; i-- { feeInfo, err := ticketFeeInfoForBlock(s, i, stake.TxTypeRegular) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain ticket fee info") } feeInfoBlocks = append(feeInfoBlocks, *feeInfo) } } // Get the fee info for the range requested, unless none is given. The // default range is for the past WorkDiffWindowSize many blocks. var feeInfoRange dcrjson.FeeInfoRange var start uint32 if c.RangeStart == nil { toEval := activeNetParams.WorkDiffWindowSize startI64 := bestHeight - toEval // Use 1 as the first block if there aren't enough blocks. if startI64 <= 0 { start = 1 } else { start = uint32(startI64) } } else { start = *c.RangeStart } end := uint32(bestHeight) if c.RangeEnd != nil { end = *c.RangeEnd } if start > end { return nil, rpcInvalidError("Start height %v is beyond end "+ "height %v", start, end) } if end > uint32(bestHeight) { return nil, rpcInvalidError("End height %v is beyond "+ "blockchain tip height %v", end, bestHeight) } feeInfo, err := ticketFeeInfoForRange(s, int64(start), int64(end+1), stake.TxTypeRegular) if err != nil { return nil, rpcInternalError(err.Error(), "Could not obtain ticket fee info") } feeInfoRange = dcrjson.FeeInfoRange{ Number: feeInfo.Number, Min: feeInfo.Min, Max: feeInfo.Max, Mean: feeInfo.Mean, Median: feeInfo.Median, StdDev: feeInfo.StdDev, } return &dcrjson.TxFeeInfoResult{ FeeInfoMempool: *feeInfoMempool, FeeInfoBlocks: feeInfoBlocks, FeeInfoRange: feeInfoRange, }, nil } // handleValidateAddress implements the validateaddress command. func handleValidateAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.ValidateAddressCmd) result := dcrjson.ValidateAddressChainResult{} addr, err := dcrutil.DecodeAddress(c.Address) if err != nil || !addr.IsForNet(s.server.chainParams) { // Return the default value (false) for IsValid. return result, nil } result.Address = addr.EncodeAddress() result.IsValid = true return result, nil } func verifyChain(s *rpcServer, level, depth int64) error { best := s.chain.BestSnapshot() finishHeight := best.Height - depth if finishHeight < 0 { finishHeight = 0 } rpcsLog.Infof("Verifying chain for %d blocks at level %d", best.Height-finishHeight, level) for height := best.Height; height > finishHeight; height-- { // Level 0 just looks up the block. block, err := s.chain.BlockByHeight(height) if err != nil { rpcsLog.Errorf("Verify is unable to fetch block at "+ "height %d: %v", height, err) return err } // Level 1 does basic chain sanity checks. if level > 0 { err := blockchain.CheckBlockSanity(block, s.server.timeSource, activeNetParams.Params) if err != nil { rpcsLog.Errorf("Verify is unable to validate "+ "block at hash %v height %d: %v", block.Hash(), height, err) return err } } } rpcsLog.Infof("Chain verify completed successfully") return nil } // handleVerifyChain implements the verifychain command. func handleVerifyChain(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.VerifyChainCmd) var checkLevel, checkDepth int64 if c.CheckLevel != nil { checkLevel = *c.CheckLevel } if c.CheckDepth != nil { checkDepth = *c.CheckDepth } err := verifyChain(s, checkLevel, checkDepth) return err == nil, nil } // handleVerifyMessage implements the verifymessage command. func handleVerifyMessage(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*dcrjson.VerifyMessageCmd) // Decode the provided address. addr, err := dcrutil.DecodeAddress(c.Address) if err != nil { return nil, rpcAddressKeyError("Could not decode address: %v", err) } // Only P2PKH addresses are valid for signing. if _, ok := addr.(*dcrutil.AddressPubKeyHash); !ok { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCType, Message: "Address is not a pay-to-pubkey-hash address", } } // Decode base64 signature. sig, err := base64.StdEncoding.DecodeString(c.Signature) if err != nil { return nil, &dcrjson.RPCError{ Code: dcrjson.ErrRPCParse.Code, Message: "Malformed base64 encoding: " + err.Error(), } } // Validate the signature - this just shows that it was valid at all. // we will compare it with the key next. var buf bytes.Buffer wire.WriteVarString(&buf, 0, "Decred Signed Message:\n") wire.WriteVarString(&buf, 0, c.Message) expectedMessageHash := chainhash.HashB(buf.Bytes()) pk, wasCompressed, err := chainec.Secp256k1.RecoverCompact(sig, expectedMessageHash) if err != nil { // Mirror Bitcoin Core behavior, which treats error in // RecoverCompact as invalid signature. return false, nil } // Reconstruct the pubkey hash. dcrPK := pk var serializedPK []byte if wasCompressed { serializedPK = dcrPK.SerializeCompressed() } else { serializedPK = dcrPK.SerializeUncompressed() } address, err := dcrutil.NewAddressSecpPubKey(serializedPK, activeNetParams.Params) if err != nil { // Again mirror Bitcoin Core behavior, which treats error in // public key reconstruction as invalid signature. return false, nil } // Return boolean if addresses match. return address.EncodeAddress() == c.Address, nil } // handleVersion implements the version command. func handleVersion(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { result := map[string]dcrjson.VersionResult{ "dcrdjsonrpcapi": { VersionString: jsonrpcSemverString, Major: jsonrpcSemverMajor, Minor: jsonrpcSemverMinor, Patch: jsonrpcSemverPatch, }, } 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) } } // 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, 0666); err != nil { return err } if err = ioutil.WriteFile(keyFile, key, 0600); err != nil { os.Remove(certFile) return err } rpcsLog.Infof("Done generating TLS certificates") return nil } // newRPCServer returns a new instance of the rpcServer struct. func newRPCServer(listenAddrs []string, 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) }