OSDN Git Service

Block node (#541)
[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);
223                                 statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
224                                 continue
225                         }
226
227                         out := &rawOutput{
228                                 OutputID:       *tx.OutputID(i),
229                                 AssetAmount:    out.AssetAmount,
230                                 ControlProgram: out.ControlProgram,
231                                 txHash:         tx.ID,
232                                 outputIndex:    uint32(i),
233                                 sourceID:       *resOut.Source.Ref,
234                                 sourcePos:      resOut.Source.Position,
235                         }
236
237                         // coinbase utxo valid height
238                         if txIndex == 0 {
239                                 out.ValidHeight = b.Height + consensus.CoinbasePendingBlockNumber
240                         }
241                         outs = append(outs, out)
242                 }
243         }
244         accOuts := loadAccountInfo(outs, w)
245
246         if err := upsertConfirmedAccountOutputs(accOuts, batch); err != nil {
247                 log.WithField("err", err).Error("building new account outputs")
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
383 transactionLoop:
384         for pos, tx := range b.Transactions {
385                 statusFail, _ := txStatus.GetStatus(pos)
386                 for _, v := range tx.Outputs {
387                         var hash [32]byte
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                                 continue transactionLoop
392                         }
393                 }
394
395                 for _, v := range tx.Inputs {
396                         outid, err := v.SpentOutputID()
397                         if err != nil {
398                                 continue
399                         }
400                         if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
401                                 annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, statusFail, pos))
402                                 continue transactionLoop
403                         }
404                 }
405         }
406
407         return annotatedTxs
408 }
409
410 // GetTransactionByTxID get transaction by txID
411 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
412         formatKey := w.DB.Get(calcTxIndexKey(txID))
413         if formatKey == nil {
414                 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
415         }
416
417         annotatedTx := &query.AnnotatedTx{}
418         txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
419         if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
420                 return nil, err
421         }
422
423         return annotatedTx, nil
424 }
425
426 // GetTransactionsByTxID get account txs by account tx ID
427 func (w *Wallet) GetTransactionsByTxID(txID string) ([]*query.AnnotatedTx, error) {
428         var annotatedTxs []*query.AnnotatedTx
429         formatKey := ""
430
431         if txID != "" {
432                 rawFormatKey := w.DB.Get(calcTxIndexKey(txID))
433                 if rawFormatKey == nil {
434                         return nil, fmt.Errorf("No transaction(txid=%s) ", txID)
435                 }
436                 formatKey = string(rawFormatKey)
437         }
438
439         txIter := w.DB.IteratorPrefix(calcAnnotatedKey(formatKey))
440         defer txIter.Release()
441         for txIter.Next() {
442                 annotatedTx := &query.AnnotatedTx{}
443                 if err := json.Unmarshal(txIter.Value(), annotatedTx); err != nil {
444                         return nil, err
445                 }
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                         annotatedTxs = append(annotatedTxs, annotatedTx)
518                 }
519         }
520
521         return annotatedTxs, nil
522 }
523
524 // GetAccountUTXOs return all account unspent outputs
525 func (w *Wallet) GetAccountUTXOs(id string) []account.UTXO {
526         var accountUTXOs []account.UTXO
527
528         accountUTXOIter := w.DB.IteratorPrefix([]byte(account.UTXOPreFix + id))
529         defer accountUTXOIter.Release()
530         for accountUTXOIter.Next() {
531                 accountUTXO := account.UTXO{}
532                 if err := json.Unmarshal(accountUTXOIter.Value(), &accountUTXO); err != nil {
533                         hashKey := accountUTXOIter.Key()[len(account.UTXOPreFix):]
534                         log.WithField("UTXO hash", string(hashKey)).Warn("get account UTXO")
535                 } else {
536                         accountUTXOs = append(accountUTXOs, accountUTXO)
537                 }
538         }
539
540         return accountUTXOs
541 }
542
543 // GetAccountBalances return all account balances
544 func (w *Wallet) GetAccountBalances(id string) []AccountBalance {
545         return w.indexBalances(w.GetAccountUTXOs(""))
546 }
547
548 // AccountBalance account balance
549 type AccountBalance struct {
550         AccountID  string `json:"account_id"`
551         Alias      string `json:"account_alias"`
552         AssetAlias string `json:"asset_alias"`
553         AssetID    string `json:"asset_id"`
554         Amount     uint64 `json:"amount"`
555 }
556
557 func (w *Wallet) indexBalances(accountUTXOs []account.UTXO) []AccountBalance {
558         accBalance := make(map[string]map[string]uint64)
559         balances := make([]AccountBalance, 0)
560         tmpBalance := AccountBalance{}
561
562         for _, accountUTXO := range accountUTXOs {
563                 assetID := accountUTXO.AssetID.String()
564                 if _, ok := accBalance[accountUTXO.AccountID]; ok {
565                         if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
566                                 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
567                         } else {
568                                 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
569                         }
570                 } else {
571                         accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
572                 }
573         }
574
575         var sortedAccount []string
576         for k := range accBalance {
577                 sortedAccount = append(sortedAccount, k)
578         }
579         sort.Strings(sortedAccount)
580
581         for _, id := range sortedAccount {
582                 var sortedAsset []string
583                 for k := range accBalance[id] {
584                         sortedAsset = append(sortedAsset, k)
585                 }
586                 sort.Strings(sortedAsset)
587
588                 for _, assetID := range sortedAsset {
589                         alias := w.AccountMgr.GetAliasByID(id)
590                         assetAlias := w.AssetReg.GetAliasByID(assetID)
591                         tmpBalance.Alias = alias
592                         tmpBalance.AccountID = id
593                         tmpBalance.AssetID = assetID
594                         tmpBalance.AssetAlias = assetAlias
595                         tmpBalance.Amount = accBalance[id][assetID]
596                         balances = append(balances, tmpBalance)
597                 }
598         }
599
600         return balances
601 }