You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cardano stake pool notification bot with chain sync via adder. Calculates leader schedules using CPRAOS and posts to Telegram.
3
+
Cardano stake pool notification bot with chain sync and built-in CPRAOS leaderlog calculation. Supports two modes: **lite** (adder tail + Koios fallback) and **full** (historical Shelley-to-tip sync via gouroboros NtN).
- VRF nonce evolution tracked per block in PostgreSQL
69
+
- VRF nonce evolution tracked per block in SQLite or PostgreSQL
19
70
- Koios API integration for stake data and nonce fallback
20
71
- WebSocket broadcast for block events
21
72
- Telegram message chunking for messages >4096 chars
@@ -34,21 +85,30 @@ Is leader = leaderValue < threshold
34
85
```
35
86
36
87
### Nonce Evolution
37
-
Per block: `nonceValue = BLAKE2b-256(0x4E || vrfOutput)`, then `eta_v = BLAKE2b-256(eta_v || nonceValue)`. Candidate nonce freezes at 70% epoch progress (stability window). Koios used as fallback when local nonce unavailable.
88
+
Per block: `nonceValue = BLAKE2b-256(0x4E || vrfOutput)`, then `eta_v = BLAKE2b-256(eta_v || nonceValue)`. Rolling eta_v accumulates across epoch boundaries (no reset). Candidate nonce freezes at 70% epoch progress (stability window). Koios used as fallback when local nonce unavailable.
38
89
39
90
### Trigger Flow
40
91
1. Every block: extract VRF output from header, update evolving nonce
41
92
2. At 70% epoch progress: freeze candidate nonce
42
93
3. After freeze: calculate next epoch schedule (mutex-guarded, one goroutine per epoch)
43
-
4. Post schedule to Telegram, store in PostgreSQL
94
+
4. Post schedule to Telegram, store in database
44
95
45
96
### Race Condition Prevention
46
97
`checkLeaderlogTrigger` fires on every block after 70% — uses `leaderlogMu` mutex + `leaderlogCalcing` map to ensure only one goroutine runs per epoch. Map entry is cleaned up after goroutine completes.
47
98
48
99
### VRF Extraction by Era
100
+
101
+
Two extraction paths depending on sync mode:
102
+
103
+
**Live tail (adder)** — `extractVrfOutput()` in `main.go`:
104
+
- Must extract from `event.Event` payload BEFORE JSON marshal (`Block` field is `json:"-"`)
- Must extract from `event.Event` payload BEFORE JSON marshal (`Block` field is `json:"-"`)
107
+
108
+
**Historical sync (gouroboros NtN)** — `extractVrfFromHeader()` in `sync.go`:
109
+
- Receives `ledger.BlockHeader` directly, type-asserts to era-specific header
110
+
- Same VRF field access pattern as adder path
111
+
- Skips Byron blocks (no VRF data)
52
112
53
113
### Network Constants
54
114
@@ -66,6 +126,11 @@ Constants defined in `leaderlog.go`: `MainnetNetworkMagic`, `PreprodNetworkMagic
66
126
-**Preprod**: Genesis 2022-06-01T00:00:00Z, Byron slots at 20s (4 epochs), Shelley at 1s
67
127
-**Preview**: Genesis 2022-11-01T00:00:00Z, all slots at 1s (no Byron era)
68
128
129
+
### Slot/Epoch Math
130
+
-`GetEpochStartSlot(epoch, networkMagic)` — first slot of an epoch, accounts for Byron offset
131
+
-`SlotToEpoch(slot, networkMagic)` — inverse, determines epoch from slot number
132
+
-`GetEpochLength(networkMagic)` — returns epoch length for network
133
+
69
134
### Data Sources
70
135
71
136
| Data | Source | Notes |
@@ -75,16 +140,16 @@ Constants defined in `leaderlog.go`: `MainnetNetworkMagic`, `PreprodNetworkMagic
75
140
| Pool stake | Koios `GetPoolInfo`|`ActiveStake` is `decimal.Decimal`, use `.IntPart()`|
76
141
| Total stake | Koios `GetEpochInfo`|`ActiveStake` is `decimal.Decimal`, use `.IntPart()`|
77
142
78
-
### Database Schema (auto-created by `InitDB`)
143
+
### Database Schema (auto-created by Store constructors)
79
144
-`blocks` — per-block VRF data (slot, epoch, block_hash, vrf_output, nonce_value)
80
145
-`epoch_nonces` — evolving/candidate/final nonces per epoch with source tracking
81
-
-`leader_schedules` — calculated schedules with slots JSONB, posted flag
82
-
83
-
All INSERT operations use `ON CONFLICT` (upsert) for idempotency on restarts.
146
+
-`leader_schedules` — calculated schedules with slots JSON, posted flag
84
147
85
148
## Config
86
149
87
150
```yaml
151
+
mode: "lite"# "lite" or "full"
152
+
88
153
poolId: "POOL_ID_HEX"
89
154
ticker: "TICKER"
90
155
poolName: "Pool Name"
@@ -94,32 +159,43 @@ nodeAddress:
94
159
networkMagic: 764824073
95
160
96
161
telegram:
162
+
enabled: true # toggle Telegram notifications
97
163
channel: "CHANNEL_ID"
164
+
# token via TELEGRAM_TOKEN env var
165
+
98
166
twitter:
99
-
consumer_key: "..."# etc.
167
+
enabled: false # toggle Twitter notifications
168
+
# credentials via env vars
100
169
101
170
leaderlog:
102
171
enabled: true
103
172
vrfKeyPath: "/keys/vrf.skey"
104
173
timezone: "America/New_York"
105
174
106
175
database:
176
+
driver: "sqlite"# "sqlite" (default) or "postgres"
177
+
path: "./goduckbot.db"# SQLite file path
178
+
# PostgreSQL settings (driver: postgres)
107
179
host: "postgres-host"
108
180
port: 5432
109
181
name: "goduckbot"
110
182
user: "goduckbot"
111
-
password: ""# Prefer GODUCKBOT_DB_PASSWORD env var
183
+
password: ""# Prefer GODUCKBOT_DB_PASSWORD env var
112
184
```
113
185
114
186
**Note:** Database password uses `net/url.URL` for URL-safe encoding in connection strings. Passwords with special characters (`@`, `:`, `/`) are handled correctly.
115
187
116
-
## Helm Chart (v0.2.0)
188
+
## Helm Chart (v0.3.0)
117
189
118
-
Key values for leaderlog:
190
+
Key values:
191
+
- `config.mode`— "lite" (default) or "full"
192
+
- `config.telegram.enabled`/ `config.twitter.enabled` — social network toggles (env vars conditionally injected)
119
193
- `config.leaderlog.enabled`— enables VRF tracking and schedule calculation
120
-
- `config.database.*`— PostgreSQL connection (password via SOPS secret)
194
+
- `config.database.driver`— "sqlite" (default) or "postgres"
Copy file name to clipboardExpand all lines: README.md
+54-17Lines changed: 54 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# duckBot
2
2
3
-
A Cardano stake pool operator's companion. Real-time block notifications, built-in CPRAOS leader schedule calculation, and nonce evolution tracking — all from a single Go binary syncing directly off your local node.
3
+
A Cardano stake pool operator's companion. Real-time block notifications, built-in CPRAOS leader schedule calculation, and full chain nonce history — all from a single Go binary syncing directly off your local node.
4
4
5
5
```text
6
6
Quack!(attention)
@@ -24,29 +24,39 @@ Lifetime Blocks: 1,337
24
24
25
25
**Leader Schedule Calculation** — Built-in CPRAOS implementation calculates your upcoming slot assignments without needing cncli as a sidecar. Validated against cncli on both preview and mainnet. Posts the full schedule to Telegram with local timezone support and message chunking for large schedules.
26
26
27
-
**Nonce Evolution** — Tracks the evolving nonce per-block by accumulating VRF outputs through BLAKE2b-256 hashing. Freezes the candidate nonce at the stability window (70% epoch progress), then triggers leader schedule calculation for the next epoch. Falls back to Koios when local data isn't available (e.g., after a restart mid-epoch).
27
+
**Full Chain Nonce History** — In full mode, syncs every block from Shelley genesis using gouroboros NtN ChainSync, building a complete local nonce evolution history. Enables retroactive leaderlog calculation and missed block detection. In lite mode, tails the chain from tip and falls back to Koios when needed.
28
+
29
+
**Nonce Evolution** — Tracks the evolving nonce per-block by accumulating VRF outputs through BLAKE2b-256 hashing. Freezes the candidate nonce at the stability window (70% epoch progress), then triggers leader schedule calculation for the next epoch.
28
30
29
31
**WebSocket Feed** — Broadcasts all block events over WebSocket for custom integrations and dashboards.
30
32
31
33
**Node Failover** — Supports multiple node addresses with exponential backoff retry. If the primary goes down, duckBot rolls over to the backup.
32
34
35
+
## Operating Modes
36
+
37
+
| Mode | Description |
38
+
| ---- | ----------- |
39
+
|**lite** (default) | Tails chain from tip via adder. Uses Koios API for epoch nonces. No historical data. |
40
+
|**full**| Historical sync from Shelley genesis via gouroboros NtN. Builds complete local nonce history. Transitions to live tail once caught up. |
0 commit comments