2022-10-10 15:41:51 +00:00
package main
import (
"bytes"
2022-10-27 08:09:39 +00:00
"encoding/binary"
2022-10-10 15:41:51 +00:00
"encoding/hex"
"encoding/json"
"fmt"
2022-10-12 10:36:41 +00:00
address2 "git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
2022-11-05 05:29:12 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/crypto"
2022-10-10 15:41:51 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool"
2022-10-26 16:46:41 +00:00
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
2022-10-10 15:41:51 +00:00
"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"
2022-10-12 12:15:47 +00:00
"math"
2022-10-10 15:41:51 +00:00
"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
}
2022-10-12 12:15:47 +00:00
func toInt64 ( t any ) int64 {
if x , ok := t . ( json . Number ) ; ok {
if n , err := x . Int64 ( ) ; err == nil {
return n
}
}
if x , ok := t . ( uint64 ) ; ok {
return int64 ( x )
} else if x , ok := t . ( int64 ) ; ok {
return x
} else if x , ok := t . ( uint ) ; ok {
return int64 ( x )
} else if x , ok := t . ( int ) ; ok {
return int64 ( x )
} else if x , ok := t . ( float64 ) ; ok {
return int64 ( x )
} else if x , ok := t . ( float32 ) ; ok {
return int64 ( x )
} else if x , ok := t . ( string ) ; ok {
if n , err := strconv . ParseInt ( x , 10 , 0 ) ; err == nil {
return n
}
}
return 0
}
2022-10-10 15:41:51 +00:00
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" )
}
2022-10-12 10:36:41 +00:00
if toUint64 ( args [ 0 ] ) == 0 {
return "never"
} else if len ( result ) == 0 {
2022-10-10 15:41:51 +00:00
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" )
}
2022-10-12 10:36:41 +00:00
if toUint64 ( args [ 0 ] ) == 0 {
return "never"
} else if len ( result ) == 0 {
2022-10-10 15:41:51 +00:00
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
}
2022-10-24 12:36:50 +00:00
env . Functions [ "sub_int" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
result := toInt64 ( args [ 0 ] )
for _ , v := range args [ 1 : ] {
result -= toInt64 ( v )
}
return result
}
2022-10-10 15:41:51 +00:00
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
}
2022-10-24 12:36:50 +00:00
env . Functions [ "prove_output_number" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) != 2 {
return nil
}
n := uint64 ( math . Ceil ( math . Log2 ( p2pool . PPLNSWindow * 4 ) ) )
//height | index
return ( toUint64 ( args [ 0 ] ) << n ) | toUint64 ( args [ 1 ] )
}
env . Functions [ "get_tx_proof" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) != 3 {
return nil
}
h , _ := types . HashFromString ( args [ 1 ] . ( string ) )
k , _ := types . HashFromString ( args [ 2 ] . ( string ) )
2022-11-05 05:29:12 +00:00
keyBytes := crypto . PrivateKeyBytes ( k )
return address2 . GetTxProofV2 ( address2 . FromBase58 ( args [ 0 ] . ( string ) ) , h , & keyBytes , "" )
2022-10-24 12:36:50 +00:00
}
env . Functions [ "get_tx_proof_v1" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) != 3 {
return nil
}
h , _ := types . HashFromString ( args [ 1 ] . ( string ) )
k , _ := types . HashFromString ( args [ 2 ] . ( string ) )
2022-11-05 05:29:12 +00:00
keyBytes := crypto . PrivateKeyBytes ( k )
return address2 . GetTxProofV1 ( address2 . FromBase58 ( args [ 0 ] . ( string ) ) , h , & keyBytes , "" )
2022-10-24 12:36:50 +00:00
}
env . Functions [ "get_ephemeral_pubkey" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) != 3 {
return nil
}
k , _ := types . HashFromString ( args [ 1 ] . ( string ) )
2022-11-05 05:29:12 +00:00
keyBytes := crypto . PrivateKeyBytes ( k )
return address2 . GetEphemeralPublicKey ( address2 . FromBase58 ( args [ 0 ] . ( string ) ) , & keyBytes , toUint64 ( args [ 2 ] ) )
}
env . Functions [ "coinbase_extra" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) != 1 {
return nil
}
b , _ := args [ 0 ] . ( * sidechain . PoolBlock ) . Main . Coinbase . Extra . MarshalBinary ( )
return b
}
env . Functions [ "extra_nonce" ] = func ( ctx stick . Context , args ... stick . Value ) stick . Value {
if len ( args ) != 1 {
return nil
}
return args [ 0 ] . ( * sidechain . PoolBlock ) . CoinbaseExtra ( sidechain . SideExtraNonce )
2022-10-24 12:36:50 +00:00
}
2022-10-10 15:41:51 +00:00
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 [ "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 ( )
2022-11-05 05:29:12 +00:00
} else if s , ok := val . ( crypto . PrivateKey ) ; ok {
return s . String ( )
} else if s , ok := val . ( crypto . PublicKey ) ; ok {
return s . String ( )
} else if s , ok := val . ( crypto . PrivateKeyBytes ) ; ok {
return s . String ( )
} else if s , ok := val . ( crypto . PublicKeyBytes ) ; ok {
return s . String ( )
2022-10-10 15:41:51 +00:00
} else if s , ok := val . ( types . Hash ) ; ok {
return s . String ( )
} else if s , ok := val . ( [ ] byte ) ; ok {
return hex . EncodeToString ( s )
2022-10-27 08:09:39 +00:00
} else if s , ok := val . ( uint32 ) ; ok {
var buf [ 4 ] byte
binary . BigEndian . PutUint32 ( buf [ : ] , s )
return hex . EncodeToString ( buf [ : ] )
} else if s , ok := val . ( uint64 ) ; ok {
var buf [ 8 ] byte
binary . BigEndian . PutUint64 ( buf [ : ] , s )
return hex . EncodeToString ( buf [ : ] )
2022-10-10 15:41:51 +00:00
}
return val
}
env . Filters [ "henc" ] = func ( ctx stick . Context , val stick . Value , args ... stick . Value ) stick . Value {
if h , ok := val . ( types . Hash ) ; ok {
2022-10-24 12:36:50 +00:00
return utils . EncodeHexBinaryNumber ( h . String ( ) )
2022-10-10 15:41:51 +00:00
} else if s , ok := val . ( string ) ; ok {
2022-10-24 12:36:50 +00:00
return utils . EncodeHexBinaryNumber ( s )
2022-10-10 15:41:51 +00:00
}
//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"
}
}
2022-10-12 10:36:41 +00:00
return "#000000"
2022-10-10 15:41:51 +00:00
}
2022-10-12 10:36:41 +00:00
2022-10-10 15:41:51 +00:00
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" )
}
2022-10-12 12:15:47 +00:00
poolInfo := getFromAPI ( "pool_info" , 5 ) . ( map [ string ] any )
d1 , _ := types . DifficultyFromString ( poolInfo [ "mainchain" ] . ( map [ string ] any ) [ "difficulty" ] . ( string ) )
d2 , _ := types . DifficultyFromString ( poolInfo [ "sidechain" ] . ( map [ string ] any ) [ "difficulty" ] . ( string ) )
secondsPerBlock := float64 ( d1 . Lo ) / float64 ( d2 . Div64 ( toUint64 ( poolInfo [ "sidechain" ] . ( map [ string ] any ) [ "block_time" ] ) ) . Lo )
blocksToFetch := uint64 ( math . Ceil ( ( ( ( time . Hour * 24 ) . Seconds ( ) / secondsPerBlock ) * 2 ) / 100 ) * 100 )
blocks := getFromAPI ( fmt . Sprintf ( "found_blocks?coinbase&limit=%d" , blocksToFetch ) , 5 ) . ( [ ] any )
shares := getFromAPI ( "shares?limit=20" , 5 ) . ( [ ] any )
2022-10-10 15:41:51 +00:00
ctx := make ( map [ string ] stick . Value )
ctx [ "refresh" ] = writer . Header ( ) . Get ( "refresh" )
2022-10-12 12:15:47 +00:00
blocksFound := NewPositionChart ( 30 * 4 , p2pool . PPLNSWindow * 4 )
tip := toInt64 ( poolInfo [ "sidechain" ] . ( map [ string ] any ) [ "height" ] )
for _ , b := range blocks {
blocksFound . Add ( int ( tip - toInt64 ( b . ( map [ string ] any ) [ "height" ] ) ) , 1 )
}
if len ( blocks ) > 0 {
blocks = blocks [ : 20 ]
}
2022-10-10 15:41:51 +00:00
ctx [ "blocks_found" ] = blocks
2022-10-12 12:15:47 +00:00
ctx [ "blocks_found_position" ] = blocksFound . String ( )
2022-10-10 15:41:51 +00:00
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-12 12:15:47 +00:00
poolInfo := getFromAPI ( "pool_info" , 5 ) . ( map [ string ] any )
2022-10-10 15:41:51 +00:00
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" )
}
2022-10-12 12:15:47 +00:00
windowCount := uint64 ( 1 )
size := uint64 ( 30 )
cacheTime := 30
2022-10-12 12:30:23 +00:00
if params . Has ( "weekly" ) {
2022-10-12 12:15:47 +00:00
windowCount = 4 * 7
size *= 2
if params . Has ( "refresh" ) {
writer . Header ( ) . Set ( "refresh" , "3600" )
}
cacheTime = 60
}
2022-10-10 15:41:51 +00:00
poolInfo := getFromAPI ( "pool_info" , 5 )
2022-10-12 12:15:47 +00:00
shares := getFromAPI ( fmt . Sprintf ( "shares?limit=%d&onlyBlocks" , p2pool . PPLNSWindow * windowCount ) , cacheTime ) . ( [ ] any )
2022-10-10 15:41:51 +00:00
miners := make ( map [ string ] map [ string ] any , 0 )
tipHeight := toUint64 ( poolInfo . ( map [ string ] any ) [ "sidechain" ] . ( map [ string ] any ) [ "height" ] )
2022-10-12 12:15:47 +00:00
wend := tipHeight - p2pool . PPLNSWindow * windowCount
2022-10-10 15:41:51 +00:00
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 )
2022-12-12 16:38:50 +00:00
miners [ miner ] [ "weight" ] = types . ZeroDifficulty
2022-10-12 12:15:47 +00:00
miners [ miner ] [ "shares" ] = NewPositionChart ( size , p2pool . PPLNSWindow * windowCount )
miners [ miner ] [ "uncles" ] = NewPositionChart ( size , p2pool . PPLNSWindow * windowCount )
2022-10-19 08:37:38 +00:00
if a , ok := share [ "miner_alias" ] ; ok {
miners [ miner ] [ "alias" ] = a
}
2022-10-10 15:41:51 +00:00
}
2022-10-12 12:15:47 +00:00
miners [ miner ] [ "shares" ] . ( * PositionChart ) . Add ( int ( toInt64 ( tip [ "height" ] ) - toInt64 ( share [ "height" ] ) ) , 1 )
2022-10-10 15:41:51 +00:00
diff := toUint64 ( share [ "weight" ] )
2022-10-28 09:47:14 +00:00
miners [ miner ] [ "weight" ] = miners [ miner ] [ "weight" ] . ( types . Difficulty ) . Add64 ( diff )
2022-10-10 15:41:51 +00:00
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 )
2022-12-12 16:38:50 +00:00
miners [ miner ] [ "weight" ] = types . ZeroDifficulty
2022-10-12 12:15:47 +00:00
miners [ miner ] [ "shares" ] = NewPositionChart ( size , p2pool . PPLNSWindow * windowCount )
miners [ miner ] [ "uncles" ] = NewPositionChart ( size , p2pool . PPLNSWindow * windowCount )
2022-10-19 08:37:38 +00:00
if a , ok := uncle [ "miner_alias" ] ; ok {
miners [ miner ] [ "alias" ] = a
}
2022-10-10 15:41:51 +00:00
}
2022-10-12 12:15:47 +00:00
miners [ miner ] [ "uncles" ] . ( * PositionChart ) . Add ( int ( toInt64 ( tip [ "height" ] ) - toInt64 ( uncle [ "height" ] ) ) , 1 )
2022-10-10 15:41:51 +00:00
diff := toUint64 ( uncle [ "weight" ] )
2022-10-28 09:47:14 +00:00
miners [ miner ] [ "weight" ] = miners [ miner ] [ "weight" ] . ( types . Difficulty ) . Add64 ( diff )
2022-10-10 15:41:51 +00:00
}
}
}
minerKeys := maps . Keys ( miners )
slices . SortFunc ( minerKeys , func ( a string , b string ) bool {
2022-10-28 09:47:14 +00:00
return miners [ a ] [ "weight" ] . ( types . Difficulty ) . Cmp ( miners [ b ] [ "weight" ] . ( types . Difficulty ) ) > 0
2022-10-10 15:41:51 +00:00
} )
sortedMiners := make ( mapslice . MapSlice , len ( minerKeys ) )
for i , k := range minerKeys {
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
2022-10-12 12:30:23 +00:00
if params . Has ( "weekly" ) {
2022-10-12 12:15:47 +00:00
render ( writer , "miners_week.html" , ctx )
} else {
render ( writer , "miners.html" , ctx )
}
2022-10-10 15:41:51 +00:00
} )
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
2022-10-26 16:46:41 +00:00
error [ "message" ] = "Share Not Found"
2022-10-10 15:41:51 +00:00
render ( writer , "error.html" , ctx )
return
}
2022-11-01 11:22:00 +00:00
var raw * sidechain . PoolBlock
2022-10-10 15:41:51 +00:00
if s , ok := rawBlock . ( [ ] byte ) ; ok && rawBlock != nil {
if buf , err := hex . DecodeString ( string ( s ) ) ; err == nil {
2023-03-07 17:57:06 +00:00
raw , _ = sidechain . NewShareFromExportedBytes ( buf , sidechain . NetworkMainnet )
2022-10-10 15:41:51 +00:00
}
}
2022-10-18 09:58:09 +00:00
payouts := getFromAPI ( fmt . Sprintf ( "block_by_id/%s/payouts" , block . ( map [ string ] any ) [ "id" ] . ( string ) ) )
2022-10-10 15:41:51 +00:00
poolInfo := getFromAPI ( "pool_info" , 5 )
ctx := make ( map [ string ] stick . Value )
ctx [ "block" ] = block
ctx [ "raw" ] = raw
ctx [ "pool" ] = poolInfo
2022-10-18 09:58:09 +00:00
ctx [ "payouts" ] = payouts
2022-10-10 15:41:51 +00:00
render ( writer , "share.html" , ctx )
} )
2022-10-19 09:03:46 +00:00
serveMux . HandleFunc ( "/miner/{miner:[^ ]+}" , func ( writer http . ResponseWriter , request * http . Request ) {
2022-10-10 15:41:51 +00:00
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-12 10:36:41 +00:00
if addr := address2 . FromBase58 ( address ) ; addr != nil {
miner := make ( map [ string ] any )
m = miner
miner [ "id" ] = uint64 ( 0 )
miner [ "address" ] = addr . ToBase58 ( )
shares := make ( map [ string ] any )
miner [ "shares" ] = shares
shares [ "blocks" ] = uint64 ( 0 )
shares [ "uncles" ] = uint64 ( 0 )
miner [ "last_share_height" ] = uint64 ( 0 )
miner [ "last_share_timestamp" ] = uint64 ( 0 )
} else {
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
error [ "message" ] = "Invalid Address"
render ( writer , "error.html" , ctx )
return
}
2022-10-10 15:41:51 +00:00
}
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" ] )
2022-10-12 10:36:41 +00:00
var shares , payouts , lastShares , lastFound [ ] any
if toUint64 ( miner [ "id" ] ) != 0 {
shares = getFromAPI ( fmt . Sprintf ( "shares_in_window/%d?from=%d&window=%d" , toUint64 ( miner [ "id" ] ) , tipHeight , wsize ) ) . ( [ ] any )
2023-01-15 19:20:51 +00:00
payouts = getFromAPI ( fmt . Sprintf ( "payouts/%d?search_limit=1000" , toUint64 ( miner [ "id" ] ) ) ) . ( [ ] any )
2022-10-12 10:36:41 +00:00
lastShares = getFromAPI ( fmt . Sprintf ( "shares?limit=50&miner=%d" , toUint64 ( miner [ "id" ] ) ) ) . ( [ ] any )
lastFound = getFromAPI ( fmt . Sprintf ( "found_blocks?limit=5&miner=%d&coinbase" , toUint64 ( miner [ "id" ] ) ) ) . ( [ ] any )
}
2022-10-10 15:41:51 +00:00
2022-10-12 12:15:47 +00:00
sharesFound := NewPositionChart ( 30 * totalWindows , p2pool . PPLNSWindow * totalWindows )
unclesFound := NewPositionChart ( 30 * totalWindows , p2pool . PPLNSWindow * totalWindows )
2022-10-10 15:41:51 +00:00
var sharesInWindow , unclesInWindow uint64
var longDiff , windowDiff types . Difficulty
wend := tipHeight - p2pool . PPLNSWindow
2022-10-12 12:15:47 +00:00
foundPayout := NewPositionChart ( 30 * totalWindows , p2pool . PPLNSWindow * totalWindows )
for _ , p := range payouts {
foundPayout . Add ( int ( int64 ( tipHeight ) - toInt64 ( p . ( map [ string ] any ) [ "height" ] ) ) , 1 )
}
2022-10-10 15:41:51 +00:00
for _ , share := range shares {
s := share . ( map [ string ] any )
if p , ok := s [ "parent" ] ; ok {
parent := p . ( map [ string ] any )
2022-10-12 12:15:47 +00:00
unclesFound . Add ( int ( int64 ( tipHeight ) - toInt64 ( parent [ "height" ] ) ) , 1 )
2022-10-10 15:41:51 +00:00
if toUint64 ( s [ "height" ] ) > wend {
unclesInWindow ++
2022-10-28 09:47:14 +00:00
windowDiff = windowDiff . Add64 ( toUint64 ( s [ "weight" ] ) )
2022-10-10 15:41:51 +00:00
}
} else {
2022-10-12 12:15:47 +00:00
sharesFound . Add ( int ( int64 ( tipHeight ) - toInt64 ( s [ "height" ] ) ) , 1 )
2022-10-10 15:41:51 +00:00
if toUint64 ( s [ "height" ] ) > wend {
sharesInWindow ++
2022-10-28 09:47:14 +00:00
windowDiff = windowDiff . Add64 ( toUint64 ( s [ "weight" ] ) )
2022-10-10 15:41:51 +00:00
}
}
2022-10-28 09:47:14 +00:00
longDiff = longDiff . Add64 ( toUint64 ( s [ "weight" ] ) )
2022-10-10 15:41:51 +00:00
}
2022-10-12 12:15:47 +00:00
if len ( payouts ) > 10 {
payouts = payouts [ : 10 ]
2022-10-10 15:41:51 +00:00
}
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
2022-10-12 12:15:47 +00:00
ctx [ "count_blocks" ] = sharesFound . Total ( )
ctx [ "count_uncles" ] = unclesFound . Total ( )
ctx [ "count_payouts" ] = foundPayout . Total ( )
ctx [ "position_resolution" ] = foundPayout . Resolution ( )
ctx [ "position_blocks" ] = sharesFound . StringWithSeparator ( p2pool . PPLNSWindow * ( totalWindows - 1 ) )
ctx [ "position_uncles" ] = unclesFound . StringWithSeparator ( p2pool . PPLNSWindow * ( totalWindows - 1 ) )
ctx [ "position_payouts" ] = foundPayout . StringWithSeparator ( p2pool . PPLNSWindow * ( totalWindows - 1 ) )
2022-10-10 15:41:51 +00:00
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-24 12:36:50 +00:00
serveMux . HandleFunc ( "/proof/{block:[0-9a-f]+|[0-9]+}/{index:[0-9]+}" , func ( writer http . ResponseWriter , request * http . Request ) {
identifier := utils . DecodeHexBinaryNumber ( mux . Vars ( request ) [ "block" ] )
index := toUint64 ( mux . Vars ( request ) [ "index" ] )
block := getFromAPI ( fmt . Sprintf ( "block_by_id/%s?coinbase" , identifier ) ) . ( map [ string ] any )
if block == nil || block [ "main" ] . ( map [ string ] any ) [ "found" ] == false {
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
2022-10-26 16:46:41 +00:00
error [ "message" ] = "Share Was Not Found"
2022-10-24 12:36:50 +00:00
render ( writer , "error.html" , ctx )
return
}
payouts := block [ "coinbase" ] . ( map [ string ] any ) [ "payouts" ] . ( [ ] any )
if uint64 ( len ( payouts ) ) <= index {
ctx := make ( map [ string ] stick . Value )
error := make ( map [ string ] stick . Value )
ctx [ "error" ] = error
ctx [ "code" ] = http . StatusNotFound
error [ "message" ] = "Output Not Found"
render ( writer , "error.html" , ctx )
return
}
poolInfo := getFromAPI ( "pool_info" , 5 ) . ( map [ string ] any )
ctx := make ( map [ string ] stick . Value )
ctx [ "block" ] = block
ctx [ "payout" ] = payouts [ index ]
ctx [ "pool" ] = poolInfo
render ( writer , "proof.html" , ctx )
} )
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 )
}
}