You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

673 lines
19 KiB

  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. package main
  17. // this file implements the explorer for DERO blockchain
  18. // this needs only RPC access
  19. // NOTE: Only use data exported from within the RPC interface, do direct use of exported variables fom packages
  20. // NOTE: we can use structs defined within the RPCserver package
  21. // This is being developed to track down and confirm some bugs
  22. // 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,
  23. // atleast for the last 1 year
  24. // TODO: error handling is non-existant ( as this was built up in hrs ). Add proper error handling
  25. //
  26. import "time"
  27. import "fmt"
  28. import "net"
  29. import "bytes"
  30. import "strings"
  31. import "encoding/hex"
  32. import "net/http"
  33. import "html/template"
  34. import "encoding/json"
  35. import "io/ioutil"
  36. import "github.com/docopt/docopt-go"
  37. import log "github.com/sirupsen/logrus"
  38. import "github.com/ybbus/jsonrpc"
  39. import "github.com/deroproject/derosuite/block"
  40. import "github.com/deroproject/derosuite/crypto"
  41. import "github.com/deroproject/derosuite/transaction"
  42. import "github.com/deroproject/derosuite/blockchain/rpcserver"
  43. var command_line string = `dero_explorer
  44. DERO Explorer: A secure, private blockchain with smart-contracts
  45. Usage:
  46. dero_explorer [--help] [--version] [--debug] [--rpc-server-address=<127.0.0.1:18091>] [--http-address=<0.0.0.0:8080>]
  47. dero_explorer -h | --help
  48. dero_explorer --version
  49. Options:
  50. -h --help Show this screen.
  51. --version Show version.
  52. --debug Debug mode enabled, print log messages
  53. --rpc-server-address=<127.0.0.1:18091> connect to this daemon port as client
  54. --http-address=<0.0.0.0:8080> explorer listens on this port to serve user requests`
  55. var rpcClient *jsonrpc.RPCClient
  56. var netClient *http.Client
  57. var endpoint string
  58. var replacer = strings.NewReplacer("h", ":", "m", ":", "s", "")
  59. func main() {
  60. var err error
  61. var arguments map[string]interface{}
  62. arguments, err = docopt.Parse(command_line, nil, true, "DERO Explorer : work in progress", false)
  63. if err != nil {
  64. log.Fatalf("Error while parsing options err: %s\n", err)
  65. }
  66. if arguments["--debug"].(bool) == true {
  67. log.SetLevel(log.DebugLevel)
  68. }
  69. log.Debugf("Arguments %+v", arguments)
  70. log.Infof("DERO Exporer : This is under heavy development, use it for testing/evaluations purpose only")
  71. log.Infof("Copyright 2017-2018 DERO Project. All rights reserved.")
  72. endpoint = "127.0.0.1:9999"
  73. if arguments["--rpc-server-address"] != nil {
  74. endpoint = arguments["--rpc-server-address"].(string)
  75. }
  76. log.Infof("using RPC endpoint %s", endpoint)
  77. listen_address := "0.0.0.0:8080"
  78. if arguments["--http-address"] != nil {
  79. listen_address = arguments["--http-address"].(string)
  80. }
  81. log.Infof("Will listen on %s", listen_address)
  82. // create client
  83. rpcClient = jsonrpc.NewRPCClient("http://" + endpoint + "/json_rpc")
  84. var netTransport = &http.Transport{
  85. Dial: (&net.Dialer{
  86. Timeout: 5 * time.Second,
  87. }).Dial,
  88. TLSHandshakeTimeout: 5 * time.Second,
  89. }
  90. netClient = &http.Client{
  91. Timeout: time.Second * 10,
  92. Transport: netTransport,
  93. }
  94. // execute rpc to service
  95. response, err := rpcClient.Call("get_info")
  96. if err == nil {
  97. log.Infof("Connection to RPC server successful")
  98. } else {
  99. log.Fatalf("Connection to RPC server Failed err %s", err)
  100. }
  101. var info rpcserver.GetInfo_Result
  102. err = response.GetObject(&info)
  103. fmt.Printf("%+v err %s\n", info, err)
  104. http.HandleFunc("/search", search_handler)
  105. http.HandleFunc("/page/", page_handler)
  106. http.HandleFunc("/block/", block_handler)
  107. http.HandleFunc("/txpool/", txpool_handler)
  108. http.HandleFunc("/tx/", tx_handler)
  109. http.HandleFunc("/", root_handler)
  110. fmt.Printf("Listening for requests\n")
  111. err = http.ListenAndServe(listen_address, nil)
  112. log.Warnf("Listening to port %s err : %s", listen_address, err)
  113. }
  114. // all the tx info which ever needs to be printed
  115. type txinfo struct {
  116. Height string // height at which tx was mined
  117. Depth uint64
  118. Timestamp uint64 // timestamp
  119. Age string // time diff from current time
  120. Block_time string // UTC time from block header
  121. Epoch uint64 // Epoch time
  122. In_Pool bool // whether tx was in pool
  123. Hash string // hash for hash
  124. PrefixHash string // prefix hash
  125. Version int // version of tx
  126. Size string // size of tx in KB
  127. Sizeuint64 uint64 // size of tx in bytes
  128. Fee string // fee in TX
  129. Feeuint64 uint64 // fee in atomic units
  130. In int // inputs counts
  131. Out int // outputs counts
  132. Amount string
  133. CoinBase bool // is tx coin base
  134. Extra string // extra within tx
  135. Keyimages []string // key images within tx
  136. OutAddress []string // contains output secret key
  137. OutOffset []uint64 // contains index offsets
  138. Type string // ringct or ruffct ( bulletproof)
  139. Ring_size int
  140. }
  141. // any information for block which needs to be printed
  142. type block_info struct {
  143. Major_Version uint64
  144. Minor_Version uint64
  145. Height uint64
  146. Depth uint64
  147. Timestamp uint64
  148. Hash string
  149. Prev_Hash string
  150. Nonce uint64
  151. Fees string
  152. Reward string
  153. Size string
  154. Age string // time diff from current time
  155. Block_time string // UTC time from block header
  156. Epoch uint64 // Epoch time
  157. Outputs string
  158. Mtx txinfo
  159. Txs []txinfo
  160. Orphan_Status bool
  161. Tx_Count int
  162. }
  163. // load and setup block_info from rpc
  164. // if hash is less than 64 bytes then it is considered a height parameter
  165. func load_block_from_rpc(info *block_info, block_hash string, recursive bool) (err error) {
  166. var bl block.Block
  167. var bresult rpcserver.GetBlock_Result
  168. var block_height int
  169. var block_bin []byte
  170. if len(block_hash) != 64 { // parameter is a height
  171. fmt.Sscanf(block_hash, "%d", &block_height)
  172. // user requested block height
  173. log.Debugf("User requested block at height %d user input %s", block_height, block_hash)
  174. response, err := rpcClient.CallNamed("getblock", map[string]interface{}{"height": uint64(block_height)})
  175. if err != nil {
  176. return err
  177. }
  178. err = response.GetObject(&bresult)
  179. if err != nil {
  180. return err
  181. }
  182. } else { // parameter is the hex blob
  183. log.Debugf("User requested block %s", block_hash)
  184. response, err := rpcClient.CallNamed("getblock", map[string]interface{}{"hash": block_hash})
  185. if err != nil {
  186. log.Warnf("err %s ", err)
  187. return err
  188. }
  189. if response.Error != nil {
  190. log.Warnf("err %s ", response.Error)
  191. return fmt.Errorf("No Such block or other Error")
  192. }
  193. err = response.GetObject(&bresult)
  194. if err != nil {
  195. return err
  196. }
  197. }
  198. // fmt.Printf("block %d %+v\n",i, bresult)
  199. info.Height = bresult.Block_Header.Height
  200. info.Depth = bresult.Block_Header.Depth
  201. duration_second := (uint64(time.Now().UTC().Unix()) - bresult.Block_Header.Timestamp)
  202. info.Age = replacer.Replace((time.Duration(duration_second) * time.Second).String())
  203. info.Block_time = time.Unix(int64(bresult.Block_Header.Timestamp), 0).Format("2006-01-02 15:04:05")
  204. info.Epoch = bresult.Block_Header.Timestamp
  205. info.Outputs = fmt.Sprintf("%.03f", float32(bresult.Block_Header.Reward)/1000000000000.0)
  206. info.Size = "N/A"
  207. info.Hash = bresult.Block_Header.Hash
  208. info.Prev_Hash = bresult.Block_Header.Prev_Hash
  209. info.Orphan_Status = bresult.Block_Header.Orphan_Status
  210. info.Nonce = bresult.Block_Header.Nonce
  211. info.Major_Version = bresult.Block_Header.Major_Version
  212. info.Minor_Version = bresult.Block_Header.Minor_Version
  213. info.Reward = fmt.Sprintf("%.03f", float32(bresult.Block_Header.Reward)/1000000000000.0)
  214. block_bin, _ = hex.DecodeString(bresult.Blob)
  215. bl.Deserialize(block_bin)
  216. if recursive {
  217. // fill in miner tx info
  218. err = load_tx_from_rpc(&info.Mtx, bl.Miner_tx.GetHash().String()) //TODO handle error
  219. info.Tx_Count = len(bl.Tx_hashes)
  220. fees := uint64(0)
  221. size := uint64(0)
  222. // if we have any other tx load them also
  223. for i := 0; i < len(bl.Tx_hashes); i++ {
  224. var tx txinfo
  225. err = load_tx_from_rpc(&tx, bl.Tx_hashes[i].String()) //TODO handle error
  226. info.Txs = append(info.Txs, tx)
  227. fees += tx.Feeuint64
  228. size += tx.Sizeuint64
  229. }
  230. info.Fees = fmt.Sprintf("%.03f", float32(fees)/1000000000000.0)
  231. info.Size = fmt.Sprintf("%.03f", float32(size)/1024)
  232. }
  233. return
  234. }
  235. // this will fill up the info struct from the tx
  236. func load_tx_info_from_tx(info *txinfo, tx *transaction.Transaction) (err error) {
  237. info.Hash = tx.GetHash().String()
  238. info.PrefixHash = tx.GetPrefixHash().String()
  239. info.Size = fmt.Sprintf("%.03f", float32(len(tx.Serialize()))/1024)
  240. info.Sizeuint64 = uint64(len(tx.Serialize()))
  241. info.Version = int(tx.Version)
  242. info.Extra = fmt.Sprintf("%x", tx.Extra)
  243. info.In = len(tx.Vin)
  244. info.Out = len(tx.Vout)
  245. if !tx.IsCoinbase() {
  246. info.Fee = fmt.Sprintf("%.012f", float64(tx.RctSignature.Get_TX_Fee())/1000000000000)
  247. info.Feeuint64 = tx.RctSignature.Get_TX_Fee()
  248. info.Amount = "?"
  249. info.Ring_size = len(tx.Vin[0].(transaction.Txin_to_key).Key_offsets)
  250. for i := 0; i < len(tx.Vin); i++ {
  251. 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))
  252. }
  253. } else {
  254. info.CoinBase = true
  255. info.In = 0
  256. info.Amount = fmt.Sprintf("%.012f", float64(tx.Vout[0].Amount)/1000000000000)
  257. }
  258. for i := 0; i < len(tx.Vout); i++ {
  259. info.OutAddress = append(info.OutAddress, tx.Vout[i].Target.(transaction.Txout_to_key).Key.String())
  260. }
  261. // if outputs cannot be located, do not panic
  262. // this will be the case for pool transactions
  263. if len(info.OutAddress) != len(info.OutOffset) {
  264. info.OutOffset = make([]uint64, len(info.OutAddress), len(info.OutAddress))
  265. }
  266. switch tx.RctSignature.Get_Sig_Type() {
  267. case 0:
  268. info.Type = "RingCT/0"
  269. case 1:
  270. info.Type = "RingCT/1 MG"
  271. case 2:
  272. info.Type = "RingCT/2 Simple"
  273. }
  274. if !info.In_Pool { // find the age of block and other meta
  275. var blinfo block_info
  276. err := load_block_from_rpc(&blinfo, fmt.Sprintf("%s", info.Height), false) // we only need block data and not data of txs
  277. if err != nil {
  278. return err
  279. }
  280. // fmt.Printf("Blinfo %+v height %d", blinfo, info.Height);
  281. info.Age = blinfo.Age
  282. info.Block_time = blinfo.Block_time
  283. info.Epoch = blinfo.Epoch
  284. info.Timestamp = blinfo.Epoch
  285. info.Depth = blinfo.Depth
  286. }
  287. return nil
  288. }
  289. // load and setup txinfo from rpc
  290. func load_tx_from_rpc(info *txinfo, txhash string) (err error) {
  291. var tx_params rpcserver.GetTransaction_Params
  292. var tx_result rpcserver.GetTransaction_Result
  293. //fmt.Printf("Requesting tx data %s", txhash);
  294. tx_params.Tx_Hashes = append(tx_params.Tx_Hashes, txhash)
  295. request_bytes, err := json.Marshal(&tx_params)
  296. response, err := http.Post("http://"+endpoint+"/gettransactions", "application/json", bytes.NewBuffer(request_bytes))
  297. if err != nil {
  298. //fmt.Printf("err while requesting tx err %s",err);
  299. return
  300. }
  301. buf, err := ioutil.ReadAll(response.Body)
  302. if err != nil {
  303. // fmt.Printf("err while reading reponse body err %s",err);
  304. return
  305. }
  306. err = json.Unmarshal(buf, &tx_result)
  307. if err != nil {
  308. return
  309. }
  310. // fmt.Printf("TX response %+v", tx_result)
  311. if tx_result.Status != "OK" {
  312. return fmt.Errorf("No Such TX RPC error status %s", tx_result.Status)
  313. }
  314. var tx transaction.Transaction
  315. if len(tx_result.Txs_as_hex[0]) < 50 {
  316. return
  317. }
  318. tx_bin, _ := hex.DecodeString(tx_result.Txs_as_hex[0])
  319. tx.DeserializeHeader(tx_bin)
  320. // fill as much info required from headers
  321. if tx_result.Txs[0].In_pool {
  322. info.In_Pool = true
  323. } else {
  324. info.Height = fmt.Sprintf("%d", tx_result.Txs[0].Block_Height)
  325. }
  326. for x := range tx_result.Txs[0].Output_Indices {
  327. info.OutOffset = append(info.OutOffset, tx_result.Txs[0].Output_Indices[x])
  328. }
  329. //fmt.Printf("tx_result %+v\n",tx_result.Txs)
  330. return load_tx_info_from_tx(info, &tx)
  331. }
  332. func block_handler(w http.ResponseWriter, r *http.Request) {
  333. param := ""
  334. fmt.Sscanf(r.URL.EscapedPath(), "/block/%s", &param)
  335. var blinfo block_info
  336. err := load_block_from_rpc(&blinfo, param, true)
  337. _ = err
  338. // execute template now
  339. data := map[string]interface{}{}
  340. data["title"] = "DERO BlockChain Explorer (Golang)"
  341. data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05")
  342. data["block"] = blinfo
  343. t, err := template.New("foo").Parse(header_template + block_template + footer_template)
  344. err = t.ExecuteTemplate(w, "block", data)
  345. if err != nil {
  346. return
  347. }
  348. return
  349. // fmt.Fprint(w, "This is a valid block")
  350. }
  351. func tx_handler(w http.ResponseWriter, r *http.Request) {
  352. var info txinfo
  353. tx_hex := ""
  354. fmt.Sscanf(r.URL.EscapedPath(), "/tx/%s", &tx_hex)
  355. txhash := crypto.HashHexToHash(tx_hex)
  356. log.Debugf("user requested TX %s", tx_hex)
  357. err := load_tx_from_rpc(&info, txhash.String()) //TODO handle error
  358. _ = err
  359. // execute template now
  360. data := map[string]interface{}{}
  361. data["title"] = "DERO BlockChain Explorer (Golang)"
  362. data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05")
  363. data["info"] = info
  364. t, err := template.New("foo").Parse(header_template + tx_template + footer_template)
  365. err = t.ExecuteTemplate(w, "tx", data)
  366. if err != nil {
  367. return
  368. }
  369. return
  370. }
  371. func pool_handler(w http.ResponseWriter, r *http.Request) {
  372. fmt.Fprint(w, "This is a valid pool")
  373. }
  374. // if there is any error, we return back empty
  375. // if pos is wrong we return back
  376. // pos is descending order
  377. func fill_tx_structure(pos int, size_in_blocks int) (data []block_info) {
  378. for i := pos; i > (pos-11) && i >= 0; i-- { // query blocks by height
  379. var blinfo block_info
  380. err := load_block_from_rpc(&blinfo, fmt.Sprintf("%d", i), true)
  381. if err == nil {
  382. data = append(data, blinfo)
  383. }
  384. }
  385. return
  386. }
  387. func show_page(w http.ResponseWriter, page int) {
  388. data := map[string]interface{}{}
  389. var info rpcserver.GetInfo_Result
  390. data["title"] = "DERO BlockChain Explorer (Golang)"
  391. data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05")
  392. t, err := template.New("foo").Parse(header_template + txpool_template + main_template + paging_template + footer_template)
  393. // collect all the data afresh
  394. // execute rpc to service
  395. response, err := rpcClient.Call("get_info")
  396. if err != nil {
  397. goto exit_error
  398. }
  399. err = response.GetObject(&info)
  400. if err != nil {
  401. goto exit_error
  402. }
  403. //fmt.Printf("get info %+v", info)
  404. data["Network_Difficulty"] = info.Difficulty
  405. data["hash_rate"] = fmt.Sprintf("%.03f", float32(info.Difficulty/1000000)/float32(info.Target))
  406. data["txpool_size"] = info.Tx_pool_size
  407. data["testnet"] = info.Testnet
  408. if int(info.Height) < page*11 { // use requested invalid page, give page 0
  409. page = 0
  410. }
  411. data["previous_page"] = 0
  412. if page > 0 {
  413. data["previous_page"] = page - 1
  414. }
  415. data["current_page"] = page
  416. data["total_page"] = int(info.Height) / 11
  417. data["next_page"] = page + 1
  418. if (page + 1) > int(info.Height)/11 {
  419. data["next_page"] = page
  420. }
  421. fill_tx_pool_info(data, 25)
  422. data["block_array"] = fill_tx_structure(int(info.Height)-(page*11), 11)
  423. err = t.ExecuteTemplate(w, "main", data)
  424. if err != nil {
  425. goto exit_error
  426. }
  427. return
  428. exit_error:
  429. fmt.Fprintf(w, "Error occurred err %s", err)
  430. }
  431. func txpool_handler(w http.ResponseWriter, r *http.Request) {
  432. data := map[string]interface{}{}
  433. var info rpcserver.GetInfo_Result
  434. data["title"] = "DERO BlockChain Explorer (Golang)"
  435. data["servertime"] = time.Now().UTC().Format("2006-01-02 15:04:05")
  436. t, err := template.New("foo").Parse(header_template + txpool_template + main_template + paging_template + footer_template + txpool_page_template)
  437. // collect all the data afresh
  438. // execute rpc to service
  439. response, err := rpcClient.Call("get_info")
  440. if err != nil {
  441. goto exit_error
  442. }
  443. err = response.GetObject(&info)
  444. if err != nil {
  445. goto exit_error
  446. }
  447. //fmt.Printf("get info %+v", info)
  448. data["Network_Difficulty"] = info.Difficulty
  449. data["hash_rate"] = fmt.Sprintf("%.03f", float32(info.Difficulty/1000000)/float32(info.Target))
  450. data["txpool_size"] = info.Tx_pool_size
  451. data["testnet"] = info.Testnet
  452. fill_tx_pool_info(data, 500) // show only 500 txs
  453. err = t.ExecuteTemplate(w, "txpool_page", data)
  454. if err != nil {
  455. goto exit_error
  456. }
  457. return
  458. exit_error:
  459. fmt.Fprintf(w, "Error occurred err %s", err)
  460. }
  461. // shows a page
  462. func page_handler(w http.ResponseWriter, r *http.Request) {
  463. page := 0
  464. page_string := r.URL.EscapedPath()
  465. fmt.Sscanf(page_string, "/page/%d", &page)
  466. log.Debugf("user requested page %d", page)
  467. show_page(w, page)
  468. }
  469. // root shows page 0
  470. func root_handler(w http.ResponseWriter, r *http.Request) {
  471. log.Debugf("Showing main page")
  472. show_page(w, 0)
  473. }
  474. // search handler, finds the items using rpc bruteforce
  475. func search_handler(w http.ResponseWriter, r *http.Request) {
  476. log.Debugf("Showing search page")
  477. values, ok := r.URL.Query()["value"]
  478. if !ok || len(values) < 1 {
  479. show_page(w, 0)
  480. return
  481. }
  482. // Query()["key"] will return an array of items,
  483. // we only want the single item.
  484. value := values[0]
  485. // check whether the page is block or tx or height
  486. var blinfo block_info
  487. var tx txinfo
  488. err := load_block_from_rpc(&blinfo, value, false)
  489. if err == nil {
  490. log.Debugf("Redirecting user to block page")
  491. http.Redirect(w, r, "/block/"+value, 302)
  492. return
  493. }
  494. err = load_tx_from_rpc(&tx, value) //TODO handle error
  495. if err == nil {
  496. log.Debugf("Redirecting user to tx page")
  497. http.Redirect(w, r, "/tx/"+value, 302)
  498. return
  499. }
  500. show_page(w, 0)
  501. return
  502. }
  503. // fill all the tx pool info as per requested
  504. func fill_tx_pool_info(data map[string]interface{}, max_count int) error {
  505. var txs []txinfo
  506. var txpool rpcserver.GetTxPool_Result
  507. data["mempool"] = txs // initialize with empty data
  508. // collect all the data afresh
  509. // execute rpc to service
  510. response, err := rpcClient.Call("gettxpool")
  511. if err != nil {
  512. return fmt.Errorf("gettxpool rpc failed")
  513. }
  514. err = response.GetObject(&txpool)
  515. if err != nil {
  516. return fmt.Errorf("gettxpool rpc failed")
  517. }
  518. for i := range txpool.Tx_list {
  519. var info txinfo
  520. err := load_tx_from_rpc(&info, txpool.Tx_list[i]) //TODO handle error
  521. if err != nil {
  522. continue
  523. }
  524. txs = append(txs, info)
  525. if len(txs) >= max_count {
  526. break
  527. }
  528. }
  529. data["mempool"] = txs
  530. return nil
  531. }