OSDN Git Service

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