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/common/arithmetic" "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) } func (a *API) listAccountVotes(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 } votes, err := a.wallet.GetAccountVotes(accountID, "") if err != nil { return NewErrorResponse(err) } return NewSuccessResponse(votes) } // 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 { AccountID string `json:"account_id"` AccountAlias string `json:"account_alias"` StartTxID string `json:"start_tx_id"` Detail bool `json:"detail"` Unconfirmed bool `json:"unconfirmed"` 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 } if accountID == "" { return NewErrorResponse(account.ErrAccountIDEmpty) } transactions, err := a.wallet.GetTransactions(accountID, filter.StartTxID, filter.Count, filter.Unconfirmed) if err != nil { return NewErrorResponse(err) } if filter.Detail == false { txSummary := a.wallet.GetTransactionsSummary(transactions) return NewSuccessResponse(txSummary) } return NewSuccessResponse(transactions) } // 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.IntraChainOutput: tx.MuxID = *out.Source.Ref case *bc.VoteOutput: 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, _ = arithmetic.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, false) 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.ActiveNetParams.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, }) }