OSDN Git Service

b97c15f577d8d1cfaf74dd4d4c05a364301eef35
[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                         cp, 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                         if cp != nil {
114                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
115                                 continue transactionLoop
116                         }
117                 }
118
119                 for _, v := range tx.Inputs {
120                         outid, err := v.SpentOutputID()
121                         if err != nil {
122                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
123                                 continue
124                         }
125                         utxo, err := w.store.GetStandardUTXO(outid)
126                         if err != nil {
127                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
128                                 continue
129                         }
130                         if utxo != nil {
131                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
132                                 continue transactionLoop
133                         }
134                 }
135         }
136
137         return annotatedTxs
138 }
139
140 // GetTransactionByTxID get transaction by txID
141 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
142         if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
143                 return annotatedTx, nil
144         } else if !w.TxIndexFlag {
145                 return nil, err
146         }
147
148         return w.getGlobalTxByTxID(txID)
149 }
150
151 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
152         annotatedTx, err := w.store.GetTransaction(txID)
153         if err != nil {
154                 return nil, err
155         }
156         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
157         return annotatedTx, nil
158 }
159
160 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
161         globalTxIdx := w.store.GetGlobalTransactionIndex(txID)
162         if globalTxIdx == nil {
163                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
164         }
165
166         blockHash, pos := parseGlobalTxIdx(globalTxIdx)
167         block, err := w.chain.GetBlockByHash(blockHash)
168         if err != nil {
169                 return nil, err
170         }
171
172         txStatus, err := w.chain.GetTransactionStatus(blockHash)
173         if err != nil {
174                 return nil, err
175         }
176
177         statusFail, err := txStatus.GetStatus(int(pos))
178         if err != nil {
179                 return nil, err
180         }
181
182         tx := block.Transactions[int(pos)]
183         return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
184 }
185
186 // GetTransactionsSummary get transactions summary
187 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
188         Txs := []TxSummary{}
189
190         for _, annotatedTx := range transactions {
191                 tmpTxSummary := TxSummary{
192                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
193                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
194                         ID:        annotatedTx.ID,
195                         Timestamp: annotatedTx.Timestamp,
196                 }
197
198                 for i, input := range annotatedTx.Inputs {
199                         tmpTxSummary.Inputs[i].Type = input.Type
200                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
201                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
202                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
203                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
204                         tmpTxSummary.Inputs[i].Amount = input.Amount
205                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
206                 }
207                 for j, output := range annotatedTx.Outputs {
208                         tmpTxSummary.Outputs[j].Type = output.Type
209                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
210                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
211                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
212                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
213                         tmpTxSummary.Outputs[j].Amount = output.Amount
214                 }
215
216                 Txs = append(Txs, tmpTxSummary)
217         }
218
219         return Txs
220 }
221
222 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
223         for _, input := range annotatedTx.Inputs {
224                 if input.AccountID == accountID {
225                         return true
226                 }
227         }
228
229         for _, output := range annotatedTx.Outputs {
230                 if output.AccountID == accountID {
231                         return true
232                 }
233         }
234
235         return false
236 }
237
238 // GetTransactions get all walletDB transactions or unconfirmed transactions, and filter transactions by accountID and StartTxID optional
239 func (w *Wallet) GetTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
240         annotatedTxs := []*query.AnnotatedTx{}
241         annotatedTxs, err := w.store.ListTransactions(accountID, StartTxID, count, unconfirmed)
242         if err != nil {
243                 return nil, err
244         }
245
246         newAnnotatedTxs := []*query.AnnotatedTx{}
247         for _, annotatedTx := range annotatedTxs {
248                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
249                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
250                         newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
251                 }
252         }
253
254         if unconfirmed {
255                 sort.Sort(SortByTimestamp(annotatedTxs))
256         }
257
258         return newAnnotatedTxs, nil
259 }
260
261 // GetAccountBalances return all account balances
262 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
263         return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
264 }
265
266 // AccountBalance account balance
267 type AccountBalance struct {
268         AccountID       string                 `json:"account_id"`
269         Alias           string                 `json:"account_alias"`
270         AssetAlias      string                 `json:"asset_alias"`
271         AssetID         string                 `json:"asset_id"`
272         Amount          uint64                 `json:"amount"`
273         AssetDefinition map[string]interface{} `json:"asset_definition"`
274 }
275
276 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
277         accBalance := make(map[string]map[string]uint64)
278         balances := []AccountBalance{}
279
280         for _, accountUTXO := range accountUTXOs {
281                 assetID := accountUTXO.AssetID.String()
282                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
283                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
284                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
285                         } else {
286                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
287                         }
288                 } else {
289                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
290                 }
291         }
292
293         var sortedAccount []string
294         for k := range accBalance {
295                 sortedAccount = append(sortedAccount, k)
296         }
297         sort.Strings(sortedAccount)
298
299         for _, id := range sortedAccount {
300                 var sortedAsset []string
301                 for k := range accBalance[id] {
302                         sortedAsset = append(sortedAsset, k)
303                 }
304                 sort.Strings(sortedAsset)
305
306                 for _, assetID := range sortedAsset {
307                         alias := w.AccountMgr.GetAliasByID(id)
308                         targetAsset, err := w.AssetReg.GetAsset(assetID)
309                         if err != nil {
310                                 return nil, err
311                         }
312
313                         assetAlias := *targetAsset.Alias
314                         balances = append(balances, AccountBalance{
315                                 Alias:           alias,
316                                 AccountID:       id,
317                                 AssetID:         assetID,
318                                 AssetAlias:      assetAlias,
319                                 Amount:          accBalance[id][assetID],
320                                 AssetDefinition: targetAsset.DefinitionMap,
321                         })
322                 }
323         }
324
325         return balances, nil
326 }
327
328 // GetAccountVotes return all account votes
329 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
330         return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
331 }
332
333 type voteDetail struct {
334         Vote       string `json:"vote"`
335         VoteNumber uint64 `json:"vote_number"`
336 }
337
338 // AccountVotes account vote
339 type AccountVotes struct {
340         AccountID       string       `json:"account_id"`
341         Alias           string       `json:"account_alias"`
342         TotalVoteNumber uint64       `json:"total_vote_number"`
343         VoteDetails     []voteDetail `json:"vote_details"`
344 }
345
346 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
347         accVote := make(map[string]map[string]uint64)
348         votes := []AccountVotes{}
349
350         for _, accountUTXO := range accountUTXOs {
351                 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
352                         continue
353                 }
354                 xpub := hex.EncodeToString(accountUTXO.Vote)
355                 if _, ok := accVote[accountUTXO.AccountID]; ok {
356                         accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
357                 } else {
358                         accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
359
360                 }
361         }
362
363         var sortedAccount []string
364         for k := range accVote {
365                 sortedAccount = append(sortedAccount, k)
366         }
367         sort.Strings(sortedAccount)
368
369         for _, id := range sortedAccount {
370                 var sortedXpub []string
371                 for k := range accVote[id] {
372                         sortedXpub = append(sortedXpub, k)
373                 }
374                 sort.Strings(sortedXpub)
375
376                 voteDetails := []voteDetail{}
377                 voteTotal := uint64(0)
378                 for _, xpub := range sortedXpub {
379                         voteDetails = append(voteDetails, voteDetail{
380                                 Vote:       xpub,
381                                 VoteNumber: accVote[id][xpub],
382                         })
383                         voteTotal += accVote[id][xpub]
384                 }
385                 alias := w.AccountMgr.GetAliasByID(id)
386                 votes = append(votes, AccountVotes{
387                         Alias:           alias,
388                         AccountID:       id,
389                         VoteDetails:     voteDetails,
390                         TotalVoteNumber: voteTotal,
391                 })
392         }
393
394         return votes, nil
395 }