From c7eaee60208ba0d3acf5cd541996a3f8841e1187 Mon Sep 17 00:00:00 2001
From: danda <danda@osc.co.cr>
Date: Sun, 15 Nov 2015 15:30:13 -0800
Subject: [PATCH] adds filteraddrs param to searchrawtransactions API

---
 btcjson/chainsvrcmds.go      |  28 +++---
 btcjson/chainsvrcmds_test.go | 110 +++++++++++++++---------
 btcjson/chainsvrresults.go   |   2 +-
 rpcserver.go                 | 160 ++++++++++++++++++++++++-----------
 rpcserverhelp.go             |   1 +
 5 files changed, 194 insertions(+), 107 deletions(-)

diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go
index 90441e6e..c16e97ab 100644
--- a/btcjson/chainsvrcmds.go
+++ b/btcjson/chainsvrcmds.go
@@ -549,12 +549,13 @@ func NewReconsiderBlockCmd(blockHash string) *ReconsiderBlockCmd {
 
 // SearchRawTransactionsCmd defines the searchrawtransactions JSON-RPC command.
 type SearchRawTransactionsCmd struct {
-	Address  string
-	Verbose  *int  `jsonrpcdefault:"1"`
-	Skip     *int  `jsonrpcdefault:"0"`
-	Count    *int  `jsonrpcdefault:"100"`
-	VinExtra *int  `jsonrpcdefault:"0"`
-	Reverse  *bool `jsonrpcdefault:"false"`
+	Address     string
+	Verbose     *int  `jsonrpcdefault:"1"`
+	Skip        *int  `jsonrpcdefault:"0"`
+	Count       *int  `jsonrpcdefault:"100"`
+	VinExtra    *int  `jsonrpcdefault:"0"`
+	Reverse     *bool `jsonrpcdefault:"false"`
+	FilterAddrs *[]string
 }
 
 // NewSearchRawTransactionsCmd returns a new instance which can be used to issue a
@@ -562,14 +563,15 @@ type SearchRawTransactionsCmd struct {
 //
 // The parameters which are pointers indicate they are optional.  Passing nil
 // for optional parameters will use the default value.
-func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinExtra *int, reverse *bool) *SearchRawTransactionsCmd {
+func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinExtra *int, reverse *bool, filterAddrs *[]string) *SearchRawTransactionsCmd {
 	return &SearchRawTransactionsCmd{
-		Address:  address,
-		Verbose:  verbose,
-		Skip:     skip,
-		Count:    count,
-		VinExtra: vinExtra,
-		Reverse:  reverse,
+		Address:     address,
+		Verbose:     verbose,
+		Skip:        skip,
+		Count:       count,
+		VinExtra:    vinExtra,
+		Reverse:     reverse,
+		FilterAddrs: filterAddrs,
 	}
 }
 
diff --git a/btcjson/chainsvrcmds_test.go b/btcjson/chainsvrcmds_test.go
index 527bc0ba..5fed0ccc 100644
--- a/btcjson/chainsvrcmds_test.go
+++ b/btcjson/chainsvrcmds_test.go
@@ -700,16 +700,17 @@ func TestChainSvrCmds(t *testing.T) {
 				return btcjson.NewCmd("searchrawtransactions", "1Address")
 			},
 			staticCmd: func() interface{} {
-				return btcjson.NewSearchRawTransactionsCmd("1Address", nil, nil, nil, nil, nil)
+				return btcjson.NewSearchRawTransactionsCmd("1Address", nil, nil, nil, nil, nil, nil)
 			},
 			marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address"],"id":1}`,
 			unmarshalled: &btcjson.SearchRawTransactionsCmd{
-				Address:  "1Address",
-				Verbose:  btcjson.Int(1),
-				Skip:     btcjson.Int(0),
-				Count:    btcjson.Int(100),
-				VinExtra: btcjson.Int(0),
-				Reverse:  btcjson.Bool(false),
+				Address:     "1Address",
+				Verbose:     btcjson.Int(1),
+				Skip:        btcjson.Int(0),
+				Count:       btcjson.Int(100),
+				VinExtra:    btcjson.Int(0),
+				Reverse:     btcjson.Bool(false),
+				FilterAddrs: nil,
 			},
 		},
 		{
@@ -719,16 +720,17 @@ func TestChainSvrCmds(t *testing.T) {
 			},
 			staticCmd: func() interface{} {
 				return btcjson.NewSearchRawTransactionsCmd("1Address",
-					btcjson.Int(0), nil, nil, nil, nil)
+					btcjson.Int(0), nil, nil, nil, nil, nil)
 			},
 			marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0],"id":1}`,
 			unmarshalled: &btcjson.SearchRawTransactionsCmd{
-				Address:  "1Address",
-				Verbose:  btcjson.Int(0),
-				Skip:     btcjson.Int(0),
-				Count:    btcjson.Int(100),
-				VinExtra: btcjson.Int(0),
-				Reverse:  btcjson.Bool(false),
+				Address:     "1Address",
+				Verbose:     btcjson.Int(0),
+				Skip:        btcjson.Int(0),
+				Count:       btcjson.Int(100),
+				VinExtra:    btcjson.Int(0),
+				Reverse:     btcjson.Bool(false),
+				FilterAddrs: nil,
 			},
 		},
 		{
@@ -738,16 +740,17 @@ func TestChainSvrCmds(t *testing.T) {
 			},
 			staticCmd: func() interface{} {
 				return btcjson.NewSearchRawTransactionsCmd("1Address",
-					btcjson.Int(0), btcjson.Int(5), nil, nil, nil)
+					btcjson.Int(0), btcjson.Int(5), nil, nil, nil, nil)
 			},
 			marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5],"id":1}`,
 			unmarshalled: &btcjson.SearchRawTransactionsCmd{
-				Address:  "1Address",
-				Verbose:  btcjson.Int(0),
-				Skip:     btcjson.Int(5),
-				Count:    btcjson.Int(100),
-				VinExtra: btcjson.Int(0),
-				Reverse:  btcjson.Bool(false),
+				Address:     "1Address",
+				Verbose:     btcjson.Int(0),
+				Skip:        btcjson.Int(5),
+				Count:       btcjson.Int(100),
+				VinExtra:    btcjson.Int(0),
+				Reverse:     btcjson.Bool(false),
+				FilterAddrs: nil,
 			},
 		},
 		{
@@ -757,16 +760,17 @@ func TestChainSvrCmds(t *testing.T) {
 			},
 			staticCmd: func() interface{} {
 				return btcjson.NewSearchRawTransactionsCmd("1Address",
-					btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), nil, nil)
+					btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), nil, nil, nil)
 			},
 			marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10],"id":1}`,
 			unmarshalled: &btcjson.SearchRawTransactionsCmd{
-				Address:  "1Address",
-				Verbose:  btcjson.Int(0),
-				Skip:     btcjson.Int(5),
-				Count:    btcjson.Int(10),
-				VinExtra: btcjson.Int(0),
-				Reverse:  btcjson.Bool(false),
+				Address:     "1Address",
+				Verbose:     btcjson.Int(0),
+				Skip:        btcjson.Int(5),
+				Count:       btcjson.Int(10),
+				VinExtra:    btcjson.Int(0),
+				Reverse:     btcjson.Bool(false),
+				FilterAddrs: nil,
 			},
 		},
 		{
@@ -776,16 +780,17 @@ func TestChainSvrCmds(t *testing.T) {
 			},
 			staticCmd: func() interface{} {
 				return btcjson.NewSearchRawTransactionsCmd("1Address",
-					btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), btcjson.Int(1), nil)
+					btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), btcjson.Int(1), nil, nil)
 			},
 			marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10,1],"id":1}`,
 			unmarshalled: &btcjson.SearchRawTransactionsCmd{
-				Address:  "1Address",
-				Verbose:  btcjson.Int(0),
-				Skip:     btcjson.Int(5),
-				Count:    btcjson.Int(10),
-				VinExtra: btcjson.Int(1),
-				Reverse:  btcjson.Bool(false),
+				Address:     "1Address",
+				Verbose:     btcjson.Int(0),
+				Skip:        btcjson.Int(5),
+				Count:       btcjson.Int(10),
+				VinExtra:    btcjson.Int(1),
+				Reverse:     btcjson.Bool(false),
+				FilterAddrs: nil,
 			},
 		},
 		{
@@ -795,16 +800,37 @@ func TestChainSvrCmds(t *testing.T) {
 			},
 			staticCmd: func() interface{} {
 				return btcjson.NewSearchRawTransactionsCmd("1Address",
-					btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), btcjson.Int(1), btcjson.Bool(true))
+					btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), btcjson.Int(1), btcjson.Bool(true), nil)
 			},
 			marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10,1,true],"id":1}`,
 			unmarshalled: &btcjson.SearchRawTransactionsCmd{
-				Address:  "1Address",
-				Verbose:  btcjson.Int(0),
-				Skip:     btcjson.Int(5),
-				Count:    btcjson.Int(10),
-				VinExtra: btcjson.Int(1),
-				Reverse:  btcjson.Bool(true),
+				Address:     "1Address",
+				Verbose:     btcjson.Int(0),
+				Skip:        btcjson.Int(5),
+				Count:       btcjson.Int(10),
+				VinExtra:    btcjson.Int(1),
+				Reverse:     btcjson.Bool(true),
+				FilterAddrs: nil,
+			},
+		},
+		{
+			name: "searchrawtransactions",
+			newCmd: func() (interface{}, error) {
+				return btcjson.NewCmd("searchrawtransactions", "1Address", 0, 5, 10, 1, true, []string{"1Address"})
+			},
+			staticCmd: func() interface{} {
+				return btcjson.NewSearchRawTransactionsCmd("1Address",
+					btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), btcjson.Int(1), btcjson.Bool(true), &[]string{"1Address"})
+			},
+			marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10,1,true,["1Address"]],"id":1}`,
+			unmarshalled: &btcjson.SearchRawTransactionsCmd{
+				Address:     "1Address",
+				Verbose:     btcjson.Int(0),
+				Skip:        btcjson.Int(5),
+				Count:       btcjson.Int(10),
+				VinExtra:    btcjson.Int(1),
+				Reverse:     btcjson.Bool(true),
+				FilterAddrs: &[]string{"1Address"},
 			},
 		},
 		{
diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go
index 6ba33d27..29d4c096 100644
--- a/btcjson/chainsvrresults.go
+++ b/btcjson/chainsvrresults.go
@@ -400,7 +400,7 @@ type TxRawResult struct {
 // SearchRawTransactionsResult models the data from the searchrawtransaction
 // command.
 type SearchRawTransactionsResult struct {
-	Hex           string       `json:"hex"`
+	Hex           string       `json:"hex,omitempty"`
 	Txid          string       `json:"txid"`
 	Version       int32        `json:"version"`
 	LockTime      uint32       `json:"locktime"`
diff --git a/rpcserver.go b/rpcserver.go
index e4365ad0..60a758e4 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -664,22 +664,41 @@ func createVinList(mtx *wire.MsgTx) []btcjson.Vin {
 	return vinList
 }
 
+// stringInSlice returns true if string a is found in array list.
+func stringInSlice(a string, list []string) bool {
+	for _, b := range list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}
+
 // createVinList returns a slice of JSON objects for the inputs of the passed
 // transaction.
-func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.Params, vinExtra int) []btcjson.VinPrevOut {
+func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.Params, vinExtra int, filterAddrMap map[string]struct{}) []btcjson.VinPrevOut {
+	// We use a dynamically sized list to accomodate address filter.
+	vinList := make([]btcjson.VinPrevOut, 0, len(mtx.TxIn))
+
 	// Coinbase transactions only have a single txin by definition.
-	vinList := make([]btcjson.VinPrevOut, len(mtx.TxIn))
 	if blockchain.IsCoinBaseTx(mtx) {
+		// include tx only if filterAddrMap is empty because coinbase has no address
+		// and so would never match a non-empty filter.
+		if len(filterAddrMap) != 0 {
+			return vinList
+		}
+		var vinEntry btcjson.VinPrevOut
 		txIn := mtx.TxIn[0]
-		vinList[0].Coinbase = hex.EncodeToString(txIn.SignatureScript)
-		vinList[0].Sequence = txIn.Sequence
+		vinEntry.Coinbase = hex.EncodeToString(txIn.SignatureScript)
+		vinEntry.Sequence = txIn.Sequence
+		vinList = append(vinList, vinEntry)
 		return vinList
 	}
 
 	// Lookup all of the referenced transactions needed to populate the
 	// previous output information if requested.
 	var txStore blockchain.TxStore
-	if vinExtra != 0 {
+	if vinExtra != 0 || len(filterAddrMap) > 0 {
 		tx := btcutil.NewTx(mtx)
 		txStoreNew, err := s.server.txMemPool.fetchInputTransactions(tx, true)
 		if err == nil {
@@ -687,26 +706,15 @@ func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.P
 		}
 	}
 
-	for i, txIn := range mtx.TxIn {
+	for _, txIn := range mtx.TxIn {
+		// reset filter flag for each.
+		passesFilter := len(filterAddrMap) == 0
+
 		// The disassembled string will contain [error] inline
 		// if the script doesn't fully parse, so ignore the
 		// error here.
 		disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
 
-		vinEntry := &vinList[i]
-		vinEntry.Txid = txIn.PreviousOutPoint.Hash.String()
-		vinEntry.Vout = txIn.PreviousOutPoint.Index
-		vinEntry.Sequence = txIn.Sequence
-		vinEntry.ScriptSig = &btcjson.ScriptSig{
-			Asm: disbuf,
-			Hex: hex.EncodeToString(txIn.SignatureScript),
-		}
-
-		// Only populate previous output information if requested and
-		// available.
-		if vinExtra == 0 || len(txStore) == 0 {
-			continue
-		}
 		txData := txStore[txIn.PreviousOutPoint.Hash]
 		if txData == nil {
 			continue
@@ -717,20 +725,42 @@ func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.P
 		// Ignore the error here since an error means the script
 		// couldn't parse and there is no additional information about
 		// it anyways.
-		var strAddrs []string
 		_, addrs, _, _ := txscript.ExtractPkScriptAddrs(
 			originTxOut.PkScript, chainParams)
-		if addrs != nil {
-			strAddrs = make([]string, len(addrs))
-			for j, addr := range addrs {
-				strAddrs[j] = addr.EncodeAddress()
+
+		encodedAddrs := make([]string, len(addrs))
+		for j, addr := range addrs {
+			encodedAddrs[j] = addr.EncodeAddress()
+
+			if len(filterAddrMap) > 0 {
+				if _, exists := filterAddrMap[encodedAddrs[j]]; exists {
+					passesFilter = true
+				}
 			}
 		}
 
-		vinEntry.PrevOut = &btcjson.PrevOut{
-			Addresses: strAddrs,
-			Value:     btcutil.Amount(originTxOut.Value).ToBTC(),
+		if !passesFilter {
+			continue
 		}
+
+		var vinEntry btcjson.VinPrevOut
+		vinEntry.Txid = txIn.PreviousOutPoint.Hash.String()
+		vinEntry.Vout = txIn.PreviousOutPoint.Index
+		vinEntry.Sequence = txIn.Sequence
+		vinEntry.ScriptSig = &btcjson.ScriptSig{
+			Asm: disbuf,
+			Hex: hex.EncodeToString(txIn.SignatureScript),
+		}
+
+		// Only populate previous output information if requested
+		if vinExtra != 0 {
+			vinEntry.PrevOut = &btcjson.PrevOut{
+				Addresses: encodedAddrs,
+				Value:     btcutil.Amount(originTxOut.Value).ToBTC(),
+			}
+		}
+
+		vinList = append(vinList, vinEntry)
 	}
 
 	return vinList
@@ -738,34 +768,47 @@ func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.P
 
 // createVoutList returns a slice of JSON objects for the outputs of the passed
 // transaction.
-func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params) []btcjson.Vout {
-	voutList := make([]btcjson.Vout, len(mtx.TxOut))
+func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap map[string]struct{}) []btcjson.Vout {
+	voutList := make([]btcjson.Vout, 0, len(mtx.TxOut))
 	for i, v := range mtx.TxOut {
-		voutList[i].N = uint32(i)
-		voutList[i].Value = btcutil.Amount(v.Value).ToBTC()
+		// reset filter flag for each.
+		passesFilter := len(filterAddrMap) == 0
 
 		// The disassembled string will contain [error] inline if the
 		// script doesn't fully parse, so ignore the error here.
 		disbuf, _ := txscript.DisasmString(v.PkScript)
-		voutList[i].ScriptPubKey.Asm = disbuf
-		voutList[i].ScriptPubKey.Hex = hex.EncodeToString(v.PkScript)
 
 		// Ignore the error here since an error means the script
 		// couldn't parse and there is no additional information about
 		// it anyways.
 		scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(
 			v.PkScript, chainParams)
-		voutList[i].ScriptPubKey.Type = scriptClass.String()
-		voutList[i].ScriptPubKey.ReqSigs = int32(reqSigs)
 
-		if addrs == nil {
-			voutList[i].ScriptPubKey.Addresses = nil
-		} else {
-			voutList[i].ScriptPubKey.Addresses = make([]string, len(addrs))
-			for j, addr := range addrs {
-				voutList[i].ScriptPubKey.Addresses[j] = addr.EncodeAddress()
+		encodedAddrs := make([]string, len(addrs))
+		for j, addr := range addrs {
+			encodedAddrs[j] = addr.EncodeAddress()
+
+			if len(filterAddrMap) > 0 {
+				if _, exists := filterAddrMap[encodedAddrs[j]]; exists {
+					passesFilter = true
+				}
 			}
 		}
+
+		if !passesFilter {
+			continue
+		}
+
+		var vout btcjson.Vout
+		vout.N = uint32(i)
+		vout.Value = btcutil.Amount(v.Value).ToBTC()
+		vout.ScriptPubKey.Addresses = encodedAddrs
+		vout.ScriptPubKey.Asm = disbuf
+		vout.ScriptPubKey.Hex = hex.EncodeToString(v.PkScript)
+		vout.ScriptPubKey.Type = scriptClass.String()
+		vout.ScriptPubKey.ReqSigs = int32(reqSigs)
+
+		voutList = append(voutList, vout)
 	}
 
 	return voutList
@@ -775,18 +818,24 @@ func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params) []btcjson.Vou
 // to a raw transaction JSON object, possibly with vin.PrevOut section.
 func createSearchRawTransactionsResult(s *rpcServer, chainParams *chaincfg.Params, mtx *wire.MsgTx,
 	txHash string, blkHeader *wire.BlockHeader, blkHash string,
-	blkHeight int32, chainHeight int32, vinExtra int) (*btcjson.SearchRawTransactionsResult, error) {
+	blkHeight int32, chainHeight int32, vinExtra int, filterAddrMap map[string]struct{}) (*btcjson.SearchRawTransactionsResult, error) {
 
-	mtxHex, err := messageToHex(mtx)
-	if err != nil {
-		return nil, err
+	// omit hex if filterAddrMap are present.  When filtering, typically the
+	// goal is to reduce unnecessary bloat in the result.
+	var mtxHex string
+	if len(filterAddrMap) == 0 {
+		mtxHexTmp, err := messageToHex(mtx)
+		if err != nil {
+			return nil, err
+		}
+		mtxHex = mtxHexTmp
 	}
 
 	txReply := &btcjson.SearchRawTransactionsResult{
 		Hex:      mtxHex,
 		Txid:     txHash,
-		Vout:     createVoutList(mtx, chainParams),
-		Vin:      createVinListPrevOut(s, mtx, chainParams, vinExtra),
+		Vout:     createVoutList(mtx, chainParams, filterAddrMap),
+		Vin:      createVinListPrevOut(s, mtx, chainParams, vinExtra, filterAddrMap),
 		Version:  mtx.Version,
 		LockTime: mtx.LockTime,
 	}
@@ -816,7 +865,7 @@ func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx,
 	txReply := &btcjson.TxRawResult{
 		Hex:      mtxHex,
 		Txid:     txHash,
-		Vout:     createVoutList(mtx, chainParams),
+		Vout:     createVoutList(mtx, chainParams, nil),
 		Vin:      createVinList(mtx),
 		Version:  mtx.Version,
 		LockTime: mtx.LockTime,
@@ -861,7 +910,7 @@ func handleDecodeRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan
 		Version:  mtx.Version,
 		Locktime: mtx.LockTime,
 		Vin:      createVinList(&mtx),
-		Vout:     createVoutList(&mtx, s.server.chainParams),
+		Vout:     createVoutList(&mtx, s.server.chainParams, nil),
 	}
 	return txReply, nil
 }
@@ -3175,8 +3224,17 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan
 			blkHeight = txReply.Height
 		}
 
+		// c.FilterAddrs can be nil, empty or non-empty.  Here we normalize that
+		// to a non-nil array (empty or non-empty) to avoid future nil checks.
+		filterAddrMap := make(map[string]struct{})
+		if c.FilterAddrs != nil && len(*c.FilterAddrs) > 0 {
+			for _, addr := range *c.FilterAddrs {
+				filterAddrMap[addr] = struct{}{}
+			}
+		}
+
 		rawTxn, err := createSearchRawTransactionsResult(s, s.server.chainParams, mtx,
-			txHash, blkHeader, blkHashStr, blkHeight, maxIdx, *c.VinExtra)
+			txHash, blkHeader, blkHashStr, blkHeight, maxIdx, *c.VinExtra, filterAddrMap)
 		if err != nil {
 			return nil, err
 		}
diff --git a/rpcserverhelp.go b/rpcserverhelp.go
index d9b0b305..1973b527 100644
--- a/rpcserverhelp.go
+++ b/rpcserverhelp.go
@@ -472,6 +472,7 @@ var helpDescsEnUS = map[string]string{
 	"searchrawtransactions-count":       "The maximum number of transactions to return",
 	"searchrawtransactions-vinextra":    "Specify that extra data from previous output will be returned in vin",
 	"searchrawtransactions-reverse":     "Specifies that the transactions should be returned in reverse chronological order",
+	"searchrawtransactions-filteraddrs": "Address list.  Only inputs or outputs with matching address will be returned",
 	"searchrawtransactions--result0":    "Hex-encoded serialized transaction",
 
 	// SendRawTransactionCmd help.