diff --git a/rpcserver.go b/rpcserver.go index 4f808c012..eba7e0474 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -65,6 +65,11 @@ const ( hash1Len = (1 + ((btcwire.HashSize + 8) / fastsha256.BlockSize)) * fastsha256.BlockSize + // 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 @@ -73,6 +78,14 @@ const ( ) var ( + // 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 @@ -1547,11 +1560,36 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo // // This function MUST be called with the state locked. func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld *bool) (*btcjson.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 := state.template + msgBlock := template.block + header := &msgBlock.Header + curTime := time.Now() + if curTime.Before(state.minTimestamp) { + return nil, btcjson.Error{ + Code: btcjson.ErrOutOfRange.Code, + Message: fmt.Sprintf("The local time is before the "+ + "minimum allowed time for a block - current "+ + "time %v, minimum time %v", curTime, + state.minTimestamp), + } + } + maxTime := curTime.Add(time.Second * btcchain.MaxTimeOffsetSeconds) + if header.Timestamp.After(maxTime) { + return nil, btcjson.Error{ + Code: btcjson.ErrOutOfRange.Code, + Message: fmt.Sprintf("The template time is after the "+ + "maximum allowed time for a block - template "+ + "time %v, maximum time %v", curTime, 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. - template := state.template - msgBlock := template.block numTx := len(msgBlock.Transactions) transactions := make([]btcjson.GetBlockTemplateResultTx, 0, numTx-1) txIndex := make(map[btcwire.ShaHash]int64, numTx) @@ -1600,13 +1638,15 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld transactions = append(transactions, resultTx) } - // Generate the block template reply. - header := &msgBlock.Header + // 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", btcchain.CompactToBig(header.Bits)) templateID := encodeTemplateID(state.prevHash, state.lastGenerated) reply := btcjson.GetBlockTemplateResult{ Bits: strconv.FormatInt(int64(header.Bits), 16), - CurTime: time.Now().Unix(), + CurTime: curTime.Unix(), Height: template.height, PreviousHash: header.PrevBlock.String(), SigOpLimit: btcchain.MaxSigOpsPerBlock, @@ -1616,6 +1656,10 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld LongPollID: templateID, SubmitOld: submitOld, Target: targetDifficulty, + MinTime: state.minTimestamp.Unix(), + MaxTime: maxTime.Unix(), + Mutable: gbtMutableFields, + NonceRange: gbtNonceRange, Capabilities: gbtCapabilities, } if useCoinbaseValue {