package wallet import ( "encoding/binary" "encoding/hex" "fmt" "sort" log "github.com/sirupsen/logrus" "github.com/vapor/account" "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" ) 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, store WalletStore) error { newStore := store.InitBatch() for _, tx := range b.Transactions { for _, orig := range tx.Inputs { 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 type Summary struct { Type string `json:"type"` AssetID bc.AssetID `json:"asset_id,omitempty"` AssetAlias string `json:"asset_alias,omitempty"` Amount uint64 `json:"amount,omitempty"` AccountID string `json:"account_id,omitempty"` AccountAlias string `json:"account_alias,omitempty"` Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"` } // TxSummary is the struct of transaction summary type TxSummary struct { ID bc.Hash `json:"tx_id"` Timestamp uint64 `json:"block_time"` Inputs []Summary `json:"inputs"` Outputs []Summary `json:"outputs"` } // indexTransactions saves all annotated transactions to the database. func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx, store WalletStore) error { for _, tx := range annotatedTxs { if err := w.Store.SetTransaction(b.Height, tx); err != nil { return err } store.DeleteUnconfirmedTransaction(tx.ID.String()) } if !w.TxIndexFlag { return nil } for position, globalTx := range b.Transactions { blockHash := b.BlockHeader.Hash() store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position)) } return nil } // filterAccountTxs related and build the fully annotated transactions. func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx { annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions)) transactionLoop: for pos, tx := range b.Transactions { statusFail, _ := txStatus.GetStatus(pos) for _, v := range tx.Outputs { var hash [32]byte 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 _, 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.") } } } return annotatedTxs } // GetTransactionByTxID get transaction by txID func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) { if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil { return annotatedTx, nil } else if !w.TxIndexFlag { return nil, err } 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}) 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{} for _, annotatedTx := range transactions { tmpTxSummary := TxSummary{ Inputs: make([]Summary, len(annotatedTx.Inputs)), Outputs: make([]Summary, len(annotatedTx.Outputs)), ID: annotatedTx.ID, Timestamp: annotatedTx.Timestamp, } for i, input := range annotatedTx.Inputs { tmpTxSummary.Inputs[i].Type = input.Type tmpTxSummary.Inputs[i].AccountID = input.AccountID tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias tmpTxSummary.Inputs[i].AssetID = input.AssetID tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias tmpTxSummary.Inputs[i].Amount = input.Amount tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary } for j, output := range annotatedTx.Outputs { tmpTxSummary.Outputs[j].Type = output.Type tmpTxSummary.Outputs[j].AccountID = output.AccountID tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias tmpTxSummary.Outputs[j].AssetID = output.AssetID tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias tmpTxSummary.Outputs[j].Amount = output.Amount } Txs = append(Txs, tmpTxSummary) } return Txs } func FindTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool { for _, input := range annotatedTx.Inputs { if input.AccountID == accountID { return true } } for _, output := range annotatedTx.Outputs { if output.AccountID == accountID { return true } } return false } // 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 } newAnnotatedTxs := []*query.AnnotatedTx{} for _, annotatedTx := range annotatedTxs { annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx}) newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...) } if unconfirmed { sort.Sort(SortByTimestamp(newAnnotatedTxs)) } else { sort.Sort(SortByHeight(newAnnotatedTxs)) } 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, false)) } // AccountBalance account balance type AccountBalance struct { AccountID string `json:"account_id"` Alias string `json:"account_alias"` AssetAlias string `json:"asset_alias"` AssetID string `json:"asset_id"` Amount uint64 `json:"amount"` AssetDefinition map[string]interface{} `json:"asset_definition"` } func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) { accBalance := make(map[string]map[string]uint64) balances := []AccountBalance{} for _, accountUTXO := range accountUTXOs { assetID := accountUTXO.AssetID.String() if _, ok := accBalance[accountUTXO.AccountID]; ok { if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok { accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount } else { accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount } } else { accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount} } } var sortedAccount []string for k := range accBalance { sortedAccount = append(sortedAccount, k) } sort.Strings(sortedAccount) for _, id := range sortedAccount { var sortedAsset []string for k := range accBalance[id] { sortedAsset = append(sortedAsset, k) } sort.Strings(sortedAsset) for _, assetID := range sortedAsset { alias := w.AccountMgr.GetAliasByID(id) targetAsset, err := w.AssetReg.GetAsset(assetID) if err != nil { return nil, err } assetAlias := *targetAsset.Alias balances = append(balances, AccountBalance{ Alias: alias, AccountID: id, AssetID: assetID, AssetAlias: assetAlias, Amount: accBalance[id][assetID], AssetDefinition: targetAsset.DefinitionMap, }) } } 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 }