github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/api/worker.go (about) 1 package api 2 3 import ( 4 "blockbook/bchain" 5 "blockbook/common" 6 "blockbook/db" 7 "bytes" 8 "fmt" 9 "math/big" 10 "strconv" 11 "time" 12 13 "github.com/golang/glog" 14 "github.com/juju/errors" 15 ) 16 17 // Worker is handle to api worker 18 type Worker struct { 19 db *db.RocksDB 20 txCache *db.TxCache 21 chain bchain.BlockChain 22 chainParser bchain.BlockChainParser 23 is *common.InternalState 24 } 25 26 // NewWorker creates new api worker 27 func NewWorker(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*Worker, error) { 28 w := &Worker{ 29 db: db, 30 txCache: txCache, 31 chain: chain, 32 chainParser: chain.GetChainParser(), 33 is: is, 34 } 35 return w, nil 36 } 37 38 func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescriptor, []string, bool, error) { 39 addrDesc, err := w.chainParser.GetAddrDescFromVout(vout) 40 if err != nil { 41 return nil, nil, false, err 42 } 43 a, s, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc) 44 return addrDesc, a, s, err 45 } 46 47 // setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output 48 // there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx 49 func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error { 50 err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error { 51 if isOutput == false { 52 tsp, err := w.db.GetTxAddresses(t) 53 if err != nil { 54 return err 55 } else if tsp == nil { 56 glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses") 57 } else if len(tsp.Inputs) > int(index) { 58 if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 { 59 spentTx, spentHeight, err := w.txCache.GetTransaction(t) 60 if err != nil { 61 glog.Warning("Tx ", t, ": not found") 62 } else { 63 if len(spentTx.Vin) > int(index) { 64 if spentTx.Vin[index].Txid == txid { 65 vout.SpentTxID = t 66 vout.SpentHeight = int(spentHeight) 67 vout.SpentIndex = int(index) 68 return &db.StopIteration{} 69 } 70 } 71 } 72 } 73 } 74 } 75 return nil 76 }) 77 return err 78 } 79 80 // GetSpendingTxid returns transaction id of transaction that spent given output 81 func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { 82 start := time.Now() 83 tx, err := w.GetTransaction(txid, false) 84 if err != nil { 85 return "", err 86 } 87 if n >= len(tx.Vout) || n < 0 { 88 return "", NewAPIError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false) 89 } 90 err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight)) 91 if err != nil { 92 return "", err 93 } 94 glog.Info("GetSpendingTxid ", txid, " ", n, " finished in ", time.Since(start)) 95 return tx.Vout[n].SpentTxID, nil 96 } 97 98 // GetTransaction reads transaction data from txid 99 func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) { 100 start := time.Now() 101 bchainTx, height, err := w.txCache.GetTransaction(txid) 102 if err != nil { 103 return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true) 104 } 105 var ta *db.TxAddresses 106 var blockhash string 107 if bchainTx.Confirmations > 0 { 108 ta, err = w.db.GetTxAddresses(txid) 109 if err != nil { 110 return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) 111 } 112 blockhash, err = w.db.GetBlockHash(height) 113 if err != nil { 114 return nil, errors.Annotatef(err, "GetBlockHash %v", height) 115 } 116 } 117 var valInSat, valOutSat, feesSat big.Int 118 vins := make([]Vin, len(bchainTx.Vin)) 119 for i := range bchainTx.Vin { 120 bchainVin := &bchainTx.Vin[i] 121 vin := &vins[i] 122 vin.Txid = bchainVin.Txid 123 vin.N = i 124 vin.Vout = bchainVin.Vout 125 vin.Sequence = int64(bchainVin.Sequence) 126 vin.ScriptSig.Hex = bchainVin.ScriptSig.Hex 127 // bchainVin.Txid=="" is coinbase transaction 128 if bchainVin.Txid != "" { 129 // load spending addresses from TxAddresses 130 tas, err := w.db.GetTxAddresses(bchainVin.Txid) 131 if err != nil { 132 return nil, errors.Annotatef(err, "GetTxAddresses %v", bchainVin.Txid) 133 } 134 if tas == nil { 135 // mempool transactions are not in TxAddresses but confirmed should be there, log a problem 136 if bchainTx.Confirmations > 0 { 137 glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses") 138 } 139 // try to load from backend 140 otx, _, err := w.txCache.GetTransaction(bchainVin.Txid) 141 if err != nil { 142 return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid) 143 } 144 if len(otx.Vout) > int(vin.Vout) { 145 vout := &otx.Vout[vin.Vout] 146 vin.ValueSat = vout.ValueSat 147 vin.AddrDesc, vin.Addresses, vin.Searchable, err = w.getAddressesFromVout(vout) 148 if err != nil { 149 glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout) 150 } 151 } 152 } else { 153 if len(tas.Outputs) > int(vin.Vout) { 154 output := &tas.Outputs[vin.Vout] 155 vin.ValueSat = output.ValueSat 156 vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) 157 vin.AddrDesc = output.AddrDesc 158 vin.Addresses, vin.Searchable, err = output.Addresses(w.chainParser) 159 if err != nil { 160 glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i) 161 } 162 } 163 } 164 vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) 165 valInSat.Add(&valInSat, &vin.ValueSat) 166 } 167 } 168 vouts := make([]Vout, len(bchainTx.Vout)) 169 for i := range bchainTx.Vout { 170 bchainVout := &bchainTx.Vout[i] 171 vout := &vouts[i] 172 vout.N = i 173 vout.ValueSat = bchainVout.ValueSat 174 vout.Value = w.chainParser.AmountToDecimalString(&bchainVout.ValueSat) 175 valOutSat.Add(&valOutSat, &bchainVout.ValueSat) 176 vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex 177 vout.ScriptPubKey.AddrDesc, vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = w.getAddressesFromVout(bchainVout) 178 if err != nil { 179 glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, bchainTx.Txid, bchainVout.N) 180 } 181 if ta != nil { 182 vout.Spent = ta.Outputs[i].Spent 183 if spendingTxs && vout.Spent { 184 err = w.setSpendingTxToVout(vout, bchainTx.Txid, height) 185 if err != nil { 186 glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.ScriptPubKey.AddrDesc, vout.N) 187 } 188 } 189 } 190 } 191 // for coinbase transactions valIn is 0 192 feesSat.Sub(&valInSat, &valOutSat) 193 if feesSat.Sign() == -1 { 194 feesSat.SetUint64(0) 195 } 196 // for now do not return size, we would have to compute vsize of segwit transactions 197 // size:=len(bchainTx.Hex) / 2 198 r := &Tx{ 199 Blockhash: blockhash, 200 Blockheight: int(height), 201 Blocktime: bchainTx.Blocktime, 202 Confirmations: bchainTx.Confirmations, 203 Fees: w.chainParser.AmountToDecimalString(&feesSat), 204 FeesSat: feesSat, 205 Locktime: bchainTx.LockTime, 206 Time: bchainTx.Time, 207 Txid: bchainTx.Txid, 208 ValueIn: w.chainParser.AmountToDecimalString(&valInSat), 209 ValueInSat: valInSat, 210 ValueOut: w.chainParser.AmountToDecimalString(&valOutSat), 211 ValueOutSat: valOutSat, 212 Version: bchainTx.Version, 213 Hex: bchainTx.Hex, 214 Vin: vins, 215 Vout: vouts, 216 } 217 if spendingTxs { 218 glog.Info("GetTransaction ", txid, " finished in ", time.Since(start)) 219 } 220 return r, nil 221 } 222 223 func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool) ([]string, error) { 224 var err error 225 txids := make([]string, 0, 4) 226 if !mempool { 227 err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error { 228 txids = append(txids, txid) 229 return nil 230 }) 231 if err != nil { 232 return nil, err 233 } 234 } else { 235 m, err := w.chain.GetMempoolTransactionsForAddrDesc(addrDesc) 236 if err != nil { 237 return nil, err 238 } 239 txids = append(txids, m...) 240 } 241 return txids, nil 242 } 243 244 func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int { 245 var val big.Int 246 for _, vout := range t.Vout { 247 if bytes.Equal(vout.ScriptPubKey.AddrDesc, addrDesc) { 248 val.Add(&val, &vout.ValueSat) 249 } 250 } 251 return &val 252 } 253 254 func (t *Tx) getAddrVinValue(addrDesc bchain.AddressDescriptor) *big.Int { 255 var val big.Int 256 for _, vin := range t.Vin { 257 if bytes.Equal(vin.AddrDesc, addrDesc) { 258 val.Add(&val, &vin.ValueSat) 259 } 260 } 261 return &val 262 } 263 264 // UniqueTxidsInReverse reverts the order of transactions (so that newest are first) and removes duplicate transactions 265 func UniqueTxidsInReverse(txids []string) []string { 266 i := len(txids) 267 ut := make([]string, i) 268 txidsMap := make(map[string]struct{}) 269 for _, txid := range txids { 270 _, e := txidsMap[txid] 271 if !e { 272 i-- 273 ut[i] = txid 274 txidsMap[txid] = struct{}{} 275 } 276 } 277 return ut[i:] 278 } 279 280 func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32) *Tx { 281 var err error 282 var valInSat, valOutSat, feesSat big.Int 283 vins := make([]Vin, len(ta.Inputs)) 284 for i := range ta.Inputs { 285 tai := &ta.Inputs[i] 286 vin := &vins[i] 287 vin.N = i 288 vin.ValueSat = tai.ValueSat 289 vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) 290 valInSat.Add(&valInSat, &vin.ValueSat) 291 vin.Addresses, vin.Searchable, err = tai.Addresses(w.chainParser) 292 if err != nil { 293 glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai) 294 } 295 } 296 vouts := make([]Vout, len(ta.Outputs)) 297 for i := range ta.Outputs { 298 tao := &ta.Outputs[i] 299 vout := &vouts[i] 300 vout.N = i 301 vout.ValueSat = tao.ValueSat 302 vout.Value = w.chainParser.AmountToDecimalString(&vout.ValueSat) 303 valOutSat.Add(&valOutSat, &vout.ValueSat) 304 vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = tao.Addresses(w.chainParser) 305 if err != nil { 306 glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao) 307 } 308 vout.Spent = tao.Spent 309 } 310 // for coinbase transactions valIn is 0 311 feesSat.Sub(&valInSat, &valOutSat) 312 if feesSat.Sign() == -1 { 313 feesSat.SetUint64(0) 314 } 315 r := &Tx{ 316 Blockhash: bi.Hash, 317 Blockheight: int(ta.Height), 318 Blocktime: bi.Time, 319 Confirmations: bestheight - ta.Height + 1, 320 Fees: w.chainParser.AmountToDecimalString(&feesSat), 321 Time: bi.Time, 322 Txid: txid, 323 ValueIn: w.chainParser.AmountToDecimalString(&valInSat), 324 ValueOut: w.chainParser.AmountToDecimalString(&valOutSat), 325 Vin: vins, 326 Vout: vouts, 327 } 328 return r 329 } 330 331 func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) { 332 from := page * itemsOnPage 333 totalPages := (count - 1) / itemsOnPage 334 if totalPages < 0 { 335 totalPages = 0 336 } 337 if from >= count { 338 page = totalPages 339 } 340 from = page * itemsOnPage 341 to := (page + 1) * itemsOnPage 342 if to > count { 343 to = count 344 } 345 return Paging{ 346 ItemsOnPage: itemsOnPage, 347 Page: page + 1, 348 TotalPages: totalPages + 1, 349 }, from, to, page 350 } 351 352 // GetAddress computes address value and gets transactions for given address 353 func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids bool) (*Address, error) { 354 start := time.Now() 355 page-- 356 if page < 0 { 357 page = 0 358 } 359 addrDesc, err := w.chainParser.GetAddrDescFromAddress(address) 360 if err != nil { 361 return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true) 362 } 363 // ba can be nil if the address is only in mempool! 364 ba, err := w.db.GetAddrDescBalance(addrDesc) 365 if err != nil { 366 return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 367 } 368 // convert the address to the format defined by the parser 369 addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc) 370 if err != nil { 371 glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc) 372 } 373 if len(addresses) == 1 { 374 address = addresses[0] 375 } 376 txc, err := w.getAddressTxids(addrDesc, false) 377 if err != nil { 378 return nil, errors.Annotatef(err, "getAddressTxids %v false", address) 379 } 380 txc = UniqueTxidsInReverse(txc) 381 var txm []string 382 // if there are only unconfirmed transactions, ba is nil 383 if ba == nil { 384 ba = &db.AddrBalance{} 385 page = 0 386 } 387 txm, err = w.getAddressTxids(addrDesc, true) 388 if err != nil { 389 return nil, errors.Annotatef(err, "getAddressTxids %v true", address) 390 } 391 txm = UniqueTxidsInReverse(txm) 392 // check if the address exist 393 if len(txc)+len(txm) == 0 { 394 return &Address{ 395 AddrStr: address, 396 }, nil 397 } 398 bestheight, _, err := w.db.GetBestBlock() 399 if err != nil { 400 return nil, errors.Annotatef(err, "GetBestBlock") 401 } 402 pg, from, to, page := computePaging(len(txc), page, txsOnPage) 403 var txs []*Tx 404 var txids []string 405 if onlyTxids { 406 txids = make([]string, len(txm)+to-from) 407 } else { 408 txs = make([]*Tx, len(txm)+to-from) 409 } 410 txi := 0 411 // load mempool transactions 412 var uBalSat big.Int 413 for _, tx := range txm { 414 tx, err := w.GetTransaction(tx, false) 415 // mempool transaction may fail 416 if err != nil { 417 glog.Error("GetTransaction in mempool ", tx, ": ", err) 418 } else { 419 uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc)) 420 uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc)) 421 if page == 0 { 422 if onlyTxids { 423 txids[txi] = tx.Txid 424 } else { 425 txs[txi] = tx 426 } 427 txi++ 428 } 429 } 430 } 431 if len(txc) != int(ba.Txs) { 432 glog.Warning("DB inconsistency for address ", address, ": number of txs from column addresses ", len(txc), ", from addressBalance ", ba.Txs) 433 } 434 for i := from; i < to; i++ { 435 txid := txc[i] 436 if onlyTxids { 437 txids[txi] = txid 438 } else { 439 ta, err := w.db.GetTxAddresses(txid) 440 if err != nil { 441 return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) 442 } 443 if ta == nil { 444 glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") 445 continue 446 } 447 bi, err := w.db.GetBlockInfo(ta.Height) 448 if err != nil { 449 return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height) 450 } 451 if bi == nil { 452 glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db") 453 continue 454 } 455 txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight) 456 } 457 txi++ 458 } 459 if onlyTxids { 460 txids = txids[:txi] 461 } else { 462 txs = txs[:txi] 463 } 464 r := &Address{ 465 Paging: pg, 466 AddrStr: address, 467 Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat), 468 TotalReceived: w.chainParser.AmountToDecimalString(ba.ReceivedSat()), 469 TotalSent: w.chainParser.AmountToDecimalString(&ba.SentSat), 470 TxApperances: len(txc), 471 UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat), 472 UnconfirmedTxApperances: len(txm), 473 Transactions: txs, 474 Txids: txids, 475 } 476 glog.Info("GetAddress ", address, " finished in ", time.Since(start)) 477 return r, nil 478 } 479 480 // GetAddressUtxo returns unspent outputs for given address 481 func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUtxo, error) { 482 start := time.Now() 483 addrDesc, err := w.chainParser.GetAddrDescFromAddress(address) 484 if err != nil { 485 return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true) 486 } 487 spentInMempool := make(map[string]struct{}) 488 r := make([]AddressUtxo, 0, 8) 489 if !onlyConfirmed { 490 // get utxo from mempool 491 txm, err := w.getAddressTxids(addrDesc, true) 492 if err != nil { 493 return nil, errors.Annotatef(err, "getAddressTxids %v true", address) 494 } 495 txm = UniqueTxidsInReverse(txm) 496 mc := make([]*bchain.Tx, len(txm)) 497 for i, txid := range txm { 498 // get mempool txs and process their inputs to detect spends between mempool txs 499 bchainTx, _, err := w.txCache.GetTransaction(txid) 500 // mempool transaction may fail 501 if err != nil { 502 glog.Error("GetTransaction in mempool ", txid, ": ", err) 503 } else { 504 mc[i] = bchainTx 505 // get outputs spent by the mempool tx 506 for i := range bchainTx.Vin { 507 vin := &bchainTx.Vin[i] 508 spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{} 509 } 510 } 511 } 512 for _, bchainTx := range mc { 513 if bchainTx != nil { 514 for i := range bchainTx.Vout { 515 vout := &bchainTx.Vout[i] 516 vad, err := w.chainParser.GetAddrDescFromVout(vout) 517 if err == nil && bytes.Equal(addrDesc, vad) { 518 // report only outpoints that are not spent in mempool 519 _, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)] 520 if !e { 521 r = append(r, AddressUtxo{ 522 Txid: bchainTx.Txid, 523 Vout: uint32(i), 524 AmountSat: vout.ValueSat, 525 Amount: w.chainParser.AmountToDecimalString(&vout.ValueSat), 526 }) 527 } 528 } 529 } 530 } 531 } 532 } 533 // get utxo from index 534 ba, err := w.db.GetAddrDescBalance(addrDesc) 535 if err != nil { 536 return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) 537 } 538 var checksum big.Int 539 // ba can be nil if the address is only in mempool! 540 if ba != nil && ba.BalanceSat.Uint64() > 0 { 541 type outpoint struct { 542 txid string 543 vout uint32 544 } 545 outpoints := make([]outpoint, 0, 8) 546 err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error { 547 if isOutput { 548 outpoints = append(outpoints, outpoint{txid, vout}) 549 } 550 return nil 551 }) 552 if err != nil { 553 return nil, err 554 } 555 var lastTxid string 556 var ta *db.TxAddresses 557 checksum = ba.BalanceSat 558 b, _, err := w.db.GetBestBlock() 559 if err != nil { 560 return nil, err 561 } 562 bestheight := int(b) 563 for i := len(outpoints) - 1; i >= 0 && checksum.Int64() > 0; i-- { 564 o := outpoints[i] 565 if lastTxid != o.txid { 566 ta, err = w.db.GetTxAddresses(o.txid) 567 if err != nil { 568 return nil, err 569 } 570 lastTxid = o.txid 571 } 572 if ta == nil { 573 glog.Warning("DB inconsistency: tx ", o.txid, ": not found in txAddresses") 574 } else { 575 if len(ta.Outputs) <= int(o.vout) { 576 glog.Warning("DB inconsistency: txAddresses ", o.txid, " does not have enough outputs") 577 } else { 578 if !ta.Outputs[o.vout].Spent { 579 v := ta.Outputs[o.vout].ValueSat 580 // report only outpoints that are not spent in mempool 581 _, e := spentInMempool[o.txid+strconv.Itoa(int(o.vout))] 582 if !e { 583 r = append(r, AddressUtxo{ 584 Txid: o.txid, 585 Vout: o.vout, 586 AmountSat: v, 587 Amount: w.chainParser.AmountToDecimalString(&v), 588 Height: int(ta.Height), 589 Confirmations: bestheight - int(ta.Height) + 1, 590 }) 591 } 592 checksum.Sub(&checksum, &v) 593 } 594 } 595 } 596 } 597 } 598 if checksum.Uint64() != 0 { 599 glog.Warning("DB inconsistency: ", address, ": checksum is not zero") 600 } 601 glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start)) 602 return r, nil 603 } 604 605 // GetBlocks returns BlockInfo for blocks on given page 606 func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) { 607 start := time.Now() 608 page-- 609 if page < 0 { 610 page = 0 611 } 612 b, _, err := w.db.GetBestBlock() 613 bestheight := int(b) 614 if err != nil { 615 return nil, errors.Annotatef(err, "GetBestBlock") 616 } 617 pg, from, to, page := computePaging(bestheight+1, page, blocksOnPage) 618 r := &Blocks{Paging: pg} 619 r.Blocks = make([]db.BlockInfo, to-from) 620 for i := from; i < to; i++ { 621 bi, err := w.db.GetBlockInfo(uint32(bestheight - i)) 622 if err != nil { 623 return nil, err 624 } 625 if bi == nil { 626 r.Blocks = r.Blocks[:i] 627 break 628 } 629 r.Blocks[i-from] = *bi 630 } 631 glog.Info("GetBlocks page ", page, " finished in ", time.Since(start)) 632 return r, nil 633 } 634 635 // GetBlock returns paged data about block 636 func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) { 637 start := time.Now() 638 page-- 639 if page < 0 { 640 page = 0 641 } 642 // try to decide if passed string (bid) is block height or block hash 643 // if it's a number, must be less than int32 644 var hash string 645 height, err := strconv.Atoi(bid) 646 if err == nil && height < int(^uint32(0)) { 647 hash, err = w.db.GetBlockHash(uint32(height)) 648 if err != nil { 649 hash = bid 650 } 651 } else { 652 hash = bid 653 } 654 bi, err := w.chain.GetBlockInfo(hash) 655 if err != nil { 656 if err == bchain.ErrBlockNotFound { 657 return nil, NewAPIError("Block not found", true) 658 } 659 return nil, NewAPIError(fmt.Sprintf("Block not found, %v", err), true) 660 } 661 dbi := &db.BlockInfo{ 662 Hash: bi.Hash, 663 Height: bi.Height, 664 Time: bi.Time, 665 } 666 txCount := len(bi.Txids) 667 bestheight, _, err := w.db.GetBestBlock() 668 if err != nil { 669 return nil, errors.Annotatef(err, "GetBestBlock") 670 } 671 pg, from, to, page := computePaging(txCount, page, txsOnPage) 672 glog.Info("GetBlock ", bid, ", page ", page, " finished in ", time.Since(start)) 673 txs := make([]*Tx, to-from) 674 txi := 0 675 for i := from; i < to; i++ { 676 txid := bi.Txids[i] 677 ta, err := w.db.GetTxAddresses(txid) 678 if err != nil { 679 return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) 680 } 681 if ta == nil { 682 glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses") 683 continue 684 } 685 txs[txi] = w.txFromTxAddress(txid, ta, dbi, bestheight) 686 txi++ 687 } 688 txs = txs[:txi] 689 bi.Txids = nil 690 return &Block{ 691 Paging: pg, 692 BlockInfo: *bi, 693 TxCount: txCount, 694 Transactions: txs, 695 }, nil 696 } 697 698 // GetSystemInfo returns information about system 699 func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) { 700 start := time.Now() 701 ci, err := w.chain.GetChainInfo() 702 if err != nil { 703 return nil, errors.Annotatef(err, "GetChainInfo") 704 } 705 vi := common.GetVersionInfo() 706 ss, bh, st := w.is.GetSyncState() 707 ms, mt, msz := w.is.GetMempoolSyncState() 708 var dbc []common.InternalStateColumn 709 var dbs int64 710 if internal { 711 dbc = w.is.GetAllDBColumnStats() 712 dbs = w.is.DBSizeTotal() 713 } 714 bi := &BlockbookInfo{ 715 Coin: w.is.Coin, 716 Host: w.is.Host, 717 Version: vi.Version, 718 GitCommit: vi.GitCommit, 719 BuildTime: vi.BuildTime, 720 SyncMode: w.is.SyncMode, 721 InitialSync: w.is.InitialSync, 722 InSync: ss, 723 BestHeight: bh, 724 LastBlockTime: st, 725 InSyncMempool: ms, 726 LastMempoolTime: mt, 727 MempoolSize: msz, 728 DbSize: w.db.DatabaseSizeOnDisk(), 729 DbSizeFromColumns: dbs, 730 DbColumns: dbc, 731 About: Text.BlockbookAbout, 732 } 733 glog.Info("GetSystemInfo finished in ", time.Since(start)) 734 return &SystemInfo{bi, ci}, nil 735 }