mirror of
https://github.com/ditatompel/xmr-remote-nodes.git
synced 2025-01-03 09:29:35 +00:00
Probe (client) check remote node
Please note that this commit is not complete. I commit to the repo because I have something to do with my another project. Just don't want to lost my work for the last couple hours.
This commit is contained in:
parent
cee2b4341b
commit
8724b81431
5 changed files with 268 additions and 5 deletions
15
.env.example
15
.env.example
|
@ -1,14 +1,21 @@
|
||||||
|
# Prober config
|
||||||
|
# #############
|
||||||
|
SERVER_ENDPOINT="http://127.0.0.1:18901"
|
||||||
|
API_KEY=
|
||||||
|
ACCEPT_TOR=true
|
||||||
|
TOR_SOCKS="127.0.0.1:9050"
|
||||||
|
|
||||||
|
# Server Config
|
||||||
|
# #############
|
||||||
SECRET_KEY="" # must be 32 char length, use `openssl rand -base64 32` to generate random secret
|
SECRET_KEY="" # must be 32 char length, use `openssl rand -base64 32` to generate random secret
|
||||||
LOG_LEVEL=INFO # can be DEBUG, INFO, WARNING, ERROR
|
LOG_LEVEL=INFO # can be DEBUG, INFO, WARNING, ERROR
|
||||||
|
|
||||||
# Fiber Config
|
# Fiber Config
|
||||||
APP_DEBUG=false # if this set to true , LOG_LEVEL will be set to DEBUG
|
APP_DEBUG=false # if this set to true , LOG_LEVEL will be set to DEBUG
|
||||||
APP_PREFORK=true
|
APP_PREFORK=true
|
||||||
APP_HOST="0.0.0.0"
|
APP_HOST="127.0.0.1"
|
||||||
APP_PORT=18090
|
APP_PORT=18090
|
||||||
APP_PROXY_HEADER="X-Real-Ip" # CF-Connecting-IP
|
APP_PROXY_HEADER="X-Real-Ip" # CF-Connecting-IP
|
||||||
APP_ALLOW_ORIGIN="http://localhost:5173,http://192.168.1.99:5173,https://ditatompel.com"
|
APP_ALLOW_ORIGIN="http://localhost:5173,http://127.0.0.1:5173,https://ditatompel.com"
|
||||||
|
|
||||||
# DB settings:
|
# DB settings:
|
||||||
DB_HOST=127.0.0.1
|
DB_HOST=127.0.0.1
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
|
|
241
cmd/probe.go
Normal file
241
cmd/probe.go
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ditatompel/xmr-nodes/internal/config"
|
||||||
|
"github.com/ditatompel/xmr-nodes/internal/repo"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
const RPCUserAgent = "ditatombot/0.0.1 (Monero RPC Monitoring; Contact: ditatombot@ditatompel.com)"
|
||||||
|
|
||||||
|
type proberClient struct {
|
||||||
|
config *config.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProber(cfg *config.App) *proberClient {
|
||||||
|
return &proberClient{config: cfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
var probeCmd = &cobra.Command{
|
||||||
|
Use: "probe",
|
||||||
|
Short: "Run Monero node prober",
|
||||||
|
Run: func(_ *cobra.Command, _ []string) {
|
||||||
|
runProbe()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(probeCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runProbe() {
|
||||||
|
cfg := config.AppCfg()
|
||||||
|
if cfg.ServerEndpoint == "" {
|
||||||
|
fmt.Println("Please set SERVER_ENDPOINT in .env")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("Accept Tor: %t\n", cfg.AcceptTor)
|
||||||
|
|
||||||
|
if cfg.AcceptTor && cfg.TorSocks == "" {
|
||||||
|
fmt.Println("Please set TOR_SOCKS in .env")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
probe := newProber(cfg)
|
||||||
|
|
||||||
|
node, err := probe.getJob()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchNode, err := probe.fetchNode(node)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println(prettyPrint(fetchNode))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proberClient) getJob() (repo.MoneroNode, error) {
|
||||||
|
queryParams := ""
|
||||||
|
if p.config.ApiKey != "" {
|
||||||
|
queryParams = "?api_key=" + p.config.ApiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
node := repo.MoneroNode{}
|
||||||
|
|
||||||
|
endpoint := fmt.Sprintf("%s/api/v1/job%s", p.config.ServerEndpoint, queryParams)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
req.Header.Add("X-Prober-Api-Key", p.config.ApiKey)
|
||||||
|
req.Header.Set("User-Agent", RPCUserAgent)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return node, fmt.Errorf("status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
response := struct {
|
||||||
|
Data repo.MoneroNode `json:"data"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
node = response.Data
|
||||||
|
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proberClient) fetchNode(node repo.MoneroNode) (repo.MoneroNode, error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
endpoint := fmt.Sprintf("%s://%s:%d/json_rpc", node.Protocol, node.Hostname, node.Port)
|
||||||
|
rpcParam := []byte(`{"jsonrpc": "2.0","id": "0","method": "get_info"}`)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(rpcParam))
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
req.Header.Set("User-Agent", RPCUserAgent)
|
||||||
|
req.Header.Set("Origin", "https://xmr.ditatompel.com")
|
||||||
|
|
||||||
|
var client http.Client
|
||||||
|
if p.config.AcceptTor && node.IsTor {
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", p.config.TorSocks, nil, proxy.Direct)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.Dial(network, addr)
|
||||||
|
}
|
||||||
|
transport := &http.Transport{
|
||||||
|
DialContext: dialContext,
|
||||||
|
DisableKeepAlives: true,
|
||||||
|
}
|
||||||
|
client.Transport = transport
|
||||||
|
client.Timeout = 60 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the default node struct
|
||||||
|
node.IsAvailable = false
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Post report to server
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
// TODO: Post report to server
|
||||||
|
return node, fmt.Errorf("status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Post report to server
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reportNode := struct {
|
||||||
|
repo.MoneroNode `json:"result"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &reportNode); err != nil {
|
||||||
|
// TODO: Post report to server
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
node.IsAvailable = true
|
||||||
|
node.NetType = reportNode.NetType
|
||||||
|
node.AdjustedTime = reportNode.AdjustedTime
|
||||||
|
node.DatabaseSize = reportNode.DatabaseSize
|
||||||
|
node.Difficulty = reportNode.Difficulty
|
||||||
|
node.NodeVersion = reportNode.NodeVersion
|
||||||
|
|
||||||
|
if resp.Header.Get("Access-Control-Allow-Origin") == "*" || resp.Header.Get("Access-Control-Allow-Origin") == "https://xmr.ditatompel.com" {
|
||||||
|
node.CorsCapable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !node.IsTor {
|
||||||
|
hostIp, err := net.LookupIP(node.Hostname)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Warning: Could not resolve hostname: " + node.Hostname)
|
||||||
|
} else {
|
||||||
|
node.Ip = hostIp[0].String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleeping 1 second to avoid too many request on host behind CloudFlare
|
||||||
|
// time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// check fee
|
||||||
|
rpcCheckFeeParam := []byte(`{"jsonrpc": "2.0","id": "0","method": "get_fee_estimate"}`)
|
||||||
|
reqCheckFee, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(rpcCheckFeeParam))
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
reqCheckFee.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
reqCheckFee.Header.Set("User-Agent", RPCUserAgent)
|
||||||
|
|
||||||
|
checkFee, err := client.Do(reqCheckFee)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
defer checkFee.Body.Close()
|
||||||
|
|
||||||
|
if checkFee.StatusCode != 200 {
|
||||||
|
return node, fmt.Errorf("status code: %d", checkFee.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyCheckFee, err := io.ReadAll(checkFee.Body)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
feeEstimate := struct {
|
||||||
|
Result struct {
|
||||||
|
Fee uint `json:"fee"`
|
||||||
|
} `json:"result"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(bodyCheckFee, &feeEstimate); err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tookTime := time.Since(startTime).Seconds()
|
||||||
|
node.EstimateFee = feeEstimate.Result.Fee
|
||||||
|
|
||||||
|
fmt.Printf("Took %f seconds\n", tookTime)
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// for debug purposes
|
||||||
|
func prettyPrint(i interface{}) string {
|
||||||
|
s, _ := json.MarshalIndent(i, "", "\t")
|
||||||
|
return string(s)
|
||||||
|
}
|
1
go.mod
1
go.mod
|
@ -10,6 +10,7 @@ require (
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
|
golang.org/x/net v0.21.0
|
||||||
golang.org/x/term v0.19.0
|
golang.org/x/term v0.19.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -56,6 +56,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
|
// configuration for server
|
||||||
Debug bool
|
Debug bool
|
||||||
Prefork bool
|
Prefork bool
|
||||||
Host string
|
Host string
|
||||||
|
@ -14,6 +15,11 @@ type App struct {
|
||||||
AllowOrigin string
|
AllowOrigin string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
LogLevel string
|
LogLevel string
|
||||||
|
// configuration for prober (client)
|
||||||
|
ServerEndpoint string
|
||||||
|
ApiKey string
|
||||||
|
AcceptTor bool
|
||||||
|
TorSocks string
|
||||||
}
|
}
|
||||||
|
|
||||||
var app = &App{}
|
var app = &App{}
|
||||||
|
@ -22,8 +28,9 @@ func AppCfg() *App {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadApp loads App configuration
|
// loads App configuration
|
||||||
func LoadApp() {
|
func LoadApp() {
|
||||||
|
// server configuration
|
||||||
app.Host = os.Getenv("APP_HOST")
|
app.Host = os.Getenv("APP_HOST")
|
||||||
app.Port, _ = strconv.Atoi(os.Getenv("APP_PORT"))
|
app.Port, _ = strconv.Atoi(os.Getenv("APP_PORT"))
|
||||||
app.Debug, _ = strconv.ParseBool(os.Getenv("APP_DEBUG"))
|
app.Debug, _ = strconv.ParseBool(os.Getenv("APP_DEBUG"))
|
||||||
|
@ -38,4 +45,9 @@ func LoadApp() {
|
||||||
if app.Debug {
|
if app.Debug {
|
||||||
app.LogLevel = "DEBUG"
|
app.LogLevel = "DEBUG"
|
||||||
}
|
}
|
||||||
|
// prober configuration
|
||||||
|
app.ServerEndpoint = os.Getenv("SERVER_ENDPOINT")
|
||||||
|
app.ApiKey = os.Getenv("API_KEY")
|
||||||
|
app.AcceptTor, _ = strconv.ParseBool(os.Getenv("ACCEPT_TOR"))
|
||||||
|
app.TorSocks = os.Getenv("TOR_SOCKS")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue