Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spv: v0.0.5 #9

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions spv/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifestVersion": 1,
"meta": {
"name": "Libdogecoin SPV",
"version": "0.0.3",
"version": "0.0.5",
"logoPath": "logo.png",
"shortDescription": "Run a libdogecoin SPV node on your dogebox",
"longDescription": "Libdogecoin SPV runs a minimal node on your dogebox",
Expand All @@ -16,7 +16,7 @@
"container": {
"build": {
"nixFile": "pup.nix",
"nixFileSha256": "737d4364ead28d6300f203fccfee002681ee97b5a904474757e108aac46a7341"
"nixFileSha256": "c58aa8c40f6fa72db975cf7ecb81c6b791c712090c77f8f72498836fbe5c1313"
},
"services": [
{
Expand Down Expand Up @@ -98,14 +98,14 @@
"history": 1
},
{
"name": "transaction_count",
"label": "Transaction Count",
"name": "utxos",
"label": "UTXOs",
"type": "string",
"history": 1
},
{
"name": "unspent_count",
"label": "UTXO Count",
"name": "transactions",
"label": "Transactions",
"type": "string",
"history": 1
}
Expand Down
115 changes: 69 additions & 46 deletions spv/monitor/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type Metrics struct {
Addresses string `json:"addresses"`
TransactionCount string `json:"transaction_count"`
UnspentCount string `json:"unspent_count"`
Transactions string `json:"transactions"`
UTXOs string `json:"utxos"`
}

func fetchEndpoint(endpoint string) (string, error) {
Expand Down Expand Up @@ -64,96 +66,119 @@ func collectMetrics() (Metrics, error) {
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
}
metrics.Chaintip = parseSimpleMetric(chaintipStr, "Chain tip: ")

// 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
}
balance := parseSimpleMetric(balanceStr, "Wallet balance: ")
metrics.Balance = fmt.Sprintf("%sÐ", balance)

// 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"
}
metrics.Addresses = parseListMetric(addressesStr, "address: ")

// Fetch transaction count
// Fetch transactions
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)
metrics.Transactions, metrics.TransactionCount = parseUTXOsOrTxs(transactionsStr)

// Fetch unspent UTXO count
// Fetch UTXOs
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.UTXOs, metrics.UnspentCount = parseUTXOsOrTxs(utxosStr)

return metrics, nil
}

// Helper to parse simple metrics
func parseSimpleMetric(input, prefix string) string {
line := strings.TrimSpace(input)
if strings.HasPrefix(line, prefix) {
return strings.TrimPrefix(line, prefix)
}
return line
}

// Helper to parse lists of metrics
func parseListMetric(input, prefix string) string {
var items []string
for _, line := range strings.Split(input, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, prefix) {
items = append(items, strings.TrimPrefix(line, prefix))
}
}
metrics.UnspentCount = fmt.Sprintf("%d", unspentCount)
if len(items) > 0 {
return strings.Join(items, "\n")
}
return "No entries found"
}

return metrics, nil
// Helper to parse UTXOs or transactions
func parseUTXOsOrTxs(input string) (string, string) {
var output []string
count := 0

parts := strings.Split(input, "----------------------")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}

txid, amount, address := "", "", ""
lines := strings.Split(part, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "txid:") {
txid = strings.TrimSpace(strings.TrimPrefix(line, "txid:"))
} else if strings.HasPrefix(line, "amount:") {
amount = strings.TrimSpace(strings.TrimPrefix(line, "amount:"))
} else if strings.HasPrefix(line, "address:") {
address = strings.TrimSpace(strings.TrimPrefix(line, "address:"))
}
}

if txid != "" && amount != "" && address != "" {
output = append(output, fmt.Sprintf("%s %sÐ %s", txid, amount, address))
count++
}
}

return strings.Join(output, "\n"), fmt.Sprintf("%d", count)
}

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},
"transactions": map[string]interface{}{"value": metrics.Transactions},
"utxos": map[string]interface{}{"value": metrics.UTXOs},
}

// 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))
Expand All @@ -176,7 +201,6 @@ func submitMetrics(metrics Metrics) {
body, _ := io.ReadAll(resp.Body)
log.Printf("Unexpected status code when submitting metrics: %d", resp.StatusCode)
log.Printf("Response body: %s", string(body))
return
}
}

Expand All @@ -198,7 +222,6 @@ func main() {

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

log.Printf("----------------------------------------")
}
}
Expand Down
34 changes: 19 additions & 15 deletions spv/pup.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,34 @@ let
}) {
};

awk = pkgs.gawk;
host = pkgs.host;

spvnode = pkgs.writeScriptBin "run.sh" ''
#!${pkgs.stdenv.shell}

# Generate a mnemonic if one doesn't exist
if [ ! -f "${storageDirectory}/1" ]; then
export MNEMONIC=$(${spvnode_bin}/bin/such -c generate_mnemonic | tee "${storageDirectory}/1")
# Ensure delegated.extended.key exists
if [ ! -f "${storageDirectory}/delegated.extended.key" ]; then
echo "Error: delegated.extended.key not found"
exit 1
fi

# Update the DNS to resolve seed.multidoge.org
resolvectl dns eth0 1.1.1.1
# Derive a few external addresses from the delegated extended key
ADDRESS0=$(${spvnode_bin}/bin/such -c derive_child_keys -m "m/0/0" -p "$(cat ${storageDirectory}/delegated.extended.key)" | ${awk}/bin/awk '/p2pkh address:/ {print $3}')
ADDRESS1=$(${spvnode_bin}/bin/such -c derive_child_keys -m "m/0/1" -p "$(cat ${storageDirectory}/delegated.extended.key)" | ${awk}/bin/awk '/p2pkh address:/ {print $3}')
ADDRESS2=$(${spvnode_bin}/bin/such -c derive_child_keys -m "m/0/2" -p "$(cat ${storageDirectory}/delegated.extended.key)" | ${awk}/bin/awk '/p2pkh address:/ {print $3}')

# Wait until DNS resolves 'seed.multidoge.org'
${host}/bin/host -w seed.multidoge.org

# Scan in continuous (-c), block mode (-b) from the latest checkpoint (-p)
# Generate wallet with a mnemonic (-n) if one doesn't exist
# Store wallet in wallet.db (-w) and headers in header.db (-f)
# Connect to initial peer (-i) due to DNS
# Enable http server on port 8888 (-u) for endpoints
# Run spvnode with the addresses
${spvnode_bin}/bin/spvnode \
-c -b -p -l \
-n "$MNEMONIC" \
-a "$ADDRESS0 $ADDRESS1 $ADDRESS2" \
-w "${storageDirectory}/wallet.db" \
-f "${storageDirectory}/headers.db" \
-i "192.7.117.243" \
-u "0.0.0.0:8888" \
scan 2>&1 | tee "${storageDirectory}/output.log"
scan 2>&1 | tee -a "${storageDirectory}/output.log"
'';

monitor = pkgs.buildGoModule {
Expand All @@ -41,7 +45,7 @@ let
vendorHash = null;

systemPackages = [ spvnode_bin ];

buildPhase = ''
export GO111MODULE=off
export GOCACHE=$(pwd)/.gocache
Expand Down Expand Up @@ -74,5 +78,5 @@ let

in
{
inherit spvnode monitor logger;
inherit spvnode monitor logger awk host;
}