OSDN Git Service

f5cdd95c5a0ec66cb2b0ed939f448673e73b8f4f
[bytom/vapor.git] / wallet / indexer.go
1 package wallet
2
3 import (
4         "encoding/binary"
5         "encoding/hex"
6         "fmt"
7         "sort"
8
9         log "github.com/sirupsen/logrus"
10
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"
18 )
19
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
26 }
27
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 {
32         if err := store.InitBatch(); err != nil {
33                 return err
34         }
35
36         for _, tx := range b.Transactions {
37                 for _, orig := range tx.Inputs {
38                         if cci, ok := orig.TypedInput.(*types.CrossChainInput); ok {
39                                 assetID := cci.AssetId
40                                 assetExist, err := store.GetAsset(assetID)
41                                 if err != nil {
42                                         return err
43                                 }
44                                 if assetExist == nil {
45                                         store.SetAssetDefinition(assetID, cci.AssetDefinition)
46                                 }
47                         }
48                 }
49         }
50
51         if err := store.CommitBatch(); err != nil {
52                 return err
53         }
54         return nil
55 }
56
57 // Summary is the struct of transaction's input and output summary
58 type Summary struct {
59         Type         string             `json:"type"`
60         AssetID      bc.AssetID         `json:"asset_id,omitempty"`
61         AssetAlias   string             `json:"asset_alias,omitempty"`
62         Amount       uint64             `json:"amount,omitempty"`
63         AccountID    string             `json:"account_id,omitempty"`
64         AccountAlias string             `json:"account_alias,omitempty"`
65         Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
66 }
67
68 // TxSummary is the struct of transaction summary
69 type TxSummary struct {
70         ID        bc.Hash   `json:"tx_id"`
71         Timestamp uint64    `json:"block_time"`
72         Inputs    []Summary `json:"inputs"`
73         Outputs   []Summary `json:"outputs"`
74 }
75
76 // indexTransactions saves all annotated transactions to the database.
77 func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx) error {
78         for _, tx := range annotatedTxs {
79                 if err := w.store.SetTransaction(b.Height, tx); err != nil {
80                         return err
81                 }
82                 w.store.DeleteUnconfirmedTransaction(tx.ID.String())
83         }
84
85         if !w.TxIndexFlag {
86                 return nil
87         }
88
89         for position, globalTx := range b.Transactions {
90                 blockHash := b.BlockHeader.Hash()
91                 w.store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
92         }
93
94         return nil
95 }
96
97 // filterAccountTxs related and build the fully annotated transactions.
98 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
99         annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
100
101 transactionLoop:
102         for pos, tx := range b.Transactions {
103                 statusFail, _ := txStatus.GetStatus(pos)
104                 for _, v := range tx.Outputs {
105                         var hash [32]byte
106                         sha3pool.Sum256(hash[:], v.ControlProgram())
107
108                         _, err := w.store.GetControlProgram(bc.NewHash(hash))
109                         if err != nil {
110                                 log.WithFields(log.Fields{"module": logModule, "err": err, "hash": string(hash[:])}).Error("filterAccountTxs fail.")
111                                 continue
112                         }
113                         annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
114                         continue transactionLoop
115                 }
116
117                 for _, v := range tx.Inputs {
118                         outid, err := v.SpentOutputID()
119                         if err != nil {
120                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
121                                 continue
122                         }
123                         _, err = w.store.GetStandardUTXO(outid)
124                         if err != nil {
125                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
126                                 continue
127                         }
128                         annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
129                         continue transactionLoop
130                 }
131         }
132
133         return annotatedTxs
134 }
135
136 // GetTransactionByTxID get transaction by txID
137 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
138         if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
139                 return annotatedTx, nil
140         } else if !w.TxIndexFlag {
141                 return nil, err
142         }
143
144         return w.getGlobalTxByTxID(txID)
145 }
146
147 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
148         annotatedTx, err := w.store.GetTransaction(txID)
149         if err != nil {
150                 return nil, err
151         }
152         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
153         return annotatedTx, nil
154 }
155
156 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
157         globalTxIdx := w.store.GetGlobalTransactionIndex(txID)
158         if globalTxIdx == nil {
159                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
160         }
161
162         blockHash, pos := parseGlobalTxIdx(globalTxIdx)
163         block, err := w.chain.GetBlockByHash(blockHash)
164         if err != nil {
165                 return nil, err
166         }
167
168         txStatus, err := w.chain.GetTransactionStatus(blockHash)
169         if err != nil {
170                 return nil, err
171         }
172
173         statusFail, err := txStatus.GetStatus(int(pos))
174         if err != nil {
175                 return nil, err
176         }
177
178         tx := block.Transactions[int(pos)]
179         return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
180 }
181
182 // GetTransactionsSummary get transactions summary
183 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
184         Txs := []TxSummary{}
185
186         for _, annotatedTx := range transactions {
187                 tmpTxSummary := TxSummary{
188                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
189                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
190                         ID:        annotatedTx.ID,
191                         Timestamp: annotatedTx.Timestamp,
192                 }
193
194                 for i, input := range annotatedTx.Inputs {
195                         tmpTxSummary.Inputs[i].Type = input.Type
196                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
197                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
198                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
199                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
200                         tmpTxSummary.Inputs[i].Amount = input.Amount
201                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
202                 }
203                 for j, output := range annotatedTx.Outputs {
204                         tmpTxSummary.Outputs[j].Type = output.Type
205                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
206                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
207                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
208                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
209                         tmpTxSummary.Outputs[j].Amount = output.Amount
210                 }
211
212                 Txs = append(Txs, tmpTxSummary)
213         }
214
215         return Txs
216 }
217
218 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
219         for _, input := range annotatedTx.Inputs {
220                 if input.AccountID == accountID {
221                         return true
222                 }
223         }
224
225         for _, output := range annotatedTx.Outputs {
226                 if output.AccountID == accountID {
227                         return true
228                 }
229         }
230
231         return false
232 }
233
234 // GetTransactions get all walletDB transactions or unconfirmed transactions, and filter transactions by accountID and StartTxID optional
235 func (w *Wallet) GetTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
236         annotatedTxs := []*query.AnnotatedTx{}
237         annotatedTxs, err := w.store.ListTransactions(accountID, StartTxID, count, unconfirmed)
238         if err != nil {
239                 return nil, err
240         }
241
242         newAnnotatedTxs := []*query.AnnotatedTx{}
243         for _, annotatedTx := range annotatedTxs {
244                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
245                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
246                         newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
247                 }
248         }
249
250         if unconfirmed {
251                 sort.Sort(SortByTimestamp(annotatedTxs))
252         } else {
253                 sort.Sort(SortByHeight(annotatedTxs))
254         }
255
256         return newAnnotatedTxs, nil
257 }
258
259 // GetAccountBalances return all account balances
260 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
261         return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
262 }
263
264 // AccountBalance account balance
265 type AccountBalance struct {
266         AccountID       string                 `json:"account_id"`
267         Alias           string                 `json:"account_alias"`
268         AssetAlias      string                 `json:"asset_alias"`
269         AssetID         string                 `json:"asset_id"`
270         Amount          uint64                 `json:"amount"`
271         AssetDefinition map[string]interface{} `json:"asset_definition"`
272 }
273
274 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
275         accBalance := make(map[string]map[string]uint64)
276         balances := []AccountBalance{}
277
278         for _, accountUTXO := range accountUTXOs {
279                 assetID := accountUTXO.AssetID.String()
280                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
281                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
282                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
283                         } else {
284                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
285                         }
286                 } else {
287                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
288                 }
289         }
290
291         var sortedAccount []string
292         for k := range accBalance {
293                 sortedAccount = append(sortedAccount, k)
294         }
295         sort.Strings(sortedAccount)
296
297         for _, id := range sortedAccount {
298                 var sortedAsset []string
299                 for k := range accBalance[id] {
300                         sortedAsset = append(sortedAsset, k)
301                 }
302                 sort.Strings(sortedAsset)
303
304                 for _, assetID := range sortedAsset {
305                         alias := w.AccountMgr.GetAliasByID(id)
306                         targetAsset, err := w.AssetReg.GetAsset(assetID)
307                         if err != nil {
308                                 return nil, err
309                         }
310
311                         assetAlias := *targetAsset.Alias
312                         balances = append(balances, AccountBalance{
313                                 Alias:           alias,
314                                 AccountID:       id,
315                                 AssetID:         assetID,
316                                 AssetAlias:      assetAlias,
317                                 Amount:          accBalance[id][assetID],
318                                 AssetDefinition: targetAsset.DefinitionMap,
319                         })
320                 }
321         }
322
323         return balances, nil
324 }
325
326 // GetAccountVotes return all account votes
327 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
328         return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
329 }
330
331 type voteDetail struct {
332         Vote       string `json:"vote"`
333         VoteNumber uint64 `json:"vote_number"`
334 }
335
336 // AccountVotes account vote
337 type AccountVotes struct {
338         AccountID       string       `json:"account_id"`
339         Alias           string       `json:"account_alias"`
340         TotalVoteNumber uint64       `json:"total_vote_number"`
341         VoteDetails     []voteDetail `json:"vote_details"`
342 }
343
344 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
345         accVote := make(map[string]map[string]uint64)
346         votes := []AccountVotes{}
347
348         for _, accountUTXO := range accountUTXOs {
349                 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
350                         continue
351                 }
352                 xpub := hex.EncodeToString(accountUTXO.Vote)
353                 if _, ok := accVote[accountUTXO.AccountID]; ok {
354                         accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
355                 } else {
356                         accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
357
358                 }
359         }
360
361         var sortedAccount []string
362         for k := range accVote {
363                 sortedAccount = append(sortedAccount, k)
364         }
365         sort.Strings(sortedAccount)
366
367         for _, id := range sortedAccount {
368                 var sortedXpub []string
369                 for k := range accVote[id] {
370                         sortedXpub = append(sortedXpub, k)
371                 }
372                 sort.Strings(sortedXpub)
373
374                 voteDetails := []voteDetail{}
375                 voteTotal := uint64(0)
376                 for _, xpub := range sortedXpub {
377                         voteDetails = append(voteDetails, voteDetail{
378                                 Vote:       xpub,
379                                 VoteNumber: accVote[id][xpub],
380                         })
381                         voteTotal += accVote[id][xpub]
382                 }
383                 alias := w.AccountMgr.GetAliasByID(id)
384                 votes = append(votes, AccountVotes{
385                         Alias:           alias,
386                         AccountID:       id,
387                         VoteDetails:     voteDetails,
388                         TotalVoteNumber: voteTotal,
389                 })
390         }
391
392         return votes, nil
393 }