|
1 | | -# goduckbot |
2 | | -duckbot using Go and a local node |
| 1 | +# duckBot |
| 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. |
| 4 | + |
| 5 | +```text |
| 6 | +Quack!(attention) |
| 7 | +duckBot notification! |
| 8 | +
|
| 9 | +DuckPool |
| 10 | +New Block! |
| 11 | +
|
| 12 | +Tx Count: 14 |
| 13 | +Block Size: 42.69 KB |
| 14 | +48.53% Full |
| 15 | +Interval: 23 seconds |
| 16 | +
|
| 17 | +Epoch Blocks: 7 |
| 18 | +Lifetime Blocks: 1,337 |
| 19 | +``` |
| 20 | + |
| 21 | +## What It Does |
| 22 | + |
| 23 | +**Block Notifications** — Every time your pool mints a block, duckBot fires off a notification to Telegram (with a random duck pic) and optionally Twitter/X. Includes tx count, block size, fill percentage, interval since last block, and running epoch/lifetime tallies. |
| 24 | + |
| 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 | + |
| 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). |
| 28 | + |
| 29 | +**WebSocket Feed** — Broadcasts all block events over WebSocket for custom integrations and dashboards. |
| 30 | + |
| 31 | +**Node Failover** — Supports multiple node addresses with exponential backoff retry. If the primary goes down, duckBot rolls over to the backup. |
| 32 | + |
| 33 | +## Architecture |
| 34 | + |
| 35 | +| File | What It Does | |
| 36 | +| -------------- | ----------------------------------------------------------------------- | |
| 37 | +| `main.go` | Config, chain sync (adder), block notifications, leaderlog, WebSocket | |
| 38 | +| `leaderlog.go` | CPRAOS schedule math, VRF key parsing, epoch/slot calculations | |
| 39 | +| `nonce.go` | Nonce evolution: VRF accumulation, candidate freeze, final derivation | |
| 40 | +| `db.go` | PostgreSQL: blocks, epoch nonces, leader schedules (upserts) | |
| 41 | + |
| 42 | +## Quick Start |
| 43 | + |
| 44 | +### Prerequisites |
| 45 | + |
| 46 | +- Go 1.24+ |
| 47 | +- Access to a Cardano node (N2N protocol) |
| 48 | +- Telegram bot token and channel |
| 49 | +- PostgreSQL (if using leaderlog) |
| 50 | +- Your pool's `vrf.skey` (if using leaderlog) |
| 51 | + |
| 52 | +### Build |
| 53 | + |
| 54 | +```bash |
| 55 | +CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o goduckbot . |
| 56 | +``` |
| 57 | + |
| 58 | +### Configure |
| 59 | + |
| 60 | +Copy the example config and fill in your values: |
| 61 | + |
| 62 | +```bash |
| 63 | +cp config.yaml.example config.yaml |
| 64 | +``` |
| 65 | + |
| 66 | +```yaml |
| 67 | +poolId: "YOUR_POOL_ID_HEX" |
| 68 | +ticker: "DUCK" |
| 69 | +poolName: "DuckPool" |
| 70 | +nodeAddress: |
| 71 | + host1: "your-node:3001" |
| 72 | + host2: "backup-node:3001" # optional |
| 73 | +networkMagic: 764824073 # mainnet=764824073, preprod=1, preview=2 |
| 74 | + |
| 75 | +telegram: |
| 76 | + channel: "-100XXXXXXXXXX" |
| 77 | + |
| 78 | +leaderlog: |
| 79 | + enabled: true |
| 80 | + vrfKeyPath: "/keys/vrf.skey" |
| 81 | + timezone: "America/New_York" |
| 82 | + |
| 83 | +database: |
| 84 | + host: "localhost" |
| 85 | + port: 5432 |
| 86 | + name: "goduckbot" |
| 87 | + user: "goduckbot" |
| 88 | + password: "" # prefer GODUCKBOT_DB_PASSWORD env var |
| 89 | +``` |
| 90 | +
|
| 91 | +### Environment Variables |
| 92 | +
|
| 93 | +| Variable | Purpose | |
| 94 | +| ------------------------------ | -------------------------------------------------- | |
| 95 | +| `TELEGRAM_TOKEN` | Telegram bot API token | |
| 96 | +| `GODUCKBOT_DB_PASSWORD` | PostgreSQL password (recommended over config file) | |
| 97 | +| `TWITTER_API_KEY` | Twitter/X API key (optional) | |
| 98 | +| `TWITTER_API_KEY_SECRET` | Twitter/X API secret (optional) | |
| 99 | +| `TWITTER_ACCESS_TOKEN` | Twitter/X access token (optional) | |
| 100 | +| `TWITTER_ACCESS_TOKEN_SECRET` | Twitter/X access token secret (optional) | |
| 101 | + |
| 102 | +### Run |
| 103 | + |
| 104 | +```bash |
| 105 | +./goduckbot |
| 106 | +``` |
| 107 | + |
| 108 | +## Docker |
| 109 | + |
| 110 | +```bash |
| 111 | +# Single arch |
| 112 | +docker build -t goduckbot . |
| 113 | +
|
| 114 | +# Multi-arch with push |
| 115 | +docker buildx build --platform linux/amd64,linux/arm64 \ |
| 116 | + -t wcatz/goduckbot:latest --push . |
| 117 | +``` |
| 118 | + |
| 119 | +## Helm Chart |
| 120 | + |
| 121 | +duckBot ships with a Helm chart for Kubernetes deployments. |
| 122 | + |
| 123 | +```bash |
| 124 | +# Package and push |
| 125 | +helm package helm-chart/ --version 0.2.0 |
| 126 | +helm push goduckbot-0.2.0.tgz oci://ghcr.io/wcatz/helm-charts |
| 127 | +
|
| 128 | +# Deploy via helmfile |
| 129 | +helmfile -e apps -l app=duckbot apply |
| 130 | +``` |
| 131 | + |
| 132 | +Key chart values for leaderlog: |
| 133 | + |
| 134 | +- `config.leaderlog.enabled` — enable VRF tracking and schedule calculation |
| 135 | +- `config.database.*` — PostgreSQL connection settings |
| 136 | +- `vrfKey.secretName` — K8s secret containing your `vrf.skey` |
| 137 | + |
| 138 | +## How CPRAOS Works (the short version) |
| 139 | + |
| 140 | +For each slot in the upcoming epoch: |
| 141 | + |
| 142 | +```text |
| 143 | +VRF input = BLAKE2b-256(slot || epochNonce) |
| 144 | +VRF output = VRF.Prove(vrfSkey, vrfInput) |
| 145 | +Leader value = BLAKE2b-256("L" || vrfOutput) |
| 146 | +Threshold = 2^256 * (1 - (1 - f)^sigma) |
| 147 | +Is leader = leaderValue < threshold |
| 148 | +``` |
| 149 | + |
| 150 | +Where `f` = active slot coefficient (0.05), `sigma` = pool's relative stake, and the `"L"` prefix is literally byte `0x4C`. This is CPRAOS (256-bit), not TPraos (512-bit) — an important distinction from some other implementations. |
| 151 | + |
| 152 | +## Supported Networks |
| 153 | + |
| 154 | +| Network | Magic | Epoch Length | Notes | |
| 155 | +| ------- | ------------- | ------------- | ------------------------------- | |
| 156 | +| Mainnet | `764824073` | 432,000 slots | Byron era offset from epoch 208 | |
| 157 | +| Preprod | `1` | 432,000 slots | Byron era offset from epoch 4 | |
| 158 | +| Preview | `2` | 86,400 slots | No Byron era | |
| 159 | + |
| 160 | +## Dependencies |
| 161 | + |
| 162 | +Built on the shoulders of: |
| 163 | + |
| 164 | +- [blinklabs-io/adder](https://github.com/blinklabs-io/adder) — Chain sync pipeline |
| 165 | +- [blinklabs-io/gouroboros](https://github.com/blinklabs-io/gouroboros) — Ouroboros protocol, VRF, ledger types |
| 166 | +- [cardano-community/koios-go-client](https://github.com/cardano-community/koios-go-client) — Koios API for stake data and nonce fallback |
| 167 | +- [jackc/pgx](https://github.com/jackc/pgx) — PostgreSQL driver |
| 168 | +- [random-d.uk](https://random-d.uk) — The ducks |
| 169 | + |
| 170 | +## Credits |
| 171 | + |
| 172 | +Early development work by [@wael](https://github.com/wael) |
| 173 | + |
| 174 | +## License |
| 175 | + |
| 176 | +MIT |
0 commit comments