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