From dc41d011eb56e125d525a07b9537a947d9a3961a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:49:02 +0000 Subject: [PATCH 01/13] Initial plan From 72fde1ac714ddda7c2e768b6c5ffa6510f676de8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:51:41 +0000 Subject: [PATCH 02/13] fix dashboard monitor metric typing and remove tx metadata Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/manifest.json | 2 +- dashboard/monitor/monitor.go | 99 ++++++++++++++---------------------- 2 files changed, 38 insertions(+), 63 deletions(-) diff --git a/dashboard/manifest.json b/dashboard/manifest.json index a765b75..5d49bbd 100644 --- a/dashboard/manifest.json +++ b/dashboard/manifest.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "meta": { "name": "Libdogecoin dashb0rd", - "version": "0.0.12", + "version": "0.0.13", "logoPath": "logo.png", "shortDescription": "Run a libdogecoin SPV node on your dogebox", "longDescription": "Libdogecoin SPV runs a minimal node on your dogebox", diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index 4443339..2e2e789 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -187,27 +187,6 @@ func parseKeyValueLines(input string) map[string]string { return m } -// Attach txid + timestamps as per-sample meta for charts. -func metaTx(stats map[string]string) map[string]interface{} { - txid := strings.TrimSpace(stats["last_seen_txid"]) - txTs := strings.TrimSpace(stats["last_seen_ts"]) - if txid == "" && txTs == "" { - return nil - } - meta := map[string]interface{}{ - "txid": txid, - "sample_ts": time.Now().Unix(), - } - if txTs != "" { - if f, ok := tryParseFlexibleFloat(txTs); ok { - meta["tx_ts"] = int64(f) - } else { - meta["tx_ts"] = txTs // keep raw if parse fails - } - } - return meta -} - func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]string) { client := &http.Client{Timeout: 10 * time.Second} @@ -219,9 +198,6 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st // Difficulty from bits (works with hex or decimal) chainDiff := difficultyFromBitsString(chainTipBitsStr) - // one meta blob reused for this tick (txid bound to this snapshot) - txmeta := metaTx(stats) - payload := map[string]interface{}{ // Basics "chaintip": map[string]interface{}{"value": metrics.Chaintip}, @@ -230,48 +206,47 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st "transactions": map[string]interface{}{"value": metrics.Transactions}, "utxos": map[string]interface{}{"value": metrics.UTXOs}, - // /stats?blocks=11 (numeric series; meta carries txid) - "stats_blocks": map[string]interface{}{"value": mustParseFloat(stats["blocks"]), "meta": txmeta}, - "stats_transactions": map[string]interface{}{"value": mustParseFloat(stats["transactions"]), "meta": txmeta}, - "stats_tps": map[string]interface{}{"value": mustParseFloat(stats["tps"]), "meta": txmeta}, - "stats_volume": map[string]interface{}{"value": mustParseFloat(stats["volume"]), "meta": txmeta}, - "stats_volume_koinu": map[string]interface{}{"value": mustParseFloat(stats["volume_koinu"]), "meta": txmeta}, - "stats_median_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_block"]), "meta": txmeta}, - "stats_avg_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_block"]), "meta": txmeta}, - "stats_median_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_kb"]), "meta": txmeta}, - "stats_avg_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_kb"]), "meta": txmeta}, - "stats_outputs": map[string]interface{}{"value": mustParseFloat(stats["outputs"]), "meta": txmeta}, - "stats_bytes": map[string]interface{}{"value": mustParseFloat(stats["bytes"]), "meta": txmeta}, + // /stats?blocks=11 (numeric series) + "stats_blocks": map[string]interface{}{"value": mustParseFloat(stats["blocks"])}, + "stats_transactions": map[string]interface{}{"value": mustParseFloat(stats["transactions"])}, + "stats_tps": map[string]interface{}{"value": mustParseFloat(stats["tps"])}, + "stats_volume": map[string]interface{}{"value": mustParseFloat(stats["volume"])}, + "stats_volume_koinu": map[string]interface{}{"value": mustParseFloat(stats["volume_koinu"])}, + "stats_median_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_block"])}, + "stats_avg_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_block"])}, + "stats_median_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_kb"])}, + "stats_avg_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_kb"])}, + "stats_outputs": map[string]interface{}{"value": mustParseFloat(stats["outputs"])}, + "stats_bytes": map[string]interface{}{"value": mustParseFloat(stats["bytes"])}, // (we leave difficulty to the chain-tip section to avoid duplication) - // /smpvStats (mempool + script types; also tagged with txmeta) - "smpv_enabled": map[string]interface{}{"value": mustParseFloat(stats["enabled"]), "meta": txmeta}, - "smpv_mempool_txs": map[string]interface{}{"value": mustParseFloat(stats["mempool_txs"]), "meta": txmeta}, - "smpv_watchers": map[string]interface{}{"value": mustParseFloat(stats["watchers"]), "meta": txmeta}, - "smpv_confirmed": map[string]interface{}{"value": mustParseFloat(stats["confirmed"]), "meta": txmeta}, - "smpv_unconfirmed": map[string]interface{}{"value": mustParseFloat(stats["unconfirmed"]), "meta": txmeta}, - "smpv_total_bytes": map[string]interface{}{"value": mustParseFloat(stats["total_bytes"]), "meta": txmeta}, - "smpv_last_seen_age_sec": map[string]interface{}{"value": mustParseFloat(stats["last_seen_age_sec"]), "meta": txmeta}, - "smpv_last_seen_txid": map[string]interface{}{"value": stats["last_seen_txid"], "meta": txmeta}, - "metadata": map[string]interface{}{"value": "txid:" + stats["last_seen_txid"], "meta": txmeta}, - "smpv_types_p2pk": map[string]interface{}{"value": mustParseFloat(stats["types_p2pk"]), "meta": txmeta}, - "smpv_types_p2pkh": map[string]interface{}{"value": mustParseFloat(stats["types_p2pkh"]), "meta": txmeta}, - "smpv_types_p2sh": map[string]interface{}{"value": mustParseFloat(stats["types_p2sh"]), "meta": txmeta}, - "smpv_types_multisig": map[string]interface{}{"value": mustParseFloat(stats["types_multisig"]), "meta": txmeta}, - "smpv_types_op_return": map[string]interface{}{"value": mustParseFloat(stats["types_op_return"]), "meta": txmeta}, - "smpv_types_nonstandard": map[string]interface{}{"value": mustParseFloat(stats["types_nonstandard"]), "meta": txmeta}, - "smpv_types_vout_total": map[string]interface{}{"value": mustParseFloat(stats["types_vout_total"]), "meta": txmeta}, - "smpv_coinbase_txs": map[string]interface{}{"value": mustParseFloat(stats["coinbase_txs"]), "meta": txmeta}, + // /smpvStats (mempool + script types) + "smpv_enabled": map[string]interface{}{"value": mustParseFloat(stats["enabled"])}, + "smpv_mempool_txs": map[string]interface{}{"value": mustParseFloat(stats["mempool_txs"])}, + "smpv_watchers": map[string]interface{}{"value": mustParseFloat(stats["watchers"])}, + "smpv_confirmed": map[string]interface{}{"value": mustParseFloat(stats["confirmed"])}, + "smpv_unconfirmed": map[string]interface{}{"value": mustParseFloat(stats["unconfirmed"])}, + "smpv_total_bytes": map[string]interface{}{"value": mustParseFloat(stats["total_bytes"])}, + "smpv_last_seen_age_sec": map[string]interface{}{"value": mustParseFloat(stats["last_seen_age_sec"])}, + "smpv_last_seen_txid": map[string]interface{}{"value": stats["last_seen_txid"]}, + "smpv_types_p2pk": map[string]interface{}{"value": mustParseFloat(stats["types_p2pk"])}, + "smpv_types_p2pkh": map[string]interface{}{"value": mustParseFloat(stats["types_p2pkh"])}, + "smpv_types_p2sh": map[string]interface{}{"value": mustParseFloat(stats["types_p2sh"])}, + "smpv_types_multisig": map[string]interface{}{"value": mustParseFloat(stats["types_multisig"])}, + "smpv_types_op_return": map[string]interface{}{"value": mustParseFloat(stats["types_op_return"])}, + "smpv_types_nonstandard": map[string]interface{}{"value": mustParseFloat(stats["types_nonstandard"])}, + "smpv_types_vout_total": map[string]interface{}{"value": mustParseFloat(stats["types_vout_total"])}, + "smpv_coinbase_txs": map[string]interface{}{"value": mustParseFloat(stats["coinbase_txs"])}, // /chainStats (session totals) - "headers_bytes": map[string]interface{}{"value": chain["headers_bytes"]}, - "blocks_total": map[string]interface{}{"value": chain["blocks_total"]}, - "transactions_total": map[string]interface{}{"value": chain["transactions_total"]}, - "outputs_total": map[string]interface{}{"value": chain["outputs_total"]}, - "output_value_total": map[string]interface{}{"value": chain["output_value_total"]}, - "fees_total": map[string]interface{}{"value": chain["fees_total"]}, - "block_bytes_total": map[string]interface{}{"value": chain["block_bytes_total"]}, - "approx_chain_bytes": map[string]interface{}{"value": chain["approx_chain_bytes"]}, + "headers_bytes": map[string]interface{}{"value": mustParseFloat(chain["headers_bytes"])}, + "blocks_total": map[string]interface{}{"value": mustParseFloat(chain["blocks_total"])}, + "transactions_total": map[string]interface{}{"value": mustParseFloat(chain["transactions_total"])}, + "outputs_total": map[string]interface{}{"value": mustParseFloat(chain["outputs_total"])}, + "output_value_total": map[string]interface{}{"value": mustParseFloat(chain["output_value_total"])}, + "fees_total": map[string]interface{}{"value": mustParseFloat(chain["fees_total"])}, + "block_bytes_total": map[string]interface{}{"value": mustParseFloat(chain["block_bytes_total"])}, + "approx_chain_bytes": map[string]interface{}{"value": mustParseFloat(chain["approx_chain_bytes"])}, "chain_tip_height": map[string]interface{}{"value": mustParseFloat(chain["tip_height"])}, "chain_tip_bits_hex": map[string]interface{}{"value": chainTipBitsStr}, "chain_tip_difficulty": map[string]interface{}{"value": chainDiff}, From 92e63fbf68253ea07a7b46259565507c9d1f2c50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:53:07 +0000 Subject: [PATCH 03/13] refine monitor payload by dropping txid metadata field Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/monitor/monitor.go | 1 - 1 file changed, 1 deletion(-) diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index 2e2e789..1a1db41 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -228,7 +228,6 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st "smpv_unconfirmed": map[string]interface{}{"value": mustParseFloat(stats["unconfirmed"])}, "smpv_total_bytes": map[string]interface{}{"value": mustParseFloat(stats["total_bytes"])}, "smpv_last_seen_age_sec": map[string]interface{}{"value": mustParseFloat(stats["last_seen_age_sec"])}, - "smpv_last_seen_txid": map[string]interface{}{"value": stats["last_seen_txid"]}, "smpv_types_p2pk": map[string]interface{}{"value": mustParseFloat(stats["types_p2pk"])}, "smpv_types_p2pkh": map[string]interface{}{"value": mustParseFloat(stats["types_p2pkh"])}, "smpv_types_p2sh": map[string]interface{}{"value": mustParseFloat(stats["types_p2sh"])}, From cfa72567461af121477fa334c513ec9da68fa12d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:06:11 +0000 Subject: [PATCH 04/13] add timestamp-only metadata to monitor chart samples Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/monitor/monitor.go | 68 ++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index 1a1db41..e1b4f9a 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -187,6 +187,21 @@ func parseKeyValueLines(input string) map[string]string { return m } +func sampleMeta(stats map[string]string) map[string]interface{} { + meta := map[string]interface{}{"timestamp": time.Now().Unix()} + + txTs := strings.TrimSpace(stats["last_seen_ts"]) + if txTs != "" { + if f, ok := tryParseFlexibleFloat(txTs); ok { + meta["timestamp"] = int64(f) + } else { + meta["timestamp"] = txTs + } + } + + return meta +} + func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]string) { client := &http.Client{Timeout: 10 * time.Second} @@ -197,6 +212,7 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st // Difficulty from bits (works with hex or decimal) chainDiff := difficultyFromBitsString(chainTipBitsStr) + tsmeta := sampleMeta(stats) payload := map[string]interface{}{ // Basics @@ -207,35 +223,35 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st "utxos": map[string]interface{}{"value": metrics.UTXOs}, // /stats?blocks=11 (numeric series) - "stats_blocks": map[string]interface{}{"value": mustParseFloat(stats["blocks"])}, - "stats_transactions": map[string]interface{}{"value": mustParseFloat(stats["transactions"])}, - "stats_tps": map[string]interface{}{"value": mustParseFloat(stats["tps"])}, - "stats_volume": map[string]interface{}{"value": mustParseFloat(stats["volume"])}, - "stats_volume_koinu": map[string]interface{}{"value": mustParseFloat(stats["volume_koinu"])}, - "stats_median_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_block"])}, - "stats_avg_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_block"])}, - "stats_median_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_kb"])}, - "stats_avg_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_kb"])}, - "stats_outputs": map[string]interface{}{"value": mustParseFloat(stats["outputs"])}, - "stats_bytes": map[string]interface{}{"value": mustParseFloat(stats["bytes"])}, + "stats_blocks": map[string]interface{}{"value": mustParseFloat(stats["blocks"]), "meta": tsmeta}, + "stats_transactions": map[string]interface{}{"value": mustParseFloat(stats["transactions"]), "meta": tsmeta}, + "stats_tps": map[string]interface{}{"value": mustParseFloat(stats["tps"]), "meta": tsmeta}, + "stats_volume": map[string]interface{}{"value": mustParseFloat(stats["volume"]), "meta": tsmeta}, + "stats_volume_koinu": map[string]interface{}{"value": mustParseFloat(stats["volume_koinu"]), "meta": tsmeta}, + "stats_median_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_block"]), "meta": tsmeta}, + "stats_avg_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_block"]), "meta": tsmeta}, + "stats_median_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_kb"]), "meta": tsmeta}, + "stats_avg_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_kb"]), "meta": tsmeta}, + "stats_outputs": map[string]interface{}{"value": mustParseFloat(stats["outputs"]), "meta": tsmeta}, + "stats_bytes": map[string]interface{}{"value": mustParseFloat(stats["bytes"]), "meta": tsmeta}, // (we leave difficulty to the chain-tip section to avoid duplication) // /smpvStats (mempool + script types) - "smpv_enabled": map[string]interface{}{"value": mustParseFloat(stats["enabled"])}, - "smpv_mempool_txs": map[string]interface{}{"value": mustParseFloat(stats["mempool_txs"])}, - "smpv_watchers": map[string]interface{}{"value": mustParseFloat(stats["watchers"])}, - "smpv_confirmed": map[string]interface{}{"value": mustParseFloat(stats["confirmed"])}, - "smpv_unconfirmed": map[string]interface{}{"value": mustParseFloat(stats["unconfirmed"])}, - "smpv_total_bytes": map[string]interface{}{"value": mustParseFloat(stats["total_bytes"])}, - "smpv_last_seen_age_sec": map[string]interface{}{"value": mustParseFloat(stats["last_seen_age_sec"])}, - "smpv_types_p2pk": map[string]interface{}{"value": mustParseFloat(stats["types_p2pk"])}, - "smpv_types_p2pkh": map[string]interface{}{"value": mustParseFloat(stats["types_p2pkh"])}, - "smpv_types_p2sh": map[string]interface{}{"value": mustParseFloat(stats["types_p2sh"])}, - "smpv_types_multisig": map[string]interface{}{"value": mustParseFloat(stats["types_multisig"])}, - "smpv_types_op_return": map[string]interface{}{"value": mustParseFloat(stats["types_op_return"])}, - "smpv_types_nonstandard": map[string]interface{}{"value": mustParseFloat(stats["types_nonstandard"])}, - "smpv_types_vout_total": map[string]interface{}{"value": mustParseFloat(stats["types_vout_total"])}, - "smpv_coinbase_txs": map[string]interface{}{"value": mustParseFloat(stats["coinbase_txs"])}, + "smpv_enabled": map[string]interface{}{"value": mustParseFloat(stats["enabled"]), "meta": tsmeta}, + "smpv_mempool_txs": map[string]interface{}{"value": mustParseFloat(stats["mempool_txs"]), "meta": tsmeta}, + "smpv_watchers": map[string]interface{}{"value": mustParseFloat(stats["watchers"]), "meta": tsmeta}, + "smpv_confirmed": map[string]interface{}{"value": mustParseFloat(stats["confirmed"]), "meta": tsmeta}, + "smpv_unconfirmed": map[string]interface{}{"value": mustParseFloat(stats["unconfirmed"]), "meta": tsmeta}, + "smpv_total_bytes": map[string]interface{}{"value": mustParseFloat(stats["total_bytes"]), "meta": tsmeta}, + "smpv_last_seen_age_sec": map[string]interface{}{"value": mustParseFloat(stats["last_seen_age_sec"]), "meta": tsmeta}, + "smpv_types_p2pk": map[string]interface{}{"value": mustParseFloat(stats["types_p2pk"]), "meta": tsmeta}, + "smpv_types_p2pkh": map[string]interface{}{"value": mustParseFloat(stats["types_p2pkh"]), "meta": tsmeta}, + "smpv_types_p2sh": map[string]interface{}{"value": mustParseFloat(stats["types_p2sh"]), "meta": tsmeta}, + "smpv_types_multisig": map[string]interface{}{"value": mustParseFloat(stats["types_multisig"]), "meta": tsmeta}, + "smpv_types_op_return": map[string]interface{}{"value": mustParseFloat(stats["types_op_return"]), "meta": tsmeta}, + "smpv_types_nonstandard": map[string]interface{}{"value": mustParseFloat(stats["types_nonstandard"]), "meta": tsmeta}, + "smpv_types_vout_total": map[string]interface{}{"value": mustParseFloat(stats["types_vout_total"]), "meta": tsmeta}, + "smpv_coinbase_txs": map[string]interface{}{"value": mustParseFloat(stats["coinbase_txs"]), "meta": tsmeta}, // /chainStats (session totals) "headers_bytes": map[string]interface{}{"value": mustParseFloat(chain["headers_bytes"])}, From 6fd80a25665fa94d4737026067a3cbd79367b665 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:07:29 +0000 Subject: [PATCH 05/13] refine timestamp metadata to frontend-compatible numeric key Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/monitor/monitor.go | 9 +++++---- root/pkg/mod/cache/lock | 0 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 root/pkg/mod/cache/lock diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index e1b4f9a..ac91f87 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -187,15 +187,15 @@ func parseKeyValueLines(input string) map[string]string { return m } -func sampleMeta(stats map[string]string) map[string]interface{} { - meta := map[string]interface{}{"timestamp": time.Now().Unix()} +func sampleMeta(stats map[string]string, fallbackTs int64) map[string]interface{} { + meta := map[string]interface{}{"timestamp": fallbackTs} txTs := strings.TrimSpace(stats["last_seen_ts"]) if txTs != "" { if f, ok := tryParseFlexibleFloat(txTs); ok { meta["timestamp"] = int64(f) } else { - meta["timestamp"] = txTs + log.Printf("Failed to parse last_seen_ts as numeric timestamp: %s", txTs) } } @@ -212,7 +212,8 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st // Difficulty from bits (works with hex or decimal) chainDiff := difficultyFromBitsString(chainTipBitsStr) - tsmeta := sampleMeta(stats) + sampleTs := time.Now().Unix() + tsmeta := sampleMeta(stats, sampleTs) payload := map[string]interface{}{ // Basics diff --git a/root/pkg/mod/cache/lock b/root/pkg/mod/cache/lock new file mode 100644 index 0000000..e69de29 From f29a43544aaff35dc3f6dcdd0a7b75a12ccdd4c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:07:40 +0000 Subject: [PATCH 06/13] chore: remove accidental go module cache artifact Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- root/pkg/mod/cache/lock | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 root/pkg/mod/cache/lock diff --git a/root/pkg/mod/cache/lock b/root/pkg/mod/cache/lock deleted file mode 100644 index e69de29..0000000 From 842ac7321adacb6f2f238141feab83b7312b14e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:10:27 +0000 Subject: [PATCH 07/13] remove synthetic monitor timestamp fallback Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/monitor/monitor.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index ac91f87..3325730 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -187,19 +187,18 @@ func parseKeyValueLines(input string) map[string]string { return m } -func sampleMeta(stats map[string]string, fallbackTs int64) map[string]interface{} { - meta := map[string]interface{}{"timestamp": fallbackTs} - - txTs := strings.TrimSpace(stats["last_seen_ts"]) - if txTs != "" { - if f, ok := tryParseFlexibleFloat(txTs); ok { - meta["timestamp"] = int64(f) - } else { - log.Printf("Failed to parse last_seen_ts as numeric timestamp: %s", txTs) - } +func sampleMeta(stats map[string]string) map[string]interface{} { + lastSeenTs := strings.TrimSpace(stats["last_seen_ts"]) + if lastSeenTs == "" { + return nil + } + + if f, ok := tryParseFlexibleFloat(lastSeenTs); ok { + return map[string]interface{}{"timestamp": int64(f)} } - return meta + log.Printf("Failed to parse last_seen_ts as numeric timestamp: %s", lastSeenTs) + return nil } func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]string) { @@ -212,8 +211,7 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st // Difficulty from bits (works with hex or decimal) chainDiff := difficultyFromBitsString(chainTipBitsStr) - sampleTs := time.Now().Unix() - tsmeta := sampleMeta(stats, sampleTs) + tsmeta := sampleMeta(stats) payload := map[string]interface{}{ // Basics From 0eec300b67b13dfcb0cb6ccb74894b865779017e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:11:38 +0000 Subject: [PATCH 08/13] handle missing timestamp metadata without synthetic fallback Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/monitor/monitor.go | 4 ++-- root/pkg/mod/cache/lock | 0 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 root/pkg/mod/cache/lock diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index 3325730..1d1db4f 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -190,7 +190,7 @@ func parseKeyValueLines(input string) map[string]string { func sampleMeta(stats map[string]string) map[string]interface{} { lastSeenTs := strings.TrimSpace(stats["last_seen_ts"]) if lastSeenTs == "" { - return nil + return map[string]interface{}{} } if f, ok := tryParseFlexibleFloat(lastSeenTs); ok { @@ -198,7 +198,7 @@ func sampleMeta(stats map[string]string) map[string]interface{} { } log.Printf("Failed to parse last_seen_ts as numeric timestamp: %s", lastSeenTs) - return nil + return map[string]interface{}{} } func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]string) { diff --git a/root/pkg/mod/cache/lock b/root/pkg/mod/cache/lock new file mode 100644 index 0000000..e69de29 From cf8433b98cb911b129e947993158b6a989a83eed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:11:48 +0000 Subject: [PATCH 09/13] chore: remove accidental go cache lock artifact Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- root/pkg/mod/cache/lock | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 root/pkg/mod/cache/lock diff --git a/root/pkg/mod/cache/lock b/root/pkg/mod/cache/lock deleted file mode 100644 index e69de29..0000000 From 8c52b6edc8f20dec857b343e478e05fcb8f4fe31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:18:53 +0000 Subject: [PATCH 10/13] Remove metric metadata from dashboard monitor payload Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/monitor/monitor.go | 68 ++++++++++++++---------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index 1d1db4f..3e7ba91 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -187,20 +187,6 @@ func parseKeyValueLines(input string) map[string]string { return m } -func sampleMeta(stats map[string]string) map[string]interface{} { - lastSeenTs := strings.TrimSpace(stats["last_seen_ts"]) - if lastSeenTs == "" { - return map[string]interface{}{} - } - - if f, ok := tryParseFlexibleFloat(lastSeenTs); ok { - return map[string]interface{}{"timestamp": int64(f)} - } - - log.Printf("Failed to parse last_seen_ts as numeric timestamp: %s", lastSeenTs) - return map[string]interface{}{} -} - func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]string) { client := &http.Client{Timeout: 10 * time.Second} @@ -211,8 +197,6 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st // Difficulty from bits (works with hex or decimal) chainDiff := difficultyFromBitsString(chainTipBitsStr) - tsmeta := sampleMeta(stats) - payload := map[string]interface{}{ // Basics "chaintip": map[string]interface{}{"value": metrics.Chaintip}, @@ -222,35 +206,35 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st "utxos": map[string]interface{}{"value": metrics.UTXOs}, // /stats?blocks=11 (numeric series) - "stats_blocks": map[string]interface{}{"value": mustParseFloat(stats["blocks"]), "meta": tsmeta}, - "stats_transactions": map[string]interface{}{"value": mustParseFloat(stats["transactions"]), "meta": tsmeta}, - "stats_tps": map[string]interface{}{"value": mustParseFloat(stats["tps"]), "meta": tsmeta}, - "stats_volume": map[string]interface{}{"value": mustParseFloat(stats["volume"]), "meta": tsmeta}, - "stats_volume_koinu": map[string]interface{}{"value": mustParseFloat(stats["volume_koinu"]), "meta": tsmeta}, - "stats_median_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_block"]), "meta": tsmeta}, - "stats_avg_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_block"]), "meta": tsmeta}, - "stats_median_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_kb"]), "meta": tsmeta}, - "stats_avg_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_kb"]), "meta": tsmeta}, - "stats_outputs": map[string]interface{}{"value": mustParseFloat(stats["outputs"]), "meta": tsmeta}, - "stats_bytes": map[string]interface{}{"value": mustParseFloat(stats["bytes"]), "meta": tsmeta}, + "stats_blocks": map[string]interface{}{"value": mustParseFloat(stats["blocks"])}, + "stats_transactions": map[string]interface{}{"value": mustParseFloat(stats["transactions"])}, + "stats_tps": map[string]interface{}{"value": mustParseFloat(stats["tps"])}, + "stats_volume": map[string]interface{}{"value": mustParseFloat(stats["volume"])}, + "stats_volume_koinu": map[string]interface{}{"value": mustParseFloat(stats["volume_koinu"])}, + "stats_median_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_block"])}, + "stats_avg_fee_per_block": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_block"])}, + "stats_median_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["median_fee_per_kb"])}, + "stats_avg_fee_per_kb": map[string]interface{}{"value": mustParseFloat(stats["avg_fee_per_kb"])}, + "stats_outputs": map[string]interface{}{"value": mustParseFloat(stats["outputs"])}, + "stats_bytes": map[string]interface{}{"value": mustParseFloat(stats["bytes"])}, // (we leave difficulty to the chain-tip section to avoid duplication) // /smpvStats (mempool + script types) - "smpv_enabled": map[string]interface{}{"value": mustParseFloat(stats["enabled"]), "meta": tsmeta}, - "smpv_mempool_txs": map[string]interface{}{"value": mustParseFloat(stats["mempool_txs"]), "meta": tsmeta}, - "smpv_watchers": map[string]interface{}{"value": mustParseFloat(stats["watchers"]), "meta": tsmeta}, - "smpv_confirmed": map[string]interface{}{"value": mustParseFloat(stats["confirmed"]), "meta": tsmeta}, - "smpv_unconfirmed": map[string]interface{}{"value": mustParseFloat(stats["unconfirmed"]), "meta": tsmeta}, - "smpv_total_bytes": map[string]interface{}{"value": mustParseFloat(stats["total_bytes"]), "meta": tsmeta}, - "smpv_last_seen_age_sec": map[string]interface{}{"value": mustParseFloat(stats["last_seen_age_sec"]), "meta": tsmeta}, - "smpv_types_p2pk": map[string]interface{}{"value": mustParseFloat(stats["types_p2pk"]), "meta": tsmeta}, - "smpv_types_p2pkh": map[string]interface{}{"value": mustParseFloat(stats["types_p2pkh"]), "meta": tsmeta}, - "smpv_types_p2sh": map[string]interface{}{"value": mustParseFloat(stats["types_p2sh"]), "meta": tsmeta}, - "smpv_types_multisig": map[string]interface{}{"value": mustParseFloat(stats["types_multisig"]), "meta": tsmeta}, - "smpv_types_op_return": map[string]interface{}{"value": mustParseFloat(stats["types_op_return"]), "meta": tsmeta}, - "smpv_types_nonstandard": map[string]interface{}{"value": mustParseFloat(stats["types_nonstandard"]), "meta": tsmeta}, - "smpv_types_vout_total": map[string]interface{}{"value": mustParseFloat(stats["types_vout_total"]), "meta": tsmeta}, - "smpv_coinbase_txs": map[string]interface{}{"value": mustParseFloat(stats["coinbase_txs"]), "meta": tsmeta}, + "smpv_enabled": map[string]interface{}{"value": mustParseFloat(stats["enabled"])}, + "smpv_mempool_txs": map[string]interface{}{"value": mustParseFloat(stats["mempool_txs"])}, + "smpv_watchers": map[string]interface{}{"value": mustParseFloat(stats["watchers"])}, + "smpv_confirmed": map[string]interface{}{"value": mustParseFloat(stats["confirmed"])}, + "smpv_unconfirmed": map[string]interface{}{"value": mustParseFloat(stats["unconfirmed"])}, + "smpv_total_bytes": map[string]interface{}{"value": mustParseFloat(stats["total_bytes"])}, + "smpv_last_seen_age_sec": map[string]interface{}{"value": mustParseFloat(stats["last_seen_age_sec"])}, + "smpv_types_p2pk": map[string]interface{}{"value": mustParseFloat(stats["types_p2pk"])}, + "smpv_types_p2pkh": map[string]interface{}{"value": mustParseFloat(stats["types_p2pkh"])}, + "smpv_types_p2sh": map[string]interface{}{"value": mustParseFloat(stats["types_p2sh"])}, + "smpv_types_multisig": map[string]interface{}{"value": mustParseFloat(stats["types_multisig"])}, + "smpv_types_op_return": map[string]interface{}{"value": mustParseFloat(stats["types_op_return"])}, + "smpv_types_nonstandard": map[string]interface{}{"value": mustParseFloat(stats["types_nonstandard"])}, + "smpv_types_vout_total": map[string]interface{}{"value": mustParseFloat(stats["types_vout_total"])}, + "smpv_coinbase_txs": map[string]interface{}{"value": mustParseFloat(stats["coinbase_txs"])}, // /chainStats (session totals) "headers_bytes": map[string]interface{}{"value": mustParseFloat(chain["headers_bytes"])}, From 4fc2c83eccdea19b9f135c5981083e5efa2921b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:19:58 +0000 Subject: [PATCH 11/13] chore: finalize metadata removal validation Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- root/pkg/mod/cache/lock | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 root/pkg/mod/cache/lock diff --git a/root/pkg/mod/cache/lock b/root/pkg/mod/cache/lock new file mode 100644 index 0000000..e69de29 From fd10b91453b51d629d076d87c8153fb981f282ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 19:20:23 +0000 Subject: [PATCH 12/13] Remove accidental cache lock artifact from PR Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- .gitignore | 1 + root/pkg/mod/cache/lock | 0 2 files changed, 1 insertion(+) delete mode 100644 root/pkg/mod/cache/lock diff --git a/.gitignore b/.gitignore index d5fec98..5307e53 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store result +root/pkg/mod/cache/lock diff --git a/root/pkg/mod/cache/lock b/root/pkg/mod/cache/lock deleted file mode 100644 index e69de29..0000000 From c80d2a6b78f12feb104a6c118b9178848311e466 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:50:46 +0000 Subject: [PATCH 13/13] Use stats endpoint with 100-block window Co-authored-by: edtubbs <84785904+edtubbs@users.noreply.github.com> --- dashboard/monitor/monitor.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dashboard/monitor/monitor.go b/dashboard/monitor/monitor.go index 3e7ba91..5cddfd1 100644 --- a/dashboard/monitor/monitor.go +++ b/dashboard/monitor/monitor.go @@ -93,8 +93,8 @@ func collectMetrics() (Metrics, map[string]string, map[string]string, error) { } metrics.UTXOs, _ = parseUTXOsOrTxs(utxosStr) - // Stats over last N blocks - statsStr, err := fetchEndpoint(fmt.Sprintf("/stats24")) + // Stats over a 100-block window + statsStr, err := fetchEndpoint("/stats?blocks=100") if err != nil { return metrics, nil, nil, err } @@ -205,7 +205,7 @@ func submitMetrics(metrics Metrics, stats map[string]string, chain map[string]st "transactions": map[string]interface{}{"value": metrics.Transactions}, "utxos": map[string]interface{}{"value": metrics.UTXOs}, - // /stats?blocks=11 (numeric series) + // /stats?blocks=100 (numeric series) "stats_blocks": map[string]interface{}{"value": mustParseFloat(stats["blocks"])}, "stats_transactions": map[string]interface{}{"value": mustParseFloat(stats["transactions"])}, "stats_tps": map[string]interface{}{"value": mustParseFloat(stats["tps"])},