Workers: the program class and era on the serve protocol; a pack of the wrong class or era is refused (Counter ASIC 2.0)
packfile.h: pf_load refuses a generator other than 2 or 3 (spec 01 section 1.4.5), reads IGNEUM_PROGRAM_CLASS (must match the generator) and IGNEUM_ERA_SEED_HEX; pf_pack_class_ok and pf_class_token are the one rule for the `class=<v2|v3> era=<hex>` tokens a job or prepare line of a class v3 epoch ends with (a v2 line is the line of before, byte for byte). CUDA worker.cpp and OpenCL host.c: a pair's identity includes its class and era when the line names them, so a prepared pack of the right class wins over the resident pack of the same seeds; right seeds with the wrong class answer `need` plus `error <id> pack <dir>: program class mismatch ...`, and a prepare on a pack of the wrong class fails in plain words. Metal main.swift: refuses every class v3 line until the Swift generator carries version 3 (the integration branch). emu/packfile-test.c: the generator rule, a v3 pack with its era, the token matcher, the contradicting class line (13 checks pass). igneum-pow verify.rs: Epoch::chain_dataset(day, class), the one entry the node's day cache goes through, so the ca2-mixer class-specific item construction and cache schedule have a seam. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1d69ba80c0
commit
d29e21b1cd
6 changed files with 182 additions and 14 deletions
|
|
@ -454,9 +454,7 @@ impl Epoch {
|
|||
/// exactly (the era bytes are ignored and not recorded); class v3 draws from [`crate::generator::V3_CLASS`]
|
||||
/// with generator version 3 and records the era seed bytes (`E_n`) in the program for the pack.
|
||||
pub fn from_chain_seeds(epoch_seed: &[u8], day_bytes: &[u8], era_bytes: Option<&[u8]>, class: ProgramClass, label: &str) -> Self {
|
||||
let mut epoch = Self::from_seed_bytes_class(epoch_seed, day_bytes, label, class.load_class());
|
||||
epoch.program = Self::chain_program(epoch_seed, era_bytes, class, label);
|
||||
epoch
|
||||
Self { program: Self::chain_program(epoch_seed, era_bytes, class, label), dataset: Self::chain_dataset(day_bytes, class) }
|
||||
}
|
||||
|
||||
/// The program alone of [`Epoch::from_chain_seeds`] (no cache fill): for an engine that shares the day's cache.
|
||||
|
|
@ -464,6 +462,18 @@ impl Epoch {
|
|||
crate::generator::generate_from_seed_bytes_program_class(label, epoch_seed, class, era_bytes)
|
||||
}
|
||||
|
||||
/// The day's cache and dataset of [`Epoch::from_chain_seeds`], the one entry the node's engine builds a day
|
||||
/// cache through. The class is an argument because the Counter ASIC 2.0 integration gives class v3 its own item
|
||||
/// construction (the mixer multiplier) and cache size schedule (ca2-mixer); today both classes build the day of
|
||||
/// [`Epoch::from_seed_bytes`], and the engine keys its day caches on `(day, class)` so the two never share one.
|
||||
pub fn chain_dataset(day_bytes: &[u8], class: ProgramClass) -> DatasetSource {
|
||||
let _ = class;
|
||||
let key = crate::seed::seed_words_from_bytes(day_bytes);
|
||||
let mut dataset = DatasetSource::from_key(key, DatasetMode::MemoryHard, DEFAULT_DATASET_LOG2);
|
||||
dataset.key_bytes = day_bytes.to_vec();
|
||||
dataset
|
||||
}
|
||||
|
||||
/// The 32 hashes of the warp starting at `base_nonce`.
|
||||
pub fn hash_warp(&self, base_nonce: u32) -> [u64; LANES] {
|
||||
hash_warp(&self.program, base_nonce, &self.dataset)
|
||||
|
|
|
|||
|
|
@ -103,11 +103,41 @@ int main(int argc, char** argv) {
|
|||
{
|
||||
char text[2000], sw[200], kw[200];
|
||||
words_hex(bare, sw); words_hex(keyw, kw);
|
||||
snprintf(text, sizeof(text), "#define IGNEUM_SEED_BYTES_HEX \"%s\"\n#define IGNEUM_DAY_BYTES_HEX \"%s\"\n#define IGNEUM_DATASET_LOG2 28\n#define IGNEUM_DATASET_MODE 1\n#define IGNEUM_SEEDW_INIT { %s }\n#define IGNEUM_KEY_INIT { %s }\n#define IGNEUM_CACHE_LOG2_WORDS 26\n#define IGNEUM_CACHE_SEGMENTS 4096u\n", EPOCH_34, DAY_20731, sw, kw);
|
||||
snprintf(text, sizeof(text), "#define IGNEUM_SEED_BYTES_HEX \"%s\"\n#define IGNEUM_DAY_BYTES_HEX \"%s\"\n#define IGNEUM_GENERATOR 2\n#define IGNEUM_DATASET_LOG2 28\n#define IGNEUM_DATASET_MODE 1\n#define IGNEUM_SEEDW_INIT { %s }\n#define IGNEUM_KEY_INIT { %s }\n#define IGNEUM_CACHE_LOG2_WORDS 26\n#define IGNEUM_CACHE_SEGMENTS 4096u\n", EPOCH_34, DAY_20731, sw, kw);
|
||||
write_file(dir, "program.h", text);
|
||||
write_file(dir, "seeds.txt", "epoch_seed_hex " EPOCH_34 "\nday_seed_hex " DAY_20731 "\n");
|
||||
err[0] = 0;
|
||||
CHECK(pf_load(dir, &pk, err, sizeof(err)) == 1 && pk.attempt == 0, "no attempt line reads as attempt 0 and the bare words load");
|
||||
CHECK(strcmp(pk.programClass, "v2") == 0 && pk.eraHex[0] == 0, "a generator 2 pack without a class line is class v2 with no era");
|
||||
}
|
||||
// 4. Program classes (Counter ASIC 2.0, 5 October 2026, spec 01 section 1.4.5): a generator this worker does not
|
||||
// run is refused; a generator 3 pack is class v3 and carries its era seed; a class line that contradicts the
|
||||
// generator is refused; a pack with no generator line at all (generator 1, the retired lever generator) is refused.
|
||||
{
|
||||
char text[2400], sw[200], kw[200], why[256];
|
||||
words_hex(bare, sw); words_hex(keyw, kw);
|
||||
snprintf(text, sizeof(text), "#define IGNEUM_SEED_BYTES_HEX \"%s\"\n#define IGNEUM_DAY_BYTES_HEX \"%s\"\n#define IGNEUM_GENERATOR 9\n#define IGNEUM_DATASET_LOG2 28\n#define IGNEUM_DATASET_MODE 1\n#define IGNEUM_SEEDW_INIT { %s }\n#define IGNEUM_KEY_INIT { %s }\n#define IGNEUM_CACHE_LOG2_WORDS 26\n#define IGNEUM_CACHE_SEGMENTS 4096u\n", EPOCH_34, DAY_20731, sw, kw);
|
||||
write_file(dir, "program.h", text);
|
||||
err[0] = 0;
|
||||
CHECK(pf_load(dir, &pk, err, sizeof(err)) == 0 && strstr(err, "generator 9 is not a generator version this worker runs") != NULL, "generator 9 is refused in plain words");
|
||||
snprintf(text, sizeof(text), "#define IGNEUM_SEED_BYTES_HEX \"%s\"\n#define IGNEUM_DAY_BYTES_HEX \"%s\"\n#define IGNEUM_GENERATOR 3\n#define IGNEUM_PROGRAM_CLASS \"v3\"\n#define IGNEUM_ERA_SEED_HEX \"%s\"\n#define IGNEUM_DATASET_LOG2 28\n#define IGNEUM_DATASET_MODE 1\n#define IGNEUM_SEEDW_INIT { %s }\n#define IGNEUM_KEY_INIT { %s }\n#define IGNEUM_CACHE_LOG2_WORDS 26\n#define IGNEUM_CACHE_SEGMENTS 4096u\n", EPOCH_34, DAY_20731, EPOCH_33, sw, kw);
|
||||
write_file(dir, "program.h", text);
|
||||
err[0] = 0;
|
||||
CHECK(pf_load(dir, &pk, err, sizeof(err)) == 1 && strcmp(pk.programClass, "v3") == 0 && strcmp(pk.eraHex, EPOCH_33) == 0, "a generator 3 pack loads as class v3 with its era seed");
|
||||
CHECK(pf_pack_class_ok(pk.programClass, pk.eraHex, "v3", EPOCH_33, why, sizeof(why)) == 1, "the v3 pack matches a job naming class v3 and its era");
|
||||
CHECK(pf_pack_class_ok(pk.programClass, pk.eraHex, "", "", why, sizeof(why)) == 1, "a job naming no class accepts the pack");
|
||||
CHECK(pf_pack_class_ok(pk.programClass, pk.eraHex, "v2", "", why, sizeof(why)) == 0 && strstr(why, "program class mismatch") == why, "a job naming class v2 refuses the v3 pack");
|
||||
CHECK(pf_pack_class_ok(pk.programClass, pk.eraHex, "v3", EPOCH_34, why, sizeof(why)) == 0 && strstr(why, "era seed mismatch") == why, "a job naming another era refuses the v3 pack");
|
||||
CHECK(pf_pack_class_ok("v2", "", "v3", EPOCH_33, why, sizeof(why)) == 0, "a job naming class v3 refuses a v2 pack");
|
||||
CHECK(pf_pack_class_ok("v2", "", "v2", EPOCH_33, why, sizeof(why)) == 1, "an era named on a v2 job is ignored");
|
||||
snprintf(text, sizeof(text), "#define IGNEUM_SEED_BYTES_HEX \"%s\"\n#define IGNEUM_DAY_BYTES_HEX \"%s\"\n#define IGNEUM_GENERATOR 3\n#define IGNEUM_PROGRAM_CLASS \"v2\"\n#define IGNEUM_DATASET_LOG2 28\n#define IGNEUM_DATASET_MODE 1\n#define IGNEUM_SEEDW_INIT { %s }\n#define IGNEUM_KEY_INIT { %s }\n#define IGNEUM_CACHE_LOG2_WORDS 26\n#define IGNEUM_CACHE_SEGMENTS 4096u\n", EPOCH_34, DAY_20731, sw, kw);
|
||||
write_file(dir, "program.h", text);
|
||||
err[0] = 0;
|
||||
CHECK(pf_load(dir, &pk, err, sizeof(err)) == 0 && strstr(err, "does not match IGNEUM_GENERATOR 3") != NULL, "a class line that contradicts the generator is refused");
|
||||
snprintf(text, sizeof(text), "#define IGNEUM_SEED_BYTES_HEX \"%s\"\n#define IGNEUM_DAY_BYTES_HEX \"%s\"\n#define IGNEUM_DATASET_LOG2 28\n#define IGNEUM_DATASET_MODE 1\n#define IGNEUM_SEEDW_INIT { %s }\n#define IGNEUM_KEY_INIT { %s }\n#define IGNEUM_CACHE_LOG2_WORDS 26\n#define IGNEUM_CACHE_SEGMENTS 4096u\n", EPOCH_34, DAY_20731, sw, kw);
|
||||
write_file(dir, "program.h", text);
|
||||
err[0] = 0;
|
||||
CHECK(pf_load(dir, &pk, err, sizeof(err)) == 0 && strstr(err, "generator 1 is not") != NULL, "a pack with no generator line (generator 1) is refused");
|
||||
}
|
||||
|
||||
printf("%s: %d failure(s)\n", argv[0], failures);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ typedef struct {
|
|||
// read-width experiment (5 October 2026): the load class (0 when absent), bytes per hash, variant 5's scratch
|
||||
uint32_t loadsPerHash, bytesPerHash, scratchOps, persistent, scratchWordsPerLane;
|
||||
char loadClass[64];
|
||||
char programClass[8]; /* IGNEUM_PROGRAM_CLASS: "v2" or "v3" (Counter ASIC 2.0); absent = the generator's class */
|
||||
char eraHex[65]; /* IGNEUM_ERA_SEED_HEX of a class v3 chain pack; empty otherwise */
|
||||
// seeds.txt (or program.h): the seeds as the worker protocol carries them
|
||||
char epochHex[65];
|
||||
char dayHex[PF_HEX_CAP];
|
||||
|
|
@ -271,6 +273,21 @@ static int pf_load(const char* dir, PfPack* pk, char* err, size_t cap) {
|
|||
if (!pf_define_u32(prog, "IGNEUM_CACHE_LOG2_WORDS", &pk->cacheLog2Words)) { free(prog); return pf_fail(err, cap, "program.h has no IGNEUM_CACHE_LOG2_WORDS"); }
|
||||
if (!pf_define_u32(prog, "IGNEUM_CACHE_SEGMENTS", &pk->cacheSegments)) { free(prog); return pf_fail(err, cap, "program.h has no IGNEUM_CACHE_SEGMENTS"); }
|
||||
if (!pf_define_u32(prog, "IGNEUM_GENERATOR", &pk->generator)) pk->generator = 1;
|
||||
/* Spec 01 section 1.4.5: a pack whose generator version is not one this worker runs is refused. Generator 2 is
|
||||
* program class v2 (the lottery hash of 4 October 2026), generator 3 is class v3 (Counter ASIC 2.0). */
|
||||
if (pk->generator != 2 && pk->generator != 3) {
|
||||
char m[200]; snprintf(m, sizeof(m), "program pack generator %u is not a generator version this worker runs (2 or 3)", (unsigned)pk->generator);
|
||||
free(prog); return pf_fail(err, cap, m);
|
||||
}
|
||||
strcpy(pk->programClass, pk->generator == 3 ? "v3" : "v2");
|
||||
{
|
||||
char named[8] = {0};
|
||||
if (pf_define_str(prog, "IGNEUM_PROGRAM_CLASS", named, sizeof(named)) && strcmp(named, pk->programClass) != 0) {
|
||||
char m[200]; snprintf(m, sizeof(m), "program pack IGNEUM_PROGRAM_CLASS \"%.7s\" does not match IGNEUM_GENERATOR %u", named, (unsigned)pk->generator);
|
||||
free(prog); return pf_fail(err, cap, m);
|
||||
}
|
||||
}
|
||||
pk->eraHex[0] = 0; pf_define_str(prog, "IGNEUM_ERA_SEED_HEX", pk->eraHex, sizeof(pk->eraHex));
|
||||
if (!pf_define_u32(prog, "IGNEUM_PROGRAM_ATTEMPT", &pk->attempt)) pk->attempt = 0;
|
||||
if (pf_define_words(prog, "IGNEUM_SEEDW_INIT", pk->seedw, 8) != 8) { free(prog); return pf_fail(err, cap, "program.h has no IGNEUM_SEEDW_INIT with 8 words"); }
|
||||
pk->loadsPerHash = 128; pf_define_u32(prog, "IGNEUM_LOADS_PER_HASH", &pk->loadsPerHash);
|
||||
|
|
@ -382,4 +399,29 @@ static int pf_selftest(const PfPack* pk, const uint32_t* cacheHead, const uint32
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Counter ASIC 2.0 (5 October 2026): a job or prepare line may end with `class=<v2|v3>` and `era=<hex>` tokens (sent
|
||||
* only when the chain is on class v3, so every v2 line is the line of before). A pack matches the line when its class
|
||||
* is the named class and, when an era is named, its era seed is that era. Empty wanted strings accept any pack.
|
||||
* Returns 1 on a match, else 0 with the reason in `why`. */
|
||||
static int pf_pack_class_ok(const char* packClass, const char* packEra, const char* wantClass, const char* wantEra, char* why, size_t cap) {
|
||||
if (wantClass && wantClass[0] && strcmp(wantClass, packClass) != 0) {
|
||||
snprintf(why, cap, "program class mismatch: this pack is class %s, the job names class %s (export the pack again)", packClass, wantClass);
|
||||
return 0;
|
||||
}
|
||||
if (wantEra && wantEra[0] && strcmp(packClass, "v3") == 0) {
|
||||
size_t i; int eq = strlen(packEra) == strlen(wantEra);
|
||||
for (i = 0; eq && packEra[i]; ++i) if (tolower((unsigned char)packEra[i]) != tolower((unsigned char)wantEra[i])) eq = 0;
|
||||
if (!eq) { snprintf(why, cap, "era seed mismatch: this pack was drawn under era %.16s, the job names era %.16s (export the pack again)", packEra[0] ? packEra : "(none)", wantEra); return 0; }
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Reads a `class=` or `era=` token into `cls` / `era` (small fixed buffers). Returns 1 when the token was one of them. */
|
||||
static int pf_class_token(const char* tok, char* cls, size_t clsCap, char* era, size_t eraCap) {
|
||||
if (strncmp(tok, "class=", 6) == 0) { strncpy(cls, tok + 6, clsCap - 1); cls[clsCap - 1] = 0; return 1; }
|
||||
if (strncmp(tok, "era=", 4) == 0) { strncpy(era, tok + 4, eraCap - 1); era[eraCap - 1] = 0; return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -427,6 +427,7 @@ struct Pair {
|
|||
double raceMs = 0;
|
||||
// read-width experiment (5 October 2026): the pack's load class and, for variant 5, the persistent-warp scratch
|
||||
std::string loadClass = "v2";
|
||||
std::string programClass = "v2", eraHex; // Counter ASIC 2.0: the pack's class and era seed (packfile.h)
|
||||
uint32_t loadsPerHash = 128, bytesPerHash = 512, scratchOps = 0;
|
||||
bool persistent = false;
|
||||
CUdeviceptr scratch = 0;
|
||||
|
|
@ -439,6 +440,24 @@ static bool pairIs(const Pair* p, const std::string& epochHex, const std::string
|
|||
return p && hexEq(p->epochHex, epochHex) && hexEq(p->dayHex, dayHex);
|
||||
}
|
||||
|
||||
// Counter ASIC 2.0: a job that names a class (and an era) belongs to a pair of that class (and era) only, so a pack
|
||||
// of the old class for the same seeds is not this job's pair and the prepared pack of the right class wins.
|
||||
static bool pairIsClass(const Pair* p, const std::string& epochHex, const std::string& dayHex, const std::string& cls, const std::string& era) {
|
||||
char why[256];
|
||||
return pairIs(p, epochHex, dayHex) && pf_pack_class_ok(p->programClass.c_str(), p->eraHex.c_str(), cls.c_str(), era.c_str(), why, sizeof(why));
|
||||
}
|
||||
|
||||
// The trailing `class=` and `era=` tokens of a job or prepare line (absent on every class v2 line), removed from `f`.
|
||||
static void takeClassTokens(std::vector<std::string>& f, std::string& cls, std::string& era) {
|
||||
while (!f.empty()) {
|
||||
char c[8] = {0}, e[65] = {0};
|
||||
if (!pf_class_token(f.back().c_str(), c, sizeof(c), e, sizeof(e))) break;
|
||||
if (c[0]) cls = c;
|
||||
if (e[0]) era = e;
|
||||
f.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// The job loop and a race take turns on the card: a variant is timed with no job running (exclusive numbers), and
|
||||
// mining resumes between variants. Held per chunk by the job loop, per variant by the race.
|
||||
static std::mutex gpuMutex;
|
||||
|
|
@ -809,6 +828,7 @@ static Pair* buildPair(Ctx& c, const std::string& dir, CUstream s, std::string&
|
|||
c.drv.occupancy(&p->blocksPerSM, p->fHashBound, 32 * c.blockWarps, 0);
|
||||
}
|
||||
p->loadClass = pk.loadClass; p->loadsPerHash = pk.loadsPerHash; p->bytesPerHash = pk.bytesPerHash; p->scratchOps = pk.scratchOps;
|
||||
p->programClass = pk.programClass; p->eraHex = pk.eraHex;
|
||||
p->persistent = pk.persistent != 0;
|
||||
p->residentWarps = p->blocksPerSM * c.blockWarps * c.sms;
|
||||
size_t scratchBytes = 0;
|
||||
|
|
@ -912,6 +932,7 @@ static std::string pairSummary(const Pair* p) {
|
|||
|
||||
struct PrepareTask {
|
||||
std::string epochHex, dayHex, dir, error;
|
||||
std::string wantClass, wantEra; // the class and era the prepare line named (empty: any)
|
||||
std::atomic<bool> done{false};
|
||||
Pair* result = nullptr;
|
||||
double t0 = 0;
|
||||
|
|
@ -929,6 +950,11 @@ static void prepareRun(Ctx* c, PrepareTask* t) {
|
|||
err = "the pack in " + t->dir + " is for epoch " + p->epochHex.substr(0, 16) + " day " + p->dayHex + ", not the prepared seeds";
|
||||
releasePair(*c, p); p = nullptr;
|
||||
}
|
||||
char why[256];
|
||||
if (p && !pf_pack_class_ok(p->programClass.c_str(), p->eraHex.c_str(), t->wantClass.c_str(), t->wantEra.c_str(), why, sizeof(why))) {
|
||||
err = "pack " + t->dir + ": " + why;
|
||||
releasePair(*c, p); p = nullptr;
|
||||
}
|
||||
t->result = p;
|
||||
t->error = err;
|
||||
t->done = true;
|
||||
|
|
@ -1083,6 +1109,8 @@ static int runServe(Ctx& c, const Options& o, Pair* cur) {
|
|||
}
|
||||
delete task; task = nullptr;
|
||||
}
|
||||
std::string wantClass, wantEra;
|
||||
takeClassTokens(f, wantClass, wantEra);
|
||||
if (f[0] == "prepare") {
|
||||
if (f.size() < 4) { emit(fmt("prepare-failed %s %s a pack directory is needed as the third field (igneum-miner --prepare-packs <dir>)", f.size() > 1 ? f[1].c_str() : "0", f.size() > 2 ? f[2].c_str() : "0")); continue; }
|
||||
if (f[1].size() != 64) { emit(fmt("prepare-failed %s %s bad field (epoch_seed 64 hex, day_seed hex)", f[1].c_str(), f[2].c_str())); continue; }
|
||||
|
|
@ -1094,6 +1122,7 @@ static int runServe(Ctx& c, const Options& o, Pair* cur) {
|
|||
prepareRoot = parentDir(dir);
|
||||
task = new PrepareTask();
|
||||
task->epochHex = f[1]; task->dayHex = f[2]; task->dir = dir; task->t0 = wallMs();
|
||||
task->wantClass = wantClass; task->wantEra = wantEra;
|
||||
task->thread = std::thread(prepareRun, &c, task);
|
||||
info(fmt("prepare started for epoch %.16s day %s from %s (NVRTC %s in the background)", f[1].c_str(), f[2].c_str(), dir.c_str(), c.archOpt.c_str()));
|
||||
continue;
|
||||
|
|
@ -1115,7 +1144,7 @@ static int runServe(Ctx& c, const Options& o, Pair* cur) {
|
|||
pf_seed_words_from_bytes(daySeed, dl, kw);
|
||||
double t0 = wallMs();
|
||||
bool switched = false;
|
||||
if (!pairIs(cur, f[6], f[7]) && !task && !pairIs(prepared, f[6], f[7])) {
|
||||
if (!pairIsClass(cur, f[6], f[7], wantClass, wantEra) && !task && !pairIsClass(prepared, f[6], f[7], wantClass, wantEra)) {
|
||||
// Self-heal: a job on seeds this worker has no pair for and no prepare in flight (a prepare failed, or
|
||||
// the miner never sent one). The miner writes a pack per pair under its --prepare-packs root; find it by
|
||||
// seeds.txt and build it now, in the foreground. The miner only re-sends prepare for the pair after this one.
|
||||
|
|
@ -1131,11 +1160,18 @@ static int runServe(Ctx& c, const Options& o, Pair* cur) {
|
|||
else emit("error " + jobId + " could not build " + dir + ": " + berr);
|
||||
}
|
||||
}
|
||||
if (!pairIs(cur, f[6], f[7])) {
|
||||
if (pairIs(prepared, f[6], f[7])) {
|
||||
if (!pairIsClass(cur, f[6], f[7], wantClass, wantEra)) {
|
||||
char why[256];
|
||||
if (pairIsClass(prepared, f[6], f[7], wantClass, wantEra)) {
|
||||
if (old) releasePair(c, old);
|
||||
old = cur; cur = prepared; prepared = nullptr; switched = true;
|
||||
info(fmt("switched to the prepared pair epoch %.16s day %s in %.2f ms", cur->epochHex.c_str(), cur->dayHex.c_str(), wallMs() - t0));
|
||||
info(fmt("switched to the prepared pair epoch %.16s day %s (class %s) in %.2f ms", cur->epochHex.c_str(), cur->dayHex.c_str(), cur->programClass.c_str(), wallMs() - t0));
|
||||
} else if (pairIs(cur, f[6], f[7]) && !pf_pack_class_ok(cur->programClass.c_str(), cur->eraHex.c_str(), wantClass.c_str(), wantEra.c_str(), why, sizeof(why))) {
|
||||
// Counter ASIC 2.0: right seeds, wrong class or era. The miner prepares the pair again from a pack of
|
||||
// the class the chain is on (the `need` line), and the pack of the other class is never mined.
|
||||
emit(fmt("need %s %s", f[6].c_str(), f[7].c_str()));
|
||||
emit(fmt("error %s pack %s: %s", jobId.c_str(), cur->dir.c_str(), why));
|
||||
continue;
|
||||
} else if (!hexEq(cur->epochHex, f[6])) {
|
||||
emit(fmt("need %s %s", f[6].c_str(), f[7].c_str())); // the miner prepares this pair (4 October 2026)
|
||||
emit(fmt("error %s epoch seed mismatch: this worker holds epoch %.16s (program words %08x %08x ...)%s, the job is for epoch %.16s (bare seed words %08x %08x ...); send prepare with a pack directory",
|
||||
|
|
|
|||
|
|
@ -3033,9 +3033,24 @@ func runServe(_ opts: Options) -> Never {
|
|||
emit("ready metal \(gpu.device.name.replacingOccurrences(of: " ", with: "_")) dataset-log2 \(datasetLog2) batch \(batch) prepare \(opts.noPrepare ? 0 : 1) race \(opts.race)")
|
||||
var lastPair: (String, String)? = nil
|
||||
while let line = readLine(strippingNewline: true) {
|
||||
let f = line.split(separator: " ").map(String.init)
|
||||
var f = line.split(separator: " ").map(String.init)
|
||||
if f.isEmpty { continue }
|
||||
if f[0] == "quit" { break }
|
||||
// Counter ASIC 2.0 (5 October 2026): a job or prepare line of a class v3 epoch ends with `class=v3 era=<hex>`.
|
||||
// This worker regenerates programs from the seed with the Swift version 2 generator, so it refuses every
|
||||
// class v3 line until the integration branch adds generator 3 here (the one-click workers build from packs
|
||||
// and take v3 packs).
|
||||
var wantClass = ""
|
||||
while let last = f.last, last.hasPrefix("class=") || last.hasPrefix("era=") {
|
||||
if last.hasPrefix("class=") { wantClass = String(last.dropFirst(6)) }
|
||||
f.removeLast()
|
||||
}
|
||||
if wantClass != "" && wantClass != "v2" {
|
||||
let id = f.count > 1 ? f[1] : "0"
|
||||
if f[0] == "prepare" { emit("prepare-failed \(id) \(f.count > 2 ? f[2] : "0") program class \(wantClass) is not implemented by this worker (Swift generator version 2 only)") }
|
||||
else { emit("error \(id) program class \(wantClass) is not implemented by this worker (Swift generator version 2 only); use the CUDA or OpenCL worker with a class \(wantClass) pack") }
|
||||
continue
|
||||
}
|
||||
if f[0] == "prepare" {
|
||||
if opts.noPrepare { emit("info ignored (started with --no-prepare): \(line)"); continue }
|
||||
if f.count < 3 { emit("prepare-failed 0 0 malformed prepare line (need epoch_seed_hex and day_seed_hex)"); continue }
|
||||
|
|
|
|||
|
|
@ -1052,6 +1052,8 @@ typedef struct {
|
|||
double buildMs, cacheMs, datasetMs, checkMs;
|
||||
char check[1024]; /* the self-test verdict (packfile.h), one line */
|
||||
int checked;
|
||||
char programClass[8]; /* Counter ASIC 2.0: the pack's class ("v2" or "v3") and era seed, from packfile.h */
|
||||
char eraHex[65];
|
||||
} ServePair;
|
||||
|
||||
static int hexEq(const char* a, const char* b) {
|
||||
|
|
@ -1071,6 +1073,18 @@ static int pairIs(const ServePair* p, const char* epochHex, const char* dayHex,
|
|||
return memcmp(sw, p->sw, 32) == 0 && memcmp(kw, p->kw, 32) == 0;
|
||||
}
|
||||
|
||||
/* Counter ASIC 2.0: a job that names a class (and an era) belongs to a pair of that class (and era) only. */
|
||||
static int pairIsClass(const ServePair* p, const char* epochHex, const char* dayHex, const uint32_t sw[8], const uint32_t kw[8], const char* cls, const char* era) {
|
||||
char why[256];
|
||||
return pairIs(p, epochHex, dayHex, sw, kw) && pf_pack_class_ok(p->programClass[0] ? p->programClass : "v2", p->eraHex, cls, era, why, sizeof(why));
|
||||
}
|
||||
|
||||
/* The trailing `class=` and `era=` tokens of a job or prepare line (absent on every class v2 line), removed from f. */
|
||||
static void takeClassTokens(char** f, int* nf, char* cls, size_t clsCap, char* era, size_t eraCap) {
|
||||
cls[0] = 0; era[0] = 0;
|
||||
while (*nf > 0 && pf_class_token(f[*nf - 1], cls, clsCap, era, eraCap)) --*nf;
|
||||
}
|
||||
|
||||
static void releasePair(ServePair* p) {
|
||||
if (!p) return;
|
||||
if (p->ds) { clReleaseMemObject(p->ds); ++gMemReleased; }
|
||||
|
|
@ -1089,6 +1103,8 @@ typedef struct {
|
|||
char epochHex[65];
|
||||
char dayHex[512];
|
||||
char packDir[1024];
|
||||
char wantClass[8]; /* the class and era the prepare line named (empty: any) */
|
||||
char wantEra[65];
|
||||
uint32_t words, cacheWords, segments;
|
||||
char error[512];
|
||||
ServePair* result; /* set by the thread on success */
|
||||
|
|
@ -1209,6 +1225,14 @@ static void prepareRun(PrepareTask* t) {
|
|||
double tb;
|
||||
strncpy(p->epochHex, t->epochHex, 64); p->epochHex[64] = 0;
|
||||
strncpy(p->dayHex, t->dayHex, sizeof(p->dayHex) - 1);
|
||||
{
|
||||
/* Counter ASIC 2.0: the pack's class and era are read before anything is built; a pack of another class
|
||||
* than the prepare line names is refused here, so the miner exports one of the right class */
|
||||
PfPack pk; char perr[512]; char why[256];
|
||||
if (!pf_load(t->packDir, &pk, perr, sizeof(perr))) { snprintf(t->error, sizeof(t->error), "pack %s: %s", t->packDir, perr); releasePair(p); t->doneAt = wallMs(); t->done = 1; return; }
|
||||
strncpy(p->programClass, pk.programClass, sizeof(p->programClass) - 1); strncpy(p->eraHex, pk.eraHex, sizeof(p->eraHex) - 1);
|
||||
if (!pf_pack_class_ok(p->programClass, p->eraHex, t->wantClass, t->wantEra, why, sizeof(why))) { snprintf(t->error, sizeof(t->error), "pack %s: %s", t->packDir, why); releasePair(p); t->doneAt = wallMs(); t->done = 1; return; }
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/kernel_bound.cl", t->packDir);
|
||||
src = readFile(path, &srcLen);
|
||||
if (!src) { snprintf(t->error, sizeof(t->error), "cannot read %s", path); releasePair(p); t->doneAt = wallMs(); t->done = 1; return; }
|
||||
|
|
@ -1370,6 +1394,7 @@ static int runServe(Device* dv, const DeviceInfo* di, const Options* o) {
|
|||
memcpy(cur->sw, gPack.seedw, 32); memcpy(cur->kw, gPack.keyw, 32);
|
||||
strncpy(cur->epochHex, gPack.epochHex, 64); cur->epochHex[64] = 0;
|
||||
strncpy(cur->dayHex, gPack.dayHex, sizeof(cur->dayHex) - 1);
|
||||
strncpy(cur->programClass, gPack.programClass, sizeof(cur->programClass) - 1); strncpy(cur->eraHex, gPack.eraHex, sizeof(cur->eraHex) - 1);
|
||||
if (!pairBuffers(dv, di, dv->q, cur, words, gServeCacheWords, gServeSegments, o->packDir, perr, sizeof(perr))) { printf("error 0 pack %s: %s\n", o->packDir, perr); fflush(stdout); return 1; }
|
||||
printf("info first pack %s: cache %.0f dataset %.0f check %.0f ms (%.0f ms in all); %s\n", o->packDir, cur->cacheMs, cur->datasetMs, cur->checkMs, wallMs() - t0, cur->check);
|
||||
} else {
|
||||
|
|
@ -1462,11 +1487,12 @@ static int runServe(Device* dv, const DeviceInfo* di, const Options* o) {
|
|||
#define SERVE_FATAL(jobIdStr, fmt, ...) do { printf("error %s worker fault: " fmt "; exiting 3 so the miner restarts the worker\n", jobIdStr, __VA_ARGS__); fflush(stdout); exit(3); } while (0)
|
||||
#define SERVE_CHECK(jobIdStr, call) do { cl_int e_ = (call); if (e_ != CL_SUCCESS) SERVE_FATAL(jobIdStr, "%s failed with %s (%d)", #call, clErrName(e_), (int)e_); } while (0)
|
||||
while (fgets(line, sizeof(line), stdin)) {
|
||||
char* f[9];
|
||||
char* f[12]; /* job + 7 fields, plus the class= and era= tokens of a class v3 line */
|
||||
int nf = 0;
|
||||
char* tok;
|
||||
char* save = NULL;
|
||||
char jobId[64];
|
||||
char wantClass[8], wantEra[65];
|
||||
uint8_t prehash[32], epochSeed[32], daySeed[256];
|
||||
size_t prehashLen = 0, epochLen = 0, dayLen = 0;
|
||||
unsigned long long target = 0, nonceStart = 0, nonceCount = 0;
|
||||
|
|
@ -1494,9 +1520,10 @@ static int runServe(Device* dv, const DeviceInfo* di, const Options* o) {
|
|||
fflush(stdout);
|
||||
free(task); task = NULL;
|
||||
}
|
||||
for (tok = strtok_r(line, " ", &save); tok && nf < 9; tok = strtok_r(NULL, " ", &save)) f[nf++] = tok;
|
||||
for (tok = strtok_r(line, " ", &save); tok && nf < 12; tok = strtok_r(NULL, " ", &save)) f[nf++] = tok;
|
||||
if (nf == 0) continue;
|
||||
if (strcmp(f[0], "quit") == 0) break;
|
||||
takeClassTokens(f, &nf, wantClass, sizeof(wantClass), wantEra, sizeof(wantEra));
|
||||
if (strcmp(f[0], "prepare") == 0) {
|
||||
if (o->noPrepare) { printf("info ignored (started with --no-prepare): prepare\n"); fflush(stdout); continue; }
|
||||
if (nf < 4) { printf("prepare-failed %s %s this ahead-of-time worker needs a pack directory as the third field (igneum-miner --prepare-packs <dir>)\n", nf > 1 ? f[1] : "0", nf > 2 ? f[2] : "0"); fflush(stdout); continue; }
|
||||
|
|
@ -1506,6 +1533,7 @@ static int runServe(Device* dv, const DeviceInfo* di, const Options* o) {
|
|||
task = (PrepareTask*)calloc(1, sizeof(PrepareTask));
|
||||
task->dv = dv; task->di = di; task->words = words; task->cacheWords = gServeCacheWords; task->segments = gServeSegments; task->t0 = wallMs();
|
||||
strncpy(task->epochHex, f[1], 64); strncpy(task->dayHex, f[2], sizeof(task->dayHex) - 1); strncpy(task->packDir, f[3], sizeof(task->packDir) - 1);
|
||||
strncpy(task->wantClass, wantClass, sizeof(task->wantClass) - 1); strncpy(task->wantEra, wantEra, sizeof(task->wantEra) - 1);
|
||||
if (!startPrepareThread(task)) { printf("prepare-failed %s %s cannot start the prepare thread\n", f[1], f[2]); fflush(stdout); free(task); task = NULL; continue; }
|
||||
printf("info prepare started for epoch %.16s day %s from %s (builds in the background)\n", f[1], f[2], f[3]); fflush(stdout);
|
||||
continue;
|
||||
|
|
@ -1522,12 +1550,19 @@ static int runServe(Device* dv, const DeviceInfo* di, const Options* o) {
|
|||
seedWordsFromBytes(epochSeed, 32, sw);
|
||||
seedWordsFromBytes(daySeed, dayLen, kw);
|
||||
t0 = wallMs();
|
||||
if (!pairIs(cur, f[6], f[7], sw, kw)) {
|
||||
if (pairIs(prepared, f[6], f[7], sw, kw)) {
|
||||
if (!pairIsClass(cur, f[6], f[7], sw, kw, wantClass, wantEra)) {
|
||||
char why[256];
|
||||
if (pairIsClass(prepared, f[6], f[7], sw, kw, wantClass, wantEra)) {
|
||||
/* The prepared pair: switch now, release the old one after this job */
|
||||
if (old) releasePair(old);
|
||||
old = cur; cur = prepared; prepared = NULL; switched = 1;
|
||||
printf("info switched to the prepared pair epoch %.16s day %s in %.2f ms\n", cur->epochHex, cur->dayHex, wallMs() - t0); fflush(stdout);
|
||||
printf("info switched to the prepared pair epoch %.16s day %s (class %s) in %.2f ms\n", cur->epochHex, cur->dayHex, cur->programClass[0] ? cur->programClass : "v2", wallMs() - t0); fflush(stdout);
|
||||
} else if (pairIs(cur, f[6], f[7], sw, kw) && !pf_pack_class_ok(cur->programClass[0] ? cur->programClass : "v2", cur->eraHex, wantClass, wantEra, why, sizeof(why))) {
|
||||
/* Counter ASIC 2.0: right seeds, wrong class or era; the miner prepares the pair from a pack of the
|
||||
* class the chain is on (the need line), and this pack is never mined */
|
||||
printf("need %s %s\n", f[6], f[7]);
|
||||
printf("error %s pack %s: %s\n", jobId, o->packDir ? o->packDir : "(compiled-in)", why);
|
||||
fflush(stdout); continue;
|
||||
} else if (cur->epochHex[0] ? !hexEq(cur->epochHex, f[6]) : memcmp(sw, cur->sw, 32) != 0) {
|
||||
printf("need %s %s\n", f[6], f[7]); /* the miner prepares this pair (4 October 2026) */
|
||||
printf("error %s epoch seed mismatch: this worker holds %s%s (program words %08x %08x ...)%s, the job is for epoch %.16s (bare seed words %08x %08x ...); send prepare with a pack directory, or run igneum-miner export-pack and rebuild\n",
|
||||
|
|
|
|||
Loading…
Reference in a new issue