OSDN Git Service

79ab3a9dd12998a23bb59ec4c33de55f710d82ce
[bytom/vapor.git] / api / query.go
1 package api
2
3 import (
4         "context"
5         "encoding/hex"
6         "fmt"
7
8         "github.com/vapor/blockchain/txbuilder"
9         "github.com/vapor/crypto/ed25519"
10
11         log "github.com/sirupsen/logrus"
12
13         "github.com/vapor/account"
14         "github.com/vapor/blockchain/query"
15         "github.com/vapor/blockchain/signers"
16         "github.com/vapor/consensus"
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         bytomtypes "github.com/vapor/protocol/bc/types/bytom/types"
23 )
24
25 // POST /list-accounts
26 func (a *API) listAccounts(ctx context.Context, filter struct {
27         ID    string `json:"id"`
28         Alias string `json:"alias"`
29 }) Response {
30         accountID := filter.ID
31         if filter.Alias != "" {
32                 acc, err := a.wallet.AccountMgr.FindByAlias(filter.Alias)
33                 if err != nil {
34                         return NewErrorResponse(err)
35                 }
36                 accountID = acc.ID
37         }
38
39         accounts, err := a.wallet.AccountMgr.ListAccounts(accountID)
40         if err != nil {
41                 log.Errorf("listAccounts: %v", err)
42                 return NewErrorResponse(err)
43         }
44
45         annotatedAccounts := []query.AnnotatedAccount{}
46         for _, acc := range accounts {
47                 annotatedAccounts = append(annotatedAccounts, *account.Annotated(acc))
48         }
49
50         return NewSuccessResponse(annotatedAccounts)
51 }
52
53 // POST /get-asset
54 func (a *API) getAsset(ctx context.Context, filter struct {
55         ID string `json:"id"`
56 }) Response {
57         asset, err := a.wallet.AssetReg.GetAsset(filter.ID)
58         if err != nil {
59                 log.Errorf("getAsset: %v", err)
60                 return NewErrorResponse(err)
61         }
62
63         return NewSuccessResponse(asset)
64 }
65
66 // POST /list-assets
67 func (a *API) listAssets(ctx context.Context, filter struct {
68         ID string `json:"id"`
69 }) Response {
70         assets, err := a.wallet.AssetReg.ListAssets(filter.ID)
71         if err != nil {
72                 log.Errorf("listAssets: %v", err)
73                 return NewErrorResponse(err)
74         }
75
76         return NewSuccessResponse(assets)
77 }
78
79 // POST /list-balances
80 func (a *API) listBalances(ctx context.Context, filter struct {
81         AccountID    string `json:"account_id"`
82         AccountAlias string `json:"account_alias"`
83 }) Response {
84         accountID := filter.AccountID
85         if filter.AccountAlias != "" {
86                 acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias)
87                 if err != nil {
88                         return NewErrorResponse(err)
89                 }
90                 accountID = acc.ID
91         }
92
93         balances, err := a.wallet.GetAccountBalances(accountID, "")
94         if err != nil {
95                 return NewErrorResponse(err)
96         }
97         return NewSuccessResponse(balances)
98 }
99
100 // POST /get-transaction
101 func (a *API) getTransaction(ctx context.Context, txInfo struct {
102         TxID string `json:"tx_id"`
103 }) Response {
104         var annotatedTx *query.AnnotatedTx
105         var err error
106
107         annotatedTx, err = a.wallet.GetTransactionByTxID(txInfo.TxID)
108         if err != nil {
109                 // transaction not found in blockchain db, search it from unconfirmed db
110                 annotatedTx, err = a.wallet.GetUnconfirmedTxByTxID(txInfo.TxID)
111                 if err != nil {
112                         return NewErrorResponse(err)
113                 }
114         }
115
116         return NewSuccessResponse(annotatedTx)
117 }
118
119 // POST /list-transactions
120 func (a *API) listTransactions(ctx context.Context, filter struct {
121         ID          string `json:"id"`
122         AccountID   string `json:"account_id"`
123         Detail      bool   `json:"detail"`
124         Unconfirmed bool   `json:"unconfirmed"`
125         From        uint   `json:"from"`
126         Count       uint   `json:"count"`
127 }) Response {
128         transactions := []*query.AnnotatedTx{}
129         var err error
130         var transaction *query.AnnotatedTx
131
132         if filter.ID != "" {
133                 transaction, err = a.wallet.GetTransactionByTxID(filter.ID)
134                 if err != nil && filter.Unconfirmed {
135                         transaction, err = a.wallet.GetUnconfirmedTxByTxID(filter.ID)
136                         if err != nil {
137                                 return NewErrorResponse(err)
138                         }
139                 }
140                 transactions = []*query.AnnotatedTx{transaction}
141         } else {
142                 transactions, err = a.wallet.GetTransactions(filter.AccountID)
143                 if err != nil {
144                         return NewErrorResponse(err)
145                 }
146
147                 if filter.Unconfirmed {
148                         unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID)
149                         if err != nil {
150                                 return NewErrorResponse(err)
151                         }
152                         transactions = append(unconfirmedTxs, transactions...)
153                 }
154         }
155
156         if filter.Detail == false {
157                 txSummary := a.wallet.GetTransactionsSummary(transactions)
158                 start, end := getPageRange(len(txSummary), filter.From, filter.Count)
159                 return NewSuccessResponse(txSummary[start:end])
160         }
161         start, end := getPageRange(len(transactions), filter.From, filter.Count)
162         return NewSuccessResponse(transactions[start:end])
163 }
164
165 // POST /get-unconfirmed-transaction
166 func (a *API) getUnconfirmedTx(ctx context.Context, filter struct {
167         TxID chainjson.HexBytes `json:"tx_id"`
168 }) Response {
169         var tmpTxID [32]byte
170         copy(tmpTxID[:], filter.TxID[:])
171
172         txHash := bc.NewHash(tmpTxID)
173         txPool := a.chain.GetTxPool()
174         txDesc, err := txPool.GetTransaction(&txHash)
175         if err != nil {
176                 return NewErrorResponse(err)
177         }
178
179         tx := &BlockTx{
180                 ID:         txDesc.Tx.ID,
181                 Version:    txDesc.Tx.Version,
182                 Size:       txDesc.Tx.SerializedSize,
183                 TimeRange:  txDesc.Tx.TimeRange,
184                 Inputs:     []*query.AnnotatedInput{},
185                 Outputs:    []*query.AnnotatedOutput{},
186                 StatusFail: txDesc.StatusFail,
187         }
188
189         resOutID := txDesc.Tx.ResultIds[0]
190         resOut := txDesc.Tx.Entries[*resOutID]
191         switch out := resOut.(type) {
192         case *bc.Output:
193                 tx.MuxID = *out.Source.Ref
194         case *bc.Retirement:
195                 tx.MuxID = *out.Source.Ref
196         }
197
198         for i := range txDesc.Tx.Inputs {
199                 tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(txDesc.Tx, uint32(i)))
200         }
201         for i := range txDesc.Tx.Outputs {
202                 tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(txDesc.Tx, i))
203         }
204
205         return NewSuccessResponse(tx)
206 }
207
208 type unconfirmedTxsResp struct {
209         Total uint64    `json:"total"`
210         TxIDs []bc.Hash `json:"tx_ids"`
211 }
212
213 // POST /list-unconfirmed-transactions
214 func (a *API) listUnconfirmedTxs(ctx context.Context) Response {
215         txIDs := []bc.Hash{}
216
217         txPool := a.chain.GetTxPool()
218         txs := txPool.GetTransactions()
219         for _, txDesc := range txs {
220                 txIDs = append(txIDs, bc.Hash(txDesc.Tx.ID))
221         }
222
223         return NewSuccessResponse(&unconfirmedTxsResp{
224                 Total: uint64(len(txIDs)),
225                 TxIDs: txIDs,
226         })
227 }
228
229 // RawTx is the tx struct for getRawTransaction
230 type RawTx struct {
231         ID        bc.Hash                  `json:"tx_id"`
232         Version   uint64                   `json:"version"`
233         Size      uint64                   `json:"size"`
234         TimeRange uint64                   `json:"time_range"`
235         Inputs    []*query.AnnotatedInput  `json:"inputs"`
236         Outputs   []*query.AnnotatedOutput `json:"outputs"`
237         Fee       uint64                   `json:"fee"`
238 }
239
240 // POST /decode-raw-transaction
241 func (a *API) decodeRawTransaction(ctx context.Context, ins struct {
242         Tx types.Tx `json:"raw_transaction"`
243 }) Response {
244         tx := &RawTx{
245                 ID:        ins.Tx.ID,
246                 Version:   ins.Tx.Version,
247                 Size:      ins.Tx.SerializedSize,
248                 TimeRange: ins.Tx.TimeRange,
249                 Inputs:    []*query.AnnotatedInput{},
250                 Outputs:   []*query.AnnotatedOutput{},
251         }
252
253         for i := range ins.Tx.Inputs {
254                 tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(&ins.Tx, uint32(i)))
255         }
256         for i := range ins.Tx.Outputs {
257                 tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i))
258         }
259
260         tx.Fee = txbuilder.CalculateTxFee(&ins.Tx)
261         return NewSuccessResponse(tx)
262 }
263
264 // POST /list-unspent-outputs
265 func (a *API) listUnspentOutputs(ctx context.Context, filter struct {
266         AccountID     string `json:"account_id"`
267         AccountAlias  string `json:"account_alias"`
268         ID            string `json:"id"`
269         Unconfirmed   bool   `json:"unconfirmed"`
270         SmartContract bool   `json:"smart_contract"`
271         From          uint   `json:"from"`
272         Count         uint   `json:"count"`
273 }) Response {
274         accountID := filter.AccountID
275         if filter.AccountAlias != "" {
276                 acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias)
277                 if err != nil {
278                         return NewErrorResponse(err)
279                 }
280                 accountID = acc.ID
281         }
282         accountUTXOs := a.wallet.GetAccountUtxos(accountID, filter.ID, filter.Unconfirmed, filter.SmartContract)
283
284         UTXOs := []query.AnnotatedUTXO{}
285         for _, utxo := range accountUTXOs {
286                 UTXOs = append([]query.AnnotatedUTXO{{
287                         AccountID:           utxo.AccountID,
288                         OutputID:            utxo.OutputID.String(),
289                         SourceID:            utxo.SourceID.String(),
290                         AssetID:             utxo.AssetID.String(),
291                         Amount:              utxo.Amount,
292                         SourcePos:           utxo.SourcePos,
293                         Program:             fmt.Sprintf("%x", utxo.ControlProgram),
294                         ControlProgramIndex: utxo.ControlProgramIndex,
295                         Address:             utxo.Address,
296                         ValidHeight:         utxo.ValidHeight,
297                         Alias:               a.wallet.AccountMgr.GetAliasByID(utxo.AccountID),
298                         AssetAlias:          a.wallet.AssetReg.GetAliasByID(utxo.AssetID.String()),
299                         Change:              utxo.Change,
300                 }}, UTXOs...)
301         }
302         start, end := getPageRange(len(UTXOs), filter.From, filter.Count)
303         return NewSuccessResponse(UTXOs[start:end])
304 }
305
306 // return gasRate
307 func (a *API) gasRate() Response {
308         gasrate := map[string]int64{"gas_rate": consensus.VMGasRate}
309         return NewSuccessResponse(gasrate)
310 }
311
312 // PubKeyInfo is structure of pubkey info
313 type PubKeyInfo struct {
314         Pubkey string               `json:"pubkey"`
315         Path   []chainjson.HexBytes `json:"derivation_path"`
316 }
317
318 // AccountPubkey is detail of account pubkey info
319 type AccountPubkey struct {
320         RootXPub    chainkd.XPub `json:"root_xpub"`
321         PubKeyInfos []PubKeyInfo `json:"pubkey_infos"`
322 }
323
324 func getPubkey(account *account.Account, change bool, index uint64) (*ed25519.PublicKey, []chainjson.HexBytes, error) {
325         rawPath, err := signers.Path(account.Signer, signers.AccountKeySpace, change, index)
326         if err != nil {
327                 return nil, nil, err
328         }
329         derivedXPub := account.XPubs[0].Derive(rawPath)
330         pubkey := derivedXPub.PublicKey()
331         var path []chainjson.HexBytes
332         for _, p := range rawPath {
333                 path = append(path, chainjson.HexBytes(p))
334         }
335
336         return &pubkey, path, nil
337 }
338
339 // POST /list-pubkeys
340 func (a *API) listPubKeys(ctx context.Context, ins struct {
341         AccountID    string `json:"account_id"`
342         AccountAlias string `json:"account_alias"`
343         PublicKey    string `json:"public_key"`
344 }) Response {
345         var err error
346         account := &account.Account{}
347         if ins.AccountAlias != "" {
348                 account, err = a.wallet.AccountMgr.FindByAlias(ins.AccountAlias)
349         } else {
350                 account, err = a.wallet.AccountMgr.FindByID(ins.AccountID)
351         }
352
353         if err != nil {
354                 return NewErrorResponse(err)
355         }
356
357         pubKeyInfos := []PubKeyInfo{}
358         if account.DeriveRule == signers.BIP0032 {
359                 idx := a.wallet.AccountMgr.GetContractIndex(account.ID)
360                 for i := uint64(1); i <= idx; i++ {
361                         pubkey, path, err := getPubkey(account, false, i)
362                         if err != nil {
363                                 return NewErrorResponse(err)
364                         }
365                         if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
366                                 continue
367                         }
368                         pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
369                                 Pubkey: hex.EncodeToString(*pubkey),
370                                 Path:   path,
371                         })
372                 }
373         } else if account.DeriveRule == signers.BIP0044 {
374                 idx := a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, true)
375                 for i := uint64(1); i <= idx; i++ {
376                         pubkey, path, err := getPubkey(account, true, i)
377                         if err != nil {
378                                 return NewErrorResponse(err)
379                         }
380                         if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
381                                 continue
382                         }
383                         pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
384                                 Pubkey: hex.EncodeToString(*pubkey),
385                                 Path:   path,
386                         })
387                 }
388
389                 idx = a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, false)
390                 for i := uint64(1); i <= idx; i++ {
391                         pubkey, path, err := getPubkey(account, false, i)
392                         if err != nil {
393                                 return NewErrorResponse(err)
394                         }
395                         if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) {
396                                 continue
397                         }
398                         pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
399                                 Pubkey: hex.EncodeToString(*pubkey),
400                                 Path:   path,
401                         })
402                 }
403         }
404
405         if len(pubKeyInfos) == 0 {
406                 return NewErrorResponse(errors.New("Not found publickey for the account"))
407         }
408
409         return NewSuccessResponse(&AccountPubkey{
410                 RootXPub:    account.XPubs[0],
411                 PubKeyInfos: pubKeyInfos,
412         })
413 }
414
415 type GetRawTransationResp struct {
416         Tx        *bytomtypes.Tx `json:"raw_transaction"`
417         BlockHash bc.Hash        `json:"block_hash"`
418 }
419
420 func (a *API) getRawTransaction(ins struct {
421         RawBlock string `json:"raw_block"`
422         TxID     string `json:"tx_id"`
423 }) Response {
424
425         var rawTransaction *bytomtypes.Tx
426         block := &bytomtypes.Block{}
427         err := block.UnmarshalText([]byte(ins.RawBlock))
428         if err != nil {
429                 return NewErrorResponse(err)
430         }
431
432         txID := bc.Hash{}
433         txID.UnmarshalText([]byte(ins.TxID))
434         for _, tx := range block.Transactions {
435                 if tx.ID.String() == txID.String() {
436                         rawTransaction = tx
437                         break
438                 }
439         }
440         if rawTransaction == nil {
441                 return NewErrorResponse(errors.New("raw transaction do not find"))
442         }
443         resp := GetRawTransationResp{Tx: rawTransaction, BlockHash: block.Hash()}
444         return NewSuccessResponse(resp)
445 }
446
447 type GetSideRawTransationResp struct {
448         Tx        *types.Tx `json:"raw_transaction"`
449         BlockHash bc.Hash   `json:"block_hash"`
450 }
451
452 func (a *API) getSideRawTransaction(ins struct {
453         BlockHeight uint64 `json:"block_height"`
454         TxID        string `json:"tx_id"`
455 }) Response {
456         block, err := a.chain.GetBlockByHeight(ins.BlockHeight)
457         if err != nil {
458                 return NewErrorResponse(err)
459         }
460
461         var rawTransaction *types.Tx
462
463         txID := bc.Hash{}
464         txID.UnmarshalText([]byte(ins.TxID))
465
466         for _, tx := range block.Transactions {
467                 if tx.ID.String() == txID.String() {
468                         rawTransaction = tx
469                         break
470                 }
471         }
472         if rawTransaction == nil {
473                 return NewErrorResponse(errors.New("raw transaction do not find"))
474         }
475         resp := GetSideRawTransationResp{Tx: rawTransaction, BlockHash: block.Hash()}
476         return NewSuccessResponse(resp)
477 }
478
479 type utxoResp struct {
480         Utxo account.UTXO `json:"utxo"`
481 }
482
483 func (a *API) getUnspentOutputs(ins struct {
484         RawBlock string `json:"raw_block"`
485         TxID     string `json:"tx_id"`
486         ID       string `json:"id"`
487         Address  string `json:"address"`
488 }) Response {
489         var rawTransaction *bytomtypes.Tx
490         block := &bytomtypes.Block{}
491         err := block.UnmarshalText([]byte(ins.RawBlock))
492         if err != nil {
493                 return NewErrorResponse(err)
494         }
495
496         txID := bc.Hash{}
497         txID.UnmarshalText([]byte(ins.TxID))
498
499         for _, tx := range block.Transactions {
500                 if tx.ID.String() == txID.String() {
501                         rawTransaction = tx
502                         break
503                 }
504         }
505         utxo := account.UTXO{}
506
507         for i, out := range rawTransaction.Outputs {
508                 key := rawTransaction.ResultIds[i]
509                 if key.String() == ins.ID {
510                         outPut, err := rawTransaction.Output(*key)
511                         if err != nil {
512                                 continue
513                         }
514                         outputID := bc.Hash{
515                                 V0: key.GetV0(),
516                                 V1: key.GetV1(),
517                                 V2: key.GetV2(),
518                                 V3: key.GetV3(),
519                         }
520
521                         assetID := bc.AssetID{
522                                 V0: out.AssetAmount.AssetId.GetV0(),
523                                 V1: out.AssetAmount.AssetId.GetV1(),
524                                 V2: out.AssetAmount.AssetId.GetV2(),
525                                 V3: out.AssetAmount.AssetId.GetV3(),
526                         }
527
528                         sourceID := bc.Hash{
529                                 V0: outPut.Source.Ref.GetV0(),
530                                 V1: outPut.Source.Ref.GetV1(),
531                                 V2: outPut.Source.Ref.GetV2(),
532                                 V3: outPut.Source.Ref.GetV3(),
533                         }
534                         utxo = account.UTXO{
535                                 OutputID:       outputID,
536                                 AssetID:        assetID,
537                                 Amount:         out.Amount,
538                                 ControlProgram: out.ControlProgram,
539                                 SourceID:       sourceID,
540                                 SourcePos:      outPut.Source.Position,
541                                 ValidHeight:    block.Height,
542                                 Address:        ins.Address,
543                         }
544                 }
545         }
546
547         return NewSuccessResponse(&utxoResp{Utxo: utxo})
548 }