From a835a9ca8bf1bec3a27b621dcea2ebe6b863d7a8 Mon Sep 17 00:00:00 2001
From: Alex <alex@akselrod.org>
Date: Tue, 24 Jan 2017 19:39:03 -0700
Subject: [PATCH] Port `loadtxfilter` JSON-RPC command from dcrd

---
 notify.go | 263 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 262 insertions(+), 1 deletion(-)

diff --git a/notify.go b/notify.go
index 2e486a1a7..3391708c5 100644
--- a/notify.go
+++ b/notify.go
@@ -1,4 +1,5 @@
-// Copyright (c) 2014-2016 The btcsuite developers
+// Copyright (c) 2014-2017 The btcsuite developers
+// Copyright (c) 2015-2017 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -93,19 +94,40 @@ type NotificationHandlers struct {
 	// (best) chain.  It will only be invoked if a preceding call to
 	// NotifyBlocks has been made to register for the notification and the
 	// function is non-nil.
+	//
+	// NOTE: Deprecated. Use OnFilteredBlockConnected instead.
 	OnBlockConnected func(hash *chainhash.Hash, height int32, t time.Time)
 
+	// OnFilteredBlockConnected 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
+	// function is non-nil.  Its parameters differ from OnBlockConnected: it
+	// receives the block's height, header, and relevant transactions.
+	OnFilteredBlockConnected func(height int32, header *wire.BlockHeader,
+		txs []*btcutil.Tx)
+
 	// OnBlockDisconnected is invoked when a block is disconnected from 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
 	// function is non-nil.
+	//
+	// NOTE: Deprecated. Use OnFilteredBlockDisconnected instead.
 	OnBlockDisconnected func(hash *chainhash.Hash, height int32, t time.Time)
 
+	// OnFilteredBlockDisconnected is invoked when a block is disconnected
+	// from the longest (best) chain.  It will only be invoked if a
+	// preceding NotifyBlocks has been made to register for the notification
+	// and the call to function is non-nil.  Its parameters differ from
+	// OnBlockDisconnected: it receives the block's height and header.
+	OnFilteredBlockDisconnected func(height int32, header *wire.BlockHeader)
+
 	// OnRecvTx is invoked when a transaction that receives funds to a
 	// registered address is received into the memory pool and also
 	// connected to the longest (best) chain.  It will only be invoked if a
 	// preceding call to NotifyReceived, Rescan, or RescanEndHeight has been
 	// made to register for the notification and the function is non-nil.
+	//
+	// NOTE: Deprecated. Use OnRelevantTxAccepted instead.
 	OnRecvTx func(transaction *btcutil.Tx, details *btcjson.BlockDetails)
 
 	// OnRedeemingTx is invoked when a transaction that spends a registered
@@ -118,8 +140,17 @@ type NotificationHandlers struct {
 	// for the outpoints that are now "owned" as a result of receiving
 	// funds to the registered addresses.  This means it is possible for
 	// this to invoked indirectly as the result of a NotifyReceived call.
+	//
+	// NOTE: Deprecated. Use OnRelevantTxAccepted instead.
 	OnRedeemingTx func(transaction *btcutil.Tx, details *btcjson.BlockDetails)
 
+	// OnRelevantTxAccepted is invoked when an unmined transaction passes
+	// the client's transaction filter.
+	//
+	// NOTE: This is a btcsuite extension ported from
+	// github.com/decred/dcrrpcclient.
+	OnRelevantTxAccepted func(transaction []byte)
+
 	// OnRescanFinished is invoked after a rescan finishes due to a previous
 	// call to Rescan or RescanEndHeight.  Finished rescans should be
 	// signaled on this notification, rather than relying on the return
@@ -200,6 +231,25 @@ func (c *Client) handleNotification(ntfn *rawNotification) {
 
 		c.ntfnHandlers.OnBlockConnected(blockHash, blockHeight, blockTime)
 
+	// OnFilteredBlockConnected
+	case btcjson.FilteredBlockConnectedNtfnMethod:
+		// Ignore the notification if the client is not interested in
+		// it.
+		if c.ntfnHandlers.OnFilteredBlockConnected == nil {
+			return
+		}
+
+		blockHeight, blockHeader, transactions, err :=
+			parseFilteredBlockConnectedParams(ntfn.Params)
+		if err != nil {
+			log.Warnf("Received invalid filtered block "+
+				"connected notification: %v", err)
+			return
+		}
+
+		c.ntfnHandlers.OnFilteredBlockConnected(blockHeight,
+			blockHeader, transactions)
+
 	// OnBlockDisconnected
 	case btcjson.BlockDisconnectedNtfnMethod:
 		// Ignore the notification if the client is not interested in
@@ -217,6 +267,25 @@ func (c *Client) handleNotification(ntfn *rawNotification) {
 
 		c.ntfnHandlers.OnBlockDisconnected(blockHash, blockHeight, blockTime)
 
+	// OnFilteredBlockDisconnected
+	case btcjson.FilteredBlockDisconnectedNtfnMethod:
+		// Ignore the notification if the client is not interested in
+		// it.
+		if c.ntfnHandlers.OnFilteredBlockDisconnected == nil {
+			return
+		}
+
+		blockHeight, blockHeader, err :=
+			parseFilteredBlockDisconnectedParams(ntfn.Params)
+		if err != nil {
+			log.Warnf("Received invalid filtered block "+
+				"disconnected notification: %v", err)
+			return
+		}
+
+		c.ntfnHandlers.OnFilteredBlockDisconnected(blockHeight,
+			blockHeader)
+
 	// OnRecvTx
 	case btcjson.RecvTxNtfnMethod:
 		// Ignore the notification if the client is not interested in
@@ -251,6 +320,23 @@ func (c *Client) handleNotification(ntfn *rawNotification) {
 
 		c.ntfnHandlers.OnRedeemingTx(tx, block)
 
+	// OnRelevantTxAccepted
+	case btcjson.RelevantTxAcceptedNtfnMethod:
+		// Ignore the notification if the client is not interested in
+		// it.
+		if c.ntfnHandlers.OnRelevantTxAccepted == nil {
+			return
+		}
+
+		transaction, err := parseRelevantTxAcceptedParams(ntfn.Params)
+		if err != nil {
+			log.Warnf("Received invalid relevanttxaccepted "+
+				"notification: %v", err)
+			return
+		}
+
+		c.ntfnHandlers.OnRelevantTxAccepted(transaction)
+
 	// OnRescanFinished
 	case btcjson.RescanFinishedNtfnMethod:
 		// Ignore the notification if the client is not interested in
@@ -435,6 +521,115 @@ func parseChainNtfnParams(params []json.RawMessage) (*chainhash.Hash,
 	return blockHash, blockHeight, blockTime, nil
 }
 
+// parseFilteredBlockConnectedParams parses out the parameters included in a
+// filteredblockconnected notification.
+//
+// NOTE: This is a btcd extension ported from github.com/decred/dcrrpcclient
+// and requires a websocket connection.
+func parseFilteredBlockConnectedParams(params []json.RawMessage) (int32,
+	*wire.BlockHeader, []*btcutil.Tx, error) {
+
+	if len(params) < 3 {
+		return 0, nil, nil, wrongNumParams(len(params))
+	}
+
+	// Unmarshal first parameter as an integer.
+	var blockHeight int32
+	err := json.Unmarshal(params[0], &blockHeight)
+	if err != nil {
+		return 0, nil, nil, err
+	}
+
+	// Unmarshal second parameter as a slice of bytes.
+	blockHeaderBytes, err := parseHexParam(params[1])
+	if err != nil {
+		return 0, nil, nil, err
+	}
+
+	// Deserialize block header from slice of bytes.
+	var blockHeader wire.BlockHeader
+	err = blockHeader.Deserialize(bytes.NewReader(blockHeaderBytes))
+	if err != nil {
+		return 0, nil, nil, err
+	}
+
+	// Unmarshal third parameter as a slice of hex-encoded strings.
+	var hexTransactions []string
+	err = json.Unmarshal(params[2], &hexTransactions)
+	if err != nil {
+		return 0, nil, nil, err
+	}
+
+	// Create slice of transactions from slice of strings by hex-decoding.
+	transactions := make([]*btcutil.Tx, len(hexTransactions))
+	for i, hexTx := range hexTransactions {
+		transaction, err := hex.DecodeString(hexTx)
+		if err != nil {
+			return 0, nil, nil, err
+		}
+
+		transactions[i], err = btcutil.NewTxFromBytes(transaction)
+		if err != nil {
+			return 0, nil, nil, err
+		}
+	}
+
+	return blockHeight, &blockHeader, transactions, nil
+}
+
+// parseFilteredBlockDisconnectedParams parses out the parameters included in a
+// filteredblockdisconnected notification.
+//
+// NOTE: This is a btcd extension ported from github.com/decred/dcrrpcclient
+// and requires a websocket connection.
+func parseFilteredBlockDisconnectedParams(params []json.RawMessage) (int32,
+	*wire.BlockHeader, error) {
+	if len(params) < 2 {
+		return 0, nil, wrongNumParams(len(params))
+	}
+
+	// Unmarshal first parameter as an integer.
+	var blockHeight int32
+	err := json.Unmarshal(params[0], &blockHeight)
+	if err != nil {
+		return 0, nil, err
+	}
+
+	// Unmarshal second parmeter as a slice of bytes.
+	blockHeaderBytes, err := parseHexParam(params[1])
+	if err != nil {
+		return 0, nil, err
+	}
+
+	// Deserialize block header from slice of bytes.
+	var blockHeader wire.BlockHeader
+	err = blockHeader.Deserialize(bytes.NewReader(blockHeaderBytes))
+	if err != nil {
+		return 0, nil, err
+	}
+
+	return blockHeight, &blockHeader, nil
+}
+
+func parseHexParam(param json.RawMessage) ([]byte, error) {
+	var s string
+	err := json.Unmarshal(param, &s)
+	if err != nil {
+		return nil, err
+	}
+	return hex.DecodeString(s)
+}
+
+// parseRelevantTxAcceptedParams parses out the parameter included in a
+// relevanttxaccepted notification.
+func parseRelevantTxAcceptedParams(params []json.RawMessage) (transaction []byte, err error) {
+	if len(params) < 1 {
+		return nil, wrongNumParams(len(params))
+	}
+
+	return parseHexParam(params[0])
+}
+
 // parseChainTxNtfnParams parses out the transaction and optional details about
 // the block it's mined in from the parameters of recvtx and redeemingtx
 // notifications.
@@ -705,6 +900,8 @@ func (c *Client) NotifyBlocks() error {
 
 // FutureNotifySpentResult is a future promise to deliver the result of a
 // NotifySpentAsync RPC invocation (or an applicable error).
+//
+// NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
 type FutureNotifySpentResult chan *response
 
 // Receive waits for the response promised by the future and returns an error
@@ -749,6 +946,8 @@ func newOutPointFromWire(op *wire.OutPoint) btcjson.OutPoint {
 // See NotifySpent for the blocking version and more details.
 //
 // NOTE: This is a btcd extension and requires a websocket connection.
+//
+// NOTE: Deprecated. Use LoadTxFilterAsync instead.
 func (c *Client) NotifySpentAsync(outpoints []*wire.OutPoint) FutureNotifySpentResult {
 	// Not supported in HTTP POST mode.
 	if c.config.HTTPPostMode {
@@ -779,6 +978,8 @@ func (c *Client) NotifySpentAsync(outpoints []*wire.OutPoint) FutureNotifySpentR
 // OnRedeemingTx.
 //
 // NOTE: This is a btcd extension and requires a websocket connection.
+//
+// NOTE: Deprecated. Use LoadTxFilter instead.
 func (c *Client) NotifySpent(outpoints []*wire.OutPoint) error {
 	return c.NotifySpentAsync(outpoints).Receive()
 }
@@ -834,6 +1035,8 @@ func (c *Client) NotifyNewTransactions(verbose bool) error {
 
 // FutureNotifyReceivedResult is a future promise to deliver the result of a
 // NotifyReceivedAsync RPC invocation (or an applicable error).
+//
+// NOTE: Deprecated. Use FutureLoadTxFilterResult instead.
 type FutureNotifyReceivedResult chan *response
 
 // Receive waits for the response promised by the future and returns an error
@@ -870,6 +1073,8 @@ func (c *Client) notifyReceivedInternal(addresses []string) FutureNotifyReceived
 // See NotifyReceived for the blocking version and more details.
 //
 // NOTE: This is a btcd extension and requires a websocket connection.
+//
+// NOTE: Deprecated. Use LoadTxFilterAsync instead.
 func (c *Client) NotifyReceivedAsync(addresses []btcutil.Address) FutureNotifyReceivedResult {
 	// Not supported in HTTP POST mode.
 	if c.config.HTTPPostMode {
@@ -908,6 +1113,8 @@ func (c *Client) NotifyReceivedAsync(addresses []btcutil.Address) FutureNotifyRe
 // the address).
 //
 // NOTE: This is a btcd extension and requires a websocket connection.
+//
+// NOTE: Deprecated. Use LoadTxFilter instead.
 func (c *Client) NotifyReceived(addresses []btcutil.Address) error {
 	return c.NotifyReceivedAsync(addresses).Receive()
 }
@@ -1080,3 +1287,57 @@ func (c *Client) RescanEndHeight(startBlock *chainhash.Hash,
 	return c.RescanEndBlockAsync(startBlock, addresses, outpoints,
 		endBlock).Receive()
 }
+
+// FutureLoadTxFilterResult is a future promise to deliver the result
+// of a LoadTxFilterAsync RPC invocation (or an applicable error).
+//
+// NOTE: This is a btcd extension ported from github.com/decred/dcrrpcclient
+// and requires a websocket connection.
+type FutureLoadTxFilterResult chan *response
+
+// Receive waits for the response promised by the future and returns an error
+// if the registration was not successful.
+//
+// NOTE: This is a btcd extension ported from github.com/decred/dcrrpcclient
+// and requires a websocket connection.
+func (r FutureLoadTxFilterResult) Receive() error {
+	_, err := receiveFuture(r)
+	return err
+}
+
+// LoadTxFilterAsync 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 LoadTxFilter for the blocking version and more details.
+//
+// NOTE: This is a btcd extension ported from github.com/decred/dcrrpcclient
+// and requires a websocket connection.
+func (c *Client) LoadTxFilterAsync(reload bool, addresses []btcutil.Address,
+	outPoints []wire.OutPoint) FutureLoadTxFilterResult {
+
+	addrStrs := make([]string, len(addresses))
+	for i, a := range addresses {
+		addrStrs[i] = a.EncodeAddress()
+	}
+	outPointObjects := make([]btcjson.OutPoint, len(outPoints))
+	for i := range outPoints {
+		outPointObjects[i] = btcjson.OutPoint{
+			Hash:  outPoints[i].Hash.String(),
+			Index: outPoints[i].Index,
+		}
+	}
+
+	cmd := btcjson.NewLoadTxFilterCmd(reload, addrStrs, outPointObjects)
+	return c.sendCmd(cmd)
+}
+
+// LoadTxFilter loads, reloads, or adds data to a websocket client's transaction
+// filter.  The filter is consistently updated based on inspected transactions
+// during mempool acceptance, block acceptance, and for all rescanned blocks.
+//
+// NOTE: This is a btcd extension ported from github.com/decred/dcrrpcclient
+// and requires a websocket connection.
+func (c *Client) LoadTxFilter(reload bool, addresses []btcutil.Address, outPoints []wire.OutPoint) error {
+	return c.LoadTxFilterAsync(reload, addresses, outPoints).Receive()
+}