OSDN Git Service

Merge pull request #1260 from Bytom/dev
[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/account"
11         "github.com/bytom/blockchain/query"
12         "github.com/bytom/blockchain/signers"
13         "github.com/bytom/consensus"
14         "github.com/bytom/crypto/ed25519/chainkd"
15         chainjson "github.com/bytom/encoding/json"
16         "github.com/bytom/errors"
17         "github.com/bytom/protocol/bc"
18         "github.com/bytom/protocol/bc/types"
19 )
20
21 // POST /list-accounts
22 func (a *API) listAccounts(ctx context.Context, filter struct {
23         ID    string `json:"id"`
24         Alias string `json:"alias"`
25 }) Response {
26         accountID := filter.ID
27         if filter.Alias != "" {
28                 acc, err := a.wallet.AccountMgr.FindByAlias(filter.Alias)
29                 if err != nil {
30                         return NewErrorResponse(err)
31                 }
32                 accountID = acc.ID
33         }
34
35         accounts, err := a.wallet.AccountMgr.ListAccounts(accountID)
36         if err != nil {
37                 log.Errorf("listAccounts: %v", err)
38                 return NewErrorResponse(err)
39         }
40
41         annotatedAccounts := []query.AnnotatedAccount{}
42         for _, acc := range accounts {
43                 annotatedAccounts = append(annotatedAccounts, *account.Annotated(acc))
44         }
45
46         return NewSuccessResponse(annotatedAccounts)
47 }
48
49 // POST /get-asset
50 func (a *API) getAsset(ctx context.Context, filter struct {
51         ID string `json:"id"`
52 }) Response {
53         asset, err := a.wallet.AssetReg.GetAsset(filter.ID)
54         if err != nil {
55                 log.Errorf("getAsset: %v", err)
56                 return NewErrorResponse(err)
57         }
58
59         return NewSuccessResponse(asset)
60 }
61
62 // POST /list-assets
63 func (a *API) listAssets(ctx context.Context, filter struct {
64         ID string `json:"id"`
65 }) Response {
66         assets, err := a.wallet.AssetReg.ListAssets(filter.ID)
67         if err != nil {
68                 log.Errorf("listAssets: %v", err)
69                 return NewErrorResponse(err)
70         }
71
72         return NewSuccessResponse(assets)
73 }
74
75 // POST /list-balances
76 func (a *API) listBalances(ctx context.Context) Response {
77         balances, err := a.wallet.GetAccountBalances("")
78         if err != nil {
79                 return NewErrorResponse(err)
80         }
81         return NewSuccessResponse(balances)
82 }
83
84 // POST /get-transaction
85 func (a *API) getTransaction(ctx context.Context, txInfo struct {
86         TxID string `json:"tx_id"`
87 }) Response {
88         var annotatedTx *query.AnnotatedTx
89         var err error
90
91         annotatedTx, err = a.wallet.GetTransactionByTxID(txInfo.TxID)
92         if err != nil {
93                 // transaction not found in blockchain db, search it from unconfirmed db
94                 annotatedTx, err = a.wallet.GetUnconfirmedTxByTxID(txInfo.TxID)
95                 if err != nil {
96                         return NewErrorResponse(err)
97                 }
98         }
99
100         return NewSuccessResponse(annotatedTx)
101 }
102
103 // POST /list-transactions
104 func (a *API) listTransactions(ctx context.Context, filter struct {
105         ID          string `json:"id"`
106         AccountID   string `json:"account_id"`
107         Detail      bool   `json:"detail"`
108         Unconfirmed bool   `json:"unconfirmed"`
109         From        uint   `json:"from"`
110         Count       uint   `json:"count"`
111 }) Response {
112         transactions := []*query.AnnotatedTx{}
113         var err error
114         var transaction *query.AnnotatedTx
115
116         if filter.ID != "" {
117                 transaction, err = a.wallet.GetTransactionByTxID(filter.ID)
118                 if err != nil && filter.Unconfirmed {
119                         transaction, err = a.wallet.GetUnconfirmedTxByTxID(filter.ID)
120                         if err != nil {
121                                 return NewErrorResponse(err)
122                         }
123                 }
124                 transactions = []*query.AnnotatedTx{transaction}
125         } else {
126                 transactions, err = a.wallet.GetTransactions(filter.AccountID)
127                 if err != nil {
128                         return NewErrorResponse(err)
129                 }
130
131                 if filter.Unconfirmed {
132                         unconfirmedTxs, err := a.wallet.GetUnconfirmedTxs(filter.AccountID)
133                         if err != nil {
134                                 return NewErrorResponse(err)
135                         }
136                         transactions = append(unconfirmedTxs, transactions...)
137                 }
138         }
139
140         if filter.Detail == false {
141                 txSummary := a.wallet.GetTransactionsSummary(transactions)
142                 start, end := getPageRange(len(txSummary), filter.From, filter.Count)
143                 return NewSuccessResponse(txSummary[start:end])
144         }
145         start, end := getPageRange(len(transactions), filter.From, filter.Count)
146         return NewSuccessResponse(transactions[start:end])
147 }
148
149 // POST /get-unconfirmed-transaction
150 func (a *API) getUnconfirmedTx(ctx context.Context, filter struct {
151         TxID chainjson.HexBytes `json:"tx_id"`
152 }) Response {
153         var tmpTxID [32]byte
154         copy(tmpTxID[:], filter.TxID[:])
155
156         txHash := bc.NewHash(tmpTxID)
157         txPool := a.chain.GetTxPool()
158         txDesc, err := txPool.GetTransaction(&txHash)
159         if err != nil {
160                 return NewErrorResponse(err)
161         }
162
163         tx := &BlockTx{
164                 ID:         txDesc.Tx.ID,
165                 Version:    txDesc.Tx.Version,
166                 Size:       txDesc.Tx.SerializedSize,
167                 TimeRange:  txDesc.Tx.TimeRange,
168                 Inputs:     []*query.AnnotatedInput{},
169                 Outputs:    []*query.AnnotatedOutput{},
170                 StatusFail: false,
171         }
172
173         for i := range txDesc.Tx.Inputs {
174                 tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(txDesc.Tx, uint32(i)))
175         }
176         for i := range txDesc.Tx.Outputs {
177                 tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(txDesc.Tx, i))
178         }
179
180         return NewSuccessResponse(tx)
181 }
182
183 type unconfirmedTxsResp struct {
184         Total uint64    `json:"total"`
185         TxIDs []bc.Hash `json:"tx_ids"`
186 }
187
188 // POST /list-unconfirmed-transactions
189 func (a *API) listUnconfirmedTxs(ctx context.Context) Response {
190         txIDs := []bc.Hash{}
191
192         txPool := a.chain.GetTxPool()
193         txs := txPool.GetTransactions()
194         for _, txDesc := range txs {
195                 txIDs = append(txIDs, bc.Hash(txDesc.Tx.ID))
196         }
197
198         return NewSuccessResponse(&unconfirmedTxsResp{
199                 Total: uint64(len(txIDs)),
200                 TxIDs: txIDs,
201         })
202 }
203
204 // RawTx is the tx struct for getRawTransaction
205 type RawTx struct {
206         ID        bc.Hash                  `json:"tx_id"`
207         Version   uint64                   `json:"version"`
208         Size      uint64                   `json:"size"`
209         TimeRange uint64                   `json:"time_range"`
210         Inputs    []*query.AnnotatedInput  `json:"inputs"`
211         Outputs   []*query.AnnotatedOutput `json:"outputs"`
212         Fee       int64                    `json:"fee"`
213 }
214
215 // POST /decode-raw-transaction
216 func (a *API) decodeRawTransaction(ctx context.Context, ins struct {
217         Tx types.Tx `json:"raw_transaction"`
218 }) Response {
219         tx := &RawTx{
220                 ID:        ins.Tx.ID,
221                 Version:   ins.Tx.Version,
222                 Size:      ins.Tx.SerializedSize,
223                 TimeRange: ins.Tx.TimeRange,
224                 Inputs:    []*query.AnnotatedInput{},
225                 Outputs:   []*query.AnnotatedOutput{},
226         }
227
228         for i := range ins.Tx.Inputs {
229                 tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(&ins.Tx, uint32(i)))
230         }
231         for i := range ins.Tx.Outputs {
232                 tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i))
233         }
234
235         totalInputBtm := uint64(0)
236         totalOutputBtm := uint64(0)
237         for _, input := range tx.Inputs {
238                 if input.AssetID.String() == consensus.BTMAssetID.String() {
239                         totalInputBtm += input.Amount
240                 }
241         }
242
243         for _, output := range tx.Outputs {
244                 if output.AssetID.String() == consensus.BTMAssetID.String() {
245                         totalOutputBtm += output.Amount
246                 }
247         }
248
249         tx.Fee = int64(totalInputBtm) - int64(totalOutputBtm)
250         return NewSuccessResponse(tx)
251 }
252
253 // POST /list-unspent-outputs
254 func (a *API) listUnspentOutputs(ctx context.Context, filter struct {
255         ID            string `json:"id"`
256         Unconfirmed   bool   `json:"unconfirmed"`
257         SmartContract bool   `json:"smart_contract"`
258         From          uint   `json:"from"`
259         Count         uint   `json:"count"`
260 }) Response {
261         accountUTXOs := a.wallet.GetAccountUtxos(filter.ID, filter.Unconfirmed, filter.SmartContract)
262
263         UTXOs := []query.AnnotatedUTXO{}
264         for _, utxo := range accountUTXOs {
265                 UTXOs = append([]query.AnnotatedUTXO{{
266                         AccountID:           utxo.AccountID,
267                         OutputID:            utxo.OutputID.String(),
268                         SourceID:            utxo.SourceID.String(),
269                         AssetID:             utxo.AssetID.String(),
270                         Amount:              utxo.Amount,
271                         SourcePos:           utxo.SourcePos,
272                         Program:             fmt.Sprintf("%x", utxo.ControlProgram),
273                         ControlProgramIndex: utxo.ControlProgramIndex,
274                         Address:             utxo.Address,
275                         ValidHeight:         utxo.ValidHeight,
276                         Alias:               a.wallet.AccountMgr.GetAliasByID(utxo.AccountID),
277                         AssetAlias:          a.wallet.AssetReg.GetAliasByID(utxo.AssetID.String()),
278                         Change:              utxo.Change,
279                 }}, UTXOs...)
280         }
281         start, end := getPageRange(len(UTXOs), filter.From, filter.Count)
282         return NewSuccessResponse(UTXOs[start:end])
283 }
284
285 // return gasRate
286 func (a *API) gasRate() Response {
287         gasrate := map[string]int64{"gas_rate": consensus.VMGasRate}
288         return NewSuccessResponse(gasrate)
289 }
290
291 // PubKeyInfo is structure of pubkey info
292 type PubKeyInfo struct {
293         Pubkey string               `json:"pubkey"`
294         Path   []chainjson.HexBytes `json:"derivation_path"`
295 }
296
297 // AccountPubkey is detail of account pubkey info
298 type AccountPubkey struct {
299         RootXPub    chainkd.XPub `json:"root_xpub"`
300         PubKeyInfos []PubKeyInfo `json:"pubkey_infos"`
301 }
302
303 // POST /list-pubkeys
304 func (a *API) listPubKeys(ctx context.Context, ins struct {
305         AccountID    string `json:"account_id"`
306         AccountAlias string `json:"account_alias"`
307         PublicKey    string `json:"public_key"`
308 }) Response {
309         var err error
310         account := &account.Account{}
311         if ins.AccountAlias != "" {
312                 account, err = a.wallet.AccountMgr.FindByAlias(ins.AccountAlias)
313         } else {
314                 account, err = a.wallet.AccountMgr.FindByID(ins.AccountID)
315         }
316
317         if err != nil {
318                 return NewErrorResponse(err)
319         }
320
321         pubKeyInfos := []PubKeyInfo{}
322         idx := a.wallet.AccountMgr.GetContractIndex(account.ID)
323         for i := uint64(1); i <= idx; i++ {
324                 rawPath := signers.Path(account.Signer, signers.AccountKeySpace, i)
325                 derivedXPub := account.XPubs[0].Derive(rawPath)
326                 pubkey := derivedXPub.PublicKey()
327
328                 if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(pubkey) {
329                         continue
330                 }
331
332                 var path []chainjson.HexBytes
333                 for _, p := range rawPath {
334                         path = append(path, chainjson.HexBytes(p))
335                 }
336
337                 pubKeyInfos = append(pubKeyInfos, PubKeyInfo{
338                         Pubkey: hex.EncodeToString(pubkey),
339                         Path:   path,
340                 })
341         }
342
343         if len(pubKeyInfos) == 0 {
344                 return NewErrorResponse(errors.New("Not found publickey for the account"))
345         }
346
347         return NewSuccessResponse(&AccountPubkey{
348                 RootXPub:    account.XPubs[0],
349                 PubKeyInfos: pubKeyInfos,
350         })
351 }