OSDN Git Service

Merge branch 'dev' into docker
[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.ExtAssetKey(&assetID)); assetExist == nil {
160                                                 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
161                                         }
162                                 }
163                         }
164                 }
165         }
166 }
167
168 // Summary is the struct of transaction's input and output summary
169 type Summary struct {
170         Type         string             `json:"type"`
171         AssetID      bc.AssetID         `json:"asset_id,omitempty"`
172         AssetAlias   string             `json:"asset_alias,omitempty"`
173         Amount       uint64             `json:"amount,omitempty"`
174         AccountID    string             `json:"account_id,omitempty"`
175         AccountAlias string             `json:"account_alias,omitempty"`
176         Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
177 }
178
179 // TxSummary is the struct of transaction summary
180 type TxSummary struct {
181         ID        bc.Hash   `json:"tx_id"`
182         Timestamp uint64    `json:"block_time"`
183         Inputs    []Summary `json:"inputs"`
184         Outputs   []Summary `json:"outputs"`
185 }
186
187 // indexTransactions saves all annotated transactions to the database.
188 func (w *Wallet) indexTransactions(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
189         annotatedTxs := w.filterAccountTxs(b, txStatus)
190         saveExternalAssetDefinition(b, w.DB)
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                                         change:    false,
302                                 }
303                                 result = append(result, newOut)
304                         }
305
306                         continue
307                 }
308
309                 sha3pool.Sum256(hash[:], []byte(s))
310                 bytes := w.DB.Get(account.ContractKey(hash))
311                 if bytes == nil {
312                         continue
313                 }
314
315                 err := json.Unmarshal(bytes, &cp)
316                 if err != nil {
317                         continue
318                 }
319
320                 isExist := w.DB.Get(account.Key(cp.AccountID))
321                 if isExist == nil {
322                         continue
323                 }
324
325                 for _, out := range outsByScript[s] {
326                         newOut := &accountOutput{
327                                 rawOutput: *out,
328                                 AccountID: cp.AccountID,
329                                 Address:   cp.Address,
330                                 keyIndex:  cp.KeyIndex,
331                                 change:    cp.Change,
332                         }
333                         result = append(result, newOut)
334                 }
335         }
336
337         return result
338 }
339
340 // upsertConfirmedAccountOutputs records the account data for confirmed utxos.
341 // If the account utxo already exists (because it's from a local tx), the
342 // block confirmation data will in the row will be updated.
343 func upsertConfirmedAccountOutputs(outs []*accountOutput, batch db.Batch) error {
344         var u *account.UTXO
345
346         for _, out := range outs {
347                 u = &account.UTXO{
348                         OutputID:            out.OutputID,
349                         SourceID:            out.sourceID,
350                         AssetID:             *out.AssetId,
351                         Amount:              out.Amount,
352                         SourcePos:           out.sourcePos,
353                         ControlProgram:      out.ControlProgram,
354                         ControlProgramIndex: out.keyIndex,
355                         AccountID:           out.AccountID,
356                         Address:             out.Address,
357                         ValidHeight:         out.ValidHeight,
358                         Change:              out.change,
359                 }
360
361                 data, err := json.Marshal(u)
362                 if err != nil {
363                         return errors.Wrap(err, "failed marshal accountutxo")
364                 }
365
366                 if segwit.IsP2WScript(out.ControlProgram) {
367                         // standard UTXOs
368                         batch.Set(account.StandardUTXOKey(out.OutputID), data)
369                 } else {
370                         // contract UTXOs
371                         batch.Set(account.ContractUTXOKey(out.OutputID), data)
372                 }
373
374         }
375         return nil
376 }
377
378 // filterAccountTxs related and build the fully annotated transactions.
379 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
380         annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
381
382 transactionLoop:
383         for pos, tx := range b.Transactions {
384                 statusFail, _ := txStatus.GetStatus(pos)
385                 for _, v := range tx.Outputs {
386                         var hash [32]byte
387                         sha3pool.Sum256(hash[:], v.ControlProgram)
388                         if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
389                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
390                                 continue transactionLoop
391                         }
392                 }
393
394                 for _, v := range tx.Inputs {
395                         outid, err := v.SpentOutputID()
396                         if err != nil {
397                                 continue
398                         }
399                         if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
400                                 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
401                                 continue transactionLoop
402                         }
403                 }
404         }
405
406         return annotatedTxs
407 }
408
409 // GetTransactionByTxID get transaction by txID
410 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
411         formatKey := w.DB.Get(calcTxIndexKey(txID))
412         if formatKey == nil {
413                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
414         }
415
416         annotatedTx := &query.AnnotatedTx{}
417         txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
418         if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
419                 return nil, err
420         }
421
422         return annotatedTx, nil
423 }
424
425 // GetTransactionsByTxID get account txs by account tx ID
426 func (w *Wallet) GetTransactionsByTxID(txID string) ([]*query.AnnotatedTx, error) {
427         annotatedTxs := []*query.AnnotatedTx{}
428         formatKey := ""
429
430         if txID != "" {
431                 rawFormatKey := w.DB.Get(calcTxIndexKey(txID))
432                 if rawFormatKey == nil {
433                         return nil, fmt.Errorf("No transaction(txid=%s) ", txID)
434                 }
435                 formatKey = string(rawFormatKey)
436         }
437
438         txIter := w.DB.IteratorPrefix(calcAnnotatedKey(formatKey))
439         defer txIter.Release()
440         for txIter.Next() {
441                 annotatedTx := &query.AnnotatedTx{}
442                 if err := json.Unmarshal(txIter.Value(), annotatedTx); err != nil {
443                         return nil, err
444                 }
445                 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
446                 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
447         }
448
449         return annotatedTxs, nil
450 }
451
452 // GetTransactionsSummary get transactions summary
453 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
454         Txs := []TxSummary{}
455
456         for _, annotatedTx := range transactions {
457                 tmpTxSummary := TxSummary{
458                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
459                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
460                         ID:        annotatedTx.ID,
461                         Timestamp: annotatedTx.Timestamp,
462                 }
463
464                 for i, input := range annotatedTx.Inputs {
465                         tmpTxSummary.Inputs[i].Type = input.Type
466                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
467                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
468                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
469                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
470                         tmpTxSummary.Inputs[i].Amount = input.Amount
471                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
472                 }
473                 for j, output := range annotatedTx.Outputs {
474                         tmpTxSummary.Outputs[j].Type = output.Type
475                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
476                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
477                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
478                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
479                         tmpTxSummary.Outputs[j].Amount = output.Amount
480                 }
481
482                 Txs = append(Txs, tmpTxSummary)
483         }
484
485         return Txs
486 }
487
488 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
489         for _, input := range annotatedTx.Inputs {
490                 if input.AccountID == accountID {
491                         return true
492                 }
493         }
494
495         for _, output := range annotatedTx.Outputs {
496                 if output.AccountID == accountID {
497                         return true
498                 }
499         }
500
501         return false
502 }
503
504 // GetTransactionsByAccountID get account txs by account ID
505 func (w *Wallet) GetTransactionsByAccountID(accountID string) ([]*query.AnnotatedTx, error) {
506         annotatedTxs := []*query.AnnotatedTx{}
507
508         txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
509         defer txIter.Release()
510         for txIter.Next() {
511                 annotatedTx := &query.AnnotatedTx{}
512                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
513                         return nil, err
514                 }
515
516                 if findTransactionsByAccount(annotatedTx, accountID) {
517                         annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
518                         annotatedTxs = append(annotatedTxs, annotatedTx)
519                 }
520         }
521
522         return annotatedTxs, nil
523 }
524
525 // GetAccountUTXOs return all account unspent outputs
526 func (w *Wallet) GetAccountUTXOs(id string) []account.UTXO {
527         var accountUTXOs []account.UTXO
528
529         accountUTXOIter := w.DB.IteratorPrefix([]byte(account.UTXOPreFix + id))
530         defer accountUTXOIter.Release()
531         for accountUTXOIter.Next() {
532                 accountUTXO := account.UTXO{}
533                 if err := json.Unmarshal(accountUTXOIter.Value(), &accountUTXO); err != nil {
534                         hashKey := accountUTXOIter.Key()[len(account.UTXOPreFix):]
535                         log.WithField("UTXO hash", string(hashKey)).Warn("get account UTXO")
536                 } else {
537                         accountUTXOs = append(accountUTXOs, accountUTXO)
538                 }
539         }
540
541         return accountUTXOs
542 }
543
544 // GetAccountBalances return all account balances
545 func (w *Wallet) GetAccountBalances(id string) ([]AccountBalance, error) {
546         return w.indexBalances(w.GetAccountUTXOs(""))
547 }
548
549 // AccountBalance account balance
550 type AccountBalance struct {
551         AccountID       string                 `json:"account_id"`
552         Alias           string                 `json:"account_alias"`
553         AssetAlias      string                 `json:"asset_alias"`
554         AssetID         string                 `json:"asset_id"`
555         Amount          uint64                 `json:"amount"`
556         AssetDefinition map[string]interface{} `json:"asset_definition"`
557 }
558
559 func (w *Wallet) indexBalances(accountUTXOs []account.UTXO) ([]AccountBalance, error) {
560         accBalance := make(map[string]map[string]uint64)
561         balances := make([]AccountBalance, 0)
562
563         for _, accountUTXO := range accountUTXOs {
564                 assetID := accountUTXO.AssetID.String()
565                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
566                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
567                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
568                         } else {
569                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
570                         }
571                 } else {
572                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
573                 }
574         }
575
576         var sortedAccount []string
577         for k := range accBalance {
578                 sortedAccount = append(sortedAccount, k)
579         }
580         sort.Strings(sortedAccount)
581
582         for _, id := range sortedAccount {
583                 var sortedAsset []string
584                 for k := range accBalance[id] {
585                         sortedAsset = append(sortedAsset, k)
586                 }
587                 sort.Strings(sortedAsset)
588
589                 for _, assetID := range sortedAsset {
590                         alias := w.AccountMgr.GetAliasByID(id)
591                         targetAsset, err := w.AssetReg.GetAsset(assetID)
592                         if err != nil {
593                                 return nil, err
594                         }
595
596                         assetAlias := *targetAsset.Alias
597                         balances = append(balances, AccountBalance{
598                                 Alias: alias,
599                                 AccountID: id,
600                                 AssetID: assetID,
601                                 AssetAlias: assetAlias,
602                                 Amount: accBalance[id][assetID],
603                                 AssetDefinition: targetAsset.DefinitionMap,
604                         })
605                 }
606         }
607
608         return balances, nil
609 }