OSDN Git Service

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