Skip to content

Json multiline reading#3

Merged
5133n merged 1 commit intofrstrtr:origin/sharechain/async_threadfrom
Kaieida:filesystem
Apr 23, 2021
Merged

Json multiline reading#3
5133n merged 1 commit intofrstrtr:origin/sharechain/async_threadfrom
Kaieida:filesystem

Conversation

@Kaieida
Copy link
Copy Markdown
Contributor

@Kaieida Kaieida commented Apr 23, 2021

No description provided.

@5133n 5133n merged commit d27d1e2 into frstrtr:origin/sharechain/async_thread Apr 23, 2021
5133n added a commit that referenced this pull request Aug 10, 2022
5133n added a commit that referenced this pull request Nov 13, 2022
5133n added a commit that referenced this pull request Feb 1, 2023
5133n added a commit that referenced this pull request Jul 20, 2023
frstrtr added a commit that referenced this pull request Apr 17, 2026
…ares

Port p2pool-dash/p2pool/data.py:181-231 generate_transaction tx_outs
formula into c2pool so the coinbase we build byte-matches what the peer
regenerates during Share.check(). The last remaining Phase 5d gap
reported by the peer was:

    ValueError: gentx doesn't match hash_link

because our coinbase emitted 100% to the miner (native c2pool PPLNS or
--no-pplns fallback), whereas p2pool-dash's canonical layout at genesis
is 2% → miner pubkey_hash, 98% → DONATION_SCRIPT. When miner==donation
(our test case XdgF55wEHBRWwbuBniNYH4GvvaoYMgL84u), the formula
collapses to 100% → DONATION_SCRIPT with worker_tx empty.

Changes
─────────────────────────────────────────────────────────────────────
coinbase_builder.hpp
  + compute_dash_payouts(subsidy, packed_payments, miner_pubkey_hash,
      weights, total_weight, params) → std::vector<MinerPayout>
    Emits tx_outs in the exact order: worker_tx (sorted by script,
    excluding DONATION, zeros dropped) || payments_tx (GBT order) ||
    donation_tx (always one entry). Inner arithmetic uses __uint128_t
    to avoid overflow in the weights branch.
  ~ build(): now takes a pre-built tx_outs_ordered list, validates
    that the last entry is DONATION_SCRIPT, and just appends OP_RETURN.
    Removed donation_value arg (now baked into tx_outs_ordered).
    Removed internal packed_payments loop (caller provides).

share_builder.hpp
  ~ build(): miner_payouts param now ignored; outputs computed via
    compute_dash_payouts with empty weights (genesis path). Works for
    the first 1-2 shares in a chain; non-genesis needs WeightsSkipList.
  ~ Merkle tree: build from [coinbase placeholder] only, not
    [coinbase] + work.m_tx_hashes. Since we advertise empty
    new_transaction_hashes (until remember_tx ships), p2pool-dash's
    Share.check() expects calculate_merkle_link([None] + [], 0) —
    an empty branch with merkle_root == coinbase_txid. Our stratum job
    must give the miner the same empty branch so its header's
    merkle_root also equals coinbase_txid.
  ~ ctx.tx_data_hex: cleared (coinbase-only blocks on a full hit).

Interim limitations
─────────────────────────────────────────────────────────────────────
- Genesis/first-2-shares only: WeightsSkipList not yet ported, so
  chains of 3+ shares will regress to "gentx doesn't match hash_link"
  on share #3+ because the PPLNS distribution needs ancestor miner
  weights. Phase 5d-continued work.
- Coinbase-only blocks: on a full block hit we'd submit a block
  containing just the coinbase (no fee-paying txs). Until remember_tx
  lets us ship tx bodies to peers, this is the correct default.

Verification (isolated LAN test)
─────────────────────────────────────────────────────────────────────
  P2Pool: 1 shares in chain (12 verified/12 total) Peers: 1 (1 incoming)

Against a p2pool-dash peer patched with MAX_TARGET=2^256-1 +
BOOTSTRAP_ADDRS=[] (both reverted), 12 consecutive shares broadcast by
c2pool-dash were deserialised, check()'d, and added to the peer's
sharechain. Zero rejections after this commit. The isolation patches
are local-only test harness — master branch files restored.
frstrtr added a commit that referenced this pull request Apr 20, 2026
Extracts dashboard.html's getColor() (lines 5404-5428) and render()
(lines 4672-4763) paint body into coin-agnostic, pure-function
primitives plus a thin DOM-touching adapter. Palette values preserved
verbatim so M2 pixel-diff holds for LTC once the host page switches.

src/explorer/colors.ts (~110 LOC)

- type ShareClass = 'dead' | 'stale' | 'unverified' | 'fee' |
                    'native' | 'signaling' | 'legacy'
- classifyShare(share, ctx): ShareClass
  Pure classifier with dashboard.html priority preserved:
  dead > stale > unverified > fee > native > signaling > legacy
  Threshold (coin.shareVersion) is a ctx parameter so Dash V16 and
  LTC V36 share the same classifier (M1 D5).
- getColor(share, ctx, palette?): string
  "Mine" variants (nativeMine, signalingMine, legacyMine) apply only
  to the three version-tier classes; dead/stale/unverified/fee are
  terminal-coloured regardless of address match. Empty or undefined
  `ctx.myAddress` never marks a share as mine.
- LTC_COLOR_PALETTE: frozen ColorPalette — byte-identical values to
  dashboard.html's hex strings. Tests assert parity.
- ColorsPlugin: id 'explorer.colors', provides 'explorer.colors'
  capability exposing { classifyShare, getColor, LTC_COLOR_PALETTE }.

src/explorer/grid-paint.ts (~180 LOC)

- type PaintCommand = setTransform | fillBackground | fillCell |
                       strokeRect | textCenter
- buildPaintProgram(opts): PaintCommand[]
  Pure function: takes a GridLayout + shares + userContext + palette
  + dpr, produces the exact ordered sequence of canvas ops that
  dashboard.html's render() issues. Empty-chain path preserved (the
  "Waiting for shares..." text). hoveredIndex appends a strokeRect.
- executePaintProgram(ctx, program): void
  Applies commands to a CanvasLike (narrow interface covering the
  eight context members we use) — easily mocked in Node for tests.
- createGridRenderer(canvas): { paint, buildProgram, destroy }
  DOM-touching adapter. Sizes canvas for dpr per dashboard.html
  (canvas.width = cssW*dpr; ctx.setTransform(dpr,0,0,dpr,0,0));
  destroy() flips a local flag so subsequent paint() is a no-op.
- GridCanvasPlugin: id 'explorer.grid.canvas', provides
  'renderer.grid.canvas', fills slot 'explorer.main.grid'.

Explorer bundle public API (src/explorer/index.ts):

- Re-exports ColorsPlugin + GridCanvasPlugin alongside the existing
  GridLayoutPlugin.
- registerExplorerBaseline(host) now also registers them.

Tests:

- tests/unit/colors.test.ts (~21 tests)
  Priority-order proofs for classifyShare, palette mapping for every
  class including mine variants, threshold-from-context (Dash V16),
  empty myAddress does NOT mark as mine, custom palette override,
  verbatim palette value parity with dashboard.html:5404-5428.

- tests/unit/grid-paint.test.ts (~10 tests)
  buildPaintProgram: empty chain emits setTransform+bg+emptyText;
  one share emits fillCell with expected colour + geometry; mixed
  shares (dead, signaling, native-mine) produce correct palette;
  hoveredIndex appends strokeRect; negative/OOB hover ignored.
  executePaintProgram: mock CanvasLike proves commands map to the
  correct context calls in the correct order. Round-trip stress
  test with 4320 shares + full mix exercises the hot path.

Status: 94/94 tests pass (63 + 21 + 10). Typecheck clean under
strict + exactOptionalPropertyTypes + noUncheckedIndexedAccess.
Bundles: shared-core 25.1 KB / 40 (63%), sharechain-explorer 29.8
KB / 120 (25%). Pipeline green.

Next: Phase B #3 — Delta merger (merge window + new shares with
fork-switch detection and windowSize slice). The algorithmic core
of real-time updates; strong candidate for property-based tests
against fast-check. Then #4 — Animator phases 1-3.
frstrtr added a commit that referenced this pull request Apr 20, 2026
Combines the end-to-end composition proof (demo page) with the
algorithmic core of real-time updates (delta merger). Two small
increments in one commit because they validate each other — the
demo shakes out papercuts that unit tests miss; the delta merger
is what the demo will drive once real-time mode lands.

Demo page (demo/)

- demo.html                 CSP-compliant static shell per delta v1
                            §B.3 (no inline script, no 'unsafe-eval');
                            header toolbar with add/rotate/reset
                            controls; canvas in a scrollable wrap.
- demo.mjs                  Pure ES module import from
                            ../dist/sharechain-explorer.js. Seeded
                            mulberry32 RNG (seed=42) → deterministic
                            synthetic 4320-share chain across the
                            full palette mix (V36 native + V35→V36
                            signalling + V35 legacy + stale/dead/
                            unverified/fee + "mine" address). Wires
                            Host + registerExplorerBaseline + paint
                            + mouse hit-test via cellAtPoint.
                            window.__demo exposes host/renderer/
                            getShares/paint for devtools poking.
- README.md                 How to run (python -m http.server 8080
                            then open demo.html). file:// note
                            because ESM imports require an origin.

Proves the following plugins compose without a c2pool server:
  shared.transport.http, shared.theme.dark, shared.addr.hue-hash,
  shared.treemap.squarified, shared.hashrate.si-units,
  shared.i18n.en, explorer.grid.layout, explorer.colors,
  explorer.grid.canvas — plus the full middleware chain.

Delta merger (src/explorer/delta.ts)

- mergeDelta(current, delta, opts) → MergeResult
    Pure; reconciles §5.3 spec:
      * prepend delta.shares (newest first)
      * dedup by short-hash (both within delta AND against current)
      * slice to windowSize, collecting evicted short-hashes
      * fork_switch: true → rebuild from delta alone, evict whole
        current window (with dedup + cap preserved)
    Returns { shares, added, evicted, forkSwitch, tip } so callers
    can drive animation decisions (Phase B #4).
- windowSize 0 + maxShares undefined = unlimited (useful for demos);
  maxShares override controls the cap independently of windowSize.
- Opaque payload fields pass through — works with DeltaShare<T>
  generic so callers can type their full share shape.
- DeltaMergerPlugin: id 'explorer.delta.merger-default',
  provides 'merger.delta', registered via registerExplorerBaseline.

Tests (tests/unit/delta.test.ts — 19 tests)

Spec contract coverage: empty inputs, normal prepend with order
preservation, multi-share delta, opaque payload passthrough, dedup
both directions (delta vs current; within-delta), eviction math,
windowSize 0 unlimited, maxShares override, fork_switch rebuild
with cap + dedup + empty edge case, immutability of inputs,
1000-iteration saturation stress with duplicate-injection
invariant (no-duplicates after any merge sequence).

Status

- 113/113 tests pass in 2.2s (94 prior + 19 new)
- Typecheck clean
- Bundles: shared-core 25.1 KB / 40, sharechain-explorer 30.6 KB /
  120 — both under budget
- HTTP probe confirms bundle + demo assets serve correctly

Next: Phase B #4 — Animator phases 1-3 (death/wave/birth); will
consume mergeDelta's { added, evicted } to drive transitions.
Or (parallel track) Qt refactor step 1 — CMake deps + option flags
in ui/c2pool-qt/CMakeLists.txt.
frstrtr added a commit that referenced this pull request Apr 24, 2026
Crash reproducibly hit on first SML sync timer fire (PID dies right
after the [SML] sync request log line, before any mnlistdiff arrives).
Apport core dump bt:

  Thread 1 SIGSEGV
  #0  0x...3f0 (??)               ← jumped to garbage
  #1  initiate_async_wait::operator()<std::function<...>&>(...)
  #2  main::{lambda(...)#3}::operator()(...)   ← timer lambda body
  #3  wait_handler::do_complete(...)
  #4  scheduler::run(error_code&)
  #5  main

Bug: the persistent std::function `sml_sync_tick` was passed BY LVALUE
to async_wait, captured `&sml_sync_tick` for self-reference. boost::
asio's perfect-forwarding into the internal handler queue moved-from
the lvalue on first dispatch (universal-reference deduction +
std::forward semantics), leaving the outer std::function empty. The
copy that ran on first fire then re-armed by passing the now-empty
outer function — second fire dereferenced the empty std::function and
SIGSEGV'd into garbage.

Fix: hold the persistent std::function on the heap via shared_ptr,
and NEVER pass it directly to async_wait. Instead, schedule via a
fresh wrapper lambda that captures the shared_ptr by value and
invokes (*sml_sync_tick)(ec) when fired. Each schedule() call hands
async_wait a brand-new lambda; boost::asio can move-from the
temporary as much as it wants without affecting the persistent
function.

Verified: pattern is the canonical chained-timer idiom. The shared_ptr
keeps the tick function + captures alive across reschedules; each
fresh wrapper lambda holds its own copy of the shared_ptr (refcount++)
so the timer chain is self-sustaining until io_context exits.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants