mirror of
https://github.com/luxfi/docs.git
synced 2026-09-24 00:25:32 +00:00
docs(zap): generate the ZAP-native package reference from godoc
24 package pages under zap/reference, generated by luxfi/zap tools/docgen from source comments. Build verified (781+ static pages). Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"pages": [
|
||||
"index",
|
||||
"api"
|
||||
"api",
|
||||
"---Package reference---",
|
||||
"reference"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: "main"
|
||||
description: "Real-world benchmark: ZAP vs gRPC/Protobuf"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/bench"
|
||||
```
|
||||
|
||||
Real-world benchmark: ZAP vs gRPC/Protobuf
|
||||
|
||||
|
||||
@@ -0,0 +1,881 @@
|
||||
---
|
||||
title: "dexsession"
|
||||
description: "Package dexsession is the ZAP/Cap'n-Proto orchestration layer for the Lux DEX."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/dexsession"
|
||||
```
|
||||
|
||||
Package dexsession is the ZAP/Cap'n-Proto orchestration layer for the Lux DEX.
|
||||
|
||||
It makes the native C<->D atomic settlement flow FEEL synchronous (capability
|
||||
transport + promise pipelining) while keeping the value boundary 100% native.
|
||||
|
||||
# The hard invariant (the whole point)
|
||||
|
||||
A ZAP response is NEVER sufficient to move money. Money moves ONLY when:
|
||||
|
||||
C consumes a real D->C atomic shared-memory export object, OR
|
||||
D consumes a real C->D atomic export object.
|
||||
|
||||
Even a fully malicious or stale ZAP layer cannot mint, credit, or substitute
|
||||
value: it can only POINT the precompile/keeper at an atomic object that the
|
||||
chain independently verifies (asset / owner / amount / one-time bound). This
|
||||
package therefore traffics in VALUES — quotes (estimates), calldata (bytes to
|
||||
sign), and POINTERS (DExportRef) — never in authority over a balance.
|
||||
|
||||
# Why the invariant holds structurally, not just by policy
|
||||
|
||||
The C-side value boundary (luxfi/precompile/dex/native_dchain_client.go) and
|
||||
the D-side value boundary (luxfi/dex/pkg/dchain/atomic.go) both operate on EVM
|
||||
host capabilities (a StateDB and an AtomicState / shared memory). This package
|
||||
holds NONE of those: it has no StateDB, no AtomicState, no shared memory, and
|
||||
— by deliberate module discipline — no compile-time edge to the precompile or
|
||||
the EVM at all. It cannot import the C Verify path, and the C Verify path does
|
||||
not import it. The only things crossing the boundary are bytes:
|
||||
|
||||
- prepareSwapIntent returns CALLDATA (hookData for 0x9999) the USER signs;
|
||||
it reserves no funds and returns no amountOut the chain trusts.
|
||||
- notifyIntent tells D to SCAN/import a C->D object the user's signed C tx
|
||||
already created; it cannot make a D match valid without a committed D
|
||||
block.
|
||||
- importSettlement returns finalization CALLDATA / submits a C tx that
|
||||
merely POINTS at a DExportRef; the chain's ImportSettlement re-reads the
|
||||
real D->C object and binds recipient/asset/amount to it.
|
||||
|
||||
So "no capability moves money" is provable by EXHAUSTION over the API surface:
|
||||
there is no creditBalance / settleFill / overrideMatch / adminWithdraw method
|
||||
to call (see capability.go). The capability gate is defence in depth on top of
|
||||
a surface that is already value-free by construction.
|
||||
|
||||
# Capabilities (Cap'n-Proto style)
|
||||
|
||||
FullServiceNode.Bootstrap returns a RESTRICTED DexSession. From the session a
|
||||
caller derives narrowly-scoped, unforgeable capabilities — QuoteCap (read),
|
||||
IntentCap (request a C->D intent + notify), WatchCap (subscribe to a D
|
||||
result), SettlementCap (ask C to import an EXISTING D->C object), AdminCap
|
||||
(halt/status only, separately gated). A capability cannot grant authority the
|
||||
session was not bootstrapped with, and none of them can mint or credit a C
|
||||
balance.
|
||||
|
||||
# Promise pipelining (the latency win)
|
||||
|
||||
Dependent calls issue before each prior resolves:
|
||||
|
||||
quote := dex.Quote(req)
|
||||
intent := dex.PrepareSwapIntent(req, quote) // takes the quote promise
|
||||
watch := dex.NotifyIntent(intent) // takes the intent promise
|
||||
settle := watch.OnCommitted().ImportSettlement()
|
||||
|
||||
The client overlaps network latency (each call dispatches the instant its
|
||||
inputs resolve), and a Pipeline batch collapses a whole dependency chain into
|
||||
a single round trip. The consensus path stays native atomic throughout.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### DecodeAtomicObject
|
||||
|
||||
```go
|
||||
func DecodeAtomicObject(v []byte) (owner Account, asset ID, amount uint64, ok bool)
|
||||
```
|
||||
|
||||
DecodeAtomicObject is the inverse. ok=false for any value that is not exactly
|
||||
the canonical width, so a corrupt record is never reinterpreted — the same
|
||||
defence the precompile and dexvm decoders apply. A consumer binds the credited
|
||||
owner/asset/amount to THIS recorded value, never to a declared claim.
|
||||
|
||||
|
||||
### EncodeAtomicObject
|
||||
|
||||
```go
|
||||
func EncodeAtomicObject(owner Account, asset ID, amount uint64) []byte
|
||||
```
|
||||
|
||||
EncodeAtomicObject serializes a cross-chain value object as the shared-memory
|
||||
value: owner(20) | asset(32) | amount(8). Byte-identical with the precompile
|
||||
and dexvm encoders. This is the ONLY value-bearing identity the atomic
|
||||
conservation binds; dexsession reproduces the encoding so a watcher can derive
|
||||
the object key it points at — it NEVER writes one into shared memory (it has
|
||||
no shared memory).
|
||||
|
||||
|
||||
### EncodeIntentHookData
|
||||
|
||||
```go
|
||||
func EncodeIntentHookData(deadline, nonce uint64) []byte
|
||||
```
|
||||
|
||||
EncodeIntentHookData builds an explicit Phase-A hookData carrying the deadline and the
|
||||
intent NONCE. BYTE-IDENTICAL to precompile/dex EncodeIntentHookData — both sides MUST
|
||||
produce the same calldata for the same (deadline, nonce) or the off-chain-derived
|
||||
intent id would diverge from the on-chain one (the watch-correlation contract). The
|
||||
encoding is MINIMAL-WIDTH (the precompile decodes all three widths):
|
||||
- deadline==0 && nonce==0 -> tag only (4 bytes)
|
||||
- nonce==0 -> tag | deadline[32]
|
||||
- else -> tag | deadline[32] | nonce[32]
|
||||
|
||||
|
||||
### EncodeModifyLiquidityCalldata
|
||||
|
||||
```go
|
||||
func EncodeModifyLiquidityCalldata(pk PoolKeyArgs, args ModifyLiquidityArgs, hookData []byte) []byte
|
||||
```
|
||||
|
||||
EncodeModifyLiquidityCalldata builds the full 0x9999 modifyLiquidity calldata:
|
||||
|
||||
selector(4) || PoolKey(5 words) || ModifyParams(4 words: tickLower, tickUpper,
|
||||
liquidityDelta, salt) || offset(1 word) || hookData(length word + padded bytes)
|
||||
|
||||
Standard Solidity ABI encoding of the modifyLiquidity signature. The dynamic
|
||||
`bytes hookData` is tail-encoded with a head offset word. hookData selects the
|
||||
phase exactly as for swap: empty / DI01 for the commit intent, DS01 for settling a
|
||||
removal's D->C export (though a removal's settlement is built via the swap-shaped
|
||||
EncodeSwapCalldata + DS01, since the credit kernel is the swap path — see
|
||||
v4session.go collect/cancel).
|
||||
|
||||
|
||||
### EncodeSettlementHookData
|
||||
|
||||
```go
|
||||
func EncodeSettlementHookData(outputID ID, amount uint64, intentID ID) []byte
|
||||
```
|
||||
|
||||
EncodeSettlementHookData builds a Phase-B hookData: tag + outputID + amount + intentID.
|
||||
Byte-identical to precompile/dex EncodeSettlementHookData (the INVERSE of its
|
||||
decodeSettlementBody). amount is right-aligned in a uint256 word (the low 8 bytes),
|
||||
matching the precompile's binary.BigEndian.PutUint64(amt[24:32], amount). intentID names
|
||||
the originating C->D intent the settlement draws against — the precompile binds the credit
|
||||
to that taker's intent record (the per-taker cap + the deadline gate), so it is REQUIRED;
|
||||
a body without it is the wrong width and the on-chain decode reverts.
|
||||
|
||||
CRITICAL: the body carries outputID + amount + intentID, but NOT the output ASSET or the
|
||||
RECIPIENT — on-chain the asset is derived from the swap direction and the recipient is the
|
||||
CALLER. So this calldata cannot name a victim's recipient or a re-denominated asset;
|
||||
ImportSettlement binds outputID/amount/intentID against the RECORDED object + the recorded
|
||||
owner's intent. (This is why a tampered DExportRef cannot substitute recipient/asset/amount
|
||||
— see the package invariant.)
|
||||
|
||||
|
||||
### EncodeSwapCalldata
|
||||
|
||||
```go
|
||||
func EncodeSwapCalldata(pk PoolKeyArgs, zeroForOne bool, amountIn uint64, hookData []byte) []byte
|
||||
```
|
||||
|
||||
EncodeSwapCalldata builds the full 0x9999 swap calldata:
|
||||
|
||||
selector(4) || PoolKey(5 words) || SwapParams(3 words) || offset(1 word) ||
|
||||
hookData(length word + padded bytes)
|
||||
|
||||
This is the standard Solidity ABI encoding of
|
||||
swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes). The
|
||||
dynamic `bytes hookData` is tail-encoded with a head offset word.
|
||||
|
||||
zeroForOne + amountSpecified come from the request; amountSpecified is encoded
|
||||
as -AmountIn (exact input). The hookData selects the phase: pass
|
||||
EncodeIntentHookData()/empty for Phase A, EncodeSettlementHookData(...) for
|
||||
Phase B.
|
||||
|
||||
|
||||
### NewSession
|
||||
|
||||
```go
|
||||
func NewSession(cfg SessionConfig) *clientSession
|
||||
```
|
||||
|
||||
NewSession builds a client session. Bootstrap (server.go) is the canonical
|
||||
entry; this is the explicit constructor for a node that already has a peer.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Account
|
||||
|
||||
Account is a 20-byte EVM account (taker / recipient / owner). Byte-identical
|
||||
to the low 20 bytes of an EVM address; the precompile binds it as the atomic
|
||||
object owner.
|
||||
|
||||
|
||||
### AdminCap
|
||||
|
||||
AdminCap permits halt/status only, and is granted SEPARATELY (AuthAdmin is not
|
||||
in AuthorityPublic). It cannot withdraw, credit, or override a match.
|
||||
|
||||
|
||||
```go
|
||||
func (c AdminCap) Authority() Authority
|
||||
```
|
||||
|
||||
### Authority
|
||||
|
||||
Authority is a bitmask of the operation classes a capability permits. Each bit
|
||||
maps to exactly one capability type; a bit grants ONLY the read/build/point/
|
||||
subscribe operations of that class — never a credit.
|
||||
|
||||
|
||||
### CollectRequest
|
||||
|
||||
CollectRequest is the off-chain request to COLLECT/DECREASE (negative-delta) or
|
||||
CANCEL a position/order. Both produce a D->C export (collected fees / withdrawn
|
||||
liquidity / cancelled-order refund) consumed by the ONE DS01 credit path. Cancel is
|
||||
the same shape with the cancel marker; the credit is identical.
|
||||
|
||||
|
||||
### DExportRef
|
||||
|
||||
DExportRef points at a D->C atomic export object. It is deliberately NOT a
|
||||
DFillReceipt: it carries no amount the chain trusts and no signature C honours.
|
||||
The chain re-reads the actual object (asset/owner/amount/one-time) on import.
|
||||
|
||||
SourceChainID — the D chain whose export the object lives under.
|
||||
SourceTxID — the D tx that produced the export.
|
||||
OutputIndex — which exported output (deriveUTXOID(SourceTxID, OutputIndex)).
|
||||
IntentID — the originating C->D intent (correlation only).
|
||||
|
||||
|
||||
```go
|
||||
func (r DExportRef) ObjectKey() ID
|
||||
```
|
||||
|
||||
### DexSession
|
||||
|
||||
DexSession is the RESTRICTED capability a FullServiceNode.Bootstrap returns. It
|
||||
exposes ONLY the orchestration surface — quote/getState/prepareSwapIntent/
|
||||
notifyIntent/importSettlement — and derives narrowly-scoped capabilities. There
|
||||
is no creditBalance / settleFill / overrideMatch / adminWithdraw: the surface is
|
||||
value-free by construction (see capability.go).
|
||||
|
||||
|
||||
### FlowStages
|
||||
|
||||
FlowStages is the set of promises a SwapFlow produces, exposed so a caller can
|
||||
observe intermediate artifacts (e.g. show the user the estimate, the intent id,
|
||||
the watch) without re-issuing calls. Each is a value/pointer, never authority.
|
||||
|
||||
|
||||
### FullServiceConfig
|
||||
|
||||
FullServiceConfig configures the server node.
|
||||
|
||||
|
||||
### FullServiceNode
|
||||
|
||||
FullServiceNode wraps a zap.Node and a Venue, exposing Bootstrap. It is the
|
||||
server-side object the spec's `FullServiceNode.bootstrap() -> DexSession`
|
||||
describes.
|
||||
|
||||
|
||||
```go
|
||||
func NewFullServiceNode(cfg FullServiceConfig) *FullServiceNode
|
||||
```
|
||||
|
||||
```go
|
||||
func (fsn *FullServiceNode) Bootstrap(peerID string, grant Authority) DexSession
|
||||
```
|
||||
|
||||
### ID
|
||||
|
||||
ID is a 32-byte identifier (chain id, tx id, market id, intent id, asset id).
|
||||
Byte-identical to luxfi/ids ids.ID and to an EVM bytes32. Native asset == the
|
||||
all-zero ID (mirrors ids.Empty in the dexvm ledger).
|
||||
|
||||
|
||||
```go
|
||||
func DeriveIntentID(
|
||||
networkID uint32,
|
||||
cChainID, dChainID ID,
|
||||
account Account,
|
||||
assetIn ID,
|
||||
amountIn uint64,
|
||||
marketID ID,
|
||||
nonce uint64,
|
||||
) ID
|
||||
```
|
||||
|
||||
```go
|
||||
func DeriveLiquidityParamsHash(tickLower, tickUpper int32, liquidityDelta int64, salt ID) ID
|
||||
```
|
||||
|
||||
```go
|
||||
func DerivePoolKeyHash(pk PoolKeyArgs) ID
|
||||
```
|
||||
|
||||
```go
|
||||
func DeriveRoutePathHash(path []ID) ID
|
||||
```
|
||||
|
||||
```go
|
||||
func DeriveSessionID(s V4ActionScope) ID
|
||||
```
|
||||
|
||||
```go
|
||||
func DeriveSwapParamsHash(zeroForOne bool, amountIn uint64) ID
|
||||
```
|
||||
|
||||
```go
|
||||
func DeriveUTXOID(sourceTxID ID, outputIndex uint32) ID
|
||||
```
|
||||
|
||||
### IntentCap
|
||||
|
||||
IntentCap permits requesting a C->D intent (build calldata) and notifying D to
|
||||
scan/import the resulting object. It cannot reserve funds, claim a fill, credit,
|
||||
or settle.
|
||||
|
||||
|
||||
```go
|
||||
func (c IntentCap) Authority() Authority
|
||||
```
|
||||
|
||||
### IntentPhase
|
||||
|
||||
IntentPhase is the lifecycle of a notified intent as the off-chain watcher
|
||||
observes it. NONE of these phases moves value — Committed merely means the
|
||||
watcher SAW a D export object; the user still imports it on C.
|
||||
|
||||
|
||||
### IntentStatus
|
||||
|
||||
IntentStatus is one poll/push of a watch. When Phase==PhaseCommitted the Ref
|
||||
points at the produced D->C object. A malicious server can set Phase=Committed
|
||||
with a bogus Ref, but importSettlement of that Ref reverts on-chain (the object
|
||||
is missing or binds to a different owner/asset/amount).
|
||||
|
||||
MatchedOut is an ESTIMATE the venue reports when D has matched (PhaseMatching/
|
||||
Committed): the orchestration "you'll receive ~N" figure the bidirectional
|
||||
MatchResult read surfaces. It is INFORMATIONAL ONLY — exactly the QuotedOut
|
||||
discipline extended to the match phase. The chain NEVER trusts it: the credit is
|
||||
the recorded D->C object's amount, bound on-chain at settlement. A lying venue can
|
||||
set MatchedOut to anything; it changes no balance (proven by the RED suite).
|
||||
|
||||
|
||||
### IntentWatch
|
||||
|
||||
IntentWatch is the subscription handle a NotifyIntent returns. Poll observes the
|
||||
current phase; OnCommitted yields a Promise that resolves to the DExportRef when
|
||||
(and only when) the watch observes a committed D export.
|
||||
|
||||
|
||||
```go
|
||||
func (w IntentWatch) IntentID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (w IntentWatch) OnCommitted(ctx context.Context) *Promise[DExportRef]
|
||||
```
|
||||
|
||||
```go
|
||||
func (w IntentWatch) Poll(ctx context.Context) (IntentStatus, error)
|
||||
```
|
||||
|
||||
### IntentWatchRef
|
||||
|
||||
IntentWatchRef is the server-side handle a notifyIntent returns: the intent id
|
||||
the watch tracks. The client polls it (MsgWatchPoll) or receives a Push
|
||||
(MsgWatchPush). It is a subscription token, not authority.
|
||||
|
||||
|
||||
### LiquidityRequest
|
||||
|
||||
LiquidityRequest is the off-chain request to COMMIT (open/increase) a funded
|
||||
position. LiquidityDelta MUST be positive for a commit (it funds the position from
|
||||
C); the negative-delta (collect/decrease/cancel) path is V4CollectSession /
|
||||
openCancel, whose credit rides the DS01 settlement.
|
||||
|
||||
|
||||
### ModifyLiquidityArgs
|
||||
|
||||
ModifyLiquidityArgs are the V4 ModifyLiquidityParams tuple fields. LiquidityDelta
|
||||
is signed: >0 add, <0 remove. The session sets the sign per action (commit vs
|
||||
collect/cancel); the SIGN is what distinguishes a funding C->D commit from a
|
||||
value-returning D->C removal — both ride this one selector, exactly as the
|
||||
position facade does.
|
||||
|
||||
|
||||
### PoolKeyArgs
|
||||
|
||||
PoolKeyArgs are the V4 PoolKey tuple fields the swap selector takes:
|
||||
(currency0, currency1, fee uint24, tickSpacing int24, hooks address). For the
|
||||
native seam the pool is identified by these; the keeper/market binds them to a
|
||||
D market. dexsession fills them from the request's market mapping.
|
||||
|
||||
|
||||
### PreparedIntent
|
||||
|
||||
PreparedIntent is the OUTPUT of prepareSwapIntent: everything the user needs to
|
||||
sign a normal C tx to 0x9999 that creates the funded C->D intent. It is bytes,
|
||||
not authority:
|
||||
|
||||
- To — the 0x9999 settlement address (the precompile).
|
||||
- Calldata — selector + ABI-encoded args for the on-chain swap entry.
|
||||
- HookData — the V4 hookData the 0x9999 handler consumes (routing payload).
|
||||
- IntentID — the deterministic id the on-chain SubmitSwapIntent will mint
|
||||
(derived identically here; lets the watch locate the D result).
|
||||
- QuotedOut — the ESTIMATE used to build it (informational, not enforceable).
|
||||
|
||||
MUST NOT: reserve funds off-chain, claim a fill final, return an amountOut the
|
||||
chain trusts. The QuotedOut is explicitly informational; the enforceable floor
|
||||
is MinAmountOut baked into Calldata, which the chain/D check.
|
||||
|
||||
|
||||
### Promise
|
||||
|
||||
Promise is a future for an async DexSession call result of type T. It is safe
|
||||
for one producer (the dispatching goroutine) and many consumers (Await is
|
||||
idempotent and concurrent-safe).
|
||||
|
||||
|
||||
```go
|
||||
func (p *Promise[T]) Await(ctx context.Context) (T, error)
|
||||
```
|
||||
|
||||
### QuoteCap
|
||||
|
||||
QuoteCap permits read-only book/state queries. It can quote and observe; it
|
||||
cannot build an intent, notify, settle, or administer.
|
||||
|
||||
|
||||
```go
|
||||
func (c QuoteCap) Authority() Authority
|
||||
```
|
||||
|
||||
### QuoteRequest
|
||||
|
||||
QuoteRequest asks the D book for an estimated output. Read-only.
|
||||
|
||||
|
||||
### QuoteResult
|
||||
|
||||
QuoteResult is an ESTIMATE. AmountOut is informational only: the chain never
|
||||
trusts it. The user still encodes their own MinAmountOut in the signed C tx,
|
||||
and D enforces slippage at match. A stale or malicious quote can only mislead
|
||||
a UI — it cannot move value, and a bad quote that misses MinAmountOut makes
|
||||
the on-chain swap revert (TestZAP_StaleResponse_BadQuoteMissesMinOutOrReverts).
|
||||
|
||||
|
||||
### RoutePhase
|
||||
|
||||
RoutePhase is the lifecycle of a route as the off-chain watcher observes it. Like
|
||||
IntentPhase, NONE of these moves value; Committed means the watcher saw the ONE
|
||||
final D->C export, Refunded means it saw the ONE refund export.
|
||||
|
||||
|
||||
### RouteRequest
|
||||
|
||||
RouteRequest is the off-chain request to PREPARE a multi-hop route intent. Like a
|
||||
SwapIntentRequest it reserves no funds and returns calldata; the difference is
|
||||
Path — the ordered list of marketIDs D walks (A->B->C). AmountIn is the SINGLE
|
||||
input the user locks on C; MinAmountOut is the floor on the FINAL output, enforced
|
||||
by D at the end of the route (a route that cannot deliver MinAmountOut of the final
|
||||
asset refunds the input — it never partially settles an intermediate asset).
|
||||
|
||||
|
||||
### RouteStatus
|
||||
|
||||
RouteStatus is one poll/push of a route watch. HopIndex/HopAmountOut describe the
|
||||
CURRENT hop (orchestration ESTIMATES). Ref is the FINAL export POINTER, valid only
|
||||
when Phase==RouteCommitted (the final output) or RouteRefunded (the refund). There
|
||||
is no per-hop Ref field — by construction there is no per-hop settleable object.
|
||||
|
||||
|
||||
### RouteVenue
|
||||
|
||||
RouteVenue is the route backend a Venue MAY ALSO implement. It is a SEPARATE
|
||||
interface (composition, not interface-expansion) so the base Venue — and every
|
||||
existing implementation — stays unchanged: a server type-asserts for RouteVenue
|
||||
and serves routes only when the backend provides it. Like Venue, it has NO
|
||||
credit/settle method: it reports route progress and the final export POINTER; it
|
||||
never moves value. Exactly TWO methods — one trigger, one status — keeps it DRY.
|
||||
|
||||
|
||||
### RouteWatch
|
||||
|
||||
RouteWatch is the subscription a route NotifyCToDExport returns. Poll observes the
|
||||
current route phase + hop progress; streamUntilFinal resolves the ONE final (or
|
||||
refund) export pointer. It NEVER moves value — the terminal pointer feeds the one
|
||||
DS01 settlement, which the chain judges.
|
||||
|
||||
|
||||
```go
|
||||
func (w RouteWatch) IntentID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (w RouteWatch) Poll(ctx context.Context) (RouteStatus, error)
|
||||
```
|
||||
|
||||
### SessionConfig
|
||||
|
||||
SessionConfig configures a client session (normally produced by Bootstrap, but
|
||||
exported so a node can construct one directly against a known peer).
|
||||
|
||||
|
||||
### SettlementCap
|
||||
|
||||
SettlementCap permits asking C to import an EXISTING D->C object (build the
|
||||
finalization calldata / submit a pointing tx). It cannot credit C through RPC,
|
||||
nor substitute recipient/asset/amount — the chain binds those to the object.
|
||||
|
||||
|
||||
```go
|
||||
func (c SettlementCap) Authority() Authority
|
||||
```
|
||||
|
||||
### SettlementMode
|
||||
|
||||
SettlementMode tells the caller HOW the settlement is realised. In both modes
|
||||
the credit happens ON-CHAIN by consuming the real object — never via this RPC.
|
||||
|
||||
|
||||
### SettlementSubmitResult
|
||||
|
||||
SettlementSubmitResult is the OUTPUT of importSettlement. It NEVER credits C; it
|
||||
returns the calldata to consume the object (or the hash of a tx that points at
|
||||
it). The on-chain ImportSettlement is the sole credit path.
|
||||
|
||||
|
||||
### StateKind
|
||||
|
||||
StateKind selects which read-only view getState returns.
|
||||
|
||||
|
||||
### StateRequest
|
||||
|
||||
StateRequest is a read-only DEX state query.
|
||||
|
||||
|
||||
### StateResult
|
||||
|
||||
StateResult is the read-only answer. Like a quote it is informational: a
|
||||
balance reported here is NOT a spendable claim the chain honours — only a
|
||||
consumed D->C object credits C.
|
||||
|
||||
|
||||
### SwapIntentRequest
|
||||
|
||||
SwapIntentRequest is the off-chain request to PREPARE a C->D swap intent. The
|
||||
session validates params, estimates a quote, derives the intent id, and
|
||||
returns a PreparedIntent (calldata). It reserves NO funds and returns NO
|
||||
enforceable amountOut.
|
||||
|
||||
|
||||
### V4Action
|
||||
|
||||
V4Action is the kind of V4 action a session capability is scoped to. A scoped
|
||||
capability is confined to exactly one kind — a swap cap cannot drive a liquidity
|
||||
action and vice versa.
|
||||
|
||||
|
||||
```go
|
||||
func (a V4Action) String() string
|
||||
```
|
||||
|
||||
### V4ActionScope
|
||||
|
||||
V4ActionScope binds a session capability to ONE V4 action. Every field that
|
||||
distinguishes one action from another is here; DeriveSessionID hashes them into a
|
||||
stable id. Two sessions for different intents/pools/params/kinds derive different
|
||||
ids and their capabilities cannot be cross-used.
|
||||
|
||||
NetworkID — the Lux network the action runs on.
|
||||
CChainID — the C-Chain (EVM balance authority).
|
||||
DChainID — the D-Chain (matching authority).
|
||||
Addr9999 — the on-chain settlement authority (the precompile address).
|
||||
Account — the caller / account the action is for (bound as the object owner).
|
||||
PoolKeyHash — hash of the V4 PoolKey (which market). For a route, hash of the
|
||||
whole path (see DeriveRoutePathHash).
|
||||
ParamsHash — hash of the action params (direction+amount for a swap; tick range
|
||||
+delta for liquidity; the collected/cancelled selector for those).
|
||||
Kind — the V4Action this scope permits.
|
||||
IntentID — the deterministic intent id (for swap/route) the action targets.
|
||||
Zero for actions whose object is not an intent (a position commit
|
||||
binds by PoolKeyHash+ParamsHash+Account instead).
|
||||
|
||||
|
||||
### V4CollectSession
|
||||
|
||||
V4CollectSession is the removal lifecycle (collect/decrease/cancel). It requests the
|
||||
removal, watches for the ONE D->C export, and settles it via the DS01 credit path.
|
||||
|
||||
|
||||
```go
|
||||
func (s *V4CollectSession) Close()
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4CollectSession) IntentID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4CollectSession) OnExportReady(ctx context.Context, watch IntentWatch) *Promise[DExportRef]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4CollectSession) SessionID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4CollectSession) WriteNotify(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4CollectSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4CollectSession) WriteRequest(ctx context.Context) *Promise[PreparedIntent]
|
||||
```
|
||||
|
||||
### V4Config
|
||||
|
||||
V4Config configures the V4 precompile session. The chain identifiers and the 0x9999
|
||||
address are fixed for the deployment; they bind every action's scope.
|
||||
|
||||
|
||||
### V4Dir
|
||||
|
||||
V4Dir classifies a message by who originates it on the bidirectional plane.
|
||||
|
||||
|
||||
### V4Event
|
||||
|
||||
V4Event is the typed D->C read a session surfaces to the caller (the orchestration
|
||||
stream). It is a VALUE: a label + the orchestration payload + an OPTIONAL pointer.
|
||||
It NEVER carries authority over a balance:
|
||||
|
||||
- Type — the V4MsgType (always a DirDToC read).
|
||||
- SessionID — the action the event belongs to (binds it to its session).
|
||||
- IntentID — the originating intent (correlation).
|
||||
- EstAmount — an ESTIMATE (quote / matched-out / hop-out). NOT a credit; the
|
||||
chain never trusts it. Exactly the QuotedOut discipline, extended to matches.
|
||||
- HopIndex — the route hop this event describes (route events only).
|
||||
- Ref — the D->C export POINTER, set ONLY on *_EXPORT_READY / REFUND_READY.
|
||||
The credit still happens on-chain when the real object behind Ref is consumed.
|
||||
- Reason — human text for ERROR / HALTED / REFUND.
|
||||
|
||||
|
||||
```go
|
||||
func (e V4Event) HasRef() bool
|
||||
```
|
||||
|
||||
### V4LiquiditySession
|
||||
|
||||
V4LiquiditySession is the position-commit lifecycle. It builds modifyLiquidity
|
||||
(+delta) calldata the user signs to fund the position, notifies D, and observes the
|
||||
position opening. It has NO settlement method (a commit credits nothing).
|
||||
|
||||
|
||||
```go
|
||||
func (s *V4LiquiditySession) Close()
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4LiquiditySession) IntentID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4LiquiditySession) SessionID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4LiquiditySession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4LiquiditySession) WritePrepareCommit(ctx context.Context) *Promise[PreparedIntent]
|
||||
```
|
||||
|
||||
### V4MsgType
|
||||
|
||||
V4MsgType is a control-plane message-type. The constants below are the V4-native
|
||||
SEMANTIC names the spec mandates. Each maps to an underlying wire frame (Msg* in
|
||||
wire.go) or to a LOCAL lifecycle transition (no wire frame — e.g. *_OPEN mints a
|
||||
scoped capability client-side). A session is a state machine that emits/consumes
|
||||
these; the mapping (v4Wire) keeps the wire the single source of transport truth.
|
||||
|
||||
|
||||
```go
|
||||
func (t V4MsgType) CreditsValue() bool
|
||||
```
|
||||
|
||||
```go
|
||||
func (t V4MsgType) Dir() V4Dir
|
||||
```
|
||||
|
||||
```go
|
||||
func (t V4MsgType) String() string
|
||||
```
|
||||
|
||||
### V4PrecompileSession
|
||||
|
||||
V4PrecompileSession is the per-action session factory the spec mandates. It wraps a
|
||||
restricted DexSession (the bootstrap grant) and opens a NARROWLY-SCOPED session per
|
||||
V4 action. Each open* mints a scopedCap confined to {networkID, cChainID, dChainID,
|
||||
0x9999, account, poolKeyHash, paramsHash, intentID} for exactly that action, so a
|
||||
session cannot drive any other intent/pool/params/kind.
|
||||
|
||||
|
||||
```go
|
||||
func NewV4PrecompileSession(cfg V4Config) (*V4PrecompileSession, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (v *V4PrecompileSession) OpenCancel(req CollectRequest) (*V4CollectSession, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (v *V4PrecompileSession) OpenCollect(req CollectRequest) (*V4CollectSession, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (v *V4PrecompileSession) OpenModifyLiquidity(req LiquidityRequest) (*V4LiquiditySession, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (v *V4PrecompileSession) OpenRoute(req RouteRequest) (*V4RouteSession, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (v *V4PrecompileSession) OpenState(account Account, marketID ID) (*V4StateSession, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (v *V4PrecompileSession) OpenSwap(req SwapIntentRequest) (*V4SwapSession, error)
|
||||
```
|
||||
|
||||
### V4RouteSession
|
||||
|
||||
V4RouteSession is the multi-hop route lifecycle state machine. It prepares EXACTLY
|
||||
ONE C->D input intent (carrying the path), streams hop progress as orchestration,
|
||||
and settles the ONE final D->C export. It NEVER produces an intermediate-asset
|
||||
settlement.
|
||||
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) Close()
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) IntentID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) OnFinalExport(ctx context.Context, watch RouteWatch) *Promise[DExportRef]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) Path() []ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) ReadStream(ctx context.Context, watch RouteWatch) <-chan V4Event
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) SessionID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[RouteWatch]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4RouteSession) WritePrepareIntent(ctx context.Context) *Promise[PreparedIntent]
|
||||
```
|
||||
|
||||
### V4StateSession
|
||||
|
||||
V4StateSession is the read-only streaming session: quotes and book/state. It cannot
|
||||
build calldata, notify, or settle — it observes.
|
||||
|
||||
|
||||
```go
|
||||
func (s *V4StateSession) Close()
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4StateSession) ReadQuote(ctx context.Context, amountIn uint64, zeroForOne bool) *Promise[QuoteResult]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4StateSession) ReadState(ctx context.Context, kind StateKind, account Account, asset ID) *Promise[StateResult]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4StateSession) SessionID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4StateSession) StreamQuotes(ctx context.Context, amountIn uint64, zeroForOne bool) <-chan V4Event
|
||||
```
|
||||
|
||||
### V4SwapSession
|
||||
|
||||
V4SwapSession is the single-swap lifecycle state machine. It exposes the
|
||||
bidirectional message set as typed methods that compose the clientSession ops and
|
||||
pipeline via Promise. It holds a scopedCap confined to ONE swap.
|
||||
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) Close()
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) IntentID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) OnExportReady(ctx context.Context, watch IntentWatch) *Promise[DExportRef]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) ReadStream(ctx context.Context, watch IntentWatch) <-chan V4Event
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) Run(ctx context.Context) FlowStages
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) SessionID() ID
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) WriteNotifyCToDExport(ctx context.Context, intent *Promise[PreparedIntent]) *Promise[IntentWatch]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) WritePrepareCSettlement(ctx context.Context, ref *Promise[DExportRef]) *Promise[SettlementSubmitResult]
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *V4SwapSession) WritePrepareIntent(ctx context.Context) *Promise[PreparedIntent]
|
||||
```
|
||||
|
||||
### Venue
|
||||
|
||||
Venue is the read + trigger backend a DexSession server delegates to. It is the
|
||||
D-Chain CLOB venue and a C/D state reader. CRITICALLY, it has no credit/settle
|
||||
method: the orchestration server cannot move value because its backend cannot
|
||||
either. (A real deployment backs Venue with the lux/dex ZAP CLOB client over the
|
||||
existing clob_* transport for quotes, plus a chain-state reader for exports.)
|
||||
|
||||
|
||||
### WatchCap
|
||||
|
||||
WatchCap permits subscribing to a notified intent's D result. It cannot move
|
||||
value; OnCommitted yields a POINTER (DExportRef), not a credit.
|
||||
|
||||
|
||||
```go
|
||||
func (c WatchCap) Authority() Authority
|
||||
```
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
---
|
||||
title: "forward"
|
||||
description: "Package forward is the canonical HIP-0110 'HTTP-over-ZAP' contract."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/forward"
|
||||
```
|
||||
|
||||
Package forward is the canonical HIP-0110 "HTTP-over-ZAP" contract.
|
||||
|
||||
It is the single source of truth for the gateway↔backend boundary,
|
||||
replacing the two inconsistent ad-hoc encodings that previously lived
|
||||
in the gateway repo (zap_wire.go and zap_backend.go). Three envelopes
|
||||
cover the entire boundary:
|
||||
|
||||
Forward — gateway → backend, one per inbound HTTP request, carrying
|
||||
the edge-validated identity (so the backend trusts the edge).
|
||||
Response — backend → gateway, the buffered HTTP response for a Forward.
|
||||
Push — backend → gateway, one per server-initiated stream frame
|
||||
(SSE / WebSocket), correlated to a Forward by ConnID.
|
||||
|
||||
On-wire bytes are github.com/luxfi/zap Objects. Every field is packed
|
||||
at an explicit byte offset within the object's fixed payload.
|
||||
|
||||
# Offset discipline
|
||||
|
||||
luxfi/zap's ObjectBuilder.SetBytes / SetText write an 8-byte slot at
|
||||
the field offset: {relOffset uint32, length uint32}. The variable-
|
||||
length data tail is appended after the fixed section in Finish(), and
|
||||
the relOffset is patched to point at it. Consequently EVERY text or
|
||||
bytes field consumes 8 bytes of fixed payload — adjacent text fields
|
||||
MUST be spaced 8 bytes apart or their slots overlap and corrupt each
|
||||
other (proven empirically: a 4-byte-spaced layout drops the body).
|
||||
|
||||
Fixed scalars use their natural width (bool=1, uint32=4, int64=8) and
|
||||
are placed on a natural boundary. These offsets are the contract;
|
||||
changing one is a wire break.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### BuildForward
|
||||
|
||||
```go
|
||||
func BuildForward(f Forward) (*zaplib.Message, error)
|
||||
```
|
||||
|
||||
BuildForward serializes f into a ZAP message tagged MsgTypeForward.
|
||||
|
||||
|
||||
### BuildPush
|
||||
|
||||
```go
|
||||
func BuildPush(p Push) (*zaplib.Message, error)
|
||||
```
|
||||
|
||||
BuildPush serializes p into a ZAP message tagged MsgTypePush.
|
||||
|
||||
|
||||
### BuildResponse
|
||||
|
||||
```go
|
||||
func BuildResponse(r Response) (*zaplib.Message, error)
|
||||
```
|
||||
|
||||
BuildResponse serializes r into a ZAP message. Status rides as a
|
||||
uint32; body and headers-JSON follow, matching the Response read order.
|
||||
|
||||
|
||||
### HandleReversePush
|
||||
|
||||
```go
|
||||
func HandleReversePush(ctx context.Context, from string, msg *zaplib.Message) (*zaplib.Message, error)
|
||||
```
|
||||
|
||||
HandleReversePush is the zaplib.Handler for MsgTypePush. It decodes the
|
||||
Push, looks up the sink for its ConnID, and delivers. An unknown ConnID
|
||||
(client already gone) is a silent no-op. A delivery error drops the sink.
|
||||
|
||||
|
||||
### RegisterReversePush
|
||||
|
||||
```go
|
||||
func RegisterReversePush(connID string, sink PushSink)
|
||||
```
|
||||
|
||||
RegisterReversePush records the sink for an active SSE / WS subscription.
|
||||
|
||||
|
||||
### RegisterReversePushHandler
|
||||
|
||||
```go
|
||||
func RegisterReversePushHandler(node *zaplib.Node)
|
||||
```
|
||||
|
||||
RegisterReversePushHandler installs HandleReversePush on node so a
|
||||
Forwarder's node receives backend-initiated Push frames.
|
||||
|
||||
|
||||
### Relay
|
||||
|
||||
```go
|
||||
func Relay(node *zaplib.Node, gate Gate, pick PeerPicker)
|
||||
```
|
||||
|
||||
Relay registers the gateway relay handler on node for MsgTypeForward.
|
||||
|
||||
On each inbound Forward the relay:
|
||||
|
||||
1. ReadForward(msg) — decodes the envelope (Body stays a sub-slice;
|
||||
it is not decoded).
|
||||
2. gate(ctx, &f) — runs auth + the balance gate on the envelope. A
|
||||
non-nil deny Response short-circuits: the relay returns it
|
||||
immediately via BuildResponse and NEVER touches a backend. A
|
||||
non-nil error is returned to the ZAP layer. Otherwise the gate has
|
||||
mutated f's identity fields in place and the request is allowed.
|
||||
3. BuildForward(f) — re-emits the Forward carrying the injected
|
||||
identity (the single per-request envelope re-encode; see the
|
||||
budget note above), then node.Call(ctx, pick(f.Path), augmented)
|
||||
forwards it to the chosen backend.
|
||||
4. returns the backend's reply *zaplib.Message VERBATIM — no
|
||||
ReadResponse/BuildResponse round-trip, the response bytes pass
|
||||
through unchanged.
|
||||
|
||||
|
||||
### Serve
|
||||
|
||||
```go
|
||||
func Serve(node *zaplib.Node, h http.Handler)
|
||||
```
|
||||
|
||||
Serve registers the canonical MsgTypeForward handler on node, dispatching
|
||||
each Forward envelope to h. The handler reconstructs the *http.Request,
|
||||
injects the edge-validated identity as X-* headers (so the backend trusts
|
||||
the edge), serves it through h, and returns a Response envelope carrying
|
||||
the captured status, headers, and body.
|
||||
|
||||
Streaming (SSE / chunked) is not yet emitted as Push frames: the full
|
||||
response is buffered and returned as one Response. The extension point is
|
||||
marked below.
|
||||
|
||||
|
||||
### UnregisterReversePush
|
||||
|
||||
```go
|
||||
func UnregisterReversePush(connID string)
|
||||
```
|
||||
|
||||
UnregisterReversePush drops the sink when the client connection closes.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Forward
|
||||
|
||||
Forward is the typed view of the gateway→backend envelope. Path
|
||||
includes the raw query string (there is no separate query field).
|
||||
|
||||
|
||||
```go
|
||||
func ReadForward(msg *zaplib.Message) Forward
|
||||
```
|
||||
|
||||
### Forwarder
|
||||
|
||||
Forwarder is the gateway-side client: it tunnels an *http.Request to a
|
||||
backend Peer over ZAP and returns the *http.Response. It is an
|
||||
http.RoundTripper, so it drops into any http.Client/transport chain.
|
||||
|
||||
|
||||
```go
|
||||
func (f *Forwarder) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
```
|
||||
|
||||
### Gate
|
||||
|
||||
Gate is the gateway-supplied policy run on each inbound Forward
|
||||
ENVELOPE. It reads f.Headers (to find and validate the JWT) and
|
||||
f.Path, runs the prepaid balance gate, and EITHER:
|
||||
|
||||
- mutates f's identity fields in place — f.TenantID, f.UserID,
|
||||
f.IsAdmin, f.Permissions — and returns (nil, nil) to ALLOW, or
|
||||
- returns a non-nil *Response to short-circuit (deny), e.g.
|
||||
{Status: 401} for a bad token or {Status: 402, Body:
|
||||
insufficient_balance JSON} when the balance gate trips.
|
||||
|
||||
A Gate MUST NOT read or copy f.Body. It operates on the envelope only.
|
||||
If it returns a non-nil error the relay surfaces it to the ZAP layer
|
||||
(the caller's Call fails); use a deny *Response for ordinary policy
|
||||
rejections so the client sees a clean HTTP status.
|
||||
|
||||
|
||||
### PeerPicker
|
||||
|
||||
PeerPicker chooses the backend node for a request path. It mirrors
|
||||
pickBackend from the gateway: /v1/base/* routes to the base peer,
|
||||
everything else routes to the cloud peer (which fans out to the
|
||||
per-subsystem services). It is given the Forward's Path (raw query
|
||||
included) and returns the backend NodeID to Call.
|
||||
|
||||
|
||||
### Push
|
||||
|
||||
Push is the typed view of the backend→gateway reverse-push envelope.
|
||||
|
||||
|
||||
```go
|
||||
func ReadPush(msg *zaplib.Message) Push
|
||||
```
|
||||
|
||||
### PushSink
|
||||
|
||||
PushSink delivers a Push frame to a live client connection.
|
||||
|
||||
|
||||
### Response
|
||||
|
||||
Response is the typed view of the backend→gateway response envelope.
|
||||
|
||||
|
||||
```go
|
||||
func ReadResponse(msg *zaplib.Message) Response
|
||||
```
|
||||
|
||||
@@ -0,0 +1,540 @@
|
||||
---
|
||||
title: "handshake"
|
||||
description: "Package handshake implements SPEC-ZAP-PQ-v1: the native post-quantum handshake and AEAD framing for ZAP."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/handshake"
|
||||
```
|
||||
|
||||
Package handshake implements SPEC-ZAP-PQ-v1: the native post-quantum
|
||||
handshake and AEAD framing for ZAP. See docs/SPEC-ZAP-PQ-v1.md for the
|
||||
authoritative wire specification.
|
||||
|
||||
The package decomposes the protocol along value/behaviour lines:
|
||||
|
||||
- Identity, Profile, SuiteID : values (data, no behaviour)
|
||||
- Transcript, SessionKeys : derived values
|
||||
- Frame{Hello, KEMInit, ...} : wire codecs
|
||||
- Initiator, Responder : state machines
|
||||
- Session : the post-handshake AEAD stream
|
||||
- ReplayCache, PSKStore : independent storage policies
|
||||
|
||||
Nothing in this package imports net.Conn — it works against any
|
||||
io.ReadWriter so it can be exercised over in-memory pipes in tests.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### PSKID
|
||||
|
||||
```go
|
||||
func PSKID(psk [PSKKeyLen]byte) [PSKIDLen]byte
|
||||
```
|
||||
|
||||
PSKID derives the §12.1 psk_id (16-byte truncation of SHA3-256 of
|
||||
the resumption_psk). Pinned in code so issuance and lookup agree.
|
||||
|
||||
|
||||
### Ratchet
|
||||
|
||||
```go
|
||||
func Ratchet(kPrev [AEADKeyLen]byte, epoch uint8) (kNext [AEADKeyLen]byte, saltNext [NonceSaltLen]byte)
|
||||
```
|
||||
|
||||
Ratchet implements §13 — derive the next per-direction key and
|
||||
nonce salt from the current key.
|
||||
|
||||
info_key = LBL_REKEY ∥ 0x00 ∥ epoch_n ∥ 0x00
|
||||
info_salt = LBL_REKEY ∥ 0x00 ∥ epoch_n ∥ 0x01
|
||||
k_{n+1} = HKDF-Expand(k_n, info_key, 32)
|
||||
salt_{n+1} = HKDF-Expand(k_n, info_salt, 4)
|
||||
|
||||
Two distinct Expand calls (not a single 36-byte read) — the
|
||||
info bytes differ so the output streams are independent.
|
||||
|
||||
The caller is responsible for zeroising the old k_n / salt_n.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### AlertCode
|
||||
|
||||
AlertCode is the §14 error code carried in ALERT frame bodies.
|
||||
Each code maps 1:1 to a sentinel error so callers can branch with
|
||||
errors.Is on the named sentinel rather than parsing the byte.
|
||||
|
||||
|
||||
```go
|
||||
func (c AlertCode) String() string
|
||||
```
|
||||
|
||||
### AlertFrame
|
||||
|
||||
```go
|
||||
func DecodeAlert(body []byte) (*AlertFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (a *AlertFrame) Encode() []byte
|
||||
```
|
||||
|
||||
### AuthFrame
|
||||
|
||||
```go
|
||||
func DecodeAuth(body []byte) (*AuthFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (a *AuthFrame) Encode() ([]byte, error)
|
||||
```
|
||||
|
||||
### AuthRole
|
||||
|
||||
AuthRole is the §6.4 role byte signed by each side.
|
||||
|
||||
|
||||
```go
|
||||
func (r AuthRole) Label() []byte
|
||||
```
|
||||
|
||||
### ClientPSK
|
||||
|
||||
ClientPSK is the value the initiator caches after a successful full
|
||||
handshake. It is the §12.1 record minus the server-side state.
|
||||
|
||||
PeerID is the verified responder identity from the ORIGINAL full
|
||||
handshake. The resumed handshake re-derives session keys but does
|
||||
NOT re-verify the responder's static_pk (possession of the PSK is
|
||||
the authentication, §12.2), so the trust anchor must be carried
|
||||
forward from when the responder's signature was last checked.
|
||||
|
||||
Callers reading [Session.PeerID] after a resumed handshake see this
|
||||
value, ensuring authorization decisions remain anchored to the
|
||||
identity that the initiator originally pinned.
|
||||
|
||||
Fields are exported ONLY to support persistence / serialization
|
||||
(KMS round-trip, sticky-session cache). Do NOT construct ClientPSK
|
||||
literals by hand — populating PeerID with a value that wasn't
|
||||
verified during the original handshake silently corrupts the
|
||||
resumed Session.PeerID() return. Always go through MakeClientPSK
|
||||
or copy a struct returned by Session.ResumptionPSK().
|
||||
|
||||
|
||||
```go
|
||||
func MakeClientPSK(psk [PSKKeyLen]byte, peerID [IDLen]byte, now time.Time) ClientPSK
|
||||
```
|
||||
|
||||
### DataFrame
|
||||
|
||||
```go
|
||||
func DecodeData(body []byte) (*DataFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (d *DataFrame) Encode() []byte
|
||||
```
|
||||
|
||||
### FrameType
|
||||
|
||||
FrameType encodes the outer envelope type byte (§5, §6).
|
||||
|
||||
|
||||
### HelloFrame
|
||||
|
||||
```go
|
||||
func DecodeHello(body []byte) (*HelloFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (h *HelloFrame) Encode() ([]byte, error)
|
||||
```
|
||||
|
||||
### HelloPSKFrame
|
||||
|
||||
```go
|
||||
func DecodeHelloPSK(body []byte) (*HelloPSKFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (h *HelloPSKFrame) Encode() []byte
|
||||
```
|
||||
|
||||
### Identity
|
||||
|
||||
Identity holds a node's or VM's static ML-DSA-65 keypair.
|
||||
|
||||
The PrivateKey field is non-nil for our own identity; for a pinned
|
||||
peer identity (Initiator.Expected, Responder.Local), PrivateKey is
|
||||
nil and only PublicKey is consulted.
|
||||
|
||||
|
||||
```go
|
||||
func GenerateIdentity() (*Identity, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func GenerateIdentityFrom(r io.Reader) (*Identity, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func IdentityFromPrivate(priv *mldsa.PrivateKey) (*Identity, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func IdentityFromPublicBytes(pub []byte) (*Identity, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (id *Identity) ID() [IDLen]byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (id *Identity) PublicBytes() []byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (id *Identity) Sign(rand io.Reader, h2 [TranscriptLen]byte, role AuthRole, suite SuiteID) ([]byte, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (id *Identity) SignDeterministic(h2 [TranscriptLen]byte, role AuthRole, suite SuiteID) ([]byte, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (id *Identity) VerifyAuth(
|
||||
h2 [TranscriptLen]byte,
|
||||
role AuthRole,
|
||||
suite SuiteID,
|
||||
sig []byte,
|
||||
) error
|
||||
```
|
||||
|
||||
### Initiator
|
||||
|
||||
Initiator runs the §4 client side of the handshake.
|
||||
|
||||
Required fields:
|
||||
|
||||
- Local: this side's static ML-DSA-65 identity (must have a
|
||||
private key).
|
||||
|
||||
Optional fields:
|
||||
|
||||
- Expected: pin the responder's identity. If non-nil, the
|
||||
handshake aborts with ErrVMIdentityMismatch when
|
||||
SHA3-256(responder_static_pk) ≠ Expected.ID().
|
||||
- Profile: chain-security stance. Affects only what the wrapper
|
||||
does on magic-prefix mismatch; once the handshake is engaged
|
||||
the profile is enforced through PQMode + OfferedSchemes.
|
||||
- PQMode: HELLO.pq_mode byte. Defaults to PQModePQOnly under
|
||||
StrictPQ / FIPS, PQModeClassicalPermitted otherwise.
|
||||
- Suite: ciphersuite byte. Defaults to SuiteX25519MLKEM (0x01).
|
||||
- OfferedSchemes: HELLO.offered_schemes. Defaults to [Suite].
|
||||
- Resume: cached PSK to attempt resumption with. If nil or
|
||||
expired, Initiator runs a full handshake.
|
||||
- Rand: entropy source for ephemerals + signing nonces. Defaults
|
||||
to crypto/rand.Reader. KAT tests inject a deterministic reader.
|
||||
- Now: clock for HELLO timestamps. Defaults to time.Now.
|
||||
|
||||
|
||||
```go
|
||||
func (i *Initiator) Run(conn io.ReadWriter) (*Session, error)
|
||||
```
|
||||
|
||||
### KEMInitFrame
|
||||
|
||||
```go
|
||||
func DecodeKEMInit(body []byte) (*KEMInitFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (k *KEMInitFrame) Encode() []byte
|
||||
```
|
||||
|
||||
### KEMReplyFrame
|
||||
|
||||
```go
|
||||
func DecodeKEMReply(body []byte) (*KEMReplyFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (k *KEMReplyFrame) Encode() ([]byte, error)
|
||||
```
|
||||
|
||||
### PQMode
|
||||
|
||||
PQMode encodes HELLO.pq_mode (§6.1).
|
||||
|
||||
|
||||
### PSKStore
|
||||
|
||||
PSKStore implements §12 PSK issuance and lookup on the responder.
|
||||
|
||||
- Issue records a fresh (psk_id, resumption_psk, client_id) at the
|
||||
end of a full handshake.
|
||||
- Redeem looks up a psk_id presented in HELLO_PSK; on hit it
|
||||
atomically marks the entry consumed (single-use, §12.2).
|
||||
|
||||
PSKs expire after PSKLifetimeSec. Expired or unknown lookups
|
||||
return (nil, false) and the caller MUST send ALERT 0x08.
|
||||
|
||||
An ABSENT store (PSKStore == nil) disables resumption. The
|
||||
responder treats every HELLO_PSK as ErrPSKUnknown.
|
||||
|
||||
|
||||
```go
|
||||
func NewPSKStore() *PSKStore
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *PSKStore) Issue(psk [PSKKeyLen]byte, clientID [IDLen]byte) [PSKIDLen]byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *PSKStore) Len() int
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *PSKStore) Redeem(id [PSKIDLen]byte) (psk [PSKKeyLen]byte, clientID [IDLen]byte, ok bool)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *PSKStore) Sweep()
|
||||
```
|
||||
|
||||
### Profile
|
||||
|
||||
Profile is the chain-security stance applied at the wire boundary
|
||||
(§6.0). It is intentionally local — callers map their richer
|
||||
notions (lux/pq.Mode, ChainConfig) onto this small enum.
|
||||
|
||||
|
||||
### RekeyFrame
|
||||
|
||||
```go
|
||||
func DecodeRekey(body []byte) (*RekeyFrame, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (r *RekeyFrame) Encode() []byte
|
||||
```
|
||||
|
||||
### ReplayCache
|
||||
|
||||
ReplayCache holds the §11 nonce-cache state for a Responder.
|
||||
|
||||
Two independent gates protect against replay:
|
||||
|
||||
1. Timestamp window: |now - timestamp_ns| ≤ 30s
|
||||
2. Nonce cache: dedup on (client_id, client_random)
|
||||
|
||||
Implementation: a two-generation map rotated every TTL window.
|
||||
New entries land in `active`; on insert we also probe `frozen`
|
||||
(the previous generation, still inside its TTL). When `now -
|
||||
frozenAt >= ttl`, `frozen` is dropped wholesale and the current
|
||||
`active` becomes the new `frozen`. This is O(1) per insert and
|
||||
O(1) per generation-flip (just rebind the map pointers). The
|
||||
worst-case admission window is between ttl and 2×ttl — any
|
||||
(id, rand) tuple is remembered for AT LEAST ttl seconds after
|
||||
its first appearance, which is what §11 requires.
|
||||
|
||||
Memory bound: 2× maxLen entries (one ttl-window of each
|
||||
generation). At the §3 design budget of 2^20 entries per
|
||||
generation, that's ~6 MiB of `(clientID, clientRandom)` tuples in
|
||||
memory — within the 4 MiB Cuckoo target order-of-magnitude, with
|
||||
the operational advantage of O(1) flush instead of O(N) sweep.
|
||||
|
||||
The previous implementation (single map + inline sweep at
|
||||
maxLen) was correct but degraded to O(N) per insert once the cap
|
||||
was reached, which a fuzzer or attacker can drive into the slow
|
||||
path. The two-generation rotation eliminates that.
|
||||
|
||||
|
||||
```go
|
||||
func NewReplayCache() *ReplayCache
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *ReplayCache) CheckTimestamp(timestampNS uint64) error
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *ReplayCache) Len() int
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *ReplayCache) SeenOrAdd(clientID [IDLen]byte, clientRandom [ClientRandLen]byte) bool
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *ReplayCache) Sweep()
|
||||
```
|
||||
|
||||
### Responder
|
||||
|
||||
Responder runs the §4 server side of the handshake.
|
||||
|
||||
Required fields:
|
||||
|
||||
- Local: server's static ML-DSA-65 identity (must have a
|
||||
private key).
|
||||
|
||||
Optional fields:
|
||||
|
||||
- Profile: chain-security stance. Under StrictPQ / FIPS the
|
||||
responder refuses HELLOs that advertise PQModeClassicalPermitted
|
||||
or offered_schemes lists containing non-PQ suites.
|
||||
- AcceptedSuites: server-side ciphersuite allowlist. Empty means
|
||||
{SuiteX25519MLKEM}.
|
||||
- ReplayCache: §11 replay state. nil disables cache lookups
|
||||
(timestamp-only protection — production must supply a cache).
|
||||
- PSKStore: §12 PSK issuer + redeemer. nil disables resumption.
|
||||
|
||||
Rand / Now: deterministic overrides for KAT testing.
|
||||
|
||||
|
||||
```go
|
||||
func (rs *Responder) Run(conn io.ReadWriter) (*Session, error)
|
||||
```
|
||||
|
||||
### Session
|
||||
|
||||
Session is the post-handshake AEAD-keyed stream specified by §9, §13.
|
||||
|
||||
Send → produces one DATA frame on the wire.
|
||||
Recv → consumes one DATA (or REKEY) frame and returns the
|
||||
|
||||
plaintext payload of a DATA frame.
|
||||
|
||||
Send and Recv are independently safe to call concurrently against
|
||||
the same Session, each under their own mutex.
|
||||
|
||||
A Session is NOT net.Conn directly — the package-level conn_pq.go
|
||||
adapter wraps it with Read/Write semantics for legacy callers.
|
||||
|
||||
|
||||
```go
|
||||
func (s *Session) Close() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Session) Epoch() uint8
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Session) PeerID() [IDLen]byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Session) Recv() ([]byte, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Session) Rekey() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Session) ResumptionPSK() *ClientPSK
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Session) Role() AuthRole
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Session) Send(payload []byte) error
|
||||
```
|
||||
|
||||
### SessionKeys
|
||||
|
||||
SessionKeys is §8.3's five-expand output — the post-handshake
|
||||
secrets every Session is keyed from.
|
||||
|
||||
|
||||
```go
|
||||
func DeriveResumed(
|
||||
h2psk [TranscriptLen]byte,
|
||||
x25519Shared [X25519SharedLen]byte,
|
||||
resumptionPSK [PSKKeyLen]byte,
|
||||
) SessionKeys
|
||||
```
|
||||
|
||||
```go
|
||||
func DeriveSession(
|
||||
h2 [TranscriptLen]byte,
|
||||
x25519Shared [X25519SharedLen]byte,
|
||||
mlkemShared [MLKEM768SharedLen]byte,
|
||||
) SessionKeys
|
||||
```
|
||||
|
||||
```go
|
||||
func (k *SessionKeys) Zeroize()
|
||||
```
|
||||
|
||||
### SuiteID
|
||||
|
||||
§3 / §3.2 ciphersuite registry. Only 0x01 is wire-callable today.
|
||||
|
||||
|
||||
```go
|
||||
func (s SuiteID) IsValid() bool
|
||||
```
|
||||
|
||||
### Transcript
|
||||
|
||||
Transcript chains SHA3-256 over every handshake byte (§7).
|
||||
|
||||
The state machine:
|
||||
|
||||
NewTranscript(suite)
|
||||
AbsorbHello(helloBody) -> commits H_0
|
||||
AbsorbKEM(initBody, replyBody) -> commits H_1
|
||||
FinishFull(pkI, pkR, schemes) -> returns H_2 (full handshake)
|
||||
-- OR --
|
||||
FinishPSK(serverEphX25519Pub) -> returns H_2_psk (resumed handshake)
|
||||
|
||||
Each step replaces the internal SHA3-256 state with the digest of
|
||||
the previous chain ∥ new material. This matches the spec's
|
||||
definition of H_n as `SHA3-256(H_{n-1} ∥ <new bytes>)`.
|
||||
|
||||
The encoded body of each frame is whatever is between the outer
|
||||
type/length fields — i.e. the slice the codec returns / consumes.
|
||||
Callers MUST feed exactly those bytes (not the outer envelope) so
|
||||
both sides agree on the transcript without re-running the codec.
|
||||
|
||||
|
||||
```go
|
||||
func NewTranscript(suite SuiteID) *Transcript
|
||||
```
|
||||
|
||||
```go
|
||||
func (t *Transcript) AbsorbHello(hello []byte)
|
||||
```
|
||||
|
||||
```go
|
||||
func (t *Transcript) AbsorbKEM(init, reply []byte)
|
||||
```
|
||||
|
||||
```go
|
||||
func (t *Transcript) FinishFull(pkI, pkR []byte, schemes []SuiteID) [TranscriptLen]byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (t *Transcript) FinishPSK(serverEphX25519Pub []byte) [TranscriptLen]byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (t *Transcript) H0() [TranscriptLen]byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (t *Transcript) H1() [TranscriptLen]byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (t *Transcript) H2() [TranscriptLen]byte
|
||||
```
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: "mcp"
|
||||
description: "Package mcp provides a bridge between MCP (Model Context Protocol) servers and ZAP for high-performance tool calling."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/mcp"
|
||||
```
|
||||
|
||||
Package mcp provides a bridge between MCP (Model Context Protocol) servers
|
||||
and ZAP for high-performance tool calling.
|
||||
|
||||
This bridge auto-discovers MCP servers and exposes their capabilities via ZAP,
|
||||
providing 10-30x performance improvement over native MCP JSON-RPC.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Bridge
|
||||
|
||||
Bridge manages multiple MCP servers and exposes them via ZAP
|
||||
|
||||
|
||||
```go
|
||||
func NewBridge(node *zap.Node) *Bridge
|
||||
```
|
||||
|
||||
```go
|
||||
func (b *Bridge) AddServer(name, command string, args ...string) error
|
||||
```
|
||||
|
||||
```go
|
||||
func (b *Bridge) AddServerConfig(cfg ServerConfig) error
|
||||
```
|
||||
|
||||
```go
|
||||
func (b *Bridge) CallTool(ctx context.Context, toolID uint32, args map[string]interface{}) (interface{}, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (b *Bridge) CallToolByName(ctx context.Context, name string, args map[string]interface{}) (interface{}, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (b *Bridge) GetTools() []Tool
|
||||
```
|
||||
|
||||
```go
|
||||
func (b *Bridge) Stop()
|
||||
```
|
||||
|
||||
### MCPServer
|
||||
|
||||
MCPServer represents a connection to an MCP server
|
||||
|
||||
|
||||
```go
|
||||
func (s *MCPServer) CallTool(name string, args map[string]interface{}) (interface{}, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *MCPServer) ListTools() ([]Tool, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *MCPServer) Start() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *MCPServer) Stop()
|
||||
```
|
||||
|
||||
### ServerConfig
|
||||
|
||||
ServerConfig configures an MCP server
|
||||
|
||||
|
||||
### Tool
|
||||
|
||||
Tool represents an MCP tool capability
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "zapmdns"
|
||||
description: "Package zapmdns publishes and browses _hanzo._tcp.local."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/mdns/go"
|
||||
```
|
||||
|
||||
Package zapmdns publishes and browses _hanzo._tcp.local. services per
|
||||
HIP-0069. Single source of truth for the service-type, TXT key list and
|
||||
role enum across every Go service in the Hanzo stack (kms, mpc, base,
|
||||
ingress, gateway, static).
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### PublishOptions
|
||||
|
||||
PublishOptions captures the TXT keys defined by HIP-0069.
|
||||
|
||||
|
||||
### Publisher
|
||||
|
||||
Publisher retracts the announcement on Close.
|
||||
|
||||
|
||||
```go
|
||||
func Publish(opts PublishOptions) (*Publisher, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (p *Publisher) Close() error
|
||||
```
|
||||
|
||||
### Service
|
||||
|
||||
Service is one peer found on the LAN.
|
||||
|
||||
|
||||
```go
|
||||
func Browse(ctx context.Context, role string, timeout time.Duration) ([]Service, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s Service) URL() string
|
||||
```
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["go"]}
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["bench","dexsession","forward","handshake","mcp","mdns","quic","tools","transport","v1","zapclient","zapweb"]}
|
||||
@@ -0,0 +1,279 @@
|
||||
---
|
||||
title: "quic"
|
||||
description: "Package quic implements the QUIC transport for the ZAP messaging substrate."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/quic"
|
||||
```
|
||||
|
||||
Package quic implements the QUIC transport for the ZAP messaging substrate.
|
||||
|
||||
QUIC offers, compared to ZAP's existing TCP+TLS transport:
|
||||
|
||||
- Stream multiplexing: N concurrent RPCs over one connection without
|
||||
head-of-line blocking.
|
||||
- Connection migration: the connection survives client-IP changes
|
||||
(Wi-Fi-to-LTE, NAT rebinding, etc.) because the QUIC connection ID
|
||||
is independent of the 5-tuple.
|
||||
- 0-RTT resumption: cached session tickets let the second handshake to
|
||||
a peer complete without a round trip before app data flows.
|
||||
- TLS 1.3 with the X25519MLKEM768 hybrid post-quantum key exchange
|
||||
(IANA NamedGroup 0x11ec) baked in.
|
||||
|
||||
# Wire format
|
||||
|
||||
One ZAP message is one length-prefixed Cap'n Proto frame on a QUIC stream.
|
||||
The frame format is identical to the TCP transport:
|
||||
|
||||
[4-byte little-endian length][ZAP message bytes]
|
||||
|
||||
Two stream patterns are used:
|
||||
|
||||
- Bidirectional streams carry one request/response exchange. After the
|
||||
response is written the server side closes its half of the stream and
|
||||
the client closes its half on Recv, freeing the stream ID. This maps
|
||||
onto Node.Call.
|
||||
- Unidirectional streams from the server carry one-way notifications
|
||||
and subscription deliveries. The receiver routes each frame through
|
||||
the same handler dispatch as the TCP path.
|
||||
|
||||
The control stream (the first bidirectional stream opened by the dialer)
|
||||
carries the 64-byte ZAP node-identity handshake exchanged before any RPC.
|
||||
|
||||
# Cryptography
|
||||
|
||||
The default TLS 1.3 configuration prefers X25519MLKEM768. That is the
|
||||
IANA-registered hybrid: the shared secret is HKDF-Extract of X25519_ss
|
||||
concatenated with ML-KEM-768 ciphertext-derived ss. The Go runtime
|
||||
performs the combination internally — this package does not roll its
|
||||
own KEM combiner. See ../papers/pq-hybrid-kem/main.pdf.
|
||||
|
||||
The server certificate signature is still classical (ECDSA or RSA);
|
||||
post-quantum signature schemes are not yet wired into Go's stdlib TLS.
|
||||
The threat model that motivates X25519MLKEM768 is harvest-now /
|
||||
decrypt-later against the confidentiality of the key-exchange, not
|
||||
forgery against a trusted-today certificate authority.
|
||||
|
||||
# 0-RTT replay
|
||||
|
||||
QUIC permits 0-RTT application data, which can be replayed by a network
|
||||
adversary. The server defaults to accepting 0-RTT data, but application
|
||||
handlers MUST treat 0-RTT-carried RPCs as either idempotent or rejected.
|
||||
Use ServerConfig.RejectEarlyData to force a full 1-RTT handshake for
|
||||
every connection.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### GenerateSelfSignedCert
|
||||
|
||||
```go
|
||||
func GenerateSelfSignedCert(hosts ...string) (tls.Certificate, error)
|
||||
```
|
||||
|
||||
GenerateSelfSignedCert generates a short-lived self-signed ECDSA-P256
|
||||
certificate suitable for testing. NOT for production use: production
|
||||
deployments must supply real certificates via tls.Config.Certificates
|
||||
or GetCertificate, signed by a trusted CA.
|
||||
|
||||
Exposed as a public helper because tests in other packages may want
|
||||
to spin up a real ZAP-QUIC server.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Client
|
||||
|
||||
Client is a QUIC dialer with optional connection migration and
|
||||
0-RTT support.
|
||||
|
||||
One Client may be shared across many goroutines and used to Dial
|
||||
multiple peers concurrently. The Client owns one *quic.Transport
|
||||
which is bound to a single UDP socket — every Dial uses the same
|
||||
local socket, so a connection migration of the local side will
|
||||
affect all connections owned by this Client (this matches QUIC
|
||||
semantics).
|
||||
|
||||
|
||||
```go
|
||||
func NewClient(cfg ClientConfig) (*Client, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Close() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Dial(ctx context.Context, addr string) (*Conn, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) DialEarly(ctx context.Context, addr string) (*Conn, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) LocalAddr() net.Addr
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Transport() *quicgo.Transport
|
||||
```
|
||||
|
||||
### ClientConfig
|
||||
|
||||
ClientConfig configures a QUIC dialer.
|
||||
|
||||
|
||||
### Config
|
||||
|
||||
Config is the user-facing knob that the zap parent package's
|
||||
NodeConfig.QUICConfig accepts. It's a thin wrapper so callers
|
||||
don't have to import quic-go just to flip RejectEarlyData; they
|
||||
can supply nil for QUIC if defaults are fine.
|
||||
|
||||
Typical use:
|
||||
|
||||
cfg := zap.NodeConfig{
|
||||
NodeID: "node-a",
|
||||
Port: 9999,
|
||||
TLS: tlsCfg,
|
||||
Transport: zap.TransportQUIC,
|
||||
QUICConfig: &quic.Config{RejectEarlyData: true},
|
||||
}
|
||||
|
||||
|
||||
### Conn
|
||||
|
||||
Conn is a multiplexed ZAP connection over QUIC.
|
||||
|
||||
Conn is the transport-level connection abstraction shared between the
|
||||
dialer (client.go) and the listener (server.go). It exposes the same
|
||||
shape as ZAP's TCP *Conn (Send, Recv, Close, peer identity) and adds
|
||||
stream-level primitives that the underlying TCP transport cannot
|
||||
efficiently express.
|
||||
|
||||
Conn is safe for concurrent use. Send serializes onto the control
|
||||
stream; concurrent callers may instead OpenStream to get independent
|
||||
per-RPC streams.
|
||||
|
||||
|
||||
```go
|
||||
func (c *Conn) AcceptStream(ctx context.Context) (*Stream, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) AcceptUniStream(ctx context.Context) (*UniReceiveStream, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) Close() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) ConnectionState() tls.ConnectionState
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) Context() context.Context
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) IsZeroRTT() bool
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) OpenStream(ctx context.Context) (*Stream, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) OpenUniStream(ctx context.Context) (*UniStream, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) QUIC() *quicgo.Conn
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) Recv() ([]byte, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Conn) Send(frame []byte) error
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
Server is a QUIC listener that yields *Conn instances ready for
|
||||
ZAP-frame exchange.
|
||||
|
||||
Server is the QUIC analogue of net.Listener. The outer ZAP Node
|
||||
drives the accept loop; this type does not spawn its own goroutines.
|
||||
|
||||
|
||||
```go
|
||||
func Listen(cfg ServerConfig) (*Server, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) Accept(ctx context.Context) (*Conn, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) Addr() net.Addr
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) Close() error
|
||||
```
|
||||
|
||||
### ServerConfig
|
||||
|
||||
ServerConfig configures a QUIC listener.
|
||||
|
||||
|
||||
### Stream
|
||||
|
||||
Stream is a bidirectional QUIC stream carrying ZAP frames.
|
||||
|
||||
|
||||
```go
|
||||
func (s *Stream) Close() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Stream) CloseWrite() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Stream) ReadFrame() ([]byte, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Stream) WriteFrame(frame []byte) error
|
||||
```
|
||||
|
||||
### UniReceiveStream
|
||||
|
||||
UniReceiveStream is a unidirectional receive-only QUIC stream.
|
||||
|
||||
|
||||
```go
|
||||
func (u *UniReceiveStream) ReadFrame() ([]byte, error)
|
||||
```
|
||||
|
||||
### UniStream
|
||||
|
||||
UniStream is a unidirectional send-only QUIC stream.
|
||||
|
||||
|
||||
```go
|
||||
func (u *UniStream) Close() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (u *UniStream) WriteFrame(frame []byte) error
|
||||
```
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: "main"
|
||||
description: "docgen renders the node's package reference from godoc — every package under the module, one MDX page mirroring the import path — for docs.lux.network."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/tools/docgen"
|
||||
```
|
||||
|
||||
docgen renders the node's package reference from godoc — every package under
|
||||
the module, one MDX page mirroring the import path — for docs.lux.network.
|
||||
It PARSES (go/parser + go/doc), never builds, so it is immune to build-graph
|
||||
skew. Same model as hanzo/cloud generating its docs from source.
|
||||
|
||||
go run ./tools/docgen <out-dir> [root] # default root: .
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["docgen"]}
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: "transport"
|
||||
description: "Package transport is the pluggable GPU-aware ZAP transport."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/transport"
|
||||
```
|
||||
|
||||
Package transport is the pluggable GPU-aware ZAP transport.
|
||||
|
||||
Three transports are implemented:
|
||||
|
||||
default — standard Go net (CPU parsing). Always available.
|
||||
uma — Apple Silicon / NVIDIA Grace unified-memory. cgo + darwin.
|
||||
Network packets land in shared RAM; GPU kernels read them
|
||||
at GPU memory speed without a copy.
|
||||
gpudirect — NVIDIA + Mellanox CX-6/7 (GPUDirect RDMA). cgo + linux.
|
||||
NIC DMA into VRAM, ZAP parsed by GPU kernel, no CPU touch.
|
||||
dpdk — Linux DPDK + GPU-mapped hugepages. cgo + linux.
|
||||
Kernel-bypass packet ingestion, GPU reads from mapped huge-
|
||||
pages. Best for non-Mellanox NICs.
|
||||
|
||||
All four implement the same Transport interface. Pick() chooses the
|
||||
best available implementation at runtime, honoring a caller-supplied
|
||||
preference and falling through gracefully when a preferred path is
|
||||
unavailable. There is no `gpu` build tag — cgo is the only gate, and
|
||||
the OS gate selects which native transports can compile in.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### Registered
|
||||
|
||||
```go
|
||||
func Registered() []string
|
||||
```
|
||||
|
||||
Registered returns the list of transports that this binary can attempt
|
||||
to instantiate (i.e. the platform-and-build-tag matrix). Used by tools
|
||||
like `zap-info` and the boot-time diagnostic in lqd.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Buffer
|
||||
|
||||
Buffer is one slab of managed memory handed out by a BufferAllocator.
|
||||
For the UMA transport on Linux+CUDA, the underlying pointer is
|
||||
cudaMallocManaged memory — the same bytes the GPU sees. Calling Bytes()
|
||||
gives a Go slice that shares the storage; calling DevicePtr() gives the
|
||||
raw device pointer for CUDA kernel launches.
|
||||
|
||||
Release returns the buffer to its allocator. Double-Release is a no-op
|
||||
(allocators detect and ignore it).
|
||||
|
||||
|
||||
### BufferAllocator
|
||||
|
||||
BufferAllocator hands out managed buffers. Transports that can deliver
|
||||
GPU-resident bytes (UMA Linux+CUDA, UMA Darwin+Metal) implement this;
|
||||
transports that can't (default TCP) return ErrNotAvailable from
|
||||
AllocBuffer so the caller knows to fall back to heap allocation.
|
||||
|
||||
Capacity in bytes is the operator-configurable pool size (default 4
|
||||
GiB); allocations from a full pool block until a buffer is Released.
|
||||
|
||||
|
||||
### Capabilities
|
||||
|
||||
Capabilities describes what a Transport implementation supports.
|
||||
|
||||
|
||||
### Transport
|
||||
|
||||
Transport is the wire-level surface multichain gossip uses to ship
|
||||
sealed MultiChainBlocks between validators. The interface is sized for
|
||||
one ZAP message per Send/Recv — batching happens above this layer.
|
||||
|
||||
|
||||
```go
|
||||
func Pick(preferred string) (Transport, error)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: "main"
|
||||
description: "Command gennest generates the nestwire test package: a singular nested object (Outer carrying an Inner that itself has a string tail)."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/cmd/gennest"
|
||||
```
|
||||
|
||||
Command gennest generates the nestwire test package: a singular nested
|
||||
object (Outer carrying an Inner that itself has a string tail). It is the
|
||||
nested-field peer of the one-off that produced testpkg/listwire, and
|
||||
proves the codegen's WriteNested/NestedAt emission round-trips on the
|
||||
wire (see testpkg/nestwire/roundtrip_test.go).
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["gennest","zapgen","zapgen-all"]}
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "main"
|
||||
description: "Command zapgen-all bulk-emits per-schema ZAP v2 accessor files from a directory of JSON schema declarations."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/cmd/zapgen-all"
|
||||
```
|
||||
|
||||
Command zapgen-all bulk-emits per-schema ZAP v2 accessor files
|
||||
from a directory of JSON schema declarations.
|
||||
|
||||
Each *.json file in the schemas directory declares one or more
|
||||
schemas in a single LP-aligned bundle. The bundle's top-level
|
||||
"schemas" array contains entries with the same fields as a
|
||||
codegen.Schema plus an "out" field naming the relative output path:
|
||||
|
||||
{
|
||||
"lp": "lp-201",
|
||||
"description": "ZAP P2P transport (LP-201 §Wire schemas)",
|
||||
"schemas": [
|
||||
{
|
||||
"wire_name": "ConsensusVote",
|
||||
"go_name": "ConsensusVoteSchema",
|
||||
"kind": "0xD0",
|
||||
"size": 62,
|
||||
"package": "p2pwire",
|
||||
"out": "lp-201/consensus_vote_zap.go",
|
||||
"fields": [
|
||||
{ "name": "Round", "type": "uint64", "offset": 1 },
|
||||
{ "name": "BlockID", "type": "bytes32", "offset": 9 },
|
||||
{ "name": "SignerID", "type": "bytes20", "offset": 41 },
|
||||
{ "name": "SigOff", "type": "uint32", "offset": 61 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Usage:
|
||||
|
||||
zapgen-all -schemas ./schemas -out ./generated
|
||||
|
||||
The tool walks schemas/, parses every *.json file, validates every
|
||||
schema, and emits one *.go file per schema under -out at the path
|
||||
declared by the schema's "out" field. Existing files are overwritten.
|
||||
|
||||
Exit codes: 0 = all schemas emitted; 1 = at least one schema failed
|
||||
to parse/validate/emit.
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: "main"
|
||||
description: "Command zapgen emits per-schema ZAP v2 accessors from a declarative schema description."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/cmd/zapgen"
|
||||
```
|
||||
|
||||
Command zapgen emits per-schema ZAP v2 accessors from a declarative
|
||||
schema description.
|
||||
|
||||
Usage:
|
||||
|
||||
zapgen -name AdvanceTimeTx -go-name AdvanceTimeSchema -kind 1 \
|
||||
-size 9 -package examples -field Time:uint64:1 \
|
||||
-out v2/examples/advance_time_zap.go
|
||||
|
||||
The emitted file's Wrap/New functions use v1 primitives directly so
|
||||
the Go compiler inlines them into the caller — matching v1
|
||||
hand-rolled performance to within compiler noise.
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
title: "codegen"
|
||||
description: "Package codegen emits per-schema ZAP v2 accessors that match v1's hand-rolled inline-everything performance."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen"
|
||||
```
|
||||
|
||||
Package codegen emits per-schema ZAP v2 accessors that match v1's
|
||||
hand-rolled inline-everything performance.
|
||||
|
||||
# Why codegen
|
||||
|
||||
Go's generic dispatch and inlining cost budget combine to put a
|
||||
hard ceiling on how fast a generic API can be: any function whose
|
||||
body is larger than the inliner's 80-cost budget cannot inline
|
||||
into its caller, so the call site pays a function-call cost (~5-7
|
||||
ns on modern hardware). The work performed by a "Wrap" function
|
||||
(parse the ZAP wire frame, validate the kind discriminator, build
|
||||
a typed view) exceeds that budget. The generic [zapv1.Wrap[S]]
|
||||
function therefore costs one function call per Wrap, even when
|
||||
every other primitive in its body would inline.
|
||||
|
||||
Hand-written per-schema [WrapX] shims (e.g. [examples.WrapAdvanceTime])
|
||||
have the same problem — even though they pin S{}.Kind() and
|
||||
S{}.Size() as constants, the body is still too large to inline.
|
||||
|
||||
Codegen solves this by emitting Wrap/Build/Read/Write functions
|
||||
that DON'T live inside a function at all — they expand inline at
|
||||
the call site through Go templates instantiated at build time.
|
||||
The user writes a single schema declaration; the codegen tool
|
||||
produces a *_zap.go file whose functions match v1's hand-rolled
|
||||
pattern byte-for-byte.
|
||||
|
||||
# Output shape
|
||||
|
||||
For a schema declared as:
|
||||
|
||||
type AdvanceTimeSchema struct{}
|
||||
func (AdvanceTimeSchema) Kind() zapv1.KindByte { return 1 }
|
||||
func (AdvanceTimeSchema) Size() int { return 9 }
|
||||
func (AdvanceTimeSchema) Name() string { return "AdvanceTimeTx" }
|
||||
|
||||
//zap:field Time uint64 @1
|
||||
|
||||
the codegen tool emits a sibling file (advance_time_zap.go) with:
|
||||
|
||||
const sizeAdvanceTimeTx = 9
|
||||
const kindAdvanceTimeTx uint8 = 1
|
||||
const offsetAdvanceTimeTx_Time = 1
|
||||
|
||||
func WrapAdvanceTime(b []byte) (zapv1.View[AdvanceTimeSchema], error) {
|
||||
msg, err := zap.Parse(b)
|
||||
...
|
||||
}
|
||||
|
||||
The emitted code uses v1 primitives directly (zap.Parse, msg.Root,
|
||||
root.Uint8) so that the inliner folds them into the caller's frame,
|
||||
matching v1 hand-rolled performance.
|
||||
|
||||
# When to use codegen
|
||||
|
||||
- Hot-path schemas where the function-call overhead matters
|
||||
(per-tx assembly, per-block validation).
|
||||
- When you have a schema description in a declarative form (a YAML
|
||||
file, a Cap'n-Proto-style schema file, struct tags) and want
|
||||
ZAP v2 accessors emitted automatically.
|
||||
|
||||
For cold paths and ad-hoc schemas, the generic [zapv1.Wrap[S]] /
|
||||
[zapv1.Build[S]] are equally correct and one extra function call
|
||||
slower — which is rarely the bottleneck.
|
||||
|
||||
# Status
|
||||
|
||||
This package is the entry point. The current implementation emits
|
||||
the canonical AdvanceTimeTx fast-path file (used as the canary in
|
||||
the bench suite); adding new schemas means feeding the codegen
|
||||
tool a schema declaration in one of the supported forms.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### Emit
|
||||
|
||||
```go
|
||||
func Emit(w io.Writer, s Schema) error
|
||||
```
|
||||
|
||||
Emit writes the per-schema *.go file to w. Returns an error if the
|
||||
schema is invalid (unknown field type, offset overlap, fields past
|
||||
declared Size, etc.).
|
||||
|
||||
The emitted code follows the v1 hand-rolled pattern: every step
|
||||
(Parse, Root, Uint reads, kind check, View compose) uses v1
|
||||
primitives that inline into the caller's frame. The result matches
|
||||
v1 hand-rolled performance to within compiler noise — verified by
|
||||
the bench suite.
|
||||
|
||||
Scalar fields go through the [zapv1.Field][S, T] generic handle
|
||||
(declared in the per-schema <GoName>Fields var). Fixed-width
|
||||
byte-array fields ("bytes<N>") are emitted as standalone typed
|
||||
accessor functions that call v1's SetBytesFixed / BytesFixedSlice
|
||||
— they do NOT appear in the Fields struct because [N]byte is not a
|
||||
[zapv1.FieldKind] member. Both forms produce the same wire layout.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Field
|
||||
|
||||
Field describes one fixed-size field in a schema.
|
||||
|
||||
Two field kinds are supported:
|
||||
|
||||
1. Scalar fields. Type is one of the [zapv1.FieldKind] members
|
||||
(bool, int8/16/32/64, uint8/16/32/64, float32/64). The emitted
|
||||
code uses a [zapv1.Field][S, T] handle and the standard
|
||||
zapv1.Read/Write generic functions.
|
||||
|
||||
2. Fixed-width byte-array fields. Type is "bytes<N>" where N is a
|
||||
positive integer (e.g., "bytes20" for NodeID, "bytes32" for
|
||||
hashes, "bytes16" for session IDs). The emitted code uses the
|
||||
v1 ObjectBuilder.SetBytesFixed / Object.BytesFixedSlice
|
||||
accessors and returns the value as a [N]byte. Byte-array fields
|
||||
do NOT use the zapv1.Field generic handle because [N]byte is
|
||||
not a [zapv1.FieldKind] member; instead they get a typed
|
||||
accessor function emitted alongside the schema.
|
||||
|
||||
3. Variable-length tail fields. Type is "string" or "bytes" (no
|
||||
<N> suffix). These occupy an 8-byte tail pointer {relOffset
|
||||
uint32, length uint32} in the fixed payload; the data lives in
|
||||
the object tail after the fixed section. The constructor uses v1
|
||||
ObjectBuilder.SetText / SetBytes; reads go through a standalone
|
||||
accessor over v1's Object.Text / Object.Bytes (zero-copy
|
||||
sub-slice of the buffer). Like byte-array fields, they do NOT use
|
||||
the zapv1.Field generic handle (string/[]byte are not FieldKind
|
||||
members).
|
||||
|
||||
List and nested-object tail fields are still hand-written (the
|
||||
generic [zapv1.ListAt] / out-of-line pointer machinery).
|
||||
|
||||
|
||||
```go
|
||||
func (f Field) IsBytes() (int, bool)
|
||||
```
|
||||
|
||||
```go
|
||||
func (f Field) IsList() (*ListElem, bool)
|
||||
```
|
||||
|
||||
```go
|
||||
func (f Field) IsNested() (*NestedMsg, bool)
|
||||
```
|
||||
|
||||
```go
|
||||
func (f Field) IsVarBytes() bool
|
||||
```
|
||||
|
||||
```go
|
||||
func (f Field) IsVarString() bool
|
||||
```
|
||||
|
||||
```go
|
||||
func (f Field) IsVariable() bool
|
||||
```
|
||||
|
||||
### ListElem
|
||||
|
||||
ListElem describes the element type of a list field. The element must
|
||||
be a FIXED-SIZE schema (scalars + fixed byte arrays only — no
|
||||
variable-length tails of its own), so every element is a flat
|
||||
Stride-byte slot the list machinery can index in O(1).
|
||||
|
||||
|
||||
### NestedMsg
|
||||
|
||||
NestedMsg describes a singular nested-object field — the proto3 message
|
||||
field. The nested object must be a FIXED-SIZE (flat, no-kind) Element
|
||||
schema, reached through a 4-byte object pointer; its flat payload lives
|
||||
in the parent's object tail. The singular peer of [ListElem].
|
||||
|
||||
|
||||
### Schema
|
||||
|
||||
Schema is the declarative description of a ZAP v1 schema. The
|
||||
codegen tool consumes a [Schema] and emits a per-schema *.go file
|
||||
with the Wrap/Build/Read/Write functions hand-rolled to inline.
|
||||
|
||||
One schema description, one emitted file, one and only one way to
|
||||
access the wire format from Go code — Hickey-style.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["cmd","testpkg"]}
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "lcwire"
|
||||
description: "Package lcwire"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/testpkg"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### LightClientBlockHeaderRequestBlockContentHash
|
||||
|
||||
```go
|
||||
func LightClientBlockHeaderRequestBlockContentHash(v zapv1.View[LCBlockHeaderReqSchema]) [32]byte
|
||||
```
|
||||
|
||||
LightClientBlockHeaderRequestBlockContentHash reads the 32-byte inline BlockContentHash field of LightClientBlockHeaderRequest.
|
||||
|
||||
Zero-copy on the read side (v1's Object.BytesFixedSlice returns
|
||||
a sub-slice of the underlying buffer); the returned [32]byte is a
|
||||
value copy so the caller's slot owns the bytes after the View's
|
||||
buffer is freed.
|
||||
|
||||
|
||||
### NewLightClientBlockHeaderRequest
|
||||
|
||||
```go
|
||||
func NewLightClientBlockHeaderRequest(version uint8, chainID uint32, blockHeight uint64, certMinTier uint8, requestID uint32, blockContentHash [32]byte) (zapv1.View[LCBlockHeaderReqSchema], []byte)
|
||||
```
|
||||
|
||||
NewLightClientBlockHeaderRequest builds a fresh LightClientBlockHeaderRequest in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Result: 1 heap
|
||||
alloc (the buffer), zero overhead vs v1.
|
||||
|
||||
|
||||
### WrapLightClientBlockHeaderRequest
|
||||
|
||||
```go
|
||||
func WrapLightClientBlockHeaderRequest(b []byte) (zapv1.View[LCBlockHeaderReqSchema], error)
|
||||
```
|
||||
|
||||
WrapLightClientBlockHeaderRequest parses a ZAP buffer into a typed
|
||||
[zapv1.View[LightClientBlockHeaderRequest]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### LCBlockHeaderReqSchema
|
||||
|
||||
LCBlockHeaderReqSchema is the v2 schema marker for LightClientBlockHeaderRequest. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[LCBlockHeaderReqSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (LCBlockHeaderReqSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (LCBlockHeaderReqSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (LCBlockHeaderReqSchema) Size() int
|
||||
```
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: "listwire"
|
||||
description: "Package listwire"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/testpkg/listwire"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### BatchTxItems
|
||||
|
||||
```go
|
||||
func BatchTxItems(v zapv1.View[BatchSchema]) zapv1.List[ItemSchema]
|
||||
```
|
||||
|
||||
BatchTxItems returns the variable-length Items list of BatchTx as a
|
||||
range-over-func-ready [zapv1.List][ItemSchema]. Zero-copy: each element view
|
||||
indexes straight into the underlying buffer.
|
||||
|
||||
for it := range BatchTxItems(view).All() {
|
||||
// zapv1.Read(it, ItemSchemaFields.X)
|
||||
}
|
||||
|
||||
|
||||
### NewBatchItem
|
||||
|
||||
```go
|
||||
func NewBatchItem(iD uint64, value uint64) (zapv1.View[ItemSchema], []byte)
|
||||
```
|
||||
|
||||
NewBatchItem builds a fresh BatchItem in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Scalars and fixed byte
|
||||
arrays live in the fixed payload; variable-length string/bytes go in
|
||||
the object tail (the fixed payload holds their 8-byte pointers).
|
||||
|
||||
|
||||
### NewBatchTx
|
||||
|
||||
```go
|
||||
func NewBatchTx(iD uint64, items []Item) (zapv1.View[BatchSchema], []byte)
|
||||
```
|
||||
|
||||
NewBatchTx builds a fresh BatchTx in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Scalars and fixed byte
|
||||
arrays live in the fixed payload; variable-length string/bytes go in
|
||||
the object tail (the fixed payload holds their 8-byte pointers).
|
||||
|
||||
|
||||
### WrapBatchItem
|
||||
|
||||
```go
|
||||
func WrapBatchItem(b []byte) (zapv1.View[ItemSchema], error)
|
||||
```
|
||||
|
||||
WrapBatchItem parses a ZAP buffer into a typed
|
||||
[zapv1.View[BatchItem]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
### WrapBatchTx
|
||||
|
||||
```go
|
||||
func WrapBatchTx(b []byte) (zapv1.View[BatchSchema], error)
|
||||
```
|
||||
|
||||
WrapBatchTx parses a ZAP buffer into a typed
|
||||
[zapv1.View[BatchTx]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### BatchSchema
|
||||
|
||||
BatchSchema is the v2 schema marker for BatchTx. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[BatchSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (BatchSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (BatchSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (BatchSchema) Size() int
|
||||
```
|
||||
|
||||
### Item
|
||||
|
||||
Item is the per-element input for the BatchTx.Items list.
|
||||
|
||||
|
||||
### ItemSchema
|
||||
|
||||
ItemSchema is the v2 schema marker for BatchItem. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[ItemSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (ItemSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (ItemSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (ItemSchema) Size() int
|
||||
```
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "consensuswire"
|
||||
description: "Package consensuswire"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/testpkg/lp182_consensuswire"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### NewQuasarCert
|
||||
|
||||
```go
|
||||
func NewQuasarCert(epoch uint64, finalityUnixNano int64, validators uint32, bLSOff uint32, bLSLen uint32, coronaOff uint32, coronaLen uint32, pulsarOff uint32, pulsarLen uint32, magnetarOff uint32, magnetarLen uint32, mLDSARollupOff uint32, mLDSARollupLen uint32) (zapv1.View[QuasarCertSchema], []byte)
|
||||
```
|
||||
|
||||
NewQuasarCert builds a fresh QuasarCert in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Result: 1 heap
|
||||
alloc (the buffer), zero overhead vs v1.
|
||||
|
||||
|
||||
### WrapQuasarCert
|
||||
|
||||
```go
|
||||
func WrapQuasarCert(b []byte) (zapv1.View[QuasarCertSchema], error)
|
||||
```
|
||||
|
||||
WrapQuasarCert parses a ZAP buffer into a typed
|
||||
[zapv1.View[QuasarCert]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### QuasarCertSchema
|
||||
|
||||
QuasarCertSchema is the v2 schema marker for QuasarCert. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[QuasarCertSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (QuasarCertSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (QuasarCertSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (QuasarCertSchema) Size() int
|
||||
```
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "zkvmwire"
|
||||
description: "Package zkvmwire"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/testpkg/lp186_zkvmwire"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### NewZKVMUTXO
|
||||
|
||||
```go
|
||||
func NewZKVMUTXO(outputIndex uint32, height uint64, commitmentOff uint32, commitmentLen uint32, ciphertextOff uint32, ciphertextLen uint32, ephemeralPKOff uint32, ephemeralPKLen uint32, txID [32]byte) (zapv1.View[ZKVMUTXOSchema], []byte)
|
||||
```
|
||||
|
||||
NewZKVMUTXO builds a fresh ZKVMUTXO in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Result: 1 heap
|
||||
alloc (the buffer), zero overhead vs v1.
|
||||
|
||||
|
||||
### WrapZKVMUTXO
|
||||
|
||||
```go
|
||||
func WrapZKVMUTXO(b []byte) (zapv1.View[ZKVMUTXOSchema], error)
|
||||
```
|
||||
|
||||
WrapZKVMUTXO parses a ZAP buffer into a typed
|
||||
[zapv1.View[ZKVMUTXO]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
### ZKVMUTXOTxID
|
||||
|
||||
```go
|
||||
func ZKVMUTXOTxID(v zapv1.View[ZKVMUTXOSchema]) [32]byte
|
||||
```
|
||||
|
||||
ZKVMUTXOTxID reads the 32-byte inline TxID field of ZKVMUTXO.
|
||||
|
||||
Zero-copy on the read side (v1's Object.BytesFixedSlice returns
|
||||
a sub-slice of the underlying buffer); the returned [32]byte is a
|
||||
value copy so the caller's slot owns the bytes after the View's
|
||||
buffer is freed.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### ZKVMUTXOSchema
|
||||
|
||||
ZKVMUTXOSchema is the v2 schema marker for ZKVMUTXO. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[ZKVMUTXOSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (ZKVMUTXOSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (ZKVMUTXOSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (ZKVMUTXOSchema) Size() int
|
||||
```
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["listwire","lp182_consensuswire","lp186_zkvmwire","nestwire","repeatwire"]}
|
||||
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: "nestwire"
|
||||
description: "Package nestwire"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/testpkg/nestwire"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### InnerLabel
|
||||
|
||||
```go
|
||||
func InnerLabel(v zapv1.View[InnerSchema]) string
|
||||
```
|
||||
|
||||
InnerLabel reads the variable-length Label string field of Inner.
|
||||
|
||||
Zero-copy: v1's Object.Text returns a string over a sub-slice of the
|
||||
underlying buffer. Valid only while the View's buffer is alive.
|
||||
|
||||
|
||||
### NewInner
|
||||
|
||||
```go
|
||||
func NewInner(iD uint64, label string) (zapv1.View[InnerSchema], []byte)
|
||||
```
|
||||
|
||||
NewInner builds a fresh Inner in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Scalars and fixed byte
|
||||
arrays live in the fixed payload; variable-length string/bytes go in
|
||||
the object tail (the fixed payload holds their 8-byte pointers).
|
||||
|
||||
|
||||
### NewOuter
|
||||
|
||||
```go
|
||||
func NewOuter(seq uint64, inner *Inner) (zapv1.View[OuterSchema], []byte)
|
||||
```
|
||||
|
||||
NewOuter builds a fresh Outer in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Scalars and fixed byte
|
||||
arrays live in the fixed payload; variable-length string/bytes go in
|
||||
the object tail (the fixed payload holds their 8-byte pointers).
|
||||
|
||||
|
||||
### OuterInner
|
||||
|
||||
```go
|
||||
func OuterInner(v zapv1.View[OuterSchema]) zapv1.View[InnerSchema]
|
||||
```
|
||||
|
||||
OuterInner returns the singular Inner nested object of Outer as a
|
||||
zero-copy [zapv1.View][InnerSchema]. An unset (null) nested field yields the zero
|
||||
View — test it with [zapv1.View.IsZero].
|
||||
|
||||
|
||||
### WrapInner
|
||||
|
||||
```go
|
||||
func WrapInner(b []byte) (zapv1.View[InnerSchema], error)
|
||||
```
|
||||
|
||||
WrapInner parses a ZAP buffer into a typed
|
||||
[zapv1.View[Inner]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
### WrapOuter
|
||||
|
||||
```go
|
||||
func WrapOuter(b []byte) (zapv1.View[OuterSchema], error)
|
||||
```
|
||||
|
||||
WrapOuter parses a ZAP buffer into a typed
|
||||
[zapv1.View[Outer]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Inner
|
||||
|
||||
Inner is the value input for the singular Outer.Inner nested object.
|
||||
|
||||
|
||||
### InnerSchema
|
||||
|
||||
InnerSchema is the v1 schema marker for Inner. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[InnerSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (InnerSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (InnerSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (InnerSchema) Size() int
|
||||
```
|
||||
|
||||
### OuterSchema
|
||||
|
||||
OuterSchema is the v1 schema marker for Outer. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[OuterSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (OuterSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (OuterSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (OuterSchema) Size() int
|
||||
```
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
title: "repeatwire"
|
||||
description: "Package repeatwire"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1/codegen/testpkg/repeatwire"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### ChildName
|
||||
|
||||
```go
|
||||
func ChildName(v zapv1.View[ChildSchema]) string
|
||||
```
|
||||
|
||||
ChildName reads the variable-length Name string field of Child.
|
||||
|
||||
Zero-copy: v1's Object.Text returns a string over a sub-slice of the
|
||||
underlying buffer. Valid only while the View's buffer is alive.
|
||||
|
||||
|
||||
### NewChild
|
||||
|
||||
```go
|
||||
func NewChild(iD uint64, name string) (zapv1.View[ChildSchema], []byte)
|
||||
```
|
||||
|
||||
NewChild builds a fresh Child in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Scalars and fixed byte
|
||||
arrays live in the fixed payload; variable-length string/bytes go in
|
||||
the object tail (the fixed payload holds their 8-byte pointers).
|
||||
|
||||
|
||||
### NewParent
|
||||
|
||||
```go
|
||||
func NewParent(count uint64, children []Child) (zapv1.View[ParentSchema], []byte)
|
||||
```
|
||||
|
||||
NewParent builds a fresh Parent in a ZAP buffer.
|
||||
|
||||
Hand-rolled fast path: inlines v1 primitives ([zap.NewBuilder],
|
||||
[*Builder.StartObject], [*ObjectBuilder.Set*], [*Builder.Finish])
|
||||
so the entire build expands at the call site. Scalars and fixed byte
|
||||
arrays live in the fixed payload; variable-length string/bytes go in
|
||||
the object tail (the fixed payload holds their 8-byte pointers).
|
||||
|
||||
|
||||
### ParentChildren
|
||||
|
||||
```go
|
||||
func ParentChildren(v zapv1.View[ParentSchema]) zapv1.ListNested[ChildSchema]
|
||||
```
|
||||
|
||||
ParentChildren returns the variable-length Children list of Parent as a
|
||||
range-over-func-ready [zapv1.ListNested][ChildSchema]. Zero-copy: each element view
|
||||
indexes straight into the underlying buffer.
|
||||
|
||||
for it := range ParentChildren(view).All() {
|
||||
// zapv1.Read(it, ChildSchemaFields.X)
|
||||
}
|
||||
|
||||
|
||||
### WrapChild
|
||||
|
||||
```go
|
||||
func WrapChild(b []byte) (zapv1.View[ChildSchema], error)
|
||||
```
|
||||
|
||||
WrapChild parses a ZAP buffer into a typed
|
||||
[zapv1.View[Child]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
### WrapParent
|
||||
|
||||
```go
|
||||
func WrapParent(b []byte) (zapv1.View[ParentSchema], error)
|
||||
```
|
||||
|
||||
WrapParent parses a ZAP buffer into a typed
|
||||
[zapv1.View[Parent]]. Validates the wire frame and the kind
|
||||
discriminator at offset 0; returns [*zapv1.SchemaError] on kind
|
||||
mismatch.
|
||||
|
||||
Hand-rolled fast path: inlines [zap.Parse] (which is itself
|
||||
inlineable). Zero heap allocs on the happy path; matches v1
|
||||
hand-rolled performance.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Child
|
||||
|
||||
Child is the per-element input for the Parent.Children list.
|
||||
|
||||
|
||||
### ChildSchema
|
||||
|
||||
ChildSchema is the v1 schema marker for Child. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[ChildSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (ChildSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (ChildSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (ChildSchema) Size() int
|
||||
```
|
||||
|
||||
### ParentSchema
|
||||
|
||||
ParentSchema is the v1 schema marker for Parent. Methods are constant-
|
||||
returning so concrete-typed [zapv1.Wrap[ParentSchema]] calls fold to
|
||||
literals at the call site.
|
||||
|
||||
|
||||
```go
|
||||
func (ParentSchema) Kind() zapv1.KindByte
|
||||
```
|
||||
|
||||
```go
|
||||
func (ParentSchema) Name() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (ParentSchema) Size() int
|
||||
```
|
||||
|
||||
@@ -0,0 +1,749 @@
|
||||
---
|
||||
title: "zapv1"
|
||||
description: "Package zapv1 is the elegant, generic, idiomatic Go reference implementation of the ZAP wire format."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/v1"
|
||||
```
|
||||
|
||||
Package zapv1 is the elegant, generic, idiomatic Go reference
|
||||
implementation of the ZAP wire format.
|
||||
|
||||
Where the v1 [github.com/luxfi/zap] package exposes a C-shaped API
|
||||
(one hand-rolled Wrap/Build/accessor block per schema, runtime offset
|
||||
constants, runtime kind-byte switch), v2 expresses the same wire
|
||||
format through Go 1.23+ generics so that every schema gets:
|
||||
|
||||
- One typed [View] over the buffer.
|
||||
- One typed [Field] per offset, with the field's value type
|
||||
enforced at compile time. A [Field][SchemaA, uint64] cannot be
|
||||
read from a [View][SchemaB]; the compiler rejects it.
|
||||
- One generic [List] with [iter.Seq] range-over-func iteration.
|
||||
- One generic [Pool] for reuse.
|
||||
- One generic [Registry] for kind-byte dispatch (reflection at
|
||||
boot, none in the hot path).
|
||||
|
||||
# Wire compatibility
|
||||
|
||||
v2 reads and writes the exact same bytes as v1. Both APIs ship
|
||||
side-by-side and may share buffers freely; nothing about the on-wire
|
||||
encoding has changed. v2 is purely an ergonomic upgrade.
|
||||
|
||||
# Threat model
|
||||
|
||||
All wire-level safety properties of v1 are inherited verbatim by v2
|
||||
(Magic check, version check, bounds-checked offsets, signed-but-
|
||||
non-header-aliasing Object/List pointers, RED-HIGH-1 length clamp,
|
||||
RED-HIGH-2 header-alias rejection). v2 adds compile-time type safety
|
||||
on top — it does not loosen any runtime check.
|
||||
|
||||
# Hickey-style decomposition
|
||||
|
||||
- A schema is a value (something implementing [Schema]), not a
|
||||
place. It carries a kind byte, a fixed size, and a name. Schemas
|
||||
are qualified by package, not by prefix.
|
||||
- A view is a value ([View]) — a typed reference into a buffer.
|
||||
View[S] and View[T] are distinct at compile time.
|
||||
- A field is a value ([Field]) — a phantom-typed offset, qualified
|
||||
by the schema it belongs to and the wire type it reads/writes.
|
||||
- A list is a value ([List]) — its element type is the schema for
|
||||
that element; iteration is generic via [iter.Seq].
|
||||
|
||||
One and only one way to express any of these. No braiding of policy
|
||||
with primitive, no inheritance, no duplication.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### Collect
|
||||
|
||||
```go
|
||||
func Collect[T any](src iter.Seq[T]) []T
|
||||
```
|
||||
|
||||
Collect drains src into a freshly allocated slice. Useful in tests
|
||||
where the caller wants to assert on the materialized list; not
|
||||
recommended in the hot path because it defeats the zero-copy
|
||||
property of [List].
|
||||
|
||||
|
||||
### Count
|
||||
|
||||
```go
|
||||
func Count[T any](src iter.Seq[T]) int
|
||||
```
|
||||
|
||||
Count returns the number of elements yielded by src. Drains the
|
||||
sequence — do not call on infinite sequences.
|
||||
|
||||
|
||||
### Filter
|
||||
|
||||
```go
|
||||
func Filter[T any](src iter.Seq[T], pred func(T) bool) iter.Seq[T]
|
||||
```
|
||||
|
||||
Filter returns an iter.Seq yielding only those elements of src for
|
||||
which pred returns true. Constant memory; one allocation per call
|
||||
(the closure), zero per element.
|
||||
|
||||
|
||||
### Map
|
||||
|
||||
```go
|
||||
func Map[T, U any](src iter.Seq[T], f func(T) U) iter.Seq[U]
|
||||
```
|
||||
|
||||
Map returns an iter.Seq[U] obtained by applying f to each element of
|
||||
src. Constant memory per element.
|
||||
|
||||
|
||||
### Read
|
||||
|
||||
```go
|
||||
func Read[S Schema, T FieldKind](v View[S], f Field[S, T]) T
|
||||
```
|
||||
|
||||
Read reads the field f's value from view v.
|
||||
|
||||
Compile-time safety: the type parameters S and T are inferred from
|
||||
the (view, field) pair. Passing a Field for a different schema to a
|
||||
View[S] fails to type-check because Field[OtherSchema, T] is not
|
||||
assignable to Field[S, T].
|
||||
|
||||
Zero copy, zero allocation. Out-of-range offsets return the zero
|
||||
value of T (matches the audited v1 [zap.Object] bounds-check
|
||||
behaviour: degrade gracefully to zero rather than panic).
|
||||
|
||||
Performance: this function reads bytes directly from the View's
|
||||
pre-sliced payload via [unsafe.Pointer], folding to a single load
|
||||
instruction per concrete T after generic instantiation. Verified by
|
||||
[bench_test.go]: matches hand-rolled v1 [zap.Object.Uint64] to
|
||||
within compiler-noise.
|
||||
|
||||
Wire format: ZAP integers are little-endian. On little-endian hosts
|
||||
(every Lux target — amd64, arm64, aarch64) the load is a direct
|
||||
cast. The pkg builds only on little-endian platforms; a future big-
|
||||
endian port would need a per-type byteswap (see init()).
|
||||
|
||||
|
||||
### ReadPayload
|
||||
|
||||
```go
|
||||
func ReadPayload[S Schema, T FieldKind](payload []byte, f Field[S, T]) T
|
||||
```
|
||||
|
||||
ReadPayload reads a field directly from a payload byte slice (as
|
||||
yielded by [List.Payloads]). This is the hot-path counterpart to
|
||||
[Read] — equivalent wire semantics, but the input is a 24-byte
|
||||
slice header (registers-friendly) rather than a 56-byte [View[S]]
|
||||
(stack-spilling).
|
||||
|
||||
Use ReadPayload inside a for-range over [List.Payloads] when
|
||||
iterating large lists with fixed-field reads only. For full View
|
||||
operations (Bytes, sub-objects, lists) use [Read] with [List.All].
|
||||
|
||||
Compile-time safety: same as [Read] — Field[S, T] is phantom-typed
|
||||
against schema S, so the compiler rejects cross-schema misuse even
|
||||
though the payload slice itself is plain []byte. The S type
|
||||
parameter is what ties the field to its declared schema.
|
||||
|
||||
|
||||
### Register
|
||||
|
||||
```go
|
||||
func Register[S Schema](r *Registry)
|
||||
```
|
||||
|
||||
Register registers schema S in r. It is safe to call from multiple
|
||||
goroutines, but the typical pattern is init():
|
||||
|
||||
func init() {
|
||||
zapv1.Register[AdvanceTimeSchema](DefaultRegistry)
|
||||
}
|
||||
|
||||
Register panics if two schemas declare the same kind byte — that
|
||||
would be a wire-protocol bug, not a runtime condition.
|
||||
|
||||
|
||||
### RootOff
|
||||
|
||||
```go
|
||||
func RootOff[S Schema](v View[S]) int32
|
||||
```
|
||||
|
||||
RootOff returns the absolute byte offset of the root object within
|
||||
the underlying buffer. Exposed for codegen'd byte-array accessors
|
||||
that need to re-anchor a [zap.Object] at the root to call v1's
|
||||
out-of-line byte-slice readers ([zap.Object.BytesFixedSlice]).
|
||||
Application code typically does not need this — use the typed
|
||||
accessors emitted alongside each schema.
|
||||
|
||||
|
||||
### Take
|
||||
|
||||
```go
|
||||
func Take[T any](src iter.Seq[T], n int) iter.Seq[T]
|
||||
```
|
||||
|
||||
Take returns an iter.Seq yielding at most n elements from src. If
|
||||
src yields fewer than n, all of them are passed through; if more,
|
||||
the iteration stops after n.
|
||||
|
||||
Constant memory: Take does not buffer. It counts and short-circuits.
|
||||
|
||||
|
||||
### Write
|
||||
|
||||
```go
|
||||
func Write[S Schema, T FieldKind](s Setter[S], f Field[S, T], val T)
|
||||
```
|
||||
|
||||
Write writes value val into field f of setter s.
|
||||
|
||||
Compile-time safety: as with [Read], passing a Field declared for a
|
||||
different schema fails to type-check.
|
||||
|
||||
Zero allocation. The write goes through the underlying v1
|
||||
[zap.ObjectBuilder] for the fixed payload — its bounds-check
|
||||
(ensureField) is inherited verbatim. After generic instantiation +
|
||||
inlining, this collapses to a direct little-endian store at the
|
||||
computed offset, matching v1 hand-rolled SetUint64 performance —
|
||||
verified by [bench_test.go].
|
||||
|
||||
Implementation note: writing through the ObjectBuilder (rather than
|
||||
a direct unsafe store into the underlying buffer) preserves v1's
|
||||
audited "ensureField" semantics — fields past the currently-written
|
||||
region get the buffer growth + zero-fill that the builder
|
||||
guarantees. A bare unsafe write would bypass that and corrupt the
|
||||
builder's invariant.
|
||||
|
||||
|
||||
### WriteB
|
||||
|
||||
```go
|
||||
func WriteB[S Schema, T FieldKind](bb Builder[S], f Field[S, T], val T)
|
||||
```
|
||||
|
||||
WriteB is the [Builder]-receiver counterpart to [Write]. Used in
|
||||
the imperative-style build path:
|
||||
|
||||
bb := zapv1.NewBuilderFor[Schema]()
|
||||
zapv1.WriteB(bb, Fields.Time, ts)
|
||||
view, buf := bb.Finish()
|
||||
|
||||
Identical wire semantics to Write — the difference is only that
|
||||
passing a Builder[S] (by value) instead of a Setter[S] (through a
|
||||
closure) keeps the wrapper on the caller's stack. The underlying
|
||||
v1 *zap.Builder still escapes (its buffer is what goes on the
|
||||
wire), so the imperative form does not eliminate all v2 overhead —
|
||||
it eliminates only the closure-captured pointer allocation.
|
||||
|
||||
|
||||
### WriteBytes
|
||||
|
||||
```go
|
||||
func WriteBytes[S Schema](s Setter[S], byteOffset uint32, v []byte)
|
||||
```
|
||||
|
||||
WriteBytes writes a variable-length byte slice to the object tail and
|
||||
stores its {relOffset, length} pointer at byteOffset. The typed peer
|
||||
of [zap.ObjectBuilder.SetBytes]. Zero-copy on read via Object.Bytes.
|
||||
|
||||
|
||||
### WriteList
|
||||
|
||||
```go
|
||||
func WriteList[S, E Schema](s Setter[S], byteOffset uint32, each func(*ElemSetter[E]))
|
||||
```
|
||||
|
||||
WriteList writes a homogeneous list of element-schema E into the
|
||||
list-pointer field at byteOffset within schema S. The `each` closure
|
||||
is called once with an [*ElemSetter[E]] handle that writes one
|
||||
element per call to [ElemSetter.Append]; the list pointer in S's
|
||||
fixed payload is patched with the final (relOffset, length) when
|
||||
each returns.
|
||||
|
||||
Example (BatchTx.Items, see examples/batch_tx.go):
|
||||
|
||||
zapv1.Build[BatchSchema](func(s zapv1.Setter[BatchSchema]) {
|
||||
zapv1.Write(s, BatchFields.ID, batchID)
|
||||
zapv1.WriteList[BatchSchema, ItemSchema](s, OffsetBatchItems,
|
||||
func(items *zapv1.ElemSetter[ItemSchema]) {
|
||||
for _, it := range source {
|
||||
items.Append(func(e zapv1.Setter[ItemSchema]) {
|
||||
zapv1.Write(e, ItemFields.ID, it.id)
|
||||
zapv1.Write(e, ItemFields.Value, it.val)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Stride is taken from E{}.Size(). The list lives in the variable
|
||||
section of the parent message, after the fixed payload — the exact
|
||||
wire layout that v1 [zap.Builder.StartList]/SetList produces.
|
||||
|
||||
Wire encoding: the list-pointer field at byteOffset holds (relOffset,
|
||||
length); length is the number of elements (NOT bytes), matching v1's
|
||||
[zap.List.Object] indexing convention.
|
||||
|
||||
|
||||
### WriteListNested
|
||||
|
||||
```go
|
||||
func WriteListNested[S, N Schema](s Setter[S], byteOffset uint32, each func(*NestedElemSetter[N]))
|
||||
```
|
||||
|
||||
WriteListNested writes a repeated nested-object field: each element is
|
||||
built as a flat out-of-line object in the parent tail (with its own
|
||||
scalars/strings/lists), then a contiguous stride-4 pointer array is laid
|
||||
down and the list field patched to it. The pointer-array peer of
|
||||
[WriteList] (which inlines fixed-stride elements). An empty list writes a
|
||||
null pointer.
|
||||
|
||||
|
||||
### WriteNested
|
||||
|
||||
```go
|
||||
func WriteNested[S, N Schema](s Setter[S], byteOffset uint32, init func(Setter[N]))
|
||||
```
|
||||
|
||||
WriteNested builds a singular nested object of schema N into the parent
|
||||
object's tail and stores its 4-byte object pointer at byteOffset in the
|
||||
parent's fixed payload. The init closure populates the nested object's
|
||||
fields (via [Write]/[WriteString]/[WriteBytes]/[WriteList]/[WriteNested]
|
||||
— it composes recursively). The singular peer of [WriteList].
|
||||
|
||||
Omitting the call entirely leaves the pointer field zero — a null
|
||||
pointer — which [NestedAt] reads back as the zero View[N]. This is how
|
||||
codegen encodes an unset proto3 message field: emit the WriteNested only
|
||||
inside an `if msg != nil` guard.
|
||||
|
||||
Wire layout matches the singular case of [WriteList]: the parent's
|
||||
fixed payload is reserved first (so the nested object cannot overwrite
|
||||
the still-unwritten pointer field), then the nested object is appended
|
||||
to the tail and the pointer patched with its relative offset.
|
||||
|
||||
|
||||
### WriteString
|
||||
|
||||
```go
|
||||
func WriteString[S Schema](s Setter[S], byteOffset uint32, v string)
|
||||
```
|
||||
|
||||
WriteString writes a variable-length UTF-8 string field to the object
|
||||
tail and stores its {relOffset, length} pointer at byteOffset in the
|
||||
fixed payload. The typed peer of [zap.ObjectBuilder.SetText] for the
|
||||
Setter-based build path. Read it back with the generated string
|
||||
accessor (which calls v1's zero-copy Object.Text).
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Builder
|
||||
|
||||
Builder is the imperative-style construction API for schema S.
|
||||
Where [Build] takes a closure (more readable, more allocations
|
||||
because the closure escapes), Builder gives the caller direct
|
||||
control over the build sequence — at the cost of three lines
|
||||
instead of one. Use it on hot paths that match the v1 hand-rolled
|
||||
allocation profile (1 alloc per Finish: the buffer itself).
|
||||
|
||||
Usage:
|
||||
|
||||
bb := zapv1.NewBuilderFor[AdvanceTimeSchema]()
|
||||
zapv1.WriteB(bb, examples.AdvanceTimeFields.Time, ts)
|
||||
view, buf := bb.Finish()
|
||||
|
||||
WriteB is the explicit-receiver counterpart to [Write] — both take
|
||||
a (Field, value) pair, but WriteB writes through a [Builder][S]
|
||||
instead of a [Setter][S], allowing escape analysis to keep the
|
||||
builder on the stack.
|
||||
|
||||
Builder is NOT safe for concurrent use; each goroutine should
|
||||
allocate its own.
|
||||
|
||||
Layout: Builder holds two pointers (b, ob) into the v1 builder
|
||||
state. Both pointers' targets are heap-allocated by v1, but the
|
||||
Builder[S] struct itself stays on the caller's stack because it's
|
||||
passed by value.
|
||||
|
||||
|
||||
```go
|
||||
func NewBuilderFor[S Schema]() Builder[S]
|
||||
```
|
||||
|
||||
```go
|
||||
func (bb Builder[S]) AsSetter() Setter[S]
|
||||
```
|
||||
|
||||
```go
|
||||
func (bb Builder[S]) Finish() (View[S], []byte)
|
||||
```
|
||||
|
||||
### ElemSetter
|
||||
|
||||
ElemSetter writes one element at a time into a list of schema E.
|
||||
Always passed by pointer so [WriteList] observes the final count
|
||||
after [each] returns.
|
||||
|
||||
|
||||
```go
|
||||
func (es *ElemSetter[E]) Append(init func(Setter[E]))
|
||||
```
|
||||
|
||||
### Entry
|
||||
|
||||
Entry describes how to handle one schema. The Wrap closure parses
|
||||
a buffer and returns the validated data slice + root offset,
|
||||
reflection-free.
|
||||
|
||||
Name and Kind are duplicated from the Schema for convenient
|
||||
inspection without instantiating the Schema receiver.
|
||||
|
||||
|
||||
### Field
|
||||
|
||||
Field is a phantom-typed accessor for a fixed-size field within
|
||||
schema S. The type parameter T is the field's wire type (one of the
|
||||
[FieldKind] union). At compile time:
|
||||
|
||||
- Read[S, T](View[S], Field[S, T]) returns a T.
|
||||
- Read[S, T](View[S], Field[OtherSchema, T]) is a compile error.
|
||||
- Write[S, T](Setter[S], Field[S, T], v) writes v at the field's
|
||||
offset.
|
||||
- Write[S, T](Setter[OtherSchema], ...) is a compile error.
|
||||
|
||||
The runtime representation of Field is just an offset; the compile-
|
||||
time tag is free. This is what gives v2 zero abstraction cost
|
||||
versus the v1 hand-rolled offset constants.
|
||||
|
||||
Construct Field values once at package init (see the canary
|
||||
AdvanceTime schema in examples/advance_time_tx.go for the pattern).
|
||||
Reusing Field values across goroutines is safe — they are immutable
|
||||
after declaration.
|
||||
|
||||
Implementation note: Go 1.23+ does not allow type parameters on
|
||||
methods of generic types ([View[S].Read[T] would be illegal]). v2
|
||||
therefore exposes Read and Write as top-level generic functions
|
||||
rather than methods on [View] / [Setter]. The call site is at least
|
||||
as terse as a method call: `zapv1.Read(view, F.Time)`.
|
||||
|
||||
|
||||
```go
|
||||
func At[S Schema, T FieldKind](offset uint32) Field[S, T]
|
||||
```
|
||||
|
||||
### FieldKind
|
||||
|
||||
FieldKind is the type-level constraint a [Field]'s value type must
|
||||
satisfy: any Go value type that can be stored in a fixed-size slot.
|
||||
The exhaustive set is the integer + float + Address + Hash + bool
|
||||
family; variable-length values (bytes, text, lists, sub-objects)
|
||||
have their own accessors because their wire representation is a
|
||||
pointer pair, not a primitive.
|
||||
|
||||
We use a comparable union so the generic [Field] type is constrained
|
||||
at compile time. Users who add new fixed-size element types extend
|
||||
this constraint; no other API surface changes.
|
||||
|
||||
|
||||
### KindByte
|
||||
|
||||
KindByte is the one-byte discriminator at offset 0 of every v2
|
||||
object payload. It identifies the schema concretely.
|
||||
|
||||
In v1 this lived as `TxKind uint8` per-package; v2 lifts it into
|
||||
the ZAP layer so the generic [View], [Wrap] and [Registry] code can
|
||||
reason about kind without importing every consumer.
|
||||
|
||||
|
||||
```go
|
||||
func PeekKind(b []byte) (KindByte, error)
|
||||
```
|
||||
|
||||
### List
|
||||
|
||||
List is a typed, zero-copy view into a homogeneous list of objects
|
||||
of schema E. List[E] and List[F] are distinct types at compile time;
|
||||
you cannot accidentally iterate a list of Outputs as if it were a
|
||||
list of Inputs.
|
||||
|
||||
The element type E identifies the schema for each entry. Each call
|
||||
to [List.At] returns a View[E] over the corresponding fixed-size
|
||||
slot, and [List.All] returns an [iter.Seq] for range-over-func.
|
||||
|
||||
Layout: like [View], List is value-typed and contains no pointers
|
||||
other than the slice header for `data`. It carries the absolute byte
|
||||
offset of the first element and the wire-encoded length. Stride
|
||||
derives from E{}.Size() at call time.
|
||||
|
||||
|
||||
```go
|
||||
func ListAt[S, E Schema](v View[S], offset uint32) List[E]
|
||||
```
|
||||
|
||||
```go
|
||||
func (l List[E]) All() iter.Seq[View[E]]
|
||||
```
|
||||
|
||||
```go
|
||||
func (l List[E]) At(i int) View[E]
|
||||
```
|
||||
|
||||
```go
|
||||
func (l List[E]) Indexed() iter.Seq2[int, View[E]]
|
||||
```
|
||||
|
||||
```go
|
||||
func (l List[E]) IsZero() bool
|
||||
```
|
||||
|
||||
```go
|
||||
func (l List[E]) Len() int
|
||||
```
|
||||
|
||||
```go
|
||||
func (l List[E]) Payloads() iter.Seq[[]byte]
|
||||
```
|
||||
|
||||
### ListNested
|
||||
|
||||
ListNested is a typed, zero-copy view into a repeated nested-object
|
||||
field. Each element is reached by following a 4-byte pointer slot to an
|
||||
out-of-line [View[N]]; elements may carry their own variable-length
|
||||
tails. Distinct compile-time type from [List[E]] so an inline-element
|
||||
list and a pointer-element list can never be confused.
|
||||
|
||||
|
||||
```go
|
||||
func ListNestedAt[S, N Schema](v View[S], offset uint32) ListNested[N]
|
||||
```
|
||||
|
||||
```go
|
||||
func (l ListNested[N]) All() iter.Seq[View[N]]
|
||||
```
|
||||
|
||||
```go
|
||||
func (l ListNested[N]) At(i int) View[N]
|
||||
```
|
||||
|
||||
```go
|
||||
func (l ListNested[N]) IsZero() bool
|
||||
```
|
||||
|
||||
```go
|
||||
func (l ListNested[N]) Len() int
|
||||
```
|
||||
|
||||
### NestedElemSetter
|
||||
|
||||
NestedElemSetter builds the elements of a repeated nested-object field,
|
||||
one out-of-line object per [Append]. It records each object's position
|
||||
so [WriteListNested] can emit the pointer array after the last element.
|
||||
|
||||
|
||||
```go
|
||||
func (es *NestedElemSetter[N]) Append(init func(Setter[N]))
|
||||
```
|
||||
|
||||
### Pool
|
||||
|
||||
Pool is a generic, type-safe sync.Pool wrapper. Instead of writing a
|
||||
dedicated sync.Pool + Get + Put per type as v1 did, register a
|
||||
constructor once:
|
||||
|
||||
var ViewPool = zapv1.NewPool(func() *View[AdvanceTimeSchema] {
|
||||
return &View[AdvanceTimeSchema]{}
|
||||
})
|
||||
|
||||
v := ViewPool.Get()
|
||||
defer ViewPool.Put(v)
|
||||
|
||||
Get returns a *T; Put returns it to the pool. The generic guarantees
|
||||
that a value pulled from Pool[A] cannot be returned to Pool[B]
|
||||
(would not type-check).
|
||||
|
||||
Pool itself is safe for concurrent use (delegated to [sync.Pool]).
|
||||
|
||||
|
||||
```go
|
||||
func NewPool[T any](new func() *T) *Pool[T]
|
||||
```
|
||||
|
||||
```go
|
||||
func (p *Pool[T]) Get() *T
|
||||
```
|
||||
|
||||
```go
|
||||
func (p *Pool[T]) Put(t *T)
|
||||
```
|
||||
|
||||
### Raw
|
||||
|
||||
Raw is the non-generic shape of [View]: same three fields, no
|
||||
schema type parameter. It exists as the value-shape that the per-
|
||||
schema Wrap shims construct in their hot path. Construction is via
|
||||
[WrapRaw] / [WrapRawUnchecked]; the public typed View[S] wraps a
|
||||
Raw with one struct cast.
|
||||
|
||||
Treat Raw as an implementation detail of the per-schema shim
|
||||
constructors — application code uses [View[S]] / [Wrap].
|
||||
|
||||
|
||||
```go
|
||||
func RawFromSlices(data []byte, rootOff, end int) Raw
|
||||
```
|
||||
|
||||
```go
|
||||
func WrapRaw(b []byte, wantKind uint8, size int, name string) (Raw, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func WrapRawUnchecked(b []byte, size int) (Raw, error)
|
||||
```
|
||||
|
||||
### Registry
|
||||
|
||||
Registry is a kind-byte → schema-typed wrapper dispatcher. Each
|
||||
registered schema produces an [Entry] that knows, at runtime, how
|
||||
to wrap a buffer as the right typed View. The Entry itself was
|
||||
generated at compile time when [Register] was called, so dispatch
|
||||
requires no reflection and no per-call allocation — only a map
|
||||
lookup.
|
||||
|
||||
Registry is safe for concurrent reads; Register is intended for use
|
||||
at package init and is guarded by a mutex against concurrent
|
||||
init-time races.
|
||||
|
||||
Hickey decomposition: Registry is a value (map of values). The
|
||||
"what kind is this?" question is answered by looking at a byte;
|
||||
the "how do I parse it?" question is answered by an Entry value;
|
||||
there is no inheritance, no interface tower, no "TxFactory".
|
||||
|
||||
|
||||
```go
|
||||
func NewRegistry() *Registry
|
||||
```
|
||||
|
||||
```go
|
||||
func (r *Registry) Entries() []Entry
|
||||
```
|
||||
|
||||
```go
|
||||
func (r *Registry) Lookup(kind KindByte) (Entry, bool)
|
||||
```
|
||||
|
||||
### Schema
|
||||
|
||||
Schema describes a v2 message shape. A type implements Schema by
|
||||
declaring its kind byte, its fixed object size (in bytes within the
|
||||
data segment), and a human-readable name.
|
||||
|
||||
A schema is a value, not a place. Implementations are typically
|
||||
zero-sized marker structs whose name documents the wire shape:
|
||||
|
||||
type AdvanceTimeSchema struct{}
|
||||
func (AdvanceTimeSchema) Kind() KindByte { return 0x14 }
|
||||
func (AdvanceTimeSchema) Size() int { return 9 }
|
||||
func (AdvanceTimeSchema) Name() string { return "AdvanceTimeTx" }
|
||||
|
||||
Implementations MUST be safe to use as the zero value of their type
|
||||
— the methods consult only the receiver type, never any state.
|
||||
This invariant is what lets Registry.Register and reflect-free
|
||||
generic dispatch work.
|
||||
|
||||
|
||||
### SchemaError
|
||||
|
||||
SchemaError is returned when a buffer does not match the expected
|
||||
schema (wrong kind byte, undersized object payload, etc).
|
||||
|
||||
|
||||
```go
|
||||
func NewSchemaError(want, got KindByte, name string) *SchemaError
|
||||
```
|
||||
|
||||
```go
|
||||
func (e *SchemaError) Error() string
|
||||
```
|
||||
|
||||
### Setter
|
||||
|
||||
Setter is the write-side counterpart to [View]. A Setter[S] writes
|
||||
fields into the buffer for schema S. Like View, Setter is phantom-
|
||||
typed: a Field[S, uint64] cannot be written through Setter[T].
|
||||
|
||||
Setter holds two pointers (ob, b) — both into the active builder.
|
||||
These pointers' targets always outlive the Setter (the builder owns
|
||||
them and is itself reachable from the caller's stack). Setter values
|
||||
are NOT safe to retain after the builder's Finish returns.
|
||||
|
||||
|
||||
```go
|
||||
func SetterFrom[S Schema](ob zap.ObjectBuilder, b *zap.Builder) Setter[S]
|
||||
```
|
||||
|
||||
### View
|
||||
|
||||
View is a typed, zero-copy reference into a ZAP buffer. The type
|
||||
parameter S identifies the schema; a View[A] and a View[B] are
|
||||
distinct types and the compiler refuses to mix them. This is what
|
||||
makes [Field] accessors safe: the compiler will not let you pass a
|
||||
Field[A, uint64] to a View[B].Read.
|
||||
|
||||
View carries no per-instance schema state — the schema's identity
|
||||
lives in the type parameter, and S{}'s methods (Kind/Size/Name) are
|
||||
queried only at boundaries (Wrap, Build, Registry). Inside the hot
|
||||
path the type parameter is purely a compile-time tag; no boxing, no
|
||||
interface dispatch.
|
||||
|
||||
View is safe to copy. The underlying buffer must outlive every View
|
||||
that references it.
|
||||
|
||||
Layout: a View holds two slice headers — `data` aliases the full ZAP
|
||||
buffer (used by variable-length tail accessors: list, bytes), and
|
||||
`payload` aliases the fixed section of the root object (used by
|
||||
Read/Write for direct unsafe-pointer indexing). Neither field is a
|
||||
pointer to a heap-allocated `*zap.Message` — that was the v1.0 design
|
||||
and forced a heap allocation per Wrap because the *Message escaped
|
||||
from the View's lifetime. The v1.1 redesign keeps everything in slice
|
||||
headers; View[S] is 56 bytes (3 slice headers + an int32 offset);
|
||||
stack-allocatable; escape-analysis-friendly.
|
||||
|
||||
|
||||
```go
|
||||
func AsView[S Schema](r Raw) View[S]
|
||||
```
|
||||
|
||||
```go
|
||||
func Build[S Schema](init func(Setter[S])) (View[S], []byte)
|
||||
```
|
||||
|
||||
```go
|
||||
func NestedAt[S, N Schema](v View[S], offset uint32) View[N]
|
||||
```
|
||||
|
||||
```go
|
||||
func Wrap[S Schema](b []byte) (View[S], error)
|
||||
```
|
||||
|
||||
```go
|
||||
func WrapAs[S Schema](b []byte) (View[S], error)
|
||||
```
|
||||
|
||||
```go
|
||||
func WrapUnchecked[S Schema](b []byte) (View[S], error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (v View[S]) Bytes() []byte
|
||||
```
|
||||
|
||||
```go
|
||||
func (v View[S]) IsZero() bool
|
||||
```
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["codegen"]}
|
||||
@@ -0,0 +1,281 @@
|
||||
---
|
||||
title: "zapclient"
|
||||
description: "Package zapclient"
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/zapclient"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### MustOpcode
|
||||
|
||||
```go
|
||||
func MustOpcode(name string) uint16
|
||||
```
|
||||
|
||||
MustOpcode is ProcedureOpcode that panics on error. Use in
|
||||
package-level var initialisers where a bad procedure name is a
|
||||
build-time bug.
|
||||
|
||||
|
||||
### ProcedureOpcode
|
||||
|
||||
```go
|
||||
func ProcedureOpcode(name string) (uint16, error)
|
||||
```
|
||||
|
||||
ProcedureOpcode returns the uint16 opcode for a procedure name.
|
||||
The lower 8 bits are zero (matching the existing MsgType<<8 shape
|
||||
in lux/zap); the upper 8 bits are FNV-1a(name) modulo 254 + 1.
|
||||
|
||||
Returns ErrReservedOpcode if the hash lands on 0x00 or 0xFF.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### AllowListVerifier
|
||||
|
||||
AllowListVerifier accepts only peers whose NodeID appears in
|
||||
Allowed[procedure]. Empty Allowed[procedure] means the procedure is
|
||||
not exposed.
|
||||
|
||||
|
||||
```go
|
||||
func (a AllowListVerifier) Verify(ctx context.Context, peer PeerInfo, procedure string) error
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
Client is a native ZAP RPC client. Created by Connect; closed by
|
||||
Close (or returned stop function from MustConnect).
|
||||
|
||||
|
||||
```go
|
||||
func Connect(ctx context.Context, serviceType string, opts ...ClientOption) (*Client, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func MustConnect(ctx context.Context, serviceType string, opts ...ClientOption) *Client
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Broadcast(ctx context.Context, procedure string, req *zap.Message) map[string]error
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Call(ctx context.Context, procedure string, req *zap.Message) (*zap.Message, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Close() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Peers() []Peer
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Send(ctx context.Context, procedure string, req *zap.Message) error
|
||||
```
|
||||
|
||||
### ClientOption
|
||||
|
||||
ClientOption is the functional-option constructor knob.
|
||||
|
||||
|
||||
```go
|
||||
func WithBrowseInterval(d time.Duration) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithCallTimeout(d time.Duration) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithDiscoverTimeout(d time.Duration) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithDiscovery(d Discovery) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithLogger(l *slog.Logger) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithMinPeers(n int) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithNodeID(id string) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithPicker(p Picker) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithStaticPeers(addrs ...string) ClientOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithTLS(cfg *tls.Config) ClientOption
|
||||
```
|
||||
|
||||
### ClientOptions
|
||||
|
||||
ClientOptions configure Connect. Construct via WithX options.
|
||||
|
||||
|
||||
### Discovery
|
||||
|
||||
Discovery is the peer-enumeration contract.
|
||||
|
||||
Implementations MUST be goroutine-safe; the client may call Peers()
|
||||
from every in-flight Call and Send.
|
||||
|
||||
|
||||
### LocalTrustVerifier
|
||||
|
||||
LocalTrustVerifier accepts any peer presenting a valid mTLS cert
|
||||
from the cluster CA. The underlying TLS handshake already verified
|
||||
the chain; this verifier returns nil for every authenticated peer.
|
||||
|
||||
Use when:
|
||||
- the peer is on the cluster-private network
|
||||
- the listener was constructed with the cluster CA in ClientCAs
|
||||
- all in-cluster services should be allowed to call all
|
||||
procedures (apply per-procedure RBAC in the handler if needed)
|
||||
|
||||
|
||||
```go
|
||||
func (LocalTrustVerifier) Verify(ctx context.Context, peer PeerInfo, procedure string) error
|
||||
```
|
||||
|
||||
### Peer
|
||||
|
||||
Peer is the discovery view of one reachable instance of a service.
|
||||
|
||||
NodeID uniquely identifies the peer within ServiceType across
|
||||
discovery cycles. Address is the dial target (host:port). For
|
||||
in-cluster peers under mTLS, the authenticated peer identity comes
|
||||
from the TLS session — NodeID is a hint, not a trust anchor.
|
||||
|
||||
|
||||
### PeerInfo
|
||||
|
||||
PeerInfo is the authenticated peer identity surfaced to server-side
|
||||
handlers and PeerVerifier.
|
||||
|
||||
|
||||
### PeerVerifier
|
||||
|
||||
PeerVerifier is the server-side authorisation hook. Runs on every
|
||||
inbound message before procedure dispatch — return non-nil to
|
||||
reject the call.
|
||||
|
||||
The PeerInfo carries the authenticated peer identity (NodeID +
|
||||
mTLS cert chain if present). Procedure is the procedure name the
|
||||
caller requested. PeerVerifier should compare the (peer, procedure)
|
||||
pair against the service's authorisation policy.
|
||||
|
||||
|
||||
### Picker
|
||||
|
||||
Picker selects one peer from a Discovery snapshot for a single
|
||||
Call or Send. Implementations MUST be cheap (sub-microsecond) and
|
||||
goroutine-safe; the client invokes Pick from every concurrent op.
|
||||
|
||||
|
||||
### ProcedureHandler
|
||||
|
||||
ProcedureHandler runs server-side for one inbound procedure call.
|
||||
Return nil + nil-err to ack a fire-and-forget Send. Return a
|
||||
response + nil-err to reply to a Call. Return nil + non-nil err to
|
||||
signal failure; the client side observes the err on its Call.
|
||||
|
||||
|
||||
### RoundRobinPicker
|
||||
|
||||
RoundRobinPicker rotates through peers in registration order.
|
||||
Zero-value is ready to use; goroutine-safe.
|
||||
|
||||
|
||||
```go
|
||||
func (p *RoundRobinPicker) Pick(peers []Peer) (Peer, error)
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
Server is the native ZAP server side: procedure-name dispatch on
|
||||
top of *zap.Node. Construct with NewServer, Register procedures,
|
||||
Start to begin accepting, Stop to release.
|
||||
|
||||
|
||||
```go
|
||||
func NewServer(serviceType string, opts ...ServerOption) (*Server, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) Dispatch(ctx context.Context, from string, msg *zap.Message) (*zap.Message, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) NodeID() string
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) Register(procedure string, h ProcedureHandler) error
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) Start() error
|
||||
```
|
||||
|
||||
```go
|
||||
func (s *Server) Stop()
|
||||
```
|
||||
|
||||
### ServerOption
|
||||
|
||||
ServerOption is a functional-option knob.
|
||||
|
||||
|
||||
```go
|
||||
func WithNoDiscovery() ServerOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithServerLogger(l *slog.Logger) ServerOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithServerMetadata(m map[string]string) ServerOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithServerNodeID(id string) ServerOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithServerPort(p int) ServerOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithServerTLS(cfg *tls.Config) ServerOption
|
||||
```
|
||||
|
||||
```go
|
||||
func WithVerifier(v PeerVerifier) ServerOption
|
||||
```
|
||||
|
||||
### ServerOptions
|
||||
|
||||
ServerOptions configure NewServer.
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "main"
|
||||
description: "Command echoserver runs a zapweb (ZAP-over-WebSocket) echo service for cross-language integration testing."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/zapweb/cmd/echoserver"
|
||||
```
|
||||
|
||||
Command echoserver runs a zapweb (ZAP-over-WebSocket) echo service for
|
||||
cross-language integration testing. It registers test.echo (prefixes
|
||||
its text@0 argument with "echo:") and serves it at /zap, so the
|
||||
TypeScript client in zapweb/ts can prove the browser path is wire-
|
||||
compatible end to end.
|
||||
|
||||
go run ./zapweb/cmd/echoserver # serves :8089/zap
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["echoserver"]}
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "zapweb"
|
||||
description: "Package zapweb serves ZAP procedures over WebSocket so browser clients — which cannot open a raw TCP/QUIC socket — reach the EXACT same zapclient.Server procedure dispatch as native in-cluster peers."
|
||||
---
|
||||
|
||||
{/* Generated from godoc by tools/docgen — edit the Go source, not this file. */}
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/zap/zapweb"
|
||||
```
|
||||
|
||||
Package zapweb serves ZAP procedures over WebSocket so browser clients
|
||||
— which cannot open a raw TCP/QUIC socket — reach the EXACT same
|
||||
zapclient.Server procedure dispatch as native in-cluster peers.
|
||||
|
||||
One dispatch path, two transports:
|
||||
|
||||
- native ZAP (TCP/QUIC), via zap.Node, for service-to-service calls
|
||||
- zapweb (WebSocket), via this package, for browser ↔ service calls
|
||||
|
||||
There is no REST here. The WebSocket handshake is an HTTP/1.1 Upgrade;
|
||||
everything after it is binary ZAP frames, dispatched through
|
||||
zapclient.Server.Dispatch (the same opcode routing + PeerVerifier the
|
||||
native node uses).
|
||||
|
||||
# Wire format (one WebSocket BINARY message per call)
|
||||
|
||||
[ reqID uint32 LE ] correlation id — echoed on the reply
|
||||
[ flag uint32 LE ] zap.ReqFlagReq on request, zap.ReqFlagResp on
|
||||
reply, FlagErr on a dispatch-level failure
|
||||
[ zap.Message bytes ] the procedure message; its Flags() header
|
||||
carries the procedure opcode, identical to the
|
||||
native transport. On a FlagErr reply the tail
|
||||
is a UTF-8 error string instead.
|
||||
|
||||
The JS/TS client builds byte-identical frames (see zapweb/ts), so the
|
||||
browser and a Go caller speak the same protocol to the same handlers.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### Handler
|
||||
|
||||
```go
|
||||
func Handler(srv *zapclient.Server, opts Options) http.Handler
|
||||
```
|
||||
|
||||
Handler returns an http.Handler that upgrades to WebSocket and bridges
|
||||
each binary frame to srv.Dispatch. Mount it on the daemon's listener
|
||||
(e.g. at "/zap"); the same zapclient.Server already serves native ZAP
|
||||
peers, so browsers and services share one procedure registry.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### Client
|
||||
|
||||
Client is a zapweb (ZAP-over-WebSocket) client. Construct with Dial,
|
||||
always Close.
|
||||
|
||||
|
||||
```go
|
||||
func Dial(ctx context.Context, url string) (*Client, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Call(ctx context.Context, procedure string, req *zap.Message) (*zap.Message, error)
|
||||
```
|
||||
|
||||
```go
|
||||
func (c *Client) Close() error
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
Options configure Handler.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["cmd"]}
|
||||
Reference in New Issue
Block a user