github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/api/query.go (about) 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 StatusFail: txDesc.StatusFail, 199 } 200 201 resOutID := txDesc.Tx.ResultIds[0] 202 resOut := txDesc.Tx.Entries[*resOutID] 203 switch out := resOut.(type) { 204 case *bc.Output: 205 tx.MuxID = *out.Source.Ref 206 case *bc.Retirement: 207 tx.MuxID = *out.Source.Ref 208 } 209 210 for i := range txDesc.Tx.Inputs { 211 tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(txDesc.Tx, uint32(i))) 212 } 213 for i := range txDesc.Tx.Outputs { 214 tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(txDesc.Tx, i)) 215 } 216 217 return NewSuccessResponse(tx) 218 } 219 220 type unconfirmedTxsResp struct { 221 Total uint64 `json:"total"` 222 TxIDs []bc.Hash `json:"tx_ids"` 223 } 224 225 // POST /list-unconfirmed-transactions 226 func (a *API) listUnconfirmedTxs(ctx context.Context) Response { 227 txIDs := []bc.Hash{} 228 229 txPool := a.chain.GetTxPool() 230 txs := txPool.GetTransactions() 231 for _, txDesc := range txs { 232 txIDs = append(txIDs, bc.Hash(txDesc.Tx.ID)) 233 } 234 235 return NewSuccessResponse(&unconfirmedTxsResp{ 236 Total: uint64(len(txIDs)), 237 TxIDs: txIDs, 238 }) 239 } 240 241 // RawTx is the tx struct for getRawTransaction 242 type RawTx struct { 243 ID bc.Hash `json:"tx_id"` 244 Version uint64 `json:"version"` 245 Size uint64 `json:"size"` 246 TimeRange uint64 `json:"time_range"` 247 Inputs []*query.AnnotatedInput `json:"inputs"` 248 Outputs []*query.AnnotatedOutput `json:"outputs"` 249 Fee uint64 `json:"fee"` 250 } 251 252 // POST /decode-raw-transaction 253 func (a *API) decodeRawTransaction(ctx context.Context, ins struct { 254 Tx types.Tx `json:"raw_transaction"` 255 }) Response { 256 tx := &RawTx{ 257 ID: ins.Tx.ID, 258 Version: ins.Tx.Version, 259 Size: ins.Tx.SerializedSize, 260 TimeRange: ins.Tx.TimeRange, 261 Inputs: []*query.AnnotatedInput{}, 262 Outputs: []*query.AnnotatedOutput{}, 263 } 264 265 for i := range ins.Tx.Inputs { 266 tx.Inputs = append(tx.Inputs, a.wallet.BuildAnnotatedInput(&ins.Tx, uint32(i))) 267 } 268 for i := range ins.Tx.Outputs { 269 tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i)) 270 } 271 272 tx.Fee = txbuilder.CalculateTxFee(&ins.Tx) 273 return NewSuccessResponse(tx) 274 } 275 276 // POST /list-unspent-outputs 277 func (a *API) listUnspentOutputs(ctx context.Context, filter struct { 278 AccountID string `json:"account_id"` 279 AccountAlias string `json:"account_alias"` 280 ID string `json:"id"` 281 Unconfirmed bool `json:"unconfirmed"` 282 SmartContract bool `json:"smart_contract"` 283 From uint `json:"from"` 284 Count uint `json:"count"` 285 }) Response { 286 accountID := filter.AccountID 287 if filter.AccountAlias != "" { 288 acc, err := a.wallet.AccountMgr.FindByAlias(filter.AccountAlias) 289 if err != nil { 290 return NewErrorResponse(err) 291 } 292 accountID = acc.ID 293 } 294 accountUTXOs := a.wallet.GetAccountUtxos(accountID, filter.ID, filter.Unconfirmed, filter.SmartContract) 295 296 UTXOs := []query.AnnotatedUTXO{} 297 for _, utxo := range accountUTXOs { 298 UTXOs = append([]query.AnnotatedUTXO{{ 299 AccountID: utxo.AccountID, 300 OutputID: utxo.OutputID.String(), 301 SourceID: utxo.SourceID.String(), 302 AssetID: utxo.AssetID.String(), 303 Amount: utxo.Amount, 304 SourcePos: utxo.SourcePos, 305 Program: fmt.Sprintf("%x", utxo.ControlProgram), 306 ControlProgramIndex: utxo.ControlProgramIndex, 307 Address: utxo.Address, 308 ValidHeight: utxo.ValidHeight, 309 Alias: a.wallet.AccountMgr.GetAliasByID(utxo.AccountID), 310 AssetAlias: a.wallet.AssetReg.GetAliasByID(utxo.AssetID.String()), 311 Change: utxo.Change, 312 }}, UTXOs...) 313 } 314 start, end := getPageRange(len(UTXOs), filter.From, filter.Count) 315 return NewSuccessResponse(UTXOs[start:end]) 316 } 317 318 // return gasRate 319 func (a *API) gasRate() Response { 320 gasrate := map[string]int64{"gas_rate": consensus.VMGasRate} 321 return NewSuccessResponse(gasrate) 322 } 323 324 // PubKeyInfo is structure of pubkey info 325 type PubKeyInfo struct { 326 Pubkey string `json:"pubkey"` 327 Path []chainjson.HexBytes `json:"derivation_path"` 328 } 329 330 // AccountPubkey is detail of account pubkey info 331 type AccountPubkey struct { 332 RootXPub chainkd.XPub `json:"root_xpub"` 333 PubKeyInfos []PubKeyInfo `json:"pubkey_infos"` 334 } 335 336 func getPubkey(account *account.Account, change bool, index uint64) (*ed25519.PublicKey, []chainjson.HexBytes, error) { 337 rawPath, err := signers.Path(account.Signer, signers.AccountKeySpace, change, index) 338 if err != nil { 339 return nil, nil, err 340 } 341 derivedXPub := account.XPubs[0].Derive(rawPath) 342 pubkey := derivedXPub.PublicKey() 343 var path []chainjson.HexBytes 344 for _, p := range rawPath { 345 path = append(path, chainjson.HexBytes(p)) 346 } 347 348 return &pubkey, path, nil 349 } 350 351 // POST /list-pubkeys 352 func (a *API) listPubKeys(ctx context.Context, ins struct { 353 AccountID string `json:"account_id"` 354 AccountAlias string `json:"account_alias"` 355 PublicKey string `json:"public_key"` 356 }) Response { 357 var err error 358 account := &account.Account{} 359 if ins.AccountAlias != "" { 360 account, err = a.wallet.AccountMgr.FindByAlias(ins.AccountAlias) 361 } else { 362 account, err = a.wallet.AccountMgr.FindByID(ins.AccountID) 363 } 364 365 if err != nil { 366 return NewErrorResponse(err) 367 } 368 369 pubKeyInfos := []PubKeyInfo{} 370 if account.DeriveRule == signers.BIP0032 { 371 idx := a.wallet.AccountMgr.GetContractIndex(account.ID) 372 for i := uint64(1); i <= idx; i++ { 373 pubkey, path, err := getPubkey(account, false, i) 374 if err != nil { 375 return NewErrorResponse(err) 376 } 377 if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) { 378 continue 379 } 380 pubKeyInfos = append(pubKeyInfos, PubKeyInfo{ 381 Pubkey: hex.EncodeToString(*pubkey), 382 Path: path, 383 }) 384 } 385 } else if account.DeriveRule == signers.BIP0044 { 386 idx := a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, true) 387 for i := uint64(1); i <= idx; i++ { 388 pubkey, path, err := getPubkey(account, true, i) 389 if err != nil { 390 return NewErrorResponse(err) 391 } 392 if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) { 393 continue 394 } 395 pubKeyInfos = append(pubKeyInfos, PubKeyInfo{ 396 Pubkey: hex.EncodeToString(*pubkey), 397 Path: path, 398 }) 399 } 400 401 idx = a.wallet.AccountMgr.GetBip44ContractIndex(account.ID, false) 402 for i := uint64(1); i <= idx; i++ { 403 pubkey, path, err := getPubkey(account, false, i) 404 if err != nil { 405 return NewErrorResponse(err) 406 } 407 if ins.PublicKey != "" && ins.PublicKey != hex.EncodeToString(*pubkey) { 408 continue 409 } 410 pubKeyInfos = append(pubKeyInfos, PubKeyInfo{ 411 Pubkey: hex.EncodeToString(*pubkey), 412 Path: path, 413 }) 414 } 415 } 416 417 if len(pubKeyInfos) == 0 { 418 return NewErrorResponse(errors.New("Not found publickey for the account")) 419 } 420 421 return NewSuccessResponse(&AccountPubkey{ 422 RootXPub: account.XPubs[0], 423 PubKeyInfos: pubKeyInfos, 424 }) 425 }