github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/cmd/explorer/explorer.go (about) 1 // Copyright 2017-2018 DERO Project. All rights reserved. 2 // Use of this source code in any form is governed by RESEARCH license. 3 // license can be found in the LICENSE file. 4 // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 5 // 6 // 7 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 8 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 9 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 10 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 12 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 13 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 14 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 15 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 17 package main 18 19 // this file implements the explorer for DERO blockchain 20 // this needs only RPC access 21 // NOTE: Only use data exported from within the RPC interface, do direct use of exported variables fom packages 22 // NOTE: we can use structs defined within the RPCserver package 23 // This is being developed to track down and confirm some bugs 24 // NOTE: This is NO longer entirely compliant with the xyz RPC interface ( the pool part is not compliant), currently and can be used as it for their chain, 25 // atleast for the last 1 year 26 27 // TODO: error handling is non-existant ( as this was built up in hrs ). Add proper error handling 28 // 29 30 import "time" 31 import "fmt" 32 import "net" 33 import "bytes" 34 import "strings" 35 import "strconv" 36 import "encoding/hex" 37 import "net/http" 38 import "html/template" 39 import "encoding/json" 40 import "io/ioutil" 41 42 import "github.com/docopt/docopt-go" 43 import log "github.com/sirupsen/logrus" 44 import "github.com/ybbus/jsonrpc" 45 46 import "github.com/deroproject/derosuite/block" 47 import "github.com/deroproject/derosuite/crypto" 48 import "github.com/deroproject/derosuite/globals" 49 import "github.com/deroproject/derosuite/transaction" 50 import "github.com/deroproject/derosuite/structures" 51 import "github.com/deroproject/derosuite/proof" 52 53 var command_line string = `dero_explorer 54 DERO Atlantis Explorer: A secure, private blockchain with smart-contracts 55 56 Usage: 57 dero_explorer [--help] [--version] [--debug] [--rpc-server-address=<127.0.0.1:18091>] [--http-address=<0.0.0.0:8080>] 58 dero_explorer -h | --help 59 dero_explorer --version 60 61 Options: 62 -h --help Show this screen. 63 --version Show version. 64 --debug Debug mode enabled, print log messages 65 --rpc-server-address=<127.0.0.1:18091> connect to this daemon port as client 66 --http-address=<0.0.0.0:8080> explorer listens on this port to serve user requests` 67 68 var rpcClient *jsonrpc.RPCClient 69 var netClient *http.Client 70 var endpoint string 71 var replacer = strings.NewReplacer("h", ":", "m", ":", "s", "") 72 73 func main() { 74 var err error 75 var arguments map[string]interface{} 76 77 arguments, err = docopt.Parse(command_line, nil, true, "DERO Explorer : work in progress", false) 78 79 if err != nil { 80 log.Fatalf("Error while parsing options err: %s\n", err) 81 } 82 83 if arguments["--debug"].(bool) == true { 84 log.SetLevel(log.DebugLevel) 85 } 86 87 log.Debugf("Arguments %+v", arguments) 88 log.Infof("DERO Atlantis Exporer : This is under heavy development, use it for testing/evaluations purpose only") 89 log.Infof("Copyright 2017-2020 DERO Project. All rights reserved.") 90 endpoint = "127.0.0.1:30306" 91 if arguments["--rpc-server-address"] != nil { 92 endpoint = arguments["--rpc-server-address"].(string) 93 } 94 95 log.Infof("using RPC endpoint %s", endpoint) 96 97 listen_address := "0.0.0.0:8081" 98 if arguments["--http-address"] != nil { 99 listen_address = arguments["--http-address"].(string) 100 } 101 log.Infof("Will listen on %s", listen_address) 102 103 // create client 104 rpcClient = jsonrpc.NewRPCClient("http://" + endpoint + "/json_rpc") 105 106 var netTransport = &http.Transport{ 107 Dial: (&net.Dialer{ 108 Timeout: 5 * time.Second, 109 }).Dial, 110 TLSHandshakeTimeout: 5 * time.Second, 111 } 112 113 netClient = &http.Client{ 114 Timeout: time.Second * 10, 115 Transport: netTransport, 116 } 117 118 // execute rpc to service 119 response, err := rpcClient.Call("get_info") 120 121 if err == nil { 122 log.Infof("Connection to RPC server successful") 123 } else { 124 log.Fatalf("Connection to RPC server Failed err %s", err) 125 } 126 var info structures.GetInfo_Result 127 err = response.GetObject(&info) 128 129 fmt.Printf("%+v err %s\n", info, err) 130 131 http.HandleFunc("/search", search_handler) 132 http.HandleFunc("/page/", page_handler) 133 http.HandleFunc("/block/", block_handler) 134 http.HandleFunc("/txpool/", txpool_handler) 135 http.HandleFunc("/tx/", tx_handler) 136 http.HandleFunc("/", root_handler) 137 138 fmt.Printf("Listening for requests\n") 139 err = http.ListenAndServe(listen_address, nil) 140 log.Warnf("Listening to port %s err : %s", listen_address, err) 141 142 } 143 144 // all the tx info which ever needs to be printed 145 type txinfo struct { 146 Hex string // raw tx 147 Height string // height at which tx was mined 148 Depth int64 149 Timestamp uint64 // timestamp 150 Age string // time diff from current time 151 Block_time string // UTC time from block header 152 Epoch uint64 // Epoch time 153 In_Pool bool // whether tx was in pool 154 Hash string // hash for hash 155 PrefixHash string // prefix hash 156 Version int // version of tx 157 Size string // size of tx in KB 158 Sizeuint64 uint64 // size of tx in bytes 159 Fee string // fee in TX 160 Feeuint64 uint64 // fee in atomic units 161 In int // inputs counts 162 Out int // outputs counts 163 Amount string 164 CoinBase bool // is tx coin base 165 Extra string // extra within tx 166 Keyimages []string // key images within tx 167 OutAddress []string // contains output secret key 168 OutOffset []uint64 // contains index offsets 169 Type string // ringct or ruffct ( bulletproof) 170 ValidBlock string // the tx is valid in which block 171 InvalidBlock []string // the tx is invalid in which block 172 Skipped bool // this is only valid, when a block is being listed 173 Ring_size int 174 Ring [][]globals.TX_Output_Data 175 176 TXpublickey string 177 PayID32 string // 32 byte payment ID 178 PayID8 string // 8 byte encrypted payment ID 179 180 181 Proof_address string // address agains which which the proving ran 182 Proof_index int64 // proof satisfied for which index 183 Proof_amount string // decoded amount 184 Proof_PayID8 string // decrypted 8 byte payment id 185 Proof_error string // error if any while decoding proof 186 187 } 188 189 // any information for block which needs to be printed 190 type block_info struct { 191 Major_Version uint64 192 Minor_Version uint64 193 Height int64 194 TopoHeight int64 195 Depth int64 196 Timestamp uint64 197 Hash string 198 Tips []string 199 Nonce uint64 200 Fees string 201 Reward string 202 Size string 203 Age string // time diff from current time 204 Block_time string // UTC time from block header 205 Epoch uint64 // Epoch time 206 Outputs string 207 Mtx txinfo 208 Txs []txinfo 209 Orphan_Status bool 210 SyncBlock bool // whether the block is sync block 211 Tx_Count int 212 } 213 214 // load and setup block_info from rpc 215 // if hash is less than 64 bytes then it is considered a height parameter 216 func load_block_from_rpc(info *block_info, block_hash string, recursive bool) (err error) { 217 var bl block.Block 218 var bresult structures.GetBlock_Result 219 220 var block_height int 221 var block_bin []byte 222 if len(block_hash) != 64 { // parameter is a height 223 fmt.Sscanf(block_hash, "%d", &block_height) 224 // user requested block height 225 log.Debugf("User requested block at height %d user input %s", block_height, block_hash) 226 response, err := rpcClient.CallNamed("getblock", map[string]interface{}{"height": uint64(block_height)}) 227 228 if err != nil { 229 return err 230 } 231 err = response.GetObject(&bresult) 232 if err != nil { 233 return err 234 } 235 236 } else { // parameter is the hex blob 237 238 log.Debugf("User requested block %s", block_hash) 239 response, err := rpcClient.CallNamed("getblock", map[string]interface{}{"hash": block_hash}) 240 241 if err != nil { 242 log.Warnf("err %s ", err) 243 return err 244 } 245 if response.Error != nil { 246 log.Warnf("err %s ", response.Error) 247 return fmt.Errorf("No Such block or other Error") 248 } 249 250 err = response.GetObject(&bresult) 251 if err != nil { 252 return err 253 } 254 } 255 256 // fmt.Printf("block %d %+v\n",i, bresult) 257 info.TopoHeight = bresult.Block_Header.TopoHeight 258 info.Height = bresult.Block_Header.Height 259 info.Depth = bresult.Block_Header.Depth 260 261 duration_second := (uint64(time.Now().UTC().Unix()) - bresult.Block_Header.Timestamp) 262 info.Age = replacer.Replace((time.Duration(duration_second) * time.Second).String()) 263 info.Block_time = time.Unix(int64(bresult.Block_Header.Timestamp), 0).Format("2006-01-02 15:04:05") 264 info.Epoch = bresult.Block_Header.Timestamp 265 info.Outputs = fmt.Sprintf("%.03f", float32(bresult.Block_Header.Reward)/1000000000000.0) 266 info.Size = "N/A" 267 info.Hash = bresult.Block_Header.Hash 268 //info.Prev_Hash = bresult.Block_Header.Prev_Hash 269 info.Tips = bresult.Block_Header.Tips 270 info.Orphan_Status = bresult.Block_Header.Orphan_Status 271 info.SyncBlock = bresult.Block_Header.SyncBlock 272 info.Nonce = bresult.Block_Header.Nonce 273 info.Major_Version = bresult.Block_Header.Major_Version 274 info.Minor_Version = bresult.Block_Header.Minor_Version 275 info.Reward = fmt.Sprintf("%.03f", float32(bresult.Block_Header.Reward)/1000000000000.0) 276 277 block_bin, _ = hex.DecodeString(bresult.Blob) 278 279 //log.Infof("block %+v bresult %+v ", bl, bresult) 280 281 bl.Deserialize(block_bin) 282 283 if recursive { 284 // fill in miner tx info 285 286 err = load_tx_from_rpc(&info.Mtx, bl.Miner_TX.GetHash().String()) //TODO handle error 287 288 // miner tx reward is calculated on runtime due to client protocol reasons in dero atlantis 289 // feed what is calculated by the daemon 290 info.Mtx.Amount = fmt.Sprintf("%.012f", float64(bresult.Block_Header.Reward)/1000000000000) 291 292 log.Infof("loading tx from rpc %s %s", bl.Miner_TX.GetHash().String(), err) 293 info.Tx_Count = len(bl.Tx_hashes) 294 295 fees := uint64(0) 296 size := uint64(0) 297 // if we have any other tx load them also 298 for i := 0; i < len(bl.Tx_hashes); i++ { 299 var tx txinfo 300 err = load_tx_from_rpc(&tx, bl.Tx_hashes[i].String()) //TODO handle error 301 if tx.ValidBlock != bresult.Block_Header.Hash { // track skipped status 302 tx.Skipped = true 303 } 304 info.Txs = append(info.Txs, tx) 305 fees += tx.Feeuint64 306 size += tx.Sizeuint64 307 } 308 309 info.Fees = fmt.Sprintf("%.03f", float32(fees)/1000000000000.0) 310 info.Size = fmt.Sprintf("%.03f", float32(size)/1024) 311 312 } 313 314 return 315 } 316 317 // this will fill up the info struct from the tx 318 func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) { 319 info.Hash = tx.GetHash().String() 320 info.PrefixHash = tx.GetPrefixHash().String() 321 info.Size = fmt.Sprintf("%.03f", float32(len(tx.Serialize()))/1024) 322 info.Sizeuint64 = uint64(len(tx.Serialize())) 323 info.Version = int(tx.Version) 324 info.Extra = fmt.Sprintf("%x", tx.Extra) 325 info.In = len(tx.Vin) 326 info.Out = len(tx.Vout) 327 328 if tx.Parse_Extra() { 329 330 // store public key if present 331 if _, ok := tx.Extra_map[transaction.TX_PUBLIC_KEY]; ok { 332 info.TXpublickey = tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key).String() 333 } 334 335 // store payment IDs if present 336 if _, ok := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID]; ok { 337 info.PayID8 = fmt.Sprintf("%x", tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)) 338 } else if _, ok := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID]; ok { 339 info.PayID32 = fmt.Sprintf("%x", tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID].([]byte)) 340 } 341 342 } 343 344 if !tx.IsCoinbase() { 345 info.Fee = fmt.Sprintf("%.012f", float64(tx.RctSignature.Get_TX_Fee())/1000000000000) 346 info.Feeuint64 = tx.RctSignature.Get_TX_Fee() 347 info.Amount = "?" 348 349 info.Ring_size = len(tx.Vin[0].(transaction.Txin_to_key).Key_offsets) 350 for i := 0; i < len(tx.Vin); i++ { 351 info.Keyimages = append(info.Keyimages, fmt.Sprintf("%s ring members %+v", tx.Vin[i].(transaction.Txin_to_key).K_image, tx.Vin[i].(transaction.Txin_to_key).Key_offsets)) 352 } 353 } else { 354 info.CoinBase = true 355 info.In = 0 356 357 //if info. 358 //info.Amount = fmt.Sprintf("%.012f", float64(tx.Vout[0].Amount)/1000000000000) 359 } 360 361 for i := 0; i < len(tx.Vout); i++ { 362 info.OutAddress = append(info.OutAddress, tx.Vout[i].Target.(transaction.Txout_to_key).Key.String()) 363 } 364 365 // if outputs cannot be located, do not panic 366 // this will be the case for pool transactions 367 if len(info.OutAddress) != len(info.OutOffset) { 368 info.OutOffset = make([]uint64, len(info.OutAddress), len(info.OutAddress)) 369 } 370 371 switch tx.RctSignature.Get_Sig_Type() { 372 case 0: 373 info.Type = "RingCT/0" 374 case 1: 375 info.Type = "RingCT/1 MG" 376 case 2: 377 info.Type = "RingCT/2 Simple" 378 case 3: 379 info.Type = "RingCT/3 Full bulletproof" 380 case 4: 381 info.Type = "RingCT/4 Simple Bulletproof" 382 } 383 384 if !info.In_Pool { // find the age of block and other meta 385 var blinfo block_info 386 err := load_block_from_rpc(&blinfo, fmt.Sprintf("%s", info.Height), false) // we only need block data and not data of txs 387 if err != nil { 388 return err 389 } 390 391 // fmt.Printf("Blinfo %+v height %d", blinfo, info.Height); 392 393 info.Age = blinfo.Age 394 info.Block_time = blinfo.Block_time 395 info.Epoch = blinfo.Epoch 396 info.Timestamp = blinfo.Epoch 397 info.Depth = blinfo.Depth 398 399 } 400 401 return nil 402 } 403 404 // load and setup txinfo from rpc 405 func load_tx_from_rpc(info *txinfo, txhash string) (err error) { 406 var tx_params structures.GetTransaction_Params 407 var tx_result structures.GetTransaction_Result 408 409 //fmt.Printf("Requesting tx data %s", txhash); 410 tx_params.Tx_Hashes = append(tx_params.Tx_Hashes, txhash) 411 412 request_bytes, err := json.Marshal(&tx_params) 413 response, err := http.Post("http://"+endpoint+"/gettransactions", "application/json", bytes.NewBuffer(request_bytes)) 414 if err != nil { 415 fmt.Printf("err while requesting tx err %s\n", err) 416 return 417 } 418 buf, err := ioutil.ReadAll(response.Body) 419 if err != nil { 420 fmt.Printf("err while reading reponse body err %s\n", err) 421 return 422 } 423 424 err = json.Unmarshal(buf, &tx_result) 425 if err != nil { 426 fmt.Printf("err while parsing reponse body err %s\n", err) 427 return 428 } 429 430 // fmt.Printf("TX response %+v", tx_result) 431 432 if tx_result.Status != "OK" { 433 return fmt.Errorf("No Such TX RPC error status %s", tx_result.Status) 434 } 435 436 var tx transaction.Transaction 437 438 if len(tx_result.Txs_as_hex[0]) < 50 { 439 return 440 } 441 442 info.Hex = tx_result.Txs_as_hex[0] 443 444 tx_bin, _ := hex.DecodeString(tx_result.Txs_as_hex[0]) 445 tx.DeserializeHeader(tx_bin) 446 447 // fill as much info required from headers 448 if tx_result.Txs[0].In_pool { 449 info.In_Pool = true 450 } else { 451 info.Height = fmt.Sprintf("%d", tx_result.Txs[0].Block_Height) 452 } 453 454 for x := range tx_result.Txs[0].Output_Indices { 455 info.OutOffset = append(info.OutOffset, tx_result.Txs[0].Output_Indices[x]) 456 } 457 458 if tx.IsCoinbase() { // fill miner tx reward from what the chain tells us 459 info.Amount = fmt.Sprintf("%.012f", float64(uint64(tx_result.Txs[0].Reward))/1000000000000) 460 } 461 462 info.ValidBlock = tx_result.Txs[0].ValidBlock 463 info.InvalidBlock = tx_result.Txs[0].InvalidBlock 464 465 info.Ring = tx_result.Txs[0].Ring 466 467 //fmt.Printf("tx_result %+v\n",tx_result.Txs) 468 469 return load_tx_info_from_tx(info, &tx) 470 } 471 472 func block_handler(w http.ResponseWriter, r *http.Request) { 473 param := "" 474 fmt.Sscanf(r.URL.EscapedPath(), "/block/%s", ¶m) 475 476 var blinfo block_info 477 err := load_block_from_rpc(&blinfo, param, true) 478 _ = err 479 480 // execute template now 481 data := map[string]interface{}{} 482 483 data["title"] = "DERO Atlantis BlockChain Explorer(v1)" 484 data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") 485 data["block"] = blinfo 486 487 t, err := template.New("foo").Parse(header_template + block_template + footer_template + notfound_page_template) 488 489 err = t.ExecuteTemplate(w, "block", data) 490 if err != nil { 491 return 492 } 493 494 return 495 496 // fmt.Fprint(w, "This is a valid block") 497 498 } 499 500 func tx_handler(w http.ResponseWriter, r *http.Request) { 501 var info txinfo 502 tx_hex := "" 503 fmt.Sscanf(r.URL.EscapedPath(), "/tx/%s", &tx_hex) 504 txhash := crypto.HashHexToHash(tx_hex) 505 log.Debugf("user requested TX %s", tx_hex) 506 507 err := load_tx_from_rpc(&info, txhash.String()) //TODO handle error 508 _ = err 509 510 // check whether user requested proof 511 512 tx_secret_key := r.PostFormValue("txprvkey") 513 daddress := r.PostFormValue("deroaddress") 514 raw_tx_data := r.PostFormValue("raw_tx_data") 515 516 if raw_tx_data != "" { // gives ability to prove transactions not in the blockchain 517 info.Hex = raw_tx_data 518 } 519 520 log.Debugf("tx key %s address %s tx %s", tx_secret_key, daddress, tx_hex) 521 522 if tx_secret_key != "" && daddress != "" { 523 524 // there may be more than 1 amounts, only first one is shown 525 indexes, amounts, payids, err := proof.Prove(tx_secret_key,daddress,info.Hex) 526 527 _ = indexes 528 _ = amounts 529 if err == nil { //&& len(amounts) > 0 && len(indexes) > 0{ 530 log.Debugf("Successfully proved transaction %s len(payids) %d",tx_hex, len(payids)) 531 info.Proof_index = int64(indexes[0]) 532 info.Proof_address = daddress 533 info.Proof_amount = globals.FormatMoney12(amounts[0]) 534 if len(payids) >=1 { 535 info.Proof_PayID8 = fmt.Sprintf("%x", payids[0]) // decrypted payment ID 536 } 537 }else{ 538 log.Debugf("err while proving %s",err) 539 if err != nil { 540 info.Proof_error = err.Error() 541 } 542 543 } 544 } 545 546 547 548 549 // execute template now 550 data := map[string]interface{}{} 551 552 data["title"] = "DERO Atlantis BlockChain Explorer(v1)" 553 data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") 554 data["info"] = info 555 556 t, err := template.New("foo").Parse(header_template + tx_template + footer_template + notfound_page_template) 557 558 err = t.ExecuteTemplate(w, "tx", data) 559 if err != nil { 560 return 561 } 562 563 return 564 565 } 566 567 func pool_handler(w http.ResponseWriter, r *http.Request) { 568 569 fmt.Fprint(w, "This is a valid pool") 570 571 } 572 573 // if there is any error, we return back empty 574 // if pos is wrong we return back 575 // pos is descending order 576 func fill_tx_structure(pos int, size_in_blocks int) (data []block_info) { 577 578 for i := pos; i > (pos-size_in_blocks) && i >= 0; i-- { // query blocks by topo height 579 var blinfo block_info 580 err := load_block_from_rpc(&blinfo, fmt.Sprintf("%d", i), true) 581 if err == nil { 582 data = append(data, blinfo) 583 } 584 } 585 return 586 } 587 588 func show_page(w http.ResponseWriter, page int) { 589 data := map[string]interface{}{} 590 var info structures.GetInfo_Result 591 592 data["title"] = "DERO Atlantis BlockChain Explorer(v1)" 593 data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") 594 595 t, err := template.New("foo").Parse(header_template + txpool_template + main_template + paging_template + footer_template + notfound_page_template) 596 597 // collect all the data afresh 598 // execute rpc to service 599 response, err := rpcClient.Call("get_info") 600 601 if err != nil { 602 goto exit_error 603 } 604 605 err = response.GetObject(&info) 606 if err != nil { 607 goto exit_error 608 } 609 610 //fmt.Printf("get info %+v", info) 611 612 data["Network_Difficulty"] = info.Difficulty 613 data["hash_rate"] = fmt.Sprintf("%.03f", float32(info.Difficulty)/float32(info.Target*1000)) 614 data["txpool_size"] = info.Tx_pool_size 615 data["testnet"] = info.Testnet 616 data["averageblocktime50"] = info.AverageBlockTime50 617 data["fee_per_kb"] = float64(info.Dynamic_fee_per_kb) / 1000000000000 618 data["median_block_size"] = fmt.Sprintf("%.02f", float32(info.Median_Block_Size)/1024) 619 data["total_supply"] = info.Total_Supply 620 621 if page == 0 { // use requested invalid page, give current page 622 page = int(info.TopoHeight)/10 623 } 624 625 data["previous_page"] = page - 1 626 if page <= 1 { 627 data["previous_page"] = 1 628 } 629 data["current_page"] = page 630 if (int(info.TopoHeight) % 10) == 0 { 631 data["total_page"] = (int(info.TopoHeight) / 10) 632 }else{ 633 data["total_page"] = (int(info.TopoHeight) / 10) 634 } 635 636 637 data["next_page"] = page + 1 638 if (page + 1) > data["total_page"].(int) { 639 data["next_page"] = page 640 } 641 642 fill_tx_pool_info(data, 25) 643 644 if page == 1{ // page 1 has 11 blocks, it does not show genesis block 645 data["block_array"] = fill_tx_structure(int(page*10), 12) 646 }else{ 647 if int(info.TopoHeight)-int(page*10) > 10{ 648 data["block_array"] = fill_tx_structure(int(page*10), 10) 649 }else{ 650 data["block_array"] = fill_tx_structure(int(info.TopoHeight), int(info.TopoHeight)-int(page*10)) 651 } 652 653 } 654 655 656 err = t.ExecuteTemplate(w, "main", data) 657 if err != nil { 658 goto exit_error 659 } 660 661 return 662 663 exit_error: 664 fmt.Fprintf(w, "Error occurred err %s", err) 665 666 } 667 668 func txpool_handler(w http.ResponseWriter, r *http.Request) { 669 data := map[string]interface{}{} 670 var info structures.GetInfo_Result 671 672 data["title"] = "DERO Atlantis BlockChain Explorer(v1)" 673 data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") 674 675 t, err := template.New("foo").Parse(header_template + txpool_template + main_template + paging_template + footer_template + txpool_page_template + notfound_page_template) 676 677 // collect all the data afresh 678 // execute rpc to service 679 response, err := rpcClient.Call("get_info") 680 681 if err != nil { 682 goto exit_error 683 } 684 685 err = response.GetObject(&info) 686 if err != nil { 687 goto exit_error 688 } 689 690 //fmt.Printf("get info %+v", info) 691 692 data["Network_Difficulty"] = info.Difficulty 693 data["hash_rate"] = fmt.Sprintf("%.03f", float32(info.Difficulty/1000000)/float32(info.Target)) 694 data["txpool_size"] = info.Tx_pool_size 695 data["testnet"] = info.Testnet 696 data["fee_per_kb"] = float64(info.Dynamic_fee_per_kb) / 1000000000000 697 data["median_block_size"] = fmt.Sprintf("%.02f", float32(info.Median_Block_Size)/1024) 698 data["total_supply"] = info.Total_Supply 699 data["averageblocktime50"] = info.AverageBlockTime50 700 701 fill_tx_pool_info(data, 500) // show only 500 txs 702 703 err = t.ExecuteTemplate(w, "txpool_page", data) 704 if err != nil { 705 goto exit_error 706 } 707 708 return 709 710 exit_error: 711 fmt.Fprintf(w, "Error occurred err %s", err) 712 713 } 714 715 // shows a page 716 func page_handler(w http.ResponseWriter, r *http.Request) { 717 page := 0 718 page_string := r.URL.EscapedPath() 719 fmt.Sscanf(page_string, "/page/%d", &page) 720 log.Debugf("user requested page %d", page) 721 show_page(w, page) 722 } 723 724 // root shows page 0 725 func root_handler(w http.ResponseWriter, r *http.Request) { 726 log.Debugf("Showing main page") 727 show_page(w, 0) 728 } 729 730 // search handler, finds the items using rpc bruteforce 731 func search_handler(w http.ResponseWriter, r *http.Request) { 732 var info structures.GetInfo_Result 733 734 log.Debugf("Showing search page") 735 736 values, ok := r.URL.Query()["value"] 737 738 if !ok || len(values) < 1 { 739 show_page(w, 0) 740 return 741 } 742 743 744 745 // Query()["key"] will return an array of items, 746 // we only want the single item. 747 value := strings.TrimSpace(values[0]) 748 good := false 749 750 response, err := rpcClient.Call("get_info") 751 if err != nil { 752 goto exit_error 753 } 754 err = response.GetObject(&info) 755 if err != nil { 756 goto exit_error 757 } 758 759 if len(value) != 64 { 760 if s, err := strconv.ParseInt(value, 10, 64); err == nil && s >= 0 && s <= info.TopoHeight{ 761 good = true 762 } 763 }else{ // check whether the string can be hex decoded 764 t, err := hex.DecodeString(value) 765 if err != nil || len(t) != 32{ 766 767 }else{ 768 good = true 769 } 770 } 771 772 773 // value should be either 64 hex chars or a topoheight which should be less than current topoheight 774 775 if good{ 776 // check whether the page is block or tx or height 777 var blinfo block_info 778 var tx txinfo 779 err := load_block_from_rpc(&blinfo, value, false) 780 if err == nil { 781 log.Debugf("Redirecting user to block page") 782 http.Redirect(w, r, "/block/"+value, 302) 783 return 784 } 785 786 err = load_tx_from_rpc(&tx, value) //TODO handle error 787 if err == nil { 788 log.Debugf("Redirecting user to tx page") 789 http.Redirect(w, r, "/tx/"+value, 302) 790 791 return 792 } 793 } 794 795 { // show error page 796 data := map[string]interface{}{} 797 var info structures.GetInfo_Result 798 799 data["title"] = "DERO Atlantis BlockChain Explorer(v1)" 800 data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05") 801 802 t, err := template.New("foo").Parse(header_template + txpool_template + main_template + paging_template + footer_template + txpool_page_template + notfound_page_template) 803 804 // collect all the data afresh 805 // execute rpc to service 806 807 808 err = response.GetObject(&info) 809 if err != nil { 810 goto exit_error 811 } 812 813 //fmt.Printf("get info %+v", info) 814 815 data["Network_Difficulty"] = info.Difficulty 816 data["hash_rate"] = fmt.Sprintf("%.03f", float32(info.Difficulty/1000000)/float32(info.Target)) 817 data["txpool_size"] = info.Tx_pool_size 818 data["testnet"] = info.Testnet 819 data["fee_per_kb"] = float64(info.Dynamic_fee_per_kb) / 1000000000000 820 data["median_block_size"] = fmt.Sprintf("%.02f", float32(info.Median_Block_Size)/1024) 821 data["total_supply"] = info.Total_Supply 822 data["averageblocktime50"] = info.AverageBlockTime50 823 824 err = t.ExecuteTemplate(w, "notfound_page", data) 825 if err == nil { 826 return 827 828 } 829 830 } 831 exit_error: 832 show_page(w, 0) 833 return 834 835 } 836 837 // fill all the tx pool info as per requested 838 func fill_tx_pool_info(data map[string]interface{}, max_count int) error { 839 840 var txs []txinfo 841 var txpool structures.GetTxPool_Result 842 843 data["mempool"] = txs // initialize with empty data 844 // collect all the data afresh 845 // execute rpc to service 846 response, err := rpcClient.Call("gettxpool") 847 848 if err != nil { 849 return fmt.Errorf("gettxpool rpc failed") 850 } 851 852 err = response.GetObject(&txpool) 853 if err != nil { 854 return fmt.Errorf("gettxpool rpc failed") 855 } 856 857 for i := range txpool.Tx_list { 858 var info txinfo 859 err := load_tx_from_rpc(&info, txpool.Tx_list[i]) //TODO handle error 860 if err != nil { 861 continue 862 } 863 txs = append(txs, info) 864 865 if len(txs) >= max_count { 866 break 867 } 868 } 869 870 data["mempool"] = txs 871 return nil 872 873 }