"encoding/hex"
"fmt"
+ "github.com/vapor/wallet"
+
log "github.com/sirupsen/logrus"
"github.com/vapor/account"
return NewSuccessResponse(annotatedTx)
}
+func (a *API) getTransacton(ctx context.Context, filter struct {
+ ID string `json:"id"`
+ Unconfirmed bool `json:"unconfirmed"`
+}) Response {
+ 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)
+ }
+ return NewSuccessResponse(transaction)
+}
+
// 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"`
}) 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)
- }
+ transactions, err = a.wallet.GetTransactionsRange(filter.AccountID, filter.ID, filter.Count)
+ unconfirmedCount := filter.Count - uint(len(transactions))
+ txID := ""
+ if err == wallet.ErrAccntTxIDNotFound {
+ txID = filter.ID
+ } else if err != nil {
+ return NewErrorResponse(err)
+ }
+ if filter.Unconfirmed && unconfirmedCount != 0 {
+ unconfirmedTxs, err := a.wallet.GetUnconfirmedTxsRange(filter.AccountID, txID, unconfirmedCount)
if err != nil {
return NewErrorResponse(err)
}
- transactions = []*query.AnnotatedTx{transaction}
- } else {
- transactions, err = a.wallet.GetTransactions(filter.AccountID)
- if err != nil {
- return NewErrorResponse(err)
- }
-
- if filter.Unconfirmed {
- unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID)
- if err != nil {
- return NewErrorResponse(err)
- }
- transactions = append(unconfirmedTxs, transactions...)
- }
+ transactions = append(unconfirmedTxs, transactions...)
}
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
-package db
+package leveldb
import . "github.com/tendermint/tmlibs/common"
NewBatch() Batch
Iterator() Iterator
IteratorPrefix([]byte) Iterator
+ // Iterate over a domain of keys in ascending order. End is exclusive.
+ // Start must be less than end, or the Iterator is invalid.
+ // A nil start is interpreted as an empty byteslice.
+ // If end is nil, iterates up to the last item (inclusive).
+ // CONTRACT: No writes may happen within a domain while an iterator exists over it.
+ // CONTRACT: start, end readonly []byte
+ IteratorRange(start, end []byte) Iterator
// For debugging
Print()
--- /dev/null
+package leveldb
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func newTempDB(t *testing.T, backend string) (db DB, dbDir string) {
+ dirname, err := ioutil.TempDir("", "db_common_test")
+ require.Nil(t, err)
+ return NewDB("testdb", backend, dirname), dirname
+}
+
+func TestDBIteratorSingleKey(t *testing.T) {
+ for backend := range backends {
+ t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
+ db, dir := newTempDB(t, backend)
+ defer os.RemoveAll(dir)
+
+ db.Set([]byte("1"), []byte("value_1"))
+ itr := db.IteratorRange(nil, nil)
+ require.Equal(t, []byte(""), itr.Key())
+ require.Equal(t, true, itr.Next())
+ require.Equal(t, []byte("1"), itr.Key())
+ })
+ }
+}
+
+func TestDBIteratorTwoKeys(t *testing.T) {
+ for backend := range backends {
+ t.Run(fmt.Sprintf("Backend %s", backend), func(t *testing.T) {
+ db, dir := newTempDB(t, backend)
+ defer os.RemoveAll(dir)
+
+ db.SetSync([]byte("1"), []byte("value_1"))
+ db.SetSync([]byte("2"), []byte("value_1"))
+
+ itr := db.IteratorRange([]byte("1"), nil)
+
+ require.Equal(t, []byte("1"), itr.Key())
+
+ require.Equal(t, true, itr.Next())
+ itr = db.IteratorRange([]byte("2"), nil)
+
+ require.Equal(t, false, itr.Next())
+ })
+ }
+}
-package db
+package leveldb
import (
"fmt"
type goLevelDBIterator struct {
source iterator.Iterator
+ start []byte
+ end []byte
+}
+
+func newGoLevelDBIterator(source iterator.Iterator, start, end []byte) *goLevelDBIterator {
+ if start != nil {
+ source.Seek(start)
+ }
+
+ return &goLevelDBIterator{
+ source: source,
+ start: start,
+ end: end,
+ }
}
// Key returns a copy of the current key.
}
func (it *goLevelDBIterator) Next() bool {
+ it.assertNoError()
return it.source.Next()
}
it.source.Release()
}
+func (it *goLevelDBIterator) assertNoError() {
+ if err := it.source.Error(); err != nil {
+ panic(err)
+ }
+}
+
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) IteratorRange(start, end []byte) Iterator {
+ itr := db.db.NewIterator(nil, nil)
+ return newGoLevelDBIterator(itr, start, end)
}
func (db *GoLevelDB) NewBatch() Batch {
-package db
+package leveldb
import (
"bytes"
// Write something
{
idx := (int64(RandInt()) % numItems)
- internal[idx] += 1
+ internal[idx]++
val := internal[idx]
idxBytes := int642Bytes(int64(idx))
valBytes := int642Bytes(int64(val))
-package db
+package leveldb
import (
"fmt"
type memDBIterator struct {
last int
keys []string
- db *MemDB
+ db DB
+
+ start []byte
+ end []byte
}
func newMemDBIterator() *memDBIterator {
return &memDBIterator{}
}
+// Keys is expected to be in reverse order for reverse iterators.
+func newMemDBIteratorWithArgs(db DB, keys []string, start, end []byte) *memDBIterator {
+ itr := &memDBIterator{
+ db: db,
+ keys: keys,
+ start: start,
+ end: end,
+ last: -1,
+ }
+ if start != nil {
+ itr.Seek(start)
+ }
+ return itr
+}
+
func (it *memDBIterator) Next() bool {
if it.last >= len(it.keys)-1 {
return false
}
func (it *memDBIterator) Key() []byte {
+ if it.last < 0 {
+ return []byte("")
+ }
return []byte(it.keys[it.last])
}
return it
}
+func (db *MemDB) IteratorRange(start, end []byte) Iterator {
+ db.mtx.Lock()
+ defer db.mtx.Unlock()
+
+ keys := db.getSortedKeys(start, end)
+ return newMemDBIteratorWithArgs(db, keys, start, end)
+}
+
func (db *MemDB) NewBatch() Batch {
return &memDBBatch{db, nil}
}
+func (db *MemDB) getSortedKeys(start, end []byte) []string {
+ keys := []string{}
+ for key := range db.db {
+ inDomain := IsKeyInDomain([]byte(key), start, end)
+ if inDomain {
+ keys = append(keys, key)
+ }
+ }
+ sort.Strings(keys)
+ return keys
+}
+
//--------------------------------------------------------------------------------
type memDBBatch struct {
-package db
+package leveldb
import (
"testing"
i := 0
for iter.Next() {
assert.Equal(t, db.Get(iter.Key()), iter.Value(), "values dont match for key")
- i += 1
+ i++
}
assert.Equal(t, i, len(db.db), "iterator didnt cover whole db")
}
--- /dev/null
+package leveldb
+
+import (
+ "bytes"
+)
+
+func cp(bz []byte) (ret []byte) {
+ ret = make([]byte, len(bz))
+ copy(ret, bz)
+ return ret
+}
+
+// Returns a slice of the same length (big endian)
+// except incremented by one.
+// Returns nil on overflow (e.g. if bz bytes are all 0xFF)
+// CONTRACT: len(bz) > 0
+func cpIncr(bz []byte) (ret []byte) {
+ if len(bz) == 0 {
+ panic("cpIncr expects non-zero bz length")
+ }
+ ret = cp(bz)
+ for i := len(bz) - 1; i >= 0; i-- {
+ if ret[i] < byte(0xFF) {
+ ret[i]++
+ return
+ }
+ ret[i] = byte(0x00)
+ if i == 0 {
+ // Overflow
+ return nil
+ }
+ }
+ return nil
+}
+
+// See DB interface documentation for more information.
+func IsKeyInDomain(key, start, end []byte) bool {
+ if bytes.Compare(key, start) < 0 {
+ return false
+ }
+ if end != nil && bytes.Compare(end, key) <= 0 {
+ return false
+ }
+ return true
+}
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)))
return annotatedTxs, nil
}
+// GetTransactionsRange get all walletDB transactions, and filter transactions by accountID and txID optional
+func (w *Wallet) GetTransactionsRange(accountID string, txID string, count uint) ([]*query.AnnotatedTx, error) {
+ annotatedTxs := []*query.AnnotatedTx{}
+ var itr dbm.Iterator
+
+ if txID != "" {
+ formatKey := w.DB.Get(calcTxIndexKey(txID))
+ if formatKey == nil {
+ return nil, ErrAccntTxIDNotFound
+ }
+ startKey := calcAnnotatedKey(string(formatKey))
+ itr = w.DB.IteratorRange(startKey, nil)
+ } else {
+ itr = w.DB.IteratorPrefix([]byte(TxPrefix))
+ }
+
+ defer itr.Release()
+
+ txNum := count
+ for itr.Next() {
+ annotatedTx := &query.AnnotatedTx{}
+ 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 txNum == 0 {
+ break
+ }
+ }
+
+ return annotatedTxs, nil
+}
+
// GetAccountBalances return all account balances
func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
"github.com/vapor/account"
"github.com/vapor/blockchain/query"
"github.com/vapor/crypto/sha3pool"
+ dbm "github.com/vapor/database/leveldb"
"github.com/vapor/protocol"
"github.com/vapor/protocol/bc/types"
)
return annotatedTxs, nil
}
+// GetUnconfirmedTxsRange get account unconfirmed transactions, filter transactions by accountID when accountID is not empty
+func (w *Wallet) GetUnconfirmedTxsRange(accountID string, txID string, count uint) ([]*query.AnnotatedTx, error) {
+ annotatedTxs := []*query.AnnotatedTx{}
+ var itr dbm.Iterator
+
+ if txID != "" {
+ itr = w.DB.IteratorRange(calcUnconfirmedTxKey(txID), nil)
+ } else {
+ itr = w.DB.IteratorPrefix([]byte(UnconfirmedTxPrefix))
+ }
+
+ defer itr.Release()
+
+ for itr.Next() {
+ annotatedTx := &query.AnnotatedTx{}
+ 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...)
+ }
+ }
+
+ sort.Sort(SortByTimestamp(annotatedTxs))
+ return annotatedTxs, nil
+}
+
// GetUnconfirmedTxByTxID get unconfirmed transaction by txID
func (w *Wallet) GetUnconfirmedTxByTxID(txID string) (*query.AnnotatedTx, error) {
annotatedTx := &query.AnnotatedTx{}