package wallet
import (
- "encoding/json"
+ "encoding/binary"
+ "encoding/hex"
"fmt"
"sort"
log "github.com/sirupsen/logrus"
- "github.com/tendermint/tmlibs/db"
"github.com/vapor/account"
- "github.com/vapor/asset"
"github.com/vapor/blockchain/query"
+ "github.com/vapor/consensus"
"github.com/vapor/crypto/sha3pool"
chainjson "github.com/vapor/encoding/json"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
)
-const (
- //TxPrefix is wallet database transactions prefix
- TxPrefix = "TXS:"
- //TxIndexPrefix is wallet database tx index prefix
- TxIndexPrefix = "TID:"
-)
-
-func formatKey(blockHeight uint64, position uint32) string {
- return fmt.Sprintf("%016x%08x", blockHeight, position)
-}
-
-func calcAnnotatedKey(formatKey string) []byte {
- return []byte(TxPrefix + formatKey)
-}
-
-func calcDeleteKey(blockHeight uint64) []byte {
- return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
-}
-
-func calcTxIndexKey(txID string) []byte {
- return []byte(TxIndexPrefix + txID)
-}
-
-// deleteTransaction delete transactions when orphan block rollback
-func (w *Wallet) deleteTransactions(batch db.Batch, height uint64) {
- tmpTx := query.AnnotatedTx{}
- txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
- defer txIter.Release()
-
- for txIter.Next() {
- if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
- batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
- }
- batch.Delete(txIter.Key())
- }
+func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
+ var hashBytes [32]byte
+ copy(hashBytes[:], globalTxIdx[:32])
+ hash := bc.NewHash(hashBytes)
+ position := binary.BigEndian.Uint64(globalTxIdx[32:])
+ return &hash, position
}
// saveExternalAssetDefinition save external and local assets definition,
// when query ,query local first and if have no then query external
// details see getAliasDefinition
-func saveExternalAssetDefinition(b *types.Block, walletDB db.DB) {
- storeBatch := walletDB.NewBatch()
- defer storeBatch.Write()
+func saveExternalAssetDefinition(b *types.Block, store WalletStore) error {
+ newStore := store.InitBatch()
for _, tx := range b.Transactions {
for _, orig := range tx.Inputs {
- if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
- if isValidJSON(ii.AssetDefinition) {
- assetID := ii.AssetID()
- if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
- storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
- }
+ if cci, ok := orig.TypedInput.(*types.CrossChainInput); ok {
+ if cci.AssetId.String() == consensus.BTMAssetID.String() {
+ continue
}
+ assetID := cci.AssetId
+ if _, err := newStore.GetAsset(assetID); err == nil {
+ continue
+ } else if err != ErrGetAsset {
+ return err
+ }
+
+ newStore.SetAssetDefinition(assetID, cci.AssetDefinition)
}
}
}
+ if err := newStore.CommitBatch(); err != nil {
+ return err
+ }
+
+ return nil
}
// Summary is the struct of transaction's input and output summary
}
// indexTransactions saves all annotated transactions to the database.
-func (w *Wallet) indexTransactions(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
- annotatedTxs := w.filterAccountTxs(b, txStatus)
- saveExternalAssetDefinition(b, w.DB)
- annotateTxsAccount(annotatedTxs, w.DB)
-
+func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx, store WalletStore) error {
for _, tx := range annotatedTxs {
- rawTx, err := json.Marshal(tx)
- if err != nil {
- log.WithField("err", err).Error("inserting annotated_txs to db")
+ if err := w.Store.SetTransaction(b.Height, tx); err != nil {
return err
}
- batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
- batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
+ store.DeleteUnconfirmedTransaction(tx.ID.String())
+ }
+
+ if !w.TxIndexFlag {
+ return nil
+ }
- // delete unconfirmed transaction
- batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
+ for position, globalTx := range b.Transactions {
+ blockHash := b.BlockHeader.Hash()
+ store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
}
+
return nil
}
statusFail, _ := txStatus.GetStatus(pos)
for _, v := range tx.Outputs {
var hash [32]byte
- sha3pool.Sum256(hash[:], v.ControlProgram)
- if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
+ sha3pool.Sum256(hash[:], v.ControlProgram())
+ if _, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash)); err == nil {
annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
continue transactionLoop
+ } else if err != account.ErrFindCtrlProgram {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "hash": hex.EncodeToString(hash[:])}).Error("filterAccountTxs fail.")
}
}
for _, v := range tx.Inputs {
outid, err := v.SpentOutputID()
if err != nil {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
continue
}
- if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
+ if _, err = w.Store.GetStandardUTXO(outid); err == nil {
annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
continue transactionLoop
+ } else if err != ErrGetStandardUTXO {
+ log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
}
}
}
// GetTransactionByTxID get transaction by txID
func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
- formatKey := w.DB.Get(calcTxIndexKey(txID))
- if formatKey == nil {
- return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
+ if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
+ return annotatedTx, nil
+ } else if !w.TxIndexFlag {
+ return nil, err
}
- annotatedTx := &query.AnnotatedTx{}
- txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
- if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
+ return w.getGlobalTxByTxID(txID)
+}
+
+func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
+ annotatedTx, err := w.Store.GetTransaction(txID)
+ if err != nil {
return nil, err
}
- annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
+ annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
return annotatedTx, nil
}
+func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
+ globalTxIdx := w.Store.GetGlobalTransactionIndex(txID)
+ if globalTxIdx == nil {
+ return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
+ }
+
+ blockHash, pos := parseGlobalTxIdx(globalTxIdx)
+ block, err := w.Chain.GetBlockByHash(blockHash)
+ if err != nil {
+ return nil, err
+ }
+
+ txStatus, err := w.Chain.GetTransactionStatus(blockHash)
+ if err != nil {
+ return nil, err
+ }
+
+ statusFail, err := txStatus.GetStatus(int(pos))
+ if err != nil {
+ return nil, err
+ }
+
+ tx := block.Transactions[int(pos)]
+ return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
+}
+
// GetTransactionsSummary get transactions summary
func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
Txs := []TxSummary{}
return Txs
}
-func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
+func FindTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
for _, input := range annotatedTx.Inputs {
if input.AccountID == accountID {
return true
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{}
+ annotatedTxs, err := w.Store.ListTransactions(accountID, StartTxID, count, unconfirmed)
+ if err != nil {
+ return nil, err
+ }
- txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
- defer txIter.Release()
- for txIter.Next() {
- annotatedTx := &query.AnnotatedTx{}
- if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
- return nil, err
- }
+ newAnnotatedTxs := []*query.AnnotatedTx{}
+ for _, annotatedTx := range annotatedTxs {
+ annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
+ newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
+ }
- if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
- annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
- annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
- }
+ if unconfirmed {
+ sort.Sort(SortByTimestamp(newAnnotatedTxs))
+ } else {
+ sort.Sort(SortByHeight(newAnnotatedTxs))
}
- return annotatedTxs, nil
+ return newAnnotatedTxs, nil
}
// GetAccountBalances return all account balances
func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
- return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
+ return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
}
// AccountBalance account balance
return balances, nil
}
+
+// GetAccountVotes return all account votes
+func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
+ return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
+}
+
+type voteDetail struct {
+ Vote string `json:"vote"`
+ VoteNumber uint64 `json:"vote_number"`
+}
+
+// AccountVotes account vote
+type AccountVotes struct {
+ AccountID string `json:"account_id"`
+ Alias string `json:"account_alias"`
+ TotalVoteNumber uint64 `json:"total_vote_number"`
+ VoteDetails []voteDetail `json:"vote_details"`
+}
+
+func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
+ accVote := make(map[string]map[string]uint64)
+ votes := []AccountVotes{}
+
+ for _, accountUTXO := range accountUTXOs {
+ if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
+ continue
+ }
+ xpub := hex.EncodeToString(accountUTXO.Vote)
+ if _, ok := accVote[accountUTXO.AccountID]; ok {
+ accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
+ } else {
+ accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
+
+ }
+ }
+
+ var sortedAccount []string
+ for k := range accVote {
+ sortedAccount = append(sortedAccount, k)
+ }
+ sort.Strings(sortedAccount)
+
+ for _, id := range sortedAccount {
+ var sortedXpub []string
+ for k := range accVote[id] {
+ sortedXpub = append(sortedXpub, k)
+ }
+ sort.Strings(sortedXpub)
+
+ voteDetails := []voteDetail{}
+ voteTotal := uint64(0)
+ for _, xpub := range sortedXpub {
+ voteDetails = append(voteDetails, voteDetail{
+ Vote: xpub,
+ VoteNumber: accVote[id][xpub],
+ })
+ voteTotal += accVote[id][xpub]
+ }
+ alias := w.AccountMgr.GetAliasByID(id)
+ votes = append(votes, AccountVotes{
+ Alias: alias,
+ AccountID: id,
+ VoteDetails: voteDetails,
+ TotalVoteNumber: voteTotal,
+ })
+ }
+
+ return votes, nil
+}