diff --git a/infra/build-server/lib.sh b/infra/build-server/lib.sh index 25928427b..57a407b29 100755 --- a/infra/build-server/lib.sh +++ b/infra/build-server/lib.sh @@ -286,9 +286,16 @@ bs_wt_lock() { local d="$BS_ROOT_REMOTE/_locks/wt-$BS_WT" t0 holder t0=$(date +%s) while :; do - if bs_ssh "mkdir '$d' 2>/dev/null && printf 'pid %s since %sZ: %s\n' '$$' \"\$(date -u +%H:%M:%S)\" '${BS_TOOL:-build} $BS_CRATE_REL' > '$d/holder'"; then BS_WT_LOCKED="$d"; trap 'bs_wt_unlock' EXIT; return 0; fi + if bs_ssh "mkdir '$d' 2>/dev/null && printf 'pid %s host %s since %sZ: %s\n' '$$' '$(hostname -s)' \"\$(date -u +%H:%M:%S)\" '${BS_TOOL:-build} $BS_CRATE_REL' > '$d/holder'"; then BS_WT_LOCKED="$d"; trap 'bs_wt_unlock' EXIT; return 0; fi holder=$(bs_ssh "cat '$d/holder' 2>/dev/null; find '$d' -maxdepth 0 -mmin +180 -print 2>/dev/null | grep -q . && echo STALE" 2>/dev/null || true) case "$holder" in *STALE*) bs_log "worktree lock $d is older than 3 h; taking it over"; bs_ssh "rm -rf '$d'"; continue ;; esac + # the dead-holder class (8 October 2026, 21:11 to 22:11 UK): a build-remote killed by its pid never runs its EXIT trap, so the + # box kept its lock for the 3 h rule and the next run on that worktree waited an hour. The holder line names the Mac and + # the pid; a waiter on the same Mac whose pid is dead takes the lock over at once and says so. + case "$holder" in + "pid "*" host $(hostname -s) since "*) local hp; hp=$(printf '%s' "$holder" | sed -n 's/^pid \([0-9]*\) host .*/\1/p') + if [ -n "$hp" ] && ! kill -0 "$hp" 2>/dev/null; then bs_log "worktree lock $d held by pid $hp of this Mac, which is dead; taking it over"; bs_ssh "rm -rf '$d'"; continue; fi ;; + esac [ $(( $(date +%s) - t0 )) -lt 7200 ] || bs_die "gave up after 2 h waiting for the worktree lock $d (held: $holder)" [ $(( ($(date +%s) - t0) % 60 )) -lt 10 ] && bs_log "waiting for another run on worktree $BS_WT: ${holder:-?}" sleep 10 diff --git a/proving/igneum-prove/elf/manifest.json b/proving/igneum-prove/elf/manifest.json index 4c87821c9..9e02aef7e 100644 --- a/proving/igneum-prove/elf/manifest.json +++ b/proving/igneum-prove/elf/manifest.json @@ -19,5 +19,7 @@ "vk_sha256": "0x8b4da5bff86d963f4210a78e5d800a1cd00ab41b158f6962f4ac009edc249d4c" }, "sp1_circuit_version": "v6.1.0", - "sp1_crate_version": "6.8.1" + "sp1_crate_version": "6.8.1", + "source_commit": "15bb6cdd43bf0e52fca0df0f1dd880e124d03acf", + "guest_input_format": 1 } diff --git a/proving/igneum-prove/host/src/main.rs b/proving/igneum-prove/host/src/main.rs index f34647c11..e1f3d96fa 100644 --- a/proving/igneum-prove/host/src/main.rs +++ b/proving/igneum-prove/host/src/main.rs @@ -74,6 +74,11 @@ fn run() -> Result<()> { println!("RESULT id: {}", pinned.describe()); return Ok(()); } + // A host whose code writes a guest input layout the pinned pair does not read never builds an input for it: refused + // here with the source commit to build from (aggregate, chain and every fixture mode build guest inputs; id and the verify modes do not). + if !mode.starts_with("verify") { + pinned.manifest.check_input_format(igneum_prove_core::shard::GUEST_INPUT_FORMAT)?; + } if mode == "verify" { // proving v0 (spec 7.7): the node's proof pool verifies a submitted shard proof off the consensus path return run_verify(&pinned, &arg("--proof").context("--proof ")?, &arg("--statement").context("--statement 0x")?); diff --git a/proving/igneum-prove/host/src/pinned.rs b/proving/igneum-prove/host/src/pinned.rs index 2843a2b4b..12f798d0b 100644 --- a/proving/igneum-prove/host/src/pinned.rs +++ b/proving/igneum-prove/host/src/pinned.rs @@ -61,6 +61,36 @@ pub struct Manifest { /// from its object, never from here). #[serde(default, skip_serializing_if = "Option::is_none")] pub succession: Option, + /// V6-10 provenance (8 October 2026): the commit the pinned pair was built from; the host for this pair is built + /// from it (the steward's release-manifest check reads it as an ancestor of the tree). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source_commit: Option, + /// The guest input layout the pinned pair reads (`igneum_prove_core::shard::GUEST_INPUT_FORMAT` of its source; absent + /// on a pin from before the field, which is format 1). A host whose code writes another layout refuses to start + /// (22:0x UK, 8 October 2026: a host from master wrote format 3 to the 5 October guests and read "public values are + /// 0 bytes" on a 3060 and a 4060; the kit's prover is built from `source_commit`, never from a tree of another format). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub guest_input_format: Option, +} + +impl Manifest { + /// The input layout the pinned pair reads: the manifest's field, or 1 for a pin from before the field. + pub fn input_format(&self) -> u32 { + self.guest_input_format.unwrap_or(1) + } + /// Refuses a host whose code writes a layout the pinned pair does not read; the line names the source commit to + /// build the prover from instead. + pub fn check_input_format(&self, code_format: u32) -> Result<()> { + let pinned = self.input_format(); + if pinned == code_format { + return Ok(()); + } + anyhow::bail!( + "this host writes guest input format {code_format} and the pinned pair (shard {}) reads format {pinned}: the prover for this pair is built from the manifest's source commit {}, never from this tree (or the pin moves with the code: proving/igneum-prove/pin-guests.sh)", + self.shard.program_id, + self.source_commit.as_deref().unwrap_or("(absent: a pin from before provenance; its source is the commit that last changed elf/igneum-prove-program.elf)") + ) + } } /// The prior pair's block: the same per-program fields as the top level and the time it was pinned. @@ -227,6 +257,36 @@ pub fn claimed_program_id(proof: &SP1ProofWithPublicValues) -> Option { mod tests { use super::*; + /// Known-failed first: a manifest naming another input layout refuses the host with the source commit to build from; + /// a manifest naming none reads as format 1; the code's own format passes. + #[test] + fn a_pinned_pair_of_another_input_format_refuses_this_host_and_names_the_source_to_build() { + let mut m = Manifest::embedded().unwrap(); + m.guest_input_format = Some(igneum_prove_core::shard::GUEST_INPUT_FORMAT + 1); + m.source_commit = Some("abc123".into()); + let e = m.check_input_format(igneum_prove_core::shard::GUEST_INPUT_FORMAT).unwrap_err().to_string(); + assert!(e.contains("built from the manifest's source commit abc123"), "{e}"); + m.guest_input_format = None; + assert_eq!(m.input_format(), 1, "a pin from before the field is format 1"); + if igneum_prove_core::shard::GUEST_INPUT_FORMAT != 1 { + let e = m.check_input_format(igneum_prove_core::shard::GUEST_INPUT_FORMAT).unwrap_err().to_string(); + assert!(e.contains("reads format 1"), "{e}"); + } + m.guest_input_format = Some(igneum_prove_core::shard::GUEST_INPUT_FORMAT); + m.check_input_format(igneum_prove_core::shard::GUEST_INPUT_FORMAT).unwrap(); + // the embedded manifest itself: Pinned::load still answers (id and the verify modes work on a held pin), and the + // input-format check either passes or names the source commit the prover is built from + let embedded = Manifest::embedded().unwrap(); + Pinned::load().unwrap(); + match embedded.check_input_format(igneum_prove_core::shard::GUEST_INPUT_FORMAT) { + Ok(()) => assert_eq!(embedded.input_format(), igneum_prove_core::shard::GUEST_INPUT_FORMAT), + Err(e) => { + assert!(e.to_string().contains("guest input format"), "{e}"); + assert!(embedded.source_commit.is_some(), "a held pin names the source commit its prover is built from"); + } + } + } + #[test] fn manifest_parses_and_names_both_programs() { let m = Manifest::embedded().unwrap();