OSDN Git Service

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