diff --git a/igneum-pow/src/verify.rs b/igneum-pow/src/verify.rs index db324e7b6..3b93e79c0 100644 --- a/igneum-pow/src/verify.rs +++ b/igneum-pow/src/verify.rs @@ -417,6 +417,11 @@ pub struct Epoch { /// Default dataset size: 2^28 words = 1 GiB. pub const DEFAULT_DATASET_LOG2: u32 = 28; +/// Days a day index lies after the network's genesis day (0 for the genesis day and any day before it). +pub fn days_since_genesis(day_index: u64, genesis_day_index: u64) -> u64 { + day_index.saturating_sub(genesis_day_index) +} + impl Epoch { pub fn new(seed: &str, day: &str, mode: DatasetMode, dataset_log2: u32) -> Self { Self { program: generate(seed), dataset: DatasetSource::new(day, mode, dataset_log2) } @@ -467,9 +472,18 @@ impl Epoch { /// 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; + Self::chain_dataset_day(day_bytes, class, 0, DEFAULT_DATASET_LOG2) + } + + /// [`Epoch::chain_dataset`] with the day's position since genesis and the network's genesis dataset size: the + /// entry the node's engine and the miner's export build every day cache through, so the cache growth schedule + /// of spec 01 section 1.13.3 has one place to act. SEAM (ca2-node, 5 October 2026): the body here builds the + /// genesis-size cache for every day; the ca2-mixer branch fills the growth rule (`growth_doublings`) and the + /// class v3 item construction, keeping this signature. + pub fn chain_dataset_day(day_bytes: &[u8], class: ProgramClass, days_since_genesis: u64, genesis_dataset_log2: u32) -> DatasetSource { + let _ = (class, days_since_genesis); let key = crate::seed::seed_words_from_bytes(day_bytes); - let mut dataset = DatasetSource::from_key(key, DatasetMode::MemoryHard, DEFAULT_DATASET_LOG2); + let mut dataset = DatasetSource::from_key(key, DatasetMode::MemoryHard, genesis_dataset_log2); dataset.key_bytes = day_bytes.to_vec(); dataset } diff --git a/infra/fast-time/override-60x.json b/infra/fast-time/override-60x.json index 7ec2e3b05..b8e77dffe 100644 --- a/infra/fast-time/override-60x.json +++ b/infra/fast-time/override-60x.json @@ -55,6 +55,7 @@ "proving_v0_activation_daa": 18446744073709551615, "finality_v3_activation_daa": 18446744073709551615, "program_class_v3_activation_daa": 18446744073709551615, + "pow_genesis_dataset_log2": 28, "fees_v1_activation_daa": 0, "fees": {"pgas": {"version": 1, "cycles_per_pgas": 1000, "intrinsic_pgas_per_tx": 300, "modexp_base": 10, "modexp_per_byte_numer": 1, "modexp_per_byte_denom": 10}, "block_proving_gas_limit": 120000, "shard_proving_gas_budget": 30000, "min_execution_base_fee_wei": 100000000000, "min_proving_base_fee_wei": 10000000000000, "initial_execution_base_fee_wei": 100000000000, "initial_proving_base_fee_wei": 10000000000000, "base_fee_change_denominator": 8} } diff --git a/proto-metal/main.swift b/proto-metal/main.swift index ef1ad029a..09e52fa42 100644 --- a/proto-metal/main.swift +++ b/proto-metal/main.swift @@ -2781,13 +2781,32 @@ func blockInitWords(prehash: [UInt8], nonceHi: UInt32) -> [UInt32] { final class ServeProgram { let seedHex: String - let program: Program + /// The Swift-generated program (class v2); nil for a program compiled from a pack (class v3, Counter ASIC 2.0), + /// which has no Swift instruction list and so never races variants + let program: Program? + /// Counter ASIC 2.0: the class ("v2" or "v3") and, for a pack program, its era seed hex and loads per hash + let programClass: String + let eraHex: String + let loadsPerHash: Int private var slot: CompiledHash private let lock = NSLock() /// true until a race ran for this program (an inline compile races after the first job on it) var raceDue = true var raceLine = "" - init(seedHex: String, program: Program, compiled: CompiledHash) { self.seedHex = seedHex; self.program = program; self.slot = compiled } + init(seedHex: String, program: Program, compiled: CompiledHash) { + self.seedHex = seedHex; self.program = program; self.slot = compiled + self.programClass = "v2"; self.eraHex = ""; self.loadsPerHash = program.loadsPerHash + } + init(seedHex: String, packClass: String, eraHex: String, loadsPerHash: Int, compiled: CompiledHash) { + self.seedHex = seedHex; self.program = nil; self.slot = compiled + self.programClass = packClass; self.eraHex = eraHex; self.loadsPerHash = loadsPerHash; self.raceDue = false + } + /// Whether this program is the one a line naming `cls` (and `era`) wants; empty names accept any (class v2 lines). + func matches(cls: String, era: String) -> Bool { + if cls != "" && cls != programClass { return false } + if era != "" && programClass == "v3" && era.lowercased() != eraHex.lowercased() { return false } + return true + } var compiled: CompiledHash { lock.lock(); defer { lock.unlock() }; return slot } func install(_ c: CompiledHash) { lock.lock(); slot = c; lock.unlock() } } @@ -2873,7 +2892,8 @@ func raceProgram(_ gpu: GPU, _ program: ServeProgram, dataset: MTLBuffer, datase // Compile (the base is already compiled) for i in 1.. deadline { entries[i].note = "not compiled: the race budget ran out"; continue } - do { entries[i].compiled = try compileBound(gpu, msl: generateMSL(program.program, datasetLog2: datasetLog2, source: .stored, bound: true, variant: entries[i].v), variant: entries[i].v) } + guard let swiftProgram = program.program else { return "race \(program.seedHex.prefix(16)) skipped: a pack program (class \(program.programClass)) has no variants to race" } + do { entries[i].compiled = try compileBound(gpu, msl: generateMSL(swiftProgram, datasetLog2: datasetLog2, source: .stored, bound: true, variant: entries[i].v), variant: entries[i].v) } catch { entries[i].note = "compile: \(error)".prefix(200).description } } let compileMs = ms(t0, nowNs()) @@ -2926,7 +2946,7 @@ func raceProgram(_ gpu: GPU, _ program: ServeProgram, dataset: MTLBuffer, datase if win != 0, let k = entries[win].compiled { program.install(k) } let total = ms(t0, nowNs()) let os = ProcessInfo.processInfo.operatingSystemVersion - var line = "race \(program.seedHex.prefix(16)) device \(device) driver macos-\(os.majorVersion).\(os.minorVersion).\(os.patchVersion) arch metal loads \(program.program.loadsPerHash) wide \(program.program.wideLoadsPerHash) variants \(entries.count)" + var line = "race \(program.seedHex.prefix(16)) device \(device) driver macos-\(os.majorVersion).\(os.minorVersion).\(os.patchVersion) arch metal loads \(program.loadsPerHash) wide \(program.program?.wideLoadsPerHash ?? 0) variants \(entries.count)" for e in entries { line += e.ok && (e.mhs > 0 || pinnedOnly) ? " \(e.v.name)=\(fmt(e.mhs, 3))/\(e.compiled!.pipeline.maxTotalThreadsPerThreadgroup)t/\(e.v.groupWidth)w" : " \(e.v.name)=-" } let gain = baseMhs > 0 ? (winMhs / baseMhs - 1) * 100 : 0 line += " winner \(entries[win].v.name) \(fmt(winMhs, 3)) base \(fmt(baseMhs, 3)) gain \(gain >= 0 ? "+" : "")\(fmt(gain, 2))% compile \(fmt(compileMs, 0)) bench \(fmt(total - compileMs, 0)) total \(fmt(total, 0)) ms" @@ -2957,14 +2977,14 @@ final class ServeStore { /// The pair of the last job (epoch seed hex, day seed hex); never evicted var current: (String, String)? = nil - func program(_ seedHex: String) -> ServeProgram? { lock.lock(); defer { lock.unlock() }; return programs.first { $0.seedHex == seedHex } } + func program(_ seedHex: String, cls: String = "", era: String = "") -> ServeProgram? { lock.lock(); defer { lock.unlock() }; return programs.first { $0.seedHex == seedHex && $0.matches(cls: cls, era: era) } } func dataset(_ dayHex: String) -> ServeDataset? { lock.lock(); defer { lock.unlock() }; return datasets.first { $0.dayHex == dayHex } } func counts() -> (Int, Int) { lock.lock(); defer { lock.unlock() }; return (programs.count, datasets.count) } /// Adds a program; with more than two resident, the oldest one that is not the current job's goes. func add(_ p: ServeProgram) { lock.lock(); defer { lock.unlock() } - if programs.contains(where: { $0.seedHex == p.seedHex }) { return } + if programs.contains(where: { $0.seedHex == p.seedHex && $0.programClass == p.programClass && $0.eraHex == p.eraHex }) { return } programs.append(p) while programs.count > 2, let i = programs.firstIndex(where: { $0.seedHex != current?.0 && $0.seedHex != p.seedHex }) { programs.remove(at: i) } } @@ -3011,6 +3031,51 @@ func serveProgram(_ gpu: GPU, _ store: ServeStore, seedHex: String, seed: [UInt8 return (sp, ms(t0, nowNs())) } +// Counter ASIC 2.0 (5 October 2026): a class v3 program comes from the pack igneum-miner export-pack / --prepare-packs +// wrote (program_bound.metal, the Rust emitter's text; program.h for the identity), never from the Swift generator. +// The pack is checked the way the one-click workers check it (proto-cuda/nvrtc/packfile.h): IGNEUM_GENERATOR 2 or 3, +// the class line against the generator, the seed bytes against the line's epoch seed, IGNEUM_SEEDW_INIT against the +// words of IGNEUM_PROGRAM_ATTEMPT of that seed, and the class and era against what the line names. +func servePackProgram(_ gpu: GPU, _ store: ServeStore, seedHex: String, seed: [UInt8], dir: String, wantClass: String, wantEra: String) throws -> (ServeProgram, Double) { + if let p = store.program(seedHex, cls: wantClass, era: wantEra) { return (p, 0) } + let t0 = nowNs() + guard let programH = try? String(contentsOfFile: dir + "/program.h", encoding: .utf8) else { throw NSError(domain: "pack", code: 1, userInfo: [NSLocalizedDescriptionKey: "cannot read \(dir)/program.h"]) } + func defineU32(_ name: String) -> UInt32? { + guard let re = try? NSRegularExpression(pattern: "#define \(name) ([0-9a-fA-Fx]+)"), let m = re.firstMatch(in: programH, range: NSRange(programH.startIndex..., in: programH)) else { return nil } + let v = String(programH[Range(m.range(at: 1), in: programH)!]).replacingOccurrences(of: "u", with: "") + return v.hasPrefix("0x") ? UInt32(v.dropFirst(2), radix: 16) : UInt32(v) + } + func defineStr(_ name: String) -> String? { + guard let re = try? NSRegularExpression(pattern: "#define \(name) \"([^\"]*)\""), let m = re.firstMatch(in: programH, range: NSRange(programH.startIndex..., in: programH)) else { return nil } + return String(programH[Range(m.range(at: 1), in: programH)!]) + } + func defineWords(_ name: String) -> [UInt32]? { + guard let re = try? NSRegularExpression(pattern: "#define \(name) \\{([^}]*)\\}"), let m = re.firstMatch(in: programH, range: NSRange(programH.startIndex..., in: programH)) else { return nil } + let body = String(programH[Range(m.range(at: 1), in: programH)!]) + let words = body.split(separator: ",").compactMap { t -> UInt32? in let v = t.trimmingCharacters(in: .whitespaces).replacingOccurrences(of: "u", with: ""); return v.hasPrefix("0x") ? UInt32(v.dropFirst(2), radix: 16) : UInt32(v) } + return words.count == 8 ? words : nil + } + func refuse(_ why: String) -> NSError { NSError(domain: "pack", code: 2, userInfo: [NSLocalizedDescriptionKey: "pack \(dir): \(why)"]) } + let generator = defineU32("IGNEUM_GENERATOR") ?? 1 + guard generator == 2 || generator == 3 else { throw refuse("program pack generator \(generator) is not a generator version this worker runs (2 or 3)") } + let packClass = generator == 3 ? "v3" : "v2" + if let named = defineStr("IGNEUM_PROGRAM_CLASS"), named != packClass { throw refuse("program pack IGNEUM_PROGRAM_CLASS \"\(named)\" does not match IGNEUM_GENERATOR \(generator)") } + let eraHex = defineStr("IGNEUM_ERA_SEED_HEX") ?? "" + if wantClass != "" && wantClass != packClass { throw refuse("program class mismatch: this pack is class \(packClass), the line names class \(wantClass) (export the pack again)") } + if wantEra != "" && packClass == "v3" && wantEra.lowercased() != eraHex.lowercased() { throw refuse("era seed mismatch: this pack was drawn under era \(eraHex.isEmpty ? "(none)" : String(eraHex.prefix(16))), the line names era \(wantEra.prefix(16)) (export the pack again)") } + if let packSeed = defineStr("IGNEUM_SEED_BYTES_HEX"), packSeed.lowercased() != seedHex.lowercased() { throw refuse("the pack is for epoch \(packSeed.prefix(16)), not \(seedHex.prefix(16))") } + let attempt = defineU32("IGNEUM_PROGRAM_ATTEMPT") ?? 0 + guard let seedw = defineWords("IGNEUM_SEEDW_INIT") else { throw refuse("program.h has no IGNEUM_SEEDW_INIT with 8 words") } + let want = attemptWords(seed, attempt) + if want != seedw { throw refuse("program pack and its seeds disagree: IGNEUM_SEEDW_INIT is not attempt \(attempt) of the epoch seed \(seedHex.prefix(16))") } + let loads = Int(defineU32("IGNEUM_LOADS_PER_HASH") ?? 128) + guard let msl = try? String(contentsOfFile: dir + "/program_bound.metal", encoding: .utf8) else { throw refuse("cannot read program_bound.metal") } + let c = try compileBound(gpu, msl: msl) + let sp = ServeProgram(seedHex: seedHex, packClass: packClass, eraHex: eraHex, loadsPerHash: loads, compiled: c) + store.add(sp) + return (sp, ms(t0, nowNs())) +} + // Builds the cache and dataset for a day seed (hex) unless resident. Returns (dataset, build ms). func serveDataset(_ gpu: GPU, _ store: ServeStore, dayHex: String, day: [UInt8], datasetLog2: Int) -> (ServeDataset, Double) { if let d = store.dataset(dayHex) { return (d, 0) } @@ -3036,19 +3101,19 @@ func runServe(_ opts: Options) -> Never { 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=`. - // 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 = "" + // Counter ASIC 2.0 (5 October 2026): a job or prepare line of a class v3 epoch ends with `class=v3 era=` + // (a class v2 line is the line of before). A class v2 program is generated here from the seed (the Swift + // version 2 generator); a class v3 program is compiled from the pack the prepare line names (servePackProgram), + // so a v3 job on seeds with no prepared v3 pack asks for one (`need`) instead of mining the wrong program. + var wantClass = "", wantEra = "" while let last = f.last, last.hasPrefix("class=") || last.hasPrefix("era=") { - if last.hasPrefix("class=") { wantClass = String(last.dropFirst(6)) } + if last.hasPrefix("class=") { wantClass = String(last.dropFirst(6)) } else { wantEra = String(last.dropFirst(4)) } f.removeLast() } - if wantClass != "" && wantClass != "v2" { + if wantClass != "" && wantClass != "v2" && wantClass != "v3" { 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") } + if f[0] == "prepare" { emit("prepare-failed \(id) \(f.count > 2 ? f[2] : "0") program class \(wantClass) is not one this worker runs (v2 or v3)") } + else { emit("error \(id) program class \(wantClass) is not one this worker runs (v2 or v3)") } continue } if f[0] == "prepare" { @@ -3058,12 +3123,16 @@ func runServe(_ opts: Options) -> Never { emit("prepare-failed \(f[1]) \(f[2]) bad field (epoch_seed 64 hex, day_seed hex)"); continue } let (epochHex, dayHex) = (f[1], f[2]) - let have = (store.program(epochHex) != nil, store.dataset(dayHex) != nil) + let have = (store.program(epochHex, cls: wantClass, era: wantEra) != nil, store.dataset(dayHex) != nil) if have.0 && have.1 { emit("prepared \(epochHex) \(dayHex) 0 program 0 dataset 0 (already resident)"); continue } + let packDir = f.count > 3 ? f[3...].joined(separator: " ") : "" + if wantClass == "v3" && packDir.isEmpty { emit("prepare-failed \(epochHex) \(dayHex) a class v3 program is compiled from a pack: send prepare with a pack directory (igneum-miner --prepare-packs )"); continue } prepareQueue.async { let t0 = nowNs() do { - let (sp, progMs) = try serveProgram(gpu, store, seedHex: epochHex, seed: epochSeed, datasetLog2: datasetLog2) + let (sp, progMs) = wantClass == "v3" + ? try servePackProgram(gpu, store, seedHex: epochHex, seed: epochSeed, dir: packDir, wantClass: wantClass, wantEra: wantEra) + : try serveProgram(gpu, store, seedHex: epochHex, seed: epochSeed, datasetLog2: datasetLog2) let (sd, dsMs) = serveDataset(gpu, store, dayHex: dayHex, day: daySeed, datasetLog2: datasetLog2) // The race: the prepared program against its own dataset, exclusive windows between jobs var raceMs = 0.0 @@ -3073,8 +3142,8 @@ func runServe(_ opts: Options) -> Never { raceMs = ms(r0, nowNs()) } let (np, nd) = store.counts() - emit("prepared \(epochHex) \(dayHex) \(fmt(ms(t0, nowNs()), 1)) program \(fmt(progMs, 1)) dataset \(fmt(dsMs, 1)) race \(fmt(raceMs, 1)) variant \(sp.compiled.variant) loads/hash \(sp.program.loadsPerHash) cache-fill \(fmt(sd.ctx.cacheFillGPUms, 1)) resident \(np) programs \(nd) datasets") - } catch { emit("prepare-failed \(epochHex) \(dayHex) Metal compile failed: \(error)") } + emit("prepared \(epochHex) \(dayHex) \(fmt(ms(t0, nowNs()), 1)) program \(fmt(progMs, 1)) dataset \(fmt(dsMs, 1)) race \(fmt(raceMs, 1)) variant \(sp.compiled.variant) class \(sp.programClass) loads/hash \(sp.loadsPerHash) cache-fill \(fmt(sd.ctx.cacheFillGPUms, 1)) resident \(np) programs \(nd) datasets") + } catch { emit("prepare-failed \(epochHex) \(dayHex) \(wantClass == "v3" ? "" : "Metal compile failed: ")\(error.localizedDescription)") } } continue } @@ -3096,9 +3165,20 @@ func runServe(_ opts: Options) -> Never { let program: ServeProgram let dataset: ServeDataset do { - if store.program(pair.0) == nil || store.dataset(pair.1) == nil { prepareQueue.sync {} } - let (sp, progMs) = try serveProgram(gpu, store, seedHex: pair.0, seed: epochSeed, datasetLog2: datasetLog2) - if progMs > 0 { emit("info program epoch \(pair.0.prefix(16)) loads/hash \(sp.program.loadsPerHash) compiled inline in \(fmt(progMs, 1)) ms (not prepared)") } + if store.program(pair.0, cls: wantClass, era: wantEra) == nil || store.dataset(pair.1) == nil { prepareQueue.sync {} } + let sp: ServeProgram + if wantClass == "v3" { + guard let resident = store.program(pair.0, cls: wantClass, era: wantEra) else { + emit("need \(pair.0) \(pair.1)") + emit("error \(jobId) pack (metal): program class mismatch: no class v3 program for epoch \(pair.0.prefix(16)) is resident (this worker compiles v3 from a pack); send prepare with a pack directory") + continue + } + sp = resident + } else { + let (p, progMs) = try serveProgram(gpu, store, seedHex: pair.0, seed: epochSeed, datasetLog2: datasetLog2) + if progMs > 0 { emit("info program epoch \(pair.0.prefix(16)) loads/hash \(p.loadsPerHash) compiled inline in \(fmt(progMs, 1)) ms (not prepared)") } + sp = p + } let (sd, dsMs) = serveDataset(gpu, store, dayHex: pair.1, day: daySeed, datasetLog2: datasetLog2) if dsMs > 0 { emit("info dataset day \(pair.1) built inline in \(fmt(dsMs, 1)) ms (cache fill \(fmt(sd.ctx.cacheFillGPUms, 1)) ms, build \(fmt(sd.ctx.lastBuildGPUms, 1)) ms GPU; not prepared)") } program = sp; dataset = sd diff --git a/tools/finality-attacks/redteam/override-60x-v3.json b/tools/finality-attacks/redteam/override-60x-v3.json index af6a2ce37..98a1f0f30 100644 --- a/tools/finality-attacks/redteam/override-60x-v3.json +++ b/tools/finality-attacks/redteam/override-60x-v3.json @@ -1 +1 @@ -{"timestamp_deviation_tolerance": 132, "past_median_time_window_size": 27, "difficulty_window_size": 661, "min_difficulty_window_size": 150, "difficulty_rule": "igneum-dual", "coinbase_payload_script_public_key_max_len": 150, "max_coinbase_payload_len": 16384, "max_tx_inputs": 1000, "max_tx_outputs": 1000, "max_signature_script_len": 250000, "max_script_public_key_len": 10000, "mass_per_tx_byte": 1, "mass_per_script_pub_key_byte": 10, "mass_per_sig_op": 1000, "block_mass_limits": {"compute": 500000, "storage": 500000, "transient": 1000000}, "block_lane_limits": {"lanes_per_block": 50, "gas_per_lane": 1000000000}, "storage_mass_parameter": 1000000000000, "deflationary_phase_daa_score": 0, "pre_deflationary_phase_base_subsidy": 50000000000, "skip_proof_of_work": false, "max_block_level": 250, "pruning_proof_m": 1000, "blockrate": {"target_time_per_block": 1000, "ghostdag_k": 18, "past_median_time_sample_rate": 10, "difficulty_sample_rate": 4, "max_block_parents": 10, "mergeset_size_limit": 180, "merge_depth": 60, "finality_depth": 720, "pruning_depth": 13838, "coinbase_maturity": 2}, "pre_crescendo_target_time_per_block": 1000, "crescendo_activation": 0, "genesis_bits": 487587840, "finality": {"checkpoint_interval": 30, "checkpoint_depth": 20, "weight_window": 120, "dust": 5, "presence_window": 1, "aggregators": 8, "equivocation_ban": 120, "min_daa": 120, "aggregator_fallback": 1}, "pow_epoch_blocks": 60, "pow_epoch_lead": 10, "pow_day_ms": 1440000, "finality_v3_activation_daa": 0, "program_class_v3_activation_daa": 18446744073709551615} \ No newline at end of file +{"timestamp_deviation_tolerance": 132, "past_median_time_window_size": 27, "difficulty_window_size": 661, "min_difficulty_window_size": 150, "difficulty_rule": "igneum-dual", "coinbase_payload_script_public_key_max_len": 150, "max_coinbase_payload_len": 16384, "max_tx_inputs": 1000, "max_tx_outputs": 1000, "max_signature_script_len": 250000, "max_script_public_key_len": 10000, "mass_per_tx_byte": 1, "mass_per_script_pub_key_byte": 10, "mass_per_sig_op": 1000, "block_mass_limits": {"compute": 500000, "storage": 500000, "transient": 1000000}, "block_lane_limits": {"lanes_per_block": 50, "gas_per_lane": 1000000000}, "storage_mass_parameter": 1000000000000, "deflationary_phase_daa_score": 0, "pre_deflationary_phase_base_subsidy": 50000000000, "skip_proof_of_work": false, "max_block_level": 250, "pruning_proof_m": 1000, "blockrate": {"target_time_per_block": 1000, "ghostdag_k": 18, "past_median_time_sample_rate": 10, "difficulty_sample_rate": 4, "max_block_parents": 10, "mergeset_size_limit": 180, "merge_depth": 60, "finality_depth": 720, "pruning_depth": 13838, "coinbase_maturity": 2}, "pre_crescendo_target_time_per_block": 1000, "crescendo_activation": 0, "genesis_bits": 487587840, "finality": {"checkpoint_interval": 30, "checkpoint_depth": 20, "weight_window": 120, "dust": 5, "presence_window": 1, "aggregators": 8, "equivocation_ban": 120, "min_daa": 120, "aggregator_fallback": 1}, "pow_epoch_blocks": 60, "pow_epoch_lead": 10, "pow_day_ms": 1440000, "finality_v3_activation_daa": 0, "program_class_v3_activation_daa": 18446744073709551615, "pow_genesis_dataset_log2": 28} \ No newline at end of file