From 42d780818a51ef6dd07f2cb1fb54813d8ca3db71 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:05:19 +0000 Subject: [PATCH] Chain scene: back to four states with a bold locked ring Co-Authored-By: Claude Fable 5.1 --- proto-cuda/host.cu | 4 +++- proto-cuda/packs/igneum-genesis-mh/kernel.cu | 2 ++ proto-cuda/packs/igneum-genesis-mh/program.metal | 1 + proto-metal/main.swift | 12 ++++++++---- sim/finality_v2.py | 16 +++++++++++----- site/index.html | 11 ++++------- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/proto-cuda/host.cu b/proto-cuda/host.cu index c501e555..7d45136a 100644 --- a/proto-cuda/host.cu +++ b/proto-cuda/host.cu @@ -36,6 +36,7 @@ static const uint32_t SEEDW[8] = IGNEUM_SEEDW_INIT; +#if IGNEUM_DATASET_MODE == 0 // Same closed form as ds_elem in kernel.cu and datasetElem in proto-metal/main.swift. static uint32_t host_ds_elem(uint32_t i, uint32_t d0, uint32_t d1) { uint32_t x = i ^ d0; @@ -45,9 +46,11 @@ static uint32_t host_ds_elem(uint32_t i, uint32_t d0, uint32_t d1) { x *= 0xC2B2AE3Du; x ^= x >> 16; return x; } +#endif static double wallMs(); +#if IGNEUM_DATASET_MODE == 1 static uint64_t fnv1a64(const void* p, size_t n) { const uint8_t* b = (const uint8_t*)p; uint64_t h = 0xcbf29ce484222325ull; @@ -55,7 +58,6 @@ static uint64_t fnv1a64(const void* p, size_t n) { return h; } -#if IGNEUM_DATASET_MODE == 1 // Memory-hard mode: the 256 MiB cache on the device (filled by the pack's kernel) and on the host (filled by the // same mh_cache_segment text on one thread). Both are built once per process in setupCache(), compared word for // word, and checked against the head, last line and FNV-1a 64 the Mac recorded in vectors.h. diff --git a/proto-cuda/packs/igneum-genesis-mh/kernel.cu b/proto-cuda/packs/igneum-genesis-mh/kernel.cu index 9fedfc99..609a19a2 100644 --- a/proto-cuda/packs/igneum-genesis-mh/kernel.cu +++ b/proto-cuda/packs/igneum-genesis-mh/kernel.cu @@ -40,6 +40,7 @@ __global__ void igneum_build(uint32_t* ds, const uint32_t* cache, uint32_t nItem for (uint32_t i = 0u; i < 16u; ++i) d[i] = s[i]; } } + // One hash per thread. blockDim.x is a multiple of 32; lane = threadIdx.x & 31 and every // __shfl_xor_sync stays inside the lane's own warp, exactly like simd_shuffle_xor inside a // 32-wide Metal SIMD group. Control flow is uniform, so the full 0xffffffff member mask is valid. @@ -144,6 +145,7 @@ cudaError_t igneum_launch_build(uint32_t* ds, const uint32_t* cache, uint32_t nI igneum_build<<>>(ds, cache, nItems); return cudaGetLastError(); } + cudaError_t igneum_launch_hash(const uint32_t* ds, uint64_t* out, uint32_t baseNonce, uint32_t mask, uint32_t nonces, uint32_t blockWarps) { if (blockWarps == 0u || blockWarps > 32u) return cudaErrorInvalidValue; diff --git a/proto-cuda/packs/igneum-genesis-mh/program.metal b/proto-cuda/packs/igneum-genesis-mh/program.metal index 65910c0e..775765f7 100644 --- a/proto-cuda/packs/igneum-genesis-mh/program.metal +++ b/proto-cuda/packs/igneum-genesis-mh/program.metal @@ -20,6 +20,7 @@ inline uint ds_elem(uint i, uint d0, uint d1) { x *= 0xC2B2AE3Du; x ^= x >> 16; return x; } + kernel void igneum_hash(device const uint* dataset [[buffer(0)]], device ulong* out [[buffer(1)]], constant uint& baseNonce [[buffer(2)]], diff --git a/proto-metal/main.swift b/proto-metal/main.swift index d7646936..e8d5bd8d 100644 --- a/proto-metal/main.swift +++ b/proto-metal/main.swift @@ -558,8 +558,9 @@ func generateMSL(_ p: Program, datasetLog2: Int, source: LoadSource = .stored) - return x; } + """ - if p.hasWide { s += " #define WMASK (MASK & ~31u)\n\n" } + if p.hasWide { s += "#define WMASK (MASK & ~31u)\n\n" } var buffer0 = "device const uint* dataset [[buffer(0)]]" if case .inlineMemhard(let mp) = source { s += emitMemhardCore(mp, cuda: false) + "\n" @@ -755,8 +756,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String { #include #include #include "program.h" - \(memhard != nil ? "#include \"memhard.h\"" : "") - + \(memhard != nil ? "#include \"memhard.h\"\n" : "") __device__ __forceinline__ uint32_t splitmix32(uint32_t x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; @@ -786,6 +786,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String { if (i < n) ds[i] = ds_elem(i, d0, d1); } + """ } else { s += """ @@ -805,6 +806,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String { } } + """ } s += """ @@ -863,6 +865,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String { return cudaGetLastError(); } + """ } else { s += """ @@ -882,6 +885,7 @@ func generateCUDA(_ p: Program, memhard: MixParams?) -> String { return cudaGetLastError(); } + """ } s += """ @@ -2141,7 +2145,7 @@ func runDeterminism(_ opts: Options, ctx: DatasetContext) -> Bool { } let sameFill = fillFps[0] == fillFps[1] if !sameFill || sampleBad != 0 { ok = false } - print("dataset fill: two fills fingerprint \(h64(fillFps[0])) and \(h64(fillFps[1])): \(sameFill ? "identical" : "DIFFERENT"); 4096 sampled words (incl. 0, 1, MASK-1, MASK) vs CPU datasetElem: \(sampleBad == 0 ? "all match" : "\(sampleBad) MISMATCH")") + print("dataset fill: two fills fingerprint \(h64(fillFps[0])) and \(h64(fillFps[1])): \(sameFill ? "identical" : "DIFFERENT"); 4096 sampled words (incl. 0, 1, MASK-1, MASK) vs CPU dataset word (\(ds.modeName)): \(sampleBad == 0 ? "all match" : "\(sampleBad) MISMATCH")") } else { print("dataset fill check skipped: could not allocate a shared copy") } diff --git a/sim/finality_v2.py b/sim/finality_v2.py index dc5d80ef..467283b1 100644 --- a/sim/finality_v2.py +++ b/sim/finality_v2.py @@ -655,7 +655,8 @@ def scenario_c(args): fracs = (0.34, 0.40, 0.45, 0.50, 0.55) configs = [("active", "cert", args.hours_c, 0.0, 240), ("active", "seen", args.hours_c, 0.0, 240), ("active", "frozen", args.hours_c, 0.0, 240), ("active", "cert", args.hours_c_total, 0.0, 2880), - ("active", "cert", args.hours_c_total, 0.8, 240), ("total", "cert", args.hours_c_total, 0.0, 240)] + ("active", "cert", args.hours_c_total, 0.8, 240), ("active", "cert", args.hours_c_total, 0.85, 240), + ("total", "cert", args.hours_c_total, 0.0, 240)] labels = [] for denom, pmode, hours, floor, presence in configs: lab = P(denom=denom, pmode=pmode, floor=floor, presence=presence).label() @@ -715,7 +716,8 @@ def scenario_d(args): fracs = (0.35, 0.50) rows = [] dconfigs = [P(denom="active", delay=args.delay), P(denom="active", presence=2880, delay=args.delay), - P(denom="active", floor=0.8, delay=args.delay), P(denom="total", delay=args.delay)] + P(denom="active", floor=0.8, delay=args.delay), P(denom="active", floor=0.85, delay=args.delay), + P(denom="total", delay=args.delay)] for frac in fracs: for p in dconfigs: days = args.days_d35 if frac < 0.4 else args.days_d50 @@ -823,7 +825,8 @@ def scenario_e(args): # supplementary: more splits, 0% attacker, plus the DAA-retarget and floor variants configs2 = configs + [P(denom="active", pmode="cert", daa="full", delay=args.delay), P(denom="active", pmode="seen", daa="full", delay=args.delay), - P(denom="active", pmode="cert", floor=0.8, daa="full", delay=args.delay)] + P(denom="active", pmode="cert", floor=0.8, daa="full", delay=args.delay), + P(denom="active", pmode="cert", floor=0.85, daa="full", delay=args.delay)] labels2 = [p.label() for p in configs2] rows = [] for name, fr in (("50/50", [0.5, 0.5]), ("60/40", [0.6, 0.4]), ("67/33", [0.67, 0.33]), ("80/20", [0.8, 0.2]), @@ -836,7 +839,8 @@ def scenario_e(args): rows.append(rc) out.append("Supplementary, 0% attacker, 150 and 360 min. Cell = conflicting locks; minutes to each side's first lock. " "'+daa' = each side retargets to 1 block/s at once (worst case for the presence clock); " - "'+floor0.80' = active denominator never below 80% of total weight (a lock needs at least 53.3% of total).") + "'+floor0.80' = active denominator never below 80% of total weight (a lock needs at least 53.3% of total), " + "'+floor0.85' needs 56.7%.") out.append("") out.append(md_table(["honest split", "partition min"] + labels2, rows)) out.append("") @@ -927,8 +931,10 @@ def scenario_f(args): "back to 1, min after end", "conflicting locks", "stalls during", "stalls after"], rows)) out.append("") rows = [] + configs2 = configs + [P(denom="active", pmode="cert", floor=0.8, delay=args.delay), + P(denom="active", pmode="cert", floor=0.85, delay=args.delay)] for dur in (1, 2, 4): - for p in configs: + for p in configs2: r = run_eclipse(args.seed, dur, True, p) rows.append([dur, p.label(), "%.3f" % r["min_part"], fm(r["recover_min"]), r["conflicts"], fm(r["first_conflict_min"]), r["ecl_locks"], r["honest_stalls"], r["post_stalls"]]) diff --git a/site/index.html b/site/index.html index b7743a22..213dd319 100644 --- a/site/index.html +++ b/site/index.html @@ -257,8 +257,7 @@ footer .wrap{padding-block:48px 32px} mined shards being proven proven - checkpoint, every 30 s on chain - locked by sustained miners, final + checkpoint locked by sustained miners
@@ -490,8 +489,7 @@ footer .wrap{padding-block:48px 32px} blocks.forEach(function(b){b.x-=speed*dt;var age=t-b.born; if(b.state==='mined'&&age>1200)b.state='proving'; if(b.state==='proving'){for(var i=0;i<4;i++){if(!b.claimed[i]&&Math.random()<0.012){b.claimed[i]=true;var fromTop=Math.random()<0.5;sparks.push({x:Math.random()*W,y:fromTop?-6:H+6,tx:b.x,ty:b.y,b:b,i:i,p:0});}} - if(b.shards.every(function(v){return v>=1;})){b.state='proven';b.glow=1;proven++;cp.textContent=proven;if(b.cp)setTimeout(function(){b.locked=true;b.flash=1;locked++;cl.textContent=locked;ripples.push({x:b.x,y:b.y,r:S,a:1});ripples.push({x:b.x,y:b.y,r:S*0.5,a:1});},1800);}} - if(b.flash>0)b.flash-=0.012; + if(b.shards.every(function(v){return v>=1;})){b.state='proven';b.glow=1;proven++;cp.textContent=proven;if(b.cp)setTimeout(function(){b.locked=true;locked++;cl.textContent=locked;ripples.push({x:b.x,y:b.y,r:S,a:1});ripples.push({x:b.x,y:b.y,r:S*0.5,a:1});},1800);}} if(b.glow>0)b.glow-=0.02;}); sparks.forEach(function(sp){sp.p+=0.035;sp.tx=sp.b.x;sp.ty=sp.b.y;if(sp.p>=1){sp.done=true;sp.b.shards[sp.i]=1;}}); sparks=sparks.filter(function(sp){return !sp.done;}); @@ -503,15 +501,14 @@ footer .wrap{padding-block:48px 32px} blocks.forEach(function(b){b.parents.forEach(function(q){if(blocks.indexOf(q)<0)return;var both=b.state==='proven'&&q.state==='proven';x.strokeStyle=both?'rgba(242,84,27,0.45)':'rgba(154,154,158,0.22)';x.lineWidth=both?1.5:1;x.beginPath();x.moveTo(b.x,b.y);var mx=(b.x+q.x)/2;x.bezierCurveTo(mx,b.y,mx,q.y,q.x,q.y);x.stroke();});}); // lock line: everything left of the newest locked block is final var lk=null;blocks.forEach(function(b){if(b.locked&&(!lk||b.x>lk.x))lk=b;}); - if(lk){if(lk.flash>0){x.fillStyle='rgba(242,84,27,'+(0.16*lk.flash)+')';x.fillRect(0,0,lk.x,H);}x.strokeStyle='rgba(242,84,27,0.7)';x.lineWidth=1.5;x.setLineDash([5,6]);x.beginPath();x.moveTo(lk.x,6);x.lineTo(lk.x,H-6);x.stroke();x.setLineDash([]);x.fillStyle='rgba(242,84,27,0.95)';x.font='600 '+Math.round(S*0.42)+'px IBM Plex Mono, monospace';x.textAlign='right';x.fillText('FINAL \u2190',lk.x-6,14);} + if(lk){x.strokeStyle='rgba(242,84,27,0.55)';x.lineWidth=1.5;x.setLineDash([5,6]);x.beginPath();x.moveTo(lk.x,6);x.lineTo(lk.x,H-6);x.stroke();x.setLineDash([]);} // blocks blocks.forEach(function(b){var h=S/2; if(b.glow>0){var g=x.createRadialGradient(b.x,b.y,0,b.x,b.y,S*1.6);g.addColorStop(0,'rgba(242,84,27,'+(0.45*b.glow)+')');g.addColorStop(1,'rgba(242,84,27,0)');x.fillStyle=g;x.beginPath();x.arc(b.x,b.y,S*1.6,0,Math.PI*2);x.fill();} if(b.state==='proven'){x.fillStyle='#F2541B';rr(b.x-h,b.y-h,S,S,S*0.24);x.fill();} else{x.fillStyle='#0C0C0E';rr(b.x-h,b.y-h,S,S,S*0.24);x.fill();x.strokeStyle=b.state==='proving'?'rgba(255,179,92,0.9)':'rgba(58,58,66,1)';x.lineWidth=2;rr(b.x-h,b.y-h,S,S,S*0.24);x.stroke(); if(b.state==='proving'){var q=S/2-3;for(var i=0;i<4;i++){if(b.shards[i]>=1){x.fillStyle='#FFB35C';var qx=b.x-h+3+(i%2)*q,qy=b.y-h+3+Math.floor(i/2)*q;rr(qx,qy,q-1,q-1,2);x.fill();}}}} - if(b.cp&&!b.locked){x.strokeStyle='rgba(255,179,92,0.7)';x.lineWidth=1.5;x.setLineDash([3,3]);x.beginPath();x.arc(b.x,b.y,S*1.05,0,Math.PI*2);x.stroke();x.setLineDash([]);x.fillStyle='rgba(255,179,92,0.9)';x.font='600 '+Math.round(S*0.38)+'px IBM Plex Mono, monospace';x.textAlign='center';x.fillText('CHECKPOINT',b.x,b.y-S*1.35);} - if(b.locked){var pulse=0.85+0.15*Math.sin(t/300);x.strokeStyle='rgba(242,84,27,'+pulse+')';x.lineWidth=3;x.beginPath();x.arc(b.x,b.y,S*1.05,0,Math.PI*2);x.stroke();x.fillStyle='#F2541B';rr(b.x-S*0.9,b.y-S*1.75,S*1.8,S*0.6,4);x.fill();x.fillStyle='#0C0C0E';x.font='700 '+Math.round(S*0.4)+'px IBM Plex Mono, monospace';x.textAlign='center';x.fillText('LOCKED',b.x,b.y-S*1.32);} + if(b.locked){var pulse=0.8+0.2*Math.sin(t/350);x.strokeStyle='rgba(242,84,27,'+pulse+')';x.lineWidth=3;x.beginPath();x.arc(b.x,b.y,S*1.15,0,Math.PI*2);x.stroke();x.fillStyle='#F4F1EC';x.beginPath();x.arc(b.x,b.y,S*0.16,0,Math.PI*2);x.fill();} }); // sparks (miners claiming shards) sparks.forEach(function(sp){var e=1-Math.pow(1-sp.p,3);var sx=sp.x+(sp.tx-sp.x)*e,sy=sp.y+(sp.ty-sp.y)*e;x.strokeStyle='rgba(255,179,92,'+(0.5*(1-sp.p))+')';x.lineWidth=1;x.beginPath();x.moveTo(sp.x+(sp.tx-sp.x)*Math.max(0,e-0.15),sp.y+(sp.ty-sp.y)*Math.max(0,e-0.15));x.lineTo(sx,sy);x.stroke();x.fillStyle='#FFB35C';x.beginPath();x.arc(sx,sy,2.2,0,Math.PI*2);x.fill();});