OSDN Git Service

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