diff --git a/database/db.go b/database/db.go index 8158d2af..33bfa82e 100644 --- a/database/db.go +++ b/database/db.go @@ -134,7 +134,9 @@ type Db interface { // Additionally, if the caller wishes to skip forward in the results // some amount, the 'seek' represents how many results to skip. // NOTE: Values for both `seek` and `limit` MUST be positive. - FetchTxsForAddr(addr btcutil.Address, skip int, limit int) ([]*TxListReply, error) + // 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) // DeleteAddrIndex deletes the entire addrindex stored within the DB. DeleteAddrIndex() error diff --git a/database/ldb/operational_test.go b/database/ldb/operational_test.go index 8072a884..b1a9fb1c 100644 --- a/database/ldb/operational_test.go +++ b/database/ldb/operational_test.go @@ -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) 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) 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) 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) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } @@ -555,30 +555,42 @@ func TestLimitAndSkipFetchTxsForAddr(t *testing.T) { } // Try skipping the first 4 results, should get 6 in return. - txReply, err := testDb.db.FetchTxsForAddr(targetAddr, 4, 100000) + txReply, txSkipped, err := testDb.db.FetchTxsForAddr(targetAddr, 4, 100000) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } + if txSkipped != 4 { + t.Fatalf("Did not correctly return skipped amount"+ + " got %v txs, expected %v", txSkipped, 4) + } if len(txReply) != 6 { t.Fatalf("Did not correctly skip forward in txs for address reply"+ " got %v txs, expected %v", len(txReply), 6) } // Limit the number of results to 3. - txReply, err = testDb.db.FetchTxsForAddr(targetAddr, 0, 3) + txReply, txSkipped, err = testDb.db.FetchTxsForAddr(targetAddr, 0, 3) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } + if txSkipped != 0 { + t.Fatalf("Did not correctly return skipped amount"+ + " got %v txs, expected %v", txSkipped, 0) + } if len(txReply) != 3 { t.Fatalf("Did not correctly limit in txs for address reply"+ " got %v txs, expected %v", len(txReply), 3) } // Skip 1, limit 5. - txReply, err = testDb.db.FetchTxsForAddr(targetAddr, 1, 5) + txReply, txSkipped, err = testDb.db.FetchTxsForAddr(targetAddr, 1, 5) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } + if txSkipped != 1 { + t.Fatalf("Did not correctly return skipped amount"+ + " got %v txs, expected %v", txSkipped, 1) + } if len(txReply) != 5 { t.Fatalf("Did not correctly limit in txs for address reply"+ " got %v txs, expected %v", len(txReply), 5) diff --git a/database/ldb/tx.go b/database/ldb/tx.go index cf9e6243..7e935890 100644 --- a/database/ldb/tx.go +++ b/database/ldb/tx.go @@ -430,16 +430,16 @@ 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, error) { + limit int) ([]*database.TxListReply, int, error) { db.dbLock.Lock() defer db.dbLock.Unlock() // Enforce constraints for skip and limit. if skip < 0 { - return nil, errors.New("offset for skip must be positive") + return nil, 0, errors.New("offset for skip must be positive") } if limit < 0 { - return nil, errors.New("value for limit must be positive") + return nil, 0, errors.New("value for limit must be positive") } // Parse address type, bailing on an unknown type. @@ -455,7 +455,7 @@ func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int, hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] default: - return nil, database.ErrUnsupportedAddressType + return nil, 0, database.ErrUnsupportedAddressType } // Create the prefix for our search. @@ -464,8 +464,10 @@ func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int, copy(addrPrefix[3:23], addrKey) iter := db.lDb.NewIterator(bytesPrefix(addrPrefix), nil) + skipped := 0 for skip != 0 && iter.Next() { skip-- + skipped++ } // Iterate through all address indexes that match the targeted prefix. @@ -491,10 +493,10 @@ func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int, } iter.Release() if err := iter.Error(); err != nil { - return nil, err + return nil, 0, err } - return replies, nil + return replies, skipped, nil } // UpdateAddrIndexForBlock updates the stored addrindex with passed diff --git a/database/memdb/memdb.go b/database/memdb/memdb.go index 25b86c9c..6754ecd9 100644 --- a/database/memdb/memdb.go +++ b/database/memdb/memdb.go @@ -690,8 +690,8 @@ 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, error) { - return nil, database.ErrNotImplemented +func (db *MemDb) FetchTxsForAddr(btcutil.Address, int, int) ([]*database.TxListReply, int, error) { + return nil, 0, database.ErrNotImplemented } // DeleteAddrIndex isn't currently implemented. This is a part of the database.Db diff --git a/rpcserver.go b/rpcserver.go index 24e2b69d..45562cf1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2939,6 +2939,43 @@ func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter return nil, nil } +// 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' +// represents how many results to skip. +// It will return the array of fetched transactions, along with the amount +// of transactions that were actually skipped. +func getMempoolTxsForAddressRange(s *rpcServer, addr btcutil.Address, skip int, + limit int) ([]*database.TxListReply, int, error) { + + memPoolTxs, err := s.server.txMemPool.FilterTransactionsByAddress(addr) + if err != nil { + return nil, 0, err + } + + // If we're asked to skip more transactions than we have, + // we skip them all and return an empty slice. + if skip >= len(memPoolTxs) { + return nil, len(memPoolTxs), nil + } + + var result []*database.TxListReply + + // Otherwise, calculate the range we have to return and return it. + rangeEnd := skip + limit + if rangeEnd > len(memPoolTxs) { + rangeEnd = len(memPoolTxs) + } + + for _, tx := range memPoolTxs[skip:rangeEnd] { + txReply := &database.TxListReply{Tx: tx.MsgTx(), Sha: tx.Sha()} + result = append(result, txReply) + } + + return result, skip, nil +} + // handleSearchRawTransaction implements the searchrawtransactions command. func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { if !cfg.AddrIndex { @@ -2968,7 +3005,7 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan var addressTxs []*database.TxListReply - var numRequested, numToSkip int + var numRequested, numToSkip, skipped int if c.Count != nil { numRequested = *c.Count if numRequested < 0 { @@ -2986,9 +3023,10 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan // 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, err := s.server.db.FetchTxsForAddr(addr, numToSkip, + 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) } @@ -2998,14 +3036,12 @@ func handleSearchRawTransactions(s *rpcServer, cmd interface{}, closeChan <-chan // 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 len(addressTxs) < numRequested { - memPoolTxs, err := s.server.txMemPool.FilterTransactionsByAddress(addr) + memPoolTxs, memPoolSkipped, err := getMempoolTxsForAddressRange(s, addr, + numToSkip-skipped, numRequested-len(addressTxs)) if err == nil { - for _, tx := range memPoolTxs { - txReply := &database.TxListReply{Tx: tx.MsgTx(), Sha: tx.Sha()} + skipped += memPoolSkipped + for _, txReply := range memPoolTxs { addressTxs = append(addressTxs, txReply) - if len(addressTxs) == numRequested { - break - } } } }