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

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