diff --git a/rpcserver.go b/rpcserver.go index 588f0c81e5..c9eb0d14bb 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -80,6 +80,12 @@ var ( Flags: hex.EncodeToString(btcscript.NewScriptBuilder(). AddData([]byte(coinbaseFlags)).Script()), } + + // gbtCapabilities describes additional capabilities returned with a + // block template generated by the getblocktemplate RPC. It is + // declared here to avoid the overhead of creating the slice on every + // invocation for constant data. + gbtCapabilities = []string{"proposal"} ) // Errors @@ -1614,8 +1620,8 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld Version: header.Version, LongPollID: templateID, SubmitOld: submitOld, - MinTime: state.minTimestamp.Unix(), Target: targetDifficulty, + Capabilities: gbtCapabilities, } if useCoinbaseValue { reply.CoinbaseAux = gbtCoinbaseAux @@ -1832,9 +1838,166 @@ func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateReques return state.blockTemplateResult(useCoinbaseValue, nil) } +// chainErrToGBTErrString converts an error returned from btcchain to a string +// which matches the reasons and format described in BIP0022 for rejection +// reasons. +func chainErrToGBTErrString(err error) string { + // When the passed error is not a RuleError, just return a generic + // rejected string with the error text. + ruleErr, ok := err.(btcchain.RuleError) + if !ok { + return "rejected: " + err.Error() + } + + switch ruleErr.ErrorCode { + case btcchain.ErrDuplicateBlock: + return "duplicate" + case btcchain.ErrBlockTooBig: + return "bad-block-size" + case btcchain.ErrBlockVersionTooOld: + return "bad-version" + case btcchain.ErrInvalidTime: + return "bad-time" + case btcchain.ErrTimeTooOld: + return "time-too-old" + case btcchain.ErrTimeTooNew: + return "time-too-new" + case btcchain.ErrDifficultyTooLow: + return "bad-diffbits" + case btcchain.ErrUnexpectedDifficulty: + return "bad-diffbits" + case btcchain.ErrHighHash: + return "high-hash" + case btcchain.ErrBadMerkleRoot: + return "bad-txnmrklroot" + case btcchain.ErrBadCheckpoint: + return "bad-checkpoint" + case btcchain.ErrForkTooOld: + return "fork-too-old" + case btcchain.ErrNoTransactions: + return "bad-txns-none" + case btcchain.ErrTooManyTransactions: + return "bad-txns-toomany" + case btcchain.ErrNoTxInputs: + return "bad-txns-noinputs" + case btcchain.ErrNoTxOutputs: + return "bad-txns-nooutputs" + case btcchain.ErrTxTooBig: + return "bad-txns-size" + case btcchain.ErrBadTxOutValue: + return "bad-txns-outputvalue" + case btcchain.ErrDuplicateTxInputs: + return "bad-txns-dupinputs" + case btcchain.ErrBadTxInput: + return "bad-txns-badinput" + case btcchain.ErrMissingTx: + return "bad-txns-missinginput" + case btcchain.ErrUnfinalizedTx: + return "bad-txns-unfinalizedtx" + case btcchain.ErrDuplicateTx: + return "bad-txns-duplicate" + case btcchain.ErrOverwriteTx: + return "bad-txns-overwrite" + case btcchain.ErrImmatureSpend: + return "bad-txns-maturity" + case btcchain.ErrDoubleSpend: + return "bad-txns-dblspend" + case btcchain.ErrSpendTooHigh: + return "bad-txns-highspend" + case btcchain.ErrBadFees: + return "bad-txns-fees" + case btcchain.ErrTooManySigOps: + return "high-sigops" + case btcchain.ErrFirstTxNotCoinbase: + return "bad-txns-nocoinbase" + case btcchain.ErrMultipleCoinbases: + return "bad-txns-multicoinbase" + case btcchain.ErrBadCoinbaseScriptLen: + return "bad-cb-length" + case btcchain.ErrBadCoinbaseValue: + return "bad-cb-value" + case btcchain.ErrMissingCoinbaseHeight: + return "bad-cb-height" + case btcchain.ErrBadCoinbaseHeight: + return "bad-cb-height" + case btcchain.ErrScriptMalformed: + return "bad-script-malformed" + case btcchain.ErrScriptValidation: + return "bad-script-validate" + } + + return "rejected: " + err.Error() +} + +// handleGetBlockTemplateProposal is a helper for handleGetBlockTemplate which +// deals with block proposals. +// +// See https://en.bitcoin.it/wiki/BIP_0023 for more details. +func handleGetBlockTemplateProposal(s *rpcServer, request *btcjson.TemplateRequest) (interface{}, error) { + hexData := request.Data + if hexData == "" { + return false, btcjson.Error{ + Code: btcjson.ErrType.Code, + Message: fmt.Sprintf("data must contain the " + + "hex-encoded serialized block that is being " + + "proposed"), + } + } + + // Ensure the provided data is sane and deserialize the proposed block. + if len(hexData)%2 != 0 { + hexData = "0" + hexData + } + dataBytes, err := hex.DecodeString(hexData) + if err != nil { + return false, btcjson.Error{ + Code: btcjson.ErrDeserialization.Code, + Message: fmt.Sprintf("data must be "+ + "hexadecimal string (not %q)", hexData), + } + } + var msgBlock btcwire.MsgBlock + if err := msgBlock.Deserialize(bytes.NewReader(dataBytes)); err != nil { + return nil, btcjson.Error{ + Code: btcjson.ErrDeserialization.Code, + Message: "Block decode failed: " + err.Error(), + } + } + block := btcutil.NewBlock(&msgBlock) + + // Ensure the block is building from the expected previous block. + expectedPrevHash, _ := s.server.blockManager.chainState.Best() + prevHash := &block.MsgBlock().Header.PrevBlock + if expectedPrevHash == nil || !expectedPrevHash.IsEqual(prevHash) { + return "bad-prevblk", nil + } + + flags := btcchain.BFDryRun | btcchain.BFNoPoWCheck + isOrphan, err := s.server.blockManager.ProcessBlock(block, flags) + if err != nil { + if _, ok := err.(btcchain.RuleError); !ok { + rpcsLog.Errorf("Failed to process block proposal: %v", + err) + return nil, btcjson.Error{ + Code: -25, // ErrRpcVerify + Message: err.Error(), + } + } + + rpcsLog.Infof("Rejected block proposal: %v", err) + return chainErrToGBTErrString(err), nil + } + if isOrphan { + return "orphan", nil + } + + return nil, nil +} + // handleGetBlockTemplate implements the getblocktemplate command. // -// See https://en.bitcoin.it/wiki/BIP_0022 for more details. +// See https://en.bitcoin.it/wiki/BIP_0022 and +// https://en.bitcoin.it/wiki/BIP_0023 for more details. func handleGetBlockTemplate(s *rpcServer, cmd btcjson.Cmd, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*btcjson.GetBlockTemplateCmd) request := c.Request @@ -1845,11 +2008,11 @@ func handleGetBlockTemplate(s *rpcServer, cmd btcjson.Cmd, closeChan <-chan stru mode = request.Mode } - // The only supported mode is currently "template". Use a switch to - // make other modes easier to implement. switch mode { case "template": return handleGetBlockTemplateRequest(s, request, closeChan) + case "proposal": + return handleGetBlockTemplateProposal(s, request) } return nil, btcjson.Error{