Skip to content

Commit

Permalink
Create generate RPC command to close btcsuite#362
Browse files Browse the repository at this point in the history
Create GenerateCmd and GenerateResult in btcjson v2. Updated tests
to check GenerateCmd.

Update chaincfg/params.go with a new bool in Params, GenerateSupported,
with true values in SimNetParams and RegressionNetParams and false in
the others.

Add generateNBlocks function to cpuminer.go and handleGenerate
function to rpcserver.go.

Update documentation for the RPC calls.
  • Loading branch information
aakselrod committed Apr 28, 2015
1 parent 2e433b0 commit dc6edc2
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 0 deletions.
14 changes: 14 additions & 0 deletions btcjson/v2/btcjson/btcdextcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ func NewDebugLevelCmd(levelSpec string) *DebugLevelCmd {
}
}

// GenerateCmd defines the generate JSON-RPC command.
type GenerateCmd struct {
NumBlocks uint32 `json:"numblocks"`
}

// NewGenerateCmd returns a new instance which can be used to issue a generate
// JSON-RPC command.
func NewGenerateCmd(numBlocks uint32) *GenerateCmd {
return &GenerateCmd{
NumBlocks: numBlocks,
}
}

// GetBestBlockCmd defines the getbestblock JSON-RPC command.
type GetBestBlockCmd struct{}

Expand All @@ -82,6 +95,7 @@ func init() {

MustRegisterCmd("debuglevel", (*DebugLevelCmd)(nil), flags)
MustRegisterCmd("node", (*NodeCmd)(nil), flags)
MustRegisterCmd("generate", (*GenerateCmd)(nil), flags)
MustRegisterCmd("getbestblock", (*GetBestBlockCmd)(nil), flags)
MustRegisterCmd("getcurrentnet", (*GetCurrentNetCmd)(nil), flags)
}
13 changes: 13 additions & 0 deletions btcjson/v2/btcjson/btcdextcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ func TestBtcdExtCmds(t *testing.T) {
ConnectSubCmd: btcjson.String("temp"),
},
},
{
name: "generate",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("generate", 1)
},
staticCmd: func() interface{} {
return btcjson.NewGenerateCmd(1)
},
marshalled: `{"jsonrpc":"1.0","method":"generate","params":[1],"id":1}`,
unmarshalled: &btcjson.GenerateCmd{
NumBlocks: 1,
},
},
{
name: "getbestblock",
newCmd: func() (interface{}, error) {
Expand Down
5 changes: 5 additions & 0 deletions btcjson/v2/btcjson/chainsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ package btcjson

import "encoding/json"

// GenerateResult models the data returned from the generate command.
type GenerateResult struct {
Blockhashes []string `json:"blockhashes"`
}

// GetBlockVerboseResult models the data from the getblock command when the
// verbose flag is set. When the verbose flag is not set, getblock returns a
// hex-encoded string.
Expand Down
5 changes: 5 additions & 0 deletions chaincfg/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type Params struct {
PowLimitBits uint32
SubsidyHalvingInterval int32
ResetMinDifficulty bool
GenerateSupported bool

// Checkpoints ordered from oldest to newest.
Checkpoints []Checkpoint
Expand Down Expand Up @@ -108,6 +109,7 @@ var MainNetParams = Params{
PowLimitBits: 0x1d00ffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: false,
GenerateSupported: false,

// Checkpoints ordered from oldest to newest.
Checkpoints: []Checkpoint{
Expand Down Expand Up @@ -171,6 +173,7 @@ var RegressionNetParams = Params{
PowLimitBits: 0x207fffff,
SubsidyHalvingInterval: 150,
ResetMinDifficulty: true,
GenerateSupported: true,

// Checkpoints ordered from oldest to newest.
Checkpoints: nil,
Expand Down Expand Up @@ -217,6 +220,7 @@ var TestNet3Params = Params{
PowLimitBits: 0x1d00ffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: true,
GenerateSupported: false,

// Checkpoints ordered from oldest to newest.
Checkpoints: []Checkpoint{
Expand Down Expand Up @@ -269,6 +273,7 @@ var SimNetParams = Params{
PowLimitBits: 0x207fffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: true,
GenerateSupported: true,

// Checkpoints ordered from oldest to newest.
Checkpoints: nil,
Expand Down
110 changes: 110 additions & 0 deletions cpuminer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package main

import (
"errors"
"fmt"
"math/rand"
"runtime"
Expand Down Expand Up @@ -508,3 +509,112 @@ func newCPUMiner(s *server) *CPUMiner {
updateHashes: make(chan uint64),
}
}

// generateNBlocks generates the requested number of blocks. It is self
// contained in that it creates block templates and attempts to solve them while
// detecting when it is performing stale work and reacting accordingly by
// generating a new block template. When a block is solved, it is submitted.
// The function returns a list of the hashes of generated blocks.
func (m *CPUMiner) generateNBlocks(n uint32) ([]*wire.ShaHash, error) {
m.Lock()

// Respond with an error if there's virtually 0 chance of CPU-mining a block.
if !m.server.chainParams.GenerateSupported {
m.Unlock()
return nil, errors.New("No support for `generate` on the current " +
"network, " + m.server.chainParams.Net.String() +
", as it's unlikely to be possible to CPU-mine a block.")
}

// Respond with an error if server is already mining.
if m.started {
m.Unlock()
return nil, errors.New("Server is already CPU mining. Please call " +
"`setgenerate 0` before calling discrete `generate` commands.")
}

m.started = true

m.speedMonitorQuit = make(chan struct{})
m.wg.Add(1)
go m.speedMonitor()

m.Unlock()

minrLog.Tracef("Generating %d blocks", n)

i := uint32(0)
blockHashes := make([]*wire.ShaHash, n, n)

// Start a ticker which is used to signal checks for stale work and
// updates to the speed monitor.
ticker := time.NewTicker(time.Second * hashUpdateSecs)
defer ticker.Stop()

for {
// Wait until there is a connection to at least one other peer
// since there is no way to relay a found block or receive
// transactions to work on when there are no connected peers.
if m.server.ConnectedCount() == 0 {
time.Sleep(time.Second)
continue
}

// Read updateNumWorkers in case someone tries a `setgenerate` while
// we're generating. We can ignore it as the `generate` RPC call only
// uses 1 worker.
select {
case <-m.updateNumWorkers:
default:
}

// No point in searching for a solution before the chain is
// synced. Also, grab the same lock as used for block
// submission, since the current block will be changing and
// this would otherwise end up building a new block template on
// a block that is in the process of becoming stale.
m.submitBlockLock.Lock()
_, curHeight := m.server.blockManager.chainState.Best()
if curHeight != 0 && !m.server.blockManager.IsCurrent() {
m.submitBlockLock.Unlock()
time.Sleep(time.Second)
continue
}

// Choose a payment address at random.
rand.Seed(time.Now().UnixNano())
payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))]

// Create a new block template using the available transactions
// in the memory pool as a source of transactions to potentially
// include in the block.
template, err := NewBlockTemplate(m.server.txMemPool, payToAddr)
m.submitBlockLock.Unlock()
if err != nil {
errStr := fmt.Sprintf("Failed to create new block "+
"template: %v", err)
minrLog.Errorf(errStr)
continue
}

// Attempt to solve the block. The function will exit early
// with false when conditions that trigger a stale block, so
// a new block template can be generated. When the return is
// true a solution was found, so submit the solved block.
if m.solveBlock(template.block, curHeight+1, ticker, nil) {
block := btcutil.NewBlock(template.block)
m.submitBlock(block)
blockHashes[i] = block.Sha()
i++
if i == n {
minrLog.Tracef("Generated %d blocks", i)
m.Lock()
close(m.speedMonitorQuit)
m.wg.Wait()
m.started = false
m.Unlock()
return blockHashes, nil
}
}
}
}
13 changes: 13 additions & 0 deletions docs/json_rpc_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ The following is an overview of the RPC methods which are implemented by btcd, b
|3|[getcurrentnet](#getcurrentnet)|Y|Get bitcoin network btcd is running on.|None|
|4|[searchrawtransactions](#searchrawtransactions)|Y|Query for transactions related to a particular address.|None|
|5|[node](#node)|N|Attempts to add or remove a peer. |None|
|6|[generate](#generate)|N|When in simnet or regtest mode, generate a set number of blocks. |None|


<a name="ExtMethodDetails" />
Expand Down Expand Up @@ -623,6 +624,18 @@ The following is an overview of the RPC methods which are implemented by btcd, b

***

<a name="generate"/>

| | |
|---|---|
|Method|generate|
|Parameters|1. numblocks (int, required) - The number of blocks to generate |
|Description|When in simnet or regtest mode, generates a set number of blocks. |
|Returns|`[ (json array of strings)` <br/>&nbsp;&nbsp; `"blockhash", ... hash of the generated block` <br/>`]` |
[Return to Overview](#MethodOverview)<br />

***

<a name="WSExtMethods" />
### 7. Websocket Extension Methods (Websocket-specific)

Expand Down
45 changes: 45 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"debuglevel": handleDebugLevel,
"decoderawtransaction": handleDecodeRawTransaction,
"decodescript": handleDecodeScript,
"generate": handleGenerate,
"getaddednodeinfo": handleGetAddedNodeInfo,
"getbestblock": handleGetBestBlock,
"getbestblockhash": handleGetBestBlockHash,
Expand Down Expand Up @@ -784,6 +785,50 @@ func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{}
return reply, nil
}

// handleGenerate handles generate commands.
func handleGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
// Respond with an error if there are no addresses to pay the
// created blocks to.
if len(cfg.miningAddrs) == 0 {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: "No payment addresses specified " +
"via --miningaddr",
}
}

c := cmd.(*btcjson.GenerateCmd)

// Respond with an error if the client is requesting 0 blocks to be generated.
if c.NumBlocks == 0 {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: "Please request a nonzero number of blocks to generate.",
}
}

// Create a reply
reply := btcjson.GenerateResult{
Blockhashes: make([]string, c.NumBlocks, c.NumBlocks),
}

blockHashes, err := s.server.cpuMiner.generateNBlocks(c.NumBlocks)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInternal.Code,
Message: err.Error(),
}
}

// Mine the correct number of blocks, assigning the hex representation of the
// hash of each one to its place in the reply.
for i, hash := range blockHashes {
reply.Blockhashes[i] = hash.String()
}

return reply, nil
}

// handleGetAddedNodeInfo handles getaddednodeinfo commands.
func handleGetAddedNodeInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.GetAddedNodeInfoCmd)
Expand Down
9 changes: 9 additions & 0 deletions rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ var helpDescsEnUS = map[string]string{
"decodescript--synopsis": "Returns a JSON object with information about the provided hex-encoded script.",
"decodescript-hexscript": "Hex-encoded script",

// GenerateCmd help
"generate--synopsis": "Generates a set number of blocks (simnet or regtest only) and returns a JSON\n" +
" array of their hashes.",
"generate-numblocks": "Number of blocks to generate",

// GenerateResult help
"generateresult-blockhashes": "The hashes, in order, of blocks generated by the call",

// GetAddedNodeInfoResultAddr help.
"getaddednodeinforesultaddr-address": "The ip address for this DNS entry",
"getaddednodeinforesultaddr-connected": "The connection 'direction' (inbound/outbound/false)",
Expand Down Expand Up @@ -506,6 +514,7 @@ var rpcResultTypes = map[string][]interface{}{
"debuglevel": []interface{}{(*string)(nil), (*string)(nil)},
"decoderawtransaction": []interface{}{(*btcjson.TxRawDecodeResult)(nil)},
"decodescript": []interface{}{(*btcjson.DecodeScriptResult)(nil)},
"generate": []interface{}{(*btcjson.GenerateResult)(nil)},
"getaddednodeinfo": []interface{}{(*[]string)(nil), (*[]btcjson.GetAddedNodeInfoResult)(nil)},
"getbestblock": []interface{}{(*btcjson.GetBestBlockResult)(nil)},
"getbestblockhash": []interface{}{(*string)(nil)},
Expand Down

0 comments on commit dc6edc2

Please sign in to comment.