OSDN Git Service

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