OSDN Git Service

Hulk did something
[bytom/vapor.git] / api / query.go
diff --git a/api/query.go b/api/query.go
new file mode 100644 (file)
index 0000000..7f29e07
--- /dev/null
@@ -0,0 +1,425 @@
+package api
+
+import (
+       "context"
+       "encoding/hex"
+       "fmt"
+
+       log "github.com/sirupsen/logrus"
+
+       "github.com/vapor/account"
+       "github.com/vapor/asset"
+       "github.com/vapor/blockchain/query"
+       "github.com/vapor/blockchain/signers"
+       "github.com/vapor/blockchain/txbuilder"
+       "github.com/vapor/consensus"
+       "github.com/vapor/crypto/ed25519"
+       "github.com/vapor/crypto/ed25519/chainkd"
+       chainjson "github.com/vapor/encoding/json"
+       "github.com/vapor/errors"
+       "github.com/vapor/protocol/bc"
+       "github.com/vapor/protocol/bc/types"
+)
+
+// POST /list-accounts
+func (a *API) listAccounts(ctx context.Context, filter struct {
+       ID    string `json:"id"`
+       Alias string `json:"alias"`
+}) Response {
+       accountID := filter.ID
+       if filter.Alias != "" {
+               acc, err := a.wallet.AccountMgr.FindByAlias(filter.Alias)
+               if err != nil {
+                       return NewErrorResponse(err)
+               }
+               accountID = acc.ID
+       }
+
+       accounts, err := a.wallet.AccountMgr.ListAccounts(accountID)
+       if err != nil {
+               log.Errorf("listAccounts: %v", err)
+               return NewErrorResponse(err)
+       }
+
+       annotatedAccounts := []query.AnnotatedAccount{}
+       for _, acc := range accounts {
+               annotatedAccounts = append(annotatedAccounts, *account.Annotated(acc))
+       }
+
+       return NewSuccessResponse(annotatedAccounts)
+}
+
+// POST /get-asset
+func (a *API) getAsset(ctx context.Context, filter struct {
+       ID string `json:"id"`
+}) Response {
+       ass, err := a.wallet.AssetReg.GetAsset(filter.ID)
+       if err != nil {
+               log.Errorf("getAsset: %v", err)
+               return NewErrorResponse(err)
+       }
+
+       annotatedAsset, err := asset.Annotated(ass)
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+       return NewSuccessResponse(annotatedAsset)
+}
+
+// POST /list-assets
+func (a *API) listAssets(ctx context.Context, filter struct {
+       ID string `json:"id"`
+}) Response {
+       assets, err := a.wallet.AssetReg.ListAssets(filter.ID)
+       if err != nil {
+               log.Errorf("listAssets: %v", err)
+               return NewErrorResponse(err)
+       }
+
+       annotatedAssets := []*query.AnnotatedAsset{}
+       for _, ass := range assets {
+               annotatedAsset, err := asset.Annotated(ass)
+               if err != nil {
+                       return NewErrorResponse(err)
+               }
+               annotatedAssets = append(annotatedAssets, annotatedAsset)
+       }
+       return NewSuccessResponse(annotatedAssets)
+}
+
+// POST /list-balances
+func (a *API) listBalances(ctx context.Context, filter struct {
+       AccountID    string `json:"account_id"`
+       AccountAlias string `json:"account_alias"`
+}) Response {
+       accountID := filter.AccountID
+       if filter.AccountAlias != "" {
+               acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias)
+               if err != nil {
+                       return NewErrorResponse(err)
+               }
+               accountID = acc.ID
+       }
+
+       balances, err := a.wallet.GetAccountBalances(accountID, "")
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+       return NewSuccessResponse(balances)
+}
+
+// POST /get-transaction
+func (a *API) getTransaction(ctx context.Context, txInfo struct {
+       TxID string `json:"tx_id"`
+}) Response {
+       var annotatedTx *query.AnnotatedTx
+       var err error
+
+       annotatedTx, err = a.wallet.GetTransactionByTxID(txInfo.TxID)
+       if err != nil {
+               // transaction not found in blockchain db, search it from unconfirmed db
+               annotatedTx, err = a.wallet.GetUnconfirmedTxByTxID(txInfo.TxID)
+               if err != nil {
+                       return NewErrorResponse(err)
+               }
+       }
+
+       return NewSuccessResponse(annotatedTx)
+}
+
+// POST /list-transactions
+func (a *API) listTransactions(ctx context.Context, filter struct {
+       ID          string `json:"id"`
+       AccountID   string `json:"account_id"`
+       Detail      bool   `json:"detail"`
+       Unconfirmed bool   `json:"unconfirmed"`
+       From        uint   `json:"from"`
+       Count       uint   `json:"count"`
+}) Response {
+       transactions := []*query.AnnotatedTx{}
+       var err error
+       var transaction *query.AnnotatedTx
+
+       if filter.ID != "" {
+               transaction, err = a.wallet.GetTransactionByTxID(filter.ID)
+               if err != nil && filter.Unconfirmed {
+                       transaction, err = a.wallet.GetUnconfirmedTxByTxID(filter.ID)
+               }
+
+               if err != nil {
+                       return NewErrorResponse(err)
+               }
+               transactions = []*query.AnnotatedTx{transaction}
+       } else {
+               transactions, err = a.wallet.GetTransactions(filter.AccountID)
+               if err != nil {
+                       return NewErrorResponse(err)
+               }
+
+               if filter.Unconfirmed {
+                       unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID)
+                       if err != nil {
+                               return NewErrorResponse(err)
+                       }
+                       transactions = append(unconfirmedTxs, transactions...)
+               }
+       }
+
+       if filter.Detail == false {
+               txSummary := a.wallet.GetTransactionsSummary(transactions)
+               start, end := getPageRange(len(txSummary), filter.From, filter.Count)
+               return NewSuccessResponse(txSummary[start:end])
+       }
+       start, end := getPageRange(len(transactions), filter.From, filter.Count)
+       return NewSuccessResponse(transactions[start:end])
+}
+
+// POST /get-unconfirmed-transaction
+func (a *API) getUnconfirmedTx(ctx context.Context, filter struct {
+       TxID chainjson.HexBytes `json:"tx_id"`
+}) Response {
+       var tmpTxID [32]byte
+       copy(tmpTxID[:], filter.TxID[:])
+
+       txHash := bc.NewHash(tmpTxID)
+       txPool := a.chain.GetTxPool()
+       txDesc, err := txPool.GetTransaction(&txHash)
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+
+       tx := &BlockTx{
+               ID:         txDesc.Tx.ID,
+               Version:    txDesc.Tx.Version,
+               Size:       txDesc.Tx.SerializedSize,
+               TimeRange:  txDesc.Tx.TimeRange,
+               Inputs:     []*query.AnnotatedInput{},
+               Outputs:    []*query.AnnotatedOutput{},
+               StatusFail: txDesc.StatusFail,
+       }
+
+       resOutID := txDesc.Tx.ResultIds[0]
+       resOut := txDesc.Tx.Entries[*resOutID]
+       switch out := resOut.(type) {
+       case *bc.Output:
+               tx.MuxID = *out.Source.Ref
+       case *bc.Retirement:
+               tx.MuxID = *out.Source.Ref
+       }
+
+       for i := range txDesc.Tx.Inputs {
+               tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(txDesc.Tx, uint32(i)))
+       }
+       for i := range txDesc.Tx.Outputs {
+               tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(txDesc.Tx, i))
+       }
+
+       return NewSuccessResponse(tx)
+}
+
+type unconfirmedTxsResp struct {
+       Total uint64    `json:"total"`
+       TxIDs []bc.Hash `json:"tx_ids"`
+}
+
+// POST /list-unconfirmed-transactions
+func (a *API) listUnconfirmedTxs(ctx context.Context) Response {
+       txIDs := []bc.Hash{}
+
+       txPool := a.chain.GetTxPool()
+       txs := txPool.GetTransactions()
+       for _, txDesc := range txs {
+               txIDs = append(txIDs, bc.Hash(txDesc.Tx.ID))
+       }
+
+       return NewSuccessResponse(&unconfirmedTxsResp{
+               Total: uint64(len(txIDs)),
+               TxIDs: txIDs,
+       })
+}
+
+// RawTx is the tx struct for getRawTransaction
+type RawTx struct {
+       ID        bc.Hash                  `json:"tx_id"`
+       Version   uint64                   `json:"version"`
+       Size      uint64                   `json:"size"`
+       TimeRange uint64                   `json:"time_range"`
+       Inputs    []*query.AnnotatedInput  `json:"inputs"`
+       Outputs   []*query.AnnotatedOutput `json:"outputs"`
+       Fee       uint64                   `json:"fee"`
+}
+
+// POST /decode-raw-transaction
+func (a *API) decodeRawTransaction(ctx context.Context, ins struct {
+       Tx types.Tx `json:"raw_transaction"`
+}) Response {
+       tx := &RawTx{
+               ID:        ins.Tx.ID,
+               Version:   ins.Tx.Version,
+               Size:      ins.Tx.SerializedSize,
+               TimeRange: ins.Tx.TimeRange,
+               Inputs:    []*query.AnnotatedInput{},
+               Outputs:   []*query.AnnotatedOutput{},
+       }
+
+       for i := range ins.Tx.Inputs {
+               tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(&ins.Tx, uint32(i)))
+       }
+       for i := range ins.Tx.Outputs {
+               tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i))
+       }
+
+       tx.Fee = txbuilder.CalculateTxFee(&ins.Tx)
+       return NewSuccessResponse(tx)
+}
+
+// POST /list-unspent-outputs
+func (a *API) listUnspentOutputs(ctx context.Context, filter struct {
+       AccountID     string `json:"account_id"`
+       AccountAlias  string `json:"account_alias"`
+       ID            string `json:"id"`
+       Unconfirmed   bool   `json:"unconfirmed"`
+       SmartContract bool   `json:"smart_contract"`
+       From          uint   `json:"from"`
+       Count         uint   `json:"count"`
+}) Response {
+       accountID := filter.AccountID
+       if filter.AccountAlias != "" {
+               acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias)
+               if err != nil {
+                       return NewErrorResponse(err)
+               }
+               accountID = acc.ID
+       }
+       accountUTXOs := a.wallet.GetAccountUtxos(accountID, filter.ID, filter.Unconfirmed, filter.SmartContract)
+
+       UTXOs := []query.AnnotatedUTXO{}
+       for _, utxo := range accountUTXOs {
+               UTXOs = append([]query.AnnotatedUTXO{{
+                       AccountID:           utxo.AccountID,
+                       OutputID:            utxo.OutputID.String(),
+                       SourceID:            utxo.SourceID.String(),
+                       AssetID:             utxo.AssetID.String(),
+                       Amount:              utxo.Amount,
+                       SourcePos:           utxo.SourcePos,
+                       Program:             fmt.Sprintf("%x", utxo.ControlProgram),
+                       ControlProgramIndex: utxo.ControlProgramIndex,
+                       Address:             utxo.Address,
+                       ValidHeight:         utxo.ValidHeight,
+                       Alias:               a.wallet.AccountMgr.GetAliasByID(utxo.AccountID),
+                       AssetAlias:          a.wallet.AssetReg.GetAliasByID(utxo.AssetID.String()),
+                       Change:              utxo.Change,
+               }}, UTXOs...)
+       }
+       start, end := getPageRange(len(UTXOs), filter.From, filter.Count)
+       return NewSuccessResponse(UTXOs[start:end])
+}
+
+// return gasRate
+func (a *API) gasRate() Response {
+       gasrate := map[string]int64{"gas_rate": consensus.VMGasRate}
+       return NewSuccessResponse(gasrate)
+}
+
+// PubKeyInfo is structure of pubkey info
+type PubKeyInfo struct {
+       Pubkey string               `json:"pubkey"`
+       Path   []chainjson.HexBytes `json:"derivation_path"`
+}
+
+// AccountPubkey is detail of account pubkey info
+type AccountPubkey struct {
+       RootXPub    chainkd.XPub `json:"root_xpub"`
+       PubKeyInfos []PubKeyInfo `json:"pubkey_infos"`
+}
+
+func getPubkey(account *account.Account, change bool, index uint64) (*ed25519.PublicKey, []chainjson.HexBytes, error) {
+       rawPath, err := signers.Path(account.Signer, signers.AccountKeySpace, change, index)
+       if err != nil {
+               return nil, nil, err
+       }
+       derivedXPub := account.XPubs[0].Derive(rawPath)
+       pubkey := derivedXPub.PublicKey()
+       var path []chainjson.HexBytes
+       for _, p := range rawPath {
+               path = append(path, chainjson.HexBytes(p))
+       }
+
+       return &pubkey, path, nil
+}
+
+// POST /list-pubkeys
+func (a *API) listPubKeys(ctx context.Context, ins struct {
+       AccountID    string `json:"account_id"`
+       AccountAlias string `json:"account_alias"`
+       PublicKey    string `json:"public_key"`
+}) Response {
+       var err error
+       account := &account.Account{}
+       if ins.AccountAlias != "" {
+               account, err = a.wallet.AccountMgr.FindByAlias(ins.AccountAlias)
+       } else {
+               account, err = a.wallet.AccountMgr.FindByID(ins.AccountID)
+       }
+
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+
+       pubKeyInfos := []PubKeyInfo{}
+       if account.DeriveRule == signers.BIP0032 {
+               idx := a.wallet.AccountMgr.GetContractIndex(account.ID)
+               for i := uint64(1); i <= idx; i++ {
+                       pubkey, path, err := getPubkey(account, false, i)
+                       if err != nil {
+                               return NewErrorResponse(err)
+                       }
+                       if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
+                               continue
+                       }
+                       pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
+                               Pubkey: hex.EncodeToString(*pubkey),
+                               Path:   path,
+                       })
+               }
+       } else if account.DeriveRule == signers.BIP0044 {
+               idx := a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, true)
+               for i := uint64(1); i <= idx; i++ {
+                       pubkey, path, err := getPubkey(account, true, i)
+                       if err != nil {
+                               return NewErrorResponse(err)
+                       }
+                       if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
+                               continue
+                       }
+                       pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
+                               Pubkey: hex.EncodeToString(*pubkey),
+                               Path:   path,
+                       })
+               }
+
+               idx = a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, false)
+               for i := uint64(1); i <= idx; i++ {
+                       pubkey, path, err := getPubkey(account, false, i)
+                       if err != nil {
+                               return NewErrorResponse(err)
+                       }
+                       if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
+                               continue
+                       }
+                       pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
+                               Pubkey: hex.EncodeToString(*pubkey),
+                               Path:   path,
+                       })
+               }
+       }
+
+       if len(pubKeyInfos) == 0 {
+               return NewErrorResponse(errors.New("Not found publickey for the account"))
+       }
+
+       return NewSuccessResponse(&AccountPubkey{
+               RootXPub:    account.XPubs[0],
+               PubKeyInfos: pubKeyInfos,
+       })
+}