OSDN Git Service

fix(api): delete transactions and rescan blocks after updating account alias (#1466)
[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         "github.com/tendermint/tmlibs/db"
10
11         "github.com/bytom/account"
12         "github.com/bytom/asset"
13         "github.com/bytom/blockchain/query"
14         "github.com/bytom/crypto/sha3pool"
15         chainjson "github.com/bytom/encoding/json"
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 )
26
27 func formatKey(blockHeight uint64, position uint32) string {
28         return fmt.Sprintf("%016x%08x", blockHeight, position)
29 }
30
31 func calcAnnotatedKey(formatKey string) []byte {
32         return []byte(TxPrefix + formatKey)
33 }
34
35 func calcDeleteKey(blockHeight uint64) []byte {
36         return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
37 }
38
39 func calcTxIndexKey(txID string) []byte {
40         return []byte(TxIndexPrefix + txID)
41 }
42
43 // deleteTransaction delete transactions when orphan block rollback
44 func (w *Wallet) deleteTransactions(batch db.Batch, height uint64) {
45         tmpTx := query.AnnotatedTx{}
46         txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
47         defer txIter.Release()
48
49         for txIter.Next() {
50                 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
51                         batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
52                 }
53                 batch.Delete(txIter.Key())
54         }
55 }
56
57 // saveExternalAssetDefinition save external and local assets definition,
58 // when query ,query local first and if have no then query external
59 // details see getAliasDefinition
60 func saveExternalAssetDefinition(b *types.Block, walletDB db.DB) {
61         storeBatch := walletDB.NewBatch()
62         defer storeBatch.Write()
63
64         for _, tx := range b.Transactions {
65                 for _, orig := range tx.Inputs {
66                         if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
67                                 if isValidJSON(ii.AssetDefinition) {
68                                         assetID := ii.AssetID()
69                                         if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
70                                                 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
71                                         }
72                                 }
73                         }
74                 }
75         }
76 }
77
78 // Summary is the struct of transaction's input and output summary
79 type Summary struct {
80         Type         string             `json:"type"`
81         AssetID      bc.AssetID         `json:"asset_id,omitempty"`
82         AssetAlias   string             `json:"asset_alias,omitempty"`
83         Amount       uint64             `json:"amount,omitempty"`
84         AccountID    string             `json:"account_id,omitempty"`
85         AccountAlias string             `json:"account_alias,omitempty"`
86         Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
87 }
88
89 // TxSummary is the struct of transaction summary
90 type TxSummary struct {
91         ID        bc.Hash   `json:"tx_id"`
92         Timestamp uint64    `json:"block_time"`
93         Inputs    []Summary `json:"inputs"`
94         Outputs   []Summary `json:"outputs"`
95 }
96
97 // indexTransactions saves all annotated transactions to the database.
98 func (w *Wallet) indexTransactions(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
99         annotatedTxs := w.filterAccountTxs(b, txStatus)
100         saveExternalAssetDefinition(b, w.DB)
101         annotateTxsAccount(annotatedTxs, w.DB)
102
103         for _, tx := range annotatedTxs {
104                 rawTx, err := json.Marshal(tx)
105                 if err != nil {
106                         log.WithField("err", err).Error("inserting annotated_txs to db")
107                         return err
108                 }
109
110                 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
111                 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
112
113                 // delete unconfirmed transaction
114                 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
115         }
116         return nil
117 }
118
119 // filterAccountTxs related and build the fully annotated transactions.
120 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
121         annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
122
123 transactionLoop:
124         for pos, tx := range b.Transactions {
125                 statusFail, _ := txStatus.GetStatus(pos)
126                 for _, v := range tx.Outputs {
127                         var hash [32]byte
128                         sha3pool.Sum256(hash[:], v.ControlProgram)
129
130                         if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
131                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
132                                 continue transactionLoop
133                         }
134                 }
135
136                 for _, v := range tx.Inputs {
137                         outid, err := v.SpentOutputID()
138                         if err != nil {
139                                 continue
140                         }
141                         if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
142                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
143                                 continue transactionLoop
144                         }
145                 }
146         }
147
148         return annotatedTxs
149 }
150
151 // GetTransactionByTxID get transaction by txID
152 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
153         formatKey := w.DB.Get(calcTxIndexKey(txID))
154         if formatKey == nil {
155                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
156         }
157
158         annotatedTx := &query.AnnotatedTx{}
159         txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
160         if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
161                 return nil, err
162         }
163         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
164
165         return annotatedTx, nil
166 }
167
168 // GetTransactionsSummary get transactions summary
169 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
170         Txs := []TxSummary{}
171
172         for _, annotatedTx := range transactions {
173                 tmpTxSummary := TxSummary{
174                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
175                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
176                         ID:        annotatedTx.ID,
177                         Timestamp: annotatedTx.Timestamp,
178                 }
179
180                 for i, input := range annotatedTx.Inputs {
181                         tmpTxSummary.Inputs[i].Type = input.Type
182                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
183                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
184                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
185                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
186                         tmpTxSummary.Inputs[i].Amount = input.Amount
187                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
188                 }
189                 for j, output := range annotatedTx.Outputs {
190                         tmpTxSummary.Outputs[j].Type = output.Type
191                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
192                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
193                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
194                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
195                         tmpTxSummary.Outputs[j].Amount = output.Amount
196                 }
197
198                 Txs = append(Txs, tmpTxSummary)
199         }
200
201         return Txs
202 }
203
204 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
205         for _, input := range annotatedTx.Inputs {
206                 if input.AccountID == accountID {
207                         return true
208                 }
209         }
210
211         for _, output := range annotatedTx.Outputs {
212                 if output.AccountID == accountID {
213                         return true
214                 }
215         }
216
217         return false
218 }
219
220 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
221 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
222         annotatedTxs := []*query.AnnotatedTx{}
223
224         txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
225         defer txIter.Release()
226         for txIter.Next() {
227                 annotatedTx := &query.AnnotatedTx{}
228                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
229                         return nil, err
230                 }
231
232                 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
233                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
234                         annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
235                 }
236         }
237
238         return annotatedTxs, nil
239 }
240
241 // GetAccountBalances return all account balances
242 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
243         return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
244 }
245
246 // AccountBalance account balance
247 type AccountBalance struct {
248         AccountID       string                 `json:"account_id"`
249         Alias           string                 `json:"account_alias"`
250         AssetAlias      string                 `json:"asset_alias"`
251         AssetID         string                 `json:"asset_id"`
252         Amount          uint64                 `json:"amount"`
253         AssetDefinition map[string]interface{} `json:"asset_definition"`
254 }
255
256 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
257         accBalance := make(map[string]map[string]uint64)
258         balances := []AccountBalance{}
259
260         for _, accountUTXO := range accountUTXOs {
261                 assetID := accountUTXO.AssetID.String()
262                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
263                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
264                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
265                         } else {
266                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
267                         }
268                 } else {
269                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
270                 }
271         }
272
273         var sortedAccount []string
274         for k := range accBalance {
275                 sortedAccount = append(sortedAccount, k)
276         }
277         sort.Strings(sortedAccount)
278
279         for _, id := range sortedAccount {
280                 var sortedAsset []string
281                 for k := range accBalance[id] {
282                         sortedAsset = append(sortedAsset, k)
283                 }
284                 sort.Strings(sortedAsset)
285
286                 for _, assetID := range sortedAsset {
287                         alias := w.AccountMgr.GetAliasByID(id)
288                         targetAsset, err := w.AssetReg.GetAsset(assetID)
289                         if err != nil {
290                                 return nil, err
291                         }
292
293                         assetAlias := *targetAsset.Alias
294                         balances = append(balances, AccountBalance{
295                                 Alias:           alias,
296                                 AccountID:       id,
297                                 AssetID:         assetID,
298                                 AssetAlias:      assetAlias,
299                                 Amount:          accBalance[id][assetID],
300                                 AssetDefinition: targetAsset.DefinitionMap,
301                         })
302                 }
303         }
304
305         return balances, nil
306 }