From 806eab817c5b3b2fad5bf70ed4651572e4d2dcc0 Mon Sep 17 00:00:00 2001 From: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> Date: Tue, 12 May 2020 15:08:24 +0300 Subject: [PATCH] [NOD-820] When the node isn't synced, make getBlockTemplate return a boolean isSynced instead of an error (#716) * [NOD-820] Add IsSynced to GetBlockTemplateResult. * [NOD-820] Add isSynced to the help file. * [NOD-820] Add MineWhenNotSynced to the kaspaminer config. * [NOD-820] Implement miner MineWhenNotSynced logic. * [NOD-820] Fixed capitalization in an error message. --- cmd/kaspaminer/config.go | 21 +++++++++++---------- cmd/kaspaminer/main.go | 2 +- cmd/kaspaminer/mineloop.go | 23 ++++++++++++++++++----- rpcmodel/rpc_results.go | 1 + server/rpc/handle_get_block_template.go | 23 ++++++++++++----------- server/rpc/rpcserverhelp.go | 1 + 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/cmd/kaspaminer/config.go b/cmd/kaspaminer/config.go index 20375e805..5f87d947e 100644 --- a/cmd/kaspaminer/config.go +++ b/cmd/kaspaminer/config.go @@ -30,16 +30,17 @@ var ( ) type configFlags struct { - ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` - RPCUser string `short:"u" long:"rpcuser" description:"RPC username"` - RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"` - RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"` - RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"` - DisableTLS bool `long:"notls" description:"Disable TLS"` - Verbose bool `long:"verbose" short:"v" description:"Enable logging of RPC requests"` - NumberOfBlocks uint64 `short:"n" long:"numblocks" description:"Number of blocks to mine. If omitted, will mine until the process is interrupted."` - BlockDelay uint64 `long:"block-delay" description:"Delay for block submission (in milliseconds). This is used only for testing purposes."` - Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"` + ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` + RPCUser string `short:"u" long:"rpcuser" description:"RPC username"` + RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"` + RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"` + RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"` + DisableTLS bool `long:"notls" description:"Disable TLS"` + Verbose bool `long:"verbose" short:"v" description:"Enable logging of RPC requests"` + NumberOfBlocks uint64 `short:"n" long:"numblocks" description:"Number of blocks to mine. If omitted, will mine until the process is interrupted."` + BlockDelay uint64 `long:"block-delay" description:"Delay for block submission (in milliseconds). This is used only for testing purposes."` + MineWhenNotSynced bool `long:"mine-when-not-synced" description:"Mine even if the node is not synced with the rest of the network."` + Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"` config.NetworkFlags } diff --git a/cmd/kaspaminer/main.go b/cmd/kaspaminer/main.go index e8110503d..32bf524df 100644 --- a/cmd/kaspaminer/main.go +++ b/cmd/kaspaminer/main.go @@ -45,7 +45,7 @@ func main() { doneChan := make(chan struct{}) spawn(func() { - err = mineLoop(client, cfg.NumberOfBlocks, cfg.BlockDelay) + err = mineLoop(client, cfg.NumberOfBlocks, cfg.BlockDelay, cfg.MineWhenNotSynced) if err != nil { panic(errors.Errorf("Error in mine loop: %s", err)) } diff --git a/cmd/kaspaminer/mineloop.go b/cmd/kaspaminer/mineloop.go index df2fa9d12..58a537eeb 100644 --- a/cmd/kaspaminer/mineloop.go +++ b/cmd/kaspaminer/mineloop.go @@ -25,7 +25,7 @@ var hashesTried uint64 const logHashRateInterval = 10 * time.Second -func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64) error { +func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64, mineWhenNotSynced bool) error { errChan := make(chan error) templateStopChan := make(chan struct{}) @@ -35,7 +35,7 @@ func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64) err wg := sync.WaitGroup{} for i := uint64(0); numberOfBlocks == 0 || i < numberOfBlocks; i++ { foundBlock := make(chan *util.Block) - mineNextBlock(client, foundBlock, templateStopChan, errChan) + mineNextBlock(client, foundBlock, mineWhenNotSynced, templateStopChan, errChan) block := <-foundBlock templateStopChan <- struct{}{} wg.Add(1) @@ -80,13 +80,15 @@ func logHashRate() { }) } -func mineNextBlock(client *minerClient, foundBlock chan *util.Block, templateStopChan chan struct{}, errChan chan error) { +func mineNextBlock(client *minerClient, foundBlock chan *util.Block, mineWhenNotSynced bool, + templateStopChan chan struct{}, errChan chan error) { + newTemplateChan := make(chan *rpcmodel.GetBlockTemplateResult) spawn(func() { templatesLoop(client, newTemplateChan, errChan, templateStopChan) }) spawn(func() { - solveLoop(newTemplateChan, foundBlock, errChan) + solveLoop(newTemplateChan, foundBlock, mineWhenNotSynced, errChan) }) } @@ -207,12 +209,23 @@ func getBlockTemplate(client *minerClient, longPollID string) (*rpcmodel.GetBloc return client.GetBlockTemplate([]string{"coinbasetxn"}, longPollID) } -func solveLoop(newTemplateChan chan *rpcmodel.GetBlockTemplateResult, foundBlock chan *util.Block, errChan chan error) { +func solveLoop(newTemplateChan chan *rpcmodel.GetBlockTemplateResult, foundBlock chan *util.Block, + mineWhenNotSynced bool, errChan chan error) { + var stopOldTemplateSolving chan struct{} for template := range newTemplateChan { if stopOldTemplateSolving != nil { close(stopOldTemplateSolving) } + + if !template.IsSynced { + if !mineWhenNotSynced { + errChan <- errors.Errorf("got template with isSynced=false") + return + } + log.Warnf("Got template with isSynced=false") + } + stopOldTemplateSolving = make(chan struct{}) block, err := parseBlock(template) if err != nil { diff --git a/rpcmodel/rpc_results.go b/rpcmodel/rpc_results.go index 0517d525c..9b3da13b6 100644 --- a/rpcmodel/rpc_results.go +++ b/rpcmodel/rpc_results.go @@ -151,6 +151,7 @@ type GetBlockTemplateResult struct { CoinbaseTxn *GetBlockTemplateResultTx `json:"coinbaseTxn,omitempty"` CoinbaseValue *uint64 `json:"coinbaseValue,omitempty"` WorkID string `json:"workId,omitempty"` + IsSynced bool `json:"isSynced"` // Optional long polling from BIP 0022. LongPollID string `json:"longPollId,omitempty"` diff --git a/server/rpc/handle_get_block_template.go b/server/rpc/handle_get_block_template.go index 7e9a7da3a..ac615a596 100644 --- a/server/rpc/handle_get_block_template.go +++ b/server/rpc/handle_get_block_template.go @@ -71,6 +71,7 @@ type gbtWorkState struct { template *mining.BlockTemplate notifyMap map[string]map[int64]chan struct{} timeSource blockdag.TimeSource + isSynced bool } // newGbtWorkState returns a new instance of a gbtWorkState with all internal @@ -105,17 +106,6 @@ func handleGetBlockTemplate(s *Server, cmd interface{}, closeChan <-chan struct{ 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. - if !isSyncedForMining(s) { - return nil, &rpcmodel.RPCError{ - Code: rpcmodel.ErrRPCClientInInitialDownload, - Message: "Kaspa is downloading blocks...", - } - } - switch mode { case "template": return handleGetBlockTemplateRequest(s, request, closeChan) @@ -655,6 +645,15 @@ func (state *gbtWorkState) updateBlockTemplate(s *Server, useCoinbaseValue bool) // consensus rules. minTimestamp := s.cfg.DAG.NextBlockMinimumTime() + // Check whether this node is synced with the rest of of the + // network. There's almost never a good reason to mine on top + // of an unsynced DAG, and miners are generally expected not to + // mine when isSynced is false. + // This is not a straight-up error because the choice of whether + // to mine or not is the responsibility of the miner rather + // than the node's. + isSynced := isSyncedForMining(s) + // Update work state to ensure another block template isn't // generated until needed. state.template = template @@ -662,6 +661,7 @@ func (state *gbtWorkState) updateBlockTemplate(s *Server, useCoinbaseValue bool) state.lastTxUpdate = lastTxUpdate state.tipHashes = tipHashes state.minTimestamp = minTimestamp + state.isSynced = isSynced log.Debugf("Generated block template (timestamp %s, "+ "target %s, merkle root %s)", @@ -820,6 +820,7 @@ func (state *gbtWorkState) blockTemplateResult(dag *blockdag.BlockDAG, useCoinba Mutable: gbtMutableFields, NonceRange: gbtNonceRange, Capabilities: gbtCapabilities, + IsSynced: state.isSynced, } if useCoinbaseValue { diff --git a/server/rpc/rpcserverhelp.go b/server/rpc/rpcserverhelp.go index 00c61931c..2514d0204 100644 --- a/server/rpc/rpcserverhelp.go +++ b/server/rpc/rpcserverhelp.go @@ -331,6 +331,7 @@ var helpDescsEnUS = map[string]string{ "getBlockTemplateResult-nonceRange": "Two concatenated hex-encoded big-endian 64-bit integers which represent the valid ranges of nonces the miner may scan", "getBlockTemplateResult-capabilities": "List of server capabilities including 'proposal' to indicate support for block proposals", "getBlockTemplateResult-rejectReason": "Reason the proposal was invalid as-is (only applies to proposal responses)", + "getBlockTemplateResult-isSynced": "Whether this node is synced with the rest of of the network. Miners are generally expected not to mine when isSynced is false", // GetBlockTemplateCmd help. "getBlockTemplate--synopsis": "Returns a JSON object with information necessary to construct a block to mine or accepts a proposal to validate.\n" +