From 041a74394dd18dc0abbee21dd6e6e18bbdbd630a Mon Sep 17 00:00:00 2001 From: wz Date: Tue, 26 Nov 2019 17:04:37 +0800 Subject: [PATCH] fix api --- account/accounts.go | 3 ++- api/query.go | 53 ++++++++++++++++------------------------- database/leveldb/db.go | 1 + database/leveldb/go_level_db.go | 42 +++++++++++++++++++++++++++++--- database/leveldb/mem_db.go | 47 +++++++++++++++++++++++++++++++++++- wallet/indexer.go | 44 +++++++++++++++++++++++++++------- wallet/unconfirmed.go | 7 ++++++ 7 files changed, 151 insertions(+), 46 deletions(-) diff --git a/account/accounts.go b/account/accounts.go index ab9bd9e2..7170e890 100644 --- a/account/accounts.go +++ b/account/accounts.go @@ -19,11 +19,11 @@ import ( "github.com/bytom/crypto" "github.com/bytom/crypto/ed25519/chainkd" "github.com/bytom/crypto/sha3pool" + dbm "github.com/bytom/database/leveldb" "github.com/bytom/errors" "github.com/bytom/protocol" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/vm/vmutil" - dbm "github.com/bytom/database/leveldb" ) const ( @@ -57,6 +57,7 @@ var ( ErrContractIndex = errors.New("Exceeded maximum addresses per account") ErrAccountIndex = errors.New("Exceeded maximum accounts per xpub") ErrFindTransaction = errors.New("No transaction") + ErrAccountIDEmpty = errors.New("account_id is empty") ) // ContractKey account control promgram store prefix diff --git a/api/query.go b/api/query.go index 5265475d..00328d2f 100644 --- a/api/query.go +++ b/api/query.go @@ -129,48 +129,37 @@ func (a *API) getTransaction(ctx context.Context, txInfo struct { // POST /list-transactions func (a *API) listTransactions(ctx context.Context, filter struct { - ID string `json:"id"` - AccountID string `json:"account_id"` - Detail bool `json:"detail"` - Unconfirmed bool `json:"unconfirmed"` - From uint `json:"from"` - Count uint `json:"count"` + AccountID string `json:"account_id"` + AccountAlias string `json:"account_alias"` + StartTxID string `json:"start_tx_id"` + Detail bool `json:"detail"` + Unconfirmed bool `json:"unconfirmed"` + Count uint `json:"count"` }) Response { - transactions := []*query.AnnotatedTx{} - var err error - var transaction *query.AnnotatedTx - - if filter.ID != "" { - transaction, err = a.wallet.GetTransactionByTxID(filter.ID) - if err != nil && filter.Unconfirmed { - transaction, err = a.wallet.GetUnconfirmedTxByTxID(filter.ID) - if err != nil { - return NewErrorResponse(err) - } - } - transactions = []*query.AnnotatedTx{transaction} - } else { - transactions, err = a.wallet.GetTransactions(filter.AccountID) + accountID := filter.AccountID + if filter.AccountAlias != "" { + acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias) if err != nil { return NewErrorResponse(err) } + accountID = acc.ID + } - if filter.Unconfirmed { - unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID) - if err != nil { - return NewErrorResponse(err) - } - transactions = append(unconfirmedTxs, transactions...) - } + if accountID == "" { + return NewErrorResponse(account.ErrAccountIDEmpty) + } + + transactions, err := a.wallet.GetTransactions(accountID, filter.StartTxID, filter.Count, filter.Unconfirmed) + if err != nil { + return NewErrorResponse(err) } if filter.Detail == false { txSummary := a.wallet.GetTransactionsSummary(transactions) - start, end := getPageRange(len(txSummary), filter.From, filter.Count) - return NewSuccessResponse(txSummary[start:end]) + return NewSuccessResponse(txSummary) } - start, end := getPageRange(len(transactions), filter.From, filter.Count) - return NewSuccessResponse(transactions[start:end]) + + return NewSuccessResponse(transactions) } // POST /get-unconfirmed-transaction diff --git a/database/leveldb/db.go b/database/leveldb/db.go index 38cab5bd..aca95866 100644 --- a/database/leveldb/db.go +++ b/database/leveldb/db.go @@ -12,6 +12,7 @@ type DB interface { NewBatch() Batch Iterator() Iterator IteratorPrefix([]byte) Iterator + IteratorPrefixWithStart(Prefix, start []byte, isReverse bool) Iterator // For debugging Print() diff --git a/database/leveldb/go_level_db.go b/database/leveldb/go_level_db.go index e9e8d3dd..174540ed 100644 --- a/database/leveldb/go_level_db.go +++ b/database/leveldb/go_level_db.go @@ -118,7 +118,28 @@ func (db *GoLevelDB) Stats() map[string]string { } type goLevelDBIterator struct { - source iterator.Iterator + source iterator.Iterator + start []byte + isReverse bool +} + +func newGoLevelDBIterator(source iterator.Iterator, start []byte, isReverse bool) *goLevelDBIterator { + if start != nil { + valid := source.Seek(start) + if !valid && isReverse { + source.Last() + source.Next() + } + } else if isReverse { + source.Last() + source.Next() + } + + return &goLevelDBIterator{ + source: source, + start: start, + isReverse: isReverse, + } } // Key returns a copy of the current key. @@ -148,19 +169,34 @@ func (it *goLevelDBIterator) Error() error { } func (it *goLevelDBIterator) Next() bool { + it.assertNoError() + if it.isReverse { + return it.source.Prev() + } return it.source.Next() } +func (it *goLevelDBIterator) assertNoError() { + if err := it.source.Error(); err != nil { + panic(err) + } +} + func (it *goLevelDBIterator) Release() { it.source.Release() } func (db *GoLevelDB) Iterator() Iterator { - return &goLevelDBIterator{db.db.NewIterator(nil, nil)} + return &goLevelDBIterator{source: db.db.NewIterator(nil, nil)} } func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator { - return &goLevelDBIterator{db.db.NewIterator(util.BytesPrefix(prefix), nil)} + return &goLevelDBIterator{source: db.db.NewIterator(util.BytesPrefix(prefix), nil)} +} + +func (db *GoLevelDB) IteratorPrefixWithStart(Prefix, start []byte, isReverse bool) Iterator { + itr := db.db.NewIterator(util.BytesPrefix(Prefix), nil) + return newGoLevelDBIterator(itr, start, isReverse) } func (db *GoLevelDB) NewBatch() Batch { diff --git a/database/leveldb/mem_db.go b/database/leveldb/mem_db.go index 62f40fc6..e70aa9d0 100644 --- a/database/leveldb/mem_db.go +++ b/database/leveldb/mem_db.go @@ -1,6 +1,7 @@ package db import ( + "bytes" "fmt" "sort" "strings" @@ -78,13 +79,29 @@ func (db *MemDB) Stats() map[string]string { type memDBIterator struct { last int keys []string - db *MemDB + db DB + + start []byte } func newMemDBIterator() *memDBIterator { return &memDBIterator{} } +// Keys is expected to be in reverse order for reverse iterators. +func newMemDBIteratorWithArgs(db DB, keys []string, start []byte) *memDBIterator { + itr := &memDBIterator{ + db: db, + keys: keys, + start: start, + last: -1, + } + if start != nil { + itr.Seek(start) + } + return itr +} + func (it *memDBIterator) Next() bool { if it.last >= len(it.keys)-1 { return false @@ -143,10 +160,38 @@ func (db *MemDB) IteratorPrefix(prefix []byte) Iterator { return it } +func (db *MemDB) IteratorPrefixWithStart(Prefix, start []byte, isReverse bool) Iterator { + db.mtx.Lock() + defer db.mtx.Unlock() + + keys := db.getSortedKeys(start, isReverse) + return newMemDBIteratorWithArgs(db, keys, start) +} + func (db *MemDB) NewBatch() Batch { return &memDBBatch{db, nil} } +func (db *MemDB) getSortedKeys(start []byte, reverse bool) []string { + keys := []string{} + for key := range db.db { + if bytes.Compare([]byte(key), start) < 0 { + continue + } + keys = append(keys, key) + } + sort.Strings(keys) + if reverse { + nkeys := len(keys) + for i := 0; i < nkeys/2; i++ { + temp := keys[i] + keys[i] = keys[nkeys-i-1] + keys[nkeys-i-1] = temp + } + } + return keys +} + //-------------------------------------------------------------------------------- type memDBBatch struct { diff --git a/wallet/indexer.go b/wallet/indexer.go index 04d9643c..9cf3392f 100644 --- a/wallet/indexer.go +++ b/wallet/indexer.go @@ -26,7 +26,7 @@ const ( GlobalTxIndexPrefix = "GTID:" ) -var errAccntTxIDNotFound = errors.New("account TXID not found") +var ErrAccntTxIDNotFound = errors.New("account TXID not found") func formatKey(blockHeight uint64, position uint32) string { return fmt.Sprintf("%016x%08x", blockHeight, position) @@ -158,7 +158,7 @@ func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) { annotatedTx := &query.AnnotatedTx{} formatKey := w.DB.Get(calcTxIndexKey(txID)) if formatKey == nil { - return nil, errAccntTxIDNotFound + return nil, ErrAccntTxIDNotFound } txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey))) @@ -174,7 +174,7 @@ func (w *Wallet) getGlobalTxByIndex(index string) (*query.AnnotatedTx, error) { annotatedTx := &query.AnnotatedTx{} formatKey := w.DB.Get(calcGlobalTxIndexKey(index)) if formatKey == nil { - return nil, errAccntTxIDNotFound + return nil, ErrAccntTxIDNotFound } txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey))) @@ -238,24 +238,50 @@ func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) return false } -// GetTransactions get all walletDB transactions, and filter transactions by accountID optional -func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) { +// GetTransactions get all walletDB transactions or unconfirmed transactions, and filter transactions by accountID and StartTxID optional +func (w *Wallet) GetTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) { annotatedTxs := []*query.AnnotatedTx{} + var startKey []byte + preFix := TxPrefix - txIter := w.DB.IteratorPrefix([]byte(TxPrefix)) - defer txIter.Release() - for txIter.Next() { + if StartTxID != "" { + if unconfirmed { + startKey = calcUnconfirmedTxKey(StartTxID) + } else { + formatKey := w.DB.Get(calcTxIndexKey(StartTxID)) + if formatKey == nil { + return nil, ErrAccntTxIDNotFound + } + startKey = calcAnnotatedKey(string(formatKey)) + } + } + + if unconfirmed { + preFix = UnconfirmedTxPrefix + } + + itr := w.DB.IteratorPrefixWithStart([]byte(preFix), startKey, true) + defer itr.Release() + + for txNum := count; itr.Next() && txNum > 0; { annotatedTx := &query.AnnotatedTx{} - if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil { + if err := json.Unmarshal(itr.Value(), &annotatedTx); err != nil { return nil, err } if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) { annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx}) annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...) + txNum-- } } + if unconfirmed { + sort.Sort(SortByTimestamp(annotatedTxs)) + } else { + sort.Sort(SortByHeight(annotatedTxs)) + } + return annotatedTxs, nil } diff --git a/wallet/unconfirmed.go b/wallet/unconfirmed.go index b616518f..5b04b1ba 100644 --- a/wallet/unconfirmed.go +++ b/wallet/unconfirmed.go @@ -33,6 +33,13 @@ func (a SortByTimestamp) Len() int { return len(a) } func (a SortByTimestamp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a SortByTimestamp) Less(i, j int) bool { return a[i].Timestamp > a[j].Timestamp } +// SortByHeight implements sort.Interface for AnnotatedTx slices +type SortByHeight []*query.AnnotatedTx + +func (a SortByHeight) Len() int { return len(a) } +func (a SortByHeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a SortByHeight) Less(i, j int) bool { return a[i].BlockHeight > a[j].BlockHeight } + // AddUnconfirmedTx handle wallet status update when tx add into txpool func (w *Wallet) AddUnconfirmedTx(txD *protocol.TxDesc) { if err := w.saveUnconfirmedTx(txD.Tx); err != nil { -- 2.11.0