OSDN Git Service

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