Add reverse order option to searchrawtransactions rpc

This commit is contained in:
Dario Nieuwenhuis 2015-08-27 19:34:24 +02:00
parent ce22159fb2
commit 0190c349aa
9 changed files with 101 additions and 35 deletions

View file

@ -546,10 +546,11 @@ 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"`
Verbose *int `jsonrpcdefault:"1"`
Skip *int `jsonrpcdefault:"0"`
Count *int `jsonrpcdefault:"100"`
VinExtra *int `jsonrpcdefault:"0"`
Reverse *bool `jsonrpcdefault:"false"`
}
// NewSearchRawTransactionsCmd returns a new instance which can be used to issue a
@ -557,13 +558,14 @@ 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) *SearchRawTransactionsCmd {
func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinExtra *int, reverse *bool) *SearchRawTransactionsCmd {
return &SearchRawTransactionsCmd{
Address: address,
Verbose: verbose,
Skip: skip,
Count: count,
VinExtra: vinExtra,
Reverse: reverse,
}
}

View file

@ -679,7 +679,7 @@ func TestChainSvrCmds(t *testing.T) {
return btcjson.NewCmd("searchrawtransactions", "1Address")
},
staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address", nil, nil, nil, nil)
return btcjson.NewSearchRawTransactionsCmd("1Address", nil, nil, nil, nil, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address"],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -688,6 +688,7 @@ func TestChainSvrCmds(t *testing.T) {
Skip: btcjson.Int(0),
Count: btcjson.Int(100),
VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false),
},
},
{
@ -697,7 +698,7 @@ func TestChainSvrCmds(t *testing.T) {
},
staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address",
btcjson.Int(0), nil, nil, nil)
btcjson.Int(0), nil, nil, nil, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -706,6 +707,7 @@ func TestChainSvrCmds(t *testing.T) {
Skip: btcjson.Int(0),
Count: btcjson.Int(100),
VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false),
},
},
{
@ -715,7 +717,7 @@ func TestChainSvrCmds(t *testing.T) {
},
staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address",
btcjson.Int(0), btcjson.Int(5), nil, nil)
btcjson.Int(0), btcjson.Int(5), nil, nil, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -724,6 +726,7 @@ func TestChainSvrCmds(t *testing.T) {
Skip: btcjson.Int(5),
Count: btcjson.Int(100),
VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false),
},
},
{
@ -733,7 +736,7 @@ func TestChainSvrCmds(t *testing.T) {
},
staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address",
btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), nil)
btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), nil, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -742,6 +745,7 @@ func TestChainSvrCmds(t *testing.T) {
Skip: btcjson.Int(5),
Count: btcjson.Int(10),
VinExtra: btcjson.Int(0),
Reverse: btcjson.Bool(false),
},
},
{
@ -751,7 +755,7 @@ 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.Int(0), btcjson.Int(5), btcjson.Int(10), btcjson.Int(1), nil)
},
marshalled: `{"jsonrpc":"1.0","method":"searchrawtransactions","params":["1Address",0,5,10,1],"id":1}`,
unmarshalled: &btcjson.SearchRawTransactionsCmd{
@ -760,6 +764,26 @@ func TestChainSvrCmds(t *testing.T) {
Skip: btcjson.Int(5),
Count: btcjson.Int(10),
VinExtra: btcjson.Int(1),
Reverse: btcjson.Bool(false),
},
},
{
name: "searchrawtransactions",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("searchrawtransactions", "1Address", 0, 5, 10, 1, true)
},
staticCmd: func() interface{} {
return btcjson.NewSearchRawTransactionsCmd("1Address",
btcjson.Int(0), btcjson.Int(5), btcjson.Int(10), btcjson.Int(1), btcjson.Bool(true))
},
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),
},
},
{

View file

@ -133,10 +133,12 @@ type Db interface {
// should be the max number of transactions to be returned.
// Additionally, if the caller wishes to skip forward in the results
// some amount, the 'seek' represents how many results to skip.
// The transactions are returned in chronological order by block height
// from old to new, or from new to old if `reverse` is set.
// NOTE: Values for both `seek` and `limit` MUST be positive.
// It will return the array of fetched transactions, along with the amount
// of transactions that were actually skipped.
FetchTxsForAddr(addr btcutil.Address, skip int, limit int) ([]*TxListReply, int, error)
FetchTxsForAddr(addr btcutil.Address, skip int, limit int, reverse bool) ([]*TxListReply, int, error)
// DeleteAddrIndex deletes the entire addrindex stored within the DB.
DeleteAddrIndex() error

View file

@ -91,12 +91,12 @@ func testAddrIndexOperations(t *testing.T, db database.Db, newestBlock *btcutil.
// Test enforcement of constraints for "limit" and "skip"
var fakeAddr btcutil.Address
_, _, err = db.FetchTxsForAddr(fakeAddr, -1, 0)
_, _, err = db.FetchTxsForAddr(fakeAddr, -1, 0, false)
if err == nil {
t.Fatalf("Negative value for skip passed, should return an error")
}
_, _, err = db.FetchTxsForAddr(fakeAddr, 0, -1)
_, _, err = db.FetchTxsForAddr(fakeAddr, 0, -1, false)
if err == nil {
t.Fatalf("Negative value for limit passed, should return an error")
}
@ -136,7 +136,7 @@ func testAddrIndexOperations(t *testing.T, db database.Db, newestBlock *btcutil.
assertAddrIndexTipIsUpdated(db, t, newestSha, newestBlockIdx)
// Check index retrieval.
txReplies, _, err := db.FetchTxsForAddr(testAddrs[0], 0, 1000)
txReplies, _, err := db.FetchTxsForAddr(testAddrs[0], 0, 1000, false)
if err != nil {
t.Fatalf("FetchTxsForAddr failed to correctly fetch txs for an "+
"address, err %v", err)
@ -171,7 +171,7 @@ func testAddrIndexOperations(t *testing.T, db database.Db, newestBlock *btcutil.
}
// Former index should no longer exist.
txReplies, _, err = db.FetchTxsForAddr(testAddrs[0], 0, 1000)
txReplies, _, err = db.FetchTxsForAddr(testAddrs[0], 0, 1000, false)
if err != nil {
t.Fatalf("Unable to fetch transactions for address: %v", err)
}
@ -555,7 +555,7 @@ func TestLimitAndSkipFetchTxsForAddr(t *testing.T) {
}
// Try skipping the first 4 results, should get 6 in return.
txReply, txSkipped, err := testDb.db.FetchTxsForAddr(targetAddr, 4, 100000)
txReply, txSkipped, err := testDb.db.FetchTxsForAddr(targetAddr, 4, 100000, false)
if err != nil {
t.Fatalf("Unable to fetch transactions for address: %v", err)
}
@ -569,7 +569,7 @@ func TestLimitAndSkipFetchTxsForAddr(t *testing.T) {
}
// Limit the number of results to 3.
txReply, txSkipped, err = testDb.db.FetchTxsForAddr(targetAddr, 0, 3)
txReply, txSkipped, err = testDb.db.FetchTxsForAddr(targetAddr, 0, 3, false)
if err != nil {
t.Fatalf("Unable to fetch transactions for address: %v", err)
}
@ -583,7 +583,7 @@ func TestLimitAndSkipFetchTxsForAddr(t *testing.T) {
}
// Skip 1, limit 5.
txReply, txSkipped, err = testDb.db.FetchTxsForAddr(targetAddr, 1, 5)
txReply, txSkipped, err = testDb.db.FetchTxsForAddr(targetAddr, 1, 5, false)
if err != nil {
t.Fatalf("Unable to fetch transactions for address: %v", err)
}

View file

@ -14,6 +14,7 @@ import (
"github.com/btcsuite/btcutil"
"github.com/btcsuite/golangcrypto/ripemd160"
"github.com/btcsuite/goleveldb/leveldb"
"github.com/btcsuite/goleveldb/leveldb/iterator"
"github.com/btcsuite/goleveldb/leveldb/util"
)
@ -423,6 +424,13 @@ func bytesPrefix(prefix []byte) *util.Range {
return &util.Range{Start: prefix, Limit: limit}
}
func advanceIterator(iter iterator.IteratorSeeker, reverse bool) bool {
if reverse {
return iter.Prev()
}
return iter.Next()
}
// FetchTxsForAddr looks up and returns all transactions which either
// spend from a previously created output of the passed address, or
// create a new output locked to the passed address. The, `limit` parameter
@ -430,7 +438,7 @@ func bytesPrefix(prefix []byte) *util.Range {
// caller wishes to seek forward in the results some amount, the 'seek'
// represents how many results to skip.
func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int,
limit int) ([]*database.TxListReply, int, error) {
limit int, reverse bool) ([]*database.TxListReply, int, error) {
db.dbLock.Lock()
defer db.dbLock.Unlock()
@ -465,7 +473,18 @@ func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int,
iter := db.lDb.NewIterator(bytesPrefix(addrPrefix), nil)
skipped := 0
for skip != 0 && iter.Next() {
if reverse {
// Go to the last element if reverse iterating.
iter.Last()
// Skip "one past" the last element so the loops below don't
// miss the last element due to Prev() being called first.
// We can safely ignore iterator exhaustion since the loops
// below will see there's no keys anyway.
iter.Next()
}
for skip != 0 && advanceIterator(iter, reverse) {
skip--
skipped++
}
@ -473,7 +492,7 @@ func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int,
// Iterate through all address indexes that match the targeted prefix.
var replies []*database.TxListReply
var rawIndex [12]byte
for iter.Next() && limit != 0 {
for advanceIterator(iter, reverse) && limit != 0 {
copy(rawIndex[:], iter.Key()[23:35])
addrIndex := unpackTxIndex(rawIndex)

View file

@ -690,7 +690,7 @@ func (db *MemDb) UpdateAddrIndexForBlock(*wire.ShaHash, int32,
// FetchTxsForAddr isn't currently implemented. This is a part of the database.Db
// interface implementation.
func (db *MemDb) FetchTxsForAddr(btcutil.Address, int, int) ([]*database.TxListReply, int, error) {
func (db *MemDb) FetchTxsForAddr(btcutil.Address, int, int, bool) ([]*database.TxListReply, int, error) {
return nil, 0, database.ErrNotImplemented
}

View file

@ -632,7 +632,7 @@ The following is an overview of the RPC methods which are implemented by btcd, b
| | |
|---|---|
|Method|searchrawtransactions|
|Parameters|1. address (string, required) - bitcoin address <br /> 2. verbose (int, optional, default=true) - specifies the transaction is returned as a JSON object instead of hex-encoded string <br />3. skip (int, optional, default=0) - the number of leading transactions to leave out of the final response <br /> 4. count (int, optional, default=100) - the maximum number of transactions to return <br /> 5. (vinextra int, optional, default=0) - Specify that extra data from previous output will be returned in vin|
|Parameters|1. address (string, required) - bitcoin address <br /> 2. verbose (int, optional, default=true) - specifies the transaction is returned as a JSON object instead of hex-encoded string <br />3. skip (int, optional, default=0) - the number of leading transactions to leave out of the final response <br /> 4. count (int, optional, default=100) - the maximum number of transactions to return <br /> 5. vinextra (int, optional, default=0) - Specify that extra data from previous output will be returned in vin <br /> 6. reverse (boolean, optional, default=false) - Specifies that the transactions should be returned in reverse chronological order|
|Description|Returns raw data for transactions involving the passed address. Returned transactions are pulled from both the database, and transactions currently in the mempool. Transactions pulled from the mempool will have the `"confirmations"` field set to 0. Usage of this RPC requires the optional `--addrindex` flag to be activated, otherwise all responses will simply return with an error stating the address index has not yet been built up. Similarly, until the address index has caught up with the current best height, all requests will return an error response in order to avoid serving stale data.|
|Returns (verbose=0)|`[ (json array of strings)` <br/>&nbsp;&nbsp; `"serializedtx", ... hex-encoded bytes of the serialized transaction` <br/>`]` |
|Returns (verbose=1)|`[ (array of json objects)` <br/> &nbsp;&nbsp; `{ (json object)`<br />&nbsp;&nbsp;`"hex": "data", (string) hex-encoded transaction`<br />&nbsp;&nbsp;`"txid": "hash", (string) the hash of the transaction`<br />&nbsp;&nbsp;`"version": n, (numeric) the transaction version`<br />&nbsp;&nbsp;`"locktime": n, (numeric) the transaction lock time`<br />&nbsp;&nbsp;`"vin": [ (array of json objects) the transaction inputs as json objects`<br />&nbsp;&nbsp;<font color="orange">For coinbase transactions:</font><br />&nbsp;&nbsp;&nbsp;&nbsp;`{ (json object)`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"coinbase": "data", (string) the hex-encoded bytes of the signature script`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"sequence": n, (numeric) the script sequence number`<br />&nbsp;&nbsp;&nbsp;&nbsp;`}`<br />&nbsp;&nbsp;<font color="orange">For non-coinbase transactions:</font><br />&nbsp;&nbsp;&nbsp;&nbsp;`{ (json object)`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"txid": "hash", (string) the hash of the origin transaction`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"vout": n, (numeric) the index of the output being redeemed from the origin transaction`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"scriptSig": { (json object) the signature script used to redeem the origin transaction`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"asm": "asm", (string) disassembly of the script`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"hex": "data", (string) hex-encoded bytes of the script`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`}`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"prevOut": { (json object) Data from the origin transaction output with index vout.`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"addresses": ["value",...], (array of string) previous output addresses`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"value": n.nnn, (numeric) previous output value`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`}`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"sequence": n, (numeric) the script sequence number`<br />&nbsp;&nbsp;&nbsp;&nbsp;`}, ...`<br />&nbsp;&nbsp;`]`<br />&nbsp;&nbsp;`"vout": [ (array of json objects) the transaction outputs as json objects`<br />&nbsp;&nbsp;&nbsp;&nbsp;`{ (json object)`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"value": n, (numeric) the value in BTC`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"n": n, (numeric) the index of this transaction output`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"scriptPubKey": { (json object) the public key script used to pay coins`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"asm": "asm", (string) disassembly of the script`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"hex": "data", (string) hex-encoded bytes of the script`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"reqSigs": n, (numeric) the number of required signatures`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"type": "scripttype" (string) the type of the script (e.g. 'pubkeyhash')`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"addresses": [ (json array of string) the bitcoin addresses associated with this output`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`"address", (string) the bitcoin address`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`...`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`]`<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`}`<br />&nbsp;&nbsp;&nbsp;&nbsp;`}, ...`<br /> &nbsp;&nbsp;&nbsp;`]`<br />&nbsp;&nbsp; `"blockhash":"hash" Hash of the block the transaction is part of.` <br /> &nbsp;&nbsp; `"confirmations":n, Number of numeric confirmations of block.` <br /> &nbsp;&nbsp;&nbsp;`"time":t, Transaction time in seconds since the epoch.` <br /> &nbsp;&nbsp;&nbsp;`"blocktime":t, Block time in seconds since the epoch.`<br />`},...`<br/> `]`|

View file

@ -2948,7 +2948,7 @@ func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter
// getMempoolTxsForAddressRange looks up and returns all transactions from the
// mempool related to the given address. The, `limit` parameter
// should be the max number of transactions to be returned. Additionally, if the
// caller wishes to seek forward in the results some amount, the 'seek'
// caller wishes to seek forward in the results some amount, the 'skip' parameter
// represents how many results to skip.
// It will return the array of fetched transactions, along with the amount
// of transactions that were actually skipped.
@ -3025,23 +3025,41 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan
}
}
// While it's more efficient to check the mempool for relevant transactions
// first, we want to return results in order of occurrence/dependency so
// we'll check the mempool only if there aren't enough results returned
// by the database.
dbTxs, dbSkipped, err := s.server.db.FetchTxsForAddr(addr, numToSkip,
numRequested-len(addressTxs))
if err == nil {
skipped += dbSkipped
for _, txReply := range dbTxs {
addressTxs = append(addressTxs, txReply)
}
var reverse bool
if c.Reverse != nil {
reverse = *c.Reverse
}
// Add txs from mempool first if client asked for reverse order, otherwise
// add them last.
// This code (and txMemPool.FilterTransactionsByAddress()) doesn't sort by
// dependency. This might be something we want to do in the future when we
// return results for the client's convenience, or leave it to the client.
if reverse && len(addressTxs) < numRequested {
memPoolTxs, memPoolSkipped, err := getMempoolTxsForAddressRange(s, addr,
numToSkip-skipped, numRequested-len(addressTxs))
if err == nil {
skipped += memPoolSkipped
for _, txReply := range memPoolTxs {
addressTxs = append(addressTxs, txReply)
}
}
}
// Fetch transactions from the database in the desired order if we need more.
if len(addressTxs) < numRequested {
dbTxs, dbSkipped, err := s.server.db.FetchTxsForAddr(addr,
numToSkip-skipped, numRequested-len(addressTxs), reverse)
if err == nil {
skipped += dbSkipped
for _, txReply := range dbTxs {
addressTxs = append(addressTxs, txReply)
}
}
}
// Add txs from mempool last if the client didn't ask for reverse order.
if !reverse && len(addressTxs) < numRequested {
memPoolTxs, memPoolSkipped, err := getMempoolTxsForAddressRange(s, addr,
numToSkip-skipped, numRequested-len(addressTxs))
if err == nil {

View file

@ -470,6 +470,7 @@ var helpDescsEnUS = map[string]string{
"searchrawtransactions-skip": "The number of leading transactions to leave out of the final response",
"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--result0": "Hex-encoded serialized transaction",
// SendRawTransactionCmd help.