github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/wallet/indexer.go (about)

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