package rpc import ( "bytes" "encoding/hex" "fmt" "github.com/kaspanet/kaspad/blockdag" "github.com/kaspanet/kaspad/config" "github.com/kaspanet/kaspad/mining" "github.com/kaspanet/kaspad/rpcmodel" "github.com/kaspanet/kaspad/txscript" "github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util/daghash" "github.com/kaspanet/kaspad/wire" "github.com/pkg/errors" "math/rand" "strconv" "strings" "sync" "time" ) const ( // gbtNonceRange is two 64-bit big-endian hexadecimal integers which // represent the valid ranges of nonces returned by the getBlockTemplate // RPC. gbtNonceRange = "000000000000ffffffffffff" // gbtRegenerateSeconds is the number of seconds that must pass before // a new template is generated when the parent block hashes has not // changed and there have been changes to the available transactions // in the memory pool. gbtRegenerateSeconds = 60 ) 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", "parentblock", "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 = &rpcmodel.GetBlockTemplateResultAux{ Flags: hex.EncodeToString(builderScript(txscript. NewScriptBuilder(). AddData([]byte(mining.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"} ) // gbtWorkState houses state that is used in between multiple RPC invocations to // getBlockTemplate. type gbtWorkState struct { sync.Mutex lastTxUpdate time.Time lastGenerated time.Time tipHashes []*daghash.Hash minTimestamp time.Time template *mining.BlockTemplate notifyMap map[string]map[int64]chan struct{} timeSource blockdag.MedianTimeSource } // newGbtWorkState returns a new instance of a gbtWorkState with all internal // fields initialized and ready to use. func newGbtWorkState(timeSource blockdag.MedianTimeSource) *gbtWorkState { return &gbtWorkState{ notifyMap: make(map[string]map[int64]chan struct{}), timeSource: timeSource, } } // 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 } // handleGetBlockTemplate implements the getBlockTemplate command. func handleGetBlockTemplate(s *Server, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*rpcmodel.GetBlockTemplateCmd) request := c.Request // Set the default mode and override it if supplied. mode := "template" if request != nil && request.Mode != "" { mode = request.Mode } // No point in generating templates or processing proposals before // the DAG is synced. Note that we make a special check for when // we have nothing besides the genesis block (blueScore == 0), // because in that state IsCurrent may still return true. currentBlueScore := s.cfg.DAG.SelectedTipBlueScore() if (currentBlueScore != 0 && !s.cfg.SyncMgr.IsCurrent()) || (currentBlueScore == 0 && !s.cfg.shouldMineOnGenesis()) { return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCClientInInitialDownload, Message: "Kaspa is downloading blocks...", } } switch mode { case "template": return handleGetBlockTemplateRequest(s, request, closeChan) case "proposal": return handleGetBlockTemplateProposal(s, request) } return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCInvalidParameter, Message: "Invalid mode", } } // 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 *Server, request *rpcmodel.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(config.ActiveConfig().MiningAddrs) == 0 { return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCInternal.Code, Message: "A coinbase transaction has been requested, " + "but the server has not been configured with " + "any payment addresses via --miningaddr", } } // 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 !(config.ActiveConfig().RegressionTest || config.ActiveConfig().Simnet) && s.cfg.ConnMgr.ConnectedCount() == 0 { return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCClientNotConnected, Message: "Kaspa is not connected", } } // 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.cfg.DAG, useCoinbaseValue, 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 DAG, or new transactions have shown up and some time // has passed without finding a solution. func handleGetBlockTemplateLongPoll(s *Server, 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. parentHashes, lastGenerated, err := decodeLongPollID(longPollID) if err != nil { result, err := state.blockTemplateResult(s.cfg.DAG, 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. areHashesEqual := daghash.AreEqual(state.template.Block.Header.ParentHashes, parentHashes) if !areHashesEqual || 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 DAG. submitOld := areHashesEqual result, err := state.blockTemplateResult(s.cfg.DAG, useCoinbaseValue, &submitOld) if err != nil { state.Unlock() return nil, err } state.Unlock() return result, nil } // Register the parent hashes 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(parentHashes, 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 DAG. submitOld := areHashesEqual result, err := state.blockTemplateResult(s.cfg.DAG, useCoinbaseValue, &submitOld) if err != nil { return nil, err } return result, nil } // handleGetBlockTemplateProposal is a helper for handleGetBlockTemplate which // deals with block proposals. func handleGetBlockTemplateProposal(s *Server, request *rpcmodel.TemplateRequest) (interface{}, error) { hexData := request.Data if hexData == "" { return false, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCType, 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, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCDeserialization, Message: fmt.Sprintf("Data must be "+ "hexadecimal string (not %q)", hexData), } } var msgBlock wire.MsgBlock if err := msgBlock.Deserialize(bytes.NewReader(dataBytes)); err != nil { return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCDeserialization, Message: "Block decode failed: " + err.Error(), } } block := util.NewBlock(&msgBlock) // Ensure the block is building from the expected parent blocks. expectedParentHashes := s.cfg.DAG.TipHashes() parentHashes := block.MsgBlock().Header.ParentHashes if !daghash.AreEqual(expectedParentHashes, parentHashes) { return "bad-parentblk", nil } if err := s.cfg.DAG.CheckConnectBlockTemplate(block); err != nil { if !errors.As(err, &blockdag.RuleError{}) { errStr := fmt.Sprintf("Failed to process block proposal: %s", err) log.Error(errStr) return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCVerify, Message: errStr, } } log.Infof("Rejected block proposal: %s", err) return dagErrToGBTErrString(err), nil } return nil, nil } // dagErrToGBTErrString converts an error returned from kaspa to a string // which matches the reasons and format described in BIP0022 for rejection // reasons. func dagErrToGBTErrString(err error) string { // When the passed error is not a RuleError, just return a generic // rejected string with the error text. var ruleErr blockdag.RuleError if !errors.As(err, &ruleErr) { return "rejected: " + err.Error() } switch ruleErr.ErrorCode { case blockdag.ErrDuplicateBlock: return "duplicate" case blockdag.ErrBlockMassTooHigh: return "bad-blk-mass" case blockdag.ErrBlockVersionTooOld: return "bad-version" case blockdag.ErrInvalidTime: return "bad-time" case blockdag.ErrTimeTooOld: return "time-too-old" case blockdag.ErrTimeTooNew: return "time-too-new" case blockdag.ErrDifficultyTooLow: return "bad-diffbits" case blockdag.ErrUnexpectedDifficulty: return "bad-diffbits" case blockdag.ErrHighHash: return "high-hash" case blockdag.ErrBadMerkleRoot: return "bad-txnmrklroot" case blockdag.ErrFinalityPointTimeTooOld: return "finality-point-time-too-old" case blockdag.ErrNoTransactions: return "bad-txns-none" case blockdag.ErrNoTxInputs: return "bad-txns-noinputs" case blockdag.ErrTxMassTooHigh: return "bad-txns-mass" case blockdag.ErrBadTxOutValue: return "bad-txns-outputvalue" case blockdag.ErrDuplicateTxInputs: return "bad-txns-dupinputs" case blockdag.ErrBadTxInput: return "bad-txns-badinput" case blockdag.ErrMissingTxOut: return "bad-txns-missinginput" case blockdag.ErrUnfinalizedTx: return "bad-txns-unfinalizedtx" case blockdag.ErrDuplicateTx: return "bad-txns-duplicate" case blockdag.ErrOverwriteTx: return "bad-txns-overwrite" case blockdag.ErrImmatureSpend: return "bad-txns-maturity" case blockdag.ErrSpendTooHigh: return "bad-txns-highspend" case blockdag.ErrBadFees: return "bad-txns-fees" case blockdag.ErrTooManySigOps: return "high-sigops" case blockdag.ErrFirstTxNotCoinbase: return "bad-txns-nocoinbase" case blockdag.ErrMultipleCoinbases: return "bad-txns-multicoinbase" case blockdag.ErrBadCoinbasePayloadLen: return "bad-cb-length" case blockdag.ErrScriptMalformed: return "bad-script-malformed" case blockdag.ErrScriptValidation: return "bad-script-validate" case blockdag.ErrParentBlockUnknown: return "parent-blk-not-found" case blockdag.ErrInvalidAncestorBlock: return "bad-parentblk" case blockdag.ErrParentBlockNotCurrentTips: return "inconclusive-not-best-parentblk" } return "rejected: " + err.Error() } // 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(tipHashes []*daghash.Hash, lastGenerated time.Time) { // Notify anything that is waiting for a block template update from // hashes which are not the current tip hashes. tipHashesStr := daghash.JoinHashesStrings(tipHashes, "") for hashesStr, channels := range state.notifyMap { if hashesStr != tipHashesStr { for _, c := range channels { close(c) } delete(state.notifyMap, hashesStr) } } // 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[tipHashesStr] 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, tipHashesStr) } } // NotifyBlockAdded uses the newly-added block to notify any long poll // clients with a new block template when their existing block template is // stale due to the newly added block. func (state *gbtWorkState) NotifyBlockAdded(tipHashes []*daghash.Hash) { spawn(func() { state.Lock() defer state.Unlock() state.notifyLongPollers(tipHashes, 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) { spawn(func() { state.Lock() defer state.Unlock() // No need to notify anything if no block templates have been generated // yet. if state.tipHashes == nil || state.lastGenerated.IsZero() { return } if time.Now().After(state.lastGenerated.Add(time.Second * gbtRegenerateSeconds)) { state.notifyLongPollers(state.tipHashes, lastUpdated) } }) } // templateUpdateChan returns a channel that will be closed once the block // template associated with the passed parent hashes 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(tipHashes []*daghash.Hash, lastGenerated int64) chan struct{} { tipHashesStr := daghash.JoinHashesStrings(tipHashes, "") // Either get the current list of channels waiting for updates about // changes to block template for the parent hashes or create a new one. channels, ok := state.notifyMap[tipHashesStr] if !ok { m := make(map[int64]chan struct{}) state.notifyMap[tipHashesStr] = 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 *Server, useCoinbaseValue bool) error { generator := s.cfg.Generator lastTxUpdate := generator.TxSource().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 tipHashes := s.cfg.DAG.TipHashes() template := state.template if template == nil || state.tipHashes == nil || !daghash.AreEqual(state.tipHashes, tipHashes) || (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.tipHashes = 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 util.Address if !useCoinbaseValue { payAddr = config.ActiveConfig().MiningAddrs[rand.Intn(len(config.ActiveConfig().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 := generator.NewBlockTemplate(payAddr) if err != nil { return internalRPCError("Failed to create new block "+ "template: "+err.Error(), "") } template = blkTemplate msgBlock = template.Block targetDifficulty = fmt.Sprintf("%064x", util.CompactToBig(msgBlock.Header.Bits)) // Get the minimum allowed timestamp for the block based on the // median timestamp of the last several blocks per the DAG // consensus rules. minTimestamp := s.cfg.DAG.NextBlockMinimumTime() // Update work state to ensure another block template isn't // generated until needed. state.template = template state.lastGenerated = time.Now() state.lastTxUpdate = lastTxUpdate state.tipHashes = tipHashes state.minTimestamp = minTimestamp log.Debugf("Generated block template (timestamp %s, "+ "target %s, merkle root %s)", msgBlock.Header.Timestamp, targetDifficulty, msgBlock.Header.HashMerkleRoot) // Notify any clients that are long polling about the new // template. state.notifyLongPollers(tipHashes, 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 := config.ActiveConfig().MiningAddrs[rand.Intn(len(config.ActiveConfig().MiningAddrs))] // Update the block coinbase output of the template to // pay to the randomly selected payment address. scriptPubKey, err := txscript.PayToAddrScript(payToAddr) if err != nil { context := "Failed to create pay-to-addr script" return internalRPCError(err.Error(), context) } template.Block.Transactions[util.CoinbaseTransactionIndex].TxOut[0].ScriptPubKey = scriptPubKey template.ValidPayAddress = true // Update the merkle root. block := util.NewBlock(template.Block) hashMerkleTree := blockdag.BuildHashMerkleTreeStore(block.Transactions()) template.Block.Header.HashMerkleRoot = hashMerkleTree.Root() } // Set locals for convenience. msgBlock = template.Block targetDifficulty = fmt.Sprintf("%064x", util.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 DAG consensus rules. generator.UpdateBlockTime(msgBlock) msgBlock.Header.Nonce = 0 log.Debugf("Updated block template (timestamp %s, "+ "target %s)", msgBlock.Header.Timestamp, targetDifficulty) } return nil } // blockTemplateResult returns the current block template associated with the // state as a rpcmodel.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(dag *blockdag.BlockDAG, useCoinbaseValue bool, submitOld *bool) (*rpcmodel.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 // block templates that will be delayed on other nodes. template := state.template msgBlock := template.Block header := &msgBlock.Header adjustedTime := state.timeSource.AdjustedTime() maxTime := adjustedTime.Add(time.Second * time.Duration(dag.TimestampDeviationTolerance)) if header.Timestamp.After(maxTime) { return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCOutOfRange, Message: fmt.Sprintf("The template time is after the "+ "maximum allowed time for a block - template "+ "time %s, maximum time %s", 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([]rpcmodel.GetBlockTemplateResultTx, 0, numTx-1) txIndex := make(map[daghash.TxID]int64, numTx) for i, tx := range msgBlock.Transactions { txID := tx.TxID() txIndex[*txID] = 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.TxID]; 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, internalRPCError(err.Error(), context) } resultTx := rpcmodel.GetBlockTemplateResultTx{ Data: hex.EncodeToString(txBuf.Bytes()), ID: txID.String(), Depends: depends, Mass: template.TxMasses[i], Fee: template.Fees[i], } transactions = append(transactions, resultTx) } // 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", util.CompactToBig(header.Bits)) longPollID := encodeLongPollID(state.tipHashes, state.lastGenerated) reply := rpcmodel.GetBlockTemplateResult{ Bits: strconv.FormatInt(int64(header.Bits), 16), CurTime: header.Timestamp.Unix(), Height: template.Height, ParentHashes: daghash.Strings(header.ParentHashes), MassLimit: wire.MaxMassPerBlock, Transactions: transactions, AcceptedIDMerkleRoot: header.AcceptedIDMerkleRoot.String(), UTXOCommitment: header.UTXOCommitment.String(), Version: header.Version, LongPollID: longPollID, 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[util.CoinbaseTransactionIndex].TxOut[0].Value } else { // Ensure the template has a valid payment address associated // with it when a full coinbase is requested. if !template.ValidPayAddress { return nil, &rpcmodel.RPCError{ Code: rpcmodel.ErrRPCInternal.Code, Message: "A coinbase transaction has been " + "requested, but the server has not " + "been configured with any payment " + "addresses via --miningaddr", } } // Serialize the transaction for conversion to hex. tx := msgBlock.Transactions[util.CoinbaseTransactionIndex] txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) if err := tx.Serialize(txBuf); err != nil { context := "Failed to serialize transaction" return nil, internalRPCError(err.Error(), context) } resultTx := rpcmodel.GetBlockTemplateResultTx{ Data: hex.EncodeToString(txBuf.Bytes()), ID: tx.TxID().String(), Depends: []int64{}, Mass: template.TxMasses[0], Fee: template.Fees[0], } reply.CoinbaseTxn = &resultTx } return &reply, nil } // encodeLongPollID encodes the passed details into an ID that can be used to // uniquely identify a block template. func encodeLongPollID(parentHashes []*daghash.Hash, lastGenerated time.Time) string { return fmt.Sprintf("%s-%d", daghash.JoinHashesStrings(parentHashes, ""), lastGenerated.Unix()) } // decodeLongPollID 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 // parent blocks hashes for the associated template and the time the associated // template was generated. func decodeLongPollID(longPollID string) ([]*daghash.Hash, int64, error) { fields := strings.Split(longPollID, "-") if len(fields) != 2 { return nil, 0, errors.New("decodeLongPollID: invalid number of fields") } parentHashesStr := fields[0] if len(parentHashesStr)%daghash.HashSize != 0 { return nil, 0, errors.New("decodeLongPollID: invalid parent hashes format") } numberOfHashes := len(parentHashesStr) / daghash.HashSize parentHashes := make([]*daghash.Hash, 0, numberOfHashes) for i := 0; i < len(parentHashesStr); i += daghash.HashSize { hash, err := daghash.NewHashFromStr(parentHashesStr[i : i+daghash.HashSize]) if err != nil { return nil, 0, errors.Errorf("decodeLongPollID: NewHashFromStr: %s", err) } parentHashes = append(parentHashes, hash) } lastGenerated, err := strconv.ParseInt(fields[1], 10, 64) if err != nil { return nil, 0, errors.Errorf("decodeLongPollID: Cannot parse timestamp %s: %s", fields[1], err) } return parentHashes, lastGenerated, nil }