Skip to content

Commit c39b05a

Browse files
committed
Merge remote-tracking branch 'origin/master' into dash/version-gate-accept-seam
2 parents c2007d9 + 527d342 commit c39b05a

104 files changed

Lines changed: 13706 additions & 327 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 66 additions & 21 deletions
Large diffs are not rendered by default.

.github/workflows/codeql-analysis.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ jobs:
5757
- if: matrix.build-mode == 'manual'
5858
name: Install Conan 2
5959
run: |
60-
pip install "conan>=2.0,<3.0"
60+
if [ "${{ runner.environment }}" = "github-hosted" ]; then
61+
pip install "conan>=2.0,<3.0"
62+
export PATH="$HOME/.local/bin:$PATH"
63+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
64+
else
65+
conan --version # pre-provisioned at /usr/local/bin on self-hosted
66+
fi
6167
conan profile detect --force
6268
sed -i 's/compiler.cppstd=.*/compiler.cppstd=20/' \
6369
"$(conan profile path default)"
@@ -89,10 +95,24 @@ jobs:
8995
cmake -S . -B build_codeql \
9096
-DCMAKE_TOOLCHAIN_FILE=build_codeql/conan_toolchain.cmake \
9197
-DCMAKE_BUILD_TYPE=Release
92-
cmake --build build_codeql --target c2pool -j$(nproc)
98+
# Cap build parallelism on the shared self-hosted 905 box so the
99+
# CodeQL build does not co-OOM with a concurrent ASan job.
100+
# github-hosted (fork) runs keep full parallelism.
101+
if [ "${{ runner.environment }}" = "github-hosted" ]; then
102+
JOBS=$(nproc)
103+
else
104+
JOBS=8
105+
fi
106+
cmake --build build_codeql --target c2pool -j"$JOBS"
93107
94108
# ── Analysis ──────────────────────────────────────────────────────────
95109
- name: Perform CodeQL Analysis
96110
uses: github/codeql-action/analyze@v4
97111
with:
98112
category: "/language:${{ matrix.language }}"
113+
# Cap the run-queries footprint on the shared self-hosted 905 box so
114+
# CodeQL does not co-OOM with a concurrent ASan job (was auto-sizing
115+
# to --ram=60466 --threads=32, nearly the whole box). 0 = action
116+
# auto-default, kept for github-hosted (fork) runs.
117+
threads: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && '12' || '0' }}
118+
ram: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && '20000' || '0' }}

.github/workflows/coin-matrix.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,13 @@ jobs:
5252

5353
- name: Install Conan 2
5454
if: steps.presence.outputs.exists == '1'
55-
run: pip install "conan>=2.0,<3.0"
55+
run: |
56+
if [ "${{ runner.environment }}" = "github-hosted" ]; then
57+
pip install "conan>=2.0,<3.0"
58+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
59+
else
60+
conan --version # pre-provisioned at /usr/local/bin on self-hosted
61+
fi
5662
5763
- name: Detect Conan profile
5864
if: steps.presence.outputs.exists == '1'

.github/workflows/pplns-parse-seedpin.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ on:
2424
jobs:
2525
seedpin:
2626
name: PPLNS parse seed-pin replay
27-
runs-on: ubuntu-24.04
27+
# Route to self-hosted for trusted events; fork PRs fall back to
28+
# GitHub-hosted ubuntu-24.04 (never run untrusted fork code self-hosted).
29+
runs-on: ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && fromJSON('["self-hosted","Linux","X64","c2pool-build"]') || 'ubuntu-24.04' }}
2830
defaults:
2931
run:
3032
working-directory: web-static/sharechain-explorer

.github/workflows/release.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,13 @@ jobs:
8787

8888
- name: Install Conan 2
8989
if: steps.presence.outputs.exists == '1'
90-
run: pip install "conan>=2.0,<3.0"
90+
run: |
91+
if [ "${{ runner.environment }}" = "github-hosted" ]; then
92+
pip install "conan>=2.0,<3.0"
93+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
94+
else
95+
conan --version # pre-provisioned at /usr/local/bin on self-hosted
96+
fi
9197
9298
- name: Detect Conan profile
9399
if: steps.presence.outputs.exists == '1'
@@ -400,7 +406,12 @@ jobs:
400406

401407
- name: Install Conan 2
402408
if: steps.presence.outputs.exists == '1'
403-
run: pip install "conan>=2.0,<3.0"
409+
run: |
410+
if [ "${{ runner.environment }}" = "github-hosted" ]; then
411+
pip install "conan>=2.0,<3.0"
412+
else
413+
conan --version # pre-provisioned at /usr/local/bin on self-hosted
414+
fi
404415
405416
- name: Detect Conan profile
406417
if: steps.presence.outputs.exists == '1'

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ Original forum thread: <https://bitcointalk.org/index.php?topic=18313>
1414
> **First V36 Twin Block:** LTC [#3085349](https://blockchair.com/litecoin/block/3085349) + DOGE [#6154761](https://blockchair.com/dogecoin/block/6154761) (2026-04-05) — simultaneous LTC+DOGE block found by v36-signalling nodes running p2pool v36 producing V35 shares with `desired_version=36`; detected and displayed by c2pool's embedded block scanner
1515
>
1616
> **Sharechain Transparency Explorer** (2026-04-07) — defragmenter-style sharechain visualization with interactive PPLNS treemaps, animated hover effects, per-miner LTC+DOGE payout breakdown, V36 upgrade pressure for V35 miners
17+
>
18+
> **Recent Bitcoin Block Mined by P2pool** (2026-06-27 05:34:44 UTC) BTC [#955609](https://blockchair.com/bitcoin/block/955609)
19+
>
20+
> **Recent Bitcoin Block Mined by P2pool** (2025-03-07 06:08:22 UTC) BTC [#886688](https://blockchair.com/bitcoin/block/886688)
1721
1822
---
1923

@@ -514,6 +518,13 @@ cd build && ctest --output-on-failure -j$(nproc)
514518

515519
---
516520

521+
## V37 development
522+
523+
- Github pages: <[https://t.me/c2pooldev](https://frstrtr.github.io/c2pool/)>
524+
- V37 dev branch primitives: <[https://github.com/frstrtr/c2pool/compare/master...v37-dev](https://github.com/frstrtr/c2pool/compare/master...v37-dev)>
525+
526+
---
527+
517528
### Install guides
518529
- [Ubuntu / Debian / Linux](doc/build-unix.md)
519530
- [macOS (Intel & Apple Silicon)](doc/build-macos.md)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# D-MINER.6 — V36 P2P Relay Notification Adapter (design)
2+
3+
Status: DESIGN (pre-implementation). Owner: dashboard-steward (web half).
4+
Builds on D-MINER.5 (notify engine, shipped) — see d-miner-5-notify-engine.md.
5+
Realizes D-MINER.5 phasing item "P2 V36 P2P relay adapter (cross-steward)".
6+
7+
## Why
8+
Email (D-MINER.5 P1) reaches any miner with a mailbox; Telegram is geo-blocked
9+
in many miner locations. The V36 P2P messaging layer offers a THIRD path that
10+
is, by construction, reachable: the miner's alert rides the pool's own
11+
share-peer mesh, and each hop forwards via whatever transport can reach the
12+
next node — including the miner's own node, which the miner already connects to.
13+
This is the geo-resilient spine the operator asked for (2026-06-24 reframe).
14+
15+
## Boundary (who owns what)
16+
This is a two-steward seam. To keep it shippable in independent halves:
17+
18+
- WEB layer (dashboard-steward) owns: the transport-adapter CONTRACT the
19+
notify engine already exposes — deliver(channel, subject, body, route)->bool
20+
(scripts/miner_notify_engine.py:234) — plus the new channel name, the route
21+
format, and the delivery-honesty invariant. No consensus code.
22+
- V36 / transport / consensus stewards own: whether the P2P message type
23+
carries an operator-alert payload, the addressing/forwarding model across
24+
the mesh, and the security/authorization of relayed payloads. Consensus-
25+
adjacent; NOT a web change.
26+
27+
This doc fixes the web half so the P2P half is a drop-in against a frozen
28+
contract, not a negotiation.
29+
30+
## Web-owned contract
31+
1. Channel name: "v36relay". Subscribers opt in via sub["channels"] exactly
32+
as they do for "email"/"logonly"; no new dispatch shape.
33+
2. Route format: opaque string owned by the P2P steward's addressing model
34+
(candidate: a miner-node identity / share-peer handle). The notify core
35+
treats route as a passthrough token — it never parses it. This lets the
36+
addressing model evolve without a web change.
37+
3. deliver("v36relay", subject, body, route) MUST raise on any failure to
38+
hand off to the P2P layer, exactly like deliver_email. The engine records
39+
RAISED -> "undelivered"; RETURNED-True -> "sent". Handoff-to-mesh counts
40+
as sent ONLY if the local enqueue is durably accepted (see honesty below).
41+
42+
## Delivery honesty (charter)
43+
The founding charter forbids the system lying about state — here, about
44+
delivery. Relay adds hops the engine cannot observe, so:
45+
- "sent" means the message was durably ACCEPTED by the local V36 relay
46+
enqueue — NOT that it reached the miner. It is a handoff receipt, not a
47+
read receipt.
48+
- If the P2P layer later exposes per-message ack, add a "delivered" status
49+
distinct from "sent"; until then the notifications row stays "sent" and
50+
MUST NOT be upgraded to imply end-to-end receipt.
51+
- No fabricated success: a relay with no reachable next hop RAISES ->
52+
"undelivered", never a silent True.
53+
54+
## Phasing
55+
P1 (web, solo): freeze this contract; add a "v36relay" branch to make_transport
56+
that enqueues to a local relay-handoff stub and raises when the stub is
57+
absent (fails closed, honest). KAT: unknown-route raises -> undelivered.
58+
P2 (cross-steward): P2P steward implements the real enqueue + addressing.
59+
P3: per-message ack -> "delivered" status, if/when the mesh exposes it.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
#
3+
# DGB AUX_DOGE link-isolation guard (negative-symbol check)
4+
# ----------------------------------------------------------
5+
# Companion to the compile-time src/impl/dgb/coin/aux_doge_isolation_guard.hpp.
6+
# The compile guard pins DGB-parent traits to dgb::coin types via static_assert;
7+
# this guard closes the gap it cannot see -- LINK-level consensus reachability.
8+
#
9+
# Invariant: c2pool-dgb built with -DCOIN_DGB=ON -DAUX_DOGE=ON (dual-parent,
10+
# DGB-Scrypt parent + DOGE aux) must NOT ODR-use any LTC per-coin CONSENSUS
11+
# symbol. A dropped dgb specialization could silently re-route the DGB parent
12+
# through ltc::coin chain-params / header-chain serialization -- a foreign
13+
# consensus path. That is the failure mode this guard freezes out.
14+
#
15+
# SCOPE (deliberate): hard-fail on the named LTC consensus entrypoints only.
16+
# ltc::coin wire-generic types (transaction.hpp) are header-only / inlined and
17+
# emit NO external symbols, so they are out of scope here. A wire-type
18+
# RELOCATION toward bitcoin_family (the v37 (b) path) is caught by the D0-seam
19+
# flag protocol with ltc-doge, not by this link guard.
20+
#
21+
# POSITIVE CONTROL: assert dgb::coin:: consensus symbols ARE present, so the
22+
# guard cannot pass vacuously on a wrong / empty / stripped binary.
23+
#
24+
# Usage: dgb_aux_doge_link_isolation_guard.sh [path-to-c2pool-dgb]
25+
set -euo pipefail
26+
27+
BIN="${1:-build_dgb_auxdoge/src/c2pool/c2pool-dgb}"
28+
29+
if [[ ! -f "$BIN" ]]; then
30+
echo "FAIL: binary not found: $BIN" >&2
31+
exit 2
32+
fi
33+
34+
if ! command -v nm >/dev/null 2>&1; then
35+
echo "FAIL: nm not available on this runner" >&2
36+
exit 2
37+
fi
38+
39+
SYMS="$(nm -C "$BIN" 2>/dev/null || true)"
40+
if [[ -z "$SYMS" ]]; then
41+
echo "FAIL: nm produced no symbols (binary stripped?): $BIN" >&2
42+
exit 2
43+
fi
44+
45+
# --- Negative: forbidden LTC consensus surface (defined T/t/W/V or referenced U) ---
46+
FORBIDDEN_RE='make_ltc_chain_params|make_litecoin_chain_params|ltc::coin::HeaderChain|ltc::coin::ChainParams'
47+
48+
HITS="$(printf '%s\n' "$SYMS" | grep -E "$FORBIDDEN_RE" || true)"
49+
if [[ -n "$HITS" ]]; then
50+
echo "FAIL: c2pool-dgb (AUX_DOGE) links forbidden LTC consensus symbol(s):" >&2
51+
printf '%s\n' "$HITS" >&2
52+
echo " -> DGB parent must not reach ltc::coin consensus surface. Route to D0 seam thread." >&2
53+
exit 1
54+
fi
55+
56+
# --- Positive control: DGB parent consensus types must be present ---
57+
DGB_COUNT="$(printf '%s\n' "$SYMS" | grep -cE 'dgb::coin::' || true)"
58+
if [[ "${DGB_COUNT:-0}" -eq 0 ]]; then
59+
echo "FAIL: no dgb::coin:: symbols found -- wrong/empty binary, guard would pass vacuously: $BIN" >&2
60+
exit 1
61+
fi
62+
63+
echo "PASS: dgb-aux-doge link isolation"
64+
echo " binary : $BIN"
65+
echo " forbidden LTC : 0 hits ($FORBIDDEN_RE)"
66+
echo " dgb::coin:: syms : $DGB_COUNT (positive control OK)"
67+
exit 0

scripts/gen_g2_oracle.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Oracle golden generator for BCH G2 coinbase-author KAT.
2+
# Transcribes VERBATIM the v36 output-assembly from p2pool-merged-v36
3+
# p2pool/data.py:920-1085 (generate_transaction, v36_active branch):
4+
# amounts = subsidy*weight//total_weight ;
5+
# total_donation = subsidy - sum(amounts) ;
6+
# if total_donation < 1 and subsidy>0: largest by (amount,script) -=1 ; recompute
7+
# amounts[DON] += total_donation
8+
# dests = sorted(scripts \ {DON}, key=(amounts[s], s))[-4000:]
9+
# payouts = [{value:amounts[s], script:s} for s in dests if amounts[s]] + [DON last]
10+
import struct
11+
12+
COMBINED_DONATION_SCRIPT = bytes.fromhex('a9148c6272621d89e8fa526dd86acff60c7136be8e8587')
13+
def p2pkh(b): return bytes.fromhex('76a914') + bytes([b])*20 + bytes.fromhex('88ac')
14+
A=p2pkh(0xaa); B=p2pkh(0xbb); C=p2pkh(0xcc); D=p2pkh(0xdd)
15+
16+
def assemble_v36(weights, donation_weight, subsidy):
17+
total_weight = sum(weights.values()) + donation_weight
18+
amounts = dict((s, subsidy*w//total_weight) for s,w in weights.items())
19+
total_donation = subsidy - sum(amounts.values())
20+
if total_donation < 1 and subsidy > 0:
21+
largest = max(amounts, key=lambda k:(amounts[k], k))
22+
amounts[largest] -= 1
23+
total_donation = subsidy - sum(amounts.values())
24+
amounts[COMBINED_DONATION_SCRIPT] = amounts.get(COMBINED_DONATION_SCRIPT,0) + total_donation
25+
excluded = {COMBINED_DONATION_SCRIPT}
26+
dests = sorted([s for s in amounts if s not in excluded], key=lambda s:(amounts[s], s))[-4000:]
27+
payouts = [(s, amounts[s]) for s in dests if amounts[s]]
28+
payouts.append((COMBINED_DONATION_SCRIPT, amounts[COMBINED_DONATION_SCRIPT]))
29+
return payouts
30+
31+
def assemble_with_finderfee(weights, finder, donation_weight, subsidy):
32+
# NEGATIVE control: pre-v36 math (199/200 haircut) + subsidy//200 finder fee.
33+
total_weight = sum(weights.values()) + donation_weight
34+
amounts = dict((s, subsidy*(199*w)//(200*total_weight)) for s,w in weights.items())
35+
amounts[finder] = amounts.get(finder,0) + subsidy//200
36+
total_donation = subsidy - sum(amounts.values())
37+
if total_donation < 1 and subsidy > 0:
38+
largest = max(amounts, key=lambda k:(amounts[k], k))
39+
amounts[largest] -= 1
40+
total_donation = subsidy - sum(amounts.values())
41+
amounts[COMBINED_DONATION_SCRIPT] = amounts.get(COMBINED_DONATION_SCRIPT,0) + total_donation
42+
dests = sorted([s for s in amounts if s != COMBINED_DONATION_SCRIPT], key=lambda s:(amounts[s], s))[-4000:]
43+
payouts = [(s, amounts[s]) for s in dests if amounts[s]]
44+
payouts.append((COMBINED_DONATION_SCRIPT, amounts[COMBINED_DONATION_SCRIPT]))
45+
return payouts
46+
47+
def varint(n):
48+
if n < 0xfd: return bytes([n])
49+
if n <= 0xffff: return b'\xfd'+struct.pack('<H',n)
50+
if n <= 0xffffffff: return b'\xfe'+struct.pack('<I',n)
51+
return b'\xff'+struct.pack('<Q',n)
52+
53+
def serialize(payouts):
54+
out = b''
55+
for script, value in payouts:
56+
out += struct.pack('<Q', value) + varint(len(script)) + script
57+
return out
58+
59+
SUB = 1_000_000_000
60+
cases = {
61+
'CASE1_ordering_tie': assemble_v36({A:10,B:20,C:20,D:50}, 0, SUB),
62+
'CASE2_donation_forced_last': assemble_v36({A:40,B:20}, 40, SUB),
63+
'CASE3_v36_no_finderfee': assemble_v36({A:60,B:40}, 0, SUB),
64+
'CASE3_NEG_with_finderfee': assemble_with_finderfee({A:60,B:40}, A, 0, SUB),
65+
}
66+
names = {A.hex():'A',B.hex():'B',C.hex():'C',D.hex():'D',COMBINED_DONATION_SCRIPT.hex():'DON'}
67+
for name, p in cases.items():
68+
print('=== %s ===' % name)
69+
for s,v in p: print(' %-4s value=%d' % (names.get(s.hex(),'?'), v))
70+
print(' OUTSECTION_HEX=%s' % serialize(p).hex())

0 commit comments

Comments
 (0)