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", &param)
   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  }