OSDN Git Service

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