Implement BIP0023 getblocktemplate block proposals.

This commit implements block proposals as defined by BIP0023.

This is work towards #124.
This commit is contained in:
Dave Collins 2014-06-27 14:12:22 -05:00
parent db20f25ff7
commit 21050b4751

View file

@ -81,6 +81,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
@ -1609,8 +1615,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
@ -1827,9 +1833,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
@ -1840,11 +2003,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{