adds filteraddrs param to searchrawtransactions API

This commit is contained in:
danda 2015-11-15 15:30:13 -08:00
parent b691a222d5
commit c7eaee6020
5 changed files with 194 additions and 107 deletions

View file

@ -555,6 +555,7 @@ type SearchRawTransactionsCmd struct {
Count *int `jsonrpcdefault:"100"` Count *int `jsonrpcdefault:"100"`
VinExtra *int `jsonrpcdefault:"0"` VinExtra *int `jsonrpcdefault:"0"`
Reverse *bool `jsonrpcdefault:"false"` Reverse *bool `jsonrpcdefault:"false"`
FilterAddrs *[]string
} }
// NewSearchRawTransactionsCmd returns a new instance which can be used to issue a // NewSearchRawTransactionsCmd returns a new instance which can be used to issue a
@ -562,7 +563,7 @@ type SearchRawTransactionsCmd struct {
// //
// The parameters which are pointers indicate they are optional. Passing nil // The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value. // 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{ return &SearchRawTransactionsCmd{
Address: address, Address: address,
Verbose: verbose, Verbose: verbose,
@ -570,6 +571,7 @@ func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinE
Count: count, Count: count,
VinExtra: vinExtra, VinExtra: vinExtra,
Reverse: reverse, Reverse: reverse,
FilterAddrs: filterAddrs,
} }
} }

View file

@ -700,7 +700,7 @@ func TestChainSvrCmds(t *testing.T) {
return btcjson.NewCmd("searchrawtransactions", "1Address") return btcjson.NewCmd("searchrawtransactions", "1Address")
}, },
staticCmd: func() interface{} { 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}`, marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address"],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{ unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -710,6 +710,7 @@ func TestChainSvrCmds(t *testing.T) {
Count: btcjson.Int(100), Count: btcjson.Int(100),
VinExtra: btcjson.Int(0), VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false), Reverse: btcjson.Bool(false),
FilterAddrs: nil,
}, },
}, },
{ {
@ -719,7 +720,7 @@ func TestChainSvrCmds(t *testing.T) {
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address", 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}`, marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{ unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -729,6 +730,7 @@ func TestChainSvrCmds(t *testing.T) {
Count: btcjson.Int(100), Count: btcjson.Int(100),
VinExtra: btcjson.Int(0), VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false), Reverse: btcjson.Bool(false),
FilterAddrs: nil,
}, },
}, },
{ {
@ -738,7 +740,7 @@ func TestChainSvrCmds(t *testing.T) {
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address", 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}`, marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{ unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -748,6 +750,7 @@ func TestChainSvrCmds(t *testing.T) {
Count: btcjson.Int(100), Count: btcjson.Int(100),
VinExtra: btcjson.Int(0), VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false), Reverse: btcjson.Bool(false),
FilterAddrs: nil,
}, },
}, },
{ {
@ -757,7 +760,7 @@ func TestChainSvrCmds(t *testing.T) {
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address", 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}`, marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{ unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -767,6 +770,7 @@ func TestChainSvrCmds(t *testing.T) {
Count: btcjson.Int(10), Count: btcjson.Int(10),
VinExtra: btcjson.Int(0), VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false), Reverse: btcjson.Bool(false),
FilterAddrs: nil,
}, },
}, },
{ {
@ -776,7 +780,7 @@ func TestChainSvrCmds(t *testing.T) {
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address", 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}`, marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10,1],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{ unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -786,6 +790,7 @@ func TestChainSvrCmds(t *testing.T) {
Count: btcjson.Int(10), Count: btcjson.Int(10),
VinExtra: btcjson.Int(1), VinExtra: btcjson.Int(1),
Reverse: btcjson.Bool(false), Reverse: btcjson.Bool(false),
FilterAddrs: nil,
}, },
}, },
{ {
@ -795,7 +800,7 @@ func TestChainSvrCmds(t *testing.T) {
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address", 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}`, marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10,1,true],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{ unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -805,6 +810,27 @@ func TestChainSvrCmds(t *testing.T) {
Count: btcjson.Int(10), Count: btcjson.Int(10),
VinExtra: btcjson.Int(1), VinExtra: btcjson.Int(1),
Reverse: btcjson.Bool(true), 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"},
}, },
}, },
{ {

View file

@ -400,7 +400,7 @@ type TxRawResult struct {
// SearchRawTransactionsResult models the data from the searchrawtransaction // SearchRawTransactionsResult models the data from the searchrawtransaction
// command. // command.
type SearchRawTransactionsResult struct { type SearchRawTransactionsResult struct {
Hex string `json:"hex"` Hex string `json:"hex,omitempty"`
Txid string `json:"txid"` Txid string `json:"txid"`
Version int32 `json:"version"` Version int32 `json:"version"`
LockTime uint32 `json:"locktime"` LockTime uint32 `json:"locktime"`

View file

@ -664,22 +664,41 @@ func createVinList(mtx *wire.MsgTx) []btcjson.Vin {
return vinList 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 // createVinList returns a slice of JSON objects for the inputs of the passed
// transaction. // 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. // Coinbase transactions only have a single txin by definition.
vinList := make([]btcjson.VinPrevOut, len(mtx.TxIn))
if blockchain.IsCoinBaseTx(mtx) { 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] txIn := mtx.TxIn[0]
vinList[0].Coinbase = hex.EncodeToString(txIn.SignatureScript) vinEntry.Coinbase = hex.EncodeToString(txIn.SignatureScript)
vinList[0].Sequence = txIn.Sequence vinEntry.Sequence = txIn.Sequence
vinList = append(vinList, vinEntry)
return vinList return vinList
} }
// Lookup all of the referenced transactions needed to populate the // Lookup all of the referenced transactions needed to populate the
// previous output information if requested. // previous output information if requested.
var txStore blockchain.TxStore var txStore blockchain.TxStore
if vinExtra != 0 { if vinExtra != 0 || len(filterAddrMap) > 0 {
tx := btcutil.NewTx(mtx) tx := btcutil.NewTx(mtx)
txStoreNew, err := s.server.txMemPool.fetchInputTransactions(tx, true) txStoreNew, err := s.server.txMemPool.fetchInputTransactions(tx, true)
if err == nil { 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 // The disassembled string will contain [error] inline
// if the script doesn't fully parse, so ignore the // if the script doesn't fully parse, so ignore the
// error here. // error here.
disbuf, _ := txscript.DisasmString(txIn.SignatureScript) 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] txData := txStore[txIn.PreviousOutPoint.Hash]
if txData == nil { if txData == nil {
continue continue
@ -717,57 +725,92 @@ func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.P
// Ignore the error here since an error means the script // Ignore the error here since an error means the script
// couldn't parse and there is no additional information about // couldn't parse and there is no additional information about
// it anyways. // it anyways.
var strAddrs []string
_, addrs, _, _ := txscript.ExtractPkScriptAddrs( _, addrs, _, _ := txscript.ExtractPkScriptAddrs(
originTxOut.PkScript, chainParams) originTxOut.PkScript, chainParams)
if addrs != nil {
strAddrs = make([]string, len(addrs)) encodedAddrs := make([]string, len(addrs))
for j, addr := range addrs { for j, addr := range addrs {
strAddrs[j] = addr.EncodeAddress() encodedAddrs[j] = addr.EncodeAddress()
if len(filterAddrMap) > 0 {
if _, exists := filterAddrMap[encodedAddrs[j]]; exists {
passesFilter = true
}
} }
} }
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{ vinEntry.PrevOut = &btcjson.PrevOut{
Addresses: strAddrs, Addresses: encodedAddrs,
Value: btcutil.Amount(originTxOut.Value).ToBTC(), Value: btcutil.Amount(originTxOut.Value).ToBTC(),
} }
} }
vinList = append(vinList, vinEntry)
}
return vinList return vinList
} }
// createVoutList returns a slice of JSON objects for the outputs of the passed // createVoutList returns a slice of JSON objects for the outputs of the passed
// transaction. // transaction.
func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params) []btcjson.Vout { func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap map[string]struct{}) []btcjson.Vout {
voutList := make([]btcjson.Vout, len(mtx.TxOut)) voutList := make([]btcjson.Vout, 0, len(mtx.TxOut))
for i, v := range mtx.TxOut { for i, v := range mtx.TxOut {
voutList[i].N = uint32(i) // reset filter flag for each.
voutList[i].Value = btcutil.Amount(v.Value).ToBTC() passesFilter := len(filterAddrMap) == 0
// The disassembled string will contain [error] inline if the // The disassembled string will contain [error] inline if the
// script doesn't fully parse, so ignore the error here. // script doesn't fully parse, so ignore the error here.
disbuf, _ := txscript.DisasmString(v.PkScript) 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 // Ignore the error here since an error means the script
// couldn't parse and there is no additional information about // couldn't parse and there is no additional information about
// it anyways. // it anyways.
scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs( scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(
v.PkScript, chainParams) v.PkScript, chainParams)
voutList[i].ScriptPubKey.Type = scriptClass.String()
voutList[i].ScriptPubKey.ReqSigs = int32(reqSigs)
if addrs == nil { encodedAddrs := make([]string, len(addrs))
voutList[i].ScriptPubKey.Addresses = nil
} else {
voutList[i].ScriptPubKey.Addresses = make([]string, len(addrs))
for j, addr := range addrs { for j, addr := range addrs {
voutList[i].ScriptPubKey.Addresses[j] = addr.EncodeAddress() 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 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. // to a raw transaction JSON object, possibly with vin.PrevOut section.
func createSearchRawTransactionsResult(s *rpcServer, chainParams *chaincfg.Params, mtx *wire.MsgTx, func createSearchRawTransactionsResult(s *rpcServer, chainParams *chaincfg.Params, mtx *wire.MsgTx,
txHash string, blkHeader *wire.BlockHeader, blkHash string, 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) // 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 { if err != nil {
return nil, err return nil, err
} }
mtxHex = mtxHexTmp
}
txReply := &btcjson.SearchRawTransactionsResult{ txReply := &btcjson.SearchRawTransactionsResult{
Hex: mtxHex, Hex: mtxHex,
Txid: txHash, Txid: txHash,
Vout: createVoutList(mtx, chainParams), Vout: createVoutList(mtx, chainParams, filterAddrMap),
Vin: createVinListPrevOut(s, mtx, chainParams, vinExtra), Vin: createVinListPrevOut(s, mtx, chainParams, vinExtra, filterAddrMap),
Version: mtx.Version, Version: mtx.Version,
LockTime: mtx.LockTime, LockTime: mtx.LockTime,
} }
@ -816,7 +865,7 @@ func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx,
txReply := &btcjson.TxRawResult{ txReply := &btcjson.TxRawResult{
Hex: mtxHex, Hex: mtxHex,
Txid: txHash, Txid: txHash,
Vout: createVoutList(mtx, chainParams), Vout: createVoutList(mtx, chainParams, nil),
Vin: createVinList(mtx), Vin: createVinList(mtx),
Version: mtx.Version, Version: mtx.Version,
LockTime: mtx.LockTime, LockTime: mtx.LockTime,
@ -861,7 +910,7 @@ func handleDecodeRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan
Version: mtx.Version, Version: mtx.Version,
Locktime: mtx.LockTime, Locktime: mtx.LockTime,
Vin: createVinList(&mtx), Vin: createVinList(&mtx),
Vout: createVoutList(&mtx, s.server.chainParams), Vout: createVoutList(&mtx, s.server.chainParams, nil),
} }
return txReply, nil return txReply, nil
} }
@ -3175,8 +3224,17 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan
blkHeight = txReply.Height 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, 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -472,6 +472,7 @@ var helpDescsEnUS = map[string]string{
"searchrawtransactions-count": "The maximum number of transactions to return", "searchrawtransactions-count": "The maximum number of transactions to return",
"searchrawtransactions-vinextra": "Specify that extra data from previous output will be returned in vin", "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-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", "searchrawtransactions--result0": "Hex-encoded serialized transaction",
// SendRawTransactionCmd help. // SendRawTransactionCmd help.