OSDN Git Service

versoin1.1.9 (#594)
[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/bytom/vapor/account"
12         "github.com/bytom/vapor/blockchain/query"
13         "github.com/bytom/vapor/consensus"
14         "github.com/bytom/vapor/crypto/sha3pool"
15         chainjson "github.com/bytom/vapor/encoding/json"
16         "github.com/bytom/vapor/protocol/bc"
17         "github.com/bytom/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 WalletStore) error {
32         newStore := store.InitBatch()
33
34         for _, tx := range b.Transactions {
35                 for _, orig := range tx.Inputs {
36                         if cci, ok := orig.TypedInput.(*types.CrossChainInput); ok {
37                                 if cci.AssetId.String() == consensus.BTMAssetID.String() {
38                                         continue
39                                 }
40                                 assetID := cci.AssetId
41                                 if _, err := newStore.GetAsset(assetID); err == nil {
42                                         continue
43                                 } else if err != ErrGetAsset {
44                                         return err
45                                 }
46
47                                 newStore.SetAssetDefinition(assetID, cci.AssetDefinition)
48                         }
49                 }
50         }
51         if err := newStore.CommitBatch(); err != nil {
52                 return err
53         }
54
55         return nil
56 }
57
58 // Summary is the struct of transaction's input and output summary
59 type Summary struct {
60         Type         string             `json:"type"`
61         AssetID      bc.AssetID         `json:"asset_id,omitempty"`
62         AssetAlias   string             `json:"asset_alias,omitempty"`
63         Amount       uint64             `json:"amount,omitempty"`
64         AccountID    string             `json:"account_id,omitempty"`
65         AccountAlias string             `json:"account_alias,omitempty"`
66         Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
67 }
68
69 // TxSummary is the struct of transaction summary
70 type TxSummary struct {
71         ID        bc.Hash   `json:"tx_id"`
72         Timestamp uint64    `json:"block_time"`
73         Inputs    []Summary `json:"inputs"`
74         Outputs   []Summary `json:"outputs"`
75 }
76
77 // indexTransactions saves all annotated transactions to the database.
78 func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx, store WalletStore) error {
79         for _, tx := range annotatedTxs {
80                 if err := w.Store.SetTransaction(b.Height, tx); err != nil {
81                         return err
82                 }
83
84                 store.DeleteUnconfirmedTransaction(tx.ID.String())
85         }
86
87         if !w.TxIndexFlag {
88                 return nil
89         }
90
91         for position, globalTx := range b.Transactions {
92                 blockHash := b.BlockHeader.Hash()
93                 store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
94         }
95
96         return nil
97 }
98
99 // filterAccountTxs related and build the fully annotated transactions.
100 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
101         annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
102
103 transactionLoop:
104         for pos, tx := range b.Transactions {
105                 statusFail, _ := txStatus.GetStatus(pos)
106                 for _, v := range tx.Outputs {
107                         var hash [32]byte
108                         sha3pool.Sum256(hash[:], v.ControlProgram())
109                         if _, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash)); err == nil {
110                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
111                                 continue transactionLoop
112                         } else if err != account.ErrFindCtrlProgram {
113                                 log.WithFields(log.Fields{"module": logModule, "err": err, "hash": hex.EncodeToString(hash[:])}).Error("filterAccountTxs fail.")
114                         }
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                         if _, err = w.Store.GetStandardUTXO(outid); err == nil {
124                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
125                                 continue transactionLoop
126                         } else if err != ErrGetStandardUTXO {
127                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": outid.String()}).Error("filterAccountTxs fail.")
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
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                 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
245                 newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
246         }
247
248         if unconfirmed {
249                 sort.Sort(SortByTimestamp(newAnnotatedTxs))
250         } else {
251                 sort.Sort(SortByHeight(newAnnotatedTxs))
252         }
253
254         return newAnnotatedTxs, nil
255 }
256
257 // GetAccountBalances return all account balances
258 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
259         return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
260 }
261
262 // AccountBalance account balance
263 type AccountBalance struct {
264         AccountID       string                 `json:"account_id"`
265         Alias           string                 `json:"account_alias"`
266         AssetAlias      string                 `json:"asset_alias"`
267         AssetID         string                 `json:"asset_id"`
268         Amount          uint64                 `json:"amount"`
269         AssetDefinition map[string]interface{} `json:"asset_definition"`
270 }
271
272 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
273         accBalance := make(map[string]map[string]uint64)
274         balances := []AccountBalance{}
275
276         for _, accountUTXO := range accountUTXOs {
277                 assetID := accountUTXO.AssetID.String()
278                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
279                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
280                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
281                         } else {
282                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
283                         }
284                 } else {
285                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
286                 }
287         }
288
289         var sortedAccount []string
290         for k := range accBalance {
291                 sortedAccount = append(sortedAccount, k)
292         }
293         sort.Strings(sortedAccount)
294
295         for _, id := range sortedAccount {
296                 var sortedAsset []string
297                 for k := range accBalance[id] {
298                         sortedAsset = append(sortedAsset, k)
299                 }
300                 sort.Strings(sortedAsset)
301
302                 for _, assetID := range sortedAsset {
303                         alias := w.AccountMgr.GetAliasByID(id)
304                         targetAsset, err := w.AssetReg.GetAsset(assetID)
305                         if err != nil {
306                                 return nil, err
307                         }
308
309                         assetAlias := *targetAsset.Alias
310                         balances = append(balances, AccountBalance{
311                                 Alias:           alias,
312                                 AccountID:       id,
313                                 AssetID:         assetID,
314                                 AssetAlias:      assetAlias,
315                                 Amount:          accBalance[id][assetID],
316                                 AssetDefinition: targetAsset.DefinitionMap,
317                         })
318                 }
319         }
320
321         return balances, nil
322 }
323
324 // GetAccountVotes return all account votes
325 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
326         return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
327 }
328
329 type voteDetail struct {
330         Vote       string `json:"vote"`
331         VoteNumber uint64 `json:"vote_number"`
332 }
333
334 // AccountVotes account vote
335 type AccountVotes struct {
336         AccountID       string       `json:"account_id"`
337         Alias           string       `json:"account_alias"`
338         TotalVoteNumber uint64       `json:"total_vote_number"`
339         VoteDetails     []voteDetail `json:"vote_details"`
340 }
341
342 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
343         accVote := make(map[string]map[string]uint64)
344         votes := []AccountVotes{}
345
346         for _, accountUTXO := range accountUTXOs {
347                 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
348                         continue
349                 }
350                 xpub := hex.EncodeToString(accountUTXO.Vote)
351                 if _, ok := accVote[accountUTXO.AccountID]; ok {
352                         accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
353                 } else {
354                         accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
355
356                 }
357         }
358
359         var sortedAccount []string
360         for k := range accVote {
361                 sortedAccount = append(sortedAccount, k)
362         }
363         sort.Strings(sortedAccount)
364
365         for _, id := range sortedAccount {
366                 var sortedXpub []string
367                 for k := range accVote[id] {
368                         sortedXpub = append(sortedXpub, k)
369                 }
370                 sort.Strings(sortedXpub)
371
372                 voteDetails := []voteDetail{}
373                 voteTotal := uint64(0)
374                 for _, xpub := range sortedXpub {
375                         voteDetails = append(voteDetails, voteDetail{
376                                 Vote:       xpub,
377                                 VoteNumber: accVote[id][xpub],
378                         })
379                         voteTotal += accVote[id][xpub]
380                 }
381                 alias := w.AccountMgr.GetAliasByID(id)
382                 votes = append(votes, AccountVotes{
383                         Alias:           alias,
384                         AccountID:       id,
385                         VoteDetails:     voteDetails,
386                         TotalVoteNumber: voteTotal,
387                 })
388         }
389
390         return votes, nil
391 }