运行与维护 Blindband
它由什么构成、如何跑一轮,以及构建过程中出过什么问题。
跑一轮
五条命令,按顺序执行。只要上一条留下了不一致的状态,下一条就会拒绝继续。
- 01
npm run deployRegisters the component and re-scopes both sealed maps to every contract id this tenant has ever held. Idempotent — a second run costs nothing.
- 02
npm run submit -- data/records.jsonOne execution carries the whole file. Rejected rows come back with an index and a reason; accepted rows come back as receipts.
- 03
npm run round -- 2026-q1Aggregates inside the enclave, applies the four gates, and writes the response to data/round.json byte for byte.
- 04
npm run anchorRecomputes the digest locally first and refuses to write anything if it does not match. Then posts it to Solana devnet as a memo.
- 05
npm run verifyOffline digest, on-chain anchor, and three receipt probes against the contract — one of them a forgery, so a verifier that only says yes is caught.
仓库结构
三个部分,这样切分是为了让核心逻辑无需节点、飞地或额度即可测试。
- contract/src/model.rs
- Wire types and the ruleset constants. No host calls.
- contract/src/stats.rs
- Percentile maths. Pure, unit-tested.
- contract/src/policy.rs
- The four gates and the aggregation. Pure, unit-tested.
- contract/src/ledger.rs
- The only module that touches the host. wasm32 only.
- agent/src/lib/
- Session, invocation, digest and Solana helpers, each with the reasoning for its approach written at the top.
- agent/state.json
- The only state outside git: tenant DID, contract ids, map names. Reproducible with one deploy.
- web/
- This site. Reads the round file off disk at build time so the bytes in your browser are the enclave's bytes.
缺陷与平台记录
下面每一条都实打实地耗掉了时间。写下来,是为了让下一个人把时间花在别处。
无论页面语言为何,这部分一律保留英文,以便原样转交给 Terminal 3——翻译过的错误信息在他们的日志里检索不到任何东西。
- BB-01平台
The flat invoke() path rejects a sandbox-claimed API key
- 现象
- `invalid api key: malformed api key token`, returned for every request, regardless of body or headers.
- 成因
- `invoke()` expects an opaque `t3n_key_…` token issued by organisation agent provisioning. A key claimed from the community sandbox page is a hex secp256k1 key intended for the SIWE session flow. The two are not interchangeable, and the error names neither.
- 修复
- Use the authenticated session instead: `tenant.contracts.execute(tail, { version, functionName, input })`. It costs one handshake per process, which is irrelevant for a round that runs quarterly.
- 代价
- Roughly half a day, most of it spent assuming the request body was wrong.
- BB-02平台
The sandbox trust manifest does not parse, so attestation cannot be verified
- 现象
- `Trust manifest at https://cn-api.sg.testnet.t3n.terminal3.io/api/trust-manifest is malformed.` The SDK then refuses to proceed unless `T3N_ALLOW_UNVERIFIED_NODE=true` is set.
- 成因
- The manifest served by the sandbox control node is not in the shape the SDK expects. The SDK's behaviour is correct — it declines to trust a node it cannot attest.
- 修复
- None available from the client side. Every run on the sandbox therefore carries an `unsafe_trust_server` warning, which the agent prints rather than suppresses.
- 代价
- Not blocking, but it undercuts the demo: the one property a TEE platform sells is the property that cannot currently be checked on its own sandbox.
- BB-03平台
A KV map's ACL names contract ids, and a version bump mints a new one
- 现象
- After registering v0.2.0, every read failed with `kv_store.get on 'z:…:bb-rounds' read denied: access denied: TenantContract(did:t3n:…/871) cannot read map` — and the log ring came back empty, because the contract dies during instantiation before it can log anything.
- 成因
- The maps were created with `readers: { only: [870] }`, the id of the previous build. Registering a new version produces a new contract id, and the old ACL does not follow it.
- 修复
- Track every id the tenant has registered in `state.json` and re-scope both maps to the union with `tenant.maps.update()` on each deploy. The method exists in the SDK but is not mentioned in the KV maps guide.
- 代价
- The longest single block of the build. The empty log ring is what made it expensive — the failure gives you nothing to search for.
- BB-04平台
A map created without `readers` succeeds, then denies every read
- 现象
- `create` returns cleanly. Every subsequent `kv_store.get` fails with AccessDenied.
- 成因
- The KV governor defaults to deny. An omitted `readers` set is not an open map, it is a closed one.
- 修复
- Always pass `readers` and `writers` explicitly at creation. Worth a line in the docs, since the failure surfaces far from the call that caused it.
- 代价
- An hour, and it would have been more without BB-03 already teaching us to suspect the ACL.
- BB-05平台
An execution locks half the sandbox allocation regardless of what it spends
- 现象
- A sandbox allocation is 20,000,000,000 base units. Each contract execution locks 10,000,000,000 while it runs, though a real round settles at roughly 100–180,000,000.
- 成因
- The lock is a worst-case reservation, not a charge. It is released on completion.
- 修复
- Batch aggressively. Submitting 117 rows one call at a time would need 117 executions; `submit-batch` does it in one for 5.9B. The constraint shaped the contract's interface, which is worth saying out loud in the docs.
- 代价
- No lost time, but it silently rules out the obvious per-row API design.
- BB-06我们自己
The verifier could not read the anchor it had just written
- 现象
- `Memo on 5uczxVJU… is not a Blindband anchor.` — reported against a transaction that was, in fact, a Blindband anchor.
- 成因
- SPL Memo v2 echoes the payload into the program log as a JSON *string literal*: `Memo (len 157): "{\"p\":\"blindband\"…}"`. Slicing between the outer quotes yields text with the escapes still in it, which fails to parse.
- 修复
- Read the decoded instruction data from `getParsedTransaction` instead of the log echo, and unescape properly in the fallback path. The instruction data is the committed bytes; the log is a convenience.
- 代价
- An hour, and a genuinely alarming few minutes in which the tool appeared to be reporting tampering.
- BB-07我们自己
A byte-order mark in state.json silently re-registered the contract
- 现象
- `contract version invalid: version 0.2.0 is not higher than current version 0.2.0`.
- 成因
- `readState()` caught the JSON parse failure and returned `{}`, so the deploy believed nothing had been registered yet. The parse failed because the file had been rewritten from PowerShell, which adds a UTF-8 BOM by default.
- 修复
- Write the file BOM-free. The deeper fix is that a state file which fails to parse should stop the deploy rather than being treated as an empty state.
- 代价
- Half an hour of doubting the platform for something local.
- BB-08我们自己
A default build target in .cargo/config.toml made cargo test impossible
- 现象
- `cargo test` compiled everything, then died with `could not execute process … z_blindband-….wasm` / `%1 is not a valid Win32 application. (os error 193)`.
- 成因
- `.cargo/config.toml` set `[build] target = "wasm32-wasip2"` so the component build could be typed more briefly. That default applies to `cargo test` too: cargo built the test harnesses as wasm and then tried to execute them as native binaries.
- 修复
- Delete the default target and name it on the one command that needs it — `cargo build --target wasm32-wasip2 --release`, which is what the deploy script already looked for. Plain `cargo test` now runs the 23 host tests.
- 代价
- Small in minutes, large in consequence: the repository claimed the gates were covered by a command that did not run. Found by actually running every command in our own documentation before publishing.
- BB-09我们自己
state.json was committed, so the first command failed for everyone but us
- 现象
- A clean clone carries `agent/state.json` naming our tenant DID and contract ids 870/871. On someone else's key, `npm run deploy` prints `contract : reusing z:efd91540…:blindband (id 871)` — a namespace that key does not own — registers nothing, and scopes their two maps to `readers: { only: [870, 871] }`. The first `submit` then dies with AccessDenied and an empty log ring.
- 成因
- `.gitignore` covered `data/*.json` and `.env` but not `state.json`, and `deploy.ts` trusted whatever the file said without checking it against the tenant it had just connected as.
- 修复
- Untrack `state.json`, and make the deploy compare `state.tenantDid` with the connected DID — foreign state is announced and discarded rather than used. An unparseable state file is now fatal too, which is the deeper fix BB-07 identified and did not make at the time.
- 代价
- Nothing to us, which is exactly the problem: we only found it by cloning our own public repository and reading it as a stranger. It would have failed for every judge, in the most confusing way this platform offers.
- BB-10我们自己
The sample data the README tells you to submit was not in the repository
- 现象
- `npm run submit -- data/records.json` is the second command in our own quickstart. A clean clone has no `agent/data/` directory at all.
- 成因
- `.gitignore` held `data/*.json` with a `!data/.gitkeep` exception, and `.gitkeep` was never created — so git dropped the directory entirely. The rule was written to keep generated round and receipt files out, and it took the one input file with them.
- 修复
- Ignore the four generated artefacts by name and track `records.json`. The 117 rows are synthetic — nine members named `member-01`…`member-09` — so there is nothing to withhold.
- 代价
- Found in the same pass as BB-09. Between them, the pipeline was reproducible by nobody, while the repository claimed otherwise in three places.
- BB-11平台
A sandbox tenant cannot provision the delegated agent identity the SDK offers it
- 现象
- `client.createAgent(did, "blindband-round-runner")` on a sandbox-claimed tenant returns `RPC Error: organisation has no policy meta`. The method is present and callable; the tier is not.
- 成因
- `createAgent` takes an *organisation* DID and requires the caller to be one of its admins. A DID claimed from the community sandbox page is not an organisation and carries no policy metadata, so there is no organisation for the agent to belong to. This is the same tier boundary as BB-01, seen from the other side: the opaque `t3n_key_…` that the flat `invoke()` path wants is precisely what `createAgent` returns.
- 修复
- None available from this tier. Rounds run under the tenant identity, and the agent reports which identity ran a round rather than implying otherwise. `npm run probe:agent` re-runs the check in one call, so whoever inherits this can tell in seconds whether their tier has lifted.
- 代价
- No lost time — but it is the single largest gap between what this is and what an agent on this platform is supposed to be, and it is not closable by writing better code.
- BB-12我们自己
The landing page was blank with JavaScript disabled
- 现象
- A crawler, a reader with scripting off, or a screenshot taken before hydration got the header, the footer, and nothing in between. `curl` on the built page shows why: every section ships as `style="opacity:0;transform:translateY(14px)"`.
- 成因
- The one entrance animation on the site starts each section at zero opacity and animates it in when it scrolls into view. That initial style is server-rendered, so the page's whole argument — the round, the gates, the verifier — depended on JavaScript running to become visible at all. Nothing in the browser ever showed it, because in a browser the animation always ran.
- 修复
- The animated wrapper carries a `data-reveal` marker and the document head carries a `<noscript>` rule that forces every one of them back to full opacity. The same commit made the light palette a `prefers-color-scheme` media query as well as a JavaScript-set attribute, so a light-configured machine gets a light page before hydration rather than a dark flash or nothing at all.
- 代价
- Found while building the screenshot script, not while using the site — which is the point. Half of the captures came back as an empty gradient, and the flaky tool turned out to be the honest witness.
继续运营或移交
我希望继续运营它,并推进到真实的联盟试点。如果 Terminal 3 更愿意自行托管,移交成本很低:仓库自成一体,部署脚本幂等,git 之外的状态只有租户 DID、合约身份和映射访问列表——它们都记录在 state.json 中,一次部署即可重建。