2021-12-04 16:42:11 +00:00
// Copyright 2017-2021 DERO Project. All rights reserved.
// Use of this source code in any form is governed by RESEARCH license.
// license can be found in the LICENSE file.
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
//
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package explorerlib
// this file implements the explorer for DERO blockchain
// this needs only RPC access
// NOTE: Only use data exported from within the RPC interface, do direct use of exported variables fom packages
// NOTE: we can use structs defined within the RPCserver package
// TODO: error handling is non-existant ( as this was built up in hrs ). Add proper error handling
//
import "time"
import "fmt"
import "embed"
import "bytes"
import "unicode"
import "unsafe" // need to avoid this, but only used by byteviewer
import "strings"
import "strconv"
import "context"
import "encoding/hex"
import "net/http"
import "html/template"
//import "encoding/json"
//import "io/ioutil"
import "github.com/go-logr/logr"
import "github.com/deroproject/derohe/block"
import "github.com/deroproject/derohe/cryptography/crypto"
import "github.com/deroproject/derohe/globals"
import "github.com/deroproject/derohe/transaction"
import "github.com/deroproject/derohe/rpc"
import "github.com/deroproject/derohe/proof"
import "github.com/deroproject/derohe/glue/rwc"
import "github.com/creachadair/jrpc2"
import "github.com/creachadair/jrpc2/channel"
import "github.com/gorilla/websocket"
//go:embed templates/*.tmpl
var tpls embed . FS
//go:embed static/*
var static embed . FS
type Client struct {
WS * websocket . Conn
RPC * jrpc2 . Client
}
var rpc_client = & Client { }
var Connected bool = false
var mainnet = true
var endpoint string
var replacer = strings . NewReplacer ( "h" , ":" , "m" , ":" , "s" , "" )
var logger logr . Logger
func ( cli * Client ) Call ( method string , params interface { } , result interface { } ) error {
try := 0
try_again :
if cli == nil || ! cli . IsDaemonOnline ( ) {
go Connect ( )
time . Sleep ( time . Second )
try ++
if try < 3 {
goto try_again
}
return fmt . Errorf ( "client is offline or not connected" )
}
return cli . RPC . CallResult ( context . Background ( ) , method , params , result )
}
// this is as simple as it gets
// single threaded communication to get the daemon status and height
// this will tell whether the wallet can connection successfully to daemon or not
func ( cli * Client ) IsDaemonOnline ( ) bool {
if cli . WS == nil || cli . RPC == nil {
return false
}
return true
}
func ( cli * Client ) onlinecheck_and_get_online ( ) {
for {
if cli . IsDaemonOnline ( ) {
var result string
if err := cli . Call ( "DERO.Ping" , nil , & result ) ; err != nil {
logger . V ( 1 ) . Error ( err , "Ping failed:" )
cli . RPC . Close ( )
cli . WS = nil
cli . RPC = nil
Connect ( ) // try to connect again
} else {
//fmt.Printf("Ping Received %s\n", result)
}
}
time . Sleep ( time . Second )
}
}
// this is as simple as it gets
// single threaded communication to get the daemon status and height
// this will tell whether the wallet can connection successfully to daemon or not
func Connect ( ) ( err error ) {
// TODO enable socks support here
//rpc_conn, err = rpcc.Dial("ws://"+ w.Daemon_Endpoint + "/ws")
daemon_endpoint := endpoint
rpc_client . WS , _ , err = websocket . DefaultDialer . Dial ( "ws://" + daemon_endpoint + "/ws" , nil )
// notify user of any state change
// if daemon connection breaks or comes live again
if err == nil {
if ! Connected {
logger . V ( 1 ) . Info ( "Connection to RPC server successful" , "daemon_endpoint" , "ws://" + daemon_endpoint + "/ws" )
Connected = true
}
} else {
logger . Error ( err , "Error connecting to daemon" )
if Connected {
logger . Error ( err , "Connection to RPC server Failed " , "daemon_endpoint" , "ws://" + daemon_endpoint + "/ws" )
}
Connected = false
return
}
input_output := rwc . New ( rpc_client . WS )
rpc_client . RPC = jrpc2 . NewClient ( channel . RawJSON ( input_output , input_output ) , nil )
var result string
if err := rpc_client . Call ( "DERO.Ping" , nil , & result ) ; err != nil {
logger . V ( 1 ) . Error ( err , "Ping failed:" )
} else {
// fmt.Printf("Ping Received %s\n", result)
}
var info rpc . GetInfo_Result
// collect all the data afresh, execute rpc to service
if err = rpc_client . Call ( "DERO.GetInfo" , nil , & info ) ; err != nil {
logger . V ( 1 ) . Error ( err , "GetInfo failed:" )
} else {
mainnet = ! info . Testnet // inverse of testnet is mainnet
}
return nil
}
func StartServer ( loggerb logr . Logger , daemon_endpoint string , listen_address string ) ( err error ) {
logger = globals . Logger . WithName ( "explorer" )
endpoint = daemon_endpoint
logger . Info ( "Daemon RPC endpoint " , "endpoint" , endpoint )
logger . Info ( "Will listen " , "address" , listen_address )
// execute rpc to service
err = Connect ( )
if err == nil {
logger . Info ( "Connection to RPC server successful" )
} else {
logger . Error ( err , "Connection to RPC server Failed" )
return
}
go rpc_client . onlinecheck_and_get_online ( ) // keep connectingto server
all_templates , err = template . ParseFS ( tpls , "templates/*.tmpl" )
if err != nil {
logger . Error ( err , "error parsing templates." )
return
}
http . Handle ( "/static/" , http . StripPrefix ( "/static/" , http . FileServer ( http . FS ( static ) ) ) ) // include all static assets
http . HandleFunc ( "/search" , search_handler )
http . HandleFunc ( "/page/" , page_handler )
http . HandleFunc ( "/block/" , block_handler )
http . HandleFunc ( "/txpool/" , txpool_handler )
http . HandleFunc ( "/tx/" , tx_handler )
http . HandleFunc ( "/" , root_handler )
go func ( ) {
logger . Info ( "Listening for requests" )
err = http . ListenAndServe ( listen_address , nil )
logger . Error ( err , "ListenAndServe failed" )
} ( )
time . Sleep ( 50 * time . Millisecond )
return err
}
// all the tx info which ever needs to be printed
type txinfo struct {
Hex string // raw tx
Height string // height at which tx was mined
HeightBuilt uint64 // height at which tx was built
2021-12-09 15:59:01 +00:00
BLID string // BLID in whose reference this tx was built
2021-12-04 16:42:11 +00:00
RootHash string // roothash which forms the basis for balance tree
TransactionType string // transaction type
Depth int64
Timestamp uint64 // timestamp
Age string // time diff from current time
Block_time string // UTC time from block header
Epoch uint64 // Epoch time
In_Pool bool // whether tx was in pool
Hash string // hash for hash
PrefixHash string // prefix hash
Version int // version of tx
Size string // size of tx in KB
Sizeuint64 uint64 // size of tx in bytes
Burn_Value string // value of burned amount
Fee string // fee in TX
Feeuint64 uint64 // fee in atomic units
In int // inputs counts
Out int // outputs counts
Amount string
CoinBase bool // is tx coin base
Extra string // extra within tx
Keyimages [ ] string // key images within tx
OutAddress [ ] string // contains output secret key
OutOffset [ ] uint64 // contains index offsets
Type string // ringct or ruffct ( bulletproof)
ValidBlock string // the tx is valid in which block
InvalidBlock [ ] string // the tx is invalid in which block
Skipped bool // this is only valid, when a block is being listed
Ring_size int
Ring [ ] [ ] string // contains entire ring in string form
TXpublickey string
PayID32 string // 32 byte payment ID
PayID8 string // 8 byte encrypted payment ID
Proof_address string // address agains which which the proving ran
Proof_index int64 // proof satisfied for which index
Proof_amount string // decoded amount
Proof_Payload_raw string // payload raw bytes
Proof_Payload string // if proof decoded, decoded , else decode error
Proof_error string // error if any while decoding proof
SC_TX_Available string //bool // whether this contains an SC TX
SC_Signer string // whether SC signer
SC_Signer_verified string // whether SC signer can be verified successfully
SC_Balance uint64 // SC SC_Balance in atomic units
SC_Balance_string string // SC_Balance in DERO
SC_Keys map [ string ] string // SC key value of
SC_Args rpc . Arguments // rpc.Arguments
SC_Code string // install SC
SC_State rpc . GetSC_Result // current SC state
SC_Install bool
Assets [ ] Asset
}
type Asset struct {
SCID string
Fees string
Burn string
Ring [ ] string
Ring_size int
}
// any information for block which needs to be printed
type block_info struct {
Block block . Block
Major_Version uint64
Minor_Version uint64
Height int64
TopoHeight int64
Depth int64
Timestamp uint64
Hash string
Tips [ ] string
Nonce uint64
Fees string
Reward string
Size string
Age string // time diff from current time
Block_time string // UTC time from block header
Epoch uint64 // Epoch time
Outputs string
Mtx txinfo
Txs [ ] txinfo
Orphan_Status bool
SyncBlock bool // whether the block is sync block
Tx_Count int
}
var all_templates * template . Template
// load and setup block_info from rpc
// if hash is less than 64 bytes then it is considered a height parameter
func load_block_from_rpc ( info * block_info , block_hash string , recursive bool ) ( err error ) {
var bl block . Block
var bresult rpc . GetBlock_Result
var block_height int
var block_bin [ ] byte
if len ( block_hash ) != 64 { // parameter is a height
fmt . Sscanf ( block_hash , "%d" , & block_height )
// user requested block height
logger . V ( 1 ) . Info ( "User requested block" , "topoheight" , block_height , "user input" , block_hash )
if err = rpc_client . Call ( "DERO.GetBlock" , rpc . GetBlock_Params { Height : uint64 ( block_height ) } , & bresult ) ; err != nil {
return fmt . Errorf ( "getblock rpc failed. err %s" , err )
}
} else { // parameter is the hex blob
logger . V ( 1 ) . Info ( "User requested block using hash" , "block_hash" , block_hash )
if err = rpc_client . Call ( "DERO.GetBlock" , rpc . GetBlock_Params { Hash : block_hash } , & bresult ) ; err != nil {
return fmt . Errorf ( "getblock rpc failed" )
}
}
// fmt.Printf("block %d %+v\n",i, bresult)
info . TopoHeight = bresult . Block_Header . TopoHeight
info . Height = bresult . Block_Header . Height
info . Depth = bresult . Block_Header . Depth
duration_millisecond := ( uint64 ( time . Now ( ) . UTC ( ) . UnixMilli ( ) ) - bresult . Block_Header . Timestamp )
info . Age = replacer . Replace ( ( time . Duration ( duration_millisecond ) * time . Millisecond ) . String ( ) )
info . Block_time = time . Unix ( 0 , int64 ( bresult . Block_Header . Timestamp * uint64 ( time . Millisecond ) ) ) . Format ( "2006-01-02 15:04:05" )
info . Epoch = bresult . Block_Header . Timestamp
info . Outputs = fmt . Sprintf ( "%.03f" , float32 ( bresult . Block_Header . Reward ) / 1000000000000.0 )
info . Size = "N/A"
info . Hash = bresult . Block_Header . Hash
//info.Prev_Hash = bresult.Block_Header.Prev_Hash
info . Tips = bresult . Block_Header . Tips
info . Orphan_Status = bresult . Block_Header . Orphan_Status
info . SyncBlock = bresult . Block_Header . SyncBlock
info . Nonce = bresult . Block_Header . Nonce
info . Major_Version = bresult . Block_Header . Major_Version
info . Minor_Version = bresult . Block_Header . Minor_Version
info . Reward = fmt . Sprintf ( "%.03f" , float32 ( bresult . Block_Header . Reward ) / 1000000000000.0 )
block_bin , _ = hex . DecodeString ( bresult . Blob )
//log.Infof("block %+v bresult %+v ", bl, bresult)
bl . Deserialize ( block_bin )
info . Block = bl
if recursive {
// fill in miner tx info
//err = load_tx_from_rpc(&info.Mtx, bl.Miner_TX.GetHash().String()) //TODO handle error
load_tx_info_from_tx ( & info . Mtx , & bl . Miner_TX )
// miner tx reward is calculated on runtime due to client protocol reasons in dero atlantis
// feed what is calculated by the daemon
reward := uint64 ( 0 )
if bl . Miner_TX . TransactionType == transaction . PREMINE {
reward += bl . Miner_TX . Value
}
info . Mtx . Amount = fmt . Sprintf ( "%.05f" , float64 ( reward + bresult . Block_Header . Reward ) / 100000 )
//logger.Error(err,"loading miner tx from rpc ", "txid", bl.Miner_TX.GetHash().String())
info . Tx_Count = len ( bl . Tx_hashes )
fees := uint64 ( 0 )
size := uint64 ( len ( bl . Serialize ( ) ) )
// if we have any other tx load them also
for i := 0 ; i < len ( bl . Tx_hashes ) ; i ++ {
var tx txinfo
err = load_tx_from_rpc ( & tx , bl . Tx_hashes [ i ] . String ( ) ) //TODO handle error
if err != nil {
logger . V ( 1 ) . Error ( err , "loading tx " , "txid" , bl . Tx_hashes [ i ] . String ( ) )
}
if tx . ValidBlock != bresult . Block_Header . Hash { // track skipped status
tx . Skipped = true
}
info . Txs = append ( info . Txs , tx )
fees += tx . Feeuint64
size += tx . Sizeuint64
}
info . Fees = fmt . Sprintf ( "%.03f" , float32 ( fees ) / 100000.0 )
info . Size = fmt . Sprintf ( "%.03f" , float32 ( size ) / 1024 )
}
return
}
// this will fill up the info struct from the tx
func load_tx_info_from_tx ( info * txinfo , tx * transaction . Transaction ) ( err error ) {
info . Hash = tx . GetHash ( ) . String ( )
//info.PrefixHash = tx.GetPrefixHash().String()
info . TransactionType = tx . TransactionType . String ( )
info . Size = fmt . Sprintf ( "%.03f" , float32 ( len ( tx . Serialize ( ) ) ) / 1024 )
info . Sizeuint64 = uint64 ( len ( tx . Serialize ( ) ) )
info . Version = int ( tx . Version )
//info.Extra = fmt.Sprintf("%x", tx.Extra)
if len ( tx . Payloads ) >= 1 {
info . RootHash = fmt . Sprintf ( "%x" , tx . Payloads [ 0 ] . Statement . Roothash [ : ] )
}
info . HeightBuilt = tx . Height
2021-12-09 15:59:01 +00:00
info . BLID = fmt . Sprintf ( "%x" , tx . BLID )
2021-12-04 16:42:11 +00:00
//info.In = len(tx.Vin)
//info.Out = len(tx.Vout)
if tx . TransactionType == transaction . BURN_TX {
info . Burn_Value = fmt . Sprintf ( " %.05f" , float64 ( tx . Value ) / 100000 )
}
switch tx . TransactionType {
case transaction . PREMINE :
var acckey crypto . Point
if err := acckey . DecodeCompressed ( tx . MinerAddress [ : ] ) ; err != nil {
panic ( err )
}
astring := rpc . NewAddressFromKeys ( & acckey )
astring . Mainnet = mainnet
info . OutAddress = append ( info . OutAddress , astring . String ( ) )
info . Amount = globals . FormatMoney ( tx . Value )
case transaction . REGISTRATION :
var acckey crypto . Point
if err := acckey . DecodeCompressed ( tx . MinerAddress [ : ] ) ; err != nil {
panic ( err )
}
astring := rpc . NewAddressFromKeys ( & acckey )
astring . Mainnet = mainnet
info . OutAddress = append ( info . OutAddress , astring . String ( ) )
case transaction . COINBASE :
info . CoinBase = true
info . In = 0
var acckey crypto . Point
if err := acckey . DecodeCompressed ( tx . MinerAddress [ : ] ) ; err != nil {
panic ( err )
}
astring := rpc . NewAddressFromKeys ( & acckey )
astring . Mainnet = mainnet
info . OutAddress = append ( info . OutAddress , astring . String ( ) )
case transaction . NORMAL , transaction . BURN_TX , transaction . SC_TX :
info . Fee = globals . FormatMoney ( tx . Fees ( ) )
info . Ring_size = int ( tx . Payloads [ 0 ] . Statement . RingSize )
}
if tx . TransactionType == transaction . SC_TX {
info . SC_Args = tx . SCDATA
2022-01-26 10:05:01 +00:00
2021-12-04 16:42:11 +00:00
}
// if outputs cannot be located, do not panic
// this will be the case for pool transactions
if len ( info . OutAddress ) != len ( info . OutOffset ) {
info . OutOffset = make ( [ ] uint64 , len ( info . OutAddress ) , len ( info . OutAddress ) )
}
switch 0 {
case 0 :
info . Type = "DERO_HOMOMORPHIC"
default :
panic ( "not implemented" )
}
if ! info . In_Pool && ! info . CoinBase && ( tx . TransactionType == transaction . NORMAL || tx . TransactionType == transaction . BURN_TX || tx . TransactionType == transaction . SC_TX ) { // find the age of block and other meta
var blinfo block_info
err := load_block_from_rpc ( & blinfo , fmt . Sprintf ( "%s" , info . Height ) , false ) // we only need block data and not data of txs
if err != nil {
return err
}
// fmt.Printf("Blinfo %+v height %d", blinfo, info.Height);
info . Age = blinfo . Age
info . Block_time = blinfo . Block_time
info . Epoch = blinfo . Epoch
info . Timestamp = blinfo . Epoch
info . Depth = blinfo . Depth
}
return nil
}
// load and setup txinfo from rpc
func load_tx_from_rpc ( info * txinfo , txhash string ) ( err error ) {
var tx_params rpc . GetTransaction_Params
var tx_result rpc . GetTransaction_Result
//fmt.Printf("Requesting tx data %s", txhash);
tx_params . Tx_Hashes = append ( tx_params . Tx_Hashes , txhash )
if err = rpc_client . Call ( "DERO.GetTransaction" , tx_params , & tx_result ) ; err != nil {
return fmt . Errorf ( "gettransa rpc failed err %s" , err )
}
//fmt.Printf("TX response %+v", tx_result)
if tx_result . Status != "OK" {
return fmt . Errorf ( "No Such TX RPC error status %s" , tx_result . Status )
}
var tx transaction . Transaction
if len ( tx_result . Txs_as_hex [ 0 ] ) < 50 {
return
}
info . Hex = tx_result . Txs_as_hex [ 0 ]
tx_bin , _ := hex . DecodeString ( tx_result . Txs_as_hex [ 0 ] )
tx . Deserialize ( tx_bin )
// fill as much info required from headers
if tx_result . Txs [ 0 ] . In_pool {
info . In_Pool = true
} else {
info . Height = fmt . Sprintf ( "%d" , tx_result . Txs [ 0 ] . Block_Height )
}
for x := range tx_result . Txs [ 0 ] . Output_Indices {
info . OutOffset = append ( info . OutOffset , tx_result . Txs [ 0 ] . Output_Indices [ x ] )
}
if tx . IsCoinbase ( ) { // fill miner tx reward from what the chain tells us
info . Amount = fmt . Sprintf ( "%.05f" , float64 ( uint64 ( tx_result . Txs [ 0 ] . Reward ) ) / 100000 )
}
info . ValidBlock = tx_result . Txs [ 0 ] . ValidBlock
info . InvalidBlock = tx_result . Txs [ 0 ] . InvalidBlock
info . Ring = tx_result . Txs [ 0 ] . Ring
2022-01-26 10:05:01 +00:00
if tx . TransactionType == transaction . SC_TX {
info . SC_Signer = "Unknown"
}
2021-12-04 16:42:11 +00:00
if tx . TransactionType == transaction . NORMAL || tx . TransactionType == transaction . BURN_TX || tx . TransactionType == transaction . SC_TX {
for t := range tx . Payloads {
var a Asset
a . SCID = tx . Payloads [ t ] . SCID . String ( )
a . Fees = fmt . Sprintf ( "%.05f" , float64 ( tx . Payloads [ t ] . Statement . Fees ) / 100000 )
a . Burn = fmt . Sprintf ( "%.05f" , float64 ( tx . Payloads [ t ] . BurnValue ) / 100000 )
if len ( tx_result . Txs [ 0 ] . Ring ) == 0 {
continue
}
a . Ring_size = len ( tx_result . Txs [ 0 ] . Ring [ t ] )
a . Ring = tx_result . Txs [ 0 ] . Ring [ t ]
2022-01-26 10:05:01 +00:00
info . SC_Signer = tx_result . Txs [ 0 ] . Signer
2021-12-04 16:42:11 +00:00
info . Assets = append ( info . Assets , a )
}
//fmt.Printf("assets now %+v\n", info.Assets)
}
info . SC_Balance = tx_result . Txs [ 0 ] . Balance
info . SC_Balance_string = fmt . Sprintf ( "%.05f" , float64 ( uint64 ( info . SC_Balance ) / 100000 ) )
info . SC_Code = tx_result . Txs [ 0 ] . Code
if tx . TransactionType == transaction . SC_TX && len ( info . SC_Code ) >= 1 {
if len ( info . SC_Code ) >= 1 {
info . SC_Install = true
}
var p = rpc . GetSC_Params { SCID : txhash , Variables : true }
var r rpc . GetSC_Result
if err = rpc_client . Call ( "DERO.GetSC" , p , & r ) ; err != nil {
logger . V ( 1 ) . Error ( err , "DERO.GetSC failed" )
} else {
info . SC_State = r
}
}
//info.Ring = strings.Join(info.OutAddress, " ")
//fmt.Printf("tx_result %+v\n",tx_result.Txs)
// fmt.Printf("response contained tx %s \n", tx.GetHash())
return load_tx_info_from_tx ( info , & tx )
}
func block_handler ( w http . ResponseWriter , r * http . Request ) {
param := ""
fmt . Sscanf ( r . URL . EscapedPath ( ) , "/block/%s" , & param )
var blinfo block_info
err := load_block_from_rpc ( & blinfo , param , true )
_ = err
// execute template now
data := map [ string ] interface { } { }
fill_common_info ( data , false )
data [ "block" ] = blinfo
err = all_templates . ExecuteTemplate ( w , "block" , data )
if err != nil {
return
}
return
// fmt.Fprint(w, "This is a valid block")
}
func tx_handler ( w http . ResponseWriter , r * http . Request ) {
var info txinfo
tx_hex := ""
fmt . Sscanf ( r . URL . EscapedPath ( ) , "/tx/%s" , & tx_hex )
txhash := crypto . HashHexToHash ( tx_hex )
logger . V ( 1 ) . Info ( "user requested " , "txid" , tx_hex )
err := load_tx_from_rpc ( & info , txhash . String ( ) ) //TODO handle error
_ = err
// check whether user requested proof
tx_proof := r . PostFormValue ( "txproof" )
raw_tx_data := r . PostFormValue ( "raw_tx_data" )
if raw_tx_data != "" { // gives ability to prove transactions not in the blockchain
info . Hex = raw_tx_data
}
if tx_proof != "" {
logger . V ( 1 ) . Info ( "Proving TX" , "proof" , tx_proof , "tx_hex" , info . Hex , "ring" , info . Ring )
// there may be more than 1 amounts, only first one is shown
addresses , amounts , raw , decoded , err := proof . Prove ( tx_proof , info . Hex , info . Ring , mainnet )
if err == nil { //&& len(amounts) > 0 && len(indexes) > 0{
logger . V ( 1 ) . Info ( "Successfully proved transaction" , "txid" , tx_hex , "payload_count" , len ( decoded ) )
info . Proof_address = addresses [ 0 ]
info . Proof_amount = globals . FormatMoney ( amounts [ 0 ] )
info . Proof_Payload_raw = BytesViewer ( raw [ 0 ] ) . String ( ) // raw payload
info . Proof_Payload = decoded [ 0 ]
} else {
logger . V ( 1 ) . Error ( err , "err while proving" )
if err != nil {
info . Proof_error = err . Error ( )
}
}
}
// execute template now
data := map [ string ] interface { } { }
fill_common_info ( data , false )
data [ "info" ] = info
err = all_templates . ExecuteTemplate ( w , "tx" , data )
if err != nil {
return
}
return
}
func pool_handler ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprint ( w , "This is a valid pool" )
}
// if there is any error, we return back empty
// if pos is wrong we return back
// pos is descending order
func fill_tx_structure ( pos int , size_in_blocks int ) ( data [ ] block_info ) {
i := pos
for ; i > ( pos - size_in_blocks ) && i >= 0 ; i -- { // query blocks by topo height
var blinfo block_info
if err := load_block_from_rpc ( & blinfo , fmt . Sprintf ( "%d" , i ) , true ) ; err == nil {
data = append ( data , blinfo )
} else {
logger . V ( 2 ) . Error ( err , "error loading block" , "i" , i )
}
}
if i == 0 {
var blinfo block_info
if err := load_block_from_rpc ( & blinfo , fmt . Sprintf ( "%d" , i ) , true ) ; err == nil {
data = append ( data , blinfo )
} else {
logger . V ( 2 ) . Error ( err , "error loading block" , "i" , i )
}
}
return
}
func show_page ( w http . ResponseWriter , page int ) {
data := map [ string ] interface { } { }
var info rpc . GetInfo_Result
var err error
if err = rpc_client . Call ( "DERO.GetInfo" , nil , & info ) ; err != nil {
goto exit_error
}
fill_common_info ( data , true )
if page == 0 { // use requested invalid page, give current page
page = int ( info . TopoHeight ) / 10
}
data [ "previous_page" ] = page - 1
if page <= 1 {
data [ "previous_page" ] = 1
}
data [ "current_page" ] = page
if ( int ( info . TopoHeight ) % 10 ) == 0 {
data [ "total_page" ] = ( int ( info . TopoHeight ) / 10 )
} else {
data [ "total_page" ] = ( int ( info . TopoHeight ) / 10 )
}
data [ "next_page" ] = page + 1
if ( page + 1 ) > data [ "total_page" ] . ( int ) {
data [ "next_page" ] = page
}
fill_tx_pool_info ( data , 25 )
if page == 1 { // page 1 has 11 blocks, it does not show genesis block
data [ "block_array" ] = fill_tx_structure ( int ( page * 10 ) , 12 )
} else {
if int ( info . TopoHeight ) - int ( page * 10 ) > 10 {
data [ "block_array" ] = fill_tx_structure ( int ( page * 10 ) , 10 )
} else {
data [ "block_array" ] = fill_tx_structure ( int ( info . TopoHeight ) , int ( info . TopoHeight ) - int ( ( page - 1 ) * 10 ) )
}
}
//fmt.Printf("page %+v\n", data)
err = all_templates . ExecuteTemplate ( w , "main" , data )
if err != nil {
goto exit_error
}
return
exit_error :
fmt . Fprintf ( w , "Error occurred err %s" , err )
}
func txpool_handler ( w http . ResponseWriter , r * http . Request ) {
data := map [ string ] interface { } { }
fill_common_info ( data , true )
fill_tx_pool_info ( data , 500 ) // show only 500 txs
var err error
if err = all_templates . ExecuteTemplate ( w , "txpool_page" , data ) ; err != nil {
goto exit_error
}
return
exit_error :
fmt . Fprintf ( w , "Error occurred err %s" , err )
}
// shows a page
func page_handler ( w http . ResponseWriter , r * http . Request ) {
page := 0
page_string := r . URL . EscapedPath ( )
fmt . Sscanf ( page_string , "/page/%d" , & page )
logger . V ( 1 ) . Info ( "user requested page" , "page" , page )
show_page ( w , page )
}
// root shows page 0
func root_handler ( w http . ResponseWriter , r * http . Request ) {
logger . V ( 1 ) . Info ( "Showing main page" )
show_page ( w , 0 )
}
// search handler, finds the items using rpc bruteforce
func search_handler ( w http . ResponseWriter , r * http . Request ) {
var info rpc . GetInfo_Result
var err error
logger . V ( 1 ) . Info ( "Showing search page" )
values , ok := r . URL . Query ( ) [ "value" ]
if ! ok || len ( values ) < 1 {
show_page ( w , 0 )
return
}
// Query()["key"] will return an array of items,
// we only want the single item.
value := strings . TrimSpace ( values [ 0 ] )
good := false
// collect all the data afresh, execute rpc to service
if err = rpc_client . Call ( "DERO.GetInfo" , nil , & info ) ; err != nil {
goto exit_error
}
if len ( value ) != 64 {
if s , err := strconv . ParseInt ( value , 10 , 64 ) ; err == nil && s >= 0 && s <= info . TopoHeight {
good = true
}
} else { // check whether the string can be hex decoded
t , err := hex . DecodeString ( value )
if err != nil || len ( t ) != 32 {
} else {
good = true
}
}
// value should be either 64 hex chars or a topoheight which should be less than current topoheight
if good {
// check whether the page is block or tx or height
var blinfo block_info
var tx txinfo
err := load_block_from_rpc ( & blinfo , value , false )
if err == nil {
logger . V ( 1 ) . Info ( "Redirecting user to block page" )
http . Redirect ( w , r , "/block/" + value , 302 )
return
}
err = load_tx_from_rpc ( & tx , value ) //TODO handle error
if err == nil {
logger . V ( 1 ) . Info ( "Redirecting user to tx page" )
http . Redirect ( w , r , "/tx/" + value , 302 )
return
}
}
{
data := map [ string ] interface { } { }
fill_common_info ( data , true )
if err = all_templates . ExecuteTemplate ( w , "notfound_page" , data ) ; err == nil {
return
}
}
exit_error :
show_page ( w , 0 )
return
}
func fill_common_info ( data map [ string ] interface { } , extra_data bool ) error {
var info rpc . GetInfo_Result
data [ "title" ] = "DERO HE BlockChain Explorer(v1)"
data [ "servertime" ] = time . Now ( ) . UTC ( ) . Format ( "2006-01-02 15:04:05" )
if ! extra_data {
return nil
}
// collect all the data afresh, execute rpc to service
if err := rpc_client . Call ( "DERO.GetInfo" , nil , & info ) ; err != nil {
return err
}
//fmt.Printf("get info %+v", info)
data [ "Network_Difficulty" ] = info . Difficulty
data [ "hash_rate" ] = fmt . Sprintf ( "%.03f" , float32 ( info . Difficulty / 1000000 ) / float32 ( info . Target ) )
data [ "txpool_size" ] = info . Tx_pool_size
data [ "testnet" ] = info . Testnet
data [ "network" ] = info . Network
data [ "fee_per_kb" ] = float64 ( info . Dynamic_fee_per_kb ) / 1000000000000
data [ "median_block_size" ] = fmt . Sprintf ( "%.02f" , float32 ( info . Median_Block_Size ) / 1024 )
data [ "total_supply" ] = info . Total_Supply
data [ "averageblocktime50" ] = info . AverageBlockTime50
return nil
}
// fill all the tx pool info as per requested
func fill_tx_pool_info ( data map [ string ] interface { } , max_count int ) error {
var err error
var txs [ ] txinfo
var txpool rpc . GetTxPool_Result
data [ "mempool" ] = txs // initialize with empty data
if err = rpc_client . Call ( "DERO.GetTxPool" , nil , & txpool ) ; err != nil {
return fmt . Errorf ( "gettxpool rpc failed" )
}
for i := range txpool . Tx_list {
var info txinfo
err := load_tx_from_rpc ( & info , txpool . Tx_list [ i ] ) //TODO handle error
if err != nil {
continue
}
txs = append ( txs , info )
if len ( txs ) >= max_count {
break
}
}
data [ "mempool" ] = txs
return nil
}
// BytesViewer bytes viewer
type BytesViewer [ ] byte
// String returns view in hexadecimal
func ( b BytesViewer ) String ( ) string {
if len ( b ) == 0 {
return "invlaid string"
}
const head = `
| Address | Hex | Text |
| -- -- -- - : | : -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | : -- -- -- -- -- -- -- - |
`
const row = 16
result := make ( [ ] byte , 0 , len ( head ) / 2 * ( len ( b ) / 16 + 3 ) )
result = append ( result , head ... )
for i := 0 ; i < len ( b ) ; i += row {
result = append ( result , "| " ... )
result = append ( result , fmt . Sprintf ( "%08x" , i ) ... )
result = append ( result , " | " ... )
k := i + row
more := 0
if k >= len ( b ) {
more = k - len ( b )
k = len ( b )
}
for j := i ; j != k ; j ++ {
if b [ j ] < 16 {
result = append ( result , '0' )
}
result = strconv . AppendUint ( result , uint64 ( b [ j ] ) , 16 )
result = append ( result , ' ' )
}
for j := 0 ; j != more ; j ++ {
result = append ( result , " " ... )
}
result = append ( result , "| " ... )
buf := bytes . Map ( func ( r rune ) rune {
if unicode . IsSpace ( r ) {
return ' '
}
return r
} , b [ i : k ] )
result = append ( result , buf ... )
for j := 0 ; j != more ; j ++ {
result = append ( result , ' ' )
}
result = append ( result , " |\n" ... )
}
return * ( * string ) ( unsafe . Pointer ( & result ) )
}