OSDN Git Service

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