Skip to content

Commit

Permalink
Implement BIP0023 getblocktemplate block proposals.
Browse files Browse the repository at this point in the history
This commit implements block proposals as defined by BIP0023.

This is work towards btcsuite#124.
  • Loading branch information
davecgh committed Jul 7, 2014
1 parent 2feeea9 commit 72be977
Showing 1 changed file with 167 additions and 4 deletions.
171 changes: 167 additions & 4 deletions rpcserver.go
Expand Up @@ -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
Expand Down Expand Up @@ -1619,8 +1625,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
Expand Down Expand Up @@ -1837,9 +1843,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
Expand All @@ -1850,11 +2013,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{
Expand Down

0 comments on commit 72be977

Please sign in to comment.