OSDN Git Service

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