OSDN Git Service

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