OSDN Git Service

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