github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/wallet/indexer.go (about)

     1  package wallet
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/json"
     6  	"fmt"
     7  	"sort"
     8  
     9  	log "github.com/sirupsen/logrus"
    10  
    11  	"github.com/bytom/bytom/account"
    12  	"github.com/bytom/bytom/asset"
    13  	"github.com/bytom/bytom/blockchain/query"
    14  	"github.com/bytom/bytom/crypto/sha3pool"
    15  	dbm "github.com/bytom/bytom/database/leveldb"
    16  	chainjson "github.com/bytom/bytom/encoding/json"
    17  	"github.com/bytom/bytom/errors"
    18  	"github.com/bytom/bytom/protocol/bc"
    19  	"github.com/bytom/bytom/protocol/bc/types"
    20  )
    21  
    22  const (
    23  	//TxPrefix is wallet database transactions prefix
    24  	TxPrefix = "TXS:"
    25  	//TxIndexPrefix is wallet database tx index prefix
    26  	TxIndexPrefix = "TID:"
    27  	//TxIndexPrefix is wallet database global tx index prefix
    28  	GlobalTxIndexPrefix = "GTID:"
    29  )
    30  
    31  var errAccntTxIDNotFound = errors.New("account TXID not found")
    32  
    33  func formatKey(blockHeight uint64, position uint32) string {
    34  	return fmt.Sprintf("%016x%08x", blockHeight, position)
    35  }
    36  
    37  func calcAnnotatedKey(formatKey string) []byte {
    38  	return []byte(TxPrefix + formatKey)
    39  }
    40  
    41  func calcDeleteKey(blockHeight uint64) []byte {
    42  	return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
    43  }
    44  
    45  func calcTxIndexKey(txID string) []byte {
    46  	return []byte(TxIndexPrefix + txID)
    47  }
    48  
    49  func calcGlobalTxIndexKey(txID string) []byte {
    50  	return []byte(GlobalTxIndexPrefix + txID)
    51  }
    52  
    53  func calcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
    54  	txIdx := make([]byte, 40)
    55  	copy(txIdx[:32], blockHash.Bytes())
    56  	binary.BigEndian.PutUint64(txIdx[32:], position)
    57  	return txIdx
    58  }
    59  
    60  func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
    61  	var hashBytes [32]byte
    62  	copy(hashBytes[:], globalTxIdx[:32])
    63  	hash := bc.NewHash(hashBytes)
    64  	position := binary.BigEndian.Uint64(globalTxIdx[32:])
    65  	return &hash, position
    66  }
    67  
    68  // deleteTransaction delete transactions when orphan block rollback
    69  func (w *Wallet) deleteTransactions(batch dbm.Batch, height uint64) {
    70  	tmpTx := query.AnnotatedTx{}
    71  	txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
    72  	defer txIter.Release()
    73  
    74  	for txIter.Next() {
    75  		if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
    76  			batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
    77  		}
    78  		batch.Delete(txIter.Key())
    79  	}
    80  }
    81  
    82  // saveExternalAssetDefinition save external and local assets definition,
    83  // when query ,query local first and if have no then query external
    84  // details see getAliasDefinition
    85  func saveExternalAssetDefinition(b *types.Block, walletDB dbm.DB) {
    86  	storeBatch := walletDB.NewBatch()
    87  	defer storeBatch.Write()
    88  
    89  	for _, tx := range b.Transactions {
    90  		for _, orig := range tx.Inputs {
    91  			if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
    92  				if isValidJSON(ii.AssetDefinition) {
    93  					assetID := ii.AssetID()
    94  					if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
    95  						storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
    96  					}
    97  				}
    98  			}
    99  		}
   100  	}
   101  }
   102  
   103  // Summary is the struct of transaction's input and output summary
   104  type Summary struct {
   105  	Type         string             `json:"type"`
   106  	AssetID      bc.AssetID         `json:"asset_id,omitempty"`
   107  	AssetAlias   string             `json:"asset_alias,omitempty"`
   108  	Amount       uint64             `json:"amount,omitempty"`
   109  	AccountID    string             `json:"account_id,omitempty"`
   110  	AccountAlias string             `json:"account_alias,omitempty"`
   111  	Arbitrary    chainjson.HexBytes `json:"arbitrary,omitempty"`
   112  }
   113  
   114  // TxSummary is the struct of transaction summary
   115  type TxSummary struct {
   116  	ID        bc.Hash   `json:"tx_id"`
   117  	Timestamp uint64    `json:"block_time"`
   118  	Inputs    []Summary `json:"inputs"`
   119  	Outputs   []Summary `json:"outputs"`
   120  }
   121  
   122  // indexTransactions saves all annotated transactions to the database.
   123  func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
   124  	annotatedTxs := w.filterAccountTxs(b, txStatus)
   125  	saveExternalAssetDefinition(b, w.DB)
   126  	annotateTxsAccount(annotatedTxs, w.DB)
   127  
   128  	for _, tx := range annotatedTxs {
   129  		rawTx, err := json.Marshal(tx)
   130  		if err != nil {
   131  			log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
   132  			return err
   133  		}
   134  
   135  		batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
   136  		batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
   137  
   138  		// delete unconfirmed transaction
   139  		batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
   140  	}
   141  
   142  	if !w.TxIndexFlag {
   143  		return nil
   144  	}
   145  
   146  	for position, globalTx := range b.Transactions {
   147  		blockHash := b.BlockHeader.Hash()
   148  		batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position)))
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  // filterAccountTxs related and build the fully annotated transactions.
   155  func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
   156  	annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
   157  
   158  transactionLoop:
   159  	for pos, tx := range b.Transactions {
   160  		statusFail, _ := txStatus.GetStatus(pos)
   161  		for _, v := range tx.Outputs {
   162  			var hash [32]byte
   163  			sha3pool.Sum256(hash[:], v.ControlProgram)
   164  
   165  			if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
   166  				annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
   167  				continue transactionLoop
   168  			}
   169  		}
   170  
   171  		for _, v := range tx.Inputs {
   172  			outid, err := v.SpentOutputID()
   173  			if err != nil {
   174  				continue
   175  			}
   176  			if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
   177  				annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
   178  				continue transactionLoop
   179  			}
   180  		}
   181  	}
   182  
   183  	return annotatedTxs
   184  }
   185  
   186  // GetTransactionByTxID get transaction by txID
   187  func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
   188  	if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
   189  		return annotatedTx, nil
   190  	} else if !w.TxIndexFlag {
   191  		return nil, err
   192  	}
   193  
   194  	return w.getGlobalTxByTxID(txID)
   195  }
   196  
   197  func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
   198  	annotatedTx := &query.AnnotatedTx{}
   199  	formatKey := w.DB.Get(calcTxIndexKey(txID))
   200  	if formatKey == nil {
   201  		return nil, errAccntTxIDNotFound
   202  	}
   203  
   204  	txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
   205  	if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
   210  	return annotatedTx, nil
   211  }
   212  
   213  func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
   214  	globalTxIdx := w.DB.Get(calcGlobalTxIndexKey(txID))
   215  	if globalTxIdx == nil {
   216  		return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
   217  	}
   218  
   219  	blockHash, pos := parseGlobalTxIdx(globalTxIdx)
   220  	block, err := w.chain.GetBlockByHash(blockHash)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	txStatus, err := w.chain.GetTransactionStatus(blockHash)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	statusFail, err := txStatus.GetStatus(int(pos))
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	tx := block.Transactions[int(pos)]
   236  	return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
   237  }
   238  
   239  // GetTransactionsSummary get transactions summary
   240  func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
   241  	Txs := []TxSummary{}
   242  
   243  	for _, annotatedTx := range transactions {
   244  		tmpTxSummary := TxSummary{
   245  			Inputs:    make([]Summary, len(annotatedTx.Inputs)),
   246  			Outputs:   make([]Summary, len(annotatedTx.Outputs)),
   247  			ID:        annotatedTx.ID,
   248  			Timestamp: annotatedTx.Timestamp,
   249  		}
   250  
   251  		for i, input := range annotatedTx.Inputs {
   252  			tmpTxSummary.Inputs[i].Type = input.Type
   253  			tmpTxSummary.Inputs[i].AccountID = input.AccountID
   254  			tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
   255  			tmpTxSummary.Inputs[i].AssetID = input.AssetID
   256  			tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
   257  			tmpTxSummary.Inputs[i].Amount = input.Amount
   258  			tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
   259  		}
   260  		for j, output := range annotatedTx.Outputs {
   261  			tmpTxSummary.Outputs[j].Type = output.Type
   262  			tmpTxSummary.Outputs[j].AccountID = output.AccountID
   263  			tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
   264  			tmpTxSummary.Outputs[j].AssetID = output.AssetID
   265  			tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
   266  			tmpTxSummary.Outputs[j].Amount = output.Amount
   267  		}
   268  
   269  		Txs = append(Txs, tmpTxSummary)
   270  	}
   271  
   272  	return Txs
   273  }
   274  
   275  func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
   276  	for _, input := range annotatedTx.Inputs {
   277  		if input.AccountID == accountID {
   278  			return true
   279  		}
   280  	}
   281  
   282  	for _, output := range annotatedTx.Outputs {
   283  		if output.AccountID == accountID {
   284  			return true
   285  		}
   286  	}
   287  
   288  	return false
   289  }
   290  
   291  // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
   292  func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
   293  	annotatedTxs := []*query.AnnotatedTx{}
   294  
   295  	txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
   296  	defer txIter.Release()
   297  	for txIter.Next() {
   298  		annotatedTx := &query.AnnotatedTx{}
   299  		if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
   300  			return nil, err
   301  		}
   302  
   303  		if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
   304  			annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
   305  			annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
   306  		}
   307  	}
   308  
   309  	return annotatedTxs, nil
   310  }
   311  
   312  // GetAccountBalances return all account balances
   313  func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
   314  	return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
   315  }
   316  
   317  // AccountBalance account balance
   318  type AccountBalance struct {
   319  	AccountID       string                 `json:"account_id"`
   320  	Alias           string                 `json:"account_alias"`
   321  	AssetAlias      string                 `json:"asset_alias"`
   322  	AssetID         string                 `json:"asset_id"`
   323  	Amount          uint64                 `json:"amount"`
   324  	AssetDefinition map[string]interface{} `json:"asset_definition"`
   325  }
   326  
   327  func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
   328  	accBalance := make(map[string]map[string]uint64)
   329  	balances := []AccountBalance{}
   330  
   331  	for _, accountUTXO := range accountUTXOs {
   332  		assetID := accountUTXO.AssetID.String()
   333  		if _, ok := accBalance[accountUTXO.AccountID]; ok {
   334  			if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
   335  				accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
   336  			} else {
   337  				accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
   338  			}
   339  		} else {
   340  			accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
   341  		}
   342  	}
   343  
   344  	var sortedAccount []string
   345  	for k := range accBalance {
   346  		sortedAccount = append(sortedAccount, k)
   347  	}
   348  	sort.Strings(sortedAccount)
   349  
   350  	for _, id := range sortedAccount {
   351  		var sortedAsset []string
   352  		for k := range accBalance[id] {
   353  			sortedAsset = append(sortedAsset, k)
   354  		}
   355  		sort.Strings(sortedAsset)
   356  
   357  		for _, assetID := range sortedAsset {
   358  			alias := w.AccountMgr.GetAliasByID(id)
   359  			targetAsset, err := w.AssetReg.GetAsset(assetID)
   360  			if err != nil {
   361  				return nil, err
   362  			}
   363  
   364  			assetAlias := *targetAsset.Alias
   365  			balances = append(balances, AccountBalance{
   366  				Alias:           alias,
   367  				AccountID:       id,
   368  				AssetID:         assetID,
   369  				AssetAlias:      assetAlias,
   370  				Amount:          accBalance[id][assetID],
   371  				AssetDefinition: targetAsset.DefinitionMap,
   372  			})
   373  		}
   374  	}
   375  
   376  	return balances, nil
   377  }