"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 (
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
// 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
NewBatch() Batch
Iterator() Iterator
IteratorPrefix([]byte) Iterator
+ IteratorPrefixWithStart(Prefix, start []byte, isReverse bool) Iterator
// For debugging
Print()
}
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.
}
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 {
package db
import (
+ "bytes"
"fmt"
"sort"
"strings"
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
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 {
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)
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)))
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)))
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
}
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 {