From 5febac5ed790dac6c53c51700ade613e66e2105d Mon Sep 17 00:00:00 2001 From: michael moore Date: Mon, 19 Oct 2026 16:58:31 +0000 Subject: [PATCH] Offender rule: pool-only work counts at 10% of 200 shares, not 0.5% of 50 Every build, v0.5.0beta included, mines the odd pool-only share right after a reconnect or a new height. On 2026-10-19 every live session but two was at 1.1% or less; the two gateways that pay only the pool were at 99.98%. The old line (0.5% of 50+ accepted) warned Knotzi, on v0.5.0beta, for ~15 minutes after one such share, and the fee wallet would have charged the custody fee in that window. New line, same in the fee wallet and admin dashboard (lazarus-ops): at least 10% of 200 or more accepted. The block half of the rule (latest block in 30 days pool-only or partial) is unchanged. On live data it drops 2 of 15 flagged identities, both one-share blips. Wording: v0.5.0beta is the current release, but its blocks still come out partial at times, so the site and MCP no longer imply that updating ends it. The miner-page notice says "producing bad coinbases" and clears once the next block splits; MCP gateway_status tells a current build that is flagged to wait for its next block or ask on Discord instead of "update"; FAQ 2026-10-19.2. Tests: pool 9 files pass, site build/indexing/lastmod/language pass, MCP 147 pass. Co-Authored-By: Claude Opus 5.5 --- cloudflare/mcp/src/faq.js | 6 +++--- cloudflare/mcp/src/tools.js | 6 ++++-- cloudflare/mcp/test/run.mjs | 7 +++++++ cloudflare/pool-site/lastmod-hashes.json | 8 ++++---- pool/server.py | 13 ++++++++----- pool/static/i18n/en.js | 4 ++-- pool/static/i18n/zh-CN.js | 4 ++-- pool/static/miner.html | 2 +- pool/test_custody_fee.py | 9 ++++++--- 9 files changed, 37 insertions(+), 22 deletions(-) diff --git a/cloudflare/mcp/src/faq.js b/cloudflare/mcp/src/faq.js index ccb0ae7..4df5dc2 100644 --- a/cloudflare/mcp/src/faq.js +++ b/cloudflare/mcp/src/faq.js @@ -6,7 +6,7 @@ // security findings). Figures that move (fees, bonus, minimum output) are given with the date they // were true and a pointer to the live tool, so an assistant never presents them as permanent. -export const FAQ_VERSION = "2026-10-19.1"; +export const FAQ_VERSION = "2026-10-19.2"; const S = "https://pool.lazarus-xbt.xyz"; const E = "https://mempool.lazarus-xbt.xyz"; @@ -71,7 +71,7 @@ export const FAQ = [ keywords: ["gateway", "datum", "build", "builds", "which", "should", "run", "best", "recommend", "recommended", "fork", "convoy", "flytheelephant", "fte", "datum_gateway", "version", "update", "upgrade", "0.4.1", "0.5.0", "v0.5.0beta", "old", "outdated"], answer: "Several gateway builds connect to Lazarus with no patch. The pool ranks them by what it measures against its own Prime (whether a found block pays the whole window, and how often a gateway hands miners a job whose coinbase pays only the pool); " + - "the ranked list, best first, is connection_info.gateway_builds and the 'Which DATUM gateway' list on the Connect page. CONVOY's build is the recommended one, then FlyTheElephant's (see 'flytheelephant'). Run CONVOY's v0.5.0beta release (69c61a9, 10 Oct 2026). Every v0.4.1-beta build is out of date: some of them mine coinbases that pay only the pool or only part of the window, which leaves miners waiting for a make-good and costs the gateway's own address the custody fee (see 'makegoods'). gateway_status flags them. " + + "the ranked list, best first, is connection_info.gateway_builds and the 'Which DATUM gateway' list on the Connect page. CONVOY's build is the recommended one, then FlyTheElephant's (see 'flytheelephant'). Run CONVOY's v0.5.0beta release (69c61a9, 10 Oct 2026); v0.4.1-beta builds are out of date and gateway_status flags them. Updating makes a gateway's blocks come out partial less often, but no build is at zero yet: any gateway can occasionally mine a partial coinbase, and the miners it leaves out are repaid by a make-good (see 'makegoods'). " + "CONVOY 971af86 and FlyTheElephant 280cf54 give every miner the whole split: on regtest, blocks found by an Antminer A3 and by a generic miner each paid all 60 miners in the window. Keep whichever build you run up to date. " + "ratum-gateway, iohzrd's Rust gateway, is also on the list (see 'ratum'). Whatever the build, run it against Bitcoin Knots 29.4.2 (see 'node-version').", links: [`${S}/connect`, `${S}/flytheelephant`, `${S}/ratum`], @@ -143,7 +143,7 @@ export const FAQ = [ "Sometimes a block is found on a job whose coinbase paid only part of the window (partial) or only the pool (pool-only), usually because a stock DATUM gateway sent miners a job before the pool's split arrived. The block is valid; the pool then owes the window what the coinbase left out. " + "It repays with a make-good: a transaction signed when the block is found, spending that same block's pool output to the addresses that were left out, so its txid is known in advance. It is queued until the chain will accept it (the coinbase it spends must mature; see 'coinbase-maturity'), then broadcast. " + "Status queued means waiting for that height; paid means broadcast and confirmed. miner_makegoods lists yours with the payable height and estimated date, and verify_payout checks a paid one on chain. Make-goods are delayed, not lost. " + - "From block 977,665 the pool keeps a 10% custody fee on the make-good to an address whose own DATUM gateway is producing pool-only or partial coinbases; every other payee is paid in full. miner_makegoods shows custody_fee_sats and net_sats on a charged one, and miner_audit says when your gateway is the cause. Update the gateway (see 'which-gateway') and later make-goods are paid in full.", + "From block 977,665 the pool keeps a 10% custody fee on the make-good to an address whose own DATUM gateway is producing pool-only or partial coinbases (its latest block in the last 30 days was pool-only or partial, or at least 10% of 200 or more accepted shares in its current connection paid only the pool); every other payee is paid in full. miner_makegoods shows custody_fee_sats and net_sats on a charged one, and miner_audit says when your gateway is the cause. Run a current build (see 'which-gateway'); once the gateway's next block splits correctly, later make-goods are paid in full.", links: [`${S}/non-custodial`], }, { diff --git a/cloudflare/mcp/src/tools.js b/cloudflare/mcp/src/tools.js index 6ce8bf5..12d2dcb 100644 --- a/cloudflare/mcp/src/tools.js +++ b/cloudflare/mcp/src/tools.js @@ -161,8 +161,10 @@ export const TOOLS = [ if (!g.offline && Number(g.last_share_s) > 600) flags.push("connected but no share for over 10 minutes: are miners pointed at the gateway?"); if (acc + rej > 100 && rej / (acc + rej) > 0.02) flags.push("over 2% of shares rejected: see last_reject"); const v = verdicts[i]; - if (v && v.outdated) flags.push(`producing improperly split coinbases (${v.reason || "pool-only or partial"}): the miners it leaves out wait for a make-good${v.fee_percent ? `, and make-goods to this gateway's own address pay ${v.fee_percent}% less (the custody fee)` : ""}; update to ${firstBuild(builds)} now`); - if (/^v0\.4\.1-beta\//.test(g.user_agent || "") || g.generation === "ocean") flags.push(`out-of-date build (v0.4.1-beta): some of these builds mine coinbases that pay only the pool or only part of the window; update to ${firstBuild(builds)} (ranked list: connection_info gateway_builds)`); + const old = /^v0\.4\.1-beta\//.test(g.user_agent || "") || g.generation === "ocean"; + if (v && v.outdated) flags.push(`producing improperly split coinbases (${v.reason || "pool-only or partial"}): the miners it leaves out wait for a make-good${v.fee_percent ? `, and make-goods to this gateway's own address pay ${v.fee_percent}% less (the custody fee) while this flag stands` : ""}; ` + + (old ? `update to ${firstBuild(builds)}` : "it clears once its next block splits correctly; if it keeps happening, ask in the pool's Discord with this gateway id")); + if (old) flags.push(`out-of-date build (v0.4.1-beta): update to ${firstBuild(builds)}, the current release. Its blocks come out partial less often; no build is at zero yet (ranked list: connection_info gateway_builds)`); const sw = gatewaySoftware(g.user_agent); return { name: label(g.name || g.secondary_tag), gateway_id: g.gateway, payout_identity: g.identity, pays_fee_as: g.fee_path === "datum" ? "own DATUM gateway (0% + bonus)" : "public stratum", connected: !g.offline, connected_for_minutes: Math.round((Number(g.connected_s) || 0) / 60), seconds_since_last_share: g.last_share_s, accepted_shares: acc, rejected_shares: rej, last_reject_reason: label(g.last_reject), accepted_work: g.work, diff --git a/cloudflare/mcp/test/run.mjs b/cloudflare/mcp/test/run.mjs index de8aa2b..9b6b6e4 100644 --- a/cloudflare/mcp/test/run.mjs +++ b/cloudflare/mcp/test/run.mjs @@ -297,6 +297,13 @@ section("out-of-date gateways"); r = await call("gateway_status", { query: "XBT-GW01" }); g = r.data.gateways?.[0]; check("the pool's offender verdict is flagged with its reason and the custody fee", g && g.flags.some((f) => /improperly split coinbases \(gateway's latest block was partial\)/.test(f) && /10% less/.test(f)), g?.flags); + reset(); + fetchOverride = async (url) => (url.endsWith("/api/miner/bc1qukgk03gdsp2s6qpyav3wpspy6z677984nrxrz9") + ? new Response(JSON.stringify({ ...minerFix(A1), gateway_outdated: true, gateway_outdated_reason: "gateway's latest block was partial", custody_fee_percent: 10 }), { status: 200, headers: { "Content-Type": "application/json" } }) : null); + r = await call("gateway_status", { query: "nine009" }); + g = r.data.gateways?.[0]; + check("a current build with a partial block is not told to update", g && g.flags.some((f) => /improperly split/.test(f) && /clears once its next block splits/.test(f)) && !g.flags.some((f) => /update to/.test(f)), g?.flags); + reset(); r = await call("gateway_status", { query: "nine009" }); check("a Ratum gateway is not flagged out of date", r.data.gateways?.[0] && !r.data.gateways[0].flags.some((f) => /out-of-date|improperly/.test(f)), r.data.gateways?.[0]?.flags); diff --git a/cloudflare/pool-site/lastmod-hashes.json b/cloudflare/pool-site/lastmod-hashes.json index 555fe07..aad65d2 100644 --- a/cloudflare/pool-site/lastmod-hashes.json +++ b/cloudflare/pool-site/lastmod-hashes.json @@ -4,8 +4,8 @@ "sha256": "94f82e833d56ffd7f8c0c13dc7e49077d1f26183c3eb53434822ce5452473197" }, "/api": { - "lastmod": "2026-10-18", - "sha256": "9aa6b6b0c7eb1208ed31d2552f6ff1ee5d04e5a7a307208d12a4fec2ea87c4b0" + "lastmod": "2026-10-19", + "sha256": "e961f7ac52e1fae31a5cfed90eaeb35ffdb29c4c2eb1ea2d80ec765eb0921eed" }, "/bip110": { "lastmod": "2026-10-18", @@ -64,8 +64,8 @@ "sha256": "83488d0f6137f4c9673a2aeb4ae6732c61b6303fce87fdd3bde5d728ea03bdf2" }, "/zh/api": { - "lastmod": "2026-10-18", - "sha256": "ca3e057a914ed462f33177b4857b7dfce9a785db7f9aa46d39a1303d3781db30" + "lastmod": "2026-10-19", + "sha256": "62d985254a71ee821f0018d156b14cc1e7f672348e97bdd995a9f54fa7fd569b" }, "/zh/bip110": { "lastmod": "2026-10-18", diff --git a/pool/server.py b/pool/server.py index 409fdcb..0de7ed6 100644 --- a/pool/server.py +++ b/pool/server.py @@ -3386,11 +3386,14 @@ def gateway_names_by_address(): # whole reward to the pool (pool-only); the newest builds in gateways.json split correctly. Who # counts as mining through one is the fee wallet's rule (fee_wallet.py `offenders_from`, the same # as the admin dashboard's offender list): the payout identity of a connected gateway session -# that sends pool-only full jobs on at least 0.5% of 50 or more accepted, or whose gateway's +# that sends pool-only full jobs on at least 10% of 200 or more accepted, or whose gateway's # latest block in the last 30 days (orphans aside) was pool-only or partial. Those identities are # the payees the wallet charges the custody fee, so the miner page warns exactly them. -OFFENDER_MIN_ACCEPTED = 50 -OFFENDER_POOL_ONLY_RATE = 0.005 +# Every build, v0.5.0beta included, mines the odd pool-only share after a reconnect or a new height +# (on 2026-10-19 all live sessions but two were at 1.1% or less; the two paying only the pool were +# at 99.98%), so 0.5% of 50 warned a current gateway for one share (Mike, 2026-10-19). +OFFENDER_MIN_ACCEPTED = 200 +OFFENDER_POOL_ONLY_RATE = 0.10 OFFENDER_BLOCK_WINDOW_S = 30 * 86400 _offender_blocks_cache = {"key": None, "latest": {}} @@ -5942,7 +5945,7 @@ class ApiEndpoint(NamedTuple): ApiField("makegood_custody_fee_sats", "integer", "sats", "Custody fee kept from this address's pending and paid make-goods.", "从该地址待付与已付补付中保留的托管费。"), ApiField("custody_fee_percent", "number", "percent", "The custody fee on make-goods for pool-only and partial coinbases. 0 when there is none.", "仅矿池与部分分账 coinbase 补付的托管费率。没有时为 0。"), ApiField("custody_from_height", "integer", "blocks", "First block whose make-goods carry the custody fee. 0 when there is none.", "其补付开始收取托管费的第一个区块。没有时为 0。"), - ApiField("gateway_outdated", "boolean", "", "True while this address is the payout identity of a connected DATUM gateway on an out-of-date build: its latest block in the last 30 days was pool-only or partial, or it sends pool-only work (at least 0.5% of 50 or more accepted jobs). The same rule decides who pays the custody fee.", "该地址是一个在线、版本过时的 DATUM 网关的收款身份时为 true:该网关最近 30 天内的最新区块是仅矿池或部分分账,或它发送仅矿池工作(在 50 个及以上已接受任务中至少占 0.5%)。托管费由谁承担也按同一规则判定。"), + ApiField("gateway_outdated", "boolean", "", "True while this address is the payout identity of a connected DATUM gateway on an out-of-date build: its latest block in the last 30 days was pool-only or partial, or it sends pool-only work (at least 10% of 200 or more accepted jobs). The same rule decides who pays the custody fee.", "该地址是一个在线、版本过时的 DATUM 网关的收款身份时为 true:该网关最近 30 天内的最新区块是仅矿池或部分分账,或它发送仅矿池工作(在 200 个及以上已接受任务中至少占 10%)。托管费由谁承担也按同一规则判定。"), ApiField("gateway_outdated_reason", "string", "", "Why, in a few words. Also set when gateway_outdated is false but a make-good in the last 7 days carried a custody fee; empty otherwise.", "简短的原因。gateway_outdated 为 false、但最近 7 天内有补付被收取托管费时也会填写;否则为空。"), ), ), @@ -7005,7 +7008,7 @@ def set_attr(m, source, target): "/ratum": ("TechArticle", "2026-09-28", "2026-10-18"), "/datum-subsidy": ("TechArticle", "2026-09-13", "2026-10-18"), "/flytheelephant": ("TechArticle", "2026-09-28", "2026-10-18"), - "/api": ("TechArticle", "2026-09-13", "2026-10-18"), + "/api": ("TechArticle", "2026-09-13", "2026-10-19"), } # The sitemap pages that are not articles, with the date their crawlable copy last changed. Same # rule as _SEO_ARTICLE: bump a date in the commit that changes that page's words, never per deploy. diff --git a/pool/static/i18n/en.js b/pool/static/i18n/en.js index fa7ccd5..74b6f4b 100644 --- a/pool/static/i18n/en.js +++ b/pool/static/i18n/en.js @@ -75,8 +75,8 @@ window.LZ_I18N_DICTS["en"] = { }, "minerwarn": { "kicker": "Action needed", - "title": "Your gateway is out of date", - "text": "This address mines through a DATUM gateway on an out-of-date build that is producing improperly split coinbases (pool-only or partial). Gateways like this have been warned for a long time. You must update. From block {height}, {pct}% of what the pool pays you for those coinbases is kept as a fee for custodying them. Miners on current gateways are paid in full.", + "title": "Your gateway is producing bad coinbases", + "text": "This address mines through a DATUM gateway that is producing improperly split coinbases (pool-only or partial). From block {height}, while this notice shows, {pct}% of what the pool pays you for those coinbases is kept as a fee for custodying them. Run a current build (CONVOY v0.5.0beta, not v0.4.1-beta); the notice clears once your gateway’s next block splits correctly. Miners whose gateways split correctly are paid in full.", "textFee": "A make-good to this address in the last 7 days carried the {pct}% custody fee. From block {height}, that fee falls only on miners whose own gateway is producing pool-only or partial coinbases. Update to a current DATUM gateway and you are paid in full.", "setup": "Update your gateway →", "discord": "Ask for help on Discord" diff --git a/pool/static/i18n/zh-CN.js b/pool/static/i18n/zh-CN.js index 3119fb9..9802608 100644 --- a/pool/static/i18n/zh-CN.js +++ b/pool/static/i18n/zh-CN.js @@ -75,8 +75,8 @@ window.LZ_I18N_DICTS["zh-CN"] = { }, "minerwarn": { "kicker": "需要处理", - "title": "你的网关版本过时", - "text": "此地址正通过一个过时版本的 DATUM 网关挖矿,它产生的 coinbase 分账不正确(仅矿池或部分分账)。这类网关早已被反复提醒。你必须更新。自区块 {height} 起,矿池为这类 coinbase 付给你的金额中,有 {pct}% 将作为代管费用被保留。使用当前版本网关的矿工照常全额到账。", + "title": "你的网关正在产生不正确的 coinbase", + "text": "此地址正通过一个产生不正确 coinbase 分账(仅矿池或部分分账)的 DATUM 网关挖矿。自区块 {height} 起,在此提示显示期间,矿池为这类 coinbase 付给你的金额中,有 {pct}% 将作为代管费用被保留。请使用当前版本(CONVOY v0.5.0beta,而不是 v0.4.1-beta);你的网关下一个区块分账正确后,此提示会自动消失。网关分账正确的矿工照常全额到账。", "textFee": "最近 7 天内付给此地址的一笔补付被收取了 {pct}% 的托管费。自区块 {height} 起,此费只由自身网关产生仅矿池或部分分账 coinbase 的矿工承担。更新到当前版本的 DATUM 网关,即可全额到账。", "setup": "更新你的网关 →", "discord": "到 Discord 寻求帮助" diff --git a/pool/static/miner.html b/pool/static/miner.html index 15ad978..a925ef9 100644 --- a/pool/static/miner.html +++ b/pool/static/miner.html @@ -71,7 +71,7 @@

—