github.com/jackcoble/blockbook@v0.3.2/api/worker.go (about) 1 package api 2 3 import ( 4 "blockbook/bchain" 5 "blockbook/bchain/coins/eth" 6 "blockbook/common" 7 "blockbook/db" 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "math" 12 "math/big" 13 "os" 14 "sort" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/golang/glog" 20 "github.com/juju/errors" 21 ) 22 23 // Worker is handle to api worker 24 type Worker struct { 25 db *db.RocksDB 26 txCache *db.TxCache 27 chain bchain.BlockChain 28 chainParser bchain.BlockChainParser 29 chainType bchain.ChainType 30 mempool bchain.Mempool 31 is *common.InternalState 32 } 33 34 // NewWorker creates new api worker 35 func NewWorker(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, is *common.InternalState) (*Worker, error) { 36 w := &Worker{ 37 db: db, 38 txCache: txCache, 39 chain: chain, 40 chainParser: chain.GetChainParser(), 41 chainType: chain.GetChainParser().GetChainType(), 42 mempool: mempool, 43 is: is, 44 } 45 return w, nil 46 } 47 48 func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescriptor, []string, bool, error) { 49 addrDesc, err := w.chainParser.GetAddrDescFromVout(vout) 50 if err != nil { 51 return nil, nil, false, err 52 } 53 a, s, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc) 54 return addrDesc, a, s, err 55 } 56 57 // setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output 58 // there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx 59 func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error { 60 err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, maxUint32, func(t string, height uint32, indexes []int32) error { 61 for _, index := range indexes { 62 // take only inputs 63 if index < 0 { 64 index = ^index 65 tsp, err := w.db.GetTxAddresses(t) 66 if err != nil { 67 return err 68 } else if tsp == nil { 69 glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses") 70 } else if len(tsp.Inputs) > int(index) { 71 if tsp.Inputs[index].ValueSat.Cmp((*big.Int)(vout.ValueSat)) == 0 { 72 spentTx, spentHeight, err := w.txCache.GetTransaction(t) 73 if err != nil { 74 glog.Warning("Tx ", t, ": not found") 75 } else { 76 if len(spentTx.Vin) > int(index) { 77 if spentTx.Vin[index].Txid == txid { 78 vout.SpentTxID = t 79 vout.SpentHeight = int(spentHeight) 80 vout.SpentIndex = int(index) 81 return &db.StopIteration{} 82 } 83 } 84 } 85 } 86 } 87 } 88 } 89 return nil 90 }) 91 return err 92 } 93 94 // GetSpendingTxid returns transaction id of transaction that spent given output 95 func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { 96 start := time.Now() 97 tx, err := w.GetTransaction(txid, false, false) 98 if err != nil { 99 return "", err 100 } 101 if n >= len(tx.Vout) || n < 0 { 102 return "", NewAPIError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false) 103 } 104 err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight)) 105 if err != nil { 106 return "", err 107 } 108 glog.Info("GetSpendingTxid ", txid, " ", n, " finished in ", time.Since(start)) 109 return tx.Vout[n].SpentTxID, nil 110 } 111 112 // GetTransaction reads transaction data from txid 113 func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool) (*Tx, error) { 114 bchainTx, height, err := w.txCache.GetTransaction(txid) 115 if err != nil { 116 if err == bchain.ErrTxNotFound { 117 return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found", txid), true) 118 } 119 return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found (%v)", txid, err), true) 120 } 121 return w.GetTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON) 122 } 123 124 // GetTransactionFromBchainTx reads transaction data from txid 125 func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spendingTxs bool, specificJSON bool) (*Tx, error) { 126 var err error 127 var ta *db.TxAddresses 128 var tokens []TokenTransfer 129 var ethSpecific *EthereumSpecific 130 var blockhash string 131 if bchainTx.Confirmations > 0 { 132 if w.chainType == bchain.ChainBitcoinType { 133 ta, err = w.db.GetTxAddresses(bchainTx.Txid) 134 if err != nil { 135 return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainTx.Txid) 136 } 137 } 138 blockhash, err = w.db.GetBlockHash(uint32(height)) 139 if err != nil { 140 return nil, errors.Annotatef(err, "GetBlockHash %v", height) 141 } 142 } 143 var valInSat, valOutSat, feesSat big.Int 144 var pValInSat *big.Int 145 vins := make([]Vin, len(bchainTx.Vin)) 146 rbf := false 147 for i := range bchainTx.Vin { 148 bchainVin := &bchainTx.Vin[i] 149 vin := &vins[i] 150 vin.Txid = bchainVin.Txid 151 vin.N = i 152 vin.Vout = bchainVin.Vout 153 vin.Sequence = int64(bchainVin.Sequence) 154 // detect explicit Replace-by-Fee transactions as defined by BIP125 155 if bchainTx.Confirmations == 0 && bchainVin.Sequence < 0xffffffff-1 { 156 rbf = true 157 } 158 vin.Hex = bchainVin.ScriptSig.Hex 159 vin.Coinbase = bchainVin.Coinbase 160 if w.chainType == bchain.ChainBitcoinType { 161 // bchainVin.Txid=="" is coinbase transaction 162 if bchainVin.Txid != "" { 163 // load spending addresses from TxAddresses 164 tas, err := w.db.GetTxAddresses(bchainVin.Txid) 165 if err != nil { 166 return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid) 167 } 168 if tas == nil { 169 // try to load from backend 170 otx, _, err := w.txCache.GetTransaction(bchainVin.Txid) 171 if err != nil { 172 if err == bchain.ErrTxNotFound { 173 // try to get AddrDesc using coin specific handling and continue processing the tx 174 vin.AddrDesc = w.chainParser.GetAddrDescForUnknownInput(bchainTx, i) 175 vin.Addresses, vin.IsAddress, err = w.chainParser.GetAddressesFromAddrDesc(vin.AddrDesc) 176 if err != nil { 177 glog.Warning("GetAddressesFromAddrDesc tx ", bchainVin.Txid, ", addrDesc ", vin.AddrDesc, ": ", err) 178 } 179 continue 180 } 181 return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid) 182 } 183 // mempool transactions are not in TxAddresses but confirmed should be there, log a problem 184 // ignore when Confirmations==1, it may be just a timing problem 185 if bchainTx.Confirmations > 1 { 186 glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses, confirmations ", bchainTx.Confirmations) 187 } 188 if len(otx.Vout) > int(vin.Vout) { 189 vout := &otx.Vout[vin.Vout] 190 vin.ValueSat = (*Amount)(&vout.ValueSat) 191 vin.AddrDesc, vin.Addresses, vin.IsAddress, err = w.getAddressesFromVout(vout) 192 if err != nil { 193 glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout) 194 } 195 } 196 } else { 197 if len(tas.Outputs) > int(vin.Vout) { 198 output := &tas.Outputs[vin.Vout] 199 vin.ValueSat = (*Amount)(&output.ValueSat) 200 vin.AddrDesc = output.AddrDesc 201 vin.Addresses, vin.IsAddress, err = output.Addresses(w.chainParser) 202 if err != nil { 203 glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i) 204 } 205 } 206 } 207 if vin.ValueSat != nil { 208 valInSat.Add(&valInSat, (*big.Int)(vin.ValueSat)) 209 } 210 } 211 } else if w.chainType == bchain.ChainEthereumType { 212 if len(bchainVin.Addresses) > 0 { 213 vin.AddrDesc, err = w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0]) 214 if err != nil { 215 glog.Errorf("GetAddrDescFromAddress error %v, tx %v, bchainVin %v", err, bchainTx.Txid, bchainVin) 216 } 217 vin.Addresses = bchainVin.Addresses 218 vin.IsAddress = true 219 } 220 } 221 } 222 vouts := make([]Vout, len(bchainTx.Vout)) 223 for i := range bchainTx.Vout { 224 bchainVout := &bchainTx.Vout[i] 225 vout := &vouts[i] 226 vout.N = i 227 vout.ValueSat = (*Amount)(&bchainVout.ValueSat) 228 valOutSat.Add(&valOutSat, &bchainVout.ValueSat) 229 vout.Hex = bchainVout.ScriptPubKey.Hex 230 vout.AddrDesc, vout.Addresses, vout.IsAddress, err = w.getAddressesFromVout(bchainVout) 231 if err != nil { 232 glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, bchainTx.Txid, bchainVout.N) 233 } 234 if ta != nil { 235 vout.Spent = ta.Outputs[i].Spent 236 if spendingTxs && vout.Spent { 237 err = w.setSpendingTxToVout(vout, bchainTx.Txid, uint32(height)) 238 if err != nil { 239 glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.AddrDesc, vout.N) 240 } 241 } 242 } 243 } 244 if w.chainType == bchain.ChainBitcoinType { 245 // for coinbase transactions valIn is 0 246 feesSat.Sub(&valInSat, &valOutSat) 247 if feesSat.Sign() == -1 { 248 feesSat.SetUint64(0) 249 } 250 pValInSat = &valInSat 251 } else if w.chainType == bchain.ChainEthereumType { 252 ets, err := w.chainParser.EthereumTypeGetErc20FromTx(bchainTx) 253 if err != nil { 254 glog.Errorf("GetErc20FromTx error %v, %v", err, bchainTx) 255 } 256 tokens = make([]TokenTransfer, len(ets)) 257 for i := range ets { 258 e := &ets[i] 259 cd, err := w.chainParser.GetAddrDescFromAddress(e.Contract) 260 if err != nil { 261 glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, e.Contract) 262 continue 263 } 264 erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd) 265 if err != nil { 266 glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, e.Contract) 267 } 268 if erc20c == nil { 269 erc20c = &bchain.Erc20Contract{Name: e.Contract} 270 } 271 tokens[i] = TokenTransfer{ 272 Type: ERC20TokenType, 273 Token: e.Contract, 274 From: e.From, 275 To: e.To, 276 Decimals: erc20c.Decimals, 277 Value: (*Amount)(&e.Tokens), 278 Name: erc20c.Name, 279 Symbol: erc20c.Symbol, 280 } 281 } 282 ethTxData := eth.GetEthereumTxData(bchainTx) 283 // mempool txs do not have fees yet 284 if ethTxData.GasUsed != nil { 285 feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed) 286 } 287 if len(bchainTx.Vout) > 0 { 288 valOutSat = bchainTx.Vout[0].ValueSat 289 } 290 ethSpecific = &EthereumSpecific{ 291 GasLimit: ethTxData.GasLimit, 292 GasPrice: (*Amount)(ethTxData.GasPrice), 293 GasUsed: ethTxData.GasUsed, 294 Nonce: ethTxData.Nonce, 295 Status: ethTxData.Status, 296 } 297 } 298 // for now do not return size, we would have to compute vsize of segwit transactions 299 // size:=len(bchainTx.Hex) / 2 300 var sj json.RawMessage 301 if specificJSON { 302 sj, err = w.chain.GetTransactionSpecific(bchainTx) 303 if err != nil { 304 return nil, err 305 } 306 } 307 // for mempool transaction get first seen time 308 if bchainTx.Confirmations == 0 { 309 bchainTx.Blocktime = int64(w.mempool.GetTransactionTime(bchainTx.Txid)) 310 } 311 r := &Tx{ 312 Blockhash: blockhash, 313 Blockheight: height, 314 Blocktime: bchainTx.Blocktime, 315 Confirmations: bchainTx.Confirmations, 316 FeesSat: (*Amount)(&feesSat), 317 Locktime: bchainTx.LockTime, 318 Txid: bchainTx.Txid, 319 ValueInSat: (*Amount)(pValInSat), 320 ValueOutSat: (*Amount)(&valOutSat), 321 Version: bchainTx.Version, 322 Hex: bchainTx.Hex, 323 Rbf: rbf, 324 Vin: vins, 325 Vout: vouts, 326 CoinSpecificData: bchainTx.CoinSpecificData, 327 CoinSpecificJSON: sj, 328 TokenTransfers: tokens, 329 EthereumSpecific: ethSpecific, 330 } 331 return r, nil 332 } 333 334 func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter *AddressFilter, maxResults int) ([]string, error) { 335 var err error 336 txids := make([]string, 0, 4) 337 var callback db.GetTransactionsCallback 338 if filter.Vout == AddressFilterVoutOff { 339 callback = func(txid string, height uint32, indexes []int32) error { 340 txids = append(txids, txid) 341 if len(txids) >= maxResults { 342 return &db.StopIteration{} 343 } 344 return nil 345 } 346 } else { 347 callback = func(txid string, height uint32, indexes []int32) error { 348 for _, index := range indexes { 349 vout := index 350 if vout < 0 { 351 vout = ^vout 352 } 353 if (filter.Vout == AddressFilterVoutInputs && index < 0) || 354 (filter.Vout == AddressFilterVoutOutputs && index >= 0) || 355 (vout == int32(filter.Vout)) { 356 txids = append(txids, txid) 357 if len(txids) >= maxResults { 358 return &db.StopIteration{} 359 } 360 break 361 } 362 } 363 return nil 364 } 365 } 366 if mempool { 367 uniqueTxs := make(map[string]struct{}) 368 o, err := w.mempool.GetAddrDescTransactions(addrDesc) 369 if err != nil { 370 return nil, err 371 } 372 for _, m := range o { 373 if _, found := uniqueTxs[m.Txid]; !found { 374 l := len(txids) 375 callback(m.Txid, 0, []int32{m.Vout}) 376 if len(txids) > l { 377 uniqueTxs[m.Txid] = struct{}{} 378 } 379 } 380 } 381 } else { 382 to := filter.ToHeight 383 if to == 0 { 384 to = maxUint32 385 } 386 err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback) 387 if err != nil { 388 return nil, err 389 } 390 } 391 return txids, nil 392 } 393 394 func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int { 395 var val big.Int 396 for _, vout := range t.Vout { 397 if bytes.Equal(vout.AddrDesc, addrDesc) && vout.ValueSat != nil { 398 val.Add(&val, (*big.Int)(vout.ValueSat)) 399 } 400 } 401 return &val 402 } 403 404 func (t *Tx) getAddrVinValue(addrDesc bchain.AddressDescriptor) *big.Int { 405 var val big.Int 406 for _, vin := range t.Vin { 407 if bytes.Equal(vin.AddrDesc, addrDesc) && vin.ValueSat != nil { 408 val.Add(&val, (*big.Int)(vin.ValueSat)) 409 } 410 } 411 return &val 412 } 413 414 // GetUniqueTxids removes duplicate transactions 415 func GetUniqueTxids(txids []string) []string { 416 ut := make([]string, len(txids)) 417 txidsMap := make(map[string]struct{}) 418 i := 0 419 for _, txid := range txids { 420 _, e := txidsMap[txid] 421 if !e { 422 ut[i] = txid 423 i++ 424 txidsMap[txid] = struct{}{} 425 } 426 } 427 return ut[0:i] 428 } 429 430 func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32) *Tx { 431 var err error 432 var valInSat, valOutSat, feesSat big.Int 433 vins := make([]Vin, len(ta.Inputs)) 434 for i := range ta.Inputs { 435 tai := &ta.Inputs[i] 436 vin := &vins[i] 437 vin.N = i 438 vin.ValueSat = (*Amount)(&tai.ValueSat) 439 valInSat.Add(&valInSat, &tai.ValueSat) 440 vin.Addresses, vin.IsAddress, err = tai.Addresses(w.chainParser) 441 if err != nil { 442 glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai) 443 } 444 } 445 vouts := make([]Vout, len(ta.Outputs)) 446 for i := range ta.Outputs { 447 tao := &ta.Outputs[i] 448 vout := &vouts[i] 449 vout.N = i 450 vout.ValueSat = (*Amount)(&tao.ValueSat) 451 valOutSat.Add(&valOutSat, &tao.ValueSat) 452 vout.Addresses, vout.IsAddress, err = tao.Addresses(w.chainParser) 453 if err != nil { 454 glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao) 455 } 456 vout.Spent = tao.Spent 457 } 458 // for coinbase transactions valIn is 0 459 feesSat.Sub(&valInSat, &valOutSat) 460 if feesSat.Sign() == -1 { 461 feesSat.SetUint64(0) 462 } 463 r := &Tx{ 464 Blockhash: bi.Hash, 465 Blockheight: int(ta.Height), 466 Blocktime: bi.Time, 467 Confirmations: bestheight - ta.Height + 1, 468 FeesSat: (*Amount)(&feesSat), 469 Txid: txid, 470 ValueInSat: (*Amount)(&valInSat), 471 ValueOutSat: (*Amount)(&valOutSat), 472 Vin: vins, 473 Vout: vouts, 474 } 475 return r 476 } 477 478 func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) { 479 from := page * itemsOnPage 480 totalPages := (count - 1) / itemsOnPage 481 if totalPages < 0 { 482 totalPages = 0 483 } 484 if from >= count { 485 page = totalPages 486 } 487 from = page * itemsOnPage 488 to := (page + 1) * itemsOnPage 489 if to > count { 490 to = count 491 } 492 return Paging{ 493 ItemsOnPage: itemsOnPage, 494 Page: page + 1, 495 TotalPages: totalPages + 1, 496 }, from, to, page 497 } 498 499 func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) { 500 var ( 501 ba *db.AddrBalance 502 tokens []Token 503 ci *bchain.Erc20Contract 504 n uint64 505 nonContractTxs int 506 ) 507 // unknown number of results for paging 508 totalResults := -1 509 ca, err := w.db.GetAddrDescContracts(addrDesc) 510 if err != nil { 511 return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 512 } 513 b, err := w.chain.EthereumTypeGetBalance(addrDesc) 514 if err != nil { 515 return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc) 516 } 517 if ca != nil { 518 ba = &db.AddrBalance{ 519 Txs: uint32(ca.TotalTxs), 520 } 521 if b != nil { 522 ba.BalanceSat = *b 523 } 524 n, err = w.chain.EthereumTypeGetNonce(addrDesc) 525 if err != nil { 526 return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc) 527 } 528 var filterDesc bchain.AddressDescriptor 529 if filter.Contract != "" { 530 filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract) 531 if err != nil { 532 return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true) 533 } 534 } 535 if details > AccountDetailsBasic { 536 tokens = make([]Token, len(ca.Contracts)) 537 var j int 538 for i, c := range ca.Contracts { 539 if len(filterDesc) > 0 { 540 if !bytes.Equal(filterDesc, c.Contract) { 541 continue 542 } 543 // filter only transactions of this contract 544 filter.Vout = i + 1 545 } 546 validContract := true 547 ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract) 548 if err != nil { 549 return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract) 550 } 551 if ci == nil { 552 ci = &bchain.Erc20Contract{} 553 addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract) 554 if len(addresses) > 0 { 555 ci.Contract = addresses[0] 556 ci.Name = addresses[0] 557 } 558 validContract = false 559 } 560 // do not read contract balances etc in case of Basic option 561 if details >= AccountDetailsTokenBalances && validContract { 562 b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract) 563 if err != nil { 564 // return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract) 565 glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err) 566 } 567 } else { 568 b = nil 569 } 570 tokens[j] = Token{ 571 Type: ERC20TokenType, 572 BalanceSat: (*Amount)(b), 573 Contract: ci.Contract, 574 Name: ci.Name, 575 Symbol: ci.Symbol, 576 Transfers: int(c.Txs), 577 Decimals: ci.Decimals, 578 ContractIndex: strconv.Itoa(i + 1), 579 } 580 j++ 581 } 582 tokens = tokens[:j] 583 } 584 ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc) 585 if err != nil { 586 return nil, nil, nil, 0, 0, 0, err 587 } 588 if filter.FromHeight == 0 && filter.ToHeight == 0 { 589 // compute total results for paging 590 if filter.Vout == AddressFilterVoutOff { 591 totalResults = int(ca.TotalTxs) 592 } else if filter.Vout == 0 { 593 totalResults = int(ca.NonContractTxs) 594 } else if filter.Vout > 0 && filter.Vout-1 < len(ca.Contracts) { 595 totalResults = int(ca.Contracts[filter.Vout-1].Txs) 596 } 597 } 598 nonContractTxs = int(ca.NonContractTxs) 599 } else { 600 // addresses without any normal transactions can have internal transactions and therefore balance 601 if b != nil { 602 ba = &db.AddrBalance{ 603 BalanceSat: *b, 604 } 605 } 606 } 607 return ba, tokens, ci, n, nonContractTxs, totalResults, nil 608 } 609 610 func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails, blockInfo *db.BlockInfo) (*Tx, error) { 611 var tx *Tx 612 var err error 613 // only ChainBitcoinType supports TxHistoryLight 614 if option == AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType { 615 ta, err := w.db.GetTxAddresses(txid) 616 if err != nil { 617 return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) 618 } 619 if ta == nil { 620 glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") 621 // as fallback, get tx from backend 622 tx, err = w.GetTransaction(txid, false, true) 623 if err != nil { 624 return nil, errors.Annotatef(err, "GetTransaction %v", txid) 625 } 626 } else { 627 if blockInfo == nil { 628 blockInfo, err = w.db.GetBlockInfo(ta.Height) 629 if err != nil { 630 return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height) 631 } 632 if blockInfo == nil { 633 glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db") 634 // provide empty BlockInfo to return the rest of tx data 635 blockInfo = &db.BlockInfo{} 636 } 637 } 638 tx = w.txFromTxAddress(txid, ta, blockInfo, bestheight) 639 } 640 } else { 641 tx, err = w.GetTransaction(txid, false, true) 642 if err != nil { 643 return nil, errors.Annotatef(err, "GetTransaction %v", txid) 644 } 645 } 646 return tx, nil 647 } 648 649 func (w *Worker) getAddrDescAndNormalizeAddress(address string) (bchain.AddressDescriptor, string, error) { 650 addrDesc, err := w.chainParser.GetAddrDescFromAddress(address) 651 if err != nil { 652 var errAd error 653 // try if the address is not address descriptor converted to string 654 addrDesc, errAd = bchain.AddressDescriptorFromString(address) 655 if errAd != nil { 656 return nil, "", NewAPIError(fmt.Sprintf("Invalid address, %v", err), true) 657 } 658 } 659 // convert the address to the format defined by the parser 660 addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc) 661 if err != nil { 662 glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc) 663 } 664 if len(addresses) == 1 { 665 address = addresses[0] 666 } 667 return addrDesc, address, nil 668 } 669 670 // GetAddress computes address value and gets transactions for given address 671 func (w *Worker) GetAddress(address string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter) (*Address, error) { 672 start := time.Now() 673 page-- 674 if page < 0 { 675 page = 0 676 } 677 var ( 678 ba *db.AddrBalance 679 tokens []Token 680 erc20c *bchain.Erc20Contract 681 txm []string 682 txs []*Tx 683 txids []string 684 pg Paging 685 uBalSat big.Int 686 totalReceived, totalSent *big.Int 687 nonce string 688 unconfirmedTxs int 689 nonTokenTxs int 690 totalResults int 691 ) 692 addrDesc, address, err := w.getAddrDescAndNormalizeAddress(address) 693 if err != nil { 694 return nil, err 695 } 696 if w.chainType == bchain.ChainEthereumType { 697 var n uint64 698 ba, tokens, erc20c, n, nonTokenTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter) 699 if err != nil { 700 return nil, err 701 } 702 nonce = strconv.Itoa(int(n)) 703 } else { 704 // ba can be nil if the address is only in mempool! 705 ba, err = w.db.GetAddrDescBalance(addrDesc, db.AddressBalanceDetailNoUTXO) 706 if err != nil { 707 return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 708 } 709 if ba != nil { 710 // totalResults is known only if there is no filter 711 if filter.Vout == AddressFilterVoutOff && filter.FromHeight == 0 && filter.ToHeight == 0 { 712 totalResults = int(ba.Txs) 713 } else { 714 totalResults = -1 715 } 716 } 717 } 718 // if there are only unconfirmed transactions, there is no paging 719 if ba == nil { 720 ba = &db.AddrBalance{} 721 page = 0 722 } 723 // process mempool, only if toHeight is not specified 724 if filter.ToHeight == 0 && !filter.OnlyConfirmed { 725 txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt) 726 if err != nil { 727 return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc) 728 } 729 for _, txid := range txm { 730 tx, err := w.GetTransaction(txid, false, false) 731 // mempool transaction may fail 732 if err != nil || tx == nil { 733 glog.Warning("GetTransaction in mempool: ", err) 734 } else { 735 // skip already confirmed txs, mempool may be out of sync 736 if tx.Confirmations == 0 { 737 unconfirmedTxs++ 738 uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc)) 739 uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc)) 740 if page == 0 { 741 if option == AccountDetailsTxidHistory { 742 txids = append(txids, tx.Txid) 743 } else if option >= AccountDetailsTxHistoryLight { 744 txs = append(txs, tx) 745 } 746 } 747 } 748 } 749 } 750 } 751 // get tx history if requested by option or check mempool if there are some transactions for a new address 752 if option >= AccountDetailsTxidHistory { 753 txc, err := w.getAddressTxids(addrDesc, false, filter, (page+1)*txsOnPage) 754 if err != nil { 755 return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc) 756 } 757 bestheight, _, err := w.db.GetBestBlock() 758 if err != nil { 759 return nil, errors.Annotatef(err, "GetBestBlock") 760 } 761 var from, to int 762 pg, from, to, page = computePaging(len(txc), page, txsOnPage) 763 if len(txc) >= txsOnPage { 764 if totalResults < 0 { 765 pg.TotalPages = -1 766 } else { 767 pg, _, _, _ = computePaging(totalResults, page, txsOnPage) 768 } 769 } 770 for i := from; i < to; i++ { 771 txid := txc[i] 772 if option == AccountDetailsTxidHistory { 773 txids = append(txids, txid) 774 } else { 775 tx, err := w.txFromTxid(txid, bestheight, option, nil) 776 if err != nil { 777 return nil, err 778 } 779 txs = append(txs, tx) 780 } 781 } 782 } 783 if w.chainType == bchain.ChainBitcoinType { 784 totalReceived = ba.ReceivedSat() 785 totalSent = &ba.SentSat 786 } 787 r := &Address{ 788 Paging: pg, 789 AddrStr: address, 790 BalanceSat: (*Amount)(&ba.BalanceSat), 791 TotalReceivedSat: (*Amount)(totalReceived), 792 TotalSentSat: (*Amount)(totalSent), 793 Txs: int(ba.Txs), 794 NonTokenTxs: nonTokenTxs, 795 UnconfirmedBalanceSat: (*Amount)(&uBalSat), 796 UnconfirmedTxs: unconfirmedTxs, 797 Transactions: txs, 798 Txids: txids, 799 Tokens: tokens, 800 Erc20Contract: erc20c, 801 Nonce: nonce, 802 } 803 glog.Info("GetAddress ", address, " finished in ", time.Since(start)) 804 return r, nil 805 } 806 807 func (w *Worker) balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp int64) (uint32, uint32, uint32, uint32) { 808 fromUnix := uint32(0) 809 toUnix := maxUint32 810 fromHeight := uint32(0) 811 toHeight := maxUint32 812 if fromTimestamp != 0 { 813 fromUnix = uint32(fromTimestamp) 814 fromHeight = w.is.GetBlockHeightOfTime(fromUnix) 815 } 816 if toTimestamp != 0 { 817 toUnix = uint32(toTimestamp) 818 toHeight = w.is.GetBlockHeightOfTime(toUnix) 819 } 820 return fromUnix, fromHeight, toUnix, toHeight 821 } 822 823 func (w *Worker) balanceHistoryForTxid(addrDesc bchain.AddressDescriptor, txid string, fromUnix, toUnix uint32) (*BalanceHistory, error) { 824 var time uint32 825 var err error 826 var ta *db.TxAddresses 827 var bchainTx *bchain.Tx 828 var height uint32 829 if w.chainType == bchain.ChainBitcoinType { 830 ta, err = w.db.GetTxAddresses(txid) 831 if err != nil { 832 return nil, err 833 } 834 if ta == nil { 835 glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") 836 return nil, nil 837 } 838 height = ta.Height 839 } else if w.chainType == bchain.ChainEthereumType { 840 var h int 841 bchainTx, h, err = w.txCache.GetTransaction(txid) 842 if err != nil { 843 return nil, err 844 } 845 if bchainTx == nil { 846 glog.Warning("Inconsistency: tx ", txid, ": not found in the blockchain") 847 return nil, nil 848 } 849 height = uint32(h) 850 } 851 time = w.is.GetBlockTime(height) 852 if time < fromUnix || time >= toUnix { 853 return nil, nil 854 } 855 bh := BalanceHistory{ 856 Time: time, 857 Txs: 1, 858 SentSat: &Amount{}, 859 ReceivedSat: &Amount{}, 860 Txid: txid, 861 } 862 if w.chainType == bchain.ChainBitcoinType { 863 for i := range ta.Inputs { 864 tai := &ta.Inputs[i] 865 if bytes.Equal(addrDesc, tai.AddrDesc) { 866 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &tai.ValueSat) 867 } 868 } 869 for i := range ta.Outputs { 870 tao := &ta.Outputs[i] 871 if bytes.Equal(addrDesc, tao.AddrDesc) { 872 (*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &tao.ValueSat) 873 } 874 } 875 } else if w.chainType == bchain.ChainEthereumType { 876 var value big.Int 877 ethTxData := eth.GetEthereumTxData(bchainTx) 878 // add received amount only for OK transactions 879 if ethTxData.Status == 1 { 880 if len(bchainTx.Vout) > 0 { 881 bchainVout := &bchainTx.Vout[0] 882 value = bchainVout.ValueSat 883 if len(bchainVout.ScriptPubKey.Addresses) > 0 { 884 txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVout.ScriptPubKey.Addresses[0]) 885 if err != nil { 886 return nil, err 887 } 888 if bytes.Equal(addrDesc, txAddrDesc) { 889 (*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &value) 890 } 891 } 892 } 893 } 894 for i := range bchainTx.Vin { 895 bchainVin := &bchainTx.Vin[i] 896 if len(bchainVin.Addresses) > 0 { 897 txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0]) 898 if err != nil { 899 return nil, err 900 } 901 if bytes.Equal(addrDesc, txAddrDesc) { 902 // add sent amount only for OK transactions, however fees always 903 if ethTxData.Status == 1 { 904 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &value) 905 } 906 var feesSat big.Int 907 // mempool txs do not have fees yet 908 if ethTxData.GasUsed != nil { 909 feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed) 910 } 911 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &feesSat) 912 } 913 } 914 } 915 } 916 return &bh, nil 917 } 918 919 func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, currencies []string) error { 920 for i := range histories { 921 bh := &histories[i] 922 t := time.Unix(int64(bh.Time), 0) 923 ticker, err := w.db.FiatRatesFindTicker(&t) 924 if err != nil { 925 glog.Errorf("Error finding ticker by date %v. Error: %v", t, err) 926 continue 927 } else if ticker == nil { 928 continue 929 } 930 if len(currencies) == 0 { 931 bh.FiatRates = ticker.Rates 932 } else { 933 rates := make(map[string]float64) 934 for _, currency := range currencies { 935 currency = strings.ToLower(currency) 936 if rate, found := ticker.Rates[currency]; found { 937 rates[currency] = rate 938 } else { 939 rates[currency] = -1 940 } 941 } 942 bh.FiatRates = rates 943 } 944 } 945 return nil 946 } 947 948 // GetBalanceHistory returns history of balance for given address 949 func (w *Worker) GetBalanceHistory(address string, fromTimestamp, toTimestamp int64, currencies []string, groupBy uint32) (BalanceHistories, error) { 950 currencies = removeEmpty(currencies) 951 bhs := make(BalanceHistories, 0) 952 start := time.Now() 953 addrDesc, _, err := w.getAddrDescAndNormalizeAddress(address) 954 if err != nil { 955 return nil, err 956 } 957 fromUnix, fromHeight, toUnix, toHeight := w.balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp) 958 if fromHeight >= toHeight { 959 return bhs, nil 960 } 961 txs, err := w.getAddressTxids(addrDesc, false, &AddressFilter{Vout: AddressFilterVoutOff, FromHeight: fromHeight, ToHeight: toHeight}, maxInt) 962 if err != nil { 963 return nil, err 964 } 965 for txi := len(txs) - 1; txi >= 0; txi-- { 966 bh, err := w.balanceHistoryForTxid(addrDesc, txs[txi], fromUnix, toUnix) 967 if err != nil { 968 return nil, err 969 } 970 if bh != nil { 971 bhs = append(bhs, *bh) 972 } 973 } 974 bha := bhs.SortAndAggregate(groupBy) 975 err = w.setFiatRateToBalanceHistories(bha, currencies) 976 if err != nil { 977 return nil, err 978 } 979 glog.Info("GetBalanceHistory ", address, ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), " finished in ", time.Since(start)) 980 return bha, nil 981 } 982 983 func (w *Worker) waitForBackendSync() { 984 // wait a short time if blockbook is synchronizing with backend 985 inSync, _, _ := w.is.GetSyncState() 986 count := 30 987 for !inSync && count > 0 { 988 time.Sleep(time.Millisecond * 100) 989 count-- 990 inSync, _, _ = w.is.GetSyncState() 991 } 992 } 993 994 func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrBalance, onlyConfirmed bool, onlyMempool bool) (Utxos, error) { 995 w.waitForBackendSync() 996 var err error 997 utxos := make(Utxos, 0, 8) 998 // store txids from mempool so that they are not added twice in case of import of new block while processing utxos, issue #275 999 inMempool := make(map[string]struct{}) 1000 // outputs could be spent in mempool, record and check mempool spends 1001 spentInMempool := make(map[string]struct{}) 1002 if !onlyConfirmed { 1003 // get utxo from mempool 1004 txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt) 1005 if err != nil { 1006 return nil, err 1007 } 1008 if len(txm) > 0 { 1009 mc := make([]*bchain.Tx, len(txm)) 1010 for i, txid := range txm { 1011 // get mempool txs and process their inputs to detect spends between mempool txs 1012 bchainTx, _, err := w.txCache.GetTransaction(txid) 1013 // mempool transaction may fail 1014 if err != nil { 1015 glog.Error("GetTransaction in mempool ", txid, ": ", err) 1016 } else { 1017 mc[i] = bchainTx 1018 // get outputs spent by the mempool tx 1019 for i := range bchainTx.Vin { 1020 vin := &bchainTx.Vin[i] 1021 spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{} 1022 } 1023 } 1024 } 1025 for _, bchainTx := range mc { 1026 if bchainTx != nil { 1027 for i := range bchainTx.Vout { 1028 vout := &bchainTx.Vout[i] 1029 vad, err := w.chainParser.GetAddrDescFromVout(vout) 1030 if err == nil && bytes.Equal(addrDesc, vad) { 1031 // report only outpoints that are not spent in mempool 1032 _, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)] 1033 if !e { 1034 coinbase := false 1035 if len(bchainTx.Vin) == 1 && len(bchainTx.Vin[0].Coinbase) > 0 { 1036 coinbase = true 1037 } 1038 utxos = append(utxos, Utxo{ 1039 Txid: bchainTx.Txid, 1040 Vout: int32(i), 1041 AmountSat: (*Amount)(&vout.ValueSat), 1042 Locktime: bchainTx.LockTime, 1043 Coinbase: coinbase, 1044 }) 1045 inMempool[bchainTx.Txid] = struct{}{} 1046 } 1047 } 1048 } 1049 } 1050 } 1051 } 1052 } 1053 if !onlyMempool { 1054 // get utxo from index 1055 if ba == nil { 1056 ba, err = w.db.GetAddrDescBalance(addrDesc, db.AddressBalanceDetailUTXO) 1057 if err != nil { 1058 return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 1059 } 1060 } 1061 // ba can be nil if the address is only in mempool! 1062 if ba != nil && len(ba.Utxos) > 0 { 1063 b, _, err := w.db.GetBestBlock() 1064 if err != nil { 1065 return nil, err 1066 } 1067 bestheight := int(b) 1068 var checksum big.Int 1069 checksum.Set(&ba.BalanceSat) 1070 // go backwards to get the newest first 1071 for i := len(ba.Utxos) - 1; i >= 0; i-- { 1072 utxo := &ba.Utxos[i] 1073 txid, err := w.chainParser.UnpackTxid(utxo.BtxID) 1074 if err != nil { 1075 return nil, err 1076 } 1077 _, e := spentInMempool[txid+strconv.Itoa(int(utxo.Vout))] 1078 if !e { 1079 confirmations := bestheight - int(utxo.Height) + 1 1080 coinbase := false 1081 // for performance reasons, check coinbase transactions only in minimum confirmantion range 1082 if confirmations < w.chainParser.MinimumCoinbaseConfirmations() { 1083 ta, err := w.db.GetTxAddresses(txid) 1084 if err != nil { 1085 return nil, err 1086 } 1087 if len(ta.Inputs) == 1 && len(ta.Inputs[0].AddrDesc) == 0 && IsZeroBigInt(&ta.Inputs[0].ValueSat) { 1088 coinbase = true 1089 } 1090 } 1091 _, e = inMempool[txid] 1092 if !e { 1093 utxos = append(utxos, Utxo{ 1094 Txid: txid, 1095 Vout: utxo.Vout, 1096 AmountSat: (*Amount)(&utxo.ValueSat), 1097 Height: int(utxo.Height), 1098 Confirmations: confirmations, 1099 Coinbase: coinbase, 1100 }) 1101 } 1102 } 1103 checksum.Sub(&checksum, &utxo.ValueSat) 1104 } 1105 if checksum.Uint64() != 0 { 1106 glog.Warning("DB inconsistency: ", addrDesc, ": checksum is not zero, checksum=", checksum.Int64()) 1107 } 1108 } 1109 } 1110 return utxos, nil 1111 } 1112 1113 // GetAddressUtxo returns unspent outputs for given address 1114 func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) (Utxos, error) { 1115 if w.chainType != bchain.ChainBitcoinType { 1116 return nil, NewAPIError("Not supported", true) 1117 } 1118 start := time.Now() 1119 addrDesc, err := w.chainParser.GetAddrDescFromAddress(address) 1120 if err != nil { 1121 return nil, NewAPIError(fmt.Sprintf("Invalid address '%v', %v", address, err), true) 1122 } 1123 r, err := w.getAddrDescUtxo(addrDesc, nil, onlyConfirmed, false) 1124 if err != nil { 1125 return nil, err 1126 } 1127 glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start)) 1128 return r, nil 1129 } 1130 1131 // GetBlocks returns BlockInfo for blocks on given page 1132 func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) { 1133 start := time.Now() 1134 page-- 1135 if page < 0 { 1136 page = 0 1137 } 1138 b, _, err := w.db.GetBestBlock() 1139 bestheight := int(b) 1140 if err != nil { 1141 return nil, errors.Annotatef(err, "GetBestBlock") 1142 } 1143 pg, from, to, page := computePaging(bestheight+1, page, blocksOnPage) 1144 r := &Blocks{Paging: pg} 1145 r.Blocks = make([]db.BlockInfo, to-from) 1146 for i := from; i < to; i++ { 1147 bi, err := w.db.GetBlockInfo(uint32(bestheight - i)) 1148 if err != nil { 1149 return nil, err 1150 } 1151 if bi == nil { 1152 r.Blocks = r.Blocks[:i] 1153 break 1154 } 1155 r.Blocks[i-from] = *bi 1156 } 1157 glog.Info("GetBlocks page ", page, " finished in ", time.Since(start)) 1158 return r, nil 1159 } 1160 1161 // removeEmpty removes empty strings from a slice 1162 func removeEmpty(stringSlice []string) []string { 1163 var ret []string 1164 for _, str := range stringSlice { 1165 if str != "" { 1166 ret = append(ret, str) 1167 } 1168 } 1169 return ret 1170 } 1171 1172 // getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result 1173 func (w *Worker) getFiatRatesResult(currencies []string, ticker *db.CurrencyRatesTicker) (*db.ResultTickerAsString, error) { 1174 currencies = removeEmpty(currencies) 1175 if len(currencies) == 0 { 1176 // Return all available ticker rates 1177 return &db.ResultTickerAsString{ 1178 Timestamp: ticker.Timestamp.UTC().Unix(), 1179 Rates: ticker.Rates, 1180 }, nil 1181 } 1182 // Check if currencies from the list are available in the ticker rates 1183 rates := make(map[string]float64) 1184 for _, currency := range currencies { 1185 currency = strings.ToLower(currency) 1186 if rate, found := ticker.Rates[currency]; found { 1187 rates[currency] = rate 1188 } else { 1189 rates[currency] = -1 1190 } 1191 } 1192 return &db.ResultTickerAsString{ 1193 Timestamp: ticker.Timestamp.UTC().Unix(), 1194 Rates: rates, 1195 }, nil 1196 } 1197 1198 // GetFiatRatesForBlockID returns fiat rates for block height or block hash 1199 func (w *Worker) GetFiatRatesForBlockID(bid string, currencies []string) (*db.ResultTickerAsString, error) { 1200 var ticker *db.CurrencyRatesTicker 1201 bi, err := w.getBlockInfoFromBlockID(bid) 1202 if err != nil { 1203 if err == bchain.ErrBlockNotFound { 1204 return nil, NewAPIError(fmt.Sprintf("Block %v not found", bid), true) 1205 } 1206 return nil, NewAPIError(fmt.Sprintf("Block %v not found, error: %v", bid, err), false) 1207 } 1208 dbi := &db.BlockInfo{Time: bi.Time} // get Unix timestamp from block 1209 tm := time.Unix(dbi.Time, 0) // convert it to Time object 1210 ticker, err = w.db.FiatRatesFindTicker(&tm) 1211 if err != nil { 1212 return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false) 1213 } else if ticker == nil { 1214 return nil, NewAPIError(fmt.Sprintf("No tickers available for %s", tm), true) 1215 } 1216 result, err := w.getFiatRatesResult(currencies, ticker) 1217 if err != nil { 1218 return nil, err 1219 } 1220 return result, nil 1221 } 1222 1223 // GetCurrentFiatRates returns last available fiat rates 1224 func (w *Worker) GetCurrentFiatRates(currencies []string) (*db.ResultTickerAsString, error) { 1225 ticker, err := w.db.FiatRatesFindLastTicker() 1226 if err != nil { 1227 return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false) 1228 } else if ticker == nil { 1229 return nil, NewAPIError(fmt.Sprintf("No tickers found!"), true) 1230 } 1231 result, err := w.getFiatRatesResult(currencies, ticker) 1232 if err != nil { 1233 return nil, err 1234 } 1235 return result, nil 1236 } 1237 1238 // makeErrorRates returns a map of currrencies, with each value equal to -1 1239 // used when there was an error finding ticker 1240 func makeErrorRates(currencies []string) map[string]float64 { 1241 rates := make(map[string]float64) 1242 for _, currency := range currencies { 1243 rates[strings.ToLower(currency)] = -1 1244 } 1245 return rates 1246 } 1247 1248 // GetFiatRatesForTimestamps returns fiat rates for each of the provided dates 1249 func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []string) (*db.ResultTickersAsString, error) { 1250 if len(timestamps) == 0 { 1251 return nil, NewAPIError("No timestamps provided", true) 1252 } 1253 currencies = removeEmpty(currencies) 1254 1255 ret := &db.ResultTickersAsString{} 1256 for _, timestamp := range timestamps { 1257 date := time.Unix(timestamp, 0) 1258 date = date.UTC() 1259 ticker, err := w.db.FiatRatesFindTicker(&date) 1260 if err != nil { 1261 glog.Errorf("Error finding ticker for date %v. Error: %v", date, err) 1262 ret.Tickers = append(ret.Tickers, db.ResultTickerAsString{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) 1263 continue 1264 } else if ticker == nil { 1265 ret.Tickers = append(ret.Tickers, db.ResultTickerAsString{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) 1266 continue 1267 } 1268 result, err := w.getFiatRatesResult(currencies, ticker) 1269 if err != nil { 1270 ret.Tickers = append(ret.Tickers, db.ResultTickerAsString{Timestamp: date.Unix(), Rates: makeErrorRates(currencies)}) 1271 continue 1272 } 1273 ret.Tickers = append(ret.Tickers, *result) 1274 } 1275 return ret, nil 1276 } 1277 1278 // GetFiatRatesTickersList returns the list of available fiatRates tickers 1279 func (w *Worker) GetFiatRatesTickersList(timestamp int64) (*db.ResultTickerListAsString, error) { 1280 date := time.Unix(timestamp, 0) 1281 date = date.UTC() 1282 1283 ticker, err := w.db.FiatRatesFindTicker(&date) 1284 if err != nil { 1285 return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false) 1286 } else if ticker == nil { 1287 return nil, NewAPIError(fmt.Sprintf("No tickers found for date %v.", date), true) 1288 } 1289 1290 keys := make([]string, 0, len(ticker.Rates)) 1291 for k := range ticker.Rates { 1292 keys = append(keys, k) 1293 } 1294 sort.Strings(keys) // sort to get deterministic results 1295 1296 return &db.ResultTickerListAsString{ 1297 Timestamp: ticker.Timestamp.Unix(), 1298 Tickers: keys, 1299 }, nil 1300 } 1301 1302 // getBlockInfoFromBlockID returns block info from block height or block hash 1303 func (w *Worker) getBlockInfoFromBlockID(bid string) (*bchain.BlockInfo, error) { 1304 // try to decide if passed string (bid) is block height or block hash 1305 // if it's a number, must be less than int32 1306 var hash string 1307 height, err := strconv.Atoi(bid) 1308 if err == nil && height < int(maxUint32) { 1309 hash, err = w.db.GetBlockHash(uint32(height)) 1310 if err != nil { 1311 hash = bid 1312 } 1313 } else { 1314 hash = bid 1315 } 1316 if hash == "" { 1317 return nil, NewAPIError("Block not found", true) 1318 } 1319 bi, err := w.chain.GetBlockInfo(hash) 1320 return bi, err 1321 } 1322 1323 // GetFeeStats returns statistics about block fees 1324 func (w *Worker) GetFeeStats(bid string) (*FeeStats, error) { 1325 // txSpecific extends Tx with an additional Size and Vsize info 1326 type txSpecific struct { 1327 *bchain.Tx 1328 Vsize int `json:"vsize,omitempty"` 1329 Size int `json:"size,omitempty"` 1330 } 1331 1332 start := time.Now() 1333 bi, err := w.getBlockInfoFromBlockID(bid) 1334 if err != nil { 1335 if err == bchain.ErrBlockNotFound { 1336 return nil, NewAPIError("Block not found", true) 1337 } 1338 return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true) 1339 } 1340 1341 feesPerKb := make([]int64, 0, len(bi.Txids)) 1342 totalFeesSat := big.NewInt(0) 1343 averageFeePerKb := int64(0) 1344 1345 for _, txid := range bi.Txids { 1346 // Get a raw JSON with transaction details, including size, vsize, hex 1347 txSpecificJSON, err := w.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid}) 1348 if err != nil { 1349 return nil, errors.Annotatef(err, "GetTransactionSpecific") 1350 } 1351 1352 // Serialize the raw JSON into TxSpecific struct 1353 var txSpec txSpecific 1354 err = json.Unmarshal(txSpecificJSON, &txSpec) 1355 if err != nil { 1356 return nil, errors.Annotatef(err, "Unmarshal") 1357 } 1358 1359 // Calculate the TX size in bytes 1360 txSize := 0 1361 if txSpec.Vsize > 0 { 1362 txSize = txSpec.Vsize 1363 } else if txSpec.Size > 0 { 1364 txSize = txSpec.Size 1365 } else if txSpec.Hex != "" { 1366 txSize = len(txSpec.Hex) / 2 1367 } else { 1368 errMsg := "Cannot determine the transaction size from neither Vsize, Size nor Hex! Txid: " + txid 1369 return nil, NewAPIError(errMsg, true) 1370 } 1371 1372 // Get values of TX inputs and outputs 1373 txAddresses, err := w.db.GetTxAddresses(txid) 1374 if err != nil { 1375 return nil, errors.Annotatef(err, "GetTxAddresses") 1376 } 1377 1378 // Calculate total fees in Satoshis 1379 feeSat := big.NewInt(0) 1380 for _, input := range txAddresses.Inputs { 1381 feeSat = feeSat.Add(&input.ValueSat, feeSat) 1382 } 1383 1384 // Zero inputs means it's a Coinbase TX - skip it 1385 if feeSat.Cmp(big.NewInt(0)) == 0 { 1386 continue 1387 } 1388 1389 for _, output := range txAddresses.Outputs { 1390 feeSat = feeSat.Sub(feeSat, &output.ValueSat) 1391 } 1392 totalFeesSat.Add(totalFeesSat, feeSat) 1393 1394 // Convert feeSat to fee per kilobyte and add to an array for decile calculation 1395 feePerKb := int64(float64(feeSat.Int64()) / float64(txSize) * 1000) 1396 averageFeePerKb += feePerKb 1397 feesPerKb = append(feesPerKb, feePerKb) 1398 } 1399 1400 var deciles [11]int64 1401 n := len(feesPerKb) 1402 1403 if n > 0 { 1404 averageFeePerKb /= int64(n) 1405 1406 // Sort fees and calculate the deciles 1407 sort.Slice(feesPerKb, func(i, j int) bool { return feesPerKb[i] < feesPerKb[j] }) 1408 for k := 0; k <= 10; k++ { 1409 index := int(math.Floor(0.5+float64(k)*float64(n+1)/10)) - 1 1410 if index < 0 { 1411 index = 0 1412 } else if index >= n { 1413 index = n - 1 1414 } 1415 deciles[k] = feesPerKb[index] 1416 } 1417 } 1418 1419 glog.Info("GetFeeStats ", bid, " (", len(feesPerKb), " txs) finished in ", time.Since(start)) 1420 1421 return &FeeStats{ 1422 TxCount: len(feesPerKb), 1423 AverageFeePerKb: averageFeePerKb, 1424 TotalFeesSat: (*Amount)(totalFeesSat), 1425 DecilesFeePerKb: deciles, 1426 }, nil 1427 } 1428 1429 // GetBlock returns paged data about block 1430 func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) { 1431 start := time.Now() 1432 page-- 1433 if page < 0 { 1434 page = 0 1435 } 1436 bi, err := w.getBlockInfoFromBlockID(bid) 1437 if err != nil { 1438 if err == bchain.ErrBlockNotFound { 1439 return nil, NewAPIError("Block not found", true) 1440 } 1441 return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true) 1442 } 1443 dbi := &db.BlockInfo{ 1444 Hash: bi.Hash, 1445 Height: bi.Height, 1446 Time: bi.Time, 1447 } 1448 txCount := len(bi.Txids) 1449 bestheight, _, err := w.db.GetBestBlock() 1450 if err != nil { 1451 return nil, errors.Annotatef(err, "GetBestBlock") 1452 } 1453 pg, from, to, page := computePaging(txCount, page, txsOnPage) 1454 txs := make([]*Tx, to-from) 1455 txi := 0 1456 for i := from; i < to; i++ { 1457 txs[txi], err = w.txFromTxid(bi.Txids[i], bestheight, AccountDetailsTxHistoryLight, dbi) 1458 if err != nil { 1459 return nil, err 1460 } 1461 txi++ 1462 } 1463 if bi.Prev == "" && bi.Height != 0 { 1464 bi.Prev, _ = w.db.GetBlockHash(bi.Height - 1) 1465 } 1466 if bi.Next == "" && bi.Height != bestheight { 1467 bi.Next, _ = w.db.GetBlockHash(bi.Height + 1) 1468 } 1469 txs = txs[:txi] 1470 bi.Txids = nil 1471 glog.Info("GetBlock ", bid, ", page ", page, " finished in ", time.Since(start)) 1472 return &Block{ 1473 Paging: pg, 1474 BlockInfo: BlockInfo{ 1475 Hash: bi.Hash, 1476 Prev: bi.Prev, 1477 Next: bi.Next, 1478 Height: bi.Height, 1479 Confirmations: bi.Confirmations, 1480 Size: bi.Size, 1481 Time: bi.Time, 1482 Bits: bi.Bits, 1483 Difficulty: string(bi.Difficulty), 1484 MerkleRoot: bi.MerkleRoot, 1485 Nonce: string(bi.Nonce), 1486 Txids: bi.Txids, 1487 Version: bi.Version, 1488 }, 1489 TxCount: txCount, 1490 Transactions: txs, 1491 }, nil 1492 } 1493 1494 // ComputeFeeStats computes fee distribution in defined blocks and logs them to log 1495 func (w *Worker) ComputeFeeStats(blockFrom, blockTo int, stopCompute chan os.Signal) error { 1496 bestheight, _, err := w.db.GetBestBlock() 1497 if err != nil { 1498 return errors.Annotatef(err, "GetBestBlock") 1499 } 1500 for block := blockFrom; block <= blockTo; block++ { 1501 hash, err := w.db.GetBlockHash(uint32(block)) 1502 if err != nil { 1503 return err 1504 } 1505 bi, err := w.chain.GetBlockInfo(hash) 1506 if err != nil { 1507 return err 1508 } 1509 // process only blocks with enough transactions 1510 if len(bi.Txids) > 20 { 1511 dbi := &db.BlockInfo{ 1512 Hash: bi.Hash, 1513 Height: bi.Height, 1514 Time: bi.Time, 1515 } 1516 txids := bi.Txids 1517 if w.chainType == bchain.ChainBitcoinType { 1518 // skip the coinbase transaction 1519 txids = txids[1:] 1520 } 1521 fees := make([]int64, len(txids)) 1522 sum := int64(0) 1523 for i, txid := range txids { 1524 select { 1525 case <-stopCompute: 1526 glog.Info("ComputeFeeStats interrupted at height ", block) 1527 return db.ErrOperationInterrupted 1528 default: 1529 tx, err := w.txFromTxid(txid, bestheight, AccountDetailsTxHistoryLight, dbi) 1530 if err != nil { 1531 return err 1532 } 1533 fee := tx.FeesSat.AsInt64() 1534 fees[i] = fee 1535 sum += fee 1536 } 1537 } 1538 sort.Slice(fees, func(i, j int) bool { return fees[i] < fees[j] }) 1539 step := float64(len(fees)) / 10 1540 percentils := "" 1541 for i := float64(0); i < float64(len(fees)+1); i += step { 1542 ii := int(math.Round(i)) 1543 if ii >= len(fees) { 1544 ii = len(fees) - 1 1545 } 1546 percentils += "," + strconv.FormatInt(fees[ii], 10) 1547 } 1548 glog.Info(block, ",", time.Unix(bi.Time, 0).Format(time.RFC3339), ",", len(bi.Txids), ",", sum, ",", float64(sum)/float64(len(bi.Txids)), percentils) 1549 } 1550 } 1551 return nil 1552 } 1553 1554 // GetSystemInfo returns information about system 1555 func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) { 1556 start := time.Now() 1557 vi := common.GetVersionInfo() 1558 inSync, bestHeight, lastBlockTime := w.is.GetSyncState() 1559 inSyncMempool, lastMempoolTime, mempoolSize := w.is.GetMempoolSyncState() 1560 ci, err := w.chain.GetChainInfo() 1561 var backendError string 1562 if err != nil { 1563 glog.Error("GetChainInfo error ", err) 1564 backendError = errors.Annotatef(err, "GetChainInfo").Error() 1565 ci = &bchain.ChainInfo{} 1566 // set not in sync in case of backend error 1567 inSync = false 1568 inSyncMempool = false 1569 } 1570 var columnStats []common.InternalStateColumn 1571 var internalDBSize int64 1572 if internal { 1573 columnStats = w.is.GetAllDBColumnStats() 1574 internalDBSize = w.is.DBSizeTotal() 1575 } 1576 blockbookInfo := &BlockbookInfo{ 1577 Coin: w.is.Coin, 1578 Host: w.is.Host, 1579 Version: vi.Version, 1580 GitCommit: vi.GitCommit, 1581 BuildTime: vi.BuildTime, 1582 SyncMode: w.is.SyncMode, 1583 InitialSync: w.is.InitialSync, 1584 InSync: inSync, 1585 BestHeight: bestHeight, 1586 LastBlockTime: lastBlockTime, 1587 InSyncMempool: inSyncMempool, 1588 LastMempoolTime: lastMempoolTime, 1589 MempoolSize: mempoolSize, 1590 Decimals: w.chainParser.AmountDecimals(), 1591 DbSize: w.db.DatabaseSizeOnDisk(), 1592 DbSizeFromColumns: internalDBSize, 1593 DbColumns: columnStats, 1594 About: Text.BlockbookAbout, 1595 } 1596 backendInfo := &BackendInfo{ 1597 BackendError: backendError, 1598 BestBlockHash: ci.Bestblockhash, 1599 Blocks: ci.Blocks, 1600 Chain: ci.Chain, 1601 Difficulty: ci.Difficulty, 1602 Headers: ci.Headers, 1603 ProtocolVersion: ci.ProtocolVersion, 1604 SizeOnDisk: ci.SizeOnDisk, 1605 Subversion: ci.Subversion, 1606 Timeoffset: ci.Timeoffset, 1607 Version: ci.Version, 1608 Warnings: ci.Warnings, 1609 } 1610 glog.Info("GetSystemInfo finished in ", time.Since(start)) 1611 return &SystemInfo{blockbookInfo, backendInfo}, nil 1612 } 1613 1614 // GetMempool returns a page of mempool txids 1615 func (w *Worker) GetMempool(page int, itemsOnPage int) (*MempoolTxids, error) { 1616 page-- 1617 if page < 0 { 1618 page = 0 1619 } 1620 entries := w.mempool.GetAllEntries() 1621 pg, from, to, _ := computePaging(len(entries), page, itemsOnPage) 1622 r := &MempoolTxids{ 1623 Paging: pg, 1624 MempoolSize: len(entries), 1625 } 1626 r.Mempool = make([]MempoolTxid, to-from) 1627 for i := from; i < to; i++ { 1628 entry := &entries[i] 1629 r.Mempool[i-from] = MempoolTxid{ 1630 Txid: entry.Txid, 1631 Time: int64(entry.Time), 1632 } 1633 } 1634 return r, nil 1635 }