Skip to content

fix(api): bind inclusion proofs to the certified shard and network; return referenceTime on block records - #182

Merged
MastaP merged 12 commits into
mainfrom
spec-conformance-followups
Aug 26, 2026
Merged

fix(api): bind inclusion proofs to the certified shard and network; return referenceTime on block records#182
MastaP merged 12 commits into
mainfrom
spec-conformance-followups

Conversation

@MastaP

@MastaP MastaP commented Aug 25, 2026

Copy link
Copy Markdown
Member

Reviewer shortcut: ~96 lines of production code across 4 files; the rest is documentation correctness and test coverage. The security fix is in pkg/api/types.go.

Security fix: the verifier accepted leaves from the wrong shard (c767749, @ristik)

InclusionProofV2.Verify checked the UC against a caller-supplied ExpectedShardID and never checked that the state ID actually belongs to the certified shard. A leaf routing to shard A, committed in shard B's SMT under B's validly signed UC, verified — so in a multi-shard deployment a faulty or Byzantine quorum could certify a state ID owned by another shard, and the same state ID could hold accepted proofs in two shards. The seal's network ID was unchecked too, a domain-separation failure exploitable when validator keys are reused across trust bases.

The fix takes the shard from the certificate rather than the caller — uc.ShardTreeCertificate.Shard.Comparator()(key) — which is the same binding the JS, Java and Rust SDKs enforce, with no VerifierContext break. It also closes a more general gap: stateId is now derived from the certification data via CreateStateID(), and owner predicate, source state hash and witness must match between proof and request. Previously only transactionHash and expiresAt were compared, so a proof could carry different certification data than the request it was verified against.

Four regression tests come with it. I mutation-checked the shard binding locally: deleting the block makes TestInclusionProofV2Verify_StateIDMustBelongToCertifiedShard fail, so it guards the hole rather than passing vacuously.

Supersedes #183 (my proposal there wanted the full partition descriptor and a public API break — unnecessary). Tracked in #185.

The other functional fix: get_block_records returned unverifiable records

modelToAPIAggregatorRecord copied four fields and dropped the rest. referenceTime was not even a field on api.AggregatorRecord — and since the leaf value is LeafValue(transactionHash, referenceTime), a consumer had no way to rebuild it. The endpoint's whole purpose is defeated for anyone verifying proofs. expiresAt was dropped too, so the request deadline couldn't be checked either.

Both are now returned, along with version. The README documented all of this plus publicKey/signature — fields that no longer exist on CertificationData — and hex-encoded predicate bytes that actually serialize as base64. The example is now generated from real output, and a test pins the emitted key set so it can't drift again.

finalizedAt is removed rather than returned: models.NewBlock stamps CreatedAt at construction, i.e. proposal time, before the certification request goes to BFT, so exposing it would underreport finalization by the whole BFT round trip. Nothing persists a real finalization timestamp. It was always null, so nothing regresses.

Everything below is correction, not capability.

Documentation (402 lines)

The README was wrong in ways that would mislead an integrator:

  • Shard routing was documented backwards — "least significant bits" of the state ID, when the code reads MSB-first via KeyBitBE. The prose, the worked example, and both ASCII diagrams all said the opposite. Confirmed against the spec too: "keys are read as big-endian bit strings".
  • BFT_KEY_CONF_FILE is read by no code — the real variable is SIGNING_KEY_FILE. Proven by execution: setting the documented one loads cleanly with an empty key path.
  • CHAIN_FORK_ID defaults to testnet, not mainnet; BATCH_LIMIT caps nothing (one consumer, a startup log field); the two child shard IDs were swapped relative to sharding-compose.yml; STATE_ID_MISMATCH is SHA256(CBOR[ownerPredicate, sourceStateHash]), not publicKey.
  • 22 environment variables had no entry at all. Both directions now hold mechanically: every variable config.go reads is documented (104/104), and every documented variable is actually read (0 phantom).

docs/inclusion-proof-wire.md is new — three source comments cited it as the frozen specification, but the file did not exist. Two defects were caught in it during review, both of which would have broken an independent client:

  • the inner-node hash rule omitted the key-prefix region, so any proof carrying a sibling verifies against a different root. The same formula was already wrong in the README, which is where it was copied from; the yellowpaper agrees with the code, so the docs were the only thing out of step. Bit addressing was likewise documented LSB-first after the switch to big-endian.
  • ownerPredicate is tagged, not a bare array — Predicate.MarshalCBOR emits tag 39032 and unmarshal requires it, so a client following the document would reject every proof's certification data.

The doc also records the remaining ExclusionCert divergence (#184). The shard-binding section it carried is gone — c767749 fixed the underlying gap, so the document no longer needs to warn about it.

Tests (316 lines)

  • The verifier's exclusive expiry boundary was unexercised. Mutating >= to > in pkg/api/types.go left the whole pkg/api suite green, because the only fully-built proof fixture leaves ExpiresAt nil. Now covered at both ends, plus presence-mismatch. (Note for future tests here: buildSignedSingleLeafProof aliases the proof's certification data to the request's, so the two can never disagree unless the copy is detached — my first attempt passed vacuously because of it.)
  • The documented hash rules are now machine-checked: a root is built from the documented formula and Verify must accept it, and a root built without the region must be rejected. Documentation of a wire format that nothing executes is exactly how this drifted twice.
  • The get_block_records key set is pinned so the README can't silently diverge again.

Metric (21 lines)

aggregator_certification_requests_by_deadline_total{origin} splits accepted requests by whether the requester supplied a deadline or the service assigned one from DEFAULT_REQUEST_TTL.

Its original justification was wrong and has been retracted in-branch. I had read the yellowpaper as making the request timeout mandatory and framed this as a migration backlog. Yellowpaper ba5d716 specifies it as optionalτ̄_Q ∈ 𝕋 ∪ {⊥}, written as CBOR null at a fixed position, effective timeout τ̂_Q = τ_a + Δ when absent — which is exactly what this service does. It also confirms the fixed-shape encoding used here: "each version of a structure has exactly one tuple shape and one element count, and an optional element occupies its position whether or not it carries a value." The counter stays as operational visibility into how much traffic depends on the default lifetime; it is not a deprecation clock. Drop this commit if you'd rather not carry it.

A review round also caught it being incremented at deadline assignment rather than after acceptance, so expired and duplicate requests inflated it. Fixed, and the regression test was mutation-checked — reintroducing the bug makes it fail.

Housekeeping

Several throwaway probe files written by audit subagents in the worktree were swept in by git add -A (cmd/cfgdump/main.go, zz*-prefixed tests under internal/smt, pkg/api, internal/signing). All removed; the net diff is clean. The reproduction of the shard-binding bug is preserved in #183 rather than shipped as a permanently failing test.

Verification

go build and go vet clean on Go 1.26. Passing: pkg/..., internal/config, internal/models, internal/gateway, internal/signing, and the targeted internal/service tests.

internal/service and internal/round are flaky against MongoDB testcontainers on loaded hardware — every failure is an i/o timeout during index creation, never an assertion, and unmodified main reproduces the same failing set under identical conditions. Use go test -p 1 locally; the default per-package parallelism starts enough Mongo containers to starve them.

Related

Found by the same spec audit: #185 (shard/network binding — fixed here by c767749; supersedes #183) and #184 (four non-critical divergences, recorded, not being pursued).

MastaP added 4 commits August 25, 2026 18:13
…k records

get_block_records dropped three fields the README documented. referenceTime is
the consequential one: it is what a consumer needs to rebuild the certified
leaf value, LeafValue(transactionHash, referenceTime), and api.AggregatorRecord
had no field for it at all. expiresAt was dropped from the certification data,
leaving no way to check the request deadline, and finalizedAt was declared on
the API type but never populated by anything.

- adds ReferenceTime to api.AggregatorRecord
- copies Version and ExpiresAt through the conversion
- populates FinalizedAt from the block, one read per page rather than per record
- corrects the README example, which also showed publicKey/signature -- fields
  that no longer exist on CertificationData -- and hex-encoded predicate bytes
  that actually serialize as base64

Pins the emitted key set in a test so the README cannot drift again.
Mutating the comparison at pkg/api/types.go from >= to > left the whole pkg/api
suite green: the only fully-built proof fixture leaves ExpiresAt nil, so the
branch was never entered. The server-side boundary is pinned at both ends but
the verifier clients actually run was not.

Covers reference time below, equal to, and above the deadline, plus the
presence-mismatch cases. Note buildSignedSingleLeafProof aliases the proof's
certification data to the request's, so the presence tests detach the copy
first -- without that the two can never disagree and the test passes vacuously.

Also corrects a comment in inclusion_cert.go still describing the leaf value as
the transaction hash, which the reference-time change invalidated.
pkg/api/types.go and pkg/api/inclusion_cert.go cite docs/inclusion-proof-wire.md
as the frozen specification in three places, but the file did not exist -- the
Go comments were the only statement of the wire shape.

Documents the tagged 5-element InclusionProofV2, the fixed 6-element
CertificationData with expiresAt holding its slot as CBOR null, the leaf value
preimage (verified byte-for-byte: 825820 || txhash || CBOR uint), the
InclusionCert and ExclusionCert binary layouts, the hash rules, and the
verification order including the exclusive deadline comparison.

Records two things a client integrator would otherwise get wrong: reference time
must be read from the proof's referenceTime element and not recovered from
UC.IR.t, which coincides only for the proof issued in the leaf's own round; and
Verify does not check that sid routes to the expected shard.
The yellowpaper defines the certification request with a mandatory exclusive
timeout tau_Q, and makes 'tau < tau_Q' a step of verifying a certified
transaction. This implementation accepts CBOR null in that slot and assigns a
deadline from DEFAULT_REQUEST_TTL instead -- but that substitute is service-local:
not in the leaf, not signed, not served in the proof. A transaction certified
that way leaves a later verifier unable to perform the spec's check at all.

Rejecting the absent form outright would break existing clients, so this only
adds the visibility needed to retire it: the service_assigned series is the
migration backlog, and it reaching zero is the precondition for rejecting
requests that omit the deadline.

aggregator_certification_requests_by_deadline_total{origin}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f25a0095b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/inclusion-proof-wire.md Outdated
## Hash rules

- Leaf: `H(0x00 || key || value)`
- Inner node, two children: `H(0x01 || depth_byte || left || right)`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include the key region in the inner-node hash rule

Correct the frozen wire specification to include the 32-byte key-prefix region in every binary inner-node preimage. The verifier actually computes H(0x01 || depth_byte || RegionFromKeyBytes(key, depth) || left || right) in pkg/api/inclusion_cert.go, so an independent client implementing the documented formula will derive a different root for any proof containing a sibling and reject otherwise valid proofs.

Useful? React with 👍 / 👎.

@MastaP
MastaP requested review from jait91 and ristik August 25, 2026 17:47
The inner-node preimage in docs/inclusion-proof-wire.md omitted the key-prefix
region. The verifier computes

    H(0x01 || depth_byte || region(key, depth) || left || right)

(pkg/api/inclusion_cert.go), so the documented formula reproduces the correct
root only for a proof with zero siblings. Any proof carrying a sibling would
verify against a different root in an independent client implementing the doc --
and that document is cited in three places as the frozen specification.

The same formula was already wrong in README.md, which is where it was copied
from; both are corrected. README also described key bit addressing as LSB-first,
which the switch to big-endian ordering invalidated: bit(key,d) is
(key[d/8] >> (7 - d%8)) & 1, verified against KeyBitBE.

Pins both rules in tests that build a root from the documented formula and
require Verify to accept it, and that assert a root built without the region is
rejected -- so this drifts in CI rather than in someone else's client.
@MastaP

MastaP commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

Good catch, fixed in 6f4bd56 — and it was worse than the comment says.

The verifier computes `H(0x01 || depth_byte || region(key, depth) || left || right)` (pkg/api/inclusion_cert.go:152), so the documented formula reproduces the correct root only for a proof with zero siblings. Any proof carrying a sibling would verify against a different root in an independent client. In a file cited in three places as the frozen specification, that is the worst place for it.

Two things beyond the flagged line:

  1. The same formula was already wrong in README.md:377, which is where I copied it from. Both are corrected. The yellowpaper agrees with the code — inner nodes "commit to their absolute branching depth and to the key prefix (region) addressing them" — so the docs were the only thing out of step.

  2. README.md:380 also described key bit addressing as LSB-first, bit(key,d) = (key[d/8] >> (d%8)) & 1. The switch to big-endian ordering (Sync Aggregator SMT bit ordering with Yellowpaper big-endian bit strings #169) invalidated that; it is (key[d/8] >> (7 - d%8)) & 1. Verified against KeyBitBE: for key[0] = 0x80, KeyBitBE(key, 0) == 1, which is MSB-first.

Rather than just editing prose, both rules are now pinned by tests that build a root from the documented formula and require Verify to accept it, plus one asserting a root built without the region is rejected with ErrCertRootMismatch. Documentation of a wire format that nothing executes is how this drifted twice; now it fails in CI instead of in someone else's client.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f4bd5652e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md
- Value: `SHA-256(CBOR([transactionHash, referenceTime]))` for every inclusion proof
- Leaf: `H(0x00 || key || value)`
- Inner node (two children): `H(0x01 || depth_byte || left || right)`
- Inner node (two children): `H(0x01 || depth_byte || region(key, depth) || left || right)`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include the region in the verification pseudocode

The fresh evidence is that this newly updated hash rule includes region(key, depth), while the immediately following pseudocode still computes each inner hash without it. An independent client copying that pseudocode will derive the wrong root for every proof containing a sibling, despite the surrounding text now claiming the algorithm is wire-compatible; add the region to both pseudocode branches.

Useful? React with 👍 / 👎.

Comment thread internal/service/service.go Outdated
return nil, fmt.Errorf("failed to get block %s: %w", req.BlockNumber.String(), err)
}
if block != nil {
finalizedAt = block.CreatedAt

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return the actual finalization time

In standalone and BFT-shard mode, models.NewBlock sets CreatedAt before the certification request is sent to BFT (internal/round/batch_processor.go), so certification and finalization occur later. Exposing that proposal-creation timestamp as finalizedAt makes every block record underreport its finalization time by the BFT wait and persistence duration; persist and return the actual finalization/proof-ready timestamp instead.

Useful? React with 👍 / 👎.

…izedAt

Two follow-ups from review of the previous commits.

The README hash-rule bullet was corrected to include region(key, depth), but the
verification pseudocode immediately below it still computed each inner hash
without the region -- the same defect, in the same section, one code block down.
An independent client copying the pseudocode would still derive a wrong root for
every proof containing a sibling.

finalizedAt was populated from the block's CreatedAt, which models.NewBlock
stamps when the block is constructed at proposal time, before the certification
request is sent to BFT. Every record would therefore have underreported its
finalization time by the BFT wait plus persistence. Nothing currently persists a
real finalization timestamp, so the field is removed from api.AggregatorRecord
and the documented example rather than returned wrong; adding one means writing
it on the block at finalization, which belongs in its own change.
@MastaP

MastaP commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

Both fixed in 6c46303.

Pseudocode (P1). Right — I corrected the hash-rule bullet and missed the code block four lines below it, which is the part an integrator would actually copy. Same defect, same section. Now H(0x01 || d || r || ...) in both branches, with r = region(key, d) spelled out inline and the descent direction commented.

finalizedAt (P2). Also right, and it was my change that introduced it. models.NewBlock stamps CreatedAt at construction (internal/models/block.go:159), which is proposal time — before the certification request goes to BFT — so every record would have underreported finalization by the BFT wait plus persistence.

I have not persisted a real finalization timestamp here, and want to be explicit about why rather than quietly doing the smaller thing: the finalization path branches across disk/non-disk, already-finalized-duplicate, and recovery, and I have not traced all of them. Adding a timestamp that is right on one branch and missing on another is a worse outcome than not having one. So finalizedAt is removed from api.AggregatorRecord and the documented example — it was always null before this PR, so nothing can regress — and persisting a real one on the block at finalization is worth its own change.

The original audit finding stands: the field was documented but never populated. This resolves that by removing the claim rather than by inventing a value.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c46303db7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmd/cfgdump/main.go Outdated
)

func main() {
os.Setenv("BFT_ENABLED", "false")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve BFT_ENABLED when dumping configuration

When cfgdump is run with a valid SHARDING_MODE=bft-shard deployment configuration, this assignment overwrites the required BFT_ENABLED=true value before loading it. Config.Validate then rejects the configuration (internal/config/config.go:625-627), so the new command exits without dumping anything for BFT-shard deployments; avoid overriding the caller's environment.

Useful? React with 👍 / 👎.

@ristik

ristik commented Aug 25, 2026

Copy link
Copy Markdown
Member

Had uncommitted YP changes. Wanted to rewrite by hand. Committed as ba5d71680f47e9d978f24bff819aeb6b6f394ce2 , now should be in sync with the implementation

A section-by-section audit against the implementation, with each claim checked
by executing something -- loading the config, marshalling the real response
struct, running the routing predicate -- rather than reading struct tags.

Factually wrong:
- CHAIN_FORK_ID default is testnet, not mainnet
- BFT_KEY_CONF_FILE is read by no code; the real variable is SIGNING_KEY_FILE
- BATCH_LIMIT caps nothing, it is only logged at startup
- shard routing reads the LEADING bits of the state ID, not the trailing ones;
  the prose, the worked example and both ASCII diagrams said the opposite
- the two child shard IDs were swapped relative to sharding-compose.yml
- STATE_ID_MISMATCH is SHA256(CBOR[ownerPredicate, sourceStateHash]), not
  publicKey; SIGNATURE_VERIFICATION_FAILED covers the source state hash too
- get_block omitted totalCommitments; several example payloads had fields that
  no longer exist or values the code never produces
- bft-shard and parent/child use the SAME routing key, not different ones

Undocumented: 22 environment variables the code reads had no README entry at
all -- HTTP/2, log rotation, Redis tuning, SMT proof precomputation and the
child poll timings. Every variable read by config.go is now documented, and
every variable documented is now read by config.go; both directions checked
mechanically.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f031c4a4f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docs/inclusion-proof-wire.md Outdated
| Index | Field | Type |
|-------|-------|------|
| 0 | `version` | uint, `2` |
| 1 | `ownerPredicate` | array |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include the predicate's CBOR tag

Document ownerPredicate as #39032([engine, code, params]), not a plain array, and add tag 39032 to the registry above. Every emitted predicate is wrapped by Predicate.MarshalCBOR, while Predicate.UnmarshalCBOR requires that tag, so an independent client implementing this newly frozen byte specification will reject or re-encode every inclusion proof's certification data incorrectly.

Useful? React with 👍 / 👎.

…as design

An audit against the yellowpaper found that this document had turned two
implementation gaps into apparent specification.

The shard binding. platform.tex VerifyInclusionProof takes the partition
description as an input and requires f_SH(sid) = sigma -- the expected shard is
derived from the KEY. It also states that VerifyUnicityCert does not by itself
prove a state identifier belongs to the shard named in the certificate, and that
the binding is the proof verifier'\''s job. InclusionProofV2.Verify instead compares
the UC'\''s shard against a caller-supplied ExpectedShardID. A leaf routing to shard
A, committed in shard B'\''s SMT under shard B'\''s validly signed UC, verifies --
reproduced in testing. The previous text told integrators to derive
ExpectedShardID from configuration, which is not the specified check and does not
close the gap. Now recorded as the soundness divergence it is. The network id
check UC.C^r.alpha = T.alpha is likewise absent.

ExclusionCert. appendix-hashtrees.tex orders the certificate
bitmap || siblings || k'\'' || v'\''; the Go type puts the fixed 32-byte fields first,
which also makes a variable-length v'\'' unencodable and leaves the spec'\''s
empty-tree certificate undecodable. Nothing generates or verifies these, and
neither security-critical check the spec names exists. Documented as a divergence
rather than as a frozen format.

The preamble now says plainly that the yellowpaper is authoritative and that this
file must not present an implementation gap as a specification.
@ristik

ristik commented Aug 25, 2026

Copy link
Copy Markdown
Member

Section 4 should be resolved, YP was out of sync. Sec. 1, 2 are not related.

Also, it should be stated that time unit is in seconds.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17e2ec0ae1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// The yellowpaper makes the deadline a mandatory element of the request; the
// service_assigned series is the migration backlog, and reaching zero is the
// precondition for rejecting requests that omit it.
CertificationRequestsByDeadline = promauto.NewCounterVec(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use a rate-aware deadline migration signal

When any accepted request omits its deadline, this Prometheus counter becomes nonzero and can never “reach zero” as the migration condition above requires; conversely, a process restart resets it to zero even if unmigrated clients are still active. This makes the advertised signal unsafe for deciding when to reject missing deadlines. Track the absence rate over an explicit observation window (or represent an actual outstanding population) rather than treating the raw cumulative counter as a backlog.

Useful? React with 👍 / 👎.

…ation case

Yellowpaper ba5d716 ("us request expiry as absolute time") specifies the request
timeout as optional: Q carries tau_Q_bar in T union {bottom}, bottom is written
as CBOR null at a fixed position, and the effective timeout is tau_a + Delta when
absent and tau_Q_bar otherwise, where tau_a is the latest consensus-derived
reference time at admission and Delta the service's default request lifetime.
The assigned value is service metadata and does not alter txhash.

That is what this service already does, so the comments claiming the paper makes
the deadline mandatory were wrong, as was framing the deadline-origin counter as
a migration backlog with retirement of the absent form as its goal. The counter
stays -- what share of traffic depends on DEFAULT_REQUEST_TTL is worth seeing --
but it is operational visibility, not a deprecation clock.

Appendix ba5d716 also confirms the fixed-shape encoding this wire already uses:
"Each version of a structure therefore has exactly one tuple shape and one
element count, and an optional element occupies its position whether or not it
carries a value."

Also conformant, and now stated as such: the admission check is permitted but not
sufficient, with the authoritative check at leaf materialisation against that
round's pinned reference time -- which is where it already runs.
MastaP added 2 commits August 25, 2026 20:38
Eleven throwaway probe tests written by the spec-audit subagents in this
worktree were swept into f031c4a and 17e2ec0 by a 'git add -A'. They are
scratch reproductions, not tests meant to ship -- roughly 1,700 lines of
zz_-prefixed files under internal/smt and pkg/api.

Net diff against main is unaffected; they were added and are now removed.

The most useful of them, the reproduction of the missing f_SH(sid) shard
binding, is preserved in issue #183 rather than here, where it would have
shipped as a permanently failing test.
Three more subagent scratch files reached the branch via 'git add -A':
cmd/cfgdump/main.go (a config dumper), internal/signing/zzaudit_test.go and
internal/signing/zzlens_predicate_test.go. The earlier cleanup missed them
because it globbed zz_* and these are cfgdump and zz-without-underscore.

Also fixes a real defect in the wire spec found in review: ownerPredicate was
documented as a bare array, but Predicate.MarshalCBOR emits tag 39032 and
Predicate.UnmarshalCBOR requires it, so a client following the document would
reject or re-encode every proof's certification data. Verified on the wire:
engine 1 / code 0x01 / params 0x02 encodes as d99878 83 01 4101 4102. Tag 39032
added to the registry.
@MastaP MastaP changed the title Spec conformance follow-ups: block record fields, verifier boundary tests, wire spec, deadline migration signal get_block_records: return referenceTime and expiresAt (+ README/wire-spec corrections, boundary tests) Aug 25, 2026
@MastaP

MastaP commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

@ristik rechecked with YP. the only business-logic fix is get_block_records, the rest is documentation sync

@MastaP MastaP changed the title get_block_records: return referenceTime and expiresAt (+ README/wire-spec corrections, boundary tests) fix(api): bind inclusion proofs to the certified shard and network; return referenceTime on block records Aug 26, 2026

@ristik ristik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MastaP
MastaP merged commit ae08165 into main Aug 26, 2026
3 checks passed
@MastaP
MastaP deleted the spec-conformance-followups branch August 26, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants