9 log "github.com/sirupsen/logrus"
11 "github.com/vapor/account"
12 "github.com/vapor/blockchain/query"
13 "github.com/vapor/consensus"
14 "github.com/vapor/crypto/sha3pool"
15 chainjson "github.com/vapor/encoding/json"
16 "github.com/vapor/protocol/bc"
17 "github.com/vapor/protocol/bc/types"
20 func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
21 var hashBytes [32]byte
22 copy(hashBytes[:], globalTxIdx[:32])
23 hash := bc.NewHash(hashBytes)
24 position := binary.BigEndian.Uint64(globalTxIdx[32:])
25 return &hash, position
28 // saveExternalAssetDefinition save external and local assets definition,
29 // when query ,query local first and if have no then query external
30 // details see getAliasDefinition
31 func saveExternalAssetDefinition(b *types.Block, store WalletStorer) error {
33 defer store.CommitBatch()
35 for _, tx := range b.Transactions {
36 for _, orig := range tx.Inputs {
37 if cci, ok := orig.TypedInput.(*types.CrossChainInput); ok {
38 assetID := cci.AssetId
39 assetExist, err := store.GetAssetDefinition(assetID)
43 if assetExist == nil {
44 store.SetAssetDefinition(assetID, cci.AssetDefinition)
52 // Summary is the struct of transaction's input and output summary
54 Type string `json:"type"`
55 AssetID bc.AssetID `json:"asset_id,omitempty"`
56 AssetAlias string `json:"asset_alias,omitempty"`
57 Amount uint64 `json:"amount,omitempty"`
58 AccountID string `json:"account_id,omitempty"`
59 AccountAlias string `json:"account_alias,omitempty"`
60 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
63 // TxSummary is the struct of transaction summary
64 type TxSummary struct {
65 ID bc.Hash `json:"tx_id"`
66 Timestamp uint64 `json:"block_time"`
67 Inputs []Summary `json:"inputs"`
68 Outputs []Summary `json:"outputs"`
71 // indexTransactions saves all annotated transactions to the database.
72 func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx) error {
73 for _, tx := range annotatedTxs {
74 if err := w.store.SetTransaction(b.Height, tx); err != nil {
77 w.store.DeleteUnconfirmedTransaction(tx.ID.String())
84 for position, globalTx := range b.Transactions {
85 blockHash := b.BlockHeader.Hash()
86 w.store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
92 // filterAccountTxs related and build the fully annotated transactions.
93 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
94 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
97 for pos, tx := range b.Transactions {
98 statusFail, _ := txStatus.GetStatus(pos)
99 for _, v := range tx.Outputs {
101 sha3pool.Sum256(hash[:], v.ControlProgram())
103 cp, err := w.store.GetControlProgram(hash)
105 log.WithFields(log.Fields{"module": logModule, "err": err, "hash": string(hash[:])}).Error("filterAccountTxs fail.")
109 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
110 continue transactionLoop
114 for _, v := range tx.Inputs {
115 outid, err := v.SpentOutputID()
117 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
120 utxo, err := w.store.GetStandardUTXO(outid)
122 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
126 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
127 continue transactionLoop
135 // GetTransactionByTxID get transaction by txID
136 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
137 if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
138 return annotatedTx, nil
139 } else if !w.TxIndexFlag {
143 return w.getGlobalTxByTxID(txID)
146 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
147 annotatedTx, err := w.store.GetTransaction(txID)
151 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
152 return annotatedTx, nil
155 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
156 globalTxIdx := w.store.GetGlobalTransactionIndex(txID)
157 if globalTxIdx == nil {
158 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
161 blockHash, pos := parseGlobalTxIdx(globalTxIdx)
162 block, err := w.chain.GetBlockByHash(blockHash)
167 txStatus, err := w.chain.GetTransactionStatus(blockHash)
172 statusFail, err := txStatus.GetStatus(int(pos))
177 tx := block.Transactions[int(pos)]
178 return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
181 // GetTransactionsSummary get transactions summary
182 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
185 for _, annotatedTx := range transactions {
186 tmpTxSummary := TxSummary{
187 Inputs: make([]Summary, len(annotatedTx.Inputs)),
188 Outputs: make([]Summary, len(annotatedTx.Outputs)),
190 Timestamp: annotatedTx.Timestamp,
193 for i, input := range annotatedTx.Inputs {
194 tmpTxSummary.Inputs[i].Type = input.Type
195 tmpTxSummary.Inputs[i].AccountID = input.AccountID
196 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
197 tmpTxSummary.Inputs[i].AssetID = input.AssetID
198 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
199 tmpTxSummary.Inputs[i].Amount = input.Amount
200 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
202 for j, output := range annotatedTx.Outputs {
203 tmpTxSummary.Outputs[j].Type = output.Type
204 tmpTxSummary.Outputs[j].AccountID = output.AccountID
205 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
206 tmpTxSummary.Outputs[j].AssetID = output.AssetID
207 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
208 tmpTxSummary.Outputs[j].Amount = output.Amount
211 Txs = append(Txs, tmpTxSummary)
217 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
218 for _, input := range annotatedTx.Inputs {
219 if input.AccountID == accountID {
224 for _, output := range annotatedTx.Outputs {
225 if output.AccountID == accountID {
233 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
234 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
235 annotatedTxs := []*query.AnnotatedTx{}
236 annotatedTxs, err := w.store.GetTransactions()
241 newAnnotatedTxs := []*query.AnnotatedTx{}
242 for _, annotatedTx := range annotatedTxs {
243 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
244 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
245 newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
249 return newAnnotatedTxs, nil
252 // GetAccountBalances return all account balances
253 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
254 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
257 // AccountBalance account balance
258 type AccountBalance struct {
259 AccountID string `json:"account_id"`
260 Alias string `json:"account_alias"`
261 AssetAlias string `json:"asset_alias"`
262 AssetID string `json:"asset_id"`
263 Amount uint64 `json:"amount"`
264 AssetDefinition map[string]interface{} `json:"asset_definition"`
267 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
268 accBalance := make(map[string]map[string]uint64)
269 balances := []AccountBalance{}
271 for _, accountUTXO := range accountUTXOs {
272 assetID := accountUTXO.AssetID.String()
273 if _, ok := accBalance[accountUTXO.AccountID]; ok {
274 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
275 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
277 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
280 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
284 var sortedAccount []string
285 for k := range accBalance {
286 sortedAccount = append(sortedAccount, k)
288 sort.Strings(sortedAccount)
290 for _, id := range sortedAccount {
291 var sortedAsset []string
292 for k := range accBalance[id] {
293 sortedAsset = append(sortedAsset, k)
295 sort.Strings(sortedAsset)
297 for _, assetID := range sortedAsset {
298 alias := w.AccountMgr.GetAliasByID(id)
299 targetAsset, err := w.AssetReg.GetAsset(assetID)
304 assetAlias := *targetAsset.Alias
305 balances = append(balances, AccountBalance{
309 AssetAlias: assetAlias,
310 Amount: accBalance[id][assetID],
311 AssetDefinition: targetAsset.DefinitionMap,
319 // GetAccountVotes return all account votes
320 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
321 return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
324 type voteDetail struct {
325 Vote string `json:"vote"`
326 VoteNumber uint64 `json:"vote_number"`
329 // AccountVotes account vote
330 type AccountVotes struct {
331 AccountID string `json:"account_id"`
332 Alias string `json:"account_alias"`
333 TotalVoteNumber uint64 `json:"total_vote_number"`
334 VoteDetails []voteDetail `json:"vote_details"`
337 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
338 accVote := make(map[string]map[string]uint64)
339 votes := []AccountVotes{}
341 for _, accountUTXO := range accountUTXOs {
342 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
345 xpub := hex.EncodeToString(accountUTXO.Vote)
346 if _, ok := accVote[accountUTXO.AccountID]; ok {
347 accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
349 accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
354 var sortedAccount []string
355 for k := range accVote {
356 sortedAccount = append(sortedAccount, k)
358 sort.Strings(sortedAccount)
360 for _, id := range sortedAccount {
361 var sortedXpub []string
362 for k := range accVote[id] {
363 sortedXpub = append(sortedXpub, k)
365 sort.Strings(sortedXpub)
367 voteDetails := []voteDetail{}
368 voteTotal := uint64(0)
369 for _, xpub := range sortedXpub {
370 voteDetails = append(voteDetails, voteDetail{
372 VoteNumber: accVote[id][xpub],
374 voteTotal += accVote[id][xpub]
376 alias := w.AccountMgr.GetAliasByID(id)
377 votes = append(votes, AccountVotes{
380 VoteDetails: voteDetails,
381 TotalVoteNumber: voteTotal,