Repository navigation
Harden autoforward/autoconvert for unattended operation - #1
Merged
Merged
Conversation
deploy.yml SSHes into MAGIC Grants' VPS on every push to main; in this fork it has no secrets and would fail (or, worse, be pointed somewhere). Cake deploys this from cake-tech/ansible. CI now byte-compiles, runs pyflakes and validates the compose file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
:latest swaps the wallet software under a hot wallet whenever anyone rebuilds. VERSION is a build arg (MONERO_WALLET_RPC_VERSION in compose). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in production (Cake Pay, 2026-10): - Monero payments silently stopped being seen for 11 days. The merchant (BTCPay) issues one subaddress per invoice; a run of unpaid invoices pushed new payments past the wallet's default 200 minor lookahead. MONERO_SUBADDRESS_LOOKAHEAD is applied right after a restore, and the loop now reports the highest subaddress that has received funds so a monitor can compare it with the merchant wallet's. - monero-wallet-rpc only writes its cache on close; the prod wallet had not been stored for 35 days. It is now stored hourly and after sweeps. - No request had a timeout, and every coin shares one loop, so a single hung RPC stalls all coins indefinitely. HTTP_TIMEOUT / SEND_TIMEOUT. - autoconvert crashed on a non-JSON Kraken response from load_balances(), which ran outside any try. A Kraken outage now costs one pass. - The intermediary route never stopped after a successful sale (it went on re-selling a spent balance through every other intermediary), and checked the second leg against the asset's minimum instead of the intermediary balance's. - Kraken has been failing MWEB peg-out deposits. AUTOFORWARD_COINS enables wallets individually (startup waits and seed imports follow it); AUTOCONVERT_ASSETS does the same for sales, e.g. to add ZEC. - MIN_*_SEND_AMOUNT and MONERO_WALLET_FILENAME were edited into constants.py on the host; they are environment settings now. - compose never passed MAX_NETWORK_FEE_PERCENT, MAX_SLIPPAGE_PERCENT or the fee-source settings to the containers, so .env values for them were ignored. They are passed through now. Each program writes STATUS_DIR/<program>.json (heartbeat, per-section last_ok/last_error/errors_total, balances, last forward/sale) for monitoring. Also: pyflakes clean-ups and copy-pasted log messages in the seed importer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DRY_RUN does everything a live pass does except the parts that move funds: Electrum transactions are built, fee-checked and signed but not broadcast; Monero sweeps are built with do_not_relay (wallet-rpc then skips commit_tx, so nothing is relayed or marked spent); Kraken orders go out with validate=true; no new Kraken deposit address is created. With no Kraken key a dry run forwards to the wallet's own address and autoconvert idles. Default ON: a staging box or a fresh deploy that forgets the setting must not move money. Every status file reports dry_run, so monitoring can tell a prod host that silently is not forwarding. tests/test_dry_run.py runs each forward/sale path against fake Electrum/Monero/Kraken endpoints at the HTTP boundary and asserts no fund-moving request went out; each scenario also runs with DRY_RUN=0 and must then produce one, so a path that gives up early cannot pass. Removing any one guard (broadcast, do_not_relay, validate) fails it. Also: autoforward's loop is now under main() so it can be imported, and get_monero_unused_address() returned the RPC result dict instead of the address (upstream's TESTNET path never worked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simple-monero-wallet-rpc's pinned v0.18.5.1 image no longer ships fixuid; it starts as root and its own /entrypoint.sh adds --non-interactive and the bind flags, chowns the volume to PUID/PGID (1000, the old monero uid) and drops privileges with su-exec. This wrapper still exec'd fixuid, so wallet-rpc crash-looped on 'fixuid: not found' - found on btcpay-stg, and it would have taken production's wallet down at cutover. Now it only adds --testnet and hands off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found testing on btcpay-stg and confirmed on Cake Pay production: - Gap limit. The LTC chain had a run of 1093 unpaid invoice addresses, past Electrum's 1000, so Electrum stopped there and never saw the 2859 outputs (4441 LTC) paid since mid-2025; staging's BTC wallet missed a payment at index 4975 the same way. ELECTRUM_GAP_LIMIT is now applied at every startup, not only at import, so raising it reaches live wallets. - Batching. Sweeping that backlog in one transaction (~195 kvB) would exceed the 100 kvB standard size and never relay. Each pass now spends at most ELECTRUM_MAX_INPUTS outputs, largest first. - MWEB. Kraken fails MWEB peg-out deposits (173 of them since 2025-07), so MWEB is no longer sent to Kraken: it is pegged out to a CHANGE address of the LTC wallet (invoices only get receiving addresses, so it can't be taken for a payment), and the LTC pass forwards it in a standard transaction once mature. LTC outputs need LITECOIN_MIN_CONFIRMATIONS (6): consensus rejects spending a peg-out before PEGOUT_MATURITY. - Volumes. Compose mounted the LTC volumes at /home/electrum-ltc/.electrum; the image's user writes /home/electrum/.electrum-ltc, so both LTC wallets lived in their containers and a recreate lost them. - Each wallet's tracked receiving-address count is reported hourly, for a monitor that compares it with the merchant's address index. Tests: LTC waits for peg-out maturity, a 1000-output backlog goes out 400 at a time largest first, MWEB goes to an LTC change address and never to Kraken; each fails when its rule is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The image's /entrypoint.sh is byte-identical to v0.18.5.1's, so the wrapper's hand-off is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lets a test bed run the whole MWEB path - the real peg-out to an LTC change address, its 6-block maturity and the LTC pass that forwards it - while every send to the exchange stays a dry run. A peg-out only moves coins between the operator's own wallets. Off by default; reported as pegout_live in the status file. Tests: a peg-out is broadcast, BTC/LTC/ XMR forwards stay dry under the same setting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both found on btcpay-stg by breaking backends on purpose: - A restarted Electrum daemon answers 'wallet not loaded', and wallets were loaded only at startup, so the coin stayed dead until this program restarted (the old runbook's 'wallet isn't loaded on startup'). Any call that gets 'wallet not loaded' now loads the wallet and retries once. - With monero-wallet-rpc paused, no pass completed for 12+ minutes: the receipts read carried the 600 s send timeout, then each later call its own. Each Monero pass now starts with a 15 s get_height probe (PROBE_TIMEOUT), and only the first full receipts read gets the long timeout. Tests for both; each fails without its fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…exists' Found by rebuilding a Monero wallet on btcpay-stg: the deploy recreated wallet-rpc as soon as the restored keys file appeared, while the importer was still ~70 s into set_subaddress_lookahead, so the lookahead was never applied and the rebuilt wallet was blind past index 200 - the very miss it exists to prevent - and nothing noticed, because every failure was printed and the importer still exited 0. It now exits 1 if any import fails, so a deploy can wait for it and check. Monero's 'Wallet already exists.' / 'No wallet dir configured' (opened by --wallet-file) were reported as import errors on every deploy; they are 'already set up' now. (The Electrum check only knew Electrum's message.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First set of Cake changes on our fork of MAGICGrants/autoforward-autoconvert. Everything here came out of the 2026-10-20 investigation on btcpay-usa-01; see the commit messages for detail.
What prompted it
set_subaddress_lookahead50:20000 plus a rescan.Failure: 173 deposits, 716 LTC, all MWEB peg-outs. That is being taken up with Kraken separately; this PR adds the switch to stop sending them.Changes
AUTOFORWARD_COINS/AUTOCONVERT_ASSETS: enable wallets and Kraken assets individually. Startup waits and seed imports follow the coin list.MONERO_SUBADDRESS_LOOKAHEAD: applied after a restore.store(hourly and after each sweep).HTTP_TIMEOUT,SEND_TIMEOUT).constants.py.status/<program>.jsonheartbeat and health for Zabbix, including the Monero wallet'smax_received_subaddress, to compare with BTCPay's.:latest.Not in this PR
.envand run this at a pinned commit.Testing
py_compile,pyflakesclean,docker compose configOK.--checkdeploy via Ansible first.🤖 Generated with Claude Code