OSDN Git Service

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