The one approved Power control step on PC 1 failed with exit 1 (15:45:21Z, no cap applied, no helper registered): cmd.exe strips the first and last quote of a /c line that starts with one and holds more than two (two NVIDIA cards, or a cap plus the registration script); both launchers now wrap the line in one outer pair (platform::cmd_c_args, host.cpp runElevated), unit-tested; Ember 2: the memory clock set through nvidia-smi -lmc/-rmc (direct and through the helper's lmc/rmc verbs), the goal, the electricity price and the hill-climb switch in Settings and /api/tune/goal, the chosen point's memory clock and the measured curve on the card
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1bd7b687c9
commit
fc2ed67df5
9 changed files with 131 additions and 7 deletions
|
|
@ -39,6 +39,9 @@ pub struct CardPref {
|
|||
pub sweep_class: String,
|
||||
#[serde(default)]
|
||||
pub sweep_source: String,
|
||||
/// Ember 2: the memory clock the last tune chose (0 = the driver's default)
|
||||
#[serde(default)]
|
||||
pub sweep_mem_mhz: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
|
|
@ -90,6 +93,15 @@ pub struct Settings {
|
|||
/// unanswered prompt switches it back off with a notice, no retries.
|
||||
#[serde(default)]
|
||||
pub power_control: bool,
|
||||
/// Ember 2 (6 October 2026): the tune's goal ("efficiency" = most MH per watt within 10% of the top rate,
|
||||
/// "balanced" = within 1%, "rate" = the fastest point), the electricity price in pence per kWh for the £/day
|
||||
/// reading on each card, and the hill-climb switch (memory up, core down from the fleet prior; off = the ladders).
|
||||
#[serde(default = "balanced")]
|
||||
pub tune_goal: String,
|
||||
#[serde(default)]
|
||||
pub power_price_pence: f64,
|
||||
#[serde(default)]
|
||||
pub tune_climb: bool,
|
||||
/// When this install first ran (unix s), for the "first hour after install" sweep.
|
||||
#[serde(default)]
|
||||
pub installed_at: u64,
|
||||
|
|
@ -113,13 +125,16 @@ pub struct Settings {
|
|||
fn one() -> u32 {
|
||||
1
|
||||
}
|
||||
fn balanced() -> String {
|
||||
"balanced".into()
|
||||
}
|
||||
fn yes() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Settings {
|
||||
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true, remote_jobs: true, prove: false, sweep: true, power_control: false, installed_at: 0, dev_fee: true, fee_total: 0, proof_verify_trust: false, prove_default_applied: false }
|
||||
Settings { setup_done: false, address: String::new(), address_source: String::new(), key_saved: false, identities: 1, cards: HashMap::new(), display_name: String::new(), vote: true, paused: false, accepted_total: 0, auto_update: true, remote_jobs: true, prove: false, sweep: true, power_control: false, tune_goal: "balanced".into(), power_price_pence: 0.0, tune_climb: false, installed_at: 0, dev_fee: true, fee_total: 0, proof_verify_trust: false, prove_default_applied: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ pub enum Cmd {
|
|||
TuneSet(u64, Result<String, String>),
|
||||
/// POST /api/tune-progress from a measurement engine beside this app: the card's live tune state
|
||||
TuneProgress(Value),
|
||||
/// Ember 2: Settings > goal, electricity price, the hill-climb switch
|
||||
TuneGoal(Option<String>, Option<f64>, Option<bool>),
|
||||
/// restart the node with the verifier decided again (src/verifier.rs): the trust setting changed, or the
|
||||
/// prover found a host that was not there when the node started
|
||||
RestartNode(String),
|
||||
|
|
@ -121,7 +123,7 @@ impl Shared {
|
|||
st.mining.accepted_total = settings.accepted_total;
|
||||
st.mining.fee_total = settings.fee_total;
|
||||
st.address = address_state(&settings, &wallet_path);
|
||||
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update, remote_jobs: settings.remote_jobs, prove: settings.prove, sweep: settings.sweep, power_control: settings.power_control, power_note: String::new(), tuning_off: false, tuning_note: String::new(), dev_fee: settings.dev_fee, proof_verify_trust: settings.proof_verify_trust };
|
||||
st.settings = crate::state::SettingsState { identities: settings.identities, vote: settings.vote, start_at_login: crate::platform::start_at_login_is_on(), auto_update: settings.auto_update, remote_jobs: settings.remote_jobs, prove: settings.prove, sweep: settings.sweep, power_control: settings.power_control, power_note: String::new(), tuning_off: false, tuning_note: String::new(), tune_goal: settings.tune_goal.clone(), power_price_pence: settings.power_price_pence, tune_climb: settings.tune_climb, dev_fee: settings.dev_fee, proof_verify_trust: settings.proof_verify_trust };
|
||||
st.dev_fee = crate::state::DevFeeState { on: settings.dev_fee, percent: if settings.dev_fee { 1 } else { 0 }, address: String::new(), line: String::new() };
|
||||
st.live_page = packaged.live_page.clone();
|
||||
st.finality.message = "waiting for the miner".into();
|
||||
|
|
@ -1145,6 +1147,28 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
Cmd::TuneProgress(v) => self.tune_progress(&v),
|
||||
Cmd::TuneGoal(goal, price, climb) => {
|
||||
{
|
||||
let mut s = self.shared.settings.lock().unwrap();
|
||||
if let Some(g) = &goal {
|
||||
s.tune_goal = g.clone();
|
||||
}
|
||||
if let Some(p) = price {
|
||||
s.power_price_pence = p;
|
||||
}
|
||||
if let Some(c) = climb {
|
||||
s.tune_climb = c;
|
||||
}
|
||||
}
|
||||
self.shared.save_settings();
|
||||
let s = self.shared.settings.lock().unwrap().clone();
|
||||
let mut st = self.st();
|
||||
st.settings.tune_goal = s.tune_goal.clone();
|
||||
st.settings.power_price_pence = s.power_price_pence;
|
||||
st.settings.tune_climb = s.tune_climb;
|
||||
drop(st);
|
||||
self.shared.event("info", &format!("tune goal: {}{}{}", s.tune_goal, if s.power_price_pence > 0.0 { format!(", electricity {:.1} p/kWh", s.power_price_pence) } else { String::new() }, if s.tune_climb { ", hill-climb on" } else { "" }));
|
||||
}
|
||||
Cmd::TuneSet(seq, r) => match r {
|
||||
Ok(text) => {
|
||||
self.tune_acked = Some(seq);
|
||||
|
|
@ -2361,11 +2385,19 @@ impl Engine {
|
|||
let key = crate::ember::prior_key(&c.name, &probe.driver, &c.program_class);
|
||||
let prior = crate::ember::prior_of(tuning.as_ref(), &key, ember.min_samples);
|
||||
let full_due = self.tune_full_due.remove(&idx);
|
||||
let (goal, climb_on) = {
|
||||
let s = self.shared.settings.lock().unwrap();
|
||||
(crate::ember::Goal::parse(&s.tune_goal), s.tune_climb)
|
||||
};
|
||||
let plan = if let Some(why) = control.as_ref() {
|
||||
if let Some(cc) = self.st().mining.cards.get_mut(idx) {
|
||||
cc.sweep_note = why.clone();
|
||||
}
|
||||
crate::ember::Plan::baseline(&limits, before, ember.tolerance_pct)
|
||||
} else if climb_on || prior.as_ref().map(|p| p.point.mem_mhz > 0).unwrap_or(false) {
|
||||
// Ember 2: the hill-climb from the fleet prior (or the card's point) toward the goal
|
||||
let start = prior.as_ref().map(|p| p.point).unwrap_or(before);
|
||||
crate::ember::Plan::climb(&limits, start, goal, ember.tolerance_pct)
|
||||
} else if let (Some(p), false, 0) = (prior.as_ref(), full_due, c.sweep_pct) {
|
||||
crate::ember::Plan::confirm(&limits, p.point, before, ember.tolerance_pct)
|
||||
} else {
|
||||
|
|
@ -2477,6 +2509,7 @@ impl Engine {
|
|||
let Some(c) = card else { return };
|
||||
let w = step.watts.round() as u64;
|
||||
let clock = step.point.clock_mhz;
|
||||
let mem = step.point.mem_mhz;
|
||||
let shared = self.shared.clone();
|
||||
self.tune_acked = None;
|
||||
match c.vendor.as_str() {
|
||||
|
|
@ -2500,6 +2533,15 @@ impl Engine {
|
|||
ok &= out.contains("All done") || out.to_ascii_lowercase().contains("clocks set") || out.to_ascii_lowercase().contains("reset");
|
||||
text.push(' ');
|
||||
text.push_str(out.trim());
|
||||
// Ember 2: the memory clock, locked to one value (`-lmc m,m`) or reset (`-rmc`)
|
||||
let out = if mem > 0 {
|
||||
crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "-lmc", &format!("{mem},{mem}")]), None, Duration::from_secs(20)).unwrap_or_else(|| "nvidia-smi did not answer".into())
|
||||
} else {
|
||||
crate::detect::run_timeout(std::process::Command::new(&smi).args(["-i", &device, "-rmc"]), None, Duration::from_secs(20)).unwrap_or_else(|| "nvidia-smi did not answer".into())
|
||||
};
|
||||
ok &= out.contains("All done") || out.to_ascii_lowercase().contains("clocks set") || out.to_ascii_lowercase().contains("reset") || out.to_ascii_lowercase().contains("not supported");
|
||||
text.push(' ');
|
||||
text.push_str(out.trim());
|
||||
shared.send(Cmd::TuneSet(seq, if ok { Ok(text) } else { Err(text) }));
|
||||
});
|
||||
}
|
||||
|
|
@ -2509,7 +2551,7 @@ impl Engine {
|
|||
return;
|
||||
}
|
||||
let dev = device.to_string();
|
||||
let cmd = format!("{seq}0 dev {dev}\n{seq}1 pl {w}\n{seq}2 {}\n", if clock > 0 { format!("lgc {clock}") } else { "rgc".to_string() });
|
||||
let cmd = format!("{seq}0 dev {dev}\n{seq}1 pl {w}\n{seq}2 {}\n{seq}3 {}\n", if clock > 0 { format!("lgc {clock}") } else { "rgc".to_string() }, if mem > 0 { format!("lmc {mem}") } else { "rmc".to_string() });
|
||||
let _ = std::fs::write(self.sweep_dir().join("cmd.txt"), cmd);
|
||||
// the helper polls twice a second and nvidia-smi answers within a second or two
|
||||
std::thread::spawn(move || {
|
||||
|
|
@ -2551,7 +2593,7 @@ impl Engine {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
self.shared.log(&format!("tune: {} MHz, {}% ({w} W) requested on device {device} (request {seq})", if clock > 0 { clock.to_string() } else { "unlocked".into() }, step.point.power_pct));
|
||||
self.shared.log(&format!("tune: {} MHz, {}% ({w} W), memory {} requested on device {device} (request {seq})", if clock > 0 { clock.to_string() } else { "unlocked".into() }, step.point.power_pct, if mem > 0 { format!("{mem} MHz") } else { "default".into() }));
|
||||
}
|
||||
|
||||
/// A measurement engine's progress for one card (POST /api/tune-progress, forwarded by the job playbook from the
|
||||
|
|
@ -2704,8 +2746,10 @@ impl Engine {
|
|||
c.tune_steps = 0;
|
||||
c.tune_eta_s = 0;
|
||||
c.tune_clock_mhz = row.point.clock_mhz;
|
||||
c.tune_mem_mhz = row.point.mem_mhz;
|
||||
c.tune_source = kind.name().into();
|
||||
c.tune_line = crate::ember::tuned_line(row.mhs, row.watts, row.eff);
|
||||
c.tune_curve = run.rows.iter().map(|r| r.json()).collect();
|
||||
let control = c.tune_control;
|
||||
if kind == crate::ember::PlanKind::Baseline {
|
||||
c.sweep_note = if control { String::new() } else { c.sweep_note.clone() };
|
||||
|
|
@ -2714,6 +2758,7 @@ impl Engine {
|
|||
} else {
|
||||
c.power_pct = row.point.power_pct;
|
||||
c.clock_cap_mhz = row.point.clock_mhz;
|
||||
c.mem_cap_mhz = row.point.mem_mhz;
|
||||
if c.vendor == "nvidia" {
|
||||
c.power_limit_w = row.limit;
|
||||
c.power_applied = true;
|
||||
|
|
@ -2728,6 +2773,7 @@ impl Engine {
|
|||
e.sweep_watts = row.watts;
|
||||
e.sweep_mhs = row.mhs;
|
||||
e.sweep_clock_mhz = row.point.clock_mhz;
|
||||
e.sweep_mem_mhz = row.point.mem_mhz;
|
||||
e.sweep_driver = c.driver.clone();
|
||||
e.sweep_class = c.program_class.clone();
|
||||
e.sweep_source = kind.name().into();
|
||||
|
|
@ -2738,7 +2784,7 @@ impl Engine {
|
|||
};
|
||||
let _ = key;
|
||||
self.shared.save_settings();
|
||||
self.sweep_say(&format!("TUNE chosen card={} clock={} cap={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} plan={}{}", run.label, row.point.clock_mhz, row.point.power_pct, row.limit, row.watts, row.mhs, row.eff, kind.name(), if pinned { " pinned=1" } else { "" }));
|
||||
self.sweep_say(&format!("TUNE chosen card={} clock={} cap={} mem={} limit={:.0} watts={:.1} mhs={:.2} eff={:.4} plan={}{}", run.label, row.point.clock_mhz, row.point.power_pct, row.point.mem_mhz, row.limit, row.watts, row.mhs, row.eff, kind.name(), if pinned { " pinned=1" } else { "" }));
|
||||
let before = run.rows.first().filter(|_| kind == crate::ember::PlanKind::Full).cloned();
|
||||
let record = crate::ember::record_json(crate::platform::unix_now_f(), &crate::config::fingerprint8(&self.shared.runtime.machine_id), VERSION, crate::manifest::platform_name(), &name, &vendor, &driver, &class, kind, &run.rows, Some(&row), before.as_ref());
|
||||
self.sweep_say(&format!("TUNE {record}"));
|
||||
|
|
|
|||
|
|
@ -163,6 +163,8 @@ pub fn apply_pref(c: &mut CardState, p: &CardPref) {
|
|||
c.sweep_at = p.sweep_at as f64;
|
||||
// Ember Tune (src/ember.rs): the clock cap the last tune chose, its plan, and the row's Tuned line
|
||||
c.tune_clock_mhz = p.sweep_clock_mhz;
|
||||
c.tune_mem_mhz = p.sweep_mem_mhz;
|
||||
c.mem_cap_mhz = if p.pinned || p.sweep_source == "baseline" { 0 } else { p.sweep_mem_mhz };
|
||||
c.clock_cap_mhz = if p.pinned || p.sweep_source == "baseline" { 0 } else { p.sweep_clock_mhz };
|
||||
c.tune_source = p.sweep_source.clone();
|
||||
if p.sweep_mhs > 0.0 && p.sweep_watts > 0.0 {
|
||||
|
|
|
|||
|
|
@ -430,11 +430,21 @@ pub fn sync_clock() -> Result<String, String> {
|
|||
|
||||
/// Runs a command line with administrator rights (one prompt): the NVIDIA power cap needs it on Windows.
|
||||
/// Blocking; call from a thread.
|
||||
/// The cmd.exe argument for one elevated line. cmd's documented rule: when the text after /c starts with a quote
|
||||
/// and holds more than two quotes (two cards, or a cap plus a script), it strips the FIRST and LAST quote and runs
|
||||
/// the broken remainder (PC 1, 6 October 2026, 15:45Z: the one approved Power control step ran
|
||||
/// `"...\nvidia-smi.exe" -i 0 -pl 460 & "...\nvidia-smi.exe" -i 1 -pl 160 & "...\powershell.exe" ... -File "...ps1"`,
|
||||
/// exit 1, no cap applied, no task registered; a single-card PC, two quotes, was fine, which is why PC 2 never
|
||||
/// showed it). Wrapping the whole line in one outer pair makes cmd strip exactly those.
|
||||
pub fn cmd_c_args(cmdline: &str) -> String {
|
||||
format!("/c \"{cmdline}\"")
|
||||
}
|
||||
|
||||
pub fn run_elevated(cmdline: &str) -> Result<(), String> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let cmd = tool("cmd").display().to_string();
|
||||
let mut c = elevated_command(&cmd, &format!("/c {cmdline}"));
|
||||
let mut c = elevated_command(&cmd, &cmd_c_args(cmdline));
|
||||
let out = c.output().map_err(|e| e.to_string())?;
|
||||
if out.status.success() {
|
||||
Ok(())
|
||||
|
|
@ -511,6 +521,20 @@ pub fn quiet(cmd: &mut Command) -> &mut Command {
|
|||
cmd
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cmd_tests {
|
||||
#[test]
|
||||
fn an_elevated_line_is_wrapped_so_cmd_keeps_every_inner_quote() {
|
||||
let line = r#""C:\WINDOWS\System32\nvidia-smi.exe" -i 0 -pl 460 & "C:\WINDOWS\System32\nvidia-smi.exe" -i 1 -pl 160 & "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -File "C:\x\register-power-task.ps1""#;
|
||||
let a = super::cmd_c_args(line);
|
||||
assert!(a.starts_with("/c \"\"C:\\WINDOWS"), "{a}");
|
||||
assert!(a.ends_with("register-power-task.ps1\"\""), "{a}");
|
||||
// the inner line is intact between the outer pair
|
||||
assert_eq!(&a[4..a.len() - 1], line);
|
||||
assert_eq!(super::cmd_c_args("echo hi"), "/c \"echo hi\"");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod lock_tests {
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ pub enum HelperCmd {
|
|||
PowerLimit(u64),
|
||||
ClockCap(u64),
|
||||
ClockReset,
|
||||
/// Ember 2: the memory clock locked to one value (`-lmc m,m`), or reset (`-rmc`)
|
||||
MemClock(u64),
|
||||
MemReset,
|
||||
Quit,
|
||||
Remove,
|
||||
}
|
||||
|
|
@ -65,6 +68,8 @@ pub fn parse_line(line: &str) -> Option<(u64, HelperCmd)> {
|
|||
[_, "pl", w] if digits(w) => Some((seq, HelperCmd::PowerLimit(w.parse().ok()?))),
|
||||
[_, "lgc", m] if digits(m) => Some((seq, HelperCmd::ClockCap(m.parse().ok()?))),
|
||||
[_, "rgc"] => Some((seq, HelperCmd::ClockReset)),
|
||||
[_, "lmc", m] if digits(m) => Some((seq, HelperCmd::MemClock(m.parse().ok()?))),
|
||||
[_, "rmc"] => Some((seq, HelperCmd::MemReset)),
|
||||
[_, "dev", d] if digits(d) => Some((seq, HelperCmd::Dev(d.to_string()))),
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -76,6 +81,8 @@ pub fn smi_args(dev: &str, c: &HelperCmd) -> Option<Vec<String>> {
|
|||
HelperCmd::PowerLimit(w) => Some(vec!["-i".into(), dev.into(), "-pl".into(), w.to_string()]),
|
||||
HelperCmd::ClockCap(m) => Some(vec!["-i".into(), dev.into(), "-lgc".into(), format!("0,{m}")]),
|
||||
HelperCmd::ClockReset => Some(vec!["-i".into(), dev.into(), "-rgc".into()]),
|
||||
HelperCmd::MemClock(m) => Some(vec!["-i".into(), dev.into(), "-lmc".into(), format!("{m},{m}")]),
|
||||
HelperCmd::MemReset => Some(vec!["-i".into(), dev.into(), "-rmc".into()]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -229,6 +236,10 @@ mod tests {
|
|||
assert_eq!(parse_line("7 pl 460"), Some((7, HelperCmd::PowerLimit(460))));
|
||||
assert_eq!(parse_line("8 lgc 2472"), Some((8, HelperCmd::ClockCap(2472))));
|
||||
assert_eq!(parse_line("9 rgc"), Some((9, HelperCmd::ClockReset)));
|
||||
assert_eq!(parse_line("10 lmc 14001"), Some((10, HelperCmd::MemClock(14001))));
|
||||
assert_eq!(parse_line("11 rmc"), Some((11, HelperCmd::MemReset)));
|
||||
assert_eq!(smi_args("0", &HelperCmd::MemClock(14001)).unwrap(), vec!["-i", "0", "-lmc", "14001,14001"]);
|
||||
assert_eq!(smi_args("0", &HelperCmd::MemReset).unwrap(), vec!["-i", "0", "-rmc"]);
|
||||
assert_eq!(parse_line("3 dev 1"), Some((3, HelperCmd::Dev("1".into()))));
|
||||
assert_eq!(parse_line("5 403"), Some((5, HelperCmd::PowerLimit(403))), "the 0.3.9 form");
|
||||
assert_eq!(parse_line("quit"), Some((0, HelperCmd::Quit)));
|
||||
|
|
|
|||
|
|
@ -251,6 +251,14 @@ fn api_post(shared: &Arc<Shared>, path: &str, body: Value) -> Result<Value, Stri
|
|||
shared.send(Cmd::ApplyCards(choices));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
"/api/tune/goal" => {
|
||||
// Ember 2: the goal, the electricity price (pence per kWh) and the hill-climb switch
|
||||
let goal = body.get("goal").and_then(|v| v.as_str()).map(|g| crate::ember::Goal::parse(g).name().to_string());
|
||||
let price = body.get("price_pence").and_then(|v| v.as_f64()).filter(|p| (0.0..=500.0).contains(p));
|
||||
let climb = body.get("climb").and_then(|v| v.as_bool());
|
||||
shared.send(Cmd::TuneGoal(goal, price, climb));
|
||||
Ok(json!({ "ok": true }))
|
||||
}
|
||||
"/api/settings" => {
|
||||
let identities = body.get("identities").and_then(|v| v.as_u64()).map(|v| v.clamp(1, 64) as u32);
|
||||
let vote = body.get("vote").and_then(|v| v.as_bool());
|
||||
|
|
|
|||
|
|
@ -132,6 +132,9 @@ pub struct CardState {
|
|||
pub tune_steps: u32,
|
||||
pub tune_eta_s: i64,
|
||||
pub tune_plan: String,
|
||||
// Ember 2: the memory clock the last tune chose and the measured curve (every row of the last plan)
|
||||
pub tune_mem_mhz: u32,
|
||||
pub tune_curve: Vec<serde_json::Value>,
|
||||
// the kernel variant race (docs/design/miner-tuning.md): what the worker's last race chose
|
||||
pub variant: String,
|
||||
pub race_mhs: f64,
|
||||
|
|
@ -290,6 +293,10 @@ pub struct SettingsState {
|
|||
/// Ember Tune is paused fleet-wide by the signed manifest's kill switch (tuning.ember.enabled = false)
|
||||
pub tuning_off: bool,
|
||||
pub tuning_note: String,
|
||||
/// Ember 2: the goal (efficiency | balanced | rate), the electricity price in pence per kWh, the hill-climb switch
|
||||
pub tune_goal: String,
|
||||
pub power_price_pence: f64,
|
||||
pub tune_climb: bool,
|
||||
/// the miner software's dev fee switch (settings; `--dev-fee 0` when off)
|
||||
pub dev_fee: bool,
|
||||
/// devnet only: the node trusts proof records without a verifier (`IGNEUM_PROOF_VERIFY=trust`)
|
||||
|
|
|
|||
|
|
@ -375,6 +375,12 @@ while ($true) {
|
|||
} elseif ($op -eq 'rgc') {
|
||||
$out = (& $Smi -i $Device -rgc 2>&1 | Out-String).Trim()
|
||||
"$(Get-Date -Format o) $($p[0]) -rgc : $out" | Out-File -FilePath $log -Append -Encoding utf8
|
||||
} elseif ($op -eq 'lmc' -and $v -match '^\d+$') {
|
||||
$out = (& $Smi -i $Device -lmc "$v,$v" 2>&1 | Out-String).Trim()
|
||||
"$(Get-Date -Format o) $($p[0]) -lmc $v,$v : $out" | Out-File -FilePath $log -Append -Encoding utf8
|
||||
} elseif ($op -eq 'rmc') {
|
||||
$out = (& $Smi -i $Device -rmc 2>&1 | Out-String).Trim()
|
||||
"$(Get-Date -Format o) $($p[0]) -rmc : $out" | Out-File -FilePath $log -Append -Encoding utf8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -411,6 +417,8 @@ while true; do
|
|||
pl) case "$v" in ''|*[!0-9]*) ;; *) echo "$(date -u +%FT%TZ) $1 -pl $v : $("$smi" -i "$dev" -pl "$v" 2>&1)" >> "$dir/helper.log";; esac ;;
|
||||
lgc) case "$v" in ''|*[!0-9]*) ;; *) echo "$(date -u +%FT%TZ) $1 -lgc 0,$v : $("$smi" -i "$dev" -lgc "0,$v" 2>&1)" >> "$dir/helper.log";; esac ;;
|
||||
rgc) echo "$(date -u +%FT%TZ) $1 -rgc : $("$smi" -i "$dev" -rgc 2>&1)" >> "$dir/helper.log" ;;
|
||||
lmc) case "$v" in ''|*[!0-9]*) ;; *) echo "$(date -u +%FT%TZ) $1 -lmc $v,$v : $("$smi" -i "$dev" -lmc "$v,$v" 2>&1)" >> "$dir/helper.log";; esac ;;
|
||||
rmc) echo "$(date -u +%FT%TZ) $1 -rmc : $("$smi" -i "$dev" -rmc 2>&1)" >> "$dir/helper.log" ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
|
@ -614,6 +622,7 @@ mod tests {
|
|||
for s in [helper_script_windows(), helper_script_unix()] {
|
||||
assert!(s.contains("cmd.txt") && s.contains("quit") && s.contains("-pl") && s.contains("20 min"));
|
||||
assert!(s.contains("-lgc") && s.contains("-rgc"), "the clock cap and its reset");
|
||||
assert!(s.contains("-lmc") && s.contains("-rmc"), "Ember 2: the memory clock and its reset");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ static void applyState(const std::string& j) {
|
|||
// Runs cmd /c <line> as administrator, waits, and answers on the engine's stdin.
|
||||
static void runElevated(std::wstring line) {
|
||||
std::thread([line] {
|
||||
std::wstring params = L"/c " + line;
|
||||
// cmd strips the first and last quote of a line that starts with one and holds more than two (two cards, or a
|
||||
// cap plus a script): one outer pair around the whole line is what it may strip (PC 1, 6 October 2026)
|
||||
std::wstring params = L"/c \"" + line + L"\"";
|
||||
wchar_t sysdir[MAX_PATH];
|
||||
GetSystemDirectoryW(sysdir, MAX_PATH);
|
||||
std::wstring cmdExe = std::wstring(sysdir) + L"\\cmd.exe"; // the absolute path, never a bare name (R4.3.3)
|
||||
|
|
|
|||
Loading…
Reference in a new issue