OSDN Git Service

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