Chain scene: back to four states with a bold locked ring
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7375cb5b7d
commit
57745fc056
6 changed files with 29 additions and 17 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<<<grid, block>>>(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;
|
||||
|
|
|
|||
|
|
@ -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)]],
|
||||
|
|
|
|||
|
|
@ -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 <cuda_runtime.h>
|
||||
#include <cstdint>
|
||||
#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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]])
|
||||
|
|
|
|||
|
|
@ -257,8 +257,7 @@ footer .wrap{padding-block:48px 32px}
|
|||
<span><i class="sw" style="border:2px solid var(--line-2)"></i>mined</span>
|
||||
<span><i class="sw" style="background:var(--molten)"></i>shards being proven</span>
|
||||
<span><i class="sw" style="background:var(--ember)"></i>proven</span>
|
||||
<span><i class="sw" style="border:2px dashed var(--molten);border-radius:50%"></i>checkpoint, every 30 s on chain</span>
|
||||
<span><i class="sw" style="border:3px solid var(--ember);border-radius:50%"></i>locked by sustained miners, final</span>
|
||||
<span><i class="sw" style="border:3px solid var(--ember);border-radius:50%;background:transparent"></i>checkpoint locked by sustained miners</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:24px">
|
||||
|
|
@ -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();});
|
||||
|
|
|
|||
Loading…
Reference in a new issue