github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/api/query.go (about)

     1  package api
     2  
     3  import (
     4  	"context"
     5  	"crypto/ed25519"
     6  	"encoding/hex"
     7  	"fmt"
     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/blockchain/signers"
    15  	"github.com/bytom/bytom/consensus"
    16  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    17  	chainjson "github.com/bytom/bytom/encoding/json"
    18  	"github.com/bytom/bytom/errors"
    19  	"github.com/bytom/bytom/protocol/bc"
    20  	"github.com/bytom/bytom/protocol/bc/types"
    21  )
    22  
    23  // POST /list-accounts
    24  func (a *API) listAccounts(ctx context.Context, filter struct {
    25  	ID    string `json:"id"`
    26  	Alias string `json:"alias"`
    27  }) Response {
    28  	accountID := filter.ID
    29  	if filter.Alias != "" {
    30  		acc, err := a.wallet.AccountMgr.FindByAlias(filter.Alias)
    31  		if err != nil {
    32  			return NewErrorResponse(err)
    33  		}
    34  		accountID = acc.ID
    35  	}
    36  
    37  	accounts, err := a.wallet.AccountMgr.ListAccounts(accountID)
    38  	if err != nil {
    39  		log.Errorf("listAccounts: %v", err)
    40  		return NewErrorResponse(err)
    41  	}
    42  
    43  	annotatedAccounts := []query.AnnotatedAccount{}
    44  	for _, acc := range accounts {
    45  		annotatedAccounts = append(annotatedAccounts, *account.Annotated(acc))
    46  	}
    47  
    48  	return NewSuccessResponse(annotatedAccounts)
    49  }
    50  
    51  // POST /get-asset
    52  func (a *API) getAsset(ctx context.Context, filter struct {
    53  	ID string `json:"id"`
    54  }) Response {
    55  	ass, err := a.wallet.AssetReg.GetAsset(filter.ID)
    56  	if err != nil {
    57  		log.Errorf("getAsset: %v", err)
    58  		return NewErrorResponse(err)
    59  	}
    60  
    61  	annotatedAsset, err := asset.Annotated(ass)
    62  	if err != nil {
    63  		return NewErrorResponse(err)
    64  	}
    65  	return NewSuccessResponse(annotatedAsset)
    66  }
    67  
    68  // POST /list-assets
    69  func (a *API) listAssets(ctx context.Context, filter struct {
    70  	ID string `json:"id"`
    71  }) Response {
    72  	assets, err := a.wallet.AssetReg.ListAssets(filter.ID)
    73  	if err != nil {
    74  		log.Errorf("listAssets: %v", err)
    75  		return NewErrorResponse(err)
    76  	}
    77  
    78  	annotatedAssets := []*query.AnnotatedAsset{}
    79  	for _, ass := range assets {
    80  		annotatedAsset, err := asset.Annotated(ass)
    81  		if err != nil {
    82  			return NewErrorResponse(err)
    83  		}
    84  		annotatedAssets = append(annotatedAssets, annotatedAsset)
    85  	}
    86  	return NewSuccessResponse(annotatedAssets)
    87  }
    88  
    89  // POST /list-balances
    90  func (a *API) listBalances(ctx context.Context, filter struct {
    91  	AccountID    string `json:"account_id"`
    92  	AccountAlias string `json:"account_alias"`
    93  }) Response {
    94  	accountID := filter.AccountID
    95  	if filter.AccountAlias != "" {
    96  		acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias)
    97  		if err != nil {
    98  			return NewErrorResponse(err)
    99  		}
   100  		accountID = acc.ID
   101  	}
   102  
   103  	balances, err := a.wallet.GetAccountBalances(accountID, "")
   104  	if err != nil {
   105  		return NewErrorResponse(err)
   106  	}
   107  	return NewSuccessResponse(balances)
   108  }
   109  
   110  // POST /get-transaction
   111  func (a *API) getTransaction(ctx context.Context, txInfo struct {
   112  	TxID string `json:"tx_id"`
   113  }) Response {
   114  	var annotatedTx *query.AnnotatedTx
   115  	var err error
   116  
   117  	annotatedTx, err = a.wallet.GetTransactionByTxID(txInfo.TxID)
   118  	if err != nil {
   119  		// transaction not found in blockchain db, search it from unconfirmed db
   120  		annotatedTx, err = a.wallet.GetUnconfirmedTxByTxID(txInfo.TxID)
   121  		if err != nil {
   122  			return NewErrorResponse(err)
   123  		}
   124  	}
   125  
   126  	return NewSuccessResponse(annotatedTx)
   127  }
   128  
   129  // POST /list-transactions
   130  func (a *API) listTransactions(ctx context.Context, filter struct {
   131  	ID          string `json:"id"`
   132  	AccountID   string `json:"account_id"`
   133  	Detail      bool   `json:"detail"`
   134  	Unconfirmed bool   `json:"unconfirmed"`
   135  	From        uint   `json:"from"`
   136  	Count       uint   `json:"count"`
   137  }) Response {
   138  	transactions := []*query.AnnotatedTx{}
   139  	var err error
   140  	var transaction *query.AnnotatedTx
   141  
   142  	if filter.ID != "" {
   143  		transaction, err = a.wallet.GetTransactionByTxID(filter.ID)
   144  		if err != nil && filter.Unconfirmed {
   145  			transaction, err = a.wallet.GetUnconfirmedTxByTxID(filter.ID)
   146  		}
   147  
   148  		if err != nil {
   149  			return NewErrorResponse(err)
   150  		}
   151  		transactions = []*query.AnnotatedTx{transaction}
   152  	} else {
   153  		transactions, err = a.wallet.GetTransactions(filter.AccountID)
   154  		if err != nil {
   155  			return NewErrorResponse(err)
   156  		}
   157  
   158  		if filter.Unconfirmed {
   159  			unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID)
   160  			if err != nil {
   161  				return NewErrorResponse(err)
   162  			}
   163  			transactions = append(unconfirmedTxs, transactions...)
   164  		}
   165  	}
   166  
   167  	if filter.Detail == false {
   168  		txSummary := a.wallet.GetTransactionsSummary(transactions)
   169  		start, end := getPageRange(len(txSummary), filter.From, filter.Count)
   170  		return NewSuccessResponse(txSummary[start:end])
   171  	}
   172  	start, end := getPageRange(len(transactions), filter.From, filter.Count)
   173  	return NewSuccessResponse(transactions[start:end])
   174  }
   175  
   176  // POST /get-unconfirmed-transaction
   177  func (a *API) getUnconfirmedTx(ctx context.Context, filter struct {
   178  	TxID chainjson.HexBytes `json:"tx_id"`
   179  }) Response {
   180  	var tmpTxID [32]byte
   181  	copy(tmpTxID[:], filter.TxID[:])
   182  
   183  	txHash := bc.NewHash(tmpTxID)
   184  	txPool := a.chain.GetTxPool()
   185  	txDesc, err := txPool.GetTransaction(&txHash)
   186  	if err != nil {
   187  		return NewErrorResponse(err)
   188  	}
   189  
   190  	tx := &BlockTx{
   191  		ID:        txDesc.Tx.ID,
   192  		Version:   txDesc.Tx.Version,
   193  		Size:      txDesc.Tx.SerializedSize,
   194  		TimeRange: txDesc.Tx.TimeRange,
   195  		Inputs:    []*query.AnnotatedInput{},
   196  		Outputs:   []*query.AnnotatedOutput{},
   197  	}
   198  
   199  	resOutID := txDesc.Tx.ResultIds[0]
   200  	resOut := txDesc.Tx.Entries[*resOutID]
   201  	switch out := resOut.(type) {
   202  	case *bc.OriginalOutput:
   203  		tx.MuxID = *out.Source.Ref
   204  	case *bc.Retirement:
   205  		tx.MuxID = *out.Source.Ref
   206  	}
   207  
   208  	for i := range txDesc.Tx.Inputs {
   209  		tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(txDesc.Tx, uint32(i)))
   210  	}
   211  	for i := range txDesc.Tx.Outputs {
   212  		tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(txDesc.Tx, i))
   213  	}
   214  
   215  	return NewSuccessResponse(tx)
   216  }
   217  
   218  type unconfirmedTxsResp struct {
   219  	Total uint64    `json:"total"`
   220  	TxIDs []bc.Hash `json:"tx_ids"`
   221  }
   222  
   223  // POST /list-unconfirmed-transactions
   224  func (a *API) listUnconfirmedTxs(ctx context.Context) Response {
   225  	txIDs := []bc.Hash{}
   226  
   227  	txPool := a.chain.GetTxPool()
   228  	txs := txPool.GetTransactions()
   229  	for _, txDesc := range txs {
   230  		txIDs = append(txIDs, bc.Hash(txDesc.Tx.ID))
   231  	}
   232  
   233  	return NewSuccessResponse(&unconfirmedTxsResp{
   234  		Total: uint64(len(txIDs)),
   235  		TxIDs: txIDs,
   236  	})
   237  }
   238  
   239  // RawTx is the tx struct for getRawTransaction
   240  type RawTx struct {
   241  	ID        bc.Hash                  `json:"tx_id"`
   242  	Version   uint64                   `json:"version"`
   243  	Size      uint64                   `json:"size"`
   244  	TimeRange uint64                   `json:"time_range"`
   245  	Inputs    []*query.AnnotatedInput  `json:"inputs"`
   246  	Outputs   []*query.AnnotatedOutput `json:"outputs"`
   247  	Fee       uint64                   `json:"fee"`
   248  }
   249  
   250  // POST /decode-raw-transaction
   251  func (a *API) decodeRawTransaction(ctx context.Context, ins struct {
   252  	Tx types.Tx `json:"raw_transaction"`
   253  }) Response {
   254  	tx := &RawTx{
   255  		ID:        ins.Tx.ID,
   256  		Version:   ins.Tx.Version,
   257  		Size:      ins.Tx.SerializedSize,
   258  		TimeRange: ins.Tx.TimeRange,
   259  		Inputs:    []*query.AnnotatedInput{},
   260  		Outputs:   []*query.AnnotatedOutput{},
   261  	}
   262  
   263  	for i := range ins.Tx.Inputs {
   264  		tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(&ins.Tx, uint32(i)))
   265  	}
   266  	for i := range ins.Tx.Outputs {
   267  		tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i))
   268  	}
   269  
   270  	tx.Fee = ins.Tx.Fee()
   271  	return NewSuccessResponse(tx)
   272  }
   273  
   274  // POST /list-unspent-outputs
   275  func (a *API) listUnspentOutputs(ctx context.Context, filter struct {
   276  	AccountID     string `json:"account_id"`
   277  	AccountAlias  string `json:"account_alias"`
   278  	ID            string `json:"id"`
   279  	Unconfirmed   bool   `json:"unconfirmed"`
   280  	SmartContract bool   `json:"smart_contract"`
   281  	From          uint   `json:"from"`
   282  	Count         uint   `json:"count"`
   283  }) Response {
   284  	accountID := filter.AccountID
   285  	if filter.AccountAlias != "" {
   286  		acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias)
   287  		if err != nil {
   288  			return NewErrorResponse(err)
   289  		}
   290  		accountID = acc.ID
   291  	}
   292  	accountUTXOs := a.wallet.GetAccountUtxos(accountID, filter.ID, filter.Unconfirmed, filter.SmartContract, false)
   293  
   294  	UTXOs := []query.AnnotatedUTXO{}
   295  	for _, utxo := range accountUTXOs {
   296  		UTXOs = append([]query.AnnotatedUTXO{{
   297  			AccountID:           utxo.AccountID,
   298  			OutputID:            utxo.OutputID.String(),
   299  			SourceID:            utxo.SourceID.String(),
   300  			AssetID:             utxo.AssetID.String(),
   301  			Amount:              utxo.Amount,
   302  			SourcePos:           utxo.SourcePos,
   303  			Program:             fmt.Sprintf("%x", utxo.ControlProgram),
   304  			ControlProgramIndex: utxo.ControlProgramIndex,
   305  			Address:             utxo.Address,
   306  			ValidHeight:         utxo.ValidHeight,
   307  			Alias:               a.wallet.AccountMgr.GetAliasByID(utxo.AccountID),
   308  			AssetAlias:          a.wallet.AssetReg.GetAliasByID(utxo.AssetID.String()),
   309  			Change:              utxo.Change,
   310  		}}, UTXOs...)
   311  	}
   312  	start, end := getPageRange(len(UTXOs), filter.From, filter.Count)
   313  	return NewSuccessResponse(UTXOs[start:end])
   314  }
   315  
   316  // return gasRate
   317  func (a *API) gasRate() Response {
   318  	gasrate := map[string]int64{"gas_rate": consensus.VMGasRate}
   319  	return NewSuccessResponse(gasrate)
   320  }
   321  
   322  // PubKeyInfo is structure of pubkey info
   323  type PubKeyInfo struct {
   324  	Pubkey string               `json:"pubkey"`
   325  	Path   []chainjson.HexBytes `json:"derivation_path"`
   326  }
   327  
   328  // AccountPubkey is detail of account pubkey info
   329  type AccountPubkey struct {
   330  	RootXPub    chainkd.XPub `json:"root_xpub"`
   331  	PubKeyInfos []PubKeyInfo `json:"pubkey_infos"`
   332  }
   333  
   334  func getPubkey(account *account.Account, change bool, index uint64) (*ed25519.PublicKey, []chainjson.HexBytes, error) {
   335  	rawPath, err := signers.Path(account.Signer, signers.AccountKeySpace, change, index)
   336  	if err != nil {
   337  		return nil, nil, err
   338  	}
   339  	derivedXPub := account.XPubs[0].Derive(rawPath)
   340  	pubkey := derivedXPub.PublicKey()
   341  	var path []chainjson.HexBytes
   342  	for _, p := range rawPath {
   343  		path = append(path, chainjson.HexBytes(p))
   344  	}
   345  
   346  	return &pubkey, path, nil
   347  }
   348  
   349  // POST /list-pubkeys
   350  func (a *API) listPubKeys(ctx context.Context, ins struct {
   351  	AccountID    string `json:"account_id"`
   352  	AccountAlias string `json:"account_alias"`
   353  	PublicKey    string `json:"public_key"`
   354  }) Response {
   355  	var err error
   356  	account := &account.Account{}
   357  	if ins.AccountAlias != "" {
   358  		account, err = a.wallet.AccountMgr.FindByAlias(ins.AccountAlias)
   359  	} else {
   360  		account, err = a.wallet.AccountMgr.FindByID(ins.AccountID)
   361  	}
   362  
   363  	if err != nil {
   364  		return NewErrorResponse(err)
   365  	}
   366  
   367  	pubKeyInfos := []PubKeyInfo{}
   368  	if account.DeriveRule == signers.BIP0032 {
   369  		idx := a.wallet.AccountMgr.GetContractIndex(account.ID)
   370  		for i := uint64(1); i <= idx; i++ {
   371  			pubkey, path, err := getPubkey(account, false, i)
   372  			if err != nil {
   373  				return NewErrorResponse(err)
   374  			}
   375  			if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
   376  				continue
   377  			}
   378  			pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
   379  				Pubkey: hex.EncodeToString(*pubkey),
   380  				Path:   path,
   381  			})
   382  		}
   383  	} else if account.DeriveRule == signers.BIP0044 {
   384  		idx := a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, true)
   385  		for i := uint64(1); i <= idx; i++ {
   386  			pubkey, path, err := getPubkey(account, true, i)
   387  			if err != nil {
   388  				return NewErrorResponse(err)
   389  			}
   390  			if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
   391  				continue
   392  			}
   393  			pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
   394  				Pubkey: hex.EncodeToString(*pubkey),
   395  				Path:   path,
   396  			})
   397  		}
   398  
   399  		idx = a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, false)
   400  		for i := uint64(1); i <= idx; i++ {
   401  			pubkey, path, err := getPubkey(account, false, i)
   402  			if err != nil {
   403  				return NewErrorResponse(err)
   404  			}
   405  			if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
   406  				continue
   407  			}
   408  			pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
   409  				Pubkey: hex.EncodeToString(*pubkey),
   410  				Path:   path,
   411  			})
   412  		}
   413  	}
   414  
   415  	if len(pubKeyInfos) == 0 {
   416  		return NewErrorResponse(errors.New("Not found publickey for the account"))
   417  	}
   418  
   419  	return NewSuccessResponse(&AccountPubkey{
   420  		RootXPub:    account.XPubs[0],
   421  		PubKeyInfos: pubKeyInfos,
   422  	})
   423  }
   424  
   425  func (a *API) listAccountVotes(ctx context.Context, filter struct {
   426  	AccountID    string `json:"account_id"`
   427  	AccountAlias string `json:"account_alias"`
   428  }) Response {
   429  	accountID := filter.AccountID
   430  	if filter.AccountAlias != "" {
   431  		acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias)
   432  		if err != nil {
   433  			return NewErrorResponse(err)
   434  		}
   435  		accountID = acc.ID
   436  	}
   437  
   438  	votes, err := a.wallet.GetAccountVotes(accountID, "")
   439  	if err != nil {
   440  		return NewErrorResponse(err)
   441  	}
   442  	return NewSuccessResponse(votes)
   443  }