github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/api/query.go (about) 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 }