2022-10-10 15:41:51 +00:00
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool"
block2 "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/block"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
"github.com/ake-persson/mapslice-json"
"github.com/gorilla/mux"
"github.com/tyler-sommer/stick"
"github.com/tyler-sommer/stick/twig"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"golang.org/x/net/html"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func toUint64 ( t any ) uint64 {
if x , ok := t . ( json . Number ) ; ok {
if n , err := x . Int64 ( ) ; err == nil {
return uint64 ( n )
}
}
if x , ok := t . ( uint64 ) ; ok {
return x
} else if x , ok := t . ( int64 ) ; ok {
return uint64 ( x )
} else if x , ok := t . ( uint ) ; ok {
return uint64 ( x )
} else if x , ok := t . ( int ) ; ok {
return uint64 ( x )
} else if x , ok := t . ( float64 ) ; ok {
return uint64 ( x )
} else if x , ok := t . ( float32 ) ; ok {
return uint64 ( x )
} else if x , ok := t . ( string ) ; ok {
if n , err := strconv . ParseUint ( x , 10 , 0 ) ; err == nil {
return n
}
}
return 0
}
func toFloat64 ( t any ) float64 {
if x , ok := t . ( json . Number ) ; ok {
if n , err := x . Float64 ( ) ; err == nil {
return n
}
}
if x , ok := t . ( float64 ) ; ok {
return x
} else if x , ok := t . ( float32 ) ; ok {
return float64 ( x )
} else if x , ok := t . ( uint64 ) ; ok {
return float64 ( x )
} else if x , ok := t . ( int64 ) ; ok {
return float64 ( x )
} else if x , ok := t . ( uint ) ; ok {
return float64 ( x )
} else if x , ok := t . ( int ) ; ok {
return float64 ( x )
2022-10-11 10:39:16 +00:00
} else if x , ok := t . ( string ) ; ok {
if n , err := strconv . ParseFloat ( x , 0 ) ; err == nil {
return n
}
2022-10-10 15:41:51 +00:00
}
return 0
}
func main ( ) {
env := twig . New ( & loader { } )
render := func ( writer http . ResponseWriter , template string , ctx map [ string ] stick . Value ) {
w := bytes . NewBuffer ( nil )
defer func ( ) {
_ , _ = writer . Write ( w . Bytes ( ) )
} ( )
defer func ( ) {
if err := recover ( ) ; err != nil {
w = bytes . NewBuffer ( nil )
writer . WriteHeader ( http . StatusInternalServerError )
opts := make ( map [ string ] stick . Value )
e := make ( map [ string ] stick . Value )
opts [ "error" ] = e
e [ "code" ] = http . StatusInternalServerError
e [ "message" ] = "Internal Server Error"
e [ "content" ] = "<pre>" + html . EscapeString ( fmt . Sprintf ( "%s" , err ) ) + "</pre>"
if err = env . Execute ( "error.html" , w , opts ) ; err != nil {
w = bytes . NewBuffer ( nil )
writer . Header ( ) . Set ( "content-type" , "text/plain" )
_ , _ = w . Write ( [ ] byte ( fmt . Sprintf ( "%s" , err ) ) )
}
}
} ( )
if err := env . Execute ( template , w , ctx ) ; err != nil {
w = bytes . NewBuffer ( nil )
writer . WriteHeader ( http . StatusInternalServerError )
opts := make ( map [ string ] stick . Value )
e := make ( map [ string ] stick . Value )
opts [ "error" ] = e
e [ "code" ] = http . StatusInternalServerError
e [ "message" ] = "Internal Server Error"
e [ "content" ] = "<pre>" + html . EscapeString ( err . Error ( ) ) + "</pre>"
if err = env . Execute ( "error.html" , w , opts ) ; err != nil {
w = bytes . NewBuffer ( nil )
writer . Header ( ) . Set ( "content-type" , "text/plain" )
_ , _ = w . Write ( [ ] byte ( err . Error ( ) ) )
}
}
}
env . Functions [ "getenv" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) == 0 {
return ""
}
return os . Getenv ( args [ 0 ] . ( string ) )
}
env . Functions [ "diff_hashrate" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if d , err := types . DifficultyFromString ( args [ 0 ] . ( string ) ) ; err == nil {
return d . Div64 ( toUint64 ( args [ 1 ] ) ) . Lo
}
return 0
}
env . Functions [ "diff_uint" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if d , err := types . DifficultyFromString ( args [ 0 ] . ( string ) ) ; err == nil {
return d . Lo
}
return 0
}
env . Functions [ "monero_to_xmr" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
v := toUint64 ( args [ 0 ] )
return fmt . Sprintf ( "%d.%012d" , v / 1000000000000 , v % 1000000000000 )
}
env . Functions [ "utc_date" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
return time . Unix ( int64 ( toUint64 ( args [ 0 ] ) ) , 0 ) . UTC ( ) . Format ( "02-01-2006 15:04:05 MST" )
}
env . Functions [ "time_elapsed" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
diff := time . Since ( time . Unix ( int64 ( toUint64 ( args [ 0 ] ) ) , 0 ) . UTC ( ) )
days := int64 ( diff . Hours ( ) / 24 )
2022-10-11 10:39:16 +00:00
hours := int64 ( diff . Hours ( ) ) % 24
2022-10-10 15:41:51 +00:00
minutes := int64 ( diff . Minutes ( ) ) % 60
seconds := int64 ( diff . Seconds ( ) ) % 60
var result [ ] string
if days > 0 {
result = append ( result , strconv . FormatInt ( days , 10 ) + "d" )
}
if hours > 0 {
result = append ( result , strconv . FormatInt ( hours , 10 ) + "h" )
}
if minutes > 0 {
result = append ( result , strconv . FormatInt ( minutes , 10 ) + "m" )
}
if seconds > 0 {
result = append ( result , strconv . FormatInt ( seconds , 10 ) + "s" )
}
if len ( result ) == 0 {
return "just now"
} else {
return strings . Join ( result , " " ) + " ago"
}
}
env . Functions [ "time_elapsed_short" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
diff := time . Since ( time . Unix ( int64 ( toUint64 ( args [ 0 ] ) ) , 0 ) . UTC ( ) )
days := int64 ( diff . Hours ( ) / 24 )
2022-10-11 10:39:16 +00:00
hours := int64 ( diff . Hours ( ) ) % 24
2022-10-10 15:41:51 +00:00
minutes := int64 ( diff . Minutes ( ) ) % 60
seconds := int64 ( diff . Seconds ( ) ) % 60
var result [ ] string
if days > 0 {
result = append ( result , strconv . FormatInt ( days , 10 ) + "d" )
}
if hours > 0 {
result = append ( result , strconv . FormatInt ( hours , 10 ) + "h" )
}
if minutes > 0 {
result = append ( result , strconv . FormatInt ( minutes , 10 ) + "m" )
}
if seconds > 0 {
result = append ( result , strconv . FormatInt ( seconds , 10 ) + "s" )
}
if len ( result ) == 0 {
return "just now"
} else {
return strings . Join ( result [ 0 : 1 ] , " " ) + " ago"
}
}
env . Functions [ "time_duration_long" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
diff := time . Second * time . Duration ( toUint64 ( args [ 0 ] ) )
diff += time . Microsecond * time . Duration ( ( toFloat64 ( toUint64 ( args [ 0 ] ) ) - toFloat64 ( args [ 0 ] ) ) * 1000000 )
days := int64 ( diff . Hours ( ) / 24 )
2022-10-11 10:39:16 +00:00
hours := int64 ( diff . Hours ( ) ) % 24
2022-10-10 15:41:51 +00:00
minutes := int64 ( diff . Minutes ( ) ) % 60
seconds := int64 ( diff . Seconds ( ) ) % 60
ms := int64 ( diff . Milliseconds ( ) ) % 1000
var result [ ] string
if days > 0 {
result = append ( result , strconv . FormatInt ( days , 10 ) + "d" )
}
if hours > 0 {
result = append ( result , strconv . FormatInt ( hours , 10 ) + "h" )
}
if minutes > 0 {
result = append ( result , strconv . FormatInt ( minutes , 10 ) + "m" )
}
if seconds > 0 {
result = append ( result , strconv . FormatInt ( seconds , 10 ) + "s" )
}
if len ( result ) == 0 || ( len ( result ) == 1 && seconds > 0 ) {
result = append ( result , strconv . FormatInt ( ms , 10 ) + "ms" )
}
return strings . Join ( result , " " )
}
env . Functions [ "add_uint" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
var result uint64
for _ , v := range args {
result += toUint64 ( v )
}
return result
}
env . Functions [ "date_diff_short" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
diff := time . Since ( time . Unix ( int64 ( toUint64 ( args [ 0 ] ) ) , 0 ) . UTC ( ) )
s := fmt . Sprintf ( "%02d:%02d:%02d" , int64 ( diff . Hours ( ) ) % 24 , int64 ( diff . Minutes ( ) ) % 60 , int64 ( diff . Seconds ( ) ) % 60 )
days := int64 ( diff . Hours ( ) / 24 )
if days > 0 {
return strconv . FormatInt ( days , 10 ) + ":" + s
}
return s
}
env . Functions [ "attribute" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) != 2 {
return nil
}
if s , ok := args [ 0 ] . ( [ ] any ) ; ok {
return s [ toUint64 ( args [ 1 ] ) ]
} else if m , ok := args [ 0 ] . ( map [ string ] any ) ; ok {
return m [ args [ 1 ] . ( string ) ]
}
return nil
}
env . Filters [ "gmp_init" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return "TODO gmp_init"
}
env . Filters [ "gmp_intval" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return "TODO gmp_intval"
}
env . Filters [ "gmp_div" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return "TODO gmp_div"
}
env . Filters [ "bcdiv" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return "TODO bcdiv"
}
env . Filters [ "slice_sum" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
var result uint64
stick . Iterate ( val , func ( k , v stick . Value , l stick . Loop ) ( brk bool , err error ) {
result += toUint64 ( v )
return false , nil
} )
return result
}
env . Filters [ "diff_div" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
if d , ok := val . ( types . Difficulty ) ; ok {
return d . Div64 ( toUint64 ( args [ 0 ] ) ) . String ( )
} else if d , err := types . DifficultyFromString ( val . ( string ) ) ; err == nil {
return d . Div64 ( toUint64 ( args [ 0 ] ) ) . String ( )
}
log . Panic ( )
return val
}
env . Filters [ "diff_int" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
if d , ok := val . ( types . Difficulty ) ; ok {
return d . Lo
} else if d , err := types . DifficultyFromString ( val . ( string ) ) ; err == nil {
return d . Lo
}
log . Panic ( )
return 0
}
env . Filters [ "benc" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
if s , ok := val . ( string ) ; ok {
if n , err := strconv . ParseUint ( s , 10 , 0 ) ; err == nil {
return utils . EncodeBinaryNumber ( n )
}
} else {
return utils . EncodeBinaryNumber ( toUint64 ( val ) )
}
//TODO: remove this
log . Panic ( )
return ""
}
env . Filters [ "hex" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
if s , ok := val . ( string ) ; ok {
return s
} else if s , ok := val . ( types . Difficulty ) ; ok {
return s . String ( )
} else if s , ok := val . ( types . Hash ) ; ok {
return s . String ( )
} else if s , ok := val . ( types . Nonce ) ; ok {
return hex . EncodeToString ( s [ : ] )
} else if s , ok := val . ( [ ] byte ) ; ok {
return hex . EncodeToString ( s )
}
return val
}
env . Filters [ "henc" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
if h , ok := val . ( types . Hash ) ; ok {
return h . String ( )
//return utils.EncodeHexBinaryNumber(h.String())
} else if s , ok := val . ( string ) ; ok {
return s
//return utils.EncodeHexBinaryNumber(s)
}
//TODO: remove this
log . Panic ( )
return ""
}
env . Filters [ "effort_color" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
if effort , ok := val . ( float64 ) ; ok {
if effort < 100 {
return "#00C000"
} else if effort < 200 {
return "#E0E000"
} else {
return "#FF0000"
}
}
//TODO: remove this
log . Panic ( )
return ""
}
env . Filters [ "time_elapsed_string" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return "TODO time_elapsed_string"
}
env . Filters [ "time_diff_string" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return "TODO time_diff_string"
}
env . Filters [ "time_elapsed_string_short" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return "TODO time_elapsed_string_short"
}
env . Filters [ "si_units" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
v := toFloat64 ( val )
if len ( args ) > 0 {
return utils . SiUnits ( v , int ( toUint64 ( args [ 0 ] ) ) )
} else {
return utils . SiUnits ( v , 3 )
}
}
env . Filters [ "effort_color" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
effort := toFloat64 ( val )
if effort < 100 {
return "#00C000"
} else if effort < 200 {
return "#E0E000"
} else {
return "#FF0000"
}
}
env . Filters [ "shorten" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
var value string
if s , ok := val . ( string ) ; ok {
value = s
} else {
value = fmt . Sprintf ( "%s" , value )
}
n := int ( toUint64 ( args [ 0 ] ) )
if len ( value ) <= n * 2 + 3 {
return value
} else {
return value [ : n ] + "..." + value [ len ( value ) - n : ]
}
}
env . Filters [ "intstr" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
return strconv . FormatUint ( toUint64 ( val ) , 10 )
}
env . Tests [ "defined" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) bool {
return val != nil
}
serveMux := mux . NewRouter ( )
serveMux . HandleFunc ( "/" , func ( writer http . ResponseWriter , request * http . Request ) {
params := request . URL . Query ( )
if params . Has ( "refresh" ) {
writer . Header ( ) . Set ( "refresh" , "120" )
}
poolInfo := getFromAPI ( "pool_info" , 5 )
blocks := getFromAPI ( "found_blocks?coinbase&limit=20" , 5 )
shares := getFromAPI ( "shares?limit=20" , 5 )
ctx := make ( map [ string ] stick . Value )
ctx [ "refresh" ] = writer . Header ( ) . Get ( "refresh" )
ctx [ "blocks_found" ] = blocks
ctx [ "shares" ] = shares
ctx [ "pool" ] = poolInfo
render ( writer , "index.html" , ctx )
} )
serveMux . HandleFunc ( "/api" , func ( writer http . ResponseWriter , request * http . Request ) {
render ( writer , "api.html" , nil )
} )
serveMux . HandleFunc ( "/calculate-share-time" , func ( writer http . ResponseWriter , request * http . Request ) {
poolInfo := getFromAPI ( "pool_info" , 5 )
2022-10-11 10:39:16 +00:00
hashRate := float64 ( 0 )
magnitude := float64 ( 1000 )
2022-10-10 15:41:51 +00:00
params := request . URL . Query ( )
if params . Has ( "hashrate" ) {
2022-10-11 10:39:16 +00:00
hashRate = toFloat64 ( params . Get ( "hashrate" ) )
2022-10-10 15:41:51 +00:00
}
if params . Has ( "magnitude" ) {
2022-10-11 10:39:16 +00:00
magnitude = toFloat64 ( params . Get ( "magnitude" ) )
2022-10-10 15:41:51 +00:00
}
ctx := make ( map [ string ] stick . Value )
ctx [ "hashrate" ] = hashRate
ctx [ "magnitude" ] = magnitude
ctx [ "pool" ] = poolInfo
render ( writer , "calculate-share-time.html" , ctx )
} )
serveMux . HandleFunc ( "/blocks" , func ( writer http . ResponseWriter , request * http . Request ) {
params := request . URL . Query ( )
if params . Has ( "refresh" ) {
writer . Header ( ) . Set ( "refresh" , "600" )
}
2022-10-11 10:39:16 +00:00
var miner map [ string ] any
if params . Has ( "miner" ) {
m := getFromAPI ( fmt . Sprintf ( "miner_info/%s" , params . Get ( "miner" ) ) )
if m == nil || m . ( map [ string ] any ) [ "address" ] == nil {
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
error [ "message" ] = "Address Not Found"
error [ "content" ] = "<div class=\"center\" style=\"text-align: center\">You need to have mined at least one share in the past. Come back later :)</div>"
render ( writer , "error.html" , ctx )
return
}
miner = m . ( map [ string ] any )
}
2022-10-10 15:41:51 +00:00
poolInfo := getFromAPI ( "pool_info" , 5 )
ctx := make ( map [ string ] stick . Value )
ctx [ "refresh" ] = writer . Header ( ) . Get ( "refresh" )
ctx [ "pool" ] = poolInfo
2022-10-11 10:39:16 +00:00
if miner != nil {
blocks := getFromAPI ( fmt . Sprintf ( "found_blocks?&limit=100&miner=%d&coinbase" , toUint64 ( miner [ "id" ] ) ) )
ctx [ "blocks_found" ] = blocks
ctx [ "miner" ] = miner
render ( writer , "blocks_miner.html" , ctx )
} else {
blocks := getFromAPI ( "found_blocks?limit=100&coinbase" , 30 )
ctx [ "blocks_found" ] = blocks
render ( writer , "blocks.html" , ctx )
}
2022-10-10 15:41:51 +00:00
} )
serveMux . HandleFunc ( "/miners" , func ( writer http . ResponseWriter , request * http . Request ) {
params := request . URL . Query ( )
if params . Has ( "refresh" ) {
writer . Header ( ) . Set ( "refresh" , "600" )
}
poolInfo := getFromAPI ( "pool_info" , 5 )
shares := getFromAPI ( fmt . Sprintf ( "shares?limit=%d&onlyBlocks" , p2pool . PPLNSWindow ) , 30 ) . ( [ ] any )
miners := make ( map [ string ] map [ string ] any , 0 )
wsize := uint64 ( p2pool . PPLNSWindow )
count := uint64 ( 30 )
tipHeight := toUint64 ( poolInfo . ( map [ string ] any ) [ "sidechain" ] . ( map [ string ] any ) [ "height" ] )
wend := tipHeight - p2pool . PPLNSWindow
tip := shares [ 0 ] . ( map [ string ] any )
for _ , s := range shares {
share := s . ( map [ string ] any )
miner := share [ "miner" ] . ( string )
if _ , ok := miners [ miner ] ; ! ok {
miners [ miner ] = make ( map [ string ] any )
miners [ miner ] [ "weight" ] = types . Difficulty { }
miners [ miner ] [ "shares" ] = make ( [ ] uint64 , count )
miners [ miner ] [ "uncles" ] = make ( [ ] uint64 , count )
}
index := ( toUint64 ( tip [ "height" ] ) - toUint64 ( share [ "height" ] ) ) / ( ( wsize + count - 1 ) / count )
miners [ miner ] [ "shares" ] . ( [ ] uint64 ) [ index ] ++
diff := toUint64 ( share [ "weight" ] )
miners [ miner ] [ "weight" ] = types . Difficulty { Uint128 : miners [ miner ] [ "weight" ] . ( types . Difficulty ) . Add64 ( diff ) }
if _ , ok := share [ "uncles" ] ; ok {
for _ , u := range share [ "uncles" ] . ( [ ] any ) {
uncle := u . ( map [ string ] any )
if toUint64 ( uncle [ "height" ] ) <= wend {
continue
}
miner := uncle [ "miner" ] . ( string )
if _ , ok := miners [ miner ] ; ! ok {
miners [ miner ] = make ( map [ string ] any )
miners [ miner ] [ "weight" ] = types . Difficulty { }
miners [ miner ] [ "shares" ] = make ( [ ] uint64 , count )
miners [ miner ] [ "uncles" ] = make ( [ ] uint64 , count )
}
index := ( toUint64 ( tip [ "height" ] ) - toUint64 ( uncle [ "height" ] ) ) / ( ( wsize + count - 1 ) / count )
miners [ miner ] [ "uncles" ] . ( [ ] uint64 ) [ index ] ++
diff := toUint64 ( uncle [ "weight" ] )
miners [ miner ] [ "weight" ] = types . Difficulty { Uint128 : miners [ miner ] [ "weight" ] . ( types . Difficulty ) . Add64 ( diff ) }
}
}
}
minerKeys := maps . Keys ( miners )
slices . SortFunc ( minerKeys , func ( a string , b string ) bool {
return miners [ a ] [ "weight" ] . ( types . Difficulty ) . Cmp ( miners [ b ] [ "weight" ] . ( types . Difficulty ) . Uint128 ) > 0
} )
sortedMiners := make ( mapslice . MapSlice , len ( minerKeys ) )
for i , k := range minerKeys {
miner := miners [ k ]
minerShares := miner [ "shares" ] . ( [ ] uint64 )
sharesPosition := make ( [ ] byte , 2 * 2 + len ( minerShares ) )
sharesPosition [ 0 ] , sharesPosition [ 1 ] = '[' , '<'
sharesPosition [ len ( sharesPosition ) - 2 ] , sharesPosition [ len ( sharesPosition ) - 1 ] = '<' , ']'
for i , p := range utils . ReverseSlice ( slices . Clone ( minerShares ) ) {
if p > 0 {
if p > 9 {
sharesPosition [ 2 + i ] = '+'
} else {
sharesPosition [ 2 + i ] = 0x30 + byte ( p )
}
} else {
sharesPosition [ 2 + i ] = '.'
}
}
minerUncles := miner [ "uncles" ] . ( [ ] uint64 )
unclesPosition := make ( [ ] byte , 2 * 2 + len ( minerUncles ) )
unclesPosition [ 0 ] , unclesPosition [ 1 ] = '[' , '<'
unclesPosition [ len ( unclesPosition ) - 2 ] , unclesPosition [ len ( unclesPosition ) - 1 ] = '<' , ']'
for i , p := range utils . ReverseSlice ( slices . Clone ( minerUncles ) ) {
if p > 0 {
if p > 9 {
unclesPosition [ 2 + i ] = '+'
} else {
unclesPosition [ 2 + i ] = 0x30 + byte ( p )
}
} else {
unclesPosition [ 2 + i ] = '.'
}
}
miners [ k ] [ "shares_position" ] = string ( sharesPosition )
miners [ k ] [ "uncles_position" ] = string ( unclesPosition )
sortedMiners [ i ] . Key = k
sortedMiners [ i ] . Value = miners [ k ]
}
ctx := make ( map [ string ] stick . Value )
ctx [ "refresh" ] = writer . Header ( ) . Get ( "refresh" )
ctx [ "miners" ] = sortedMiners
ctx [ "tip" ] = tip
ctx [ "pool" ] = poolInfo
render ( writer , "miners.html" , ctx )
} )
serveMux . HandleFunc ( "/share/{block:[0-9a-f]+|[0-9]+}" , func ( writer http . ResponseWriter , request * http . Request ) {
identifier := mux . Vars ( request ) [ "block" ]
var block any
var rawBlock any
if len ( identifier ) == 64 {
block = getFromAPI ( fmt . Sprintf ( "block_by_id/%s?coinbase" , identifier ) )
rawBlock = getFromAPI ( fmt . Sprintf ( "block_by_id/%s/raw" , identifier ) )
} else {
block = getFromAPI ( fmt . Sprintf ( "block_by_height/%s?coinbase" , identifier ) )
rawBlock = getFromAPI ( fmt . Sprintf ( "block_by_height/%s/raw" , identifier ) )
}
if block == nil {
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
error [ "message" ] = "Block Not Found"
render ( writer , "error.html" , ctx )
return
}
var raw * block2 . Block
if s , ok := rawBlock . ( [ ] byte ) ; ok && rawBlock != nil {
if buf , err := hex . DecodeString ( string ( s ) ) ; err == nil {
raw , _ = block2 . NewBlockFromBytes ( buf )
}
}
poolInfo := getFromAPI ( "pool_info" , 5 )
ctx := make ( map [ string ] stick . Value )
ctx [ "block" ] = block
ctx [ "raw" ] = raw
ctx [ "pool" ] = poolInfo
render ( writer , "share.html" , ctx )
} )
serveMux . HandleFunc ( "/miner/{miner:[0-9]+|4[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+}" , func ( writer http . ResponseWriter , request * http . Request ) {
params := request . URL . Query ( )
if params . Has ( "refresh" ) {
writer . Header ( ) . Set ( "refresh" , "300" )
}
address := mux . Vars ( request ) [ "miner" ]
m := getFromAPI ( fmt . Sprintf ( "miner_info/%s" , address ) )
2022-10-10 16:22:35 +00:00
if m == nil || m . ( map [ string ] any ) [ "address" ] == nil {
2022-10-10 15:41:51 +00:00
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
error [ "message" ] = "Address Not Found"
error [ "content" ] = "<div class=\"center\" style=\"text-align: center\">You need to have mined at least one share in the past. Come back later :)</div>"
render ( writer , "error.html" , ctx )
return
}
2022-10-10 16:22:35 +00:00
miner := m . ( map [ string ] any )
2022-10-10 15:41:51 +00:00
poolInfo := getFromAPI ( "pool_info" , 5 ) . ( map [ string ] any )
const totalWindows = 4
wsize := uint64 ( p2pool . PPLNSWindow * totalWindows )
tipHeight := toUint64 ( poolInfo [ "sidechain" ] . ( map [ string ] any ) [ "height" ] )
shares := getFromAPI ( fmt . Sprintf ( "shares_in_window/%d?from=%d&window=%d" , toUint64 ( miner [ "id" ] ) , tipHeight , wsize ) ) . ( [ ] any )
payouts := getFromAPI ( fmt . Sprintf ( "payouts/%d?search_limit=10" , toUint64 ( miner [ "id" ] ) ) ) . ( [ ] any )
lastShares := getFromAPI ( fmt . Sprintf ( "shares?limit=50&miner=%d" , toUint64 ( miner [ "id" ] ) ) ) . ( [ ] any )
2022-10-11 10:39:16 +00:00
lastFound := getFromAPI ( fmt . Sprintf ( "found_blocks?limit=5&miner=%d&coinbase" , toUint64 ( miner [ "id" ] ) ) ) . ( [ ] any )
2022-10-10 15:41:51 +00:00
count := uint64 ( 30 * totalWindows )
blocksFound := make ( [ ] uint64 , count )
unclesFound := make ( [ ] uint64 , count )
var sharesInWindow , unclesInWindow uint64
var longDiff , windowDiff types . Difficulty
wend := tipHeight - p2pool . PPLNSWindow
for _ , share := range shares {
s := share . ( map [ string ] any )
if p , ok := s [ "parent" ] ; ok {
parent := p . ( map [ string ] any )
index := ( tipHeight - toUint64 ( parent [ "height" ] ) ) / ( ( wsize + count - 1 ) / count )
unclesFound [ utils . Min ( index , count - 1 ) ] ++
if toUint64 ( s [ "height" ] ) > wend {
unclesInWindow ++
windowDiff . Uint128 = windowDiff . Add64 ( toUint64 ( s [ "weight" ] ) )
}
} else {
index := ( tipHeight - toUint64 ( s [ "height" ] ) ) / ( ( wsize + count - 1 ) / count )
blocksFound [ utils . Min ( index , count - 1 ) ] ++
if toUint64 ( s [ "height" ] ) > wend {
sharesInWindow ++
windowDiff . Uint128 = windowDiff . Add64 ( toUint64 ( s [ "weight" ] ) )
}
}
longDiff . Uint128 = longDiff . Add64 ( toUint64 ( s [ "weight" ] ) )
}
const separatorOffset = 30 * ( totalWindows - 1 )
sharesPosition := make ( [ ] byte , 1 + 2 * 2 + len ( blocksFound ) )
sharesPosition [ 0 ] , sharesPosition [ 1 ] = '[' , '<'
sharesPosition [ 2 + separatorOffset ] = '|'
sharesPosition [ len ( sharesPosition ) - 2 ] , sharesPosition [ len ( sharesPosition ) - 1 ] = '<' , ']'
for i , p := range utils . ReverseSlice ( slices . Clone ( blocksFound ) ) {
if i >= separatorOffset {
i ++
}
if p > 0 {
if p > 9 {
sharesPosition [ 2 + i ] = '+'
} else {
sharesPosition [ 2 + i ] = 0x30 + byte ( p )
}
} else {
sharesPosition [ 2 + i ] = '.'
}
}
unclesPosition := make ( [ ] byte , 1 + 2 * 2 + len ( unclesFound ) )
unclesPosition [ 0 ] , unclesPosition [ 1 ] = '[' , '<'
unclesPosition [ 2 + separatorOffset ] = '|'
unclesPosition [ len ( unclesPosition ) - 2 ] , unclesPosition [ len ( unclesPosition ) - 1 ] = '<' , ']'
for i , p := range utils . ReverseSlice ( slices . Clone ( unclesFound ) ) {
if i >= separatorOffset {
i ++
}
if p > 0 {
if p > 9 {
unclesPosition [ 2 + i ] = '+'
} else {
unclesPosition [ 2 + i ] = 0x30 + byte ( p )
}
} else {
unclesPosition [ 2 + i ] = '.'
}
}
ctx := make ( map [ string ] stick . Value )
ctx [ "refresh" ] = writer . Header ( ) . Get ( "refresh" )
ctx [ "pool" ] = poolInfo
ctx [ "miner" ] = miner
ctx [ "last_shares" ] = lastShares
2022-10-11 10:39:16 +00:00
ctx [ "last_found" ] = lastFound
2022-10-10 15:41:51 +00:00
ctx [ "last_payouts" ] = payouts
ctx [ "window_weight" ] = windowDiff . Lo
ctx [ "weight" ] = longDiff . Lo
ctx [ "window_count_blocks" ] = sharesInWindow
ctx [ "window_count_uncles" ] = unclesInWindow
ctx [ "count_blocks" ] = func ( ) ( result uint64 ) {
for _ , n := range blocksFound {
result += n
}
return
} ( )
ctx [ "count_uncles" ] = func ( ) ( result uint64 ) {
for _ , n := range unclesFound {
result += n
}
return
} ( )
ctx [ "position_blocks" ] = string ( sharesPosition )
ctx [ "position_uncles" ] = string ( unclesPosition )
render ( writer , "miner.html" , ctx )
} )
2022-10-10 16:13:01 +00:00
serveMux . HandleFunc ( "/miner" , func ( writer http . ResponseWriter , request * http . Request ) {
params := request . URL . Query ( )
if params . Get ( "address" ) == "" {
http . Redirect ( writer , request , "/" , http . StatusMovedPermanently )
return
}
http . Redirect ( writer , request , fmt . Sprintf ( "/miner/%s" , params . Get ( "address" ) ) , http . StatusMovedPermanently )
} )
2022-10-10 15:41:51 +00:00
serveMux . HandleFunc ( "/payouts/{miner:[0-9]+|4[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+}" , func ( writer http . ResponseWriter , request * http . Request ) {
params := request . URL . Query ( )
if params . Has ( "refresh" ) {
writer . Header ( ) . Set ( "refresh" , "600" )
}
address := mux . Vars ( request ) [ "miner" ]
if params . Has ( "address" ) {
address = params . Get ( "address" )
}
m := getFromAPI ( fmt . Sprintf ( "miner_info/%s" , address ) )
if m == nil {
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
error [ "message" ] = "Address Not Found"
error [ "content" ] = "<div class=\"center\" style=\"text-align: center\">You need to have mined at least one share in the past. Come back later :)</div>"
render ( writer , "error.html" , ctx )
return
}
2022-10-10 16:22:35 +00:00
miner := m . ( map [ string ] any )
2022-10-10 15:41:51 +00:00
payouts := getFromAPI ( fmt . Sprintf ( "payouts/%d?search_limit=0" , toUint64 ( miner [ "id" ] ) ) ) . ( [ ] any )
if len ( payouts ) == 0 {
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
error [ "message" ] = "No payout for address found"
error [ "content" ] = "<div class=\"center\" style=\"text-align: center\">You need to have mined at least one share in the past, and a main block found during that period. Come back later :)</div>"
render ( writer , "error.html" , ctx )
return
}
ctx := make ( map [ string ] stick . Value )
ctx [ "refresh" ] = writer . Header ( ) . Get ( "refresh" )
ctx [ "miner" ] = miner
ctx [ "payouts" ] = payouts
ctx [ "total" ] = func ( ) ( result uint64 ) {
for _ , p := range payouts {
result += toUint64 ( p . ( map [ string ] any ) [ "coinbase" ] . ( map [ string ] any ) [ "reward" ] )
}
return
} ( )
render ( writer , "payouts.html" , ctx )
} )
server := & http . Server {
Addr : "0.0.0.0:8444" ,
ReadTimeout : time . Second * 2 ,
Handler : http . HandlerFunc ( func ( writer http . ResponseWriter , request * http . Request ) {
if request . Method != "GET" && request . Method != "HEAD" {
writer . WriteHeader ( http . StatusForbidden )
return
}
writer . Header ( ) . Set ( "content-type" , "text/html; charset=utf-8" )
serveMux . ServeHTTP ( writer , request )
} ) ,
}
if err := server . ListenAndServe ( ) ; err != nil {
log . Panic ( err )
}
}