OSDN Git Service

rename GetGlobalTransactionIndex
[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         store.InitBatch()
33         defer store.CommitBatch()
34
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)
40                                 if err != nil {
41                                         return err
42                                 }
43                                 if assetExist == nil {
44                                         store.SetAssetDefinition(assetID, cci.AssetDefinition)
45                                 }
46                         }
47                 }
48         }
49         return nil
50 }
51
52 // Summary is the struct of transaction's input and output summary
53 type Summary struct {
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"`
61 }
62
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"`
69 }
70
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 {
75                         return err
76                 }
77                 w.store.DeleteUnconfirmedTransaction(tx.ID.String())
78         }
79
80         if !w.TxIndexFlag {
81                 return nil
82         }
83
84         for position, globalTx := range b.Transactions {
85                 blockHash := b.BlockHeader.Hash()
86                 w.store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
87         }
88
89         return nil
90 }
91
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))
95
96 transactionLoop:
97         for pos, tx := range b.Transactions {
98                 statusFail, _ := txStatus.GetStatus(pos)
99                 for _, v := range tx.Outputs {
100                         var hash [32]byte
101                         sha3pool.Sum256(hash[:], v.ControlProgram())
102
103                         cp, err := w.store.GetControlProgram(hash)
104                         if err != nil {
105                                 log.WithFields(log.Fields{"module": logModule, "err": err, "hash": string(hash[:])}).Error("filterAccountTxs fail.")
106                                 continue
107                         }
108                         if cp != nil {
109                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
110                                 continue transactionLoop
111                         }
112                 }
113
114                 for _, v := range tx.Inputs {
115                         outid, err := v.SpentOutputID()
116                         if err != nil {
117                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
118                                 continue
119                         }
120                         utxo, err := w.store.GetStandardUTXO(outid)
121                         if err != nil {
122                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
123                                 continue
124                         }
125                         if utxo != nil {
126                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
127                                 continue transactionLoop
128                         }
129                 }
130         }
131
132         return annotatedTxs
133 }
134
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 {
140                 return nil, err
141         }
142
143         return w.getGlobalTxByTxID(txID)
144 }
145
146 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
147         annotatedTx, err := w.store.GetTransaction(txID)
148         if err != nil {
149                 return nil, err
150         }
151         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
152         return annotatedTx, nil
153 }
154
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)
159         }
160
161         blockHash, pos := parseGlobalTxIdx(globalTxIdx)
162         block, err := w.chain.GetBlockByHash(blockHash)
163         if err != nil {
164                 return nil, err
165         }
166
167         txStatus, err := w.chain.GetTransactionStatus(blockHash)
168         if err != nil {
169                 return nil, err
170         }
171
172         statusFail, err := txStatus.GetStatus(int(pos))
173         if err != nil {
174                 return nil, err
175         }
176
177         tx := block.Transactions[int(pos)]
178         return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
179 }
180
181 // GetTransactionsSummary get transactions summary
182 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
183         Txs := []TxSummary{}
184
185         for _, annotatedTx := range transactions {
186                 tmpTxSummary := TxSummary{
187                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
188                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
189                         ID:        annotatedTx.ID,
190                         Timestamp: annotatedTx.Timestamp,
191                 }
192
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
201                 }
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
209                 }
210
211                 Txs = append(Txs, tmpTxSummary)
212         }
213
214         return Txs
215 }
216
217 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
218         for _, input := range annotatedTx.Inputs {
219                 if input.AccountID == accountID {
220                         return true
221                 }
222         }
223
224         for _, output := range annotatedTx.Outputs {
225                 if output.AccountID == accountID {
226                         return true
227                 }
228         }
229
230         return false
231 }
232
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()
237         if err != nil {
238                 return nil, err
239         }
240
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...)
246                 }
247         }
248
249         return newAnnotatedTxs, nil
250 }
251
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))
255 }
256
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"`
265 }
266
267 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
268         accBalance := make(map[string]map[string]uint64)
269         balances := []AccountBalance{}
270
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
276                         } else {
277                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
278                         }
279                 } else {
280                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
281                 }
282         }
283
284         var sortedAccount []string
285         for k := range accBalance {
286                 sortedAccount = append(sortedAccount, k)
287         }
288         sort.Strings(sortedAccount)
289
290         for _, id := range sortedAccount {
291                 var sortedAsset []string
292                 for k := range accBalance[id] {
293                         sortedAsset = append(sortedAsset, k)
294                 }
295                 sort.Strings(sortedAsset)
296
297                 for _, assetID := range sortedAsset {
298                         alias := w.AccountMgr.GetAliasByID(id)
299                         targetAsset, err := w.AssetReg.GetAsset(assetID)
300                         if err != nil {
301                                 return nil, err
302                         }
303
304                         assetAlias := *targetAsset.Alias
305                         balances = append(balances, AccountBalance{
306                                 Alias:           alias,
307                                 AccountID:       id,
308                                 AssetID:         assetID,
309                                 AssetAlias:      assetAlias,
310                                 Amount:          accBalance[id][assetID],
311                                 AssetDefinition: targetAsset.DefinitionMap,
312                         })
313                 }
314         }
315
316         return balances, nil
317 }
318
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))
322 }
323
324 type voteDetail struct {
325         Vote       string `json:"vote"`
326         VoteNumber uint64 `json:"vote_number"`
327 }
328
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"`
335 }
336
337 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
338         accVote := make(map[string]map[string]uint64)
339         votes := []AccountVotes{}
340
341         for _, accountUTXO := range accountUTXOs {
342                 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
343                         continue
344                 }
345                 xpub := hex.EncodeToString(accountUTXO.Vote)
346                 if _, ok := accVote[accountUTXO.AccountID]; ok {
347                         accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
348                 } else {
349                         accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
350
351                 }
352         }
353
354         var sortedAccount []string
355         for k := range accVote {
356                 sortedAccount = append(sortedAccount, k)
357         }
358         sort.Strings(sortedAccount)
359
360         for _, id := range sortedAccount {
361                 var sortedXpub []string
362                 for k := range accVote[id] {
363                         sortedXpub = append(sortedXpub, k)
364                 }
365                 sort.Strings(sortedXpub)
366
367                 voteDetails := []voteDetail{}
368                 voteTotal := uint64(0)
369                 for _, xpub := range sortedXpub {
370                         voteDetails = append(voteDetails, voteDetail{
371                                 Vote:       xpub,
372                                 VoteNumber: accVote[id][xpub],
373                         })
374                         voteTotal += accVote[id][xpub]
375                 }
376                 alias := w.AccountMgr.GetAliasByID(id)
377                 votes = append(votes, AccountVotes{
378                         Alias:           alias,
379                         AccountID:       id,
380                         VoteDetails:     voteDetails,
381                         TotalVoteNumber: voteTotal,
382                 })
383         }
384
385         return votes, nil
386 }