Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cloudflare/mcp/src/faq.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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`],
Expand Down Expand Up @@ -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`],
},
{
Expand Down
6 changes: 4 additions & 2 deletions cloudflare/mcp/src/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions cloudflare/mcp/test/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
8 changes: 4 additions & 4 deletions cloudflare/pool-site/lastmod-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"sha256": "94f82e833d56ffd7f8c0c13dc7e49077d1f26183c3eb53434822ce5452473197"
},
"/api": {
"lastmod": "2026-10-18",
"sha256": "9aa6b6b0c7eb1208ed31d2552f6ff1ee5d04e5a7a307208d12a4fec2ea87c4b0"
"lastmod": "2026-10-19",
"sha256": "e961f7ac52e1fae31a5cfed90eaeb35ffdb29c4c2eb1ea2d80ec765eb0921eed"
},
"/bip110": {
"lastmod": "2026-10-18",
Expand Down Expand Up @@ -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",
Expand Down
13 changes: 8 additions & 5 deletions pool/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}}

Expand Down Expand Up @@ -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 天内有补付被收取托管费时也会填写;否则为空。"),
),
),
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions pool/static/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <b>out-of-date build</b> that is producing improperly split coinbases (pool-only or partial). Gateways like this have been warned for a long time. <b>You must update.</b> 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. <b>Run a current build</b> (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. <b>Update to a current DATUM gateway</b> and you are paid in full.",
"setup": "Update your gateway →",
"discord": "Ask for help on Discord"
Expand Down
4 changes: 2 additions & 2 deletions pool/static/i18n/zh-CN.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ window.LZ_I18N_DICTS["zh-CN"] = {
},
"minerwarn": {
"kicker": "需要处理",
"title": "你的网关版本过时",
"text": "此地址正通过一个<b>过时版本的 DATUM 网关</b>挖矿,它产生的 coinbase 分账不正确(仅矿池或部分分账)。这类网关早已被反复提醒。<b>你必须更新。</b>自区块 {height} 起,矿池为这类 coinbase 付给你的金额中,有 {pct}% 将作为代管费用被保留。使用当前版本网关的矿工照常全额到账。",
"title": "你的网关正在产生不正确的 coinbase",
"text": "此地址正通过一个产生不正确 coinbase 分账(仅矿池或部分分账)的 DATUM 网关挖矿。自区块 {height} 起,在此提示显示期间,矿池为这类 coinbase 付给你的金额中,有 {pct}% 将作为代管费用被保留。<b>请使用当前版本</b>(CONVOY v0.5.0beta,而不是 v0.4.1-beta);你的网关下一个区块分账正确后,此提示会自动消失。网关分账正确的矿工照常全额到账。",
"textFee": "最近 7 天内付给此地址的一笔补付被收取了 {pct}% 的托管费。自区块 {height} 起,此费只由自身网关产生仅矿池或部分分账 coinbase 的矿工承担。<b>更新到当前版本的 DATUM 网关</b>,即可全额到账。",
"setup": "更新你的网关 →",
"discord": "到 Discord 寻求帮助"
Expand Down
2 changes: 1 addition & 1 deletion pool/static/miner.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ <h2 id="miner-title" class="mono addr-title">—</h2>
</header>
<aside class="softfork urgent" id="minerwarn" role="alert" aria-labelledby="minerwarn-title" style="margin: 0 0 1.2rem" hidden>
<div class="softfork-row">
<p class="softfork-head"><span class="softfork-tag" data-i18n="minerwarn.kicker">Action needed</span><strong class="softfork-title" id="minerwarn-title" data-i18n="minerwarn.title">Your gateway is out of date</strong></p>
<p class="softfork-head"><span class="softfork-tag" data-i18n="minerwarn.kicker">Action needed</span><strong class="softfork-title" id="minerwarn-title" data-i18n="minerwarn.title">Your gateway is producing bad coinbases</strong></p>
<p class="softfork-text" id="minerwarn-text"></p>
<p class="softfork-actions"><a class="btn small" id="minerwarn-setup" href="/connect" data-i18n="minerwarn.setup">Update your gateway →</a> <a class="btn small ghost" href="https://discord.gg/fD33dJXnzz" target="_blank" rel="noreferrer" data-i18n="minerwarn.discord">Ask for help on Discord</a></p>
</div>
Expand Down
9 changes: 6 additions & 3 deletions pool/test_custody_fee.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,12 @@ def client(ident, gw, accepted=0, pof=0, offline=False):
class OutdatedGateways(unittest.TestCase):
def test_pool_only_work_rate(self):
Book(self).write()
clients = [client(A, "g1", 1000, 5), client(B, "g2", 1000, 4), client(L, "g3", 49, 49), client(S, "g4", 1000, 900, offline=True)]
# A: pays only the pool. B: a current build's odd pool-only share (1.1%). L: too few shares.
# S: offline. "blip": one pool-only share right after a reconnect (Knotzi, 2026-10-19).
clients = [client(A, "g1", 1000, 999), client(B, "g2", 1000, 11), client(L, "g3", 199, 199), client(S, "g4", 1000, 900, offline=True),
client("blip", "g5", 60, 1), client("edge", "g6", 200, 20)]
bad = server.gateway_offenders(clients, now=NOW)
self.assertEqual(set(bad), {A}, "0.5% of 50+ accepted, connected only")
self.assertEqual(set(bad), {A, "edge"}, "10% of 200+ accepted, connected only")
self.assertIn("pool-only work", bad[A])

def test_latest_block_in_30_days(self):
Expand All @@ -223,7 +226,7 @@ def test_latest_block_in_30_days(self):

def test_miner_flags(self):
Book(self).write()
clients = [client(A, "g1", 1000, 50)]
clients = [client(A, "g1", 1000, 500)]
self.assertEqual(server.gateway_outdated_for(A, [], clients, now=NOW)[:2], (True, server.gateway_offenders(clients, NOW)[A]))
self.assertEqual(server.gateway_outdated_for(B, [], clients, now=NOW), (False, "", False))
charged = [{"custody_fee_sats": 10, "ts": NOW - 6 * DAY}]
Expand Down
Loading