OSDN Git Service

Revert "remove CtrlProgram#Change and accountOutput#change (#600)"
[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                                                 storeBatch.Set(asset.CalcExtAssetKey(&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         annotateTxsAsset(w, annotatedTxs)
192         annotateTxsAccount(annotatedTxs, w.DB)
193
194         for _, tx := range annotatedTxs {
195                 rawTx, err := json.Marshal(tx)
196                 if err != nil {
197                         log.WithField("err", err).Error("inserting annotated_txs to db")
198                         return err
199                 }
200
201                 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
202                 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
203         }
204         return nil
205 }
206
207 // buildAccountUTXOs process valid blocks to build account unspent outputs db
208 func (w *Wallet) buildAccountUTXOs(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
209         // get the spent UTXOs and delete the UTXOs from DB
210         prevoutDBKeys(batch, b, txStatus)
211
212         // handle new UTXOs
213         var outs []*rawOutput
214         for txIndex, tx := range b.Transactions {
215                 for i, out := range tx.Outputs {
216                         resOutID := tx.ResultIds[i]
217                         resOut, ok := tx.Entries[*resOutID].(*bc.Output)
218                         if !ok {
219                                 continue
220                         }
221
222                         if statusFail, _ := txStatus.GetStatus(txIndex); statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
223                                 continue
224                         }
225
226                         out := &rawOutput{
227                                 OutputID:       *tx.OutputID(i),
228                                 AssetAmount:    out.AssetAmount,
229                                 ControlProgram: out.ControlProgram,
230                                 txHash:         tx.ID,
231                                 outputIndex:    uint32(i),
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         }
248 }
249
250 func prevoutDBKeys(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
251         for txIndex, tx := range b.Transactions {
252                 for _, inpID := range tx.Tx.InputIDs {
253                         sp, err := tx.Spend(inpID)
254                         if err != nil {
255                                 continue
256                         }
257
258                         statusFail, _ := txStatus.GetStatus(txIndex)
259                         if statusFail && *sp.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
260                                 continue
261                         }
262
263                         resOut, ok := tx.Entries[*sp.SpentOutputId].(*bc.Output)
264                         if !ok {
265                                 // retirement
266                                 log.WithField("SpentOutputId", *sp.SpentOutputId).Info("the OutputId is retirement")
267                                 continue
268                         }
269
270                         if segwit.IsP2WScript(resOut.ControlProgram.Code) {
271                                 // delete standard UTXOs
272                                 batch.Delete(account.StandardUTXOKey(*sp.SpentOutputId))
273                         } else {
274                                 // delete contract UTXOs
275                                 batch.Delete(account.ContractUTXOKey(*sp.SpentOutputId))
276                         }
277                 }
278         }
279         return
280 }
281
282 // loadAccountInfo turns a set of output IDs into a set of
283 // outputs by adding account annotations.  Outputs that can't be
284 // annotated are excluded from the result.
285 func loadAccountInfo(outs []*rawOutput, w *Wallet) []*accountOutput {
286         outsByScript := make(map[string][]*rawOutput, len(outs))
287         for _, out := range outs {
288                 scriptStr := string(out.ControlProgram)
289                 outsByScript[scriptStr] = append(outsByScript[scriptStr], out)
290         }
291
292         result := make([]*accountOutput, 0, len(outs))
293         cp := account.CtrlProgram{}
294
295         var hash [32]byte
296         for s := range outsByScript {
297                 // smart contract UTXO
298                 if !segwit.IsP2WScript([]byte(s)) {
299                         for _, out := range outsByScript[s] {
300                                 newOut := &accountOutput{
301                                         rawOutput: *out,
302                                         change:    false,
303                                 }
304                                 result = append(result, newOut)
305                         }
306
307                         continue
308                 }
309
310                 sha3pool.Sum256(hash[:], []byte(s))
311                 bytes := w.DB.Get(account.CPKey(hash))
312                 if bytes == nil {
313                         continue
314                 }
315
316                 err := json.Unmarshal(bytes, &cp)
317                 if err != nil {
318                         continue
319                 }
320
321                 isExist := w.DB.Get(account.Key(cp.AccountID))
322                 if isExist == nil {
323                         continue
324                 }
325
326                 for _, out := range outsByScript[s] {
327                         newOut := &accountOutput{
328                                 rawOutput: *out,
329                                 AccountID: cp.AccountID,
330                                 Address:   cp.Address,
331                                 keyIndex:  cp.KeyIndex,
332                                 change:    cp.Change,
333                         }
334                         result = append(result, newOut)
335                 }
336         }
337
338         return result
339 }
340
341 // upsertConfirmedAccountOutputs records the account data for confirmed utxos.
342 // If the account utxo already exists (because it's from a local tx), the
343 // block confirmation data will in the row will be updated.
344 func upsertConfirmedAccountOutputs(outs []*accountOutput, batch db.Batch) error {
345         var u *account.UTXO
346
347         for _, out := range outs {
348                 u = &account.UTXO{
349                         OutputID:            out.OutputID,
350                         SourceID:            out.sourceID,
351                         AssetID:             *out.AssetId,
352                         Amount:              out.Amount,
353                         SourcePos:           out.sourcePos,
354                         ControlProgram:      out.ControlProgram,
355                         ControlProgramIndex: out.keyIndex,
356                         AccountID:           out.AccountID,
357                         Address:             out.Address,
358                         ValidHeight:         out.ValidHeight,
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.CPKey(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         var 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                 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
446         }
447
448         return annotatedTxs, nil
449 }
450
451 // GetTransactionsSummary get transactions summary
452 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
453         Txs := []TxSummary{}
454
455         for _, annotatedTx := range transactions {
456                 tmpTxSummary := TxSummary{
457                         Inputs:    make([]Summary, len(annotatedTx.Inputs)),
458                         Outputs:   make([]Summary, len(annotatedTx.Outputs)),
459                         ID:        annotatedTx.ID,
460                         Timestamp: annotatedTx.Timestamp,
461                 }
462
463                 for i, input := range annotatedTx.Inputs {
464                         tmpTxSummary.Inputs[i].Type = input.Type
465                         tmpTxSummary.Inputs[i].AccountID = input.AccountID
466                         tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
467                         tmpTxSummary.Inputs[i].AssetID = input.AssetID
468                         tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
469                         tmpTxSummary.Inputs[i].Amount = input.Amount
470                         tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
471                 }
472                 for j, output := range annotatedTx.Outputs {
473                         tmpTxSummary.Outputs[j].Type = output.Type
474                         tmpTxSummary.Outputs[j].AccountID = output.AccountID
475                         tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
476                         tmpTxSummary.Outputs[j].AssetID = output.AssetID
477                         tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
478                         tmpTxSummary.Outputs[j].Amount = output.Amount
479                 }
480
481                 Txs = append(Txs, tmpTxSummary)
482         }
483
484         return Txs
485 }
486
487 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
488         for _, input := range annotatedTx.Inputs {
489                 if input.AccountID == accountID {
490                         return true
491                 }
492         }
493
494         for _, output := range annotatedTx.Outputs {
495                 if output.AccountID == accountID {
496                         return true
497                 }
498         }
499
500         return false
501 }
502
503 // GetTransactionsByAccountID get account txs by account ID
504 func (w *Wallet) GetTransactionsByAccountID(accountID string) ([]*query.AnnotatedTx, error) {
505         var annotatedTxs []*query.AnnotatedTx
506
507         txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
508         defer txIter.Release()
509         for txIter.Next() {
510                 annotatedTx := &query.AnnotatedTx{}
511                 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
512                         return nil, err
513                 }
514
515                 if findTransactionsByAccount(annotatedTx, accountID) {
516                         annotatedTxs = append(annotatedTxs, annotatedTx)
517                 }
518         }
519
520         return annotatedTxs, nil
521 }
522
523 // GetAccountUTXOs return all account unspent outputs
524 func (w *Wallet) GetAccountUTXOs(id string) []account.UTXO {
525         var accountUTXOs []account.UTXO
526
527         accountUTXOIter := w.DB.IteratorPrefix([]byte(account.UTXOPreFix + id))
528         defer accountUTXOIter.Release()
529         for accountUTXOIter.Next() {
530                 accountUTXO := account.UTXO{}
531                 if err := json.Unmarshal(accountUTXOIter.Value(), &accountUTXO); err != nil {
532                         hashKey := accountUTXOIter.Key()[len(account.UTXOPreFix):]
533                         log.WithField("UTXO hash", string(hashKey)).Warn("get account UTXO")
534                 } else {
535                         accountUTXOs = append(accountUTXOs, accountUTXO)
536                 }
537         }
538
539         return accountUTXOs
540 }
541
542 // GetAccountBalances return all account balances
543 func (w *Wallet) GetAccountBalances(id string) []AccountBalance {
544         return w.indexBalances(w.GetAccountUTXOs(""))
545 }
546
547 // AccountBalance account balance
548 type AccountBalance struct {
549         AccountID  string `json:"account_id"`
550         Alias      string `json:"account_alias"`
551         AssetAlias string `json:"asset_alias"`
552         AssetID    string `json:"asset_id"`
553         Amount     uint64 `json:"amount"`
554 }
555
556 func (w *Wallet) indexBalances(accountUTXOs []account.UTXO) []AccountBalance {
557         accBalance := make(map[string]map[string]uint64)
558         balances := make([]AccountBalance, 0)
559         tmpBalance := AccountBalance{}
560
561         for _, accountUTXO := range accountUTXOs {
562                 assetID := accountUTXO.AssetID.String()
563                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
564                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
565                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
566                         } else {
567                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
568                         }
569                 } else {
570                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
571                 }
572         }
573
574         var sortedAccount []string
575         for k := range accBalance {
576                 sortedAccount = append(sortedAccount, k)
577         }
578         sort.Strings(sortedAccount)
579
580         for _, id := range sortedAccount {
581                 var sortedAsset []string
582                 for k := range accBalance[id] {
583                         sortedAsset = append(sortedAsset, k)
584                 }
585                 sort.Strings(sortedAsset)
586
587                 for _, assetID := range sortedAsset {
588                         alias := w.AccountMgr.GetAliasByID(id)
589                         assetAlias := w.AssetReg.GetAliasByID(assetID)
590                         tmpBalance.Alias = alias
591                         tmpBalance.AccountID = id
592                         tmpBalance.AssetID = assetID
593                         tmpBalance.AssetAlias = assetAlias
594                         tmpBalance.Amount = accBalance[id][assetID]
595                         balances = append(balances, tmpBalance)
596                 }
597         }
598
599         return balances
600 }