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
|
||||
LOG_LEVEL=INFO # can be DEBUG, INFO, WARNING, ERROR
|
||||
|
||||
# Fiber Config
|
||||
APP_DEBUG=false # if this set to true , LOG_LEVEL will be set to DEBUG
|
||||
APP_PREFORK=true
|
||||
APP_HOST="0.0.0.0"
|
||||
APP_HOST="127.0.0.1"
|
||||
APP_PORT=18090
|
||||
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_HOST=127.0.0.1
|
||||
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/joho/godotenv v1.5.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
golang.org/x/net v0.21.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.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.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-20220722155255-886fb9371eb4/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 {
|
||||
// configuration for server
|
||||
Debug bool
|
||||
Prefork bool
|
||||
Host string
|
||||
|
@ -14,6 +15,11 @@ type App struct {
|
|||
AllowOrigin string
|
||||
SecretKey string
|
||||
LogLevel string
|
||||
// configuration for prober (client)
|
||||
ServerEndpoint string
|
||||
ApiKey string
|
||||
AcceptTor bool
|
||||
TorSocks string
|
||||
}
|
||||
|
||||
var app = &App{}
|
||||
|
@ -22,8 +28,9 @@ func AppCfg() *App {
|
|||
return app
|
||||
}
|
||||
|
||||
// LoadApp loads App configuration
|
||||
// loads App configuration
|
||||
func LoadApp() {
|
||||
// server configuration
|
||||
app.Host = os.Getenv("APP_HOST")
|
||||
app.Port, _ = strconv.Atoi(os.Getenv("APP_PORT"))
|
||||
app.Debug, _ = strconv.ParseBool(os.Getenv("APP_DEBUG"))
|
||||
|
@ -38,4 +45,9 @@ func LoadApp() {
|
|||
if app.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