Skip to content

Commit

Permalink
v0.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
edtubbs committed Oct 18, 2024
1 parent 18f7130 commit d7cc00e
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 0 deletions.
3 changes: 3 additions & 0 deletions dogebox.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
},
{
"location": "identity"
},
{
"location": "spv"
}
]
}
11 changes: 11 additions & 0 deletions spv/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div align="center">
<img src="../docs/img/dogebox-logo.png" alt="Dogebox Logo"/>
<p>Libdogecoin SPV</p>
</div>

> [!CAUTION]
> This pup does not have a stable release yet.
This pup will install [Libdogecoin SPV](https://github.com/dogecoinfoundation/libdogecoin) as a pup on your node.

It will generate a new wallet and start block sync from the last checkpoint.
53 changes: 53 additions & 0 deletions spv/logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

import (
"fmt"
"io"
"log"
"os"
"time"

"path/filepath"
)

var storageDirectory string
var debugLogFilePath = filepath.Join(storageDirectory, "output.log")

func main() {

for {
if _, err := os.Stat(debugLogFilePath); os.IsNotExist(err) {
log.Printf("Waiting for output.log file to be created at %s", debugLogFilePath)
time.Sleep(5 * time.Second)
continue
}
break
}

file, err := os.Open(debugLogFilePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()

_, err = file.Seek(0, io.SeekEnd)
if err != nil {
log.Fatal(err)
}

for {
time.Sleep(2 * time.Second)

buffer := make([]byte, 1024)
for {
n, err := file.Read(buffer)
if err != nil && err != io.EOF {
log.Fatal(err)
}
if n == 0 {
break
}
fmt.Print(string(buffer[:n]))
}
}
}
Binary file added spv/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 113 additions & 0 deletions spv/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"manifestVersion": 1,
"meta": {
"name": "Libdogecoin SPV",
"version": "0.0.3",
"logoPath": "logo.png",
"shortDescription": "Run a libdogecoin SPV node on your dogebox",
"longDescription": "Libdogecoin SPV runs a minimal node on your dogebox",
"upstreamVersions": {
"Libdogecoin": "v0.1.4-dogebox-pre"
}
},
"config": {
"sections": null
},
"container": {
"build": {
"nixFile": "pup.nix",
"nixFileSha256": "737d4364ead28d6300f203fccfee002681ee97b5a904474757e108aac46a7341"
},
"services": [
{
"name": "spvnode",
"command": {
"exec": "/bin/run.sh",
"cwd": "",
"env": null
}
},
{
"name": "monitor",
"command": {
"exec": "/bin/monitor",
"cwd": "",
"env": null
}
},
{
"name": "logger",
"command": {
"exec": "/bin/logger",
"cwd": "",
"env": null
}
}
],
"exposes": [
{
"name": "p2p-port",
"type": "tcp",
"port": 22556,
"interfaces": null,
"listenOnHost": true
},
{
"name": "rest-port",
"type": "http",
"port": 8888,
"interfaces": ["lib-rest"],
"listenOnHost": false
}
],
"requiresInternet": true
},
"interfaces": [
{
"name": "lib-rest",
"version": "0.0.1",
"permissionGroups": [
{
"name": "REST",
"description": "Allows RESTful access to the Libdogecoin SPV node",
"severity": 2,
"routes": ["/*"],
"port": 0
}
]
}
],
"dependencies": null,
"metrics": [
{
"name": "chaintip",
"label": "Chain Tip",
"type": "string",
"history": 1
},
{
"name": "addresses",
"label": "Addresses",
"type": "string",
"history": 1
},
{
"name": "balance",
"label": "Wallet Balance",
"type": "string",
"history": 1
},
{
"name": "transaction_count",
"label": "Transaction Count",
"type": "string",
"history": 1
},
{
"name": "unspent_count",
"label": "UTXO Count",
"type": "string",
"history": 1
}
]
}
205 changes: 205 additions & 0 deletions spv/monitor/monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)

type Metrics struct {
Chaintip string `json:"chaintip"`
Balance string `json:"balance"`
Addresses string `json:"addresses"`
TransactionCount string `json:"transaction_count"`
UnspentCount string `json:"unspent_count"`
}

func fetchEndpoint(endpoint string) (string, error) {
url := fmt.Sprintf("http://0.0.0.0:8888%s", endpoint)

client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
DisableKeepAlives: true,
},
}

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("error creating request: %w", err)
}

req.Header.Set("Connection", "close")

resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("error sending request to %s: %w", endpoint, err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("unexpected status code %d for %s: %s", resp.StatusCode, endpoint, string(body))
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("error reading response body for %s: %w", endpoint, err)
}

return string(body), nil
}

func collectMetrics() (Metrics, error) {
var metrics Metrics

// Fetch chain tip
chaintipStr, err := fetchEndpoint("/getChaintip")
if err != nil {
return metrics, err
}
chaintipLine := strings.TrimSpace(chaintipStr)
if strings.HasPrefix(chaintipLine, "Chain tip: ") {
metrics.Chaintip = strings.TrimPrefix(chaintipLine, "Chain tip: ")
} else {
metrics.Chaintip = chaintipLine // In case the format is different
}

// Fetch balance
balanceStr, err := fetchEndpoint("/getBalance")
if err != nil {
return metrics, err
}
balanceLine := strings.TrimSpace(balanceStr)
if strings.HasPrefix(balanceLine, "Wallet balance: ") {
metrics.Balance = strings.TrimPrefix(balanceLine, "Wallet balance: ")
} else {
metrics.Balance = balanceLine // In case the format is different
}

// Fetch addresses
addressesStr, err := fetchEndpoint("/getAddresses")
if err != nil {
return metrics, err
}
var addresses []string
for _, line := range strings.Split(addressesStr, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "address: ") {
address := strings.TrimPrefix(line, "address: ")
addresses = append(addresses, address)
}
}
if len(addresses) > 0 {
metrics.Addresses = strings.Join(addresses, "\n")
} else {
metrics.Addresses = "No addresses found"
}

// Fetch transaction count
transactionsStr, err := fetchEndpoint("/getTransactions")
if err != nil {
return metrics, err
}
transactionCount := 0
for _, line := range strings.Split(transactionsStr, "\n") {
if strings.HasPrefix(line, "----------------------") {
transactionCount++
}
}
metrics.TransactionCount = fmt.Sprintf("%d", transactionCount)

// Fetch unspent UTXO count
utxosStr, err := fetchEndpoint("/getUTXOs")
if err != nil {
return metrics, err
}
unspentCount := 0
for _, line := range strings.Split(utxosStr, "\n") {
if strings.HasPrefix(line, "----------------------") {
unspentCount++
}
}
metrics.UnspentCount = fmt.Sprintf("%d", unspentCount)

return metrics, nil
}

func submitMetrics(metrics Metrics) {
client := &http.Client{
Timeout: 10 * time.Second,
}

// Create a nested structure for the metrics data
jsonData := map[string]interface{}{
"chaintip": map[string]interface{}{"value": metrics.Chaintip},
"balance": map[string]interface{}{"value": metrics.Balance},
"addresses": map[string]interface{}{"value": metrics.Addresses},
"transaction_count": map[string]interface{}{"value": metrics.TransactionCount},
"unspent_count": map[string]interface{}{"value": metrics.UnspentCount},
}

// Marshal the data to JSON
marshalledData, err := json.Marshal(jsonData)
if err != nil {
log.Printf("Error marshalling metrics: %v", err)
return
}

log.Printf("Submitting metrics: %+v", jsonData)

url := fmt.Sprintf("http://%s:%s/dbx/metrics", os.Getenv("DBX_HOST"), os.Getenv("DBX_PORT"))

req, err := http.NewRequest("POST", url, bytes.NewBuffer(marshalledData))
if err != nil {
log.Printf("Error creating request: %v", err)
return
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Connection", "close")

resp, err := client.Do(req)
if err != nil {
log.Printf("Error sending metrics: %v", err)
return
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
log.Printf("Unexpected status code when submitting metrics: %d", resp.StatusCode)
log.Printf("Response body: %s", string(body))
return
}
}

func main() {
log.Println("Sleeping to give spvnode time to start...")
time.Sleep(10 * time.Second)

ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()

for {
select {
case <-ticker.C:
metrics, err := collectMetrics()
if err != nil {
log.Printf("Error collecting metrics: %v", err)
continue
}

log.Printf("Metrics: %+v", metrics)
submitMetrics(metrics)

log.Printf("----------------------------------------")
}
}
}
Loading

0 comments on commit d7cc00e

Please sign in to comment.