1 package rpcclient
2 3 import (
4 "bytes"
5 "encoding/hex"
6 js "encoding/json"
7 "errors"
8 "fmt"
9 "github.com/p9c/p9/pkg/amt"
10 "github.com/p9c/p9/pkg/btcaddr"
11 "time"
12 13 "github.com/p9c/p9/pkg/btcjson"
14 "github.com/p9c/p9/pkg/chainhash"
15 "github.com/p9c/p9/pkg/util"
16 "github.com/p9c/p9/pkg/wire"
17 )
18 19 var (
20 // ErrWebsocketsRequired is an error to describe the condition where the caller is trying to use a websocket-only
21 // feature, such as requesting notifications or other websocket requests when the client is configured to run in
22 // HTTP POST mode.
23 ErrWebsocketsRequired = errors.New(
24 "a websocket connection is required " +
25 "to use this feature",
26 )
27 )
28 29 // notificationState is used to track the current state of successfully registered notification so the state can be
30 // automatically re-established on reconnect.
31 type notificationState struct {
32 notifyBlocks bool
33 notifyNewTx bool
34 notifyNewTxVerbose bool
35 notifyReceived map[string]struct{}
36 notifySpent map[btcjson.OutPoint]struct{}
37 }
38 39 // Copy returns a deep copy of the receiver.
40 func (s *notificationState) Copy() *notificationState {
41 var stateCopy notificationState
42 stateCopy.notifyBlocks = s.notifyBlocks
43 stateCopy.notifyNewTx = s.notifyNewTx
44 stateCopy.notifyNewTxVerbose = s.notifyNewTxVerbose
45 stateCopy.notifyReceived = make(map[string]struct{})
46 for addr := range s.notifyReceived {
47 stateCopy.notifyReceived[addr] = struct{}{}
48 }
49 stateCopy.notifySpent = make(map[btcjson.OutPoint]struct{})
50 for op := range s.notifySpent {
51 stateCopy.notifySpent[op] = struct{}{}
52 }
53 return &stateCopy
54 }
55 56 func // newNotificationState returns a new notification state ready to be
57 // populated.
58 newNotificationState() *notificationState {
59 return ¬ificationState{
60 notifyReceived: make(map[string]struct{}),
61 notifySpent: make(map[btcjson.OutPoint]struct{}),
62 }
63 }
64 65 func // newNilFutureResult returns a new future result channel that already
66 // has the result waiting on the channel with the reply set to nil.
67 // This is useful to ignore things such as notifications when the caller didn't
68 // specify any notification handlers.
69 newNilFutureResult() chan *response {
70 responseChan := make(chan *response, 1)
71 responseChan <- &response{result: nil, err: nil}
72 return responseChan
73 }
74 75 // NotificationHandlers defines callback function pointers to invoke with
76 // notifications. Since all of the functions are nil by default, all notifications are effectively ignored until
77 // their handlers are set to a concrete callback.
78 //
79 // NOTE: Unless otherwise documented, these handlers must NOT directly call any blocking calls on the client
80 // instance since the input reader goroutine blocks until the callback has completed. Doing so will result in a
81 // deadlock situation.
82 type NotificationHandlers struct {
83 // OnClientConnected is invoked when the client connects or reconnects to the RPC server. This callback is run async
84 // with the rest of the notification handlers, and is safe for blocking client requests.
85 OnClientConnected func()
86 // OnBlockConnected is invoked when a block is connected to the longest (best) chain. It will only be invoked if a
87 // preceding call to NotifyBlocks has been made to register for the notification and the function is non-nil. NOTE:
88 // Deprecated. Use OnFilteredBlockConnected instead.
89 OnBlockConnected func(hash *chainhash.Hash, height int32, t time.Time)
90 // OnFilteredBlockConnected is invoked when a block is connected to the longest (best) chain. It will only be
91 // invoked if a preceding call to NotifyBlocks has been made to register for the notification and the function is
92 // non-nil. Its parameters differ from OnBlockConnected: it receives the block's height, header, and relevant
93 // transactions.
94 OnFilteredBlockConnected func(height int32, header *wire.BlockHeader, txs []*util.Tx)
95 // OnBlockDisconnected is invoked when a block is disconnected from the longest (best) chain. It will only be
96 // invoked if a preceding call to NotifyBlocks has been made to register for the notification and the function is
97 // non-nil. NOTE: Deprecated. Use OnFilteredBlockDisconnected instead.
98 OnBlockDisconnected func(hash *chainhash.Hash, height int32, t time.Time)
99 // OnFilteredBlockDisconnected is invoked when a block is disconnected from the longest (best) chain. It will only
100 // be invoked if a preceding NotifyBlocks has been made to register for the notification and the call to function is
101 // non-nil. Its parameters differ from OnBlockDisconnected: it receives the block's height and header.
102 OnFilteredBlockDisconnected func(height int32, header *wire.BlockHeader)
103 // OnRecvTx is invoked when a transaction that receives funds to a registered address is received into the memory
104 // pool and also connected to the longest (best) chain. It will only be invoked if a preceding call to
105 // NotifyReceived, Rescan, or RescanEndHeight has been made to register for the notification and the function is
106 // non-nil. NOTE: Deprecated. Use OnRelevantTxAccepted instead.
107 OnRecvTx func(transaction *util.Tx, details *btcjson.BlockDetails)
108 // OnRedeemingTx is invoked when a transaction that spends a registered outpoint is received into the memory pool
109 // and also connected to the longest (best) chain.
110 //
111 // It will only be invoked if a preceding call to NotifySpent, Rescan, or RescanEndHeight has been made to register
112 // for the notification and the function is non-nil.
113 //
114 // NOTE: The NotifyReceived will automatically register notifications for the outpoints that are now "owned" as a
115 // result of receiving funds to the registered addresses.
116 //
117 // This means it is possible for this to invoked indirectly as the result of a NotifyReceived call. NOTE:
118 // Deprecated. Use OnRelevantTxAccepted instead.
119 OnRedeemingTx func(transaction *util.Tx, details *btcjson.BlockDetails)
120 // OnRelevantTxAccepted is invoked when an unmined transaction passes the client's transaction filter.
121 //
122 // NOTE: This is a btcsuite extension ported from github.com/decred/dcrrpcclient.
123 OnRelevantTxAccepted func(transaction []byte)
124 // OnRescanFinished is invoked after a rescan finishes due to a previous call to Rescan or RescanEndHeight. Finished
125 // rescans should be signaled on this notification, rather than relying on the return result of a rescan request,
126 // due to how pod may send various rescan notifications after the rescan request has already returned.
127 //
128 // NOTE: Deprecated. Not used with RescanBlocks.
129 OnRescanFinished func(hash *chainhash.Hash, height int32, blkTime time.Time)
130 // OnRescanProgress is invoked periodically when a rescan is underway. It will only be invoked if a preceding call
131 // to Rescan or RescanEndHeight has been made and the function is non-nil.
132 //
133 // NOTE: Deprecated. Not used with RescanBlocks.
134 OnRescanProgress func(hash *chainhash.Hash, height int32, blkTime time.Time)
135 // OnTxAccepted is invoked when a transaction is accepted into the memory pool. It will only be invoked if a
136 // preceding call to NotifyNewTransactions with the verbose flag set to false has been made to register for the
137 // notification and the function is non-nil.
138 OnTxAccepted func(hash *chainhash.Hash, amount amt.Amount)
139 // OnTxAccepted is invoked when a transaction is accepted into the memory pool. It will only be invoked if a
140 // preceding call to NotifyNewTransactions with the verbose flag set to true has been made to register for the
141 // notification and the function is non-nil.
142 OnTxAcceptedVerbose func(txDetails *btcjson.TxRawResult)
143 // OnPodConnected is invoked when a wallet connects or disconnects from pod. This will only be available when client
144 // is connected to a wallet server such as btcwallet.
145 OnPodConnected func(connected bool)
146 // OnAccountBalance is invoked with account balance updates. This will only be available when speaking to a wallet
147 // server such as btcwallet.
148 OnAccountBalance func(account string, balance amt.Amount, confirmed bool)
149 // OnWalletLockState is invoked when a wallet is locked or unlocked. This will only be available when client is
150 // connected to a wallet server such as btcwallet.
151 OnWalletLockState func(locked bool)
152 // OnUnknownNotification is invoked when an unrecognized notification is received. This typically means the
153 // notification handling code for this package needs to be updated for a new notification type or the caller is
154 // using a custom notification this package does not know about.
155 OnUnknownNotification func(method string, params []js.RawMessage)
156 }
157 158 // handleNotification examines the passed notification type, performs conversions to get the raw notification types into
159 // higher level types and delivers the notification to the appropriate On<X> handler registered with the client.
160 func (c *Client) handleNotification(ntfn *rawNotification) {
161 D.Ln("<<<Handling Notification>>>", ntfn.Method)
162 // Ignore the notification if the client is not interested in any notifications.
163 if c.ntfnHandlers == nil {
164 D.Ln("<<<no notification handlers registered>>>")
165 return
166 }
167 switch ntfn.Method {
168 // OnBlockConnected
169 case btcjson.BlockConnectedNtfnMethod:
170 // Ignore the notification if the client is not interested in it.
171 if c.ntfnHandlers.OnBlockConnected == nil {
172 D.Ln("<<<no OnBlockConnected callback registered>>>")
173 return
174 }
175 blockHash, blockHeight, blockTime, e := parseChainNtfnParams(ntfn.Params)
176 if e != nil {
177 W.Ln("received invalid block connected notification:", e)
178 return
179 }
180 c.ntfnHandlers.OnBlockConnected(blockHash, blockHeight, blockTime)
181 // OnFilteredBlockConnected
182 case btcjson.FilteredBlockConnectedNtfnMethod:
183 // Ignore the notification if the client is not interested in it.
184 if c.ntfnHandlers.OnFilteredBlockConnected == nil {
185 D.Ln("<<<no OnFilteredBlockConnected callback registered>>>")
186 return
187 }
188 blockHeight, blockHeader, transactions, e :=
189 parseFilteredBlockConnectedParams(ntfn.Params)
190 if e != nil {
191 W.Ln(
192 "received invalid filtered block connected notification:",
193 e,
194 )
195 return
196 }
197 c.ntfnHandlers.OnFilteredBlockConnected(
198 blockHeight,
199 blockHeader, transactions,
200 )
201 // OnBlockDisconnected
202 case btcjson.BlockDisconnectedNtfnMethod:
203 // Ignore the notification if the client is not interested in it.
204 if c.ntfnHandlers.OnBlockDisconnected == nil {
205 D.Ln("<<<no OnBlockDisconnected callback registered>>>")
206 return
207 }
208 blockHash, blockHeight, blockTime, e := parseChainNtfnParams(ntfn.Params)
209 if e != nil {
210 W.Ln("received invalid block connected notification:", e)
211 return
212 }
213 c.ntfnHandlers.OnBlockDisconnected(blockHash, blockHeight, blockTime)
214 // OnFilteredBlockDisconnected
215 case btcjson.FilteredBlockDisconnectedNtfnMethod:
216 // Ignore the notification if the client is not interested in it.
217 if c.ntfnHandlers.OnFilteredBlockDisconnected == nil {
218 D.Ln("<<<no OnFilteredBlockDisconnected callback registered>>>")
219 return
220 }
221 blockHeight, blockHeader, e := parseFilteredBlockDisconnectedParams(ntfn.Params)
222 if e != nil {
223 W.Ln(
224 "received invalid filtered block disconnected"+
225 " notification"+
226 ":", e,
227 )
228 return
229 }
230 c.ntfnHandlers.OnFilteredBlockDisconnected(blockHeight, blockHeader)
231 // OnRecvTx
232 case btcjson.RecvTxNtfnMethod:
233 // Ignore the notification if the client is not interested in it.
234 if c.ntfnHandlers.OnRecvTx == nil {
235 D.Ln("<<<no OnRecvTx callback registered>>>")
236 return
237 }
238 tx, block, e := parseChainTxNtfnParams(ntfn.Params)
239 if e != nil {
240 W.Ln("received invalid recvtx notification:", e)
241 return
242 }
243 c.ntfnHandlers.OnRecvTx(tx, block)
244 // OnRedeemingTx
245 case btcjson.RedeemingTxNtfnMethod:
246 // Ignore the notification if the client is not interested in it.
247 if c.ntfnHandlers.OnRedeemingTx == nil {
248 D.Ln("<<<no OnRedeemingTx callback registered>>>")
249 return
250 }
251 tx, block, e := parseChainTxNtfnParams(ntfn.Params)
252 if e != nil {
253 W.Ln("received invalid redeemingtx notification:", e)
254 return
255 }
256 c.ntfnHandlers.OnRedeemingTx(tx, block)
257 // OnRelevantTxAccepted
258 case btcjson.RelevantTxAcceptedNtfnMethod:
259 // Ignore the notification if the client is not interested in it.
260 if c.ntfnHandlers.OnRelevantTxAccepted == nil {
261 D.Ln("<<<no OnRelevantTxAccepted callback registered>>>")
262 return
263 }
264 transaction, e := parseRelevantTxAcceptedParams(ntfn.Params)
265 if e != nil {
266 W.Ln("received invalid relevanttxaccepted notification:", e)
267 return
268 }
269 c.ntfnHandlers.OnRelevantTxAccepted(transaction)
270 // OnRescanFinished
271 case btcjson.RescanFinishedNtfnMethod:
272 // Ignore the notification if the client is not interested in it.
273 if c.ntfnHandlers.OnRescanFinished == nil {
274 D.Ln("<<<no OnRescanFinished callback registered>>>")
275 return
276 }
277 hash, height, blkTime, e := parseRescanProgressParams(ntfn.Params)
278 if e != nil {
279 W.Ln("received invalid rescanfinished notification:", e)
280 return
281 }
282 c.ntfnHandlers.OnRescanFinished(hash, height, blkTime)
283 // OnRescanProgress
284 case btcjson.RescanProgressNtfnMethod:
285 // Ignore the notification if the client is not interested in it.
286 if c.ntfnHandlers.OnRescanProgress == nil {
287 D.Ln("<<<no OnRescanProgress callback registered>>>")
288 return
289 }
290 hash, height, blkTime, e := parseRescanProgressParams(ntfn.Params)
291 if e != nil {
292 W.Ln("received invalid rescanprogress notification:", e)
293 return
294 }
295 c.ntfnHandlers.OnRescanProgress(hash, height, blkTime)
296 // OnTxAccepted
297 case btcjson.TxAcceptedNtfnMethod:
298 // Ignore the notification if the client is not interested in it.
299 if c.ntfnHandlers.OnTxAccepted == nil {
300 D.Ln("<<<no OnTxAccepted callback registered>>>")
301 return
302 }
303 hash, amt, e := parseTxAcceptedNtfnParams(ntfn.Params)
304 if e != nil {
305 W.Ln("received invalid tx accepted notification:", e)
306 return
307 }
308 c.ntfnHandlers.OnTxAccepted(hash, amt)
309 // OnTxAcceptedVerbose
310 case btcjson.TxAcceptedVerboseNtfnMethod:
311 // Ignore the notification if the client is not interested in it.
312 if c.ntfnHandlers.OnTxAcceptedVerbose == nil {
313 D.Ln("<<<no OnTxAcceptedVerbose callback registered>>>")
314 return
315 }
316 rawTx, e := parseTxAcceptedVerboseNtfnParams(ntfn.Params)
317 if e != nil {
318 W.Ln("received invalid tx accepted verbose notification:", e)
319 return
320 }
321 c.ntfnHandlers.OnTxAcceptedVerbose(rawTx)
322 // OnPodConnected
323 case btcjson.PodConnectedNtfnMethod:
324 // Ignore the notification if the client is not interested in it.
325 if c.ntfnHandlers.OnPodConnected == nil {
326 D.Ln("<<<no OnPodConnected callback registered>>>")
327 return
328 }
329 connected, e := parsePodConnectedNtfnParams(ntfn.Params)
330 if e != nil {
331 W.Ln("received invalid pod connected notification:", e)
332 return
333 }
334 c.ntfnHandlers.OnPodConnected(connected)
335 // OnAccountBalance
336 case btcjson.AccountBalanceNtfnMethod:
337 // Ignore the notification if the client is not interested in it.
338 if c.ntfnHandlers.OnAccountBalance == nil {
339 D.Ln("<<<no OnAccountBalance callback registered>>>")
340 return
341 }
342 account, bal, conf, e := parseAccountBalanceNtfnParams(ntfn.Params)
343 if e != nil {
344 W.Ln("received invalid account balance notification:", e)
345 return
346 }
347 c.ntfnHandlers.OnAccountBalance(account, bal, conf)
348 // OnWalletLockState
349 case btcjson.WalletLockStateNtfnMethod:
350 // Ignore the notification if the client is not interested in it.
351 if c.ntfnHandlers.OnWalletLockState == nil {
352 D.Ln("<<<no OnWalletLockState callback registered>>>")
353 return
354 }
355 // The account name is not notified, so the return value is discarded.
356 _, locked, e := parseWalletLockStateNtfnParams(ntfn.Params)
357 if e != nil {
358 W.Ln("received invalid wallet lock state notification:", e)
359 return
360 }
361 c.ntfnHandlers.OnWalletLockState(locked)
362 // OnUnknownNotification
363 default:
364 if c.ntfnHandlers.OnUnknownNotification == nil {
365 D.Ln("<<<no OnUnknownNotification callback registered>>>")
366 return
367 }
368 c.ntfnHandlers.OnUnknownNotification(ntfn.Method, ntfn.Params)
369 }
370 }
371 372 // wrongNumParams is an error type describing an unparseable JSON-RPC notification due to an incorrect number of
373 // parameters for the expected notification type.
374 //
375 // The value is the number of parameters of the invalid notification.
376 type wrongNumParams int
377 378 // BTCJSONError satisfies the builtin error interface.
379 func (e wrongNumParams) Error() string {
380 return fmt.Sprintf("wrong number of parameters (%d)", e)
381 }
382 383 // parseChainNtfnParams parses out the block hash and height from the parameters of blockconnected and blockdisconnected
384 // notifications.
385 func parseChainNtfnParams(params []js.RawMessage) (
386 *chainhash.Hash,
387 int32, time.Time, error,
388 ) {
389 if len(params) != 3 {
390 return nil, 0, time.Time{}, wrongNumParams(len(params))
391 }
392 // Unmarshal first parameter as a string.
393 var blockHashStr string
394 e := js.Unmarshal(params[0], &blockHashStr)
395 if e != nil {
396 return nil, 0, time.Time{}, e
397 }
398 // Unmarshal second parameter as an integer.
399 var blockHeight int32
400 e = js.Unmarshal(params[1], &blockHeight)
401 if e != nil {
402 return nil, 0, time.Time{}, e
403 }
404 // Unmarshal third parameter as unix time.
405 var blockTimeUnix int64
406 e = js.Unmarshal(params[2], &blockTimeUnix)
407 if e != nil {
408 return nil, 0, time.Time{}, e
409 }
410 // Create hash from block hash string.
411 blockHash, e := chainhash.NewHashFromStr(blockHashStr)
412 if e != nil {
413 return nil, 0, time.Time{}, e
414 }
415 // Create time.Time from unix time.
416 blockTime := time.Unix(blockTimeUnix, 0)
417 return blockHash, blockHeight, blockTime, nil
418 }
419 420 // parseFilteredBlockConnectedParams parses out the parameters included in a filteredblockconnected notification.
421 //
422 // NOTE: This is a pod extension ported from github. com/decred/dcrrpcclient and requires a websocket connection.
423 func parseFilteredBlockConnectedParams(params []js.RawMessage) (
424 int32,
425 *wire.BlockHeader, []*util.Tx, error,
426 ) {
427 if len(params) < 3 {
428 return 0, nil, nil, wrongNumParams(len(params))
429 }
430 // Unmarshal first parameter as an integer.
431 var blockHeight int32
432 e := js.Unmarshal(params[0], &blockHeight)
433 if e != nil {
434 return 0, nil, nil, e
435 }
436 // Unmarshal second parameter as a slice of bytes.
437 blockHeaderBytes, e := parseHexParam(params[1])
438 if e != nil {
439 return 0, nil, nil, e
440 }
441 // Deserialize block header from slice of bytes.
442 var blockHeader wire.BlockHeader
443 e = blockHeader.Deserialize(bytes.NewReader(blockHeaderBytes))
444 if e != nil {
445 return 0, nil, nil, e
446 }
447 // Unmarshal third parameter as a slice of hex-encoded strings.
448 var hexTransactions []string
449 e = js.Unmarshal(params[2], &hexTransactions)
450 if e != nil {
451 return 0, nil, nil, e
452 }
453 // Create slice of transactions from slice of strings by hex-decoding.
454 transactions := make([]*util.Tx, len(hexTransactions))
455 for i, hexTx := range hexTransactions {
456 transaction, e := hex.DecodeString(hexTx)
457 if e != nil {
458 return 0, nil, nil, e
459 }
460 transactions[i], e = util.NewTxFromBytes(transaction)
461 if e != nil {
462 return 0, nil, nil, e
463 }
464 }
465 return blockHeight, &blockHeader, transactions, nil
466 }
467 468 // parseFilteredBlockDisconnectedParams parses out the parameters included in a filteredblockdisconnected notification.
469 //
470 // NOTE: This is a pod extension ported from github. com/decred/dcrrpcclient and requires a websocket connection.
471 func parseFilteredBlockDisconnectedParams(params []js.RawMessage) (
472 int32,
473 *wire.BlockHeader, error,
474 ) {
475 if len(params) < 2 {
476 return 0, nil, wrongNumParams(len(params))
477 }
478 // Unmarshal first parameter as an integer.
479 var blockHeight int32
480 e := js.Unmarshal(params[0], &blockHeight)
481 if e != nil {
482 return 0, nil, e
483 }
484 // Unmarshal second parameter as a slice of bytes.
485 blockHeaderBytes, e := parseHexParam(params[1])
486 if e != nil {
487 return 0, nil, e
488 }
489 // Deserialize block header from slice of bytes.
490 var blockHeader wire.BlockHeader
491 e = blockHeader.Deserialize(bytes.NewReader(blockHeaderBytes))
492 if e != nil {
493 return 0, nil, e
494 }
495 return blockHeight, &blockHeader, nil
496 }
497 498 func parseHexParam(param js.RawMessage) ([]byte, error) {
499 var s string
500 e := js.Unmarshal(param, &s)
501 if e != nil {
502 return nil, e
503 }
504 return hex.DecodeString(s)
505 }
506 507 // parseRelevantTxAcceptedParams parses out the parameter included in a relevanttxaccepted notification.
508 func parseRelevantTxAcceptedParams(params []js.RawMessage) (transaction []byte, e error) {
509 if len(params) < 1 {
510 return nil, wrongNumParams(len(params))
511 }
512 return parseHexParam(params[0])
513 }
514 515 // parseChainTxNtfnParams parses out the transaction and optional details about the block it's mined in from the
516 // parameters of recvtx and redeemingtx notifications.
517 func parseChainTxNtfnParams(params []js.RawMessage) (
518 *util.Tx,
519 *btcjson.BlockDetails, error,
520 ) {
521 if len(params) == 0 || len(params) > 2 {
522 return nil, nil, wrongNumParams(len(params))
523 }
524 // Unmarshal first parameter as a string.
525 var txHex string
526 e := js.Unmarshal(params[0], &txHex)
527 if e != nil {
528 return nil, nil, e
529 }
530 // If present, unmarshal second optional parameter as the block details JSON object.
531 var block *btcjson.BlockDetails
532 if len(params) > 1 {
533 e = js.Unmarshal(params[1], &block)
534 if e != nil {
535 return nil, nil, e
536 }
537 }
538 // Hex decode and deserialize the transaction.
539 serializedTx, e := hex.DecodeString(txHex)
540 if e != nil {
541 return nil, nil, e
542 }
543 var msgTx wire.MsgTx
544 e = msgTx.Deserialize(bytes.NewReader(serializedTx))
545 if e != nil {
546 return nil, nil, e
547 }
548 // TODO: Change recvtx and redeemingtx callback signatures to use nicer
549 // types for details about the block (block hash as a chainhash.Hash,
550 // block time as a time.Time, etc.).
551 return util.NewTx(&msgTx), block, nil
552 }
553 554 // parseRescanProgressParams parses out the height of the last rescanned block from the parameters of rescanfinished and
555 // rescanprogress notifications.
556 func parseRescanProgressParams(params []js.RawMessage) (*chainhash.Hash, int32, time.Time, error) {
557 if len(params) != 3 {
558 return nil, 0, time.Time{}, wrongNumParams(len(params))
559 }
560 // Unmarshal first parameter as an string.
561 var hashStr string
562 e := js.Unmarshal(params[0], &hashStr)
563 if e != nil {
564 return nil, 0, time.Time{}, e
565 }
566 // Unmarshal second parameter as an integer.
567 var height int32
568 e = js.Unmarshal(params[1], &height)
569 if e != nil {
570 return nil, 0, time.Time{}, e
571 }
572 // Unmarshal third parameter as an integer.
573 var blkTime int64
574 e = js.Unmarshal(params[2], &blkTime)
575 if e != nil {
576 return nil, 0, time.Time{}, e
577 }
578 // Decode string encoding of block hash.
579 hash, e := chainhash.NewHashFromStr(hashStr)
580 if e != nil {
581 return nil, 0, time.Time{}, e
582 }
583 return hash, height, time.Unix(blkTime, 0), nil
584 }
585 586 // parseTxAcceptedNtfnParams parses out the transaction hash and total amount from the parameters of a txaccepted
587 // notification.
588 func parseTxAcceptedNtfnParams(params []js.RawMessage) (
589 *chainhash.Hash,
590 amt.Amount, error,
591 ) {
592 if len(params) != 2 {
593 return nil, 0, wrongNumParams(len(params))
594 }
595 // Unmarshal first parameter as a string.
596 var txHashStr string
597 e := js.Unmarshal(params[0], &txHashStr)
598 if e != nil {
599 return nil, 0, e
600 }
601 // Unmarshal second parameter as a floating point number.
602 var fAmt float64
603 e = js.Unmarshal(params[1], &fAmt)
604 if e != nil {
605 return nil, 0, e
606 }
607 // Bounds check amount.
608 amt, e := amt.NewAmount(fAmt)
609 if e != nil {
610 return nil, 0, e
611 }
612 // Decode string encoding of transaction sha.
613 txHash, e := chainhash.NewHashFromStr(txHashStr)
614 if e != nil {
615 return nil, 0, e
616 }
617 return txHash, amt, nil
618 }
619 620 // parseTxAcceptedVerboseNtfnParams parses out details about a raw transaction from the parameters of a
621 // txacceptedverbose notification.
622 func parseTxAcceptedVerboseNtfnParams(params []js.RawMessage) (
623 *btcjson.TxRawResult,
624 error,
625 ) {
626 if len(params) != 1 {
627 return nil, wrongNumParams(len(params))
628 }
629 // Unmarshal first parameter as a raw transaction result object.
630 var rawTx btcjson.TxRawResult
631 e := js.Unmarshal(params[0], &rawTx)
632 if e != nil {
633 return nil, e
634 }
635 // TODO: change txacceptedverbose notification callbacks to use nicer
636 // types for all details about the transaction (i.e.
637 // decoding hashes from their string encoding).
638 return &rawTx, nil
639 }
640 641 // parsePodConnectedNtfnParams parses out the connection status of pod and btcwallet from the parameters of a
642 // podconnected notification.
643 func parsePodConnectedNtfnParams(params []js.RawMessage) (bool, error) {
644 if len(params) != 1 {
645 return false, wrongNumParams(len(params))
646 }
647 // Unmarshal first parameter as a boolean.
648 var connected bool
649 e := js.Unmarshal(params[0], &connected)
650 if e != nil {
651 return false, e
652 }
653 return connected, nil
654 }
655 656 // parseAccountBalanceNtfnParams parses out the account name, total balance, and whether or not the balance is confirmed
657 // or unconfirmed from the parameters of an accountbalance notification.
658 func parseAccountBalanceNtfnParams(params []js.RawMessage) (
659 account string,
660 balance amt.Amount, confirmed bool, e error,
661 ) {
662 if len(params) != 3 {
663 return "", 0, false, wrongNumParams(len(params))
664 }
665 // Unmarshal first parameter as a string.
666 e = js.Unmarshal(params[0], &account)
667 if e != nil {
668 return "", 0, false, e
669 }
670 // Unmarshal second parameter as a floating point number.
671 var fBal float64
672 e = js.Unmarshal(params[1], &fBal)
673 if e != nil {
674 return "", 0, false, e
675 }
676 // Unmarshal third parameter as a boolean.
677 e = js.Unmarshal(params[2], &confirmed)
678 if e != nil {
679 return "", 0, false, e
680 }
681 // Bounds check amount.
682 bal, e := amt.NewAmount(fBal)
683 if e != nil {
684 return "", 0, false, e
685 }
686 return account, bal, confirmed, nil
687 }
688 689 // parseWalletLockStateNtfnParams parses out the account name and locked state of an account from the parameters of a
690 // walletlockstate notification.
691 func parseWalletLockStateNtfnParams(params []js.RawMessage) (
692 account string,
693 locked bool, e error,
694 ) {
695 if len(params) != 2 {
696 return "", false, wrongNumParams(len(params))
697 }
698 // Unmarshal first parameter as a string.
699 e = js.Unmarshal(params[0], &account)
700 if e != nil {
701 return "", false, e
702 }
703 // Unmarshal second parameter as a boolean.
704 e = js.Unmarshal(params[1], &locked)
705 if e != nil {
706 return "", false, e
707 }
708 return account, locked, nil
709 }
710 711 // FutureNotifyBlocksResult is a future promise to deliver the result of a NotifyBlocksAsync RPC invocation (or an
712 // applicable error).
713 type FutureNotifyBlocksResult chan *response
714 715 // Receive waits for the response promised by the future and returns an
716 // error if the registration was not successful.
717 func (r FutureNotifyBlocksResult) Receive() (e error) {
718 _, e = receiveFuture(r)
719 return e
720 }
721 722 // NotifyBlocksAsync returns an instance of a type that can be used to get the result of the RPC at some future time by
723 // invoking the Receive function on the returned instance.
724 //
725 // See NotifyBlocks for the blocking version and more details.
726 //
727 // NOTE: This is a pod extension and requires a websocket connection.
728 func (c *Client) NotifyBlocksAsync() FutureNotifyBlocksResult {
729 // Not supported in HTTP POST mode.
730 if c.config.HTTPPostMode {
731 return newFutureError(ErrWebsocketsRequired)
732 }
733 // Ignore the notification if the client is not interested in notifications.
734 if c.ntfnHandlers == nil {
735 return newNilFutureResult()
736 }
737 cmd := btcjson.NewNotifyBlocksCmd()
738 return c.sendCmd(cmd)
739 }
740 741 // NotifyBlocks registers the client to receive notifications when blocks are connected and disconnected from the main
742 // chain.
743 //
744 // The notifications are delivered to the notification handlers associated with the client. Calling this function has no
745 // effect if there are no notification handlers and will result in an error if the client is configured to run in HTTP
746 // POST mode.
747 //
748 // The notifications delivered as a result of this call will be via one of or OnBlockDisconnected. NOTE: This is a pod
749 // extension and requires a websocket connection.
750 func (c *Client) NotifyBlocks() (e error) {
751 return c.NotifyBlocksAsync().Receive()
752 }
753 754 // FutureNotifySpentResult is a future promise to deliver the result of a NotifySpentAsync RPC invocation (or an
755 // applicable error).
756 //
757 // NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
758 type FutureNotifySpentResult chan *response
759 760 // Receive waits for the response promised by the future and returns an
761 // error if the registration was not successful.
762 func (r FutureNotifySpentResult) Receive() (e error) {
763 _, e = receiveFuture(r)
764 return e
765 }
766 767 // notifySpentInternal is the same as notifySpentAsync except it accepts the converted outpoints as a parameter so the
768 // client can more efficiently recreate the previous notification state on reconnect.
769 func (c *Client) notifySpentInternal(outpoints []btcjson.OutPoint) FutureNotifySpentResult {
770 // Not supported in HTTP POST mode.
771 if c.config.HTTPPostMode {
772 return newFutureError(ErrWebsocketsRequired)
773 }
774 // Ignore the notification if the client is not interested in notifications.
775 if c.ntfnHandlers == nil {
776 return newNilFutureResult()
777 }
778 cmd := btcjson.NewNotifySpentCmd(outpoints)
779 return c.sendCmd(cmd)
780 }
781 782 // newOutPointFromWire constructs the json representation of a transaction outpoint from the wire type.
783 func newOutPointFromWire(op *wire.OutPoint) btcjson.OutPoint {
784 return btcjson.OutPoint{
785 Hash: op.Hash.String(),
786 Index: op.Index,
787 }
788 }
789 790 // NotifySpentAsync returns an instance of a type that can be used to get the result of the RPC at some future time by
791 // invoking the Receive function on the returned instance.
792 //
793 // See NotifySpent for the blocking version and more details.
794 //
795 // NOTE: This is a pod extension and requires a websocket connection.
796 //
797 // NOTE: Deprecated. Use LoadTxFilterAsync instead.
798 func (c *Client) NotifySpentAsync(outpoints []*wire.OutPoint) FutureNotifySpentResult {
799 // Not supported in HTTP POST mode.
800 if c.config.HTTPPostMode {
801 return newFutureError(ErrWebsocketsRequired)
802 }
803 // Ignore the notification if the client is not interested in notifications.
804 if c.ntfnHandlers == nil {
805 return newNilFutureResult()
806 }
807 ops := make([]btcjson.OutPoint, 0, len(outpoints))
808 for _, outpoint := range outpoints {
809 ops = append(ops, newOutPointFromWire(outpoint))
810 }
811 cmd := btcjson.NewNotifySpentCmd(ops)
812 return c.sendCmd(cmd)
813 }
814 815 // NotifySpent registers the client to receive notifications when the passed transaction outputs are spent.
816 //
817 // The notifications are delivered to the notification handlers associated with the client. Calling this function has no
818 // effect if there are no notification handlers and will result in an error if the client is configured to run in HTTP
819 // POST mode.
820 //
821 // The notifications delivered as a result of this call will be via OnRedeemingTx.
822 //
823 // NOTE: This is a pod extension and requires a websocket connection.
824 //
825 // NOTE: Deprecated. Use LoadTxFilter instead.
826 func (c *Client) NotifySpent(outpoints []*wire.OutPoint) (e error) {
827 return c.NotifySpentAsync(outpoints).Receive()
828 }
829 830 // FutureNotifyNewTransactionsResult is a future promise to deliver the result of a NotifyNewTransactionsAsync RPC
831 // invocation (or an applicable error).
832 type FutureNotifyNewTransactionsResult chan *response
833 834 // Receive waits for the response promised by the future and returns an error if the registration was not successful.
835 func (r FutureNotifyNewTransactionsResult) Receive() (e error) {
836 _, e = receiveFuture(r)
837 return e
838 }
839 840 // NotifyNewTransactionsAsync returns an instance of a type that can be used to get the result of the RPC at some future
841 // time by invoking the Receive function on the returned instance.
842 //
843 // See NotifyNewTransactionsAsync for the blocking version and more details.
844 //
845 // NOTE: This is a pod extension and requires a websocket connection.
846 func (c *Client) NotifyNewTransactionsAsync(verbose bool) FutureNotifyNewTransactionsResult {
847 // Not supported in HTTP POST mode.
848 if c.config.HTTPPostMode {
849 return newFutureError(ErrWebsocketsRequired)
850 }
851 // Ignore the notification if the client is not interested in notifications.
852 if c.ntfnHandlers == nil {
853 return newNilFutureResult()
854 }
855 cmd := btcjson.NewNotifyNewTransactionsCmd(&verbose)
856 return c.sendCmd(cmd)
857 }
858 859 // NotifyNewTransactions registers the client to receive notifications every time a new transaction is accepted to the
860 // memory pool.
861 //
862 // The notifications are delivered to the notification handlers associated with the client. Calling this function has no
863 // effect if there are no notification handlers and will result in an error if the client is configured to run in HTTP
864 // POST mode.
865 //
866 // The notifications delivered as a result of this call will be via one of OnTxAccepted (when verbose is false) or
867 // OnTxAcceptedVerbose ( when verbose is true). NOTE: This is a pod extension and requires a websocket connection.
868 func (c *Client) NotifyNewTransactions(verbose bool) (e error) {
869 return c.NotifyNewTransactionsAsync(verbose).Receive()
870 }
871 872 // FutureNotifyReceivedResult is a future promise to deliver the result of a NotifyReceivedAsync RPC invocation (or an
873 // applicable error).
874 //
875 // NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
876 type FutureNotifyReceivedResult chan *response
877 878 // Receive waits for the response promised by the future and returns an error if the registration was not successful.
879 func (r FutureNotifyReceivedResult) Receive() (e error) {
880 _, e = receiveFuture(r)
881 return e
882 }
883 884 // notifyReceivedInternal is the same as notifyReceivedAsync except it accepts the converted addresses as a parameter so
885 // the client can more efficiently recreate the previous notification state on reconnect.
886 func (c *Client) notifyReceivedInternal(addresses []string) FutureNotifyReceivedResult {
887 // Not supported in HTTP POST mode.
888 if c.config.HTTPPostMode {
889 return newFutureError(ErrWebsocketsRequired)
890 }
891 // Ignore the notification if the client is not interested in notifications.
892 if c.ntfnHandlers == nil {
893 return newNilFutureResult()
894 }
895 // Convert addresses to strings.
896 cmd := btcjson.NewNotifyReceivedCmd(addresses)
897 return c.sendCmd(cmd)
898 }
899 900 // NotifyReceivedAsync returns an instance of a type that can be used to get the result of the RPC at some future time
901 // by invoking the Receive function on the returned instance.
902 //
903 // See NotifyReceived for the blocking version and more details.
904 //
905 // NOTE: This is a pod extension and requires a websocket connection.
906 //
907 // NOTE: Deprecated. Use LoadTxFilterAsync instead.
908 func (c *Client) NotifyReceivedAsync(addresses []btcaddr.Address) FutureNotifyReceivedResult {
909 // Not supported in HTTP POST mode.
910 if c.config.HTTPPostMode {
911 return newFutureError(ErrWebsocketsRequired)
912 }
913 // Ignore the notification if the client is not interested in notifications.
914 if c.ntfnHandlers == nil {
915 return newNilFutureResult()
916 }
917 // Convert addresses to strings.
918 addrs := make([]string, 0, len(addresses))
919 for _, addr := range addresses {
920 addrs = append(addrs, addr.String())
921 }
922 cmd := btcjson.NewNotifyReceivedCmd(addrs)
923 return c.sendCmd(cmd)
924 }
925 926 // NotifyReceived registers the client to receive notifications every time a new transaction which pays to one of the
927 // passed addresses is accepted to memory pool or in a block connected to the block chain.
928 //
929 // In addition, when one of these transactions is detected, the client is also automatically registered for
930 // notifications when the new transaction outpoints the address now has available are spent (
931 //
932 // See NotifySpent). The notifications are delivered to the notification handlers associated with the client.
933 //
934 // Calling this function has no effect if there are no notification handlers and will result in an error if the client
935 // is configured to run in HTTP POST mode.
936 //
937 // The notifications delivered as a result of this call will be via one of *OnRecvTx (for transactions that receive
938 // funds to one of the passed addresses) or OnRedeemingTx ( for transactions which spend from one of the outpoints which
939 // are automatically registered upon receipt of funds to the address).
940 //
941 // NOTE: This is a pod extension and requires a websocket connection.
942 //
943 // NOTE: Deprecated. Use LoadTxFilter instead.
944 func (c *Client) NotifyReceived(addresses []btcaddr.Address) (e error) {
945 return c.NotifyReceivedAsync(addresses).Receive()
946 }
947 948 // FutureRescanResult is a future promise to deliver the result of a RescanAsync or RescanEndHeightAsync RPC invocation
949 // ( or an applicable error).
950 //
951 // NOTE: Deprecated. Use FutureRescanBlocksResult instead.
952 type FutureRescanResult chan *response
953 954 // Receive waits for the response promised by the future and returns an error if the rescan was not successful.
955 func (r FutureRescanResult) Receive() (e error) {
956 _, e = receiveFuture(r)
957 return e
958 }
959 960 // RescanAsync returns an instance of a type that can be used to get the result of the RPC at some future time by
961 // invoking the Receive function on the returned instance.
962 //
963 // See Rescan for the blocking version and more details.
964 //
965 // NOTE: Rescan requests are not issued on client reconnect and must be performed manually (ideally with a new start
966 // height based on the last rescan progress notification).
967 //
968 // See the OnClientConnected notification callback for a good call site to reissue rescan requests on connect and
969 // reconnect.
970 //
971 // NOTE: This is a pod extension and requires a websocket connection.
972 //
973 // NOTE: Deprecated. Use RescanBlocksAsync instead.
974 func (c *Client) RescanAsync(
975 startBlock *chainhash.Hash,
976 addresses []btcaddr.Address,
977 outpoints []*wire.OutPoint,
978 ) FutureRescanResult {
979 // Not supported in HTTP POST mode.
980 if c.config.HTTPPostMode {
981 return newFutureError(ErrWebsocketsRequired)
982 }
983 // Ignore the notification if the client is not interested in notifications.
984 if c.ntfnHandlers == nil {
985 return newNilFutureResult()
986 }
987 // Convert block hashes to strings.
988 var startBlockHashStr string
989 if startBlock != nil {
990 startBlockHashStr = startBlock.String()
991 }
992 // Convert addresses to strings.
993 addrs := make([]string, 0, len(addresses))
994 for _, addr := range addresses {
995 addrs = append(addrs, addr.String())
996 }
997 // Convert outpoints.
998 ops := make([]btcjson.OutPoint, 0, len(outpoints))
999 for _, op := range outpoints {
1000 ops = append(ops, newOutPointFromWire(op))
1001 }
1002 cmd := btcjson.NewRescanCmd(startBlockHashStr, addrs, ops, nil)
1003 return c.sendCmd(cmd)
1004 }
1005 1006 // Rescan rescans the block chain starting from the provided starting block to the end of the longest chain for
1007 // transactions that pay to the passed addresses and transactions which spend the passed outpoints.
1008 //
1009 // The notifications of found transactions are delivered to the notification handlers associated with client and this
1010 // call will not return until the rescan has completed. Calling this function has no effect if there are no notification
1011 // handlers and will result in an error if the client is configured to run in HTTP POST mode.
1012 //
1013 // The notifications delivered as a result of this call will be via one of OnRedeemingTx (for transactions which spend
1014 // from the one of the passed outpoints), OnRecvTx (for transactions that receive funds to one of the passed addresses),
1015 // and OnRescanProgress (for rescan progress updates).
1016 //
1017 // See RescanEndBlock to also specify an ending block to finish the rescan without continuing through the best block on
1018 // the main chain.
1019 //
1020 // NOTE: Rescan requests are not issued on client reconnect and must be performed manually (ideally with a new start
1021 // height based on the last rescan progress notification).
1022 //
1023 // See the OnClientConnected notification callback for a good call site to reissue rescan requests on connect and
1024 // reconnect.
1025 //
1026 // NOTE: This is a pod extension and requires a websocket connection.
1027 //
1028 // NOTE: Deprecated. Use RescanBlocks instead.
1029 func (c *Client) Rescan(
1030 startBlock *chainhash.Hash,
1031 addresses []btcaddr.Address,
1032 outpoints []*wire.OutPoint,
1033 ) (e error) {
1034 return c.RescanAsync(startBlock, addresses, outpoints).Receive()
1035 }
1036 1037 // RescanEndBlockAsync returns an instance of a type that can be used to get the result of the RPC at some future time
1038 // by invoking the Receive function on the returned instance.
1039 //
1040 // See RescanEndBlock for the blocking version and more details.
1041 //
1042 // NOTE: This is a pod extension and requires a websocket connection.
1043 //
1044 // NOTE: Deprecated. Use RescanBlocksAsync instead.
1045 func (c *Client) RescanEndBlockAsync(
1046 startBlock *chainhash.Hash,
1047 addresses []btcaddr.Address, outpoints []*wire.OutPoint,
1048 endBlock *chainhash.Hash,
1049 ) FutureRescanResult {
1050 // Not supported in HTTP POST mode.
1051 if c.config.HTTPPostMode {
1052 return newFutureError(ErrWebsocketsRequired)
1053 }
1054 // Ignore the notification if the client is not interested in notifications.
1055 if c.ntfnHandlers == nil {
1056 return newNilFutureResult()
1057 }
1058 // Convert block hashes to strings.
1059 var startBlockHashStr, endBlockHashStr string
1060 if startBlock != nil {
1061 startBlockHashStr = startBlock.String()
1062 }
1063 if endBlock != nil {
1064 endBlockHashStr = endBlock.String()
1065 }
1066 // Convert addresses to strings.
1067 addrs := make([]string, 0, len(addresses))
1068 for _, addr := range addresses {
1069 addrs = append(addrs, addr.String())
1070 }
1071 // Convert outpoints.
1072 ops := make([]btcjson.OutPoint, 0, len(outpoints))
1073 for _, op := range outpoints {
1074 ops = append(ops, newOutPointFromWire(op))
1075 }
1076 cmd := btcjson.NewRescanCmd(
1077 startBlockHashStr, addrs, ops,
1078 &endBlockHashStr,
1079 )
1080 return c.sendCmd(cmd)
1081 }
1082 1083 // RescanEndHeight rescans the block chain starting from the provided starting block up to the provided ending block for
1084 // transactions that pay to the passed addresses and transactions which spend the passed outpoints.
1085 //
1086 // The notifications of found transactions are delivered to the notification handlers associated with client and this
1087 // call will not return until the rescan has completed.
1088 //
1089 // Calling this function has no effect if there are no notification handlers and will result in an error if the client
1090 // is configured to run in HTTP POST mode.
1091 //
1092 // The notifications delivered as a result of this call will be via one of OnRedeemingTx (for transactions which spend
1093 // from the one of the passed outpoints), OnRecvTx (for transactions that receive funds to one of the passed addresses),
1094 // and OnRescanProgress (for rescan progress updates).
1095 //
1096 // See Rescan to also perform a rescan through current end of the longest
1097 // chain. NOTE: This is a pod extension and requires a websocket connection.
1098 //
1099 // NOTE: Deprecated. Use RescanBlocks instead.
1100 func (c *Client) RescanEndHeight(
1101 startBlock *chainhash.Hash,
1102 addresses []btcaddr.Address, outpoints []*wire.OutPoint,
1103 endBlock *chainhash.Hash,
1104 ) (e error) {
1105 return c.RescanEndBlockAsync(
1106 startBlock, addresses, outpoints,
1107 endBlock,
1108 ).Receive()
1109 }
1110 1111 // FutureLoadTxFilterResult is a future promise to deliver the result of a LoadTxFilterAsync RPC invocation (or an
1112 // applicable error).
1113 //
1114 // NOTE: This is a pod extension ported from github.com/decred/dcrrpcclient and requires a websocket connection.
1115 type FutureLoadTxFilterResult chan *response
1116 1117 // Receive waits for the response promised by the future and returns an error if the registration was not successful.
1118 //
1119 // NOTE: This is a pod extension ported from github.com/decred/dcrrpcclient and requires a websocket connection.
1120 func (r FutureLoadTxFilterResult) Receive() (e error) {
1121 _, e = receiveFuture(r)
1122 return e
1123 }
1124 1125 // LoadTxFilterAsync returns an instance of a type that can be used to get the result of the RPC at some future time by
1126 // invoking the Receive function on the returned instance.
1127 //
1128 // See LoadTxFilter for the blocking version and more details.
1129 //
1130 // NOTE: This is a pod extension ported from github. com/decred/dcrrpcclient and requires a websocket connection.
1131 func (c *Client) LoadTxFilterAsync(
1132 reload bool, addresses []btcaddr.Address,
1133 outPoints []wire.OutPoint,
1134 ) FutureLoadTxFilterResult {
1135 addrStrs := make([]string, len(addresses))
1136 for i, a := range addresses {
1137 addrStrs[i] = a.EncodeAddress()
1138 }
1139 outPointObjects := make([]btcjson.OutPoint, len(outPoints))
1140 for i := range outPoints {
1141 outPointObjects[i] = btcjson.OutPoint{
1142 Hash: outPoints[i].Hash.String(),
1143 Index: outPoints[i].Index,
1144 }
1145 }
1146 cmd := btcjson.NewLoadTxFilterCmd(reload, addrStrs, outPointObjects)
1147 return c.sendCmd(cmd)
1148 }
1149 1150 // LoadTxFilter loads reloads or adds data to a websocket client's transaction filter.
1151 //
1152 // The filter is consistently updated based on inspected transactions during mempool acceptance, block acceptance, and
1153 // for all rescanned blocks.
1154 //
1155 // NOTE: This is a pod extension ported from github. com/decred/dcrrpcclient and requires a websocket connection.
1156 func (c *Client) LoadTxFilter(reload bool, addresses []btcaddr.Address, outPoints []wire.OutPoint) (e error) {
1157 return c.LoadTxFilterAsync(reload, addresses, outPoints).Receive()
1158 }
1159