OSDN Git Service

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