github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/api/worker.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "math" 8 "math/big" 9 "os" 10 "sort" 11 "strconv" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/cryptohub-digital/blockbook-fork/bchain" 17 "github.com/cryptohub-digital/blockbook-fork/bchain/coins/eth" 18 "github.com/cryptohub-digital/blockbook-fork/bchain/coins/xcb" 19 "github.com/cryptohub-digital/blockbook-fork/common" 20 "github.com/cryptohub-digital/blockbook-fork/db" 21 "github.com/cryptohub-digital/blockbook-fork/fiat" 22 "github.com/golang/glog" 23 "github.com/juju/errors" 24 ) 25 26 // Worker is handle to api worker 27 type Worker struct { 28 db *db.RocksDB 29 txCache *db.TxCache 30 chain bchain.BlockChain 31 chainParser bchain.BlockChainParser 32 chainType bchain.ChainType 33 useAddressAliases bool 34 mempool bchain.Mempool 35 is *common.InternalState 36 fiatRates *fiat.FiatRates 37 metrics *common.Metrics 38 } 39 40 // NewWorker creates new api worker 41 func NewWorker(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState, fiatRates *fiat.FiatRates) (*Worker, error) { 42 w := &Worker{ 43 db: db, 44 txCache: txCache, 45 chain: chain, 46 chainParser: chain.GetChainParser(), 47 chainType: chain.GetChainParser().GetChainType(), 48 useAddressAliases: chain.GetChainParser().UseAddressAliases(), 49 mempool: mempool, 50 is: is, 51 fiatRates: fiatRates, 52 metrics: metrics, 53 } 54 if w.chainType == bchain.ChainBitcoinType { 55 w.initXpubCache() 56 } 57 return w, nil 58 } 59 60 func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescriptor, []string, bool, error) { 61 addrDesc, err := w.chainParser.GetAddrDescFromVout(vout) 62 if err != nil { 63 return nil, nil, false, err 64 } 65 a, s, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc) 66 return addrDesc, a, s, err 67 } 68 69 // setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output 70 // there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx 71 func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error { 72 err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, maxUint32, func(t string, height uint32, indexes []int32) error { 73 for _, index := range indexes { 74 // take only inputs 75 if index < 0 { 76 index = ^index 77 tsp, err := w.db.GetTxAddresses(t) 78 if err != nil { 79 return err 80 } else if tsp == nil { 81 glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses") 82 } else if len(tsp.Inputs) > int(index) { 83 if tsp.Inputs[index].ValueSat.Cmp((*big.Int)(vout.ValueSat)) == 0 { 84 spentTx, spentHeight, err := w.txCache.GetTransaction(t) 85 if err != nil { 86 glog.Warning("Tx ", t, ": not found") 87 } else { 88 if len(spentTx.Vin) > int(index) { 89 if spentTx.Vin[index].Txid == txid { 90 vout.SpentTxID = t 91 vout.SpentHeight = int(spentHeight) 92 vout.SpentIndex = int(index) 93 return &db.StopIteration{} 94 } 95 } 96 } 97 } 98 } 99 } 100 } 101 return nil 102 }) 103 return err 104 } 105 106 // GetSpendingTxid returns transaction id of transaction that spent given output 107 func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { 108 if w.db.HasExtendedIndex() { 109 tsp, err := w.db.GetTxAddresses(txid) 110 if err != nil { 111 return "", err 112 } else if tsp == nil { 113 glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") 114 return "", NewAPIError(fmt.Sprintf("Txid %v not found", txid), false) 115 } 116 if n >= len(tsp.Outputs) || n < 0 { 117 return "", NewAPIError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, txid, len(tsp.Outputs)), false) 118 } 119 return tsp.Outputs[n].SpentTxid, nil 120 } 121 start := time.Now() 122 tx, err := w.getTransaction(txid, false, false, nil) 123 if err != nil { 124 return "", err 125 } 126 if n >= len(tx.Vout) || n < 0 { 127 return "", NewAPIError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false) 128 } 129 err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight)) 130 if err != nil { 131 return "", err 132 } 133 glog.Info("GetSpendingTxid ", txid, " ", n, ", ", time.Since(start)) 134 return tx.Vout[n].SpentTxID, nil 135 } 136 137 func aggregateAddress(m map[string]struct{}, a string) { 138 if m != nil && len(a) > 0 { 139 m[a] = struct{}{} 140 } 141 } 142 143 func aggregateAddresses(m map[string]struct{}, addresses []string, isAddress bool) { 144 if m != nil && isAddress { 145 for _, a := range addresses { 146 if len(a) > 0 { 147 m[a] = struct{}{} 148 } 149 } 150 } 151 } 152 153 func (w *Worker) newAddressesMapForAliases() map[string]struct{} { 154 // return non nil map only if the chain supports address aliases 155 if w.useAddressAliases { 156 return make(map[string]struct{}) 157 } 158 // returning nil disables the processing of the address aliases 159 return nil 160 } 161 162 func (w *Worker) getAddressAliases(addresses map[string]struct{}) AddressAliasesMap { 163 if len(addresses) > 0 { 164 aliases := make(AddressAliasesMap) 165 var t string 166 if w.chainType == bchain.ChainEthereumType { 167 t = "ENS" 168 } else { 169 t = "Alias" 170 } 171 for a := range addresses { 172 if w.chainType == bchain.ChainEthereumType { 173 ci, err := w.db.GetContractInfoForAddress(a) 174 if err == nil && ci != nil && ci.Name != "" { 175 aliases[a] = AddressAlias{Type: "Contract", Alias: ci.Name} 176 } 177 } 178 n := w.db.GetAddressAlias(a) 179 if len(n) > 0 { 180 aliases[a] = AddressAlias{Type: t, Alias: n} 181 } 182 } 183 return aliases 184 } 185 return nil 186 } 187 188 // GetTransaction reads transaction data from txid 189 func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool) (*Tx, error) { 190 addresses := w.newAddressesMapForAliases() 191 tx, err := w.getTransaction(txid, spendingTxs, specificJSON, addresses) 192 if err != nil { 193 return nil, err 194 } 195 tx.AddressAliases = w.getAddressAliases(addresses) 196 return tx, nil 197 } 198 199 // getTransaction reads transaction data from txid 200 func (w *Worker) getTransaction(txid string, spendingTxs bool, specificJSON bool, addresses map[string]struct{}) (*Tx, error) { 201 bchainTx, height, err := w.txCache.GetTransaction(txid) 202 if err != nil { 203 if err == bchain.ErrTxNotFound { 204 return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found", txid), true) 205 } 206 return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found (%v)", txid, err), true) 207 } 208 return w.getTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON, addresses) 209 } 210 211 func (w *Worker) getParsedEthereumInputData(data string) *bchain.EthereumParsedInputData { 212 var err error 213 var signatures *[]bchain.FourByteSignature 214 fourBytes := eth.GetSignatureFromData(data) 215 if fourBytes != 0 { 216 signatures, err = w.db.GetFourByteSignatures(fourBytes) 217 if err != nil { 218 glog.Errorf("GetFourByteSignatures(%v) error %v", fourBytes, err) 219 return nil 220 } 221 if signatures == nil { 222 return nil 223 } 224 } 225 return eth.ParseInputData(signatures, data) 226 } 227 228 // getConfirmationETA returns confirmation ETA in seconds and blocks 229 func (w *Worker) getConfirmationETA(tx *Tx) (int64, uint32) { 230 var etaBlocks uint32 231 var etaSeconds int64 232 if w.chainType == bchain.ChainBitcoinType && tx.FeesSat != nil { 233 _, _, mempoolSize := w.is.GetMempoolSyncState() 234 // if there are a few transactions in the mempool, the estimate fee does not work well 235 // and the tx is most probably going to be confirmed in the first block 236 if mempoolSize < 32 { 237 etaBlocks = 1 238 } else { 239 var txFeePerKB int64 240 if tx.VSize > 0 { 241 txFeePerKB = 1000 * tx.FeesSat.AsInt64() / int64(tx.VSize) 242 } else if tx.Size > 0 { 243 txFeePerKB = 1000 * tx.FeesSat.AsInt64() / int64(tx.Size) 244 } 245 if txFeePerKB > 0 { 246 // binary search the estimate, split it to more common first 7 blocks and the rest up to 70 blocks 247 var b int 248 fee, _ := w.cachedEstimateFee(7, true) 249 if fee.Int64() <= txFeePerKB { 250 b = sort.Search(7, func(i int) bool { 251 // fee is in sats/kB 252 fee, _ := w.cachedEstimateFee(i+1, true) 253 return fee.Int64() <= txFeePerKB 254 }) 255 b += 1 256 } else { 257 b = sort.Search(63, func(i int) bool { 258 fee, _ := w.cachedEstimateFee(i+7, true) 259 return fee.Int64() <= txFeePerKB 260 }) 261 b += 7 262 } 263 etaBlocks = uint32(b) 264 } 265 } 266 etaSeconds = int64(etaBlocks * w.is.AvgBlockPeriod) 267 } 268 return etaSeconds, etaBlocks 269 } 270 271 // getTransactionFromBchainTx reads transaction data from txid 272 func (w *Worker) getTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spendingTxs bool, specificJSON bool, addresses map[string]struct{}) (*Tx, error) { 273 var err error 274 var ta *db.TxAddresses 275 var tokens []TokenTransfer 276 var ethSpecific *EthereumSpecific 277 var xcbSpecific *CoreCoinSpecific 278 var blockhash string 279 if bchainTx.Confirmations > 0 { 280 if w.chainType == bchain.ChainBitcoinType { 281 ta, err = w.db.GetTxAddresses(bchainTx.Txid) 282 if err != nil { 283 return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainTx.Txid) 284 } 285 } 286 blockhash, err = w.db.GetBlockHash(uint32(height)) 287 if err != nil { 288 return nil, errors.Annotatef(err, "GetBlockHash %v", height) 289 } 290 } 291 var valInSat, valOutSat, feesSat big.Int 292 var pValInSat *big.Int 293 vins := make([]Vin, len(bchainTx.Vin)) 294 rbf := false 295 for i := range bchainTx.Vin { 296 bchainVin := &bchainTx.Vin[i] 297 vin := &vins[i] 298 vin.Txid = bchainVin.Txid 299 vin.N = i 300 vin.Vout = bchainVin.Vout 301 vin.Sequence = int64(bchainVin.Sequence) 302 // detect explicit Replace-by-Fee transactions as defined by BIP125 303 if bchainTx.Confirmations == 0 && bchainVin.Sequence < 0xffffffff-1 { 304 rbf = true 305 } 306 vin.Hex = bchainVin.ScriptSig.Hex 307 vin.Coinbase = bchainVin.Coinbase 308 if w.chainType == bchain.ChainBitcoinType { 309 // bchainVin.Txid=="" is coinbase transaction 310 if bchainVin.Txid != "" { 311 // load spending addresses from TxAddresses 312 tas, err := w.db.GetTxAddresses(bchainVin.Txid) 313 if err != nil { 314 return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid) 315 } 316 if tas == nil { 317 // try to load from backend 318 otx, _, err := w.txCache.GetTransaction(bchainVin.Txid) 319 if err != nil { 320 if err == bchain.ErrTxNotFound { 321 // try to get AddrDesc using coin specific handling and continue processing the tx 322 vin.AddrDesc = w.chainParser.GetAddrDescForUnknownInput(bchainTx, i) 323 vin.Addresses, vin.IsAddress, err = w.chainParser.GetAddressesFromAddrDesc(vin.AddrDesc) 324 if err != nil { 325 glog.Warning("GetAddressesFromAddrDesc tx ", bchainVin.Txid, ", addrDesc ", vin.AddrDesc, ": ", err) 326 } 327 aggregateAddresses(addresses, vin.Addresses, vin.IsAddress) 328 continue 329 } 330 return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid) 331 } 332 // mempool transactions are not in TxAddresses but confirmed should be there, log a problem 333 // ignore when Confirmations==1, it may be just a timing problem 334 if bchainTx.Confirmations > 1 { 335 glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses, confirmations ", bchainTx.Confirmations) 336 } 337 if len(otx.Vout) > int(vin.Vout) { 338 vout := &otx.Vout[vin.Vout] 339 vin.ValueSat = (*Amount)(&vout.ValueSat) 340 vin.AddrDesc, vin.Addresses, vin.IsAddress, err = w.getAddressesFromVout(vout) 341 if err != nil { 342 glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout) 343 } 344 aggregateAddresses(addresses, vin.Addresses, vin.IsAddress) 345 } 346 } else { 347 if len(tas.Outputs) > int(vin.Vout) { 348 output := &tas.Outputs[vin.Vout] 349 vin.ValueSat = (*Amount)(&output.ValueSat) 350 vin.AddrDesc = output.AddrDesc 351 vin.Addresses, vin.IsAddress, err = output.Addresses(w.chainParser) 352 if err != nil { 353 glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i) 354 } 355 aggregateAddresses(addresses, vin.Addresses, vin.IsAddress) 356 } 357 } 358 if vin.ValueSat != nil { 359 valInSat.Add(&valInSat, (*big.Int)(vin.ValueSat)) 360 } 361 } 362 } else if w.chainType == bchain.ChainEthereumType || w.chainType == bchain.ChainCoreCoinType { 363 if len(bchainVin.Addresses) > 0 { 364 vin.AddrDesc, err = w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0]) 365 if err != nil { 366 glog.Errorf("GetAddrDescFromAddress error %v, tx %v, bchainVin %v", err, bchainTx.Txid, bchainVin) 367 } 368 vin.Addresses = bchainVin.Addresses 369 vin.IsAddress = true 370 aggregateAddresses(addresses, vin.Addresses, vin.IsAddress) 371 } 372 } 373 } 374 vouts := make([]Vout, len(bchainTx.Vout)) 375 for i := range bchainTx.Vout { 376 bchainVout := &bchainTx.Vout[i] 377 vout := &vouts[i] 378 vout.N = i 379 vout.ValueSat = (*Amount)(&bchainVout.ValueSat) 380 valOutSat.Add(&valOutSat, &bchainVout.ValueSat) 381 vout.Hex = bchainVout.ScriptPubKey.Hex 382 vout.AddrDesc, vout.Addresses, vout.IsAddress, err = w.getAddressesFromVout(bchainVout) 383 if err != nil { 384 glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, bchainTx.Txid, bchainVout.N) 385 } 386 aggregateAddresses(addresses, vout.Addresses, vout.IsAddress) 387 if ta != nil { 388 vout.Spent = ta.Outputs[i].Spent 389 if vout.Spent { 390 if w.db.HasExtendedIndex() { 391 vout.SpentTxID = ta.Outputs[i].SpentTxid 392 vout.SpentIndex = int(ta.Outputs[i].SpentIndex) 393 vout.SpentHeight = int(ta.Outputs[i].SpentHeight) 394 } else if spendingTxs { 395 err = w.setSpendingTxToVout(vout, bchainTx.Txid, uint32(height)) 396 if err != nil { 397 glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.AddrDesc, vout.N) 398 } 399 } 400 } 401 } 402 } 403 if w.chainType == bchain.ChainBitcoinType { 404 // for coinbase transactions valIn is 0 405 feesSat.Sub(&valInSat, &valOutSat) 406 if feesSat.Sign() == -1 { 407 feesSat.SetUint64(0) 408 } 409 pValInSat = &valInSat 410 } else if w.chainType == bchain.ChainEthereumType { 411 tokenTransfers, err := w.chainParser.EthereumTypeGetTokenTransfersFromTx(bchainTx) 412 if err != nil { 413 glog.Errorf("GetTokenTransfersFromTx error %v, %v", err, bchainTx) 414 } 415 tokens = w.getEthereumTokensTransfers(tokenTransfers, addresses) 416 ethTxData := eth.GetEthereumTxData(bchainTx) 417 418 var internalData *bchain.EthereumInternalData 419 if eth.ProcessInternalTransactions { 420 internalData, err = w.db.GetEthereumInternalData(bchainTx.Txid) 421 if err != nil { 422 return nil, err 423 } 424 } 425 426 parsedInputData := w.getParsedEthereumInputData(ethTxData.Data) 427 428 // mempool txs do not have fees yet 429 if ethTxData.GasUsed != nil { 430 feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed) 431 } 432 if len(bchainTx.Vout) > 0 { 433 valOutSat = bchainTx.Vout[0].ValueSat 434 } 435 ethSpecific = &EthereumSpecific{ 436 GasLimit: ethTxData.GasLimit, 437 GasPrice: (*Amount)(ethTxData.GasPrice), 438 GasUsed: ethTxData.GasUsed, 439 Nonce: ethTxData.Nonce, 440 Status: ethTxData.Status, 441 Data: ethTxData.Data, 442 ParsedData: parsedInputData, 443 } 444 if internalData != nil { 445 ethSpecific.Type = internalData.Type 446 ethSpecific.CreatedContract = internalData.Contract 447 ethSpecific.Error = internalData.Error 448 ethSpecific.InternalTransfers = make([]EthereumInternalTransfer, len(internalData.Transfers)) 449 for i := range internalData.Transfers { 450 f := &internalData.Transfers[i] 451 t := ðSpecific.InternalTransfers[i] 452 t.From = f.From 453 aggregateAddress(addresses, t.From) 454 t.To = f.To 455 aggregateAddress(addresses, t.To) 456 t.Type = f.Type 457 t.Value = (*Amount)(&f.Value) 458 } 459 } 460 461 } else if w.chainType == bchain.ChainCoreCoinType { 462 tokenTransfers, err := w.chainParser.CoreCoinTypeGetTokenTransfersFromTx(bchainTx) 463 if err != nil { 464 glog.Errorf("CoreCoinTypeGetTokenTransfersFromTx error %v, %v", err, bchainTx) 465 } 466 tokens = w.getCoreCoinTokens(tokenTransfers) 467 xcbTxData := xcb.GetCoreCoinTxData(bchainTx) 468 // mempool txs do not have fees yet 469 if xcbTxData.EnergyUsed != nil { 470 feesSat.Mul(xcbTxData.EnergyPrice, xcbTxData.EnergyUsed) 471 } 472 if len(bchainTx.Vout) > 0 { 473 valOutSat = bchainTx.Vout[0].ValueSat 474 } 475 xcbSpecific = &CoreCoinSpecific{ 476 EnergyLimit: xcbTxData.EnergyLimit, 477 EnergyPrice: (*Amount)(xcbTxData.EnergyPrice), 478 EnergyUsed: xcbTxData.EnergyUsed, 479 Nonce: xcbTxData.Nonce, 480 Status: xcbTxData.Status, 481 Data: xcbTxData.Data, 482 } 483 484 } 485 var sj json.RawMessage 486 // return CoinSpecificData for all mempool transactions or if requested 487 if specificJSON || bchainTx.Confirmations == 0 { 488 sj, err = w.chain.GetTransactionSpecific(bchainTx) 489 if err != nil { 490 return nil, err 491 } 492 } 493 r := &Tx{ 494 Blockhash: blockhash, 495 Blockheight: height, 496 Blocktime: bchainTx.Blocktime, 497 Confirmations: bchainTx.Confirmations, 498 FeesSat: (*Amount)(&feesSat), 499 Locktime: bchainTx.LockTime, 500 Txid: bchainTx.Txid, 501 ValueInSat: (*Amount)(pValInSat), 502 ValueOutSat: (*Amount)(&valOutSat), 503 Version: bchainTx.Version, 504 Size: len(bchainTx.Hex) >> 1, 505 VSize: int(bchainTx.VSize), 506 Hex: bchainTx.Hex, 507 Rbf: rbf, 508 Vin: vins, 509 Vout: vouts, 510 CoinSpecificData: sj, 511 TokenTransfers: tokens, 512 EthereumSpecific: ethSpecific, 513 CoreCoinSpecific: xcbSpecific, 514 } 515 if bchainTx.Confirmations == 0 { 516 r.Blocktime = int64(w.mempool.GetTransactionTime(bchainTx.Txid)) 517 r.ConfirmationETASeconds, r.ConfirmationETABlocks = w.getConfirmationETA(r) 518 } 519 return r, nil 520 } 521 522 // GetTransactionFromMempoolTx converts bchain.MempoolTx to Tx, with limited amount of data 523 // it is not doing any request to backend or to db 524 func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx, error) { 525 var err error 526 var valInSat, valOutSat, feesSat big.Int 527 var pValInSat *big.Int 528 var tokens []TokenTransfer 529 var ethSpecific *EthereumSpecific 530 var xcbSpecific *CoreCoinSpecific 531 addresses := w.newAddressesMapForAliases() 532 vins := make([]Vin, len(mempoolTx.Vin)) 533 rbf := false 534 for i := range mempoolTx.Vin { 535 bchainVin := &mempoolTx.Vin[i] 536 vin := &vins[i] 537 vin.Txid = bchainVin.Txid 538 vin.N = i 539 vin.Vout = bchainVin.Vout 540 vin.Sequence = int64(bchainVin.Sequence) 541 // detect explicit Replace-by-Fee transactions as defined by BIP125 542 if bchainVin.Sequence < 0xffffffff-1 { 543 rbf = true 544 } 545 vin.Hex = bchainVin.ScriptSig.Hex 546 vin.Coinbase = bchainVin.Coinbase 547 if w.chainType == bchain.ChainBitcoinType { 548 // bchainVin.Txid=="" is coinbase transaction 549 if bchainVin.Txid != "" { 550 vin.ValueSat = (*Amount)(&bchainVin.ValueSat) 551 vin.AddrDesc = bchainVin.AddrDesc 552 vin.Addresses, vin.IsAddress, _ = w.chainParser.GetAddressesFromAddrDesc(vin.AddrDesc) 553 if vin.ValueSat != nil { 554 valInSat.Add(&valInSat, (*big.Int)(vin.ValueSat)) 555 } 556 aggregateAddresses(addresses, vin.Addresses, vin.IsAddress) 557 } 558 } else if w.chainType == bchain.ChainEthereumType || w.chainType == bchain.ChainCoreCoinType { 559 if len(bchainVin.Addresses) > 0 { 560 vin.AddrDesc, err = w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0]) 561 if err != nil { 562 glog.Errorf("GetAddrDescFromAddress error %v, tx %v, bchainVin %v", err, mempoolTx.Txid, bchainVin) 563 } 564 vin.Addresses = bchainVin.Addresses 565 vin.IsAddress = true 566 aggregateAddresses(addresses, vin.Addresses, vin.IsAddress) 567 } 568 } 569 } 570 vouts := make([]Vout, len(mempoolTx.Vout)) 571 for i := range mempoolTx.Vout { 572 bchainVout := &mempoolTx.Vout[i] 573 vout := &vouts[i] 574 vout.N = i 575 vout.ValueSat = (*Amount)(&bchainVout.ValueSat) 576 valOutSat.Add(&valOutSat, &bchainVout.ValueSat) 577 vout.Hex = bchainVout.ScriptPubKey.Hex 578 vout.AddrDesc, vout.Addresses, vout.IsAddress, err = w.getAddressesFromVout(bchainVout) 579 if err != nil { 580 glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, mempoolTx.Txid, bchainVout.N) 581 } 582 aggregateAddresses(addresses, vout.Addresses, vout.IsAddress) 583 } 584 if w.chainType == bchain.ChainBitcoinType { 585 // for coinbase transactions valIn is 0 586 feesSat.Sub(&valInSat, &valOutSat) 587 if feesSat.Sign() == -1 { 588 feesSat.SetUint64(0) 589 } 590 pValInSat = &valInSat 591 } else if w.chainType == bchain.ChainEthereumType { 592 if len(mempoolTx.Vout) > 0 { 593 valOutSat = mempoolTx.Vout[0].ValueSat 594 } 595 tokens = w.getEthereumTokensTransfers(mempoolTx.TokenTransfers, addresses) 596 ethTxData := eth.GetEthereumTxDataFromSpecificData(mempoolTx.CoinSpecificData) 597 ethSpecific = &EthereumSpecific{ 598 GasLimit: ethTxData.GasLimit, 599 GasPrice: (*Amount)(ethTxData.GasPrice), 600 GasUsed: ethTxData.GasUsed, 601 Nonce: ethTxData.Nonce, 602 Status: ethTxData.Status, 603 Data: ethTxData.Data, 604 } 605 } else if w.chainType == bchain.ChainCoreCoinType { 606 if len(mempoolTx.Vout) > 0 { 607 valOutSat = mempoolTx.Vout[0].ValueSat 608 } 609 tokens = w.getCoreCoinTokens(mempoolTx.TokenTransfers) 610 xcbTxData := xcb.GetCoreCoinTxDataFromSpecificData(mempoolTx.CoinSpecificData) 611 xcbSpecific = &CoreCoinSpecific{ 612 EnergyLimit: xcbTxData.EnergyLimit, 613 EnergyPrice: (*Amount)(xcbTxData.EnergyPrice), 614 EnergyUsed: xcbTxData.EnergyUsed, 615 Nonce: xcbTxData.Nonce, 616 Status: xcbTxData.Status, 617 Data: xcbTxData.Data, 618 } 619 } 620 r := &Tx{ 621 Blocktime: mempoolTx.Blocktime, 622 FeesSat: (*Amount)(&feesSat), 623 Locktime: mempoolTx.LockTime, 624 Txid: mempoolTx.Txid, 625 ValueInSat: (*Amount)(pValInSat), 626 ValueOutSat: (*Amount)(&valOutSat), 627 Version: mempoolTx.Version, 628 Size: len(mempoolTx.Hex) >> 1, 629 VSize: int(mempoolTx.VSize), 630 Hex: mempoolTx.Hex, 631 Rbf: rbf, 632 Vin: vins, 633 Vout: vouts, 634 TokenTransfers: tokens, 635 EthereumSpecific: ethSpecific, 636 CoreCoinSpecific: xcbSpecific, 637 AddressAliases: w.getAddressAliases(addresses), 638 } 639 r.ConfirmationETASeconds, r.ConfirmationETABlocks = w.getConfirmationETA(r) 640 return r, nil 641 } 642 643 func (w *Worker) getContractInfo(contract string, typeFromContext bchain.TokenTypeName) (*bchain.ContractInfo, bool, error) { 644 cd, err := w.chainParser.GetAddrDescFromAddress(contract) 645 if err != nil { 646 return nil, false, err 647 } 648 return w.getContractDescriptorInfo(cd, typeFromContext) 649 } 650 651 func (w *Worker) getContractDescriptorInfo(cd bchain.AddressDescriptor, typeFromContext bchain.TokenTypeName) (*bchain.ContractInfo, bool, error) { 652 var err error 653 validContract := true 654 contractInfo, err := w.db.GetContractInfo(cd, typeFromContext) 655 if err != nil { 656 return nil, false, err 657 } 658 if contractInfo == nil { 659 // log warning only if the contract should have been known from processing of the internal data 660 if eth.ProcessInternalTransactions { 661 glog.Warningf("Contract %v %v not found in DB", cd, typeFromContext) 662 } 663 contractInfo, err = w.chain.GetContractInfo(cd) 664 if err != nil { 665 glog.Errorf("GetContractInfo from chain error %v, contract %v", err, cd) 666 } 667 if contractInfo == nil { 668 contractInfo = &bchain.ContractInfo{Type: bchain.UnknownTokenType, Decimals: w.chainParser.AmountDecimals()} 669 addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(cd) 670 if len(addresses) > 0 { 671 contractInfo.Contract = addresses[0] 672 } 673 674 validContract = false 675 } else { 676 if typeFromContext != bchain.UnknownTokenType && contractInfo.Type == bchain.UnknownTokenType { 677 contractInfo.Type = typeFromContext 678 } 679 if err = w.db.StoreContractInfo(contractInfo); err != nil { 680 glog.Errorf("StoreContractInfo error %v, contract %v", err, cd) 681 } 682 } 683 } else if (len(contractInfo.Name) > 0 && contractInfo.Name[0] == 0) || (len(contractInfo.Symbol) > 0 && contractInfo.Symbol[0] == 0) { 684 // fix contract name/symbol that was parsed as a string consisting of zeroes 685 blockchainContractInfo, err := w.chain.GetContractInfo(cd) 686 if err != nil { 687 glog.Errorf("GetContractInfo from chain error %v, contract %v", err, cd) 688 } else { 689 if blockchainContractInfo != nil && len(blockchainContractInfo.Name) > 0 && blockchainContractInfo.Name[0] != 0 { 690 contractInfo.Name = blockchainContractInfo.Name 691 } else { 692 contractInfo.Name = "" 693 } 694 if blockchainContractInfo != nil && len(blockchainContractInfo.Symbol) > 0 && blockchainContractInfo.Symbol[0] != 0 { 695 contractInfo.Symbol = blockchainContractInfo.Symbol 696 } else { 697 contractInfo.Symbol = "" 698 } 699 if blockchainContractInfo != nil { 700 contractInfo.Decimals = blockchainContractInfo.Decimals 701 } 702 if err = w.db.StoreContractInfo(contractInfo); err != nil { 703 glog.Errorf("StoreContractInfo error %v, contract %v", err, cd) 704 } 705 } 706 } 707 return contractInfo, validContract, nil 708 } 709 710 func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers, addresses map[string]struct{}) []TokenTransfer { 711 sort.Sort(transfers) 712 tokens := make([]TokenTransfer, len(transfers)) 713 for i := range transfers { 714 t := transfers[i] 715 typeName := bchain.TokenTypeMap[t.Type] 716 contractInfo, _, err := w.getContractInfo(t.Contract, typeName) 717 if err != nil { 718 glog.Errorf("getContractInfo error %v, contract %v", err, t.Contract) 719 continue 720 } 721 var value *Amount 722 var values []MultiTokenValue 723 if t.Type == bchain.MultiToken { 724 values = make([]MultiTokenValue, len(t.MultiTokenValues)) 725 for j := range values { 726 values[j].Id = (*Amount)(&t.MultiTokenValues[j].Id) 727 values[j].Value = (*Amount)(&t.MultiTokenValues[j].Value) 728 } 729 } else { 730 value = (*Amount)(&t.Value) 731 } 732 aggregateAddress(addresses, t.From) 733 aggregateAddress(addresses, t.To) 734 tokens[i] = TokenTransfer{ 735 Type: typeName, 736 Contract: t.Contract, 737 From: t.From, 738 To: t.To, 739 Value: value, 740 MultiTokenValues: values, 741 Decimals: contractInfo.Decimals, 742 Name: contractInfo.Name, 743 Symbol: contractInfo.Symbol, 744 } 745 } 746 return tokens 747 } 748 749 func (w *Worker) getCoreCoinTokens(transfers bchain.TokenTransfers) []TokenTransfer { 750 tokens := make([]TokenTransfer, len(transfers)) 751 for i := range transfers { 752 e := transfers[i] 753 cd, err := w.chainParser.GetAddrDescFromAddress(e.Contract) 754 if err != nil { 755 glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, e.Contract) 756 continue 757 } 758 typeName := bchain.TokenTypeMap[e.Type] 759 с, err := w.chain.GetContractInfo(cd) 760 if err != nil { 761 glog.Errorf("getCoreCoinTokens error %v, contract %v", err, e.Contract) 762 } 763 if с == nil { 764 с = &bchain.ContractInfo{Name: e.Contract} 765 } 766 tokens[i] = TokenTransfer{ 767 Type: typeName, 768 Contract: e.Contract, 769 From: e.From, 770 To: e.To, 771 Decimals: с.Decimals, 772 Value: (*Amount)(&e.Value), 773 Name: с.Name, 774 Symbol: с.Symbol, 775 } 776 } 777 return tokens 778 } 779 780 func (w *Worker) GetEthereumTokenURI(contract string, id string) (string, *bchain.ContractInfo, error) { 781 cd, err := w.chainParser.GetAddrDescFromAddress(contract) 782 if err != nil { 783 return "", nil, err 784 } 785 tokenId, ok := new(big.Int).SetString(id, 10) 786 if !ok { 787 return "", nil, errors.New("Invalid token id") 788 } 789 uri, err := w.chain.GetTokenURI(cd, tokenId) 790 if err != nil { 791 return "", nil, err 792 } 793 ci, _, err := w.getContractDescriptorInfo(cd, bchain.UnknownTokenType) 794 if err != nil { 795 return "", nil, err 796 } 797 return uri, ci, nil 798 } 799 800 func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter *AddressFilter, maxResults int) ([]string, error) { 801 var err error 802 txids := make([]string, 0, 4) 803 var callback db.GetTransactionsCallback 804 if filter.Vout == AddressFilterVoutOff { 805 callback = func(txid string, height uint32, indexes []int32) error { 806 txids = append(txids, txid) 807 if len(txids) >= maxResults { 808 return &db.StopIteration{} 809 } 810 return nil 811 } 812 } else { 813 callback = func(txid string, height uint32, indexes []int32) error { 814 for _, index := range indexes { 815 vout := index 816 if vout < 0 { 817 vout = ^vout 818 } 819 if (filter.Vout == AddressFilterVoutInputs && index < 0) || 820 (filter.Vout == AddressFilterVoutOutputs && index >= 0) || 821 (vout == int32(filter.Vout)) { 822 txids = append(txids, txid) 823 if len(txids) >= maxResults { 824 return &db.StopIteration{} 825 } 826 break 827 } 828 } 829 return nil 830 } 831 } 832 if mempool { 833 uniqueTxs := make(map[string]struct{}) 834 o, err := w.mempool.GetAddrDescTransactions(addrDesc) 835 if err != nil { 836 return nil, err 837 } 838 for _, m := range o { 839 if _, found := uniqueTxs[m.Txid]; !found { 840 l := len(txids) 841 callback(m.Txid, 0, []int32{m.Vout}) 842 if len(txids) > l { 843 uniqueTxs[m.Txid] = struct{}{} 844 } 845 } 846 } 847 } else { 848 to := filter.ToHeight 849 if to == 0 { 850 to = maxUint32 851 } 852 err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback) 853 if err != nil { 854 return nil, err 855 } 856 } 857 return txids, nil 858 } 859 860 func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int { 861 var val big.Int 862 for _, vout := range t.Vout { 863 if bytes.Equal(vout.AddrDesc, addrDesc) && vout.ValueSat != nil { 864 val.Add(&val, (*big.Int)(vout.ValueSat)) 865 } 866 } 867 return &val 868 } 869 func (t *Tx) getAddrEthereumTypeMempoolInputValue(addrDesc bchain.AddressDescriptor) *big.Int { 870 var val big.Int 871 if len(t.Vin) > 0 && len(t.Vout) > 0 && bytes.Equal(t.Vin[0].AddrDesc, addrDesc) { 872 val.Add(&val, (*big.Int)(t.Vout[0].ValueSat)) 873 // add maximum possible fee (the used value is not yet known) 874 if t.EthereumSpecific != nil && t.EthereumSpecific.GasLimit != nil && t.EthereumSpecific.GasPrice != nil { 875 var fees big.Int 876 fees.Mul((*big.Int)(t.EthereumSpecific.GasPrice), t.EthereumSpecific.GasLimit) 877 val.Add(&val, &fees) 878 } 879 } 880 return &val 881 } 882 883 func (t *Tx) getAddrCoreCoinTypeMempoolInputValue(addrDesc bchain.AddressDescriptor) *big.Int { 884 var val big.Int 885 if len(t.Vin) > 0 && len(t.Vout) > 0 && bytes.Equal(t.Vin[0].AddrDesc, addrDesc) { 886 val.Add(&val, (*big.Int)(t.Vout[0].ValueSat)) 887 // add maximum possible fee (the used value is not yet known) 888 if t.CoreCoinSpecific != nil && t.CoreCoinSpecific.EnergyLimit != nil && t.CoreCoinSpecific.EnergyPrice != nil { 889 var fees big.Int 890 fees.Mul((*big.Int)(t.CoreCoinSpecific.EnergyPrice), t.CoreCoinSpecific.EnergyLimit) 891 val.Add(&val, &fees) 892 } 893 } 894 return &val 895 } 896 897 func (t *Tx) getAddrVinValue(addrDesc bchain.AddressDescriptor) *big.Int { 898 var val big.Int 899 for _, vin := range t.Vin { 900 if bytes.Equal(vin.AddrDesc, addrDesc) && vin.ValueSat != nil { 901 val.Add(&val, (*big.Int)(vin.ValueSat)) 902 } 903 } 904 return &val 905 } 906 907 // GetUniqueTxids removes duplicate transactions 908 func GetUniqueTxids(txids []string) []string { 909 ut := make([]string, len(txids)) 910 txidsMap := make(map[string]struct{}) 911 i := 0 912 for _, txid := range txids { 913 _, e := txidsMap[txid] 914 if !e { 915 ut[i] = txid 916 i++ 917 txidsMap[txid] = struct{}{} 918 } 919 } 920 return ut[0:i] 921 } 922 923 func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32, addresses map[string]struct{}) *Tx { 924 var err error 925 var valInSat, valOutSat, feesSat big.Int 926 vins := make([]Vin, len(ta.Inputs)) 927 for i := range ta.Inputs { 928 tai := &ta.Inputs[i] 929 vin := &vins[i] 930 vin.N = i 931 vin.ValueSat = (*Amount)(&tai.ValueSat) 932 valInSat.Add(&valInSat, &tai.ValueSat) 933 vin.Addresses, vin.IsAddress, err = tai.Addresses(w.chainParser) 934 if err != nil { 935 glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai) 936 } 937 if w.db.HasExtendedIndex() { 938 vin.Txid = tai.Txid 939 vin.Vout = tai.Vout 940 } 941 aggregateAddresses(addresses, vin.Addresses, vin.IsAddress) 942 } 943 vouts := make([]Vout, len(ta.Outputs)) 944 for i := range ta.Outputs { 945 tao := &ta.Outputs[i] 946 vout := &vouts[i] 947 vout.N = i 948 vout.ValueSat = (*Amount)(&tao.ValueSat) 949 valOutSat.Add(&valOutSat, &tao.ValueSat) 950 vout.Addresses, vout.IsAddress, err = tao.Addresses(w.chainParser) 951 if err != nil { 952 glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao) 953 } 954 vout.Spent = tao.Spent 955 if vout.Spent && w.db.HasExtendedIndex() { 956 vout.SpentTxID = tao.SpentTxid 957 vout.SpentIndex = int(tao.SpentIndex) 958 vout.SpentHeight = int(tao.SpentHeight) 959 } 960 aggregateAddresses(addresses, vout.Addresses, vout.IsAddress) 961 } 962 // for coinbase transactions valIn is 0 963 feesSat.Sub(&valInSat, &valOutSat) 964 if feesSat.Sign() == -1 { 965 feesSat.SetUint64(0) 966 } 967 r := &Tx{ 968 Blockhash: bi.Hash, 969 Blockheight: int(ta.Height), 970 Blocktime: bi.Time, 971 Confirmations: bestheight - ta.Height + 1, 972 FeesSat: (*Amount)(&feesSat), 973 Txid: txid, 974 ValueInSat: (*Amount)(&valInSat), 975 ValueOutSat: (*Amount)(&valOutSat), 976 Vin: vins, 977 Vout: vouts, 978 } 979 if w.chainParser.SupportsVSize() { 980 r.VSize = int(ta.VSize) 981 } else { 982 r.Size = int(ta.VSize) 983 } 984 return r 985 } 986 987 func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) { 988 from := page * itemsOnPage 989 totalPages := (count - 1) / itemsOnPage 990 if totalPages < 0 { 991 totalPages = 0 992 } 993 if from >= count { 994 page = totalPages 995 } 996 from = page * itemsOnPage 997 to := (page + 1) * itemsOnPage 998 if to > count { 999 to = count 1000 } 1001 return Paging{ 1002 ItemsOnPage: itemsOnPage, 1003 Page: page + 1, 1004 TotalPages: totalPages + 1, 1005 }, from, to, page 1006 } 1007 1008 func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, index int, c *db.AddrContract, details AccountDetails, ticker *common.CurrencyRatesTicker, secondaryCoin string) (*Token, error) { 1009 typeName := bchain.TokenTypeMap[c.Type] 1010 ci, validContract, err := w.getContractDescriptorInfo(c.Contract, typeName) 1011 if err != nil { 1012 return nil, errors.Annotatef(err, "getEthereumContractBalance %v", c.Contract) 1013 } 1014 t := Token{ 1015 Contract: ci.Contract, 1016 Name: ci.Name, 1017 Symbol: ci.Symbol, 1018 Type: typeName, 1019 Transfers: int(c.Txs), 1020 Decimals: ci.Decimals, 1021 ContractIndex: strconv.Itoa(index), 1022 } 1023 // return contract balances/values only at or above AccountDetailsTokenBalances 1024 if details >= AccountDetailsTokenBalances && validContract { 1025 if c.Type == bchain.FungibleToken { 1026 // get Erc20 Contract Balance from blockchain, balance obtained from adding and subtracting transfers is not correct 1027 b, err := w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract) 1028 if err != nil { 1029 // return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract) 1030 glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err) 1031 } else { 1032 t.BalanceSat = (*Amount)(b) 1033 if secondaryCoin != "" { 1034 baseRate, found := w.GetContractBaseRate(ticker, t.Contract, 0) 1035 if found { 1036 value, err := strconv.ParseFloat(t.BalanceSat.DecimalString(t.Decimals), 64) 1037 if err == nil { 1038 t.BaseValue = value * baseRate 1039 if ticker != nil { 1040 secondaryRate, found := ticker.Rates[secondaryCoin] 1041 if found { 1042 t.SecondaryValue = t.BaseValue * float64(secondaryRate) 1043 } 1044 } 1045 } 1046 } 1047 } 1048 } 1049 } else { 1050 if len(c.Ids) > 0 { 1051 ids := make([]Amount, len(c.Ids)) 1052 for j := range ids { 1053 ids[j] = (Amount)(c.Ids[j]) 1054 } 1055 t.Ids = ids 1056 } 1057 if len(c.MultiTokenValues) > 0 { 1058 idValues := make([]MultiTokenValue, len(c.MultiTokenValues)) 1059 for j := range idValues { 1060 idValues[j].Id = (*Amount)(&c.MultiTokenValues[j].Id) 1061 idValues[j].Value = (*Amount)(&c.MultiTokenValues[j].Value) 1062 } 1063 t.MultiTokenValues = idValues 1064 } 1065 } 1066 } 1067 1068 return &t, nil 1069 } 1070 1071 func (w *Worker) getCoreCoinContractBalance(addrDesc bchain.AddressDescriptor, index int, c *db.AddrContract, details AccountDetails) (*Token, error) { 1072 typeName := bchain.TokenTypeMap[c.Type] 1073 ci, validContract, err := w.getContractDescriptorInfo(c.Contract, typeName) 1074 if err != nil { 1075 return nil, errors.Annotatef(err, "getCoreCoinContractBalance %v", c.Contract) 1076 } 1077 t := Token{ 1078 Contract: ci.Contract, 1079 Name: ci.Name, 1080 Symbol: ci.Symbol, 1081 Type: typeName, 1082 Transfers: int(c.Txs), 1083 Decimals: ci.Decimals, 1084 ContractIndex: strconv.Itoa(index), 1085 } 1086 // return contract balances/values only at or above AccountDetailsTokenBalances 1087 if details >= AccountDetailsTokenBalances && validContract { 1088 if c.Type == bchain.FungibleToken { 1089 // get Crc20 Contract Balance from blockchain, balance obtained from adding and subtracting transfers is not correct 1090 b, err := w.chain.CoreCoinTypeGetCrc20ContractBalance(addrDesc, c.Contract) 1091 if err != nil { 1092 // return nil, nil, nil, errors.Annotatef(err, "CoreCoinTypeGetCrc20ContractBalance %v %v", addrDesc, c.Contract) 1093 glog.Warningf("CoreCoinTypeGetCrc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err) 1094 } else { 1095 t.BalanceSat = (*Amount)(b) 1096 } 1097 } else { 1098 if len(c.Ids) > 0 { 1099 ids := make([]Amount, len(c.Ids)) 1100 for j := range ids { 1101 ids[j] = (Amount)(c.Ids[j]) 1102 } 1103 t.Ids = ids 1104 } 1105 } 1106 } 1107 1108 return &t, nil 1109 } 1110 1111 // a fallback method in case internal transactions are not processed and there is no indexed info about contract balance for an address 1112 func (w *Worker) getCoreCoinContractBalanceFromBlockchain(addrDesc, contract bchain.AddressDescriptor, details AccountDetails) (*Token, error) { 1113 var b *big.Int 1114 ci, validContract, err := w.getContractDescriptorInfo(contract, bchain.UnknownTokenType) 1115 if err != nil { 1116 return nil, errors.Annotatef(err, "GetContractInfo %v", contract) 1117 } 1118 // do not read contract balances etc in case of Basic option 1119 if details >= AccountDetailsTokenBalances && validContract { 1120 b, err = w.chain.CoreCoinTypeGetCrc20ContractBalance(addrDesc, contract) 1121 if err != nil { 1122 // return nil, nil, nil, errors.Annotatef(err, "CoreCoinTypeGetCrc20ContractBalance %v %v", addrDesc, c.Contract) 1123 glog.Warningf("CoreCoinTypeGetCrc20ContractBalance addr %v, contract %v, %v", addrDesc, contract, err) 1124 } 1125 } else { 1126 b = nil 1127 } 1128 return &Token{ 1129 Type: ci.Type, 1130 BalanceSat: (*Amount)(b), 1131 Contract: ci.Contract, 1132 Name: ci.Name, 1133 Symbol: ci.Symbol, 1134 Transfers: 0, 1135 Decimals: ci.Decimals, 1136 ContractIndex: "0", 1137 }, nil 1138 } 1139 1140 // a fallback method in case internal transactions are not processed and there is no indexed info about contract balance for an address 1141 func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bchain.AddressDescriptor, details AccountDetails) (*Token, error) { 1142 var b *big.Int 1143 ci, validContract, err := w.getContractDescriptorInfo(contract, bchain.UnknownTokenType) 1144 if err != nil { 1145 return nil, errors.Annotatef(err, "GetContractInfo %v", contract) 1146 } 1147 // do not read contract balances etc in case of Basic option 1148 if details >= AccountDetailsTokenBalances && validContract { 1149 b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, contract) 1150 if err != nil { 1151 // return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract) 1152 glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, contract, err) 1153 } 1154 } else { 1155 b = nil 1156 } 1157 return &Token{ 1158 Type: ci.Type, 1159 BalanceSat: (*Amount)(b), 1160 Contract: ci.Contract, 1161 Name: ci.Name, 1162 Symbol: ci.Symbol, 1163 Transfers: 0, 1164 Decimals: ci.Decimals, 1165 ContractIndex: "0", 1166 }, nil 1167 } 1168 1169 // GetContractBaseRate returns contract rate in base coin from the ticker or DB at the timestamp. Zero timestamp means now. 1170 func (w *Worker) GetContractBaseRate(ticker *common.CurrencyRatesTicker, token string, timestamp int64) (float64, bool) { 1171 if ticker == nil { 1172 return 0, false 1173 } 1174 rate, found := ticker.GetTokenRate(token) 1175 if !found { 1176 if timestamp == 0 { 1177 ticker = w.fiatRates.GetCurrentTicker("", token) 1178 } else { 1179 tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{timestamp}, "", token) 1180 if err != nil || tickers == nil || len(*tickers) == 0 { 1181 ticker = nil 1182 } else { 1183 ticker = (*tickers)[0] 1184 } 1185 } 1186 if ticker == nil { 1187 return 0, false 1188 } 1189 rate, found = ticker.GetTokenRate(token) 1190 } 1191 1192 return float64(rate), found 1193 } 1194 1195 type ethereumTypeAddressData struct { 1196 tokens Tokens 1197 contractInfo *bchain.ContractInfo 1198 nonce string 1199 nonContractTxs int 1200 internalTxs int 1201 totalResults int 1202 tokensBaseValue float64 1203 tokensSecondaryValue float64 1204 } 1205 1206 func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter, secondaryCoin string) (*db.AddrBalance, *ethereumTypeAddressData, error) { 1207 var ba *db.AddrBalance 1208 var n uint64 1209 // unknown number of results for paging initially 1210 d := ethereumTypeAddressData{totalResults: -1} 1211 ca, err := w.db.GetAddrDescContracts(addrDesc) 1212 if err != nil { 1213 return nil, nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 1214 } 1215 b, err := w.chain.EthereumTypeGetBalance(addrDesc) 1216 if err != nil { 1217 return nil, nil, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc) 1218 } 1219 var filterDesc bchain.AddressDescriptor 1220 if filter.Contract != "" { 1221 filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract) 1222 if err != nil { 1223 return nil, nil, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true) 1224 } 1225 } 1226 if ca != nil { 1227 ba = &db.AddrBalance{ 1228 Txs: uint32(ca.TotalTxs), 1229 } 1230 if b != nil { 1231 ba.BalanceSat = *b 1232 } 1233 n, err = w.chain.EthereumTypeGetNonce(addrDesc) 1234 if err != nil { 1235 return nil, nil, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc) 1236 } 1237 ticker := w.fiatRates.GetCurrentTicker("", "") 1238 if details > AccountDetailsBasic { 1239 d.tokens = make([]Token, len(ca.Contracts)) 1240 var j int 1241 for i := range ca.Contracts { 1242 c := &ca.Contracts[i] 1243 if len(filterDesc) > 0 { 1244 if !bytes.Equal(filterDesc, c.Contract) { 1245 continue 1246 } 1247 // filter only transactions of this contract 1248 filter.Vout = i + db.ContractIndexOffset 1249 } 1250 t, err := w.getEthereumContractBalance(addrDesc, i+db.ContractIndexOffset, c, details, ticker, secondaryCoin) 1251 if err != nil { 1252 return nil, nil, err 1253 } 1254 d.tokens[j] = *t 1255 d.tokensBaseValue += t.BaseValue 1256 d.tokensSecondaryValue += t.SecondaryValue 1257 j++ 1258 } 1259 d.tokens = d.tokens[:j] 1260 sort.Sort(d.tokens) 1261 } 1262 d.contractInfo, err = w.db.GetContractInfo(addrDesc, "") 1263 if err != nil { 1264 return nil, nil, err 1265 } 1266 if filter.FromHeight == 0 && filter.ToHeight == 0 { 1267 // compute total results for paging 1268 if filter.Vout == AddressFilterVoutOff { 1269 d.totalResults = int(ca.TotalTxs) 1270 } else if filter.Vout == 0 { 1271 d.totalResults = int(ca.NonContractTxs) 1272 } else if filter.Vout == db.InternalTxIndexOffset { 1273 d.totalResults = int(ca.InternalTxs) 1274 } else if filter.Vout >= db.ContractIndexOffset && filter.Vout-db.ContractIndexOffset < len(ca.Contracts) { 1275 d.totalResults = int(ca.Contracts[filter.Vout-db.ContractIndexOffset].Txs) 1276 } else if filter.Vout == AddressFilterVoutQueryNotNecessary { 1277 d.totalResults = 0 1278 } 1279 } 1280 d.nonContractTxs = int(ca.NonContractTxs) 1281 d.internalTxs = int(ca.InternalTxs) 1282 } else { 1283 // addresses without any normal transactions can have internal transactions that were not processed and therefore balance 1284 if b != nil { 1285 ba = &db.AddrBalance{ 1286 BalanceSat: *b, 1287 } 1288 } 1289 } 1290 // returns 0 for unknown address 1291 d.nonce = strconv.Itoa(int(n)) 1292 // special handling if filtering for a contract, return the contract details even though the address had no transactions with it 1293 if len(d.tokens) == 0 && len(filterDesc) > 0 && details >= AccountDetailsTokens { 1294 t, err := w.getEthereumContractBalanceFromBlockchain(addrDesc, filterDesc, details) 1295 if err != nil { 1296 return nil, nil, err 1297 } 1298 d.tokens = []Token{*t} 1299 // switch off query for transactions, there are no transactions 1300 filter.Vout = AddressFilterVoutQueryNotNecessary 1301 d.totalResults = -1 1302 } 1303 return ba, &d, nil 1304 } 1305 1306 func (w *Worker) getCoreCoinTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, *ethereumTypeAddressData, error) { 1307 var ba *db.AddrBalance 1308 var n uint64 1309 // unknown number of results for paging initially 1310 d := ethereumTypeAddressData{totalResults: -1} 1311 ca, err := w.db.GetCoreCoinAddrDescContracts(addrDesc) 1312 if err != nil { 1313 return nil, nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 1314 } 1315 b, err := w.chain.CoreCoinTypeGetBalance(addrDesc) 1316 if err != nil { 1317 return nil, nil, errors.Annotatef(err, "CoreCoinTypeGetBalance %v", addrDesc) 1318 } 1319 var filterDesc bchain.AddressDescriptor 1320 if filter.Contract != "" { 1321 filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract) 1322 if err != nil { 1323 return nil, nil, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true) 1324 } 1325 } 1326 if ca != nil { 1327 ba = &db.AddrBalance{ 1328 Txs: uint32(ca.TotalTxs), 1329 } 1330 if b != nil { 1331 ba.BalanceSat = *b 1332 } 1333 n, err = w.chain.CoreCoinTypeGetNonce(addrDesc) 1334 if err != nil { 1335 return nil, nil, errors.Annotatef(err, "CoreCoinTypeGetNonce %v", addrDesc) 1336 } 1337 if details > AccountDetailsBasic { 1338 d.tokens = make([]Token, len(ca.Contracts)) 1339 var j int 1340 for i := range ca.Contracts { 1341 c := &ca.Contracts[i] 1342 if len(filterDesc) > 0 { 1343 if !bytes.Equal(filterDesc, c.Contract) { 1344 continue 1345 } 1346 // filter only transactions of this contract 1347 filter.Vout = i + db.ContractIndexOffset 1348 } 1349 t, err := w.getCoreCoinContractBalance(addrDesc, i+db.ContractIndexOffset, c, details) 1350 if err != nil { 1351 return nil, nil, err 1352 } 1353 d.tokens[j] = *t 1354 j++ 1355 } 1356 d.tokens = d.tokens[:j] 1357 sort.Sort(d.tokens) 1358 } 1359 d.contractInfo, err = w.db.GetContractInfo(addrDesc, "") 1360 if err != nil { 1361 return nil, nil, err 1362 } 1363 if filter.FromHeight == 0 && filter.ToHeight == 0 { 1364 // compute total results for paging 1365 if filter.Vout == AddressFilterVoutOff { 1366 d.totalResults = int(ca.TotalTxs) 1367 } else if filter.Vout == 0 { 1368 d.totalResults = int(ca.NonContractTxs) 1369 } else if filter.Vout == db.InternalTxIndexOffset { 1370 d.totalResults = int(ca.InternalTxs) 1371 } else if filter.Vout >= db.ContractIndexOffset && filter.Vout-db.ContractIndexOffset < len(ca.Contracts) { 1372 d.totalResults = int(ca.Contracts[filter.Vout-db.ContractIndexOffset].Txs) 1373 } else if filter.Vout == AddressFilterVoutQueryNotNecessary { 1374 d.totalResults = 0 1375 } 1376 } 1377 d.nonContractTxs = int(ca.NonContractTxs) 1378 } else { 1379 // addresses without any normal transactions can have internal transactions that were not processed and therefore balance 1380 if b != nil { 1381 ba = &db.AddrBalance{ 1382 BalanceSat: *b, 1383 } 1384 } 1385 } 1386 // returns 0 for unknown address 1387 d.nonce = strconv.Itoa(int(n)) 1388 // special handling if filtering for a contract, return the contract details even though the address had no transactions with it 1389 if len(d.tokens) == 0 && len(filterDesc) > 0 && details >= AccountDetailsTokens { 1390 t, err := w.getCoreCoinContractBalanceFromBlockchain(addrDesc, filterDesc, details) 1391 if err != nil { 1392 return nil, nil, err 1393 } 1394 d.tokens = []Token{*t} 1395 // switch off query for transactions, there are no transactions 1396 filter.Vout = AddressFilterVoutQueryNotNecessary 1397 d.totalResults = -1 1398 } 1399 return ba, &d, nil 1400 } 1401 1402 func (w *Worker) getCoreCoinToken(index int, addrDesc, contract bchain.AddressDescriptor, details AccountDetails, txs int) (*Token, error) { 1403 var b *big.Int 1404 validContract := true 1405 ci, err := w.chain.GetContractInfo(contract) 1406 if err != nil { 1407 return nil, errors.Annotatef(err, "GetContractInfo %v", contract) 1408 } 1409 if ci == nil { 1410 ci = &bchain.ContractInfo{} 1411 addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(contract) 1412 if len(addresses) > 0 { 1413 ci.Contract = addresses[0] 1414 ci.Name = addresses[0] 1415 } 1416 validContract = false 1417 } 1418 // do not read contract balances etc in case of Basic option 1419 if details >= AccountDetailsTokenBalances && validContract { 1420 b, err = w.chain.CoreCoinTypeGetCrc20ContractBalance(addrDesc, contract) 1421 if err != nil { 1422 // return nil, nil, nil, errors.Annotatef(err, "CoreCoinTypeGetCrc20ContractBalance %v %v", addrDesc, c.Contract) 1423 glog.Warningf("CoreCoinTypeGetCrc20ContractBalance addr %v, contract %v, %v", addrDesc, contract, err) 1424 } 1425 } else { 1426 b = nil 1427 } 1428 return &Token{ 1429 Type: ci.Type, 1430 BalanceSat: (*Amount)(b), 1431 Contract: ci.Contract, 1432 Name: ci.Name, 1433 Symbol: ci.Symbol, 1434 Transfers: txs, 1435 Decimals: ci.Decimals, 1436 ContractIndex: strconv.Itoa(index), 1437 }, nil 1438 } 1439 1440 func (w *Worker) txFromTxid(txid string, bestHeight uint32, option AccountDetails, blockInfo *db.BlockInfo, addresses map[string]struct{}) (*Tx, error) { 1441 var tx *Tx 1442 var err error 1443 // only ChainBitcoinType supports TxHistoryLight 1444 if option == AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType { 1445 ta, err := w.db.GetTxAddresses(txid) 1446 if err != nil { 1447 return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) 1448 } 1449 if ta == nil { 1450 glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") 1451 // as fallback, get tx from backend 1452 tx, err = w.getTransaction(txid, false, false, addresses) 1453 if err != nil { 1454 return nil, errors.Annotatef(err, "getTransaction %v", txid) 1455 } 1456 } else { 1457 if blockInfo == nil { 1458 blockInfo, err = w.db.GetBlockInfo(ta.Height) 1459 if err != nil { 1460 return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height) 1461 } 1462 if blockInfo == nil { 1463 glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db") 1464 // provide empty BlockInfo to return the rest of tx data 1465 blockInfo = &db.BlockInfo{} 1466 } 1467 } 1468 tx = w.txFromTxAddress(txid, ta, blockInfo, bestHeight, addresses) 1469 } 1470 } else { 1471 tx, err = w.getTransaction(txid, false, false, addresses) 1472 if err != nil { 1473 return nil, errors.Annotatef(err, "getTransaction %v", txid) 1474 } 1475 } 1476 return tx, nil 1477 } 1478 1479 func (w *Worker) getAddrDescAndNormalizeAddress(address string) (bchain.AddressDescriptor, string, error) { 1480 addrDesc, err := w.chainParser.GetAddrDescFromAddress(address) 1481 if err != nil { 1482 var errAd error 1483 // try if the address is not address descriptor converted to string 1484 addrDesc, errAd = bchain.AddressDescriptorFromString(address) 1485 if errAd != nil { 1486 return nil, "", NewAPIError(fmt.Sprintf("Invalid address, %v", err), true) 1487 } 1488 } 1489 // convert the address to the format defined by the parser 1490 addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc) 1491 if err != nil { 1492 glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc) 1493 } 1494 if len(addresses) == 1 { 1495 address = addresses[0] 1496 } 1497 return addrDesc, address, nil 1498 } 1499 1500 func isOwnAddress(address string, addresses []string) bool { 1501 if len(addresses) == 1 { 1502 return address == addresses[0] 1503 } 1504 return false 1505 } 1506 1507 func setIsOwnAddress(tx *Tx, address string) { 1508 for j := range tx.Vin { 1509 vin := &tx.Vin[j] 1510 if isOwnAddress(address, vin.Addresses) { 1511 vin.IsOwn = true 1512 } 1513 } 1514 for j := range tx.Vout { 1515 vout := &tx.Vout[j] 1516 if isOwnAddress(address, vout.Addresses) { 1517 vout.IsOwn = true 1518 } 1519 } 1520 } 1521 1522 // GetAddress computes address value and gets transactions for given address 1523 func (w *Worker) GetAddress(address string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, secondaryCoin string) (*Address, error) { 1524 start := time.Now() 1525 page-- 1526 if page < 0 { 1527 page = 0 1528 } 1529 var ( 1530 ba *db.AddrBalance 1531 txm []string 1532 txs []*Tx 1533 txids []string 1534 pg Paging 1535 uBalSat big.Int 1536 totalReceived, totalSent *big.Int 1537 unconfirmedTxs int 1538 totalResults int 1539 ) 1540 ed := ðereumTypeAddressData{} 1541 addrDesc, address, err := w.getAddrDescAndNormalizeAddress(address) 1542 if err != nil { 1543 return nil, err 1544 } 1545 if w.chainType == bchain.ChainEthereumType { 1546 ba, ed, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter, secondaryCoin) 1547 if err != nil { 1548 return nil, err 1549 } 1550 totalResults = ed.totalResults 1551 } else if w.chainType == bchain.ChainCoreCoinType { 1552 // var nonce uint64 1553 ba, ed, err = w.getCoreCoinTypeAddressBalances(addrDesc, option, filter) 1554 // ed.nonce = strconv.Itoa(int(nonce)) 1555 if err != nil { 1556 return nil, err 1557 } 1558 totalResults = ed.totalResults 1559 } else { 1560 // ba can be nil if the address is only in mempool! 1561 ba, err = w.db.GetAddrDescBalance(addrDesc, db.AddressBalanceDetailNoUTXO) 1562 if err != nil { 1563 return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 1564 } 1565 if ba != nil { 1566 // totalResults is known only if there is no filter 1567 if filter.Vout == AddressFilterVoutOff && filter.FromHeight == 0 && filter.ToHeight == 0 { 1568 totalResults = int(ba.Txs) 1569 } else { 1570 totalResults = -1 1571 } 1572 } 1573 } 1574 // if there are only unconfirmed transactions, there is no paging 1575 if ba == nil { 1576 ba = &db.AddrBalance{} 1577 page = 0 1578 } 1579 addresses := w.newAddressesMapForAliases() 1580 // process mempool, only if toHeight is not specified 1581 if filter.ToHeight == 0 && !filter.OnlyConfirmed { 1582 txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt) 1583 if err != nil { 1584 return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc) 1585 } 1586 for _, txid := range txm { 1587 tx, err := w.getTransaction(txid, false, true, addresses) 1588 // mempool transaction may fail 1589 if err != nil || tx == nil { 1590 glog.Warning("GetTransaction in mempool: ", err) 1591 } else { 1592 // skip already confirmed txs, mempool may be out of sync 1593 if tx.Confirmations == 0 { 1594 unconfirmedTxs++ 1595 uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc)) 1596 // ethereum and corecoin have a different logic - value not in input and add maximum possible fees 1597 if w.chainType == bchain.ChainEthereumType { 1598 uBalSat.Sub(&uBalSat, tx.getAddrEthereumTypeMempoolInputValue(addrDesc)) 1599 } else if w.chainType == bchain.ChainCoreCoinType { 1600 uBalSat.Sub(&uBalSat, tx.getAddrCoreCoinTypeMempoolInputValue(addrDesc)) 1601 } else { 1602 uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc)) 1603 } 1604 if page == 0 { 1605 if option == AccountDetailsTxidHistory { 1606 txids = append(txids, tx.Txid) 1607 } else if option >= AccountDetailsTxHistoryLight { 1608 setIsOwnAddress(tx, address) 1609 txs = append(txs, tx) 1610 } 1611 } 1612 } 1613 } 1614 } 1615 } 1616 // get tx history if requested by option or check mempool if there are some transactions for a new address 1617 if option >= AccountDetailsTxidHistory && filter.Vout != AddressFilterVoutQueryNotNecessary { 1618 txc, err := w.getAddressTxids(addrDesc, false, filter, (page+1)*txsOnPage) 1619 if err != nil { 1620 return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc) 1621 } 1622 bestheight, _, err := w.db.GetBestBlock() 1623 if err != nil { 1624 return nil, errors.Annotatef(err, "GetBestBlock") 1625 } 1626 var from, to int 1627 pg, from, to, page = computePaging(len(txc), page, txsOnPage) 1628 if len(txc) >= txsOnPage { 1629 if totalResults < 0 { 1630 pg.TotalPages = -1 1631 } else { 1632 pg, _, _, _ = computePaging(totalResults, page, txsOnPage) 1633 } 1634 } 1635 for i := from; i < to; i++ { 1636 txid := txc[i] 1637 if option == AccountDetailsTxidHistory { 1638 txids = append(txids, txid) 1639 } else { 1640 tx, err := w.txFromTxid(txid, bestheight, option, nil, addresses) 1641 if err != nil { 1642 return nil, err 1643 } 1644 setIsOwnAddress(tx, address) 1645 txs = append(txs, tx) 1646 } 1647 } 1648 } 1649 if w.chainType == bchain.ChainBitcoinType { 1650 totalReceived = ba.ReceivedSat() 1651 totalSent = &ba.SentSat 1652 } 1653 var secondaryRate, totalSecondaryValue, totalBaseValue, secondaryValue float64 1654 if secondaryCoin != "" { 1655 ticker := w.fiatRates.GetCurrentTicker("", "") 1656 balance, err := strconv.ParseFloat((*Amount)(&ba.BalanceSat).DecimalString(w.chainParser.AmountDecimals()), 64) 1657 if ticker != nil && err == nil { 1658 r, found := ticker.Rates[secondaryCoin] 1659 if found { 1660 secondaryRate = float64(r) 1661 } 1662 } 1663 secondaryValue = secondaryRate * balance 1664 if w.chainType == bchain.ChainEthereumType { 1665 totalBaseValue += balance + ed.tokensBaseValue 1666 totalSecondaryValue = secondaryRate * totalBaseValue 1667 } 1668 } 1669 r := &Address{ 1670 Paging: pg, 1671 AddrStr: address, 1672 BalanceSat: (*Amount)(&ba.BalanceSat), 1673 TotalReceivedSat: (*Amount)(totalReceived), 1674 TotalSentSat: (*Amount)(totalSent), 1675 Txs: int(ba.Txs), 1676 NonTokenTxs: ed.nonContractTxs, 1677 InternalTxs: ed.internalTxs, 1678 UnconfirmedBalanceSat: (*Amount)(&uBalSat), 1679 UnconfirmedTxs: unconfirmedTxs, 1680 Transactions: txs, 1681 Txids: txids, 1682 Tokens: ed.tokens, 1683 SecondaryValue: secondaryValue, 1684 TokensBaseValue: ed.tokensBaseValue, 1685 TokensSecondaryValue: ed.tokensSecondaryValue, 1686 TotalBaseValue: totalBaseValue, 1687 TotalSecondaryValue: totalSecondaryValue, 1688 ContractInfo: ed.contractInfo, 1689 Nonce: ed.nonce, 1690 AddressAliases: w.getAddressAliases(addresses), 1691 } 1692 // keep address backward compatible, set deprecated Erc20Contract value if ERC20 token 1693 if ed.contractInfo != nil && (ed.contractInfo.Type == bchain.ERC20TokenType || ed.contractInfo.Type == xcb.CRC20TokenType) { 1694 r.Erc20Contract = ed.contractInfo 1695 } 1696 glog.Info("GetAddress ", address, ", ", time.Since(start)) 1697 return r, nil 1698 } 1699 1700 func (w *Worker) balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp int64) (uint32, uint32, uint32, uint32) { 1701 fromUnix := uint32(0) 1702 toUnix := maxUint32 1703 fromHeight := uint32(0) 1704 toHeight := maxUint32 1705 if fromTimestamp != 0 { 1706 fromUnix = uint32(fromTimestamp) 1707 fromHeight = w.is.GetBlockHeightOfTime(fromUnix) 1708 } 1709 if toTimestamp != 0 { 1710 toUnix = uint32(toTimestamp) 1711 toHeight = w.is.GetBlockHeightOfTime(toUnix) 1712 } 1713 return fromUnix, fromHeight, toUnix, toHeight 1714 } 1715 1716 func (w *Worker) balanceHistoryForTxid(addrDesc bchain.AddressDescriptor, txid string, fromUnix, toUnix uint32, selfAddrDesc map[string]struct{}) (*BalanceHistory, error) { 1717 var time uint32 1718 var err error 1719 var ta *db.TxAddresses 1720 var bchainTx *bchain.Tx 1721 var height uint32 1722 if w.chainType == bchain.ChainBitcoinType { 1723 ta, err = w.db.GetTxAddresses(txid) 1724 if err != nil { 1725 return nil, err 1726 } 1727 if ta == nil { 1728 glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") 1729 return nil, nil 1730 } 1731 height = ta.Height 1732 } else if w.chainType == bchain.ChainEthereumType || w.chainType == bchain.ChainCoreCoinType { 1733 var h int 1734 bchainTx, h, err = w.txCache.GetTransaction(txid) 1735 if err != nil { 1736 return nil, err 1737 } 1738 if bchainTx == nil { 1739 glog.Warning("Inconsistency: tx ", txid, ": not found in the blockchain") 1740 return nil, nil 1741 } 1742 height = uint32(h) 1743 } 1744 time = w.is.GetBlockTime(height) 1745 if time < fromUnix || time >= toUnix { 1746 return nil, nil 1747 } 1748 bh := BalanceHistory{ 1749 Time: time, 1750 Txs: 1, 1751 ReceivedSat: &Amount{}, 1752 SentSat: &Amount{}, 1753 SentToSelfSat: &Amount{}, 1754 Txid: txid, 1755 } 1756 countSentToSelf := false 1757 if w.chainType == bchain.ChainBitcoinType { 1758 // detect if this input is the first of selfAddrDesc 1759 // to not to count sentToSelf multiple times if counting multiple xpub addresses 1760 ownInputIndex := -1 1761 for i := range ta.Inputs { 1762 tai := &ta.Inputs[i] 1763 if _, found := selfAddrDesc[string(tai.AddrDesc)]; found { 1764 if ownInputIndex < 0 { 1765 ownInputIndex = i 1766 } 1767 } 1768 if bytes.Equal(addrDesc, tai.AddrDesc) { 1769 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &tai.ValueSat) 1770 if ownInputIndex == i { 1771 countSentToSelf = true 1772 } 1773 } 1774 } 1775 for i := range ta.Outputs { 1776 tao := &ta.Outputs[i] 1777 if bytes.Equal(addrDesc, tao.AddrDesc) { 1778 (*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &tao.ValueSat) 1779 } 1780 if countSentToSelf { 1781 if _, found := selfAddrDesc[string(tao.AddrDesc)]; found { 1782 (*big.Int)(bh.SentToSelfSat).Add((*big.Int)(bh.SentToSelfSat), &tao.ValueSat) 1783 } 1784 } 1785 } 1786 } else if w.chainType == bchain.ChainEthereumType { 1787 var value big.Int 1788 ethTxData := eth.GetEthereumTxData(bchainTx) 1789 // add received amount only for OK or unknown status (old) transactions 1790 if ethTxData.Status == eth.TxStatusOK || ethTxData.Status == eth.TxStatusUnknown { 1791 if len(bchainTx.Vout) > 0 { 1792 bchainVout := &bchainTx.Vout[0] 1793 value = bchainVout.ValueSat 1794 if len(bchainVout.ScriptPubKey.Addresses) > 0 { 1795 txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVout.ScriptPubKey.Addresses[0]) 1796 if err != nil { 1797 return nil, err 1798 } 1799 if bytes.Equal(addrDesc, txAddrDesc) { 1800 (*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &value) 1801 } 1802 if _, found := selfAddrDesc[string(txAddrDesc)]; found { 1803 countSentToSelf = true 1804 } 1805 } 1806 } 1807 // process internal transactions 1808 if eth.ProcessInternalTransactions { 1809 internalData, err := w.db.GetEthereumInternalData(txid) 1810 if err != nil { 1811 return nil, err 1812 } 1813 if internalData != nil { 1814 for i := range internalData.Transfers { 1815 f := &internalData.Transfers[i] 1816 txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(f.From) 1817 if err != nil { 1818 return nil, err 1819 } 1820 if bytes.Equal(addrDesc, txAddrDesc) { 1821 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &f.Value) 1822 if f.From == f.To { 1823 (*big.Int)(bh.SentToSelfSat).Add((*big.Int)(bh.SentToSelfSat), &f.Value) 1824 } 1825 } 1826 txAddrDesc, err = w.chainParser.GetAddrDescFromAddress(f.To) 1827 if err != nil { 1828 return nil, err 1829 } 1830 if bytes.Equal(addrDesc, txAddrDesc) { 1831 (*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &f.Value) 1832 } 1833 } 1834 } 1835 } 1836 } 1837 for i := range bchainTx.Vin { 1838 bchainVin := &bchainTx.Vin[i] 1839 if len(bchainVin.Addresses) > 0 { 1840 txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0]) 1841 if err != nil { 1842 return nil, err 1843 } 1844 if bytes.Equal(addrDesc, txAddrDesc) { 1845 // add received amount only for OK or unknown status (old) transactions, fees always 1846 if ethTxData.Status == eth.TxStatusOK || ethTxData.Status == eth.TxStatusUnknown { 1847 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &value) 1848 if countSentToSelf { 1849 if _, found := selfAddrDesc[string(txAddrDesc)]; found { 1850 (*big.Int)(bh.SentToSelfSat).Add((*big.Int)(bh.SentToSelfSat), &value) 1851 } 1852 } 1853 } 1854 var feesSat big.Int 1855 // mempool txs do not have fees yet 1856 if ethTxData.GasUsed != nil { 1857 feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed) 1858 } 1859 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &feesSat) 1860 } 1861 } 1862 } 1863 } else if w.chainType == bchain.ChainCoreCoinType { 1864 1865 var value big.Int 1866 xcbTxData := xcb.GetCoreCoinTxData(bchainTx) 1867 // add received amount only for OK or unknown status (old) transactions 1868 if xcbTxData.Status == xcb.TxStatusOK || xcbTxData.Status == xcb.TxStatusUnknown { 1869 if len(bchainTx.Vout) > 0 { 1870 bchainVout := &bchainTx.Vout[0] 1871 value = bchainVout.ValueSat 1872 if len(bchainVout.ScriptPubKey.Addresses) > 0 { 1873 txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVout.ScriptPubKey.Addresses[0]) 1874 if err != nil { 1875 return nil, err 1876 } 1877 if bytes.Equal(addrDesc, txAddrDesc) { 1878 (*big.Int)(bh.ReceivedSat).Add((*big.Int)(bh.ReceivedSat), &value) 1879 } 1880 if _, found := selfAddrDesc[string(txAddrDesc)]; found { 1881 countSentToSelf = true 1882 } 1883 } 1884 } 1885 } 1886 for i := range bchainTx.Vin { 1887 bchainVin := &bchainTx.Vin[i] 1888 if len(bchainVin.Addresses) > 0 { 1889 txAddrDesc, err := w.chainParser.GetAddrDescFromAddress(bchainVin.Addresses[0]) 1890 if err != nil { 1891 return nil, err 1892 } 1893 if bytes.Equal(addrDesc, txAddrDesc) { 1894 // add received amount only for OK or unknown status (old) transactions, fees always 1895 if xcbTxData.Status == xcb.TxStatusOK || xcbTxData.Status == xcb.TxStatusUnknown { 1896 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &value) 1897 if countSentToSelf { 1898 if _, found := selfAddrDesc[string(txAddrDesc)]; found { 1899 (*big.Int)(bh.SentToSelfSat).Add((*big.Int)(bh.SentToSelfSat), &value) 1900 } 1901 } 1902 } 1903 var feesSat big.Int 1904 // mempool txs do not have fees yet 1905 if xcbTxData.EnergyUsed != nil { 1906 feesSat.Mul(xcbTxData.EnergyPrice, xcbTxData.EnergyUsed) 1907 } 1908 (*big.Int)(bh.SentSat).Add((*big.Int)(bh.SentSat), &feesSat) 1909 } 1910 } 1911 } 1912 } 1913 return &bh, nil 1914 } 1915 1916 func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, currencies []string) error { 1917 for i := range histories { 1918 bh := &histories[i] 1919 tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{int64(bh.Time)}, "", "") 1920 if err != nil || tickers == nil || len(*tickers) == 0 { 1921 glog.Errorf("Error finding ticker by date %v. Error: %v", bh.Time, err) 1922 continue 1923 } 1924 ticker := (*tickers)[0] 1925 if ticker == nil { 1926 continue 1927 } 1928 if len(currencies) == 0 { 1929 bh.FiatRates = ticker.Rates 1930 } else { 1931 rates := make(map[string]float32) 1932 for _, currency := range currencies { 1933 currency = strings.ToLower(currency) 1934 if rate, found := ticker.Rates[currency]; found { 1935 rates[currency] = rate 1936 } else { 1937 rates[currency] = -1 1938 } 1939 } 1940 bh.FiatRates = rates 1941 } 1942 } 1943 return nil 1944 } 1945 1946 // GetBalanceHistory returns history of balance for given address 1947 func (w *Worker) GetBalanceHistory(address string, fromTimestamp, toTimestamp int64, currencies []string, groupBy uint32) (BalanceHistories, error) { 1948 currencies = removeEmpty(currencies) 1949 bhs := make(BalanceHistories, 0) 1950 start := time.Now() 1951 addrDesc, _, err := w.getAddrDescAndNormalizeAddress(address) 1952 if err != nil { 1953 return nil, err 1954 } 1955 fromUnix, fromHeight, toUnix, toHeight := w.balanceHistoryHeightsFromTo(fromTimestamp, toTimestamp) 1956 if fromHeight >= toHeight { 1957 return bhs, nil 1958 } 1959 txs, err := w.getAddressTxids(addrDesc, false, &AddressFilter{Vout: AddressFilterVoutOff, FromHeight: fromHeight, ToHeight: toHeight}, maxInt) 1960 if err != nil { 1961 return nil, err 1962 } 1963 selfAddrDesc := map[string]struct{}{string(addrDesc): {}} 1964 for txi := len(txs) - 1; txi >= 0; txi-- { 1965 bh, err := w.balanceHistoryForTxid(addrDesc, txs[txi], fromUnix, toUnix, selfAddrDesc) 1966 if err != nil { 1967 return nil, err 1968 } 1969 if bh != nil { 1970 bhs = append(bhs, *bh) 1971 } 1972 } 1973 bha := bhs.SortAndAggregate(groupBy) 1974 err = w.setFiatRateToBalanceHistories(bha, currencies) 1975 if err != nil { 1976 return nil, err 1977 } 1978 glog.Info("GetBalanceHistory ", address, ", blocks ", fromHeight, "-", toHeight, ", count ", len(bha), ", ", time.Since(start)) 1979 return bha, nil 1980 } 1981 1982 func (w *Worker) waitForBackendSync() { 1983 // wait a short time if blockbook is synchronizing with backend 1984 inSync, _, _, _ := w.is.GetSyncState() 1985 count := 30 1986 for !inSync && count > 0 { 1987 time.Sleep(time.Millisecond * 100) 1988 count-- 1989 inSync, _, _, _ = w.is.GetSyncState() 1990 } 1991 } 1992 1993 func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrBalance, onlyConfirmed bool, onlyMempool bool) (Utxos, error) { 1994 w.waitForBackendSync() 1995 var err error 1996 utxos := make(Utxos, 0, 8) 1997 // store txids from mempool so that they are not added twice in case of import of new block while processing utxos, issue #275 1998 inMempool := make(map[string]struct{}) 1999 // outputs could be spent in mempool, record and check mempool spends 2000 spentInMempool := make(map[string]struct{}) 2001 if !onlyConfirmed { 2002 // get utxo from mempool 2003 txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt) 2004 if err != nil { 2005 return nil, err 2006 } 2007 if len(txm) > 0 { 2008 mc := make([]*bchain.Tx, len(txm)) 2009 for i, txid := range txm { 2010 // get mempool txs and process their inputs to detect spends between mempool txs 2011 bchainTx, _, err := w.txCache.GetTransaction(txid) 2012 // mempool transaction may fail 2013 if err != nil { 2014 glog.Error("GetTransaction in mempool ", txid, ": ", err) 2015 } else { 2016 mc[i] = bchainTx 2017 // get outputs spent by the mempool tx 2018 for i := range bchainTx.Vin { 2019 vin := &bchainTx.Vin[i] 2020 spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{} 2021 } 2022 } 2023 } 2024 for _, bchainTx := range mc { 2025 if bchainTx != nil { 2026 for i := range bchainTx.Vout { 2027 vout := &bchainTx.Vout[i] 2028 vad, err := w.chainParser.GetAddrDescFromVout(vout) 2029 if err == nil && bytes.Equal(addrDesc, vad) { 2030 // report only outpoints that are not spent in mempool 2031 _, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)] 2032 if !e { 2033 coinbase := false 2034 if len(bchainTx.Vin) == 1 && len(bchainTx.Vin[0].Coinbase) > 0 { 2035 coinbase = true 2036 } 2037 utxos = append(utxos, Utxo{ 2038 Txid: bchainTx.Txid, 2039 Vout: int32(i), 2040 AmountSat: (*Amount)(&vout.ValueSat), 2041 Locktime: bchainTx.LockTime, 2042 Coinbase: coinbase, 2043 }) 2044 inMempool[bchainTx.Txid] = struct{}{} 2045 } 2046 } 2047 } 2048 } 2049 } 2050 } 2051 } 2052 if !onlyMempool { 2053 // get utxo from index 2054 if ba == nil { 2055 ba, err = w.db.GetAddrDescBalance(addrDesc, db.AddressBalanceDetailUTXO) 2056 if err != nil { 2057 return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 2058 } 2059 } 2060 // ba can be nil if the address is only in mempool! 2061 if ba != nil && len(ba.Utxos) > 0 { 2062 b, _, err := w.db.GetBestBlock() 2063 if err != nil { 2064 return nil, err 2065 } 2066 bestheight := int(b) 2067 var checksum big.Int 2068 checksum.Set(&ba.BalanceSat) 2069 // go backwards to get the newest first 2070 for i := len(ba.Utxos) - 1; i >= 0; i-- { 2071 utxo := &ba.Utxos[i] 2072 txid, err := w.chainParser.UnpackTxid(utxo.BtxID) 2073 if err != nil { 2074 return nil, err 2075 } 2076 _, e := spentInMempool[txid+strconv.Itoa(int(utxo.Vout))] 2077 if !e { 2078 confirmations := bestheight - int(utxo.Height) + 1 2079 coinbase := false 2080 // for performance reasons, check coinbase transactions only in minimum confirmantion range 2081 if confirmations < w.chainParser.MinimumCoinbaseConfirmations() { 2082 ta, err := w.db.GetTxAddresses(txid) 2083 if err != nil { 2084 return nil, err 2085 } 2086 if len(ta.Inputs) == 1 && len(ta.Inputs[0].AddrDesc) == 0 && IsZeroBigInt(&ta.Inputs[0].ValueSat) { 2087 coinbase = true 2088 } 2089 } 2090 _, e = inMempool[txid] 2091 if !e { 2092 utxos = append(utxos, Utxo{ 2093 Txid: txid, 2094 Vout: utxo.Vout, 2095 AmountSat: (*Amount)(&utxo.ValueSat), 2096 Height: int(utxo.Height), 2097 Confirmations: confirmations, 2098 Coinbase: coinbase, 2099 }) 2100 } 2101 } 2102 checksum.Sub(&checksum, &utxo.ValueSat) 2103 } 2104 if checksum.Uint64() != 0 { 2105 glog.Warning("DB inconsistency: ", addrDesc, ": checksum is not zero, checksum=", checksum.Int64()) 2106 } 2107 } 2108 } 2109 return utxos, nil 2110 } 2111 2112 // GetAddressUtxo returns unspent outputs for given address 2113 func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) (Utxos, error) { 2114 if w.chainType != bchain.ChainBitcoinType { 2115 return nil, NewAPIError("Not supported", true) 2116 } 2117 start := time.Now() 2118 addrDesc, err := w.chainParser.GetAddrDescFromAddress(address) 2119 if err != nil { 2120 return nil, NewAPIError(fmt.Sprintf("Invalid address '%v', %v", address, err), true) 2121 } 2122 r, err := w.getAddrDescUtxo(addrDesc, nil, onlyConfirmed, false) 2123 if err != nil { 2124 return nil, err 2125 } 2126 glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, ", time.Since(start)) 2127 return r, nil 2128 } 2129 2130 // GetBlocks returns BlockInfo for blocks on given page 2131 func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) { 2132 start := time.Now() 2133 page-- 2134 if page < 0 { 2135 page = 0 2136 } 2137 b, _, err := w.db.GetBestBlock() 2138 bestheight := int(b) 2139 if err != nil { 2140 return nil, errors.Annotatef(err, "GetBestBlock") 2141 } 2142 pg, from, to, page := computePaging(bestheight+1, page, blocksOnPage) 2143 r := &Blocks{Paging: pg} 2144 r.Blocks = make([]db.BlockInfo, to-from) 2145 for i := from; i < to; i++ { 2146 bi, err := w.db.GetBlockInfo(uint32(bestheight - i)) 2147 if err != nil { 2148 return nil, err 2149 } 2150 if bi == nil { 2151 r.Blocks = r.Blocks[:i] 2152 break 2153 } 2154 r.Blocks[i-from] = *bi 2155 } 2156 glog.Info("GetBlocks page ", page, ", ", time.Since(start)) 2157 return r, nil 2158 } 2159 2160 // removeEmpty removes empty strings from a slice 2161 func removeEmpty(stringSlice []string) []string { 2162 var ret []string 2163 for _, str := range stringSlice { 2164 if str != "" { 2165 ret = append(ret, str) 2166 } 2167 } 2168 return ret 2169 } 2170 2171 // getFiatRatesResult checks if CurrencyRatesTicker contains all necessary data and returns formatted result 2172 func (w *Worker) getFiatRatesResult(currencies []string, ticker *common.CurrencyRatesTicker, token string) (*FiatTicker, error) { 2173 if token != "" { 2174 rates := make(map[string]float32) 2175 if len(currencies) == 0 { 2176 for currency := range ticker.Rates { 2177 currency = strings.ToLower(currency) 2178 rate := ticker.TokenRateInCurrency(token, currency) 2179 if rate <= 0 { 2180 rate = -1 2181 } 2182 rates[currency] = rate 2183 } 2184 } else { 2185 for _, currency := range currencies { 2186 currency = strings.ToLower(currency) 2187 rate := ticker.TokenRateInCurrency(token, currency) 2188 if rate <= 0 { 2189 rate = -1 2190 } 2191 rates[currency] = rate 2192 } 2193 } 2194 return &FiatTicker{ 2195 Timestamp: ticker.Timestamp.UTC().Unix(), 2196 Rates: rates, 2197 }, nil 2198 } 2199 if len(currencies) == 0 { 2200 // Return all available ticker rates 2201 return &FiatTicker{ 2202 Timestamp: ticker.Timestamp.UTC().Unix(), 2203 Rates: ticker.Rates, 2204 }, nil 2205 } 2206 // Check if currencies from the list are available in the ticker rates 2207 rates := make(map[string]float32) 2208 for _, currency := range currencies { 2209 currency = strings.ToLower(currency) 2210 if rate, found := ticker.Rates[currency]; found { 2211 rates[currency] = rate 2212 } else { 2213 rates[currency] = -1 2214 } 2215 } 2216 return &FiatTicker{ 2217 Timestamp: ticker.Timestamp.UTC().Unix(), 2218 Rates: rates, 2219 }, nil 2220 } 2221 2222 // GetCurrentFiatRates returns last available fiat rates 2223 func (w *Worker) GetCurrentFiatRates(currencies []string, token string) (*FiatTicker, error) { 2224 vsCurrency := "" 2225 currencies = removeEmpty(currencies) 2226 if len(currencies) == 1 { 2227 vsCurrency = currencies[0] 2228 } 2229 ticker := w.fiatRates.GetCurrentTicker(vsCurrency, token) 2230 var err error 2231 if ticker == nil { 2232 ticker, err = w.db.FiatRatesFindLastTicker(vsCurrency, token) 2233 if err != nil { 2234 return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false) 2235 } else if ticker == nil { 2236 return nil, NewAPIError("No tickers found!", true) 2237 } 2238 } 2239 result, err := w.getFiatRatesResult(currencies, ticker, token) 2240 if err != nil { 2241 return nil, err 2242 } 2243 return result, nil 2244 } 2245 2246 // makeErrorRates returns a map of currencies, with each value equal to -1 2247 // used when there was an error finding ticker 2248 func makeErrorRates(currencies []string) map[string]float32 { 2249 rates := make(map[string]float32) 2250 for _, currency := range currencies { 2251 rates[strings.ToLower(currency)] = -1 2252 } 2253 return rates 2254 } 2255 2256 // GetFiatRatesForTimestamps returns fiat rates for each of the provided dates 2257 func (w *Worker) GetFiatRatesForTimestamps(timestamps []int64, currencies []string, token string) (*FiatTickers, error) { 2258 if len(timestamps) == 0 { 2259 return nil, NewAPIError("No timestamps provided", true) 2260 } 2261 vsCurrency := "" 2262 currencies = removeEmpty(currencies) 2263 if len(currencies) == 1 { 2264 vsCurrency = currencies[0] 2265 } 2266 tickers, err := w.fiatRates.GetTickersForTimestamps(timestamps, vsCurrency, token) 2267 if err != nil { 2268 return nil, err 2269 } 2270 if tickers == nil { 2271 return nil, NewAPIError("No tickers found", true) 2272 } 2273 if len(*tickers) != len(timestamps) { 2274 glog.Error("GetFiatRatesForTimestamps: number of tickers does not match timestamps ", len(*tickers), ", ", len(timestamps)) 2275 return nil, NewAPIError("No tickers found", false) 2276 } 2277 fiatTickers := make([]FiatTicker, len(*tickers)) 2278 for i, t := range *tickers { 2279 if t == nil { 2280 fiatTickers[i] = FiatTicker{Timestamp: timestamps[i], Rates: makeErrorRates(currencies)} 2281 continue 2282 } 2283 result, err := w.getFiatRatesResult(currencies, t, token) 2284 if err != nil { 2285 if apiErr, ok := err.(*APIError); ok { 2286 if apiErr.Public { 2287 return nil, err 2288 } 2289 } 2290 fiatTickers[i] = FiatTicker{Timestamp: timestamps[i], Rates: makeErrorRates(currencies)} 2291 continue 2292 } 2293 fiatTickers[i] = *result 2294 } 2295 return &FiatTickers{Tickers: fiatTickers}, nil 2296 } 2297 2298 // GetFiatRatesForBlockID returns fiat rates for block height or block hash 2299 func (w *Worker) GetFiatRatesForBlockID(blockID string, currencies []string, token string) (*FiatTicker, error) { 2300 bi, err := w.getBlockInfoFromBlockID(blockID) 2301 if err != nil { 2302 if err == bchain.ErrBlockNotFound { 2303 return nil, NewAPIError(fmt.Sprintf("Block %v not found", blockID), true) 2304 } 2305 return nil, NewAPIError(fmt.Sprintf("Block %v not found, error: %v", blockID, err), false) 2306 } 2307 tickers, err := w.GetFiatRatesForTimestamps([]int64{bi.Time}, currencies, token) 2308 if err != nil || tickers == nil || len(tickers.Tickers) == 0 { 2309 return nil, err 2310 } 2311 return &tickers.Tickers[0], nil 2312 } 2313 2314 // GetAvailableVsCurrencies returns the list of available versus currencies for exchange rates 2315 func (w *Worker) GetAvailableVsCurrencies(timestamp int64, token string) (*AvailableVsCurrencies, error) { 2316 tickers, err := w.fiatRates.GetTickersForTimestamps([]int64{timestamp}, "", token) 2317 if err != nil { 2318 return nil, NewAPIError(fmt.Sprintf("Error finding ticker: %v", err), false) 2319 } 2320 if tickers == nil || len(*tickers) == 0 { 2321 return nil, NewAPIError("No tickers found", true) 2322 } 2323 ticker := (*tickers)[0] 2324 keys := make([]string, 0, len(ticker.Rates)) 2325 for k := range ticker.Rates { 2326 keys = append(keys, k) 2327 } 2328 sort.Strings(keys) // sort to get deterministic results 2329 2330 return &AvailableVsCurrencies{ 2331 Timestamp: ticker.Timestamp.Unix(), 2332 Tickers: keys, 2333 }, nil 2334 } 2335 2336 // getBlockHashBlockID returns block hash from block height or block hash 2337 func (w *Worker) getBlockHashBlockID(bid string) string { 2338 // try to decide if passed string (bid) is block height or block hash 2339 // if it's a number, must be less than int32 2340 var hash string 2341 height, err := strconv.Atoi(bid) 2342 if err == nil && height < int(maxUint32) { 2343 hash, err = w.db.GetBlockHash(uint32(height)) 2344 if err != nil { 2345 hash = bid 2346 } 2347 } else { 2348 hash = bid 2349 } 2350 return hash 2351 } 2352 2353 // getBlockInfoFromBlockID returns block info from block height or block hash 2354 func (w *Worker) getBlockInfoFromBlockID(bid string) (*bchain.BlockInfo, error) { 2355 hash := w.getBlockHashBlockID(bid) 2356 if hash == "" { 2357 return nil, NewAPIError("Block not found", true) 2358 } 2359 bi, err := w.chain.GetBlockInfo(hash) 2360 return bi, err 2361 } 2362 2363 // GetFeeStats returns statistics about block fees 2364 func (w *Worker) GetFeeStats(bid string) (*FeeStats, error) { 2365 // txSpecific extends Tx with an additional Size and Vsize info 2366 type txSpecific struct { 2367 *bchain.Tx 2368 Vsize int `json:"vsize,omitempty"` 2369 Size int `json:"size,omitempty"` 2370 } 2371 2372 start := time.Now() 2373 bi, err := w.getBlockInfoFromBlockID(bid) 2374 if err != nil { 2375 if err == bchain.ErrBlockNotFound { 2376 return nil, NewAPIError("Block not found", true) 2377 } 2378 return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true) 2379 } 2380 2381 feesPerKb := make([]int64, 0, len(bi.Txids)) 2382 totalFeesSat := big.NewInt(0) 2383 averageFeePerKb := int64(0) 2384 2385 for _, txid := range bi.Txids { 2386 // Get a raw JSON with transaction details, including size, vsize, hex 2387 txSpecificJSON, err := w.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid}) 2388 if err != nil { 2389 return nil, errors.Annotatef(err, "GetTransactionSpecific") 2390 } 2391 2392 // Serialize the raw JSON into TxSpecific struct 2393 var txSpec txSpecific 2394 err = json.Unmarshal(txSpecificJSON, &txSpec) 2395 if err != nil { 2396 return nil, errors.Annotatef(err, "Unmarshal") 2397 } 2398 2399 // Calculate the TX size in bytes 2400 txSize := 0 2401 if txSpec.Vsize > 0 { 2402 txSize = txSpec.Vsize 2403 } else if txSpec.Size > 0 { 2404 txSize = txSpec.Size 2405 } else if txSpec.Hex != "" { 2406 txSize = len(txSpec.Hex) / 2 2407 } else { 2408 errMsg := "Cannot determine the transaction size from neither Vsize, Size nor Hex! Txid: " + txid 2409 return nil, NewAPIError(errMsg, true) 2410 } 2411 2412 // Get values of TX inputs and outputs 2413 txAddresses, err := w.db.GetTxAddresses(txid) 2414 if err != nil { 2415 return nil, errors.Annotatef(err, "GetTxAddresses") 2416 } 2417 2418 // Calculate total fees in Satoshis 2419 feeSat := big.NewInt(0) 2420 for _, input := range txAddresses.Inputs { 2421 feeSat = feeSat.Add(&input.ValueSat, feeSat) 2422 } 2423 2424 // Zero inputs means it's a Coinbase TX - skip it 2425 if feeSat.Cmp(big.NewInt(0)) == 0 { 2426 continue 2427 } 2428 2429 for _, output := range txAddresses.Outputs { 2430 feeSat = feeSat.Sub(feeSat, &output.ValueSat) 2431 } 2432 totalFeesSat.Add(totalFeesSat, feeSat) 2433 2434 // Convert feeSat to fee per kilobyte and add to an array for decile calculation 2435 feePerKb := int64(float64(feeSat.Int64()) / float64(txSize) * 1000) 2436 averageFeePerKb += feePerKb 2437 feesPerKb = append(feesPerKb, feePerKb) 2438 } 2439 2440 var deciles [11]int64 2441 n := len(feesPerKb) 2442 2443 if n > 0 { 2444 averageFeePerKb /= int64(n) 2445 2446 // Sort fees and calculate the deciles 2447 sort.Slice(feesPerKb, func(i, j int) bool { return feesPerKb[i] < feesPerKb[j] }) 2448 for k := 0; k <= 10; k++ { 2449 index := int(math.Floor(0.5+float64(k)*float64(n+1)/10)) - 1 2450 if index < 0 { 2451 index = 0 2452 } else if index >= n { 2453 index = n - 1 2454 } 2455 deciles[k] = feesPerKb[index] 2456 } 2457 } 2458 2459 glog.Info("GetFeeStats ", bid, " (", len(feesPerKb), " txs), ", time.Since(start)) 2460 2461 return &FeeStats{ 2462 TxCount: len(feesPerKb), 2463 AverageFeePerKb: averageFeePerKb, 2464 TotalFeesSat: (*Amount)(totalFeesSat), 2465 DecilesFeePerKb: deciles, 2466 }, nil 2467 } 2468 2469 // GetBlock returns paged data about block 2470 func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) { 2471 start := time.Now() 2472 page-- 2473 if page < 0 { 2474 page = 0 2475 } 2476 bi, err := w.getBlockInfoFromBlockID(bid) 2477 if err != nil { 2478 if err == bchain.ErrBlockNotFound { 2479 return nil, NewAPIError("Block not found", true) 2480 } 2481 return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true) 2482 } 2483 dbi := &db.BlockInfo{ 2484 Hash: bi.Hash, 2485 Height: bi.Height, 2486 Time: bi.Time, 2487 } 2488 txCount := len(bi.Txids) 2489 bestheight, _, err := w.db.GetBestBlock() 2490 if err != nil { 2491 return nil, errors.Annotatef(err, "GetBestBlock") 2492 } 2493 pg, from, to, page := computePaging(txCount, page, txsOnPage) 2494 txs := make([]*Tx, to-from) 2495 txi := 0 2496 addresses := w.newAddressesMapForAliases() 2497 for i := from; i < to; i++ { 2498 txs[txi], err = w.txFromTxid(bi.Txids[i], bestheight, AccountDetailsTxHistoryLight, dbi, addresses) 2499 if err != nil { 2500 return nil, err 2501 } 2502 txi++ 2503 } 2504 if bi.Prev == "" && bi.Height != 0 { 2505 bi.Prev, _ = w.db.GetBlockHash(bi.Height - 1) 2506 } 2507 if bi.Next == "" && bi.Height != bestheight { 2508 bi.Next, _ = w.db.GetBlockHash(bi.Height + 1) 2509 } 2510 txs = txs[:txi] 2511 bi.Txids = nil 2512 glog.Info("GetBlock ", bid, ", page ", page, ", ", time.Since(start)) 2513 return &Block{ 2514 Paging: pg, 2515 BlockInfo: BlockInfo{ 2516 Hash: bi.Hash, 2517 Prev: bi.Prev, 2518 Next: bi.Next, 2519 Height: bi.Height, 2520 Confirmations: bi.Confirmations, 2521 Size: bi.Size, 2522 Time: bi.Time, 2523 Bits: bi.Bits, 2524 Difficulty: string(bi.Difficulty), 2525 MerkleRoot: bi.MerkleRoot, 2526 Nonce: string(bi.Nonce), 2527 Txids: bi.Txids, 2528 Version: bi.Version, 2529 }, 2530 TxCount: txCount, 2531 Transactions: txs, 2532 AddressAliases: w.getAddressAliases(addresses), 2533 }, nil 2534 } 2535 2536 // GetBlock returns paged data about block 2537 func (w *Worker) GetBlockRaw(bid string) (*BlockRaw, error) { 2538 hash := w.getBlockHashBlockID(bid) 2539 if hash == "" { 2540 return nil, NewAPIError("Block not found", true) 2541 } 2542 hex, err := w.chain.GetBlockRaw(hash) 2543 if err != nil { 2544 if err == bchain.ErrBlockNotFound { 2545 return nil, NewAPIError("Block not found", true) 2546 } 2547 return nil, err 2548 } 2549 return &BlockRaw{Hex: hex}, err 2550 } 2551 2552 // ComputeFeeStats computes fee distribution in defined blocks and logs them to log 2553 func (w *Worker) ComputeFeeStats(blockFrom, blockTo int, stopCompute chan os.Signal) error { 2554 bestheight, _, err := w.db.GetBestBlock() 2555 if err != nil { 2556 return errors.Annotatef(err, "GetBestBlock") 2557 } 2558 for block := blockFrom; block <= blockTo; block++ { 2559 hash, err := w.db.GetBlockHash(uint32(block)) 2560 if err != nil { 2561 return err 2562 } 2563 bi, err := w.chain.GetBlockInfo(hash) 2564 if err != nil { 2565 return err 2566 } 2567 // process only blocks with enough transactions 2568 if len(bi.Txids) > 20 { 2569 dbi := &db.BlockInfo{ 2570 Hash: bi.Hash, 2571 Height: bi.Height, 2572 Time: bi.Time, 2573 } 2574 txids := bi.Txids 2575 if w.chainType == bchain.ChainBitcoinType { 2576 // skip the coinbase transaction 2577 txids = txids[1:] 2578 } 2579 fees := make([]int64, len(txids)) 2580 sum := int64(0) 2581 for i, txid := range txids { 2582 select { 2583 case <-stopCompute: 2584 glog.Info("ComputeFeeStats interrupted at height ", block) 2585 return db.ErrOperationInterrupted 2586 default: 2587 tx, err := w.txFromTxid(txid, bestheight, AccountDetailsTxHistoryLight, dbi, nil) 2588 if err != nil { 2589 return err 2590 } 2591 fee := tx.FeesSat.AsInt64() 2592 fees[i] = fee 2593 sum += fee 2594 } 2595 } 2596 sort.Slice(fees, func(i, j int) bool { return fees[i] < fees[j] }) 2597 step := float64(len(fees)) / 10 2598 percentils := "" 2599 for i := float64(0); i < float64(len(fees)+1); i += step { 2600 ii := int(math.Round(i)) 2601 if ii >= len(fees) { 2602 ii = len(fees) - 1 2603 } 2604 percentils += "," + strconv.FormatInt(fees[ii], 10) 2605 } 2606 glog.Info(block, ",", time.Unix(bi.Time, 0).Format(time.RFC3339), ",", len(bi.Txids), ",", sum, ",", float64(sum)/float64(len(bi.Txids)), percentils) 2607 } 2608 } 2609 return nil 2610 } 2611 2612 func nonZeroTime(t time.Time) *time.Time { 2613 if t.IsZero() { 2614 return nil 2615 } 2616 return &t 2617 } 2618 2619 // GetSystemInfo returns information about system 2620 func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) { 2621 start := time.Now().UTC() 2622 vi := common.GetVersionInfo() 2623 inSync, bestHeight, lastBlockTime, startSync := w.is.GetSyncState() 2624 if !inSync && !w.is.InitialSync { 2625 // if less than 5 seconds into syncing, return inSync=true to avoid short time not in sync reports that confuse monitoring 2626 if startSync.Add(5 * time.Second).After(start) { 2627 inSync = true 2628 } 2629 } 2630 inSyncMempool, lastMempoolTime, mempoolSize := w.is.GetMempoolSyncState() 2631 ci, err := w.chain.GetChainInfo() 2632 var backendError string 2633 if err != nil { 2634 glog.Error("GetChainInfo error ", err) 2635 backendError = errors.Annotatef(err, "GetChainInfo").Error() 2636 ci = &bchain.ChainInfo{} 2637 // set not in sync in case of backend error 2638 inSync = false 2639 inSyncMempool = false 2640 } 2641 var columnStats []common.InternalStateColumn 2642 var internalDBSize int64 2643 if internal { 2644 columnStats = w.is.GetAllDBColumnStats() 2645 internalDBSize = w.is.DBSizeTotal() 2646 } 2647 var currentFiatRatesTime time.Time 2648 ct := w.fiatRates.GetCurrentTicker("", "") 2649 if ct != nil { 2650 currentFiatRatesTime = ct.Timestamp 2651 } 2652 blockbookInfo := &BlockbookInfo{ 2653 Coin: w.is.Coin, 2654 Host: w.is.Host, 2655 Version: vi.Version, 2656 GitCommit: vi.GitCommit, 2657 BuildTime: vi.BuildTime, 2658 SyncMode: w.is.SyncMode, 2659 InitialSync: w.is.InitialSync, 2660 InSync: inSync, 2661 BestHeight: bestHeight, 2662 LastBlockTime: lastBlockTime, 2663 InSyncMempool: inSyncMempool, 2664 LastMempoolTime: lastMempoolTime, 2665 MempoolSize: mempoolSize, 2666 Decimals: w.chainParser.AmountDecimals(), 2667 HasFiatRates: w.is.HasFiatRates, 2668 HasTokenFiatRates: w.is.HasTokenFiatRates, 2669 CurrentFiatRatesTime: nonZeroTime(currentFiatRatesTime), 2670 HistoricalFiatRatesTime: nonZeroTime(w.is.HistoricalFiatRatesTime), 2671 HistoricalTokenFiatRatesTime: nonZeroTime(w.is.HistoricalTokenFiatRatesTime), 2672 DbSize: w.db.DatabaseSizeOnDisk(), 2673 DbSizeFromColumns: internalDBSize, 2674 DbColumns: columnStats, 2675 About: Text.BlockbookAbout, 2676 } 2677 backendInfo := &common.BackendInfo{ 2678 BackendError: backendError, 2679 BestBlockHash: ci.Bestblockhash, 2680 Blocks: ci.Blocks, 2681 Chain: ci.Chain, 2682 Difficulty: ci.Difficulty, 2683 Headers: ci.Headers, 2684 ProtocolVersion: ci.ProtocolVersion, 2685 SizeOnDisk: ci.SizeOnDisk, 2686 Subversion: ci.Subversion, 2687 Timeoffset: ci.Timeoffset, 2688 Version: ci.Version, 2689 Warnings: ci.Warnings, 2690 ConsensusVersion: ci.ConsensusVersion, 2691 Consensus: ci.Consensus, 2692 } 2693 w.is.SetBackendInfo(backendInfo) 2694 glog.Info("GetSystemInfo, ", time.Since(start)) 2695 return &SystemInfo{blockbookInfo, backendInfo}, nil 2696 } 2697 2698 // GetMempool returns a page of mempool txids 2699 func (w *Worker) GetMempool(page int, itemsOnPage int) (*MempoolTxids, error) { 2700 page-- 2701 if page < 0 { 2702 page = 0 2703 } 2704 entries := w.mempool.GetAllEntries() 2705 pg, from, to, _ := computePaging(len(entries), page, itemsOnPage) 2706 r := &MempoolTxids{ 2707 Paging: pg, 2708 MempoolSize: len(entries), 2709 } 2710 r.Mempool = make([]MempoolTxid, to-from) 2711 for i := from; i < to; i++ { 2712 entry := &entries[i] 2713 r.Mempool[i-from] = MempoolTxid{ 2714 Txid: entry.Txid, 2715 Time: int64(entry.Time), 2716 } 2717 } 2718 return r, nil 2719 } 2720 2721 type bitcoinTypeEstimatedFee struct { 2722 timestamp int64 2723 fee big.Int 2724 lock sync.Mutex 2725 } 2726 2727 const estimatedFeeCacheSize = 300 2728 2729 var estimatedFeeCache [estimatedFeeCacheSize]bitcoinTypeEstimatedFee 2730 var estimatedFeeConservativeCache [estimatedFeeCacheSize]bitcoinTypeEstimatedFee 2731 2732 func (w *Worker) cachedEstimateFee(blocks int, conservative bool) (big.Int, error) { 2733 var s *bitcoinTypeEstimatedFee 2734 if conservative { 2735 s = &estimatedFeeConservativeCache[blocks] 2736 } else { 2737 s = &estimatedFeeCache[blocks] 2738 } 2739 s.lock.Lock() 2740 defer s.lock.Unlock() 2741 // 10 seconds cache 2742 threshold := time.Now().Unix() - 10 2743 if s.timestamp >= threshold { 2744 return s.fee, nil 2745 } 2746 fee, err := w.chain.EstimateSmartFee(blocks, conservative) 2747 if err == nil { 2748 s.timestamp = time.Now().Unix() 2749 s.fee = fee 2750 // store metrics for the first 32 block estimates 2751 if blocks < 33 { 2752 w.metrics.EstimatedFee.With(common.Labels{ 2753 "blocks": strconv.Itoa(blocks), 2754 "conservative": strconv.FormatBool(conservative), 2755 }).Set(float64(fee.Int64())) 2756 } 2757 } 2758 return fee, err 2759 } 2760 2761 // EstimateFee returns a fee estimation for given number of blocks 2762 // it uses 10 second cache to reduce calls to the backend 2763 func (w *Worker) EstimateFee(blocks int, conservative bool) (big.Int, error) { 2764 if blocks >= estimatedFeeCacheSize { 2765 return w.chain.EstimateSmartFee(blocks, conservative) 2766 } 2767 return w.cachedEstimateFee(blocks, conservative) 2768 }