OSDN Git Service

update master (#487)
[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/consensus"
15         "github.com/bytom/consensus/segwit"
16         "github.com/bytom/crypto/sha3pool"
17         chainjson "github.com/bytom/encoding/json"
18         "github.com/bytom/errors"
19         "github.com/bytom/protocol/bc"
20         "github.com/bytom/protocol/bc/types"
21 )
22
23 type rawOutput struct {
24         OutputID bc.Hash
25         bc.AssetAmount
26         ControlProgram []byte
27         txHash         bc.Hash
28         outputIndex    uint32
29         sourceID       bc.Hash
30         sourcePos      uint64
31         ValidHeight    uint64
32 }
33
34 type accountOutput struct {
35         rawOutput
36         AccountID string
37         Address   string
38         keyIndex  uint64
39         change    bool
40 }
41
42 const (
43         //TxPrefix is wallet database transactions prefix
44         TxPrefix = "TXS:"
45         //TxIndexPrefix is wallet database tx index prefix
46         TxIndexPrefix = "TID:"
47 )
48
49 func formatKey(blockHeight uint64, position uint32) string {
50         return fmt.Sprintf("%016x%08x", blockHeight, position)
51 }
52
53 func calcAnnotatedKey(formatKey string) []byte {
54         return []byte(TxPrefix + formatKey)
55 }
56
57 func calcDeleteKey(blockHeight uint64) []byte {
58         return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
59 }
60
61 func calcTxIndexKey(txID string) []byte {
62         return []byte(TxIndexPrefix + txID)
63 }
64
65 // deleteTransaction delete transactions when orphan block rollback
66 func (w *Wallet) deleteTransactions(batch db.Batch, height uint64) {
67         tmpTx := query.AnnotatedTx{}
68
69         txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
70         defer txIter.Release()
71
72         for txIter.Next() {
73                 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
74                         // delete index
75                         batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
76                 }
77
78                 batch.Delete(txIter.Key())
79         }
80 }
81
82 // ReverseAccountUTXOs process the invalid blocks when orphan block rollback
83 func (w *Wallet) reverseAccountUTXOs(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
84         var err error
85
86         // unknow how many spent and retire outputs
87         reverseOuts := []*rawOutput{}
88
89         // handle spent UTXOs
90         for txIndex, tx := range b.Transactions {
91                 for _, inpID := range tx.Tx.InputIDs {
92                         // spend and retire
93                         sp, err := tx.Spend(inpID)
94                         if err != nil {
95                                 continue
96                         }
97
98                         resOut, ok := tx.Entries[*sp.SpentOutputId].(*bc.Output)
99                         if !ok {
100                                 continue
101                         }
102
103                         statusFail, _ := txStatus.GetStatus(txIndex)
104                         if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
105                                 continue
106                         }
107
108                         out := &rawOutput{
109                                 OutputID:       *sp.SpentOutputId,
110                                 AssetAmount:    *resOut.Source.Value,
111                                 ControlProgram: resOut.ControlProgram.Code,
112                                 txHash:         tx.ID,
113                                 sourceID:       *resOut.Source.Ref,
114                                 sourcePos:      resOut.Source.Position,
115                         }
116                         reverseOuts = append(reverseOuts, out)
117                 }
118         }
119
120         accOuts := loadAccountInfo(reverseOuts, w)
121         if err = upsertConfirmedAccountOutputs(accOuts, batch); err != nil {
122                 log.WithField("err", err).Error("reversing account spent and retire outputs")
123                 return
124         }
125
126         // handle new UTXOs
127         for _, tx := range b.Transactions {
128                 for j := range tx.Outputs {
129                         resOutID := tx.ResultIds[j]
130                         resOut, ok := tx.Entries[*resOutID].(*bc.Output)
131                         if !ok {
132                                 // retirement
133                                 continue
134                         }
135
136                         if segwit.IsP2WScript(resOut.ControlProgram.Code) {
137                                 // delete standard UTXOs
138                                 batch.Delete(account.StandardUTXOKey(*resOutID))
139                         } else {
140                                 // delete contract UTXOs
141                                 batch.Delete(account.ContractUTXOKey(*resOutID))
142                         }
143                 }
144         }
145 }
146
147 // saveExternalAssetDefinition save external and local assets definition,
148 // when query ,query local first and if have no then query external
149 // details see getAliasDefinition
150 func saveExternalAssetDefinition(b *types.Block, walletDB db.DB) {
151         storeBatch := walletDB.NewBatch()
152         defer storeBatch.Write()
153
154         for _, tx := range b.Transactions {
155                 for _, orig := range tx.Inputs {
156                         if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
157                                 if isValidJSON(ii.AssetDefinition) {
158                                         assetID := ii.AssetID()
159                                         if assetExist := walletDB.Get(asset.CalcExtAssetKey(&assetID)); assetExist != nil {
160                                                 continue
161                                         }
162                                         storeBatch.Set(asset.CalcExtAssetKey(&assetID), ii.AssetDefinition)
163                                 }
164                         }
165                 }
166         }
167 }
168
169 // Summary is the struct of transaction's input and output summary
170 type Summary struct {
171         Type         string             `json:"type"`
172         AssetID      bc.AssetID         `json:"asset_id,omitempty"`
173         AssetAlias   string             `json:"asset_alias,omitempty"`
174         Amount       uint64             `json:"amount,omitempty"`
175         AccountID    string             `json:"account_id,omitempty"`
176         AccountAlias string             `json:"account_alias,omitempty"`
177         Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
178 }
179
180 // TxSummary is the struct of transaction summary
181 type TxSummary struct {
182         ID        bc.Hash   `json:"tx_id"`
183         Timestamp uint64    `json:"block_time"`
184         Inputs    []Summary `json:"inputs"`
185         Outputs   []Summary `json:"outputs"`
186 }
187
188 // indexTransactions saves all annotated transactions to the database.
189 func (w *Wallet) indexTransactions(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
190         annotatedTxs := w.filterAccountTxs(b, txStatus)
191         saveExternalAssetDefinition(b, w.DB)
192         annotateTxsAsset(w, annotatedTxs)
193         annotateTxsAccount(annotatedTxs, w.DB)
194
195         for _, tx := range annotatedTxs {
196                 rawTx, err := json.Marshal(tx)
197                 if err != nil {
198                         log.WithField("err", err).Error("inserting annotated_txs to db")
199                         return err
200                 }
201
202                 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
203                 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
204         }
205         return nil
206 }
207
208 // buildAccountUTXOs process valid blocks to build account unspent outputs db
209 func (w *Wallet) buildAccountUTXOs(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
210         // get the spent UTXOs and delete the UTXOs from DB
211         prevoutDBKeys(batch, b, txStatus)
212
213         // handle new UTXOs
214         outs := make([]*rawOutput, 0, len(b.Transactions))
215         for txIndex, tx := range b.Transactions {
216                 for j, out := range tx.Outputs {
217                         resOutID := tx.ResultIds[j]
218                         resOut, ok := tx.Entries[*resOutID].(*bc.Output)
219                         if !ok {
220                                 continue
221                         }
222                         statusFail, _ := txStatus.GetStatus(txIndex)
223                         if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
224                                 continue
225                         }
226                         out := &rawOutput{
227                                 OutputID:       *tx.OutputID(j),
228                                 AssetAmount:    out.AssetAmount,
229                                 ControlProgram: out.ControlProgram,
230                                 txHash:         tx.ID,
231                                 outputIndex:    uint32(j),
232                                 sourceID:       *resOut.Source.Ref,
233                                 sourcePos:      resOut.Source.Position,
234                         }
235
236                         // coinbase utxo valid height
237                         if txIndex == 0 {
238                                 out.ValidHeight = b.Height + consensus.CoinbasePendingBlockNumber
239                         }
240                         outs = append(outs, out)
241                 }
242         }
243         accOuts := loadAccountInfo(outs, w)
244
245         if err := upsertConfirmedAccountOutputs(accOuts, batch); err != nil {
246                 log.WithField("err", err).Error("building new account outputs")
247                 return
248         }
249 }
250
251 func prevoutDBKeys(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
252         for txIndex, tx := range b.Transactions {
253                 for _, inpID := range tx.Tx.InputIDs {
254                         sp, err := tx.Spend(inpID)
255                         if err != nil {
256                                 continue
257                         }
258
259                         statusFail, _ := txStatus.GetStatus(txIndex)
260                         if statusFail && *sp.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
261                                 continue
262                         }
263
264                         resOut, ok := tx.Entries[*sp.SpentOutputId].(*bc.Output)
265                         if !ok {
266                                 // retirement
267                                 log.WithField("SpentOutputId", *sp.SpentOutputId).Info("the OutputId is retirement")
268                                 continue
269                         }
270
271                         if segwit.IsP2WScript(resOut.ControlProgram.Code) {
272                                 // delete standard UTXOs
273                                 batch.Delete(account.StandardUTXOKey(*sp.SpentOutputId))
274                         } else {
275                                 // delete contract UTXOs
276                                 batch.Delete(account.ContractUTXOKey(*sp.SpentOutputId))
277                         }
278                 }
279         }
280         return
281 }
282
283 // loadAccountInfo turns a set of output IDs into a set of
284 // outputs by adding account annotations.  Outputs that can't be
285 // annotated are excluded from the result.
286 func loadAccountInfo(outs []*rawOutput, w *Wallet) []*accountOutput {
287         outsByScript := make(map[string][]*rawOutput, len(outs))
288         for _, out := range outs {
289                 scriptStr := string(out.ControlProgram)
290                 outsByScript[scriptStr] = append(outsByScript[scriptStr], out)
291         }
292
293         result := make([]*accountOutput, 0, len(outs))
294         cp := account.CtrlProgram{}
295
296         var hash [32]byte
297         for s := range outsByScript {
298                 // smart contract UTXO
299                 if !segwit.IsP2WScript([]byte(s)) {
300                         for _, out := range outsByScript[s] {
301                                 newOut := &accountOutput{
302                                         rawOutput: *out,
303                                         change:    false,
304                                 }
305                                 result = append(result, newOut)
306                         }
307
308                         continue
309                 }
310
311                 sha3pool.Sum256(hash[:], []byte(s))
312                 bytes := w.DB.Get(account.CPKey(hash))
313                 if bytes == nil {
314                         continue
315                 }
316
317                 err := json.Unmarshal(bytes, &cp)
318                 if err != nil {
319                         continue
320                 }
321
322                 isExist := w.DB.Get(account.Key(cp.AccountID))
323                 if isExist == nil {
324                         continue
325                 }
326
327                 for _, out := range outsByScript[s] {
328                         newOut := &accountOutput{
329                                 rawOutput: *out,
330                                 AccountID: cp.AccountID,
331                                 Address:   cp.Address,
332                                 keyIndex:  cp.KeyIndex,
333                                 change:    cp.Change,
334                         }
335                         result = append(result, newOut)
336                 }
337         }
338
339         return result
340 }
341
342 // upsertConfirmedAccountOutputs records the account data for confirmed utxos.
343 // If the account utxo already exists (because it's from a local tx), the
344 // block confirmation data will in the row will be updated.
345 func upsertConfirmedAccountOutputs(outs []*accountOutput, batch db.Batch) error {
346         var u *account.UTXO
347
348         for _, out := range outs {
349                 u = &account.UTXO{
350                         OutputID:            out.OutputID,
351                         SourceID:            out.sourceID,
352                         AssetID:             *out.AssetId,
353                         Amount:              out.Amount,
354                         SourcePos:           out.sourcePos,
355                         ControlProgram:      out.ControlProgram,
356                         ControlProgramIndex: out.keyIndex,
357                         AccountID:           out.AccountID,
358                         Address:             out.Address,
359                         ValidHeight:         out.ValidHeight,
360                 }
361
362                 data, err := json.Marshal(u)
363                 if err != nil {
364                         return errors.Wrap(err, "failed marshal accountutxo")
365                 }
366
367                 if segwit.IsP2WScript(out.ControlProgram) {
368                         // standard UTXOs
369                         batch.Set(account.StandardUTXOKey(out.OutputID), data)
370                 } else {
371                         // contract UTXOs
372                         batch.Set(account.ContractUTXOKey(out.OutputID), data)
373                 }
374
375         }
376         return nil
377 }
378
379 // filterAccountTxs related and build the fully annotated transactions.
380 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
381         annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
382         for pos, tx := range b.Transactions {
383                 statusFail, _ := txStatus.GetStatus(pos)
384                 local := false
385                 for _, v := range tx.Outputs {
386                         var hash [32]byte
387
388                         sha3pool.Sum256(hash[:], v.ControlProgram)
389                         if bytes := w.DB.Get(account.CPKey(hash)); bytes != nil {
390                                 annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, statusFail, pos))
391                                 local = true
392                                 break
393                         }
394                 }
395
396                 if local == true {
397                         continue
398                 }
399
400                 for _, v := range tx.Inputs {
401                         outid, err := v.SpentOutputID()
402                         if err != nil {
403                                 continue
404                         }
405                         if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
406                                 annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, statusFail, pos))
407                                 break
408                         }
409                 }
410         }
411
412         return annotatedTxs
413 }
414
415 // GetTransactionByTxID get transaction by txID
416 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
417         formatKey := w.DB.Get(calcTxIndexKey(txID))
418         if formatKey == nil {
419                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
420         }
421
422         annotatedTx := &query.AnnotatedTx{}
423         txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
424         if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
425                 return nil, err
426         }
427
428         return annotatedTx, nil
429 }
430
431 // GetTransactionsByTxID get account txs by account tx ID
432 func (w *Wallet) GetTransactionsByTxID(txID string) ([]*query.AnnotatedTx, error) {
433         annotatedTxs := []*query.AnnotatedTx{}
434         formatKey := ""
435
436         if txID != "" {
437                 rawFormatKey := w.DB.Get(calcTxIndexKey(txID))
438                 if rawFormatKey == nil {
439                         return nil, fmt.Errorf("No transaction(txid=%s) ", txID)
440                 }
441                 formatKey = string(rawFormatKey)
442         }
443
444         txIter := w.DB.IteratorPrefix([]byte(TxPrefix + formatKey))
445         defer txIter.Release()
446         for txIter.Next() {
447                 annotatedTx := &query.AnnotatedTx{}
448                 if err := json.Unmarshal(txIter.Value(), annotatedTx); err != nil {
449                         return nil, err
450                 }
451                 annotatedTxs = append(annotatedTxs, annotatedTx)
452         }
453
454         return annotatedTxs, nil
455 }
456
457 // GetTransactionsSummary get transactions summary
458 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
459         Txs := []TxSummary{}
460
461         for _, annotatedTx := range transactions {
462                 tmpTxSummary := TxSummary{
463                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
464                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
465                         ID:        annotatedTx.ID,
466                         Timestamp: annotatedTx.Timestamp,
467                 }
468
469                 for i, input := range annotatedTx.Inputs {
470                         tmpTxSummary.Inputs[i].Type = input.Type
471                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
472                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
473                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
474                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
475                         tmpTxSummary.Inputs[i].Amount = input.Amount
476                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
477                 }
478                 for j, output := range annotatedTx.Outputs {
479                         tmpTxSummary.Outputs[j].Type = output.Type
480                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
481                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
482                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
483                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
484                         tmpTxSummary.Outputs[j].Amount = output.Amount
485                 }
486
487                 Txs = append(Txs, tmpTxSummary)
488         }
489
490         return Txs
491 }
492
493 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
494         for _, input := range annotatedTx.Inputs {
495                 if input.AccountID == accountID {
496                         return true
497                 }
498         }
499
500         for _, output := range annotatedTx.Outputs {
501                 if output.AccountID == accountID {
502                         return true
503                 }
504         }
505
506         return false
507 }
508
509 // GetTransactionsByAccountID get account txs by account ID
510 func (w *Wallet) GetTransactionsByAccountID(accountID string) ([]*query.AnnotatedTx, error) {
511         annotatedTxs := []*query.AnnotatedTx{}
512
513         txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
514         defer txIter.Release()
515         for txIter.Next() {
516                 annotatedTx := &query.AnnotatedTx{}
517                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
518                         return nil, err
519                 }
520
521                 if findTransactionsByAccount(annotatedTx, accountID) {
522                         annotatedTxs = append(annotatedTxs, annotatedTx)
523                 }
524         }
525
526         return annotatedTxs, nil
527 }
528
529 // GetAccountUTXOs return all account unspent outputs
530 func (w *Wallet) GetAccountUTXOs(id string) ([]account.UTXO, error) {
531         accountUTXO := account.UTXO{}
532         accountUTXOs := []account.UTXO{}
533
534         accountUTXOIter := w.DB.IteratorPrefix([]byte(account.UTXOPreFix + id))
535         defer accountUTXOIter.Release()
536         for accountUTXOIter.Next() {
537                 if err := json.Unmarshal(accountUTXOIter.Value(), &accountUTXO); err != nil {
538                         hashKey := accountUTXOIter.Key()[len(account.UTXOPreFix):]
539                         log.WithField("UTXO hash", string(hashKey)).Warn("get account UTXO")
540                         continue
541                 }
542
543                 accountUTXOs = append(accountUTXOs, accountUTXO)
544         }
545
546         return accountUTXOs, nil
547 }
548
549 func (w *Wallet) GetAccountBalances(id string) ([]accountBalance, error) {
550         accountUTXOs, err := w.GetAccountUTXOs("")
551         if err != nil {
552                 return nil, err
553         }
554
555         return w.indexBalances(accountUTXOs), nil
556 }
557
558 type accountBalance struct {
559         AccountID  string `json:"account_id"`
560         Alias      string `json:"account_alias"`
561         AssetAlias string `json:"asset_alias"`
562         AssetID    string `json:"asset_id"`
563         Amount     uint64 `json:"amount"`
564 }
565
566 func (w *Wallet) indexBalances(accountUTXOs []account.UTXO) []accountBalance {
567         accBalance := make(map[string]map[string]uint64)
568         balances := make([]accountBalance, 0)
569         tmpBalance := accountBalance{}
570
571         for _, accountUTXO := range accountUTXOs {
572                 assetID := accountUTXO.AssetID.String()
573                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
574                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
575                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
576                         } else {
577                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
578                         }
579                 } else {
580                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
581                 }
582         }
583
584         var sortedAccount []string
585         for k := range accBalance {
586                 sortedAccount = append(sortedAccount, k)
587         }
588         sort.Strings(sortedAccount)
589
590         for _, id := range sortedAccount {
591                 var sortedAsset []string
592                 for k := range accBalance[id] {
593                         sortedAsset = append(sortedAsset, k)
594                 }
595                 sort.Strings(sortedAsset)
596
597                 for _, assetID := range sortedAsset {
598                         alias := w.AccountMgr.GetAliasByID(id)
599                         assetAlias := w.AssetReg.GetAliasByID(assetID)
600                         tmpBalance.Alias = alias
601                         tmpBalance.AccountID = id
602                         tmpBalance.AssetID = assetID
603                         tmpBalance.AssetAlias = assetAlias
604                         tmpBalance.Amount = accBalance[id][assetID]
605                         balances = append(balances, tmpBalance)
606                 }
607         }
608
609         return balances
610 }