diff --git a/blockmanager.go b/blockmanager.go index 6a569bf7fa..fe2141301c 100644 --- a/blockmanager.go +++ b/blockmanager.go @@ -61,6 +61,12 @@ const ( // do expensive lottery data look ups for these blocks. It is // equivalent to 24 hours of work on mainnet. maxLotteryDataBlockDelta = 288 + + // templateRegenSeconds 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 + // in the memory pool. + templateRegenSeconds = 15 ) // zeroHash is the zero value hash (all zeros). It is defined as a convenience. @@ -286,6 +292,13 @@ type setParentTemplateMsg struct { reply chan setParentTemplateResponse } +// RegenTemplateMsg is a message sent to regenerate a block template. +type RegenTemplateMsg struct{} + +// SetNextTemplateRegenMsg is a message sent to set a future time for the next +// block template regeneration. +type SetNextTemplateRegenMsg struct{} + // setParentTemplateResponse is a response sent to the reply channel of a // setParentTemplateMsg. type setParentTemplateResponse struct { @@ -409,6 +422,13 @@ type blockManager struct { lotteryDataBroadcast map[chainhash.Hash]struct{} lotteryDataBroadcastMutex sync.RWMutex + // the following fields handle block template regeneration. + templateChan chan interface{} + lastRegen time.Time + nextRegen time.Time + nextRegenUpdated bool + regenTicker *time.Ticker + cachedCurrentTemplate *BlockTemplate cachedParentTemplate *BlockTemplate AggressiveMining bool @@ -1929,6 +1949,70 @@ out: bmgrLog.Trace("Block handler done") } +// evaluateBlockTemplate generates a new block template if the current +// template has been invalidated. It must be run as a goroutine. +func (b *blockManager) evaluateBlockTemplate() { + b.regenTicker = time.NewTicker(time.Second) + b.nextRegen = b.server.txMemPool.LastUpdated(). + Add(time.Second * templateRegenSeconds) +out: + for { + select { + case msg := <-b.templateChan: + switch msg.(type) { + case RegenTemplateMsg: + // Choose a payment address at random. + payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))] + template, err := NewBlockTemplate(b.server.rpcServer.policy, + b.server, payToAddr) + if err != nil { + bmgrLog.Errorf("Failed to create new block template: %v", err) + } + + if template == nil { + // This happens if the template is returned nil because + // there are not enough voters on HEAD and there is + // currently an unsuitable parent cached template to + // try building off of. + bmgrLog.Error("Failed to create new block template: not " + + "enough voters and failed to find a suitable " + + "parent template to build from") + } + + // Update cached templates. + b.SetParentTemplate(b.cachedCurrentTemplate) + b.SetCurrentTemplate(template) + + // Set the next update time. + b.lastRegen = time.Now() + b.nextRegen = b.lastRegen.Add(time.Second * templateRegenSeconds) + b.nextRegenUpdated = false + + case SetNextTemplateRegenMsg: + // Extend the next template regeneration time. + if !b.nextRegenUpdated { + b.nextRegen = b.nextRegen.Add(time.Second * templateRegenSeconds) + b.nextRegenUpdated = true + } + } + case <-b.regenTicker.C: + // Assert the mempool has been updated since the last template regeneration. + if b.server.txMemPool.LastUpdated().After(b.lastRegen) { + // Regenerate block template when the threshold is met. + if b.server.txMemPool.LastUpdated().Equal(b.nextRegen) || + b.server.txMemPool.LastUpdated().After(b.nextRegen) { + b.templateChan <- RegenTemplateMsg{} + } + } + + case <-b.quit: + break out + } + } + + b.regenTicker.Stop() +} + // handleNotifyMsg handles notifications from blockchain. It does things such // as request orphan block parents and relay accepted blocks to connected peers. func (b *blockManager) handleNotifyMsg(notification *blockchain.Notification) { diff --git a/dcrjson/chainsvrwscmds.go b/dcrjson/chainsvrwscmds.go index f42639fcd6..ac1961ef76 100644 --- a/dcrjson/chainsvrwscmds.go +++ b/dcrjson/chainsvrwscmds.go @@ -58,6 +58,15 @@ func NewNotifyBlocksCmd() *NotifyBlocksCmd { return &NotifyBlocksCmd{} } +// NotifyWorkCmd defines the notifywork JSON-RPC command. +type NotifyWorkCmd struct{} + +// NewNotifyWorkCmd returns a new instance which can be used to issue a +// notifywork JSON-RPC command. +func NewNotifyWorkCmd() *NotifyWorkCmd { + return &NotifyWorkCmd{} +} + // NotifyWinningTicketsCmd is a type handling custom marshaling and // unmarshaling of notifywinningtickets JSON websocket extension // commands. @@ -111,6 +120,15 @@ func NewStopNotifyBlocksCmd() *StopNotifyBlocksCmd { return &StopNotifyBlocksCmd{} } +// StopNotifyWorkCmd defines the stopnotifywork JSON-RPC command. +type StopNotifyWorkCmd struct{} + +// NewStopNotifyWorkCmd returns a new instance which can be used to issue a +// stopnotifywork JSON-RPC command. +func NewStopNotifyWorkCmd() *StopNotifyWorkCmd { + return &StopNotifyWorkCmd{} +} + // NotifyNewTransactionsCmd defines the notifynewtransactions JSON-RPC command. type NotifyNewTransactionsCmd struct { Verbose *bool `jsonrpcdefault:"false"` @@ -168,6 +186,7 @@ func init() { MustRegisterCmd("authenticate", (*AuthenticateCmd)(nil), flags) MustRegisterCmd("loadtxfilter", (*LoadTxFilterCmd)(nil), flags) MustRegisterCmd("notifyblocks", (*NotifyBlocksCmd)(nil), flags) + MustRegisterCmd("notifywork", (*NotifyWorkCmd)(nil), flags) MustRegisterCmd("notifynewtransactions", (*NotifyNewTransactionsCmd)(nil), flags) MustRegisterCmd("notifynewtickets", (*NotifyNewTicketsCmd)(nil), flags) MustRegisterCmd("notifyspentandmissedtickets", @@ -178,6 +197,7 @@ func init() { (*NotifyWinningTicketsCmd)(nil), flags) MustRegisterCmd("session", (*SessionCmd)(nil), flags) MustRegisterCmd("stopnotifyblocks", (*StopNotifyBlocksCmd)(nil), flags) + MustRegisterCmd("stopnotifywork", (*StopNotifyWorkCmd)(nil), flags) MustRegisterCmd("stopnotifynewtransactions", (*StopNotifyNewTransactionsCmd)(nil), flags) MustRegisterCmd("rescan", (*RescanCmd)(nil), flags) } diff --git a/dcrjson/chainsvrwsntfns.go b/dcrjson/chainsvrwsntfns.go index dcab78177a..2b8ff0000f 100644 --- a/dcrjson/chainsvrwsntfns.go +++ b/dcrjson/chainsvrwsntfns.go @@ -13,6 +13,10 @@ const ( // the chain server that a block has been connected. BlockConnectedNtfnMethod = "blockconnected" + // WorkAvialableNtfnMethod is the method used for notifications from + // the chain server that a block is available to mined. + WorkAvailableNtfnMethod = "workavailable" + // BlockDisconnectedNtfnMethod is the method used for notifications from // the chain server that a block has been disconnected. BlockDisconnectedNtfnMethod = "blockdisconnected" @@ -52,6 +56,21 @@ func NewBlockConnectedNtfn(header string, subscribedTxs []string) *BlockConnecte } } +// WorkAvailableNtfn models the data from the workavailable JSON-RPC notification. +type WorkAvailableNtfn struct { + Data string `json:"data"` + Target string `json:"target"` +} + +// NewWorkNtfn returns a new instance which can be used to issue a +// newwork JSON-RPC notification. +func NewWorkAvailableNtfn(data string, target string) *WorkAvailableNtfn { + return &WorkAvailableNtfn{ + Data: data, + Target: target, + } +} + // BlockDisconnectedNtfn defines the blockdisconnected JSON-RPC notification. type BlockDisconnectedNtfn struct { Header string `json:"header"` diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md index 53088d8370..26bfdca5d3 100644 --- a/docs/json_rpc_api.md +++ b/docs/json_rpc_api.md @@ -693,16 +693,18 @@ user. Click the method name for further details such as parameter and return in |---|------|-----------|-------------| |1|[authenticate](#authenticate)|Authenticate the connection against the username and passphrase configured for the RPC server.

NOTE: This is only required if an HTTP Authorization header is not being used.|None| |2|[notifyblocks](#notifyblocks)|Send notifications when a block is connected or disconnected from the best chain.|[blockconnected](#blockconnected) and [blockdisconnected](#blockdisconnected)| -|3|[stopnotifyblocks](#stopnotifyblocks)|Cancel registered notifications for whenever a block is connected or disconnected from the main (best) chain. |None| -|4|[notifyreceived](#notifyreceived)|Send notifications when a txout spends to an address.|[recvtx](#recvtx) and [redeemingtx](#redeemingtx)| -|5|[stopnotifyreceived](#stopnotifyreceived)|Cancel registered notifications for when a txout spends to any of the passed addresses.|None| -|6|[notifyspent](#notifyspent)|Send notification when a txout is spent.|[redeemingtx](#redeemingtx)| -|7|[stopnotifyspent](#stopnotifyspent)|Cancel registered spending notifications for each passed outpoint.|None| -|8|[loadtxfilter](#loadtxfilter)|Load, add to, or reload a websocket client's transaction filter for mempool transactions, new blocks and rescanblocks.|[relevanttxaccepted](#relevanttxaccepted)| -|9|[rescan](#rescan)|Rescan block chain for transactions to addresses and spent transaction outpoints.|[recvtx](#recvtx), [redeemingtx](#redeemingtx), [rescanprogress](#rescanprogress), and [rescanfinished](#rescanfinished) | -|10|[notifynewtransactions](#notifynewtransactions)|Send notifications for all new transactions as they are accepted into the mempool.|[txaccepted](#txaccepted) or [txacceptedverbose](#txacceptedverbose)| -|11|[stopnotifynewtransactions](#stopnotifynewtransactions)|Stop sending either a txaccepted or a txacceptedverbose notification when a new transaction is accepted into the mempool.|None| -|12|[session](#session)|Return details regarding a websocket client's current connection.|None| +|3|[notifywork](#notifywork)|Send notifications when a block is available to be mined.| [workavailable](#workavailable)| +|4|[stopnotifyblocks](#stopnotifyblocks)|Cancel registered notifications for whenever a block is connected or disconnected from the main (best) chain. |None| +|5|[stopnotifywork](#stopnotifywork)|Cancel registered notifications for whenever a block is available to be mined. |None| +|6|[notifyreceived](#notifyreceived)|Send notifications when a txout spends to an address.|[recvtx](#recvtx) and [redeemingtx](#redeemingtx)| +|7|[stopnotifyreceived](#stopnotifyreceived)|Cancel registered notifications for when a txout spends to any of the passed addresses.|None| +|8|[notifyspent](#notifyspent)|Send notification when a txout is spent.|[redeemingtx](#redeemingtx)| +|9|[stopnotifyspent](#stopnotifyspent)|Cancel registered spending notifications for each passed outpoint.|None| +|10|[loadtxfilter](#loadtxfilter)|Load, add to, or reload a websocket client's transaction filter for mempool transactions, new blocks and rescanblocks.|[relevanttxaccepted](#relevanttxaccepted)| +|11|[rescan](#rescan)|Rescan block chain for transactions to addresses and spent transaction outpoints.|[recvtx](#recvtx), [redeemingtx](#redeemingtx), [rescanprogress](#rescanprogress), and [rescanfinished](#rescanfinished) | +|12|[notifynewtransactions](#notifynewtransactions)|Send notifications for all new transactions as they are accepted into the mempool.|[txaccepted](#txaccepted) or [txacceptedverbose](#txacceptedverbose)| +|13|[stopnotifynewtransactions](#stopnotifynewtransactions)|Stop sending either a txaccepted or a txacceptedverbose notification when a new transaction is accepted into the mempool.|None| +|14|[session](#session)|Return details regarding a websocket client's current connection.|None| **6.2 Method Details**
@@ -730,6 +732,19 @@ user. Click the method name for further details such as parameter and return in |Returns|Nothing| [Return to Overview](#WSMethodOverview)
+*** + +
+ +| | | +|---|---| +|Method|notifywork| +|Notifications|[workavailable](#workavailable)| +|Parameters|None| +|Description|Request notifications for whenever a block is available to be mined.| +|Returns|Nothing| +[Return to Overview](#WSMethodOverview)
+ ***
@@ -742,6 +757,18 @@ user. Click the method name for further details such as parameter and return in |Returns|Nothing| [Return to Overview](#WSMethodOverview)
+*** +
+ +| | | +|---|---| +|Method|stopnotifywork| +|Notifications|None| +|Parameters|None| +|Description|Cancel sending notifications for whenever a block is available to be mined.| +|Returns|Nothing| +[Return to Overview](#WSMethodOverview)
+ ***
diff --git a/mempool/mempool.go b/mempool/mempool.go index aa5b39ef97..5506e332d9 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -1166,7 +1166,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow mp.addTransaction(utxoView, tx, txType, bestHeight, txFee) // If it's an SSGen (vote), insert it into the list of - // votes. + // votes and send block template generation message. if txType == stake.TxTypeSSGen { mp.votesMtx.Lock() err := mp.insertVote(tx) @@ -1174,6 +1174,21 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow if err != nil { return nil, err } + + // send block template generation message here. + + // currently can not because the mempool does not have a reference + // to the dcrd server or the block manager. Looking for an elegant way + // of exposing the template channel to the mempool for this. + + // So far passing a reference of the template channel to the mempool + // seems to be the way to go. That's posible since the mempool and + // the block manager are initialized when the dcrd server is being + // created. Let me know if I'm overthinking this, thanks. + } + + if txType != stake.TxTypeSSGen { + // send block template generation message here. } log.Debugf("Accepted transaction %v (pool size: %v)", txHash, diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index dea40eecca..0db521c504 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -524,6 +524,14 @@ func (c *Client) reregisterNtfns() error { } } + // Reregister notifywork if needed. + if stateCopy.notifyWork { + log.Debugf("Reregistering [notifywork]") + if err := c.NotifyWork(); err != nil { + return err + } + } + // Reregister notifywinningtickets if needed. if stateCopy.notifyWinningTickets { log.Debugf("Reregistering [notifywinningtickets]") diff --git a/rpcclient/notify.go b/rpcclient/notify.go index 7ec621a4c4..bdd826899a 100644 --- a/rpcclient/notify.go +++ b/rpcclient/notify.go @@ -32,6 +32,7 @@ var ( // reconnect. type notificationState struct { notifyBlocks bool + notifyWork bool notifyWinningTickets bool notifySpentAndMissedTickets bool notifyNewTickets bool @@ -44,6 +45,7 @@ type notificationState struct { func (s *notificationState) Copy() *notificationState { var stateCopy notificationState stateCopy.notifyBlocks = s.notifyBlocks + stateCopy.notifyWork = s.notifyWork stateCopy.notifyWinningTickets = s.notifyWinningTickets stateCopy.notifySpentAndMissedTickets = s.notifySpentAndMissedTickets stateCopy.notifyNewTickets = s.notifyNewTickets @@ -84,6 +86,12 @@ type NotificationHandlers struct { // notification handlers, and is safe for blocking client requests. OnClientConnected func() + // OnWorkAvailable is invoked when a block is available to be mined. + // It will only be invoked if a preceding call to + // NotifyWork has been made to register for the notification and the + // function is non-nil. + OnWorkAvailable func(data string, target string) + // OnBlockConnected is invoked when a block is connected to the longest // (best) chain. It will only be invoked if a preceding call to // NotifyBlocks has been made to register for the notification and the @@ -231,6 +239,23 @@ func (c *Client) handleNotification(ntfn *rawNotification) { c.ntfnHandlers.OnBlockConnected(blockHeader, transactions) + // OnWorkAvailable + case dcrjson.WorkAvailableNtfnMethod: + // Ignore the notification if the client is not interested in + // it. + if c.ntfnHandlers.OnWorkAvailable == nil { + return + } + + data, target, err := parseWorkAvailableParams(ntfn.Params) + if err != nil { + log.Warnf("Received invalid workavailable "+ + "notification: %v", err) + return + } + + c.ntfnHandlers.OnWorkAvailable(string(data), string(target)) + // OnBlockDisconnected case dcrjson.BlockDisconnectedNtfnMethod: // Ignore the notification if the client is not interested in @@ -560,6 +585,16 @@ func parseBlockConnectedParams(params []json.RawMessage) (blockHeader []byte, tr return blockHeader, transactions, nil } +// parseWorkAvailableParams parses out the parameters included in a +// workavailable notification. +func parseWorkAvailableParams(params []json.RawMessage) (data, target []byte, err error) { + if len(params) != 2 { + return nil, nil, wrongNumParams(len(params)) + } + + return params[0], params[1], nil +} + // parseBlockDisconnectedParams parses out the parameters included in a // blockdisconnected notification. func parseBlockDisconnectedParams(params []json.RawMessage) (blockHeader []byte, err error) { @@ -1174,6 +1209,54 @@ func (c *Client) NotifyBlocks() error { return c.NotifyBlocksAsync().Receive() } +// FutureNotifyWorkResult is a future promise to deliver the result of a +// NotifyWorkAsync RPC invocation (or an applicable error). +type FutureNotifyWorkResult chan *response + +// Receive waits for the response promised by the future and returns an error +// if the registration was not successful. +func (r FutureNotifyWorkResult) Receive() error { + _, err := receiveFuture(r) + return err +} + +// NotifyWorkAsync returns an instance of a type that can be used to get the +// result of the RPC at some future time by invoking the Receive function on +// the returned instance. +// +// See NotifyWork for the blocking version and more details. +// +// NOTE: This is a dcrd extension and requires a websocket connection. +func (c *Client) NotifyWorkAsync() FutureNotifyWorkResult { + // Not supported in HTTP POST mode. + if c.config.HTTPPostMode { + return newFutureError(ErrWebsocketsRequired) + } + + // Ignore the notification if the client is not interested in + // notifications. + if c.ntfnHandlers == nil { + return newNilFutureResult() + } + + cmd := dcrjson.NewNotifyWorkCmd() + return c.sendCmd(cmd) +} + +// NotifyWork registers the client to receive notifications when blocks are +// connected and disconnected from the main chain. The notifications are +// delivered to the notification handlers associated with the client. Calling +// this function has no effect if there are no notification handlers and will +// result in an error if the client is configured to run in HTTP POST mode. +// +// The notifications delivered as a result of this call will be via +// OnWorkAvailable. +// +// NOTE: This is a dcrd extension and requires a websocket connection. +func (c *Client) NotifyWork() error { + return c.NotifyWorkAsync().Receive() +} + // FutureNotifyWinningTicketsResult is a future promise to deliver the result of a // NotifyWinningTicketsAsync RPC invocation (or an applicable error). type FutureNotifyWinningTicketsResult chan *response diff --git a/rpcserver.go b/rpcserver.go index 134f3f3167..7ba9c217a1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -307,6 +307,7 @@ var rpcUnimplemented = map[string]struct{}{ var rpcLimited = map[string]struct{}{ // Websockets commands "notifyblocks": {}, + "notifywork": {}, "notifynewtransactions": {}, "notifyreceived": {}, "notifyspent": {}, diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 5b460e24e1..df56bc634e 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -780,9 +780,15 @@ var helpDescsEnUS = map[string]string{ // NotifyBlocksCmd help. "notifyblocks--synopsis": "Request notifications for whenever a block is connected or disconnected from the main (best) chain.", + // NotifyWorkCmd help. + "notifywork--synopsis": "Request notifications for whenever a block is available to be mined.", + // StopNotifyBlocksCmd help. "stopnotifyblocks--synopsis": "Cancel registered notifications for whenever a block is connected or disconnected from the main (best) chain.", + // StopNotifyWorkCmd help. + "stopnotifywork--synopsis": "Cancel registered notifications for whenever a block is available to be mined.", + // NotifyNewTransactionsCmd help. "notifynewtransactions--synopsis": "Send either a txaccepted or a txacceptedverbose notification when a new transaction is accepted into the mempool.", "notifynewtransactions-verbose": "Specifies which type of notification to receive. If verbose is true, then the caller receives txacceptedverbose, otherwise the caller receives txaccepted", @@ -995,11 +1001,13 @@ var rpcResultTypes = map[string][]interface{}{ "notifynewtickets": nil, "notifystakedifficulty": nil, "notifyblocks": nil, + "notifywork": nil, "notifynewtransactions": nil, "notifyreceived": nil, "notifyspent": nil, "rescan": nil, "stopnotifyblocks": nil, + "stopnotifywork": nil, "stopnotifynewtransactions": nil, "stopnotifyreceived": nil, "stopnotifyspent": nil, diff --git a/rpcwebsocket.go b/rpcwebsocket.go index 046e76e7e4..be1efa9d9e 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -65,6 +65,7 @@ var wsHandlers map[string]wsCommandHandler var wsHandlersBeforeInit = map[string]wsCommandHandler{ "loadtxfilter": handleLoadTxFilter, "notifyblocks": handleNotifyBlocks, + "notifywork": handleNotifyWork, "notifywinningtickets": handleWinningTickets, "notifyspentandmissedtickets": handleSpentAndMissedTickets, "notifynewtickets": handleNewTickets, @@ -74,6 +75,7 @@ var wsHandlersBeforeInit = map[string]wsCommandHandler{ "help": handleWebsocketHelp, "rescan": handleRescan, "stopnotifyblocks": handleStopNotifyBlocks, + "stopnotifywork": handleStopNotifyWork, "stopnotifynewtransactions": handleStopNotifyNewTransactions, } @@ -223,6 +225,19 @@ func (m *wsNotificationManager) NotifyBlockDisconnected(block *dcrutil.Block) { } } +// NotifyWorkAvailable passes an available block to be mined to the notification +// manager for processing. +func (m *wsNotificationManager) NotifyWorkAvailable(wd *dcrjson.WorkAvailableNtfn) { + // As NotifyWorkAvailable will be called by the block manager + // and the RPC server may no longer be running, use a select + // statement to unblock enqueuing the notification once the RPC + // server has begun shutting down. + select { + case m.queueNotification <- (*notificationWorkAvailable)(wd): + case <-m.quit: + } +} + // NotifyReorganization passes a blockchain reorganization notification for // reorganization notification processing. func (m *wsNotificationManager) NotifyReorganization(rd *blockchain.ReorganizationNtfnsData) { @@ -452,6 +467,7 @@ func (f *wsClientFilter) existsUnspentOutPoint(op *wire.OutPoint) bool { // Notification types type notificationBlockConnected dcrutil.Block type notificationBlockDisconnected dcrutil.Block +type notificationWorkAvailable dcrjson.WorkAvailableNtfn type notificationReorganization blockchain.ReorganizationNtfnsData type notificationWinningTickets WinningTicketsNtfnData type notificationSpentAndMissedTickets blockchain.TicketNotificationsData @@ -467,6 +483,8 @@ type notificationRegisterClient wsClient type notificationUnregisterClient wsClient type notificationRegisterBlocks wsClient type notificationUnregisterBlocks wsClient +type notificationRegisterWorkAvailable wsClient +type notificationUnregisterWorkAvailable wsClient type notificationRegisterWinningTickets wsClient type notificationUnregisterWinningTickets wsClient type notificationRegisterSpentAndMissedTickets wsClient @@ -492,6 +510,7 @@ func (m *wsNotificationManager) notificationHandler() { // Where possible, the quit channel is used as the unique id for a client // since it is quite a bit more efficient than using the entire struct. blockNotifications := make(map[chan struct{}]*wsClient) + workNotifications := make(map[chan struct{}]*wsClient) winningTicketNotifications := make(map[chan struct{}]*wsClient) ticketSMNotifications := make(map[chan struct{}]*wsClient) ticketNewNotifications := make(map[chan struct{}]*wsClient) @@ -522,6 +541,10 @@ out: m.notifyBlockDisconnected(blockNotifications, (*dcrutil.Block)(n)) + case *notificationWorkAvailable: + m.notifyWorkAvailable(workNotifications, + (*dcrjson.WorkAvailableNtfn)(n)) + case *notificationReorganization: m.notifyReorganization(blockNotifications, (*blockchain.ReorganizationNtfnsData)(n)) @@ -556,6 +579,14 @@ out: wsc := (*wsClient)(n) delete(blockNotifications, wsc.quit) + case *notificationRegisterWorkAvailable: + wsc := (*wsClient)(n) + workNotifications[wsc.quit] = wsc + + case *notificationUnregisterWorkAvailable: + wsc := (*wsClient)(n) + delete(workNotifications, wsc.quit) + case *notificationRegisterWinningTickets: wsc := (*wsClient)(n) winningTicketNotifications[wsc.quit] = wsc @@ -597,6 +628,7 @@ out: // Remove any requests made by the client as well as // the client itself. delete(blockNotifications, wsc.quit) + delete(workNotifications, wsc.quit) delete(txNotifications, wsc.quit) delete(clients, wsc.quit) @@ -756,6 +788,33 @@ func (m *wsNotificationManager) notifyBlockConnected(clients map[chan struct{}]* } } +// RegisterWorkAvailable requests block update notifications to the passed +// websocket client. +func (m *wsNotificationManager) RegisterWorkUpdates(wsc *wsClient) { + m.queueNotification <- (*notificationRegisterWorkAvailable)(wsc) +} + +// UnregisterWorkUpdates removes work update notifications for the passed +// websocket client. +func (m *wsNotificationManager) UnregisterWorkUpdates(wsc *wsClient) { + m.queueNotification <- (*notificationUnregisterWorkAvailable)(wsc) +} + +// notifyWorkAvailable notifies websocket clients that have registered for +// work updates when a block is available to be mined. +func (m *wsNotificationManager) notifyWorkAvailable(clients map[chan struct{}]*wsClient, workNtfn *dcrjson.WorkAvailableNtfn) { + for _, client := range clients { + // Marshal and queue notification. + marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, workNtfn) + if err != nil { + rpcsLog.Errorf("Failed to marshal work available"+ + "notification: %v", err) + continue + } + client.QueueNotification(marshalledJSON) + } +} + // notifyBlockDisconnected notifies websocket clients that have registered for // block updates when a block is disconnected from the main chain (due to a // reorganize). @@ -2019,6 +2078,13 @@ func handleNotifyBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) { return nil, nil } +// handleNotifyWork implements the notifywork command extension for +// websocket connections. +func handleNotifyWork(wsc *wsClient, icmd interface{}) (interface{}, error) { + wsc.server.ntfnMgr.RegisterWorkUpdates(wsc) + return nil, nil +} + // handleSession implements the session command extension for websocket // connections. func handleSession(wsc *wsClient, icmd interface{}) (interface{}, error) { @@ -2060,6 +2126,13 @@ func handleStopNotifyBlocks(wsc *wsClient, icmd interface{}) (interface{}, error return nil, nil } +// handleStopNotifyWork implements the stopnotifywork command extension for +// websocket connections. +func handleStopNotifyWork(wsc *wsClient, icmd interface{}) (interface{}, error) { + wsc.server.ntfnMgr.UnregisterWorkUpdates(wsc) + return nil, nil +} + // handleNotifyNewTransations implements the notifynewtransactions command // extension for websocket connections. func handleNotifyNewTransactions(wsc *wsClient, icmd interface{}) (interface{}, error) {