Skip to content

Governance Canister Build

sons_governance is the per-campaign governance canister. It is not a fixed canister listed in dfx.json: the launcher backend creates one instance for each LGE campaign at runtime and installs the governance module into it.

  • Source: src/sons_governance/
  • Candid interface: src/sons_governance/sons_governance.did
  • Client declarations: src/declarations/sons_governance/
  • Embedded module: src/backend/governance_wasm/sons_governance.wasm.gz, compiled into the backend with include_bytes! (src/backend/src/lib.rs)

Because the backend carries the governance module inside its own module, the governance module has to be built before the backend, and the backend’s hash covers the governance module too.

./Deploy/docker-build.sh runs the Dockerfile, which builds the governance module first and then the backend that embeds it:

STEP 1: cargo build --release --locked -p sons_governance

ic-wasm shrink

ic-wasm metadata candid:service from sons_governance.did

gzip -9 -n into src/backend/governance_wasm/sons_governance.wasm.gz

STEP 2: build ohshii_launcher_backend, dao_storage, pool_manager, ohshii_governance

optimize every module, compress each with gzip -9 -n inside the container

export to Deploy/wasm/ and record hashes in Deploy/WASM_HASHES.txt

The relevant outputs in Deploy/wasm/:

FileWhat it is
sons_governance_docker.wasmthe raw governance module
sons_governance_docker.wasm.gzthe compressed module a SONS upgrade proposal carries
sons_governance_embedded_in_backend.wasm.gzthe exact blob compiled into the backend

The last two are the same bytes: the Dockerfile compares them during the build. Deploy/WASM_HASHES.txt lists the embedded blob as embedded_backend_gz.

The backend installs the governance module in chunks (src/backend/src/sons_install.rs): it uploads the embedded .wasm.gz in pieces of at most 1 MiB into the new canister’s own chunk store, calls install_chunked_code, and clears the store afterwards. The platform completes the install only if the chunks reassemble to the declared hash. The chunks are slices of the embedded blob, so:

  • a governance canister created by the backend reports the embedded_backend_gz hash as its module hash;
  • a governance canister later upgraded by one of its own proposals reports that proposal’s WASM Hash instead.

The embedded .wasm.gz is a build output committed to the repository, so that builds other than the Docker build can compile the backend. Next to it sits a sidecar, sons_governance.wasm.gz.source, written by the build that produced the embed. It records:

  • which build produced it (local or docker);
  • the SHA-256 of the embed and of the module inside it;
  • the build recipe (the same as Dockerfile STEP 1);
  • the SHA-256 of every input that can change the module: the files of every local crate sons_governance depends on, files they include, the root Cargo.toml, Cargo.lock, rust-toolchain.toml, and the build recipes.

scripts/check_sons_embed_fresh.py recomputes all of it and reports whether the committed embed still matches the current source. Anyone can run it at a commit with Python 3:

Terminal window
python3 scripts/check_sons_embed_fresh.py

Exit 0 means the embed matches its recorded inputs; exit 1 (STALE) means an input changed after the embed was built; exit 2 means the sidecar is missing or unreadable.

This check is a consistency aid, not the proof a voter relies on. The release artefact is the Docker build, which rebuilds the embed from source, so a matching backend hash proves the embedded governance module is exactly what the declared commit’s src/sons_governance/ produces. See the Voter Verification Guide for the full procedure.

After ./Deploy/docker-build.sh (or the minimal docker buildx path in the Voter Verification Guide):

Terminal window
shasum -a 256 Deploy/wasm/sons_governance_docker.wasm
shasum -a 256 Deploy/wasm/sons_governance_docker.wasm.gz
shasum -a 256 Deploy/wasm/sons_governance_embedded_in_backend.wasm.gz

./Deploy/verify-proposal.sh ohshii_launcher_backend also prints the embedded blob’s hash and checks that those bytes are inside the backend module.

To read the module hash of a live campaign governance canister (public, no identity needed):

Terminal window
dfx canister info <governance_canister_id> --network ic --identity anonymous

sons_governance is not in dfx.json, so dfx generate does not cover it. Its JavaScript and TypeScript bindings are generated from the .did with didc:

Terminal window
didc bind src/sons_governance/sons_governance.did -t js > src/declarations/sons_governance/sons_governance.did.js
didc bind src/sons_governance/sons_governance.did -t ts > src/declarations/sons_governance/sons_governance.did.d.ts

An integrator can generate bindings for any language from src/sons_governance/sons_governance.did with the standard Candid tooling.