OSDN Git Service

edit contract
[bytom/bytom.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/bytom/bytom/account"
13         "github.com/bytom/bytom/asset"
14         "github.com/bytom/bytom/blockchain/query"
15         "github.com/bytom/bytom/consensus"
16         "github.com/bytom/bytom/crypto/sha3pool"
17         dbm "github.com/bytom/bytom/database/leveldb"
18         chainjson "github.com/bytom/bytom/encoding/json"
19         "github.com/bytom/bytom/errors"
20         "github.com/bytom/bytom/protocol/bc"
21         "github.com/bytom/bytom/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 ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
94                                 if isValidJSON(ii.AssetDefinition) {
95                                         assetID := ii.AssetID()
96                                         if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
97                                                 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
98                                         }
99                                 }
100                         }
101                 }
102         }
103 }
104
105 // Summary is the struct of transaction's input and output summary
106 type Summary struct {
107         Type         string             `json:"type"`
108         AssetID      bc.AssetID         `json:"asset_id,omitempty"`
109         AssetAlias   string             `json:"asset_alias,omitempty"`
110         Amount       uint64             `json:"amount,omitempty"`
111         AccountID    string             `json:"account_id,omitempty"`
112         AccountAlias string             `json:"account_alias,omitempty"`
113         Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
114 }
115
116 // TxSummary is the struct of transaction summary
117 type TxSummary struct {
118         ID        bc.Hash   `json:"tx_id"`
119         Timestamp uint64    `json:"block_time"`
120         Inputs    []Summary `json:"inputs"`
121         Outputs   []Summary `json:"outputs"`
122 }
123
124 // indexTransactions saves all annotated transactions to the database.
125 func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block) error {
126         annotatedTxs := w.filterAccountTxs(b)
127         saveExternalAssetDefinition(b, w.DB)
128         annotateTxsAccount(annotatedTxs, w.DB)
129
130         for _, tx := range annotatedTxs {
131                 rawTx, err := json.Marshal(tx)
132                 if err != nil {
133                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
134                         return err
135                 }
136
137                 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
138                 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
139
140                 // delete unconfirmed transaction
141                 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
142         }
143
144         if !w.TxIndexFlag {
145                 return nil
146         }
147
148         for position, globalTx := range b.Transactions {
149                 blockHash := b.BlockHeader.Hash()
150                 batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position)))
151         }
152
153         return nil
154 }
155
156 // filterAccountTxs related and build the fully annotated transactions.
157 func (w *Wallet) filterAccountTxs(b *types.Block) []*query.AnnotatedTx {
158         annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
159
160 transactionLoop:
161         for pos, tx := range b.Transactions {
162                 for _, v := range tx.Outputs {
163                         var hash [32]byte
164                         sha3pool.Sum256(hash[:], v.ControlProgram)
165
166                         if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
167                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, pos))
168                                 continue transactionLoop
169                         }
170                 }
171
172                 for _, v := range tx.Inputs {
173                         outid, err := v.SpentOutputID()
174                         if err != nil {
175                                 continue
176                         }
177                         if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
178                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, pos))
179                                 continue transactionLoop
180                         }
181                 }
182         }
183
184         return annotatedTxs
185 }
186
187 // GetTransactionByTxID get transaction by txID
188 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
189         if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
190                 return annotatedTx, nil
191         } else if !w.TxIndexFlag {
192                 return nil, err
193         }
194
195         return w.getGlobalTxByTxID(txID)
196 }
197
198 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
199         annotatedTx := &query.AnnotatedTx{}
200         formatKey := w.DB.Get(calcTxIndexKey(txID))
201         if formatKey == nil {
202                 return nil, errAccntTxIDNotFound
203         }
204
205         txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
206         if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
207                 return nil, err
208         }
209
210         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
211         return annotatedTx, nil
212 }
213
214 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
215         globalTxIdx := w.DB.Get(calcGlobalTxIndexKey(txID))
216         if globalTxIdx == nil {
217                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
218         }
219
220         blockHash, pos := parseGlobalTxIdx(globalTxIdx)
221         block, err := w.chain.GetBlockByHash(blockHash)
222         if err != nil {
223                 return nil, err
224         }
225
226         tx := block.Transactions[int(pos)]
227         return w.buildAnnotatedTransaction(tx, block, int(pos)), nil
228 }
229
230 // GetTransactionsSummary get transactions summary
231 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
232         Txs := []TxSummary{}
233
234         for _, annotatedTx := range transactions {
235                 tmpTxSummary := TxSummary{
236                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
237                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
238                         ID:        annotatedTx.ID,
239                         Timestamp: annotatedTx.Timestamp,
240                 }
241
242                 for i, input := range annotatedTx.Inputs {
243                         tmpTxSummary.Inputs[i].Type = input.Type
244                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
245                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
246                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
247                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
248                         tmpTxSummary.Inputs[i].Amount = input.Amount
249                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
250                 }
251                 for j, output := range annotatedTx.Outputs {
252                         tmpTxSummary.Outputs[j].Type = output.Type
253                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
254                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
255                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
256                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
257                         tmpTxSummary.Outputs[j].Amount = output.Amount
258                 }
259
260                 Txs = append(Txs, tmpTxSummary)
261         }
262
263         return Txs
264 }
265
266 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
267         for _, input := range annotatedTx.Inputs {
268                 if input.AccountID == accountID {
269                         return true
270                 }
271         }
272
273         for _, output := range annotatedTx.Outputs {
274                 if output.AccountID == accountID {
275                         return true
276                 }
277         }
278
279         return false
280 }
281
282 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
283 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
284         annotatedTxs := []*query.AnnotatedTx{}
285
286         txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
287         defer txIter.Release()
288         for txIter.Next() {
289                 annotatedTx := &query.AnnotatedTx{}
290                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
291                         return nil, err
292                 }
293
294                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
295                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
296                         annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
297                 }
298         }
299
300         return annotatedTxs, nil
301 }
302
303 // GetAccountBalances return all account balances
304 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
305         return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
306 }
307
308 // AccountBalance account balance
309 type AccountBalance struct {
310         AccountID       string                 `json:"account_id"`
311         Alias           string                 `json:"account_alias"`
312         AssetAlias      string                 `json:"asset_alias"`
313         AssetID         string                 `json:"asset_id"`
314         Amount          uint64                 `json:"amount"`
315         AssetDefinition map[string]interface{} `json:"asset_definition"`
316 }
317
318 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
319         accBalance := make(map[string]map[string]uint64)
320         balances := []AccountBalance{}
321
322         for _, accountUTXO := range accountUTXOs {
323                 assetID := accountUTXO.AssetID.String()
324                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
325                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
326                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
327                         } else {
328                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
329                         }
330                 } else {
331                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
332                 }
333         }
334
335         var sortedAccount []string
336         for k := range accBalance {
337                 sortedAccount = append(sortedAccount, k)
338         }
339         sort.Strings(sortedAccount)
340
341         for _, id := range sortedAccount {
342                 var sortedAsset []string
343                 for k := range accBalance[id] {
344                         sortedAsset = append(sortedAsset, k)
345                 }
346                 sort.Strings(sortedAsset)
347
348                 for _, assetID := range sortedAsset {
349                         alias := w.AccountMgr.GetAliasByID(id)
350                         targetAsset, err := w.AssetReg.GetAsset(assetID)
351                         if err != nil {
352                                 return nil, err
353                         }
354
355                         assetAlias := *targetAsset.Alias
356                         balances = append(balances, AccountBalance{
357                                 Alias:           alias,
358                                 AccountID:       id,
359                                 AssetID:         assetID,
360                                 AssetAlias:      assetAlias,
361                                 Amount:          accBalance[id][assetID],
362                                 AssetDefinition: targetAsset.DefinitionMap,
363                         })
364                 }
365         }
366
367         return balances, nil
368 }
369
370 // GetAccountVotes return all account votes
371 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
372         return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
373 }
374
375 type voteDetail struct {
376         Vote       string `json:"vote"`
377         VoteNumber uint64 `json:"vote_number"`
378 }
379
380 // AccountVotes account vote
381 type AccountVotes struct {
382         AccountID       string       `json:"account_id"`
383         Alias           string       `json:"account_alias"`
384         TotalVoteNumber uint64       `json:"total_vote_number"`
385         VoteDetails     []voteDetail `json:"vote_details"`
386 }
387
388 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
389         accVote := make(map[string]map[string]uint64)
390         votes := []AccountVotes{}
391
392         for _, accountUTXO := range accountUTXOs {
393                 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
394                         continue
395                 }
396                 xpub := hex.EncodeToString(accountUTXO.Vote)
397                 if _, ok := accVote[accountUTXO.AccountID]; ok {
398                         accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
399                 } else {
400                         accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
401
402                 }
403         }
404
405         var sortedAccount []string
406         for k := range accVote {
407                 sortedAccount = append(sortedAccount, k)
408         }
409         sort.Strings(sortedAccount)
410
411         for _, id := range sortedAccount {
412                 var sortedXpub []string
413                 for k := range accVote[id] {
414                         sortedXpub = append(sortedXpub, k)
415                 }
416                 sort.Strings(sortedXpub)
417
418                 voteDetails := []voteDetail{}
419                 voteTotal := uint64(0)
420                 for _, xpub := range sortedXpub {
421                         voteDetails = append(voteDetails, voteDetail{
422                                 Vote:       xpub,
423                                 VoteNumber: accVote[id][xpub],
424                         })
425                         voteTotal += accVote[id][xpub]
426                 }
427                 alias := w.AccountMgr.GetAliasByID(id)
428                 votes = append(votes, AccountVotes{
429                         Alias:           alias,
430                         AccountID:       id,
431                         VoteDetails:     voteDetails,
432                         TotalVoteNumber: voteTotal,
433                 })
434         }
435
436         return votes, nil
437 }
438