OSDN Git Service

Wallet store interface (#217)
[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/vapor/account"
12         "github.com/vapor/blockchain/query"
13         "github.com/vapor/consensus"
14         "github.com/vapor/crypto/sha3pool"
15         chainjson "github.com/vapor/encoding/json"
16         "github.com/vapor/protocol/bc"
17         "github.com/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                                 assetID := cci.AssetId
38                                 if _, err := newStore.GetAsset(assetID); err == nil {
39                                         continue
40                                 } else if err != ErrGetAsset {
41                                         return err
42                                 }
43
44                                 newStore.SetAssetDefinition(assetID, cci.AssetDefinition)
45                         }
46                 }
47         }
48         if err := newStore.CommitBatch(); err != nil {
49                 return err
50         }
51
52         return nil
53 }
54
55 // Summary is the struct of transaction's input and output summary
56 type Summary struct {
57         Type         string             `json:"type"`
58         AssetID      bc.AssetID         `json:"asset_id,omitempty"`
59         AssetAlias   string             `json:"asset_alias,omitempty"`
60         Amount       uint64             `json:"amount,omitempty"`
61         AccountID    string             `json:"account_id,omitempty"`
62         AccountAlias string             `json:"account_alias,omitempty"`
63         Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
64 }
65
66 // TxSummary is the struct of transaction summary
67 type TxSummary struct {
68         ID        bc.Hash   `json:"tx_id"`
69         Timestamp uint64    `json:"block_time"`
70         Inputs    []Summary `json:"inputs"`
71         Outputs   []Summary `json:"outputs"`
72 }
73
74 // indexTransactions saves all annotated transactions to the database.
75 func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx, store WalletStore) error {
76         for _, tx := range annotatedTxs {
77                 if err := w.Store.SetTransaction(b.Height, tx); err != nil {
78                         return err
79                 }
80
81                 store.DeleteUnconfirmedTransaction(tx.ID.String())
82         }
83
84         if !w.TxIndexFlag {
85                 return nil
86         }
87
88         for position, globalTx := range b.Transactions {
89                 blockHash := b.BlockHeader.Hash()
90                 store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
91         }
92
93         return nil
94 }
95
96 // filterAccountTxs related and build the fully annotated transactions.
97 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
98         annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
99
100 transactionLoop:
101         for pos, tx := range b.Transactions {
102                 statusFail, _ := txStatus.GetStatus(pos)
103                 for _, v := range tx.Outputs {
104                         var hash [32]byte
105                         sha3pool.Sum256(hash[:], v.ControlProgram())
106                         if _, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash)); err == nil {
107                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
108                                 continue transactionLoop
109                         } else {
110                                 log.WithFields(log.Fields{"module": logModule, "err": err, "hash": hex.EncodeToString(hash[:])}).Info("filterAccountTxs fail.")
111                         }
112                 }
113
114                 for _, v := range tx.Inputs {
115                         outid, err := v.SpentOutputID()
116                         if err != nil {
117                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": hex.EncodeToString(outid.Bytes())}).Info("filterAccountTxs fail.")
118                                 continue
119                         }
120                         if _, err = w.Store.GetStandardUTXO(outid); err == nil {
121                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
122                                 continue transactionLoop
123                         } else {
124                                 log.WithFields(log.Fields{"module": logModule, "err": err, "outputID": hex.EncodeToString(outid.Bytes())}).Info("filterAccountTxs fail.")
125                         }
126                 }
127         }
128
129         return annotatedTxs
130 }
131
132 // GetTransactionByTxID get transaction by txID
133 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
134         if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
135                 return annotatedTx, nil
136         } else if !w.TxIndexFlag {
137                 return nil, err
138         }
139
140         return w.getGlobalTxByTxID(txID)
141 }
142
143 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
144         annotatedTx, err := w.Store.GetTransaction(txID)
145         if err != nil {
146                 return nil, err
147         }
148
149         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
150         return annotatedTx, nil
151 }
152
153 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
154         globalTxIdx := w.Store.GetGlobalTransactionIndex(txID)
155         if globalTxIdx == nil {
156                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
157         }
158
159         blockHash, pos := parseGlobalTxIdx(globalTxIdx)
160         block, err := w.Chain.GetBlockByHash(blockHash)
161         if err != nil {
162                 return nil, err
163         }
164
165         txStatus, err := w.Chain.GetTransactionStatus(blockHash)
166         if err != nil {
167                 return nil, err
168         }
169
170         statusFail, err := txStatus.GetStatus(int(pos))
171         if err != nil {
172                 return nil, err
173         }
174
175         tx := block.Transactions[int(pos)]
176         return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
177 }
178
179 // GetTransactionsSummary get transactions summary
180 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
181         Txs := []TxSummary{}
182
183         for _, annotatedTx := range transactions {
184                 tmpTxSummary := TxSummary{
185                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
186                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
187                         ID:        annotatedTx.ID,
188                         Timestamp: annotatedTx.Timestamp,
189                 }
190
191                 for i, input := range annotatedTx.Inputs {
192                         tmpTxSummary.Inputs[i].Type = input.Type
193                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
194                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
195                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
196                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
197                         tmpTxSummary.Inputs[i].Amount = input.Amount
198                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
199                 }
200                 for j, output := range annotatedTx.Outputs {
201                         tmpTxSummary.Outputs[j].Type = output.Type
202                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
203                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
204                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
205                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
206                         tmpTxSummary.Outputs[j].Amount = output.Amount
207                 }
208
209                 Txs = append(Txs, tmpTxSummary)
210         }
211
212         return Txs
213 }
214
215 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
216         for _, input := range annotatedTx.Inputs {
217                 if input.AccountID == accountID {
218                         return true
219                 }
220         }
221
222         for _, output := range annotatedTx.Outputs {
223                 if output.AccountID == accountID {
224                         return true
225                 }
226         }
227
228         return false
229 }
230
231 // GetTransactions get all walletDB transactions or unconfirmed transactions, and filter transactions by accountID and StartTxID optional
232 func (w *Wallet) GetTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
233         annotatedTxs := []*query.AnnotatedTx{}
234         annotatedTxs, err := w.Store.ListTransactions(accountID, StartTxID, count, unconfirmed)
235         if err != nil {
236                 return nil, err
237         }
238
239         newAnnotatedTxs := []*query.AnnotatedTx{}
240         for _, annotatedTx := range annotatedTxs {
241                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
242                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
243                         newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
244                 }
245         }
246
247         if unconfirmed {
248                 sort.Sort(SortByTimestamp(annotatedTxs))
249         } else {
250                 sort.Sort(SortByHeight(annotatedTxs))
251         }
252
253         return newAnnotatedTxs, nil
254 }
255
256 // GetAccountBalances return all account balances
257 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
258         return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
259 }
260
261 // AccountBalance account balance
262 type AccountBalance struct {
263         AccountID       string                 `json:"account_id"`
264         Alias           string                 `json:"account_alias"`
265         AssetAlias      string                 `json:"asset_alias"`
266         AssetID         string                 `json:"asset_id"`
267         Amount          uint64                 `json:"amount"`
268         AssetDefinition map[string]interface{} `json:"asset_definition"`
269 }
270
271 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
272         accBalance := make(map[string]map[string]uint64)
273         balances := []AccountBalance{}
274
275         for _, accountUTXO := range accountUTXOs {
276                 assetID := accountUTXO.AssetID.String()
277                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
278                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
279                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
280                         } else {
281                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
282                         }
283                 } else {
284                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
285                 }
286         }
287
288         var sortedAccount []string
289         for k := range accBalance {
290                 sortedAccount = append(sortedAccount, k)
291         }
292         sort.Strings(sortedAccount)
293
294         for _, id := range sortedAccount {
295                 var sortedAsset []string
296                 for k := range accBalance[id] {
297                         sortedAsset = append(sortedAsset, k)
298                 }
299                 sort.Strings(sortedAsset)
300
301                 for _, assetID := range sortedAsset {
302                         alias := w.AccountMgr.GetAliasByID(id)
303                         targetAsset, err := w.AssetReg.GetAsset(assetID)
304                         if err != nil {
305                                 return nil, err
306                         }
307
308                         assetAlias := *targetAsset.Alias
309                         balances = append(balances, AccountBalance{
310                                 Alias:           alias,
311                                 AccountID:       id,
312                                 AssetID:         assetID,
313                                 AssetAlias:      assetAlias,
314                                 Amount:          accBalance[id][assetID],
315                                 AssetDefinition: targetAsset.DefinitionMap,
316                         })
317                 }
318         }
319
320         return balances, nil
321 }
322
323 // GetAccountVotes return all account votes
324 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
325         return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
326 }
327
328 type voteDetail struct {
329         Vote       string `json:"vote"`
330         VoteNumber uint64 `json:"vote_number"`
331 }
332
333 // AccountVotes account vote
334 type AccountVotes struct {
335         AccountID       string       `json:"account_id"`
336         Alias           string       `json:"account_alias"`
337         TotalVoteNumber uint64       `json:"total_vote_number"`
338         VoteDetails     []voteDetail `json:"vote_details"`
339 }
340
341 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
342         accVote := make(map[string]map[string]uint64)
343         votes := []AccountVotes{}
344
345         for _, accountUTXO := range accountUTXOs {
346                 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
347                         continue
348                 }
349                 xpub := hex.EncodeToString(accountUTXO.Vote)
350                 if _, ok := accVote[accountUTXO.AccountID]; ok {
351                         accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
352                 } else {
353                         accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
354
355                 }
356         }
357
358         var sortedAccount []string
359         for k := range accVote {
360                 sortedAccount = append(sortedAccount, k)
361         }
362         sort.Strings(sortedAccount)
363
364         for _, id := range sortedAccount {
365                 var sortedXpub []string
366                 for k := range accVote[id] {
367                         sortedXpub = append(sortedXpub, k)
368                 }
369                 sort.Strings(sortedXpub)
370
371                 voteDetails := []voteDetail{}
372                 voteTotal := uint64(0)
373                 for _, xpub := range sortedXpub {
374                         voteDetails = append(voteDetails, voteDetail{
375                                 Vote:       xpub,
376                                 VoteNumber: accVote[id][xpub],
377                         })
378                         voteTotal += accVote[id][xpub]
379                 }
380                 alias := w.AccountMgr.GetAliasByID(id)
381                 votes = append(votes, AccountVotes{
382                         Alias:           alias,
383                         AccountID:       id,
384                         VoteDetails:     voteDetails,
385                         TotalVoteNumber: voteTotal,
386                 })
387         }
388
389         return votes, nil
390 }