OSDN Git Service

add get-consensus-nodes api (#159)
[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, and filter transactions by accountID optional
292 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
293         annotatedTxs := []*query.AnnotatedTx{}
294
295         txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
296         defer txIter.Release()
297         for txIter.Next() {
298                 annotatedTx := &query.AnnotatedTx{}
299                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
300                         return nil, err
301                 }
302
303                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
304                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
305                         annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
306                 }
307         }
308
309         return annotatedTxs, nil
310 }
311
312 // GetAccountBalances return all account balances
313 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
314         return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
315 }
316
317 // AccountBalance account balance
318 type AccountBalance struct {
319         AccountID       string                 `json:"account_id"`
320         Alias           string                 `json:"account_alias"`
321         AssetAlias      string                 `json:"asset_alias"`
322         AssetID         string                 `json:"asset_id"`
323         Amount          uint64                 `json:"amount"`
324         AssetDefinition map[string]interface{} `json:"asset_definition"`
325 }
326
327 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
328         accBalance := make(map[string]map[string]uint64)
329         balances := []AccountBalance{}
330
331         for _, accountUTXO := range accountUTXOs {
332                 assetID := accountUTXO.AssetID.String()
333                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
334                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
335                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
336                         } else {
337                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
338                         }
339                 } else {
340                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
341                 }
342         }
343
344         var sortedAccount []string
345         for k := range accBalance {
346                 sortedAccount = append(sortedAccount, k)
347         }
348         sort.Strings(sortedAccount)
349
350         for _, id := range sortedAccount {
351                 var sortedAsset []string
352                 for k := range accBalance[id] {
353                         sortedAsset = append(sortedAsset, k)
354                 }
355                 sort.Strings(sortedAsset)
356
357                 for _, assetID := range sortedAsset {
358                         alias := w.AccountMgr.GetAliasByID(id)
359                         targetAsset, err := w.AssetReg.GetAsset(assetID)
360                         if err != nil {
361                                 return nil, err
362                         }
363
364                         assetAlias := *targetAsset.Alias
365                         balances = append(balances, AccountBalance{
366                                 Alias:           alias,
367                                 AccountID:       id,
368                                 AssetID:         assetID,
369                                 AssetAlias:      assetAlias,
370                                 Amount:          accBalance[id][assetID],
371                                 AssetDefinition: targetAsset.DefinitionMap,
372                         })
373                 }
374         }
375
376         return balances, nil
377 }
378
379 // GetAccountVotes return all account votes
380 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
381         return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
382 }
383
384 type voteDetail struct {
385         Vote       string `json:"vote"`
386         VoteNumber uint64 `json:"vote_number"`
387 }
388
389 // AccountVotes account vote
390 type AccountVotes struct {
391         AccountID       string       `json:"account_id"`
392         Alias           string       `json:"account_alias"`
393         TotalVoteNumber uint64       `json:"total_vote_number"`
394         VoteDetails     []voteDetail `json:"vote_details"`
395 }
396
397 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
398         accVote := make(map[string]map[string]uint64)
399         votes := []AccountVotes{}
400
401         for _, accountUTXO := range accountUTXOs {
402                 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
403                         continue
404                 }
405                 xpub := hex.EncodeToString(accountUTXO.Vote)
406                 if _, ok := accVote[accountUTXO.AccountID]; ok {
407                         accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
408                 } else {
409                         accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
410
411                 }
412         }
413
414         var sortedAccount []string
415         for k := range accVote {
416                 sortedAccount = append(sortedAccount, k)
417         }
418         sort.Strings(sortedAccount)
419
420         for _, id := range sortedAccount {
421                 var sortedXpub []string
422                 for k := range accVote[id] {
423                         sortedXpub = append(sortedXpub, k)
424                 }
425                 sort.Strings(sortedXpub)
426
427                 voteDetails := []voteDetail{}
428                 voteTotal := uint64(0)
429                 for _, xpub := range sortedXpub {
430                         voteDetails = append(voteDetails, voteDetail{
431                                 Vote:       xpub,
432                                 VoteNumber: accVote[id][xpub],
433                         })
434                         voteTotal += accVote[id][xpub]
435                 }
436                 alias := w.AccountMgr.GetAliasByID(id)
437                 votes = append(votes, AccountVotes{
438                         Alias:           alias,
439                         AccountID:       id,
440                         VoteDetails:     voteDetails,
441                         TotalVoteNumber: voteTotal,
442                 })
443         }
444
445         return votes, nil
446 }