Create generate RPC command to close #362

Create GenerateCmd in btcjson v2. Update 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.

Create new flag, discreteMining, in CPUMiner struct.

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

Update documentation for the RPC calls.
This commit is contained in:
Alex Akselrod 2015-04-27 11:19:02 -04:00
parent 927a0e9c37
commit d26aaffb2b
7 changed files with 199 additions and 4 deletions

View file

@ -58,6 +58,19 @@ func NewDebugLevelCmd(levelSpec string) *DebugLevelCmd {
}
}
// GenerateCmd defines the generate JSON-RPC command.
type GenerateCmd struct {
NumBlocks uint32
}
// 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{}
@ -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)
}

View file

@ -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) {

View file

@ -63,6 +63,7 @@ type Params struct {
PowLimitBits uint32
SubsidyHalvingInterval int32
ResetMinDifficulty bool
GenerateSupported bool
// Checkpoints ordered from oldest to newest.
Checkpoints []Checkpoint
@ -108,6 +109,7 @@ var MainNetParams = Params{
PowLimitBits: 0x1d00ffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: false,
GenerateSupported: false,
// Checkpoints ordered from oldest to newest.
Checkpoints: []Checkpoint{
@ -171,6 +173,7 @@ var RegressionNetParams = Params{
PowLimitBits: 0x207fffff,
SubsidyHalvingInterval: 150,
ResetMinDifficulty: true,
GenerateSupported: true,
// Checkpoints ordered from oldest to newest.
Checkpoints: nil,
@ -217,6 +220,7 @@ var TestNet3Params = Params{
PowLimitBits: 0x1d00ffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: true,
GenerateSupported: false,
// Checkpoints ordered from oldest to newest.
Checkpoints: []Checkpoint{
@ -269,6 +273,7 @@ var SimNetParams = Params{
PowLimitBits: 0x207fffff,
SubsidyHalvingInterval: 210000,
ResetMinDifficulty: true,
GenerateSupported: true,
// Checkpoints ordered from oldest to newest.
Checkpoints: nil,

View file

@ -5,6 +5,7 @@
package main
import (
"errors"
"fmt"
"math/rand"
"runtime"
@ -54,6 +55,7 @@ type CPUMiner struct {
server *server
numWorkers uint32
started bool
discreteMining bool
submitBlockLock sync.Mutex
wg sync.WaitGroup
workerWg sync.WaitGroup
@ -394,8 +396,9 @@ func (m *CPUMiner) Start() {
m.Lock()
defer m.Unlock()
// Nothing to do if the miner is already running.
if m.started {
// Nothing to do if the miner is already running or if running in discrete
// mode (using GenerateNBlocks).
if m.started || m.discreteMining {
return
}
@ -418,8 +421,9 @@ func (m *CPUMiner) Stop() {
m.Lock()
defer m.Unlock()
// Nothing to do if the miner is not currently running.
if !m.started {
// Nothing to do if the miner is not currently running or if running in
// discrete mode (using GenerateNBlocks).
if !m.started || m.discreteMining {
return
}
@ -496,6 +500,102 @@ func (m *CPUMiner) NumWorkers() int32 {
return int32(m.numWorkers)
}
// 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.discreteMining {
m.Unlock()
return nil, errors.New("Server is already CPU mining. Please call " +
"`setgenerate 0` before calling discrete `generate` commands.")
}
m.started = true
m.discreteMining = 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 {
// 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:
}
// Grab the lock 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()
// 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.discreteMining = false
m.Unlock()
return blockHashes, nil
}
}
}
}
// newCPUMiner returns a new instance of a CPU miner for the provided server.
// Use Start to begin the mining process. See the documentation for CPUMiner
// type for more details.

View file

@ -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" />
@ -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 `numblocks` blocks. If blocks arrive from elsewhere, they are built upon but don't count toward the number of blocks to generate. Only generated blocks are returned. This RPC call will exit with an error if the server is already CPU mining, and will prevent the server from CPU mining for another command while it runs. |
|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)

View file

@ -132,6 +132,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"debuglevel": handleDebugLevel,
"decoderawtransaction": handleDecodeRawTransaction,
"decodescript": handleDecodeScript,
"generate": handleGenerate,
"getaddednodeinfo": handleGetAddedNodeInfo,
"getbestblock": handleGetBestBlock,
"getbestblockhash": handleGetBestBlockHash,
@ -783,6 +784,48 @@ 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 := make([]string, 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[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)

View file

@ -99,6 +99,12 @@ 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",
"generate--result0": "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)",
@ -506,6 +512,7 @@ var rpcResultTypes = map[string][]interface{}{
"debuglevel": []interface{}{(*string)(nil), (*string)(nil)},
"decoderawtransaction": []interface{}{(*btcjson.TxRawDecodeResult)(nil)},
"decodescript": []interface{}{(*btcjson.DecodeScriptResult)(nil)},
"generate": []interface{}{(*[]string)(nil)},
"getaddednodeinfo": []interface{}{(*[]string)(nil), (*[]btcjson.GetAddedNodeInfoResult)(nil)},
"getbestblock": []interface{}{(*btcjson.GetBestBlockResult)(nil)},
"getbestblockhash": []interface{}{(*string)(nil)},