OSDN Git Service

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