mirror of
https://github.com/luxfi/kms.git
synced 2026-07-26 23:59:24 +00:00
build: resync vendor/ so the kms-operator image can build again
ghcr.io/luxfi/kms-operator has had no new image since v1.12.3. The Build KMS
Operator workflow failed on every tag since (v1.12.4 … v1.12.7) while its
sibling Build KMS succeeded on the same tag, so the tags looked published.
Dockerfile.operator:27 builds with -mod=vendor, and vendor/ had drifted from
go.mod in both directions:
- go.opentelemetry.io/otel/{metric,trace}: in vendor/modules.txt, not in go.mod
- github.com/luxfi/{crypto,geth,ids,keys,zap,address,age,cache,constants,
container,formatting,math,go-bip32,go-bip39}: in go.mod, not marked explicit
`go mod vendor` alone could not fix it — it aborted first on go.sum entries
missing for the k8s packages cmd/kms-operator imports (k8s.io/api/core/v1,
k8s.io/apimachinery/..., k8s.io/client-go/{dynamic,kubernetes,rest}). So:
go mod tidy (adds them, drops now-unused indirects) then go mod vendor.
Verified by stash control:
operator, -mod=vendor: rc=1 -> rc=0 ("inconsistent vendoring" 1 -> 0)
server, -mod=vendor: rc=1 -> rc=1 (unchanged)
The server's vendor build was already broken and stays broken, for an unrelated
reason: blst.h has never been vendored (confirmed absent from HEAD's tree, so it
could not have worked before either) — go mod vendor does not copy cgo headers
that live outside the package dir. It is not a regression and does not affect
CI: the server's Dockerfile builds with GOFLAGS=-mod=mod after `rm -f go.sum &&
go mod download`, i.e. from the module cache, never from vendor/. The operator
is the only -mod=vendor consumer, which is exactly why it was the only failure.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
@@ -19,18 +19,14 @@ require (
|
||||
|
||||
require (
|
||||
filippo.io/hpke v0.4.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.6 // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/consensys/gnark-crypto v0.20.1 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.7 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
@@ -57,7 +53,6 @@ require (
|
||||
github.com/luxfi/constants v1.6.2 // indirect
|
||||
github.com/luxfi/container v0.2.1 // indirect
|
||||
github.com/luxfi/crypto v1.20.2 // indirect
|
||||
github.com/luxfi/crypto/ipa v1.2.4 // indirect
|
||||
github.com/luxfi/formatting v1.1.1 // indirect
|
||||
github.com/luxfi/geth v1.20.1 // indirect
|
||||
github.com/luxfi/go-bip32 v1.1.0 // indirect
|
||||
@@ -66,8 +61,6 @@ require (
|
||||
github.com/luxfi/mdns v0.1.1 // indirect
|
||||
github.com/luxfi/metric v1.8.1 // indirect
|
||||
github.com/luxfi/mock v0.1.1 // indirect
|
||||
github.com/luxfi/pq v1.1.0 // indirect
|
||||
github.com/luxfi/proto v1.4.2 // indirect
|
||||
github.com/luxfi/protocol v0.0.2 // indirect
|
||||
github.com/luxfi/sampler v1.1.0 // indirect
|
||||
github.com/luxfi/tls v1.1.1 // indirect
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
|
||||
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
|
||||
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
|
||||
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
||||
@@ -32,29 +34,30 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/consensys/gnark-crypto v0.20.1 h1:PXDUBvk8AzhvWowHLWBEAfUQcV1/aZgWIqD6eMpXmDg=
|
||||
github.com/consensys/gnark-crypto v0.20.1/go.mod h1:RBWrSgy+IDbGR69RRV313th3M/aZU1ubk2om+qHuTSc=
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc=
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.7 h1:aat3CuITdDbPC6pmEGRT0zJ5eOxzrZj8TJT5z7Xk//M=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.7/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
@@ -66,10 +69,16 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
@@ -81,11 +90,16 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
|
||||
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
|
||||
@@ -98,8 +112,10 @@ github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXei
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
@@ -112,89 +128,65 @@ github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu
|
||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/luxfi/accel v1.2.4 h1:5VbIHyEvvfobn2zBiTFODxDw1CeqxCepZOLlvkuf9yQ=
|
||||
github.com/luxfi/accel v1.2.4/go.mod h1:ISIwAX+ZfsL/S5nsP2JvfldXN6Nc+QzoWf6Jtaq+xsQ=
|
||||
github.com/luxfi/address v1.0.1/go.mod h1:5j3Eh66v9zvv1GbNdZwt+23krV8JlSDaRzmWZU8ZRM0=
|
||||
github.com/luxfi/address v1.1.1 h1:4afWzyBWzTiZN7RenBtdMC9LIvP9L4CSBzSquwKEAgI=
|
||||
github.com/luxfi/address v1.1.1/go.mod h1:KG0jUBcgoJYeieKP5jboCq9UewwDBIOus8ZCqUMVlw8=
|
||||
github.com/luxfi/age v1.5.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
|
||||
github.com/luxfi/age v1.6.0 h1:KMD8gSOP4NVCb7NWSlRcgBZNV2xm2a+qQWPyPmiX6f4=
|
||||
github.com/luxfi/age v1.6.0/go.mod h1:7cu9CIyikgyAvr5MlXFapEDQ15yBaHOSdKkK5lG04WE=
|
||||
github.com/luxfi/cache v1.2.1 h1:kAzOS55/hmYeNKR+0HAKv4ma48Y6JjkI8UQeqdZ8bfI=
|
||||
github.com/luxfi/cache v1.2.1/go.mod h1:co7JTxZZHpKT31Yh01LFp5aZOxmoUg157FhBLQdQHVU=
|
||||
github.com/luxfi/cache v1.3.1 h1:grQhi/B5GKypG7avDMeY143QTgFbfEvQICKNIh1Cw6U=
|
||||
github.com/luxfi/cache v1.3.1/go.mod h1:2MokdbeNUy/9O3mdREWkE6BiN7tRvePkXiKkcb+4M7g=
|
||||
github.com/luxfi/constants v1.5.8/go.mod h1:Pu5jWHdnUtQRbWC43yTUjU/pbIIKMDOd2a2yroSfo48=
|
||||
github.com/luxfi/constants v1.6.2 h1:pXHdKIFbfE9qX4xOjq2LxYvagNhhNvspUVEbPcIEKfA=
|
||||
github.com/luxfi/constants v1.6.2/go.mod h1:r0oH8C/+r/XFYBq1AJxt6zWRKKRKgDzrEMop/CCs9rI=
|
||||
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
|
||||
github.com/luxfi/container v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
|
||||
github.com/luxfi/container v0.2.1 h1:MTnfKXzS5+oxV5jKZerdOxSA6iMPaQI9/FWGufizzaw=
|
||||
github.com/luxfi/container v0.2.1/go.mod h1:B+uM0wP0lGvt/SSK7QOEn/qBcsHzILVHlKikdCyzSgM=
|
||||
github.com/luxfi/crypto v1.19.27 h1:K4l9nsnB0t61zHO5u8si06zVzW5WTjWW+/6+KRb/bfo=
|
||||
github.com/luxfi/crypto v1.19.27/go.mod h1:0DCU62kX8+zhYU2qeM07A4pifJyPkPujnUOfgc8TOFQ=
|
||||
github.com/luxfi/crypto v1.20.2 h1:L81WEsU/hs2A76F5PWBusG0yU74QqkDdUqqgexWUxh4=
|
||||
github.com/luxfi/crypto v1.20.2/go.mod h1:qYHOM0lO4PRh7LEaObxFQUIMjmT1/paVm/WgZkobT1k=
|
||||
github.com/luxfi/crypto/ipa v1.2.4 h1:6xfwhI9/HrcDkF3Ti5/NxsNQIWbwYDJmRSNIHRQ/xfU=
|
||||
github.com/luxfi/crypto/ipa v1.2.4/go.mod h1:43J6f6rcfUMrZt4cQectMOZb6Ps+fAEj8ZTPC3Kk+gE=
|
||||
github.com/luxfi/formatting v1.0.1/go.mod h1:mYzNf5DJOiqSSKUPzNj5dKy4tstFbN3pZlkI5716eKc=
|
||||
github.com/luxfi/formatting v1.1.1 h1:MJhVXIPh1dbysvYEjtaEA/Z0FUTiI7n0DwOF54FS08c=
|
||||
github.com/luxfi/formatting v1.1.1/go.mod h1:zhBWp6fLZduhpiAdPgVDdPVOyhw4FvwRUksF6+xKQCE=
|
||||
github.com/luxfi/geth v1.17.12 h1:UP/fhpcfbGPTrkOCwX3d88Oc3jVm5gTOgfjgq+lek6s=
|
||||
github.com/luxfi/geth v1.17.12/go.mod h1:3vQfQJd9JC+AVBjxNXa9PYQOqpbE2dKu8E3jqhPZ3LU=
|
||||
github.com/luxfi/geth v1.20.1 h1:QUGQr4AKvADjwMi7t8a0OfoyxShgEcI9pwie1jFYfm0=
|
||||
github.com/luxfi/geth v1.20.1/go.mod h1:GV5bIMEgWviRN+jPXERyVpI16H3iHqPcdIokDoZdrvU=
|
||||
github.com/luxfi/go-bip32 v1.0.2/go.mod h1:bc7/LXDKAJQZ/F0Xjf5yXaTZxY9/ssLb4FC+Hxn/cDk=
|
||||
github.com/luxfi/go-bip32 v1.1.0 h1:zjy19WKa1KJnGamRNyOZM3l/MPtV/sax7M4NMPwLHZQ=
|
||||
github.com/luxfi/go-bip32 v1.1.0/go.mod h1:QyDXlzWL3xRiCbMUi4Z3J52Q/SMoCvdRLXXlYvSZor8=
|
||||
github.com/luxfi/go-bip39 v1.1.2/go.mod h1:96de9VkR2kY/ASAnhMtvt3TSh+PZkAFAngNj0GjRGDo=
|
||||
github.com/luxfi/go-bip39 v1.2.0 h1:fx3pFuSGawCG4In6pA4OLLStqbgIqD1j8EygFskoHzY=
|
||||
github.com/luxfi/go-bip39 v1.2.0/go.mod h1:if+2OVbG4k4jKIuBt/Rse1KV1kgWQM5j5xFbUtwbNtc=
|
||||
github.com/luxfi/ids v1.2.15 h1:omE+E4+0Poj9DzM11ejSFgteaSQ3KDHi5g54iH6jcxI=
|
||||
github.com/luxfi/ids v1.2.15/go.mod h1:Fj73K5xcblvdE0SxU/ip+jE8VqNdu+80548su5KJ7xI=
|
||||
github.com/luxfi/ids v1.3.2 h1:c6Rft5kZB4XqiCtWaGH47bfhaNFm3FGRfhEzI01GVeI=
|
||||
github.com/luxfi/ids v1.3.2/go.mod h1:+5l8cYMbKpORJbQ2r98CYJo9TQATgUdnmzpYFZWMwwc=
|
||||
github.com/luxfi/keys v1.1.0/go.mod h1:U3tZNDmv3nXkPoZwLtq9RNjwyN0XyoN29worigfT+c0=
|
||||
github.com/luxfi/keys v1.4.1 h1:2Zcoovaz9OLPz7m7VGXfRrGnrlqt0GeUpJclsPBi4EU=
|
||||
github.com/luxfi/keys v1.4.1/go.mod h1:P8EUP5DKrR1SUZBGZjDT3rWcp2P1miUlVh7IBRNBphU=
|
||||
github.com/luxfi/log v1.4.3 h1:xkUKRWvQ4ZwvlUC2e0/RTtHYZOYSMvSQ9W9lbjwBmiI=
|
||||
github.com/luxfi/log v1.4.3/go.mod h1:myIkufyiQomSQH34K981kbz6cG4WUoerRUh7F4XhlQI=
|
||||
github.com/luxfi/math v1.4.1 h1:1t9bCCsEqnl9yIKrShlbs80DBKyYTWdnzkVfBqEeO7Q=
|
||||
github.com/luxfi/math v1.4.1/go.mod h1:QvbRxauQyE1w4lvbcLSe6c8yeJz2Zj1Bq1rayGgs2tA=
|
||||
github.com/luxfi/math v1.5.1 h1:FDOY75e4vn/Xra1ij99xOS/9XdxQGCPP6HONHRkCwfg=
|
||||
github.com/luxfi/math v1.5.1/go.mod h1:3j9R24hVfPhrbvs45YSJP7jAyVNfwx/cj/+lAO8IGro=
|
||||
github.com/luxfi/math/big v0.1.0 h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
|
||||
github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk=
|
||||
github.com/luxfi/mdns v0.1.1 h1:g2eRr9AXcziPkkcd24M+Qu9ApEpoKKjfI79QSNqv0rQ=
|
||||
github.com/luxfi/mdns v0.1.1/go.mod h1:dbp5f3h3aE7CGzwbaWzBM9cwdcekhmSrWhQevgYhhNA=
|
||||
github.com/luxfi/metric v1.5.8 h1:axPwfq+erOlIue7IJp5g+hMcMtVhYHja9gJAaT3+KNA=
|
||||
github.com/luxfi/metric v1.5.8/go.mod h1:fO2giazkg4NDtr72JM/QXJBYebplAMeWC1JoZyNDvKw=
|
||||
github.com/luxfi/metric v1.8.1 h1:v58GgPFAOLPVxSa/JiNLwqJQNEFHdWbXZV28piMXX4s=
|
||||
github.com/luxfi/metric v1.8.1/go.mod h1:R1OPAIeW4UBW3osK7j2r3/XPmczfNRFTXg4bnlemTuE=
|
||||
github.com/luxfi/mock v0.1.1 h1:0HEtIjg1J6CWz+IUyP6rsGqNWTcmxjFnSQIhaDuARwY=
|
||||
github.com/luxfi/mock v0.1.1/go.mod h1:jo35akl3Vtd8LbzDts8VJ0jmSVycrd1/eBi6g6t5hKU=
|
||||
github.com/luxfi/pq v1.0.3 h1:ksw1dmfTR0dqqNMRS7BjGcprCO2Fhc+3Iiq2/NMuONw=
|
||||
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
|
||||
github.com/luxfi/proto v1.3.4/go.mod h1:S80KrTzISCktptit/pWLzk6nHgagjJS86qbQL6gHutE=
|
||||
github.com/luxfi/pq v1.1.0 h1:ADplfUSyirLymSxs3Ix0HeDTyl5oswCNUpXJt/5vLY8=
|
||||
github.com/luxfi/pq v1.1.0/go.mod h1:KT5rG9ztpzIkT9QSnXK4WFqBBLzKCLjY7l1c/unBi8I=
|
||||
github.com/luxfi/protocol v0.0.2 h1:TAuXMm1K4VDpG3B3brq1EE9Lb7XlbhQmVBObskgNhmc=
|
||||
github.com/luxfi/protocol v0.0.2/go.mod h1:Yjn2VRwn5PXim0+JTalAyrVG6YbmuSnYOfeUXSbAsqA=
|
||||
github.com/luxfi/sampler v1.1.0 h1:u3iRDl7V06ARh0e85h3HT+aZ1saCFo2yMMsh+dCJbqk=
|
||||
github.com/luxfi/sampler v1.1.0/go.mod h1:kJa53S3tC9+VSbuV3RFu68MmbCCBlr2UM39LOClQ/Hs=
|
||||
github.com/luxfi/tls v1.0.3/go.mod h1:dQqSiGE7YxXUxOwICoReUuIitBms9DYOaCeteBwmIWw=
|
||||
github.com/luxfi/tls v1.1.1 h1:BSZ0gHSp7U8vzlmzx7WSSCz+b7Ky4JtD9HDDhn7vrDg=
|
||||
github.com/luxfi/tls v1.1.1/go.mod h1:+5TDy8UtLL+tz124brZzpUDBRj+sKrq0JFqdmpMUHgw=
|
||||
github.com/luxfi/vm v1.2.3/go.mod h1:ZXmw1sKA6GL3Ma3BfYUv5Fzvx3aWu2QMtkxF3lL/3R4=
|
||||
github.com/luxfi/vm v1.3.1 h1:rLCbygaajehVkUoJ5czwhpUAJaC5J5okq3p+j2QSPSo=
|
||||
github.com/luxfi/vm v1.3.1/go.mod h1:uViH3COP8hhCbj42v1MTohkPCDwYQCZanNIOb/StWqY=
|
||||
github.com/luxfi/zap v0.7.2 h1:YecWTWNE5PPJXL56sLIkzS8b23bprUwZ5lPAQuLUtTE=
|
||||
github.com/luxfi/zap v0.7.2/go.mod h1:1k+nwT+JW802YzuPAuf7CxMSGr/qxvbGgGwi5k6X9Ok=
|
||||
github.com/luxfi/zap v1.2.6 h1:NBpbm9Gib41Oi/XAkAZKQ3hb+xCafo7JsrUjw+bKiAc=
|
||||
github.com/luxfi/zap v1.2.6/go.mod h1:sTAe/AMMamoE85cVoe81+NbqHJkgvqS0LhY9ByHEmr0=
|
||||
github.com/luxfi/zapdb v1.10.0 h1:1lLHEmkyC0BucnA/zjQYsMkUVxuEo2vQkEaQGjYfuuc=
|
||||
github.com/luxfi/zapdb v1.10.0/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
@@ -210,33 +202,47 @@ github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEp
|
||||
github.com/minio/minio-go/v7 v7.0.100 h1:ShkWi8Tyj9RtU57OQB2HIXKz4bFgtVib0bbT1sbtLI8=
|
||||
github.com/minio/minio-go/v7 v7.0.100/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI=
|
||||
github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
||||
github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -244,28 +250,28 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE=
|
||||
github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
||||
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
@@ -294,6 +300,7 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -314,12 +321,14 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -343,9 +352,12 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
@@ -354,14 +366,25 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE=
|
||||
k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug=
|
||||
k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0=
|
||||
k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo=
|
||||
k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
# Hanzo Address
|
||||
|
||||
## Overview
|
||||
Go module: github.com/luxfi/address
|
||||
|
||||
## Tech Stack
|
||||
- **Language**: Go
|
||||
|
||||
## Build & Run
|
||||
```bash
|
||||
go build ./...
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Structure
|
||||
```
|
||||
address/
|
||||
LICENSE
|
||||
address.go
|
||||
converter.go
|
||||
go.mod
|
||||
go.sum
|
||||
```
|
||||
|
||||
## Key Files
|
||||
- `go.mod` -- Go module definition
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD-3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD-3-Clause License terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# age
|
||||
|
||||
**Org:** luxfi · **Ecosystem:** lux · **Path:** `/Users/a/work/lux/luxfi/age`
|
||||
**Origin:** https://github.com/luxfi/age.git
|
||||
|
||||
## Discovery
|
||||
|
||||
This file (`CLAUDE.md`) is the canonical agent-facing readme; `LLM.md` is a symlink to it. Update either name and both stay in sync.
|
||||
|
||||
## Where to look first
|
||||
|
||||
- `README.md` — human-facing overview (if present)
|
||||
- `package.json` / `Cargo.toml` / `pyproject.toml` / `go.mod` — language & deps
|
||||
- `.github/workflows/` — CI surface
|
||||
- `docs/` — extended docs (if present)
|
||||
|
||||
## Sibling repos
|
||||
|
||||
See the org-level `LLM.md` at `/Users/a/work/lux/luxfi/LLM.md` for the full inventory of sibling repos and inter-repo dependencies.
|
||||
+2
-2
@@ -74,8 +74,8 @@ REPLICATE_AGE_RECIPIENTS_FALLBACK=age1pq1... # comma-separated, multi-recipi
|
||||
### E. Operator CRD
|
||||
|
||||
```yaml
|
||||
apiVersion: liquidity.io/v1
|
||||
kind: LiquidReplicate
|
||||
apiVersion: lux.network/v1
|
||||
kind: ReplicateConfig
|
||||
spec:
|
||||
encryption:
|
||||
kem: xwing # enum: hpke-mlkem768x25519 | xwing
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="age" width="880"></p>
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/FiloSottile/age/blob/main/logo/logo_white.svg">
|
||||
|
||||
+1
-1
@@ -9,9 +9,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"filippo.io/hpke"
|
||||
"github.com/luxfi/age/internal/bech32"
|
||||
"github.com/luxfi/age/internal/format"
|
||||
"filippo.io/hpke"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Lux Partners Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD 3-Clause License** (see
|
||||
[LICENSE](LICENSE)). It belongs to the **public** tier of the Lux
|
||||
three-tier IP strategy: anyone may use, fork, or redistribute it,
|
||||
including for commercial purposes, subject to the BSD-3 terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond BSD-3 (e.g. private moat
|
||||
acceleration kernels), contact `licensing@lux.network`.
|
||||
+22
-10
@@ -21,7 +21,8 @@ type ChainConfig struct {
|
||||
QChainID ids.ID // Quantum chain - post-quantum cryptography
|
||||
AChainID ids.ID // Attestation chain - oracles, compute attestation
|
||||
BChainID ids.ID // Bridge chain - cross-chain interop
|
||||
TChainID ids.ID // Threshold chain - FHE, threshold crypto
|
||||
MChainID ids.ID // MPC chain - threshold signing / bridge custody (consumes mpcvm library)
|
||||
FChainID ids.ID // FHE chain - confidential compute / encrypted state (consumes mpcvm library)
|
||||
ZChainID ids.ID // ZK chain - zero-knowledge proofs
|
||||
GChainID ids.ID // Graph chain - dgraph
|
||||
KChainID ids.ID // KMS chain - key management
|
||||
@@ -109,8 +110,10 @@ func (r *ChainRegistry) MigrateChain(networkID uint32, chainName string, newChai
|
||||
config.AChainID = newChainID
|
||||
case "B", "bridge":
|
||||
config.BChainID = newChainID
|
||||
case "T", "threshold":
|
||||
config.TChainID = newChainID
|
||||
case "M", "mpc":
|
||||
config.MChainID = newChainID
|
||||
case "F", "fhe":
|
||||
config.FChainID = newChainID
|
||||
case "Z", "zk":
|
||||
config.ZChainID = newChainID
|
||||
case "D", "dex":
|
||||
@@ -166,9 +169,14 @@ func (r *ChainRegistry) GetBChainID(networkID uint32) ids.ID {
|
||||
return r.GetOrDefault(networkID).BChainID
|
||||
}
|
||||
|
||||
// GetTChainID returns the T-chain ID for the given network.
|
||||
func (r *ChainRegistry) GetTChainID(networkID uint32) ids.ID {
|
||||
return r.GetOrDefault(networkID).TChainID
|
||||
// GetMChainID returns the M-chain ID for the given network.
|
||||
func (r *ChainRegistry) GetMChainID(networkID uint32) ids.ID {
|
||||
return r.GetOrDefault(networkID).MChainID
|
||||
}
|
||||
|
||||
// GetFChainID returns the F-chain ID for the given network.
|
||||
func (r *ChainRegistry) GetFChainID(networkID uint32) ids.ID {
|
||||
return r.GetOrDefault(networkID).FChainID
|
||||
}
|
||||
|
||||
// GetZChainID returns the Z-chain ID for the given network.
|
||||
@@ -192,7 +200,8 @@ func defaultMainnetConfig() *ChainConfig {
|
||||
QChainID: ids.QChainID,
|
||||
AChainID: ids.AChainID,
|
||||
BChainID: ids.BChainID,
|
||||
TChainID: ids.TChainID,
|
||||
MChainID: ids.MChainID,
|
||||
FChainID: ids.FChainID,
|
||||
ZChainID: ids.ZChainID,
|
||||
DChainID: ids.DChainID,
|
||||
}
|
||||
@@ -207,7 +216,8 @@ func defaultTestnetConfig() *ChainConfig {
|
||||
QChainID: ids.QChainID,
|
||||
AChainID: ids.AChainID,
|
||||
BChainID: ids.BChainID,
|
||||
TChainID: ids.TChainID,
|
||||
MChainID: ids.MChainID,
|
||||
FChainID: ids.FChainID,
|
||||
ZChainID: ids.ZChainID,
|
||||
DChainID: ids.DChainID,
|
||||
}
|
||||
@@ -222,7 +232,8 @@ func defaultDevnetConfig() *ChainConfig {
|
||||
QChainID: ids.QChainID,
|
||||
AChainID: ids.AChainID,
|
||||
BChainID: ids.BChainID,
|
||||
TChainID: ids.TChainID,
|
||||
MChainID: ids.MChainID,
|
||||
FChainID: ids.FChainID,
|
||||
ZChainID: ids.ZChainID,
|
||||
DChainID: ids.DChainID,
|
||||
}
|
||||
@@ -237,7 +248,8 @@ func defaultCustomConfig() *ChainConfig {
|
||||
QChainID: ids.QChainID,
|
||||
AChainID: ids.AChainID,
|
||||
BChainID: ids.BChainID,
|
||||
TChainID: ids.TChainID,
|
||||
MChainID: ids.MChainID,
|
||||
FChainID: ids.FChainID,
|
||||
ZChainID: ids.ZChainID,
|
||||
DChainID: ids.DChainID,
|
||||
}
|
||||
|
||||
+2
-1
@@ -73,7 +73,8 @@ var (
|
||||
QChainID = ids.QChainID // Q-Chain: 11111111111111111111111111111111Q
|
||||
AChainID = ids.AChainID // A-Chain: 11111111111111111111111111111111A
|
||||
BChainID = ids.BChainID // B-Chain: 11111111111111111111111111111111B
|
||||
TChainID = ids.TChainID // T-Chain: 11111111111111111111111111111111T
|
||||
MChainID = ids.MChainID // M-Chain: 11111111111111111111111111111111M (MPC — threshold signing / bridge custody)
|
||||
FChainID = ids.FChainID // F-Chain: 11111111111111111111111111111111F (FHE — confidential compute)
|
||||
ZChainID = ids.ZChainID // Z-Chain: 11111111111111111111111111111111Z (Zero-knowledge)
|
||||
GChainID = ids.GChainID // G-Chain: 11111111111111111111111111111111G (Graph/dgraph)
|
||||
KChainID = ids.KChainID // K-Chain: 11111111111111111111111111111111K (KMS)
|
||||
|
||||
+8
-4
@@ -18,7 +18,8 @@ const (
|
||||
QuantumVMName = "quantumvm" // Q-Chain: Quantum-resistant security
|
||||
AIVMName = "aivm" // A-Chain: AI Virtual Machine
|
||||
BridgeVMName = "bridgevm" // B-Chain: Bridge/Cross-chain
|
||||
ThresholdVMName = "thresholdvm" // T-Chain: Threshold signatures
|
||||
MPCVMName = "mpcvm" // M-Chain: MPC threshold signing / bridge custody (LP-7100)
|
||||
FHEVMName = "fhevm" // F-Chain: FHE confidential compute / encrypted state (LP-8200)
|
||||
KeyVMName = "keyvm" // K-Chain: Key Management
|
||||
ZKVMName = "zkvm" // Z-Chain: Zero-Knowledge proofs
|
||||
GraphVMName = "graphvm" // G-Chain: GraphQL/DGraph unified data layer
|
||||
@@ -40,7 +41,8 @@ var (
|
||||
AttestationVMID = ids.ID{'a', 'i', 'v', 'm'} // A-Chain: Attestation/AI VM
|
||||
AIVMID = AttestationVMID // Alias for AttestationVMID
|
||||
BridgeVMID = ids.ID{'b', 'r', 'i', 'd', 'g', 'e', 'v', 'm'}
|
||||
ThresholdVMID = ids.ID{'t', 'h', 'r', 'e', 's', 'h', 'o', 'l', 'd', 'v', 'm'}
|
||||
MPCVMID = ids.ID{'m', 'p', 'c', 'v', 'm'} // M-Chain: MPC threshold signing / bridge custody
|
||||
FHEVMID = ids.ID{'f', 'h', 'e', 'v', 'm'} // F-Chain: FHE confidential compute
|
||||
KeyVMID = ids.ID{'k', 'e', 'y', 'v', 'm'} // K-Chain: Key Management
|
||||
KVMID = KeyVMID // Alias for KeyVMID
|
||||
ZKVMID = ids.ID{'z', 'k', 'v', 'm'}
|
||||
@@ -107,8 +109,10 @@ func VMName(vmID ids.ID) string {
|
||||
return AIVMName
|
||||
case BridgeVMID:
|
||||
return BridgeVMName
|
||||
case ThresholdVMID:
|
||||
return ThresholdVMName
|
||||
case MPCVMID:
|
||||
return MPCVMName
|
||||
case FHEVMID:
|
||||
return FHEVMName
|
||||
case KeyVMID:
|
||||
return KeyVMName
|
||||
case ZKVMID:
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Lux Partners Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD-3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD-3-Clause License terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Hanzo Container
|
||||
|
||||
## Overview
|
||||
Go module: github.com/luxfi/container
|
||||
|
||||
## Tech Stack
|
||||
- **Language**: Go
|
||||
|
||||
## Build & Run
|
||||
```bash
|
||||
go build ./...
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Structure
|
||||
```
|
||||
container/
|
||||
LICENSE
|
||||
bimap/
|
||||
bloom/
|
||||
buffer/
|
||||
cache.go
|
||||
container.go
|
||||
go.mod
|
||||
go.sum
|
||||
heap/
|
||||
iterator/
|
||||
linked/
|
||||
linkedhashmap/
|
||||
maybe/
|
||||
pool/
|
||||
sampler/
|
||||
```
|
||||
|
||||
## Key Files
|
||||
- `go.mod` -- Go module definition
|
||||
+3
-3
@@ -50,7 +50,7 @@ func (c *LRUCache[K, V]) Put(key K, value V) {
|
||||
elem.Value.(*cacheEntry[K, V]).value = value
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if len(c.elements) >= c.capacity {
|
||||
back := c.lru.Back()
|
||||
if back != nil {
|
||||
@@ -58,7 +58,7 @@ func (c *LRUCache[K, V]) Put(key K, value V) {
|
||||
delete(c.elements, back.Value.(*cacheEntry[K, V]).key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
elem := c.lru.PushFront(&cacheEntry[K, V]{key, value})
|
||||
c.elements[key] = elem
|
||||
}
|
||||
@@ -76,4 +76,4 @@ func (c *LRUCache[K, V]) Len() int {
|
||||
|
||||
func (c *LRUCache[K, V]) Evict(key K) {
|
||||
c.Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -31,4 +31,4 @@ type Cache[K comparable, V any] interface {
|
||||
Delete(K)
|
||||
Len() int
|
||||
Evict(K)
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="crypto" width="880"></p>
|
||||
|
||||
# Lux Crypto
|
||||
|
||||
Cryptographic primitives for the Lux Network -- post-quantum signatures, key encapsulation, BLS aggregation, threshold signing, ring signatures, and EVM-compatible secp256k1.
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Lux Partners Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD-3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD-3-Clause License terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Hanzo Formatting
|
||||
|
||||
## Overview
|
||||
Go module: github.com/luxfi/formatting
|
||||
|
||||
## Tech Stack
|
||||
- **Language**: Go
|
||||
|
||||
## Build & Run
|
||||
```bash
|
||||
go build ./...
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Structure
|
||||
```
|
||||
formatting/
|
||||
LICENSE
|
||||
encoding.go
|
||||
encoding_benchmark_test.go
|
||||
encoding_test.go
|
||||
go.mod
|
||||
go.sum
|
||||
int_format.go
|
||||
int_format_test.go
|
||||
prefixed_stringer.go
|
||||
```
|
||||
|
||||
## Key Files
|
||||
- `go.mod` -- Go module definition
|
||||
+3
-3
@@ -11,7 +11,7 @@ import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
hashing "github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/crypto/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -107,7 +107,7 @@ func Encode(encoding Encoding, bytes []byte) (string, error) {
|
||||
}
|
||||
checked := make([]byte, bytesLen+checksumLen)
|
||||
copy(checked, bytes)
|
||||
copy(checked[len(bytes):], hashing.Checksum(bytes, checksumLen))
|
||||
copy(checked[len(bytes):], hash.Checksum(bytes, checksumLen))
|
||||
bytes = checked
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ func Decode(encoding Encoding, str string) ([]byte, error) {
|
||||
// Verify the checksum
|
||||
rawBytes := decodedBytes[:len(decodedBytes)-checksumLen]
|
||||
checksum := decodedBytes[len(decodedBytes)-checksumLen:]
|
||||
if !bytes.Equal(checksum, hashing.Checksum(rawBytes, checksumLen)) {
|
||||
if !bytes.Equal(checksum, hash.Checksum(rawBytes, checksumLen)) {
|
||||
return nil, errBadChecksum
|
||||
}
|
||||
decodedBytes = rawBytes
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Lux Partners Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD-3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD-3-Clause License terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="go-bip32" width="880"></p>
|
||||
|
||||
# GO-BIP32
|
||||
This repository contains a local copy of the original ``github.com/tyler-smith/go-bip32`` library.
|
||||
|
||||
|
||||
-2
@@ -263,9 +263,7 @@ func validateChildPublicKey(key []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// Numerical
|
||||
//
|
||||
func uint32Bytes(i uint32) []byte {
|
||||
bytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(bytes, i)
|
||||
|
||||
+8
@@ -135,3 +135,11 @@ fabric.properties
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/goland,go
|
||||
|
||||
|
||||
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
GEMINI.md
|
||||
GROK.md
|
||||
QWEN.md
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Lux Partners Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD-3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD-3-Clause License terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# AI Assistant Knowledge Base
|
||||
|
||||
**Last Updated**: $(date +%Y-%m-%d)
|
||||
**Project**: $(basename "$REPO_PATH")
|
||||
**Organization**: $(basename "$(dirname "$REPO_PATH")")
|
||||
|
||||
## Project Overview
|
||||
|
||||
This repository is part of the $(basename "$(dirname "$REPO_PATH")") organization.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### Development
|
||||
```bash
|
||||
# Add common commands here
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
## Key Technologies
|
||||
|
||||
## Development Workflow
|
||||
|
||||
## Context for All AI Assistants
|
||||
|
||||
This file (`LLM.md`) is symlinked as:
|
||||
- `.AGENTS.md`
|
||||
- `CLAUDE.md`
|
||||
- `QWEN.md`
|
||||
- `GEMINI.md`
|
||||
|
||||
All files reference the same knowledge base. Updates here propagate to all AI systems.
|
||||
|
||||
## Rules for AI Assistants
|
||||
|
||||
1. **ALWAYS** update LLM.md with significant discoveries
|
||||
2. **NEVER** commit symlinked files (.AGENTS.md, CLAUDE.md, etc.) - they're in .gitignore
|
||||
3. **NEVER** create random summary files - update THIS file
|
||||
|
||||
---
|
||||
|
||||
**Note**: This file serves as the single source of truth for all AI assistants working on this project.
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="go-bip39" width="880"></p>
|
||||
|
||||
# GO-BIP39
|
||||
This repository contains a local copy of the original ``github.com/tyler-smith/go-bip39`` library.
|
||||
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="ids" width="880"></p>
|
||||
|
||||
# Lux IDs Package
|
||||
|
||||
[](https://pkg.go.dev/github.com/luxfi/ids)
|
||||
|
||||
+29
-15
@@ -13,9 +13,10 @@ package ids
|
||||
// C-Chain: 11111111111111111111111111111111C (Contract/EVM)
|
||||
// X-Chain: 11111111111111111111111111111111X (Exchange/DAG)
|
||||
// Q-Chain: 11111111111111111111111111111111Q (Quantum)
|
||||
// A-Chain: 11111111111111111111111111111111A (AI)
|
||||
// A-Chain: 11111111111111111111111111111111A (AI/Attestation)
|
||||
// B-Chain: 11111111111111111111111111111111B (Bridge)
|
||||
// T-Chain: 11111111111111111111111111111111T (Threshold)
|
||||
// M-Chain: 11111111111111111111111111111111M (MPC — threshold signing / bridge custody)
|
||||
// F-Chain: 11111111111111111111111111111111F (FHE — confidential compute)
|
||||
// Z-Chain: 11111111111111111111111111111111Z (Zero-knowledge)
|
||||
// G-Chain: 11111111111111111111111111111111G (Graph/dgraph)
|
||||
// K-Chain: 11111111111111111111111111111111K (KMS)
|
||||
@@ -42,7 +43,8 @@ const (
|
||||
QChainIDStr = nativeChainPrefix + "Q"
|
||||
AChainIDStr = nativeChainPrefix + "A"
|
||||
BChainIDStr = nativeChainPrefix + "B"
|
||||
TChainIDStr = nativeChainPrefix + "T"
|
||||
MChainIDStr = nativeChainPrefix + "M"
|
||||
FChainIDStr = nativeChainPrefix + "F"
|
||||
ZChainIDStr = nativeChainPrefix + "Z"
|
||||
GChainIDStr = nativeChainPrefix + "G" // Coming soon
|
||||
IChainIDStr = nativeChainPrefix + "I" // Identity - Coming soon
|
||||
@@ -69,8 +71,11 @@ var (
|
||||
// BChainID is the well-known B-Chain (Bridge) ID
|
||||
BChainID ID
|
||||
|
||||
// TChainID is the well-known T-Chain (Threshold) ID
|
||||
TChainID ID
|
||||
// MChainID is the well-known M-Chain (MPC — threshold signing, bridge custody) ID
|
||||
MChainID ID
|
||||
|
||||
// FChainID is the well-known F-Chain (FHE — confidential compute) ID
|
||||
FChainID ID
|
||||
|
||||
// ZChainID is the well-known Z-Chain (Zero-knowledge) ID
|
||||
ZChainID ID
|
||||
@@ -96,7 +101,8 @@ func init() {
|
||||
QChainID[nativeChainLetterPos] = 'Q'
|
||||
AChainID[nativeChainLetterPos] = 'A'
|
||||
BChainID[nativeChainLetterPos] = 'B'
|
||||
TChainID[nativeChainLetterPos] = 'T'
|
||||
MChainID[nativeChainLetterPos] = 'M'
|
||||
FChainID[nativeChainLetterPos] = 'F'
|
||||
ZChainID[nativeChainLetterPos] = 'Z'
|
||||
GChainID[nativeChainLetterPos] = 'G'
|
||||
IChainID[nativeChainLetterPos] = 'I'
|
||||
@@ -138,8 +144,10 @@ func NativeChainString(id ID) string {
|
||||
return AChainIDStr
|
||||
case 'B':
|
||||
return BChainIDStr
|
||||
case 'T':
|
||||
return TChainIDStr
|
||||
case 'M':
|
||||
return MChainIDStr
|
||||
case 'F':
|
||||
return FChainIDStr
|
||||
case 'Z':
|
||||
return ZChainIDStr
|
||||
case 'G':
|
||||
@@ -187,8 +195,10 @@ func NativeChainFromString(s string) (ID, bool) {
|
||||
return AChainID, true
|
||||
case 'B', 'b':
|
||||
return BChainID, true
|
||||
case 'T', 't':
|
||||
return TChainID, true
|
||||
case 'M', 'm':
|
||||
return MChainID, true
|
||||
case 'F', 'f':
|
||||
return FChainID, true
|
||||
case 'Z', 'z':
|
||||
return ZChainID, true
|
||||
case 'G', 'g':
|
||||
@@ -227,8 +237,10 @@ func NativeChainFromString(s string) (ID, bool) {
|
||||
return AChainID, true
|
||||
case 'B':
|
||||
return BChainID, true
|
||||
case 'T':
|
||||
return TChainID, true
|
||||
case 'M':
|
||||
return MChainID, true
|
||||
case 'F':
|
||||
return FChainID, true
|
||||
case 'Z':
|
||||
return ZChainID, true
|
||||
case 'G':
|
||||
@@ -256,7 +268,7 @@ func NativeChainAlias(id ID) string {
|
||||
|
||||
// AllNativeChainIDs returns all well-known native chain IDs.
|
||||
func AllNativeChainIDs() []ID {
|
||||
return []ID{PChainID, CChainID, XChainID, QChainID, AChainID, BChainID, TChainID, ZChainID, GChainID, IChainID, KChainID, DChainID}
|
||||
return []ID{PChainID, CChainID, XChainID, QChainID, AChainID, BChainID, MChainID, FChainID, ZChainID, GChainID, IChainID, KChainID, DChainID}
|
||||
}
|
||||
|
||||
// NativeChainIDFromLetter returns the chain ID for a given letter.
|
||||
@@ -277,8 +289,10 @@ func NativeChainIDFromLetter(letter byte) (ID, bool) {
|
||||
return AChainID, true
|
||||
case 'B', 'b':
|
||||
return BChainID, true
|
||||
case 'T', 't':
|
||||
return TChainID, true
|
||||
case 'M', 'm':
|
||||
return MChainID, true
|
||||
case 'F', 'f':
|
||||
return FChainID, true
|
||||
case 'Z', 'z':
|
||||
return ZChainID, true
|
||||
case 'G', 'g':
|
||||
|
||||
+2
-2
@@ -33,10 +33,10 @@ const (
|
||||
// Allocation represents a P-chain genesis allocation
|
||||
type Allocation struct {
|
||||
// ETHAddr is the C-chain compatible address (0x...)
|
||||
ETHAddr string `json:"evmAddr"`
|
||||
ETHAddr string `json:"ethAddr"`
|
||||
|
||||
// LUXAddr is the P/X-chain address (P-lux1...)
|
||||
LUXAddr string `json:"utxoAddr"`
|
||||
LUXAddr string `json:"luxAddr"`
|
||||
|
||||
// InitialAmount is immediately available on X-chain (usually 0)
|
||||
InitialAmount uint64 `json:"initialAmount"`
|
||||
|
||||
+5
-6
@@ -24,7 +24,7 @@ import (
|
||||
"github.com/luxfi/go-bip32"
|
||||
"github.com/luxfi/go-bip39"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/proto/p/signer"
|
||||
"github.com/luxfi/protocol/p/signer"
|
||||
luxtls "github.com/luxfi/tls"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
@@ -546,7 +546,7 @@ func deriveMnemonicKeyForPath(mnemonic string, account, index uint32) ([]byte, e
|
||||
}
|
||||
|
||||
// m/44'/60' (coin type for LUX)
|
||||
key, err = key.NewChildKey(bip32.FirstHardenedChild + CoinTypeEVM)
|
||||
key, err = key.NewChildKey(bip32.FirstHardenedChild + LUXCoinType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive coin type: %w", err)
|
||||
}
|
||||
@@ -572,9 +572,8 @@ func deriveMnemonicKeyForPath(mnemonic string, account, index uint32) ([]byte, e
|
||||
return key.Key, nil
|
||||
}
|
||||
|
||||
// CoinTypeEVM is the SLIP-0044 coin_type for EVM chains (60', shared with
|
||||
// Ethereum). C-Chain and any non-Lux L1 EVM derive under this tree.
|
||||
const CoinTypeEVM = 60
|
||||
// LUXCoinType is the BIP-44 coin type for LUX (60' = standard Ethereum)
|
||||
const LUXCoinType = 60
|
||||
|
||||
// deriveMnemonicKey derives an EC private key from mnemonic using BIP44 path m/44'/60'/0'/0/{index}
|
||||
func deriveMnemonicKey(mnemonic string, accountIndex uint32) ([]byte, error) {
|
||||
@@ -597,7 +596,7 @@ func deriveMnemonicKey(mnemonic string, accountIndex uint32) ([]byte, error) {
|
||||
}
|
||||
|
||||
// m/44'/60' (coin type for LUX)
|
||||
key, err = key.NewChildKey(bip32.FirstHardenedChild + CoinTypeEVM)
|
||||
key, err = key.NewChildKey(bip32.FirstHardenedChild + LUXCoinType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive coin type: %w", err)
|
||||
}
|
||||
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// zap.go — load a BIP-39 mnemonic from Liquid KMS over native ZAP.
|
||||
//
|
||||
// This file is the one canonical path every Lux-derived service uses
|
||||
// to resolve the bootstrap mnemonic: luxd, netrunner, lux/cli, and any
|
||||
// descending L1's bootstrap (liquidity/network-bootstrap, hanzo-bootstrap,
|
||||
// zoo-bootstrap, …). No file mount, no projected volume, no per-consumer
|
||||
// copy of the env-vs-KMS precedence chain.
|
||||
//
|
||||
// Separation of concerns:
|
||||
//
|
||||
// - zap (the protocol) → github.com/zap-proto/zap
|
||||
// - luxfi/kms (secret store) → opaque Get/Put/List over ZAP
|
||||
// - luxfi/keys (THIS PACKAGE) → BIP-39, BIP44 derivation, mnemonic
|
||||
// semantics. Composes luxfi/kms with
|
||||
// bip39 validation. KMS knows nothing
|
||||
// about mnemonics; this layer does.
|
||||
//
|
||||
// Precedence (env wins; KMS is the production fallback):
|
||||
//
|
||||
// 1. MNEMONIC env var local dev + CI test seam
|
||||
// 2. KMS at (addr, env, path) native ZAP from Liquid KMS
|
||||
|
||||
package keys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
bip39 "github.com/luxfi/go-bip39"
|
||||
"github.com/luxfi/kms/pkg/envelope"
|
||||
"github.com/luxfi/kms/pkg/zapclient"
|
||||
)
|
||||
|
||||
// MnemonicReader is the minimum surface LoadMnemonicFromKMS needs from
|
||||
// a KMS client. The real *zapclient.Client satisfies it; tests inject
|
||||
// fakes via the dialKMS seam below.
|
||||
type MnemonicReader interface {
|
||||
GetAt(ctx context.Context, path, name, env string) (string, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
// dialKMS is the production seam. Tests override to inject a fake
|
||||
// without touching the network.
|
||||
//
|
||||
// identity is REQUIRED for the production path (the KMS server's
|
||||
// consensus-auth gate rejects envelopes without a signed identity).
|
||||
// Passing nil dials anonymously — accepted only by legacy KMS peers
|
||||
// that have explicitly disabled the auth gate (dev / loopback only).
|
||||
var dialKMS = func(ctx context.Context, addr string, identity *ServiceIdentity) (MnemonicReader, error) {
|
||||
cfg := zapclient.Config{PeerAddr: addr}
|
||||
if identity != nil {
|
||||
cfg.IdentityHeader = envelope.IdentityHeader{
|
||||
NodeID: identity.NodeID,
|
||||
FullDigest: identity.FullDigest,
|
||||
ServicePath: identity.ServicePath,
|
||||
PublicKey: identity.PublicKey,
|
||||
}
|
||||
cfg.Signer = identity
|
||||
}
|
||||
c, err := zapclient.DialWithConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// LoadMnemonic returns the BIP-39 mnemonic for the calling service.
|
||||
// MNEMONIC env wins when set; otherwise dials KMS over native ZAP at
|
||||
// `addr` and reads the secret at `path` under `env`.
|
||||
//
|
||||
// ctx cancellable context
|
||||
// addr KMS host:port (e.g. "kms.internal.svc:9999")
|
||||
// env KMS env scope ("mainnet" | "testnet" | "devnet")
|
||||
// path KMS secret path (e.g. "/mnemonic" or "/foo/master")
|
||||
// identity ServiceIdentity to sign the secret-opcode envelope. May
|
||||
// be nil for legacy peers and the env-win short-circuit
|
||||
// (the KMS dial is never reached); required for production
|
||||
// peers whose consensus-auth gate is engaged.
|
||||
//
|
||||
// Returns the validated BIP-39 phrase. Caller is responsible for
|
||||
// keeping it on the goroutine stack and not logging / persisting it.
|
||||
func LoadMnemonic(ctx context.Context, addr, env, path string, identity *ServiceIdentity) (string, error) {
|
||||
if e := strings.TrimSpace(os.Getenv("MNEMONIC")); e != "" {
|
||||
if !bip39.IsMnemonicValid(e) {
|
||||
return "", errors.New("MNEMONIC env is not a valid BIP-39 phrase")
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
return LoadMnemonicFromKMS(ctx, addr, env, path, identity)
|
||||
}
|
||||
|
||||
// LoadMnemonicFromKMS is the production-only path (no MNEMONIC env
|
||||
// short-circuit). Useful when a caller wants to force the KMS read
|
||||
// regardless of ambient env — e.g., a regression test pinning the
|
||||
// production code path.
|
||||
//
|
||||
// identity is the *ServiceIdentity threaded into the ZAP dial: its
|
||||
// IdentityHeader becomes envelope.Identity and the same identity
|
||||
// signs every secret-opcode envelope. Pass nil only when dialling a
|
||||
// legacy KMS peer that has not enabled the consensus-auth gate
|
||||
// (dev / loopback fakes — see zap_test.go's withDial seam).
|
||||
func LoadMnemonicFromKMS(ctx context.Context, addr, env, path string, identity *ServiceIdentity) (string, error) {
|
||||
if addr == "" {
|
||||
return "", errors.New("keys.LoadMnemonicFromKMS: KMS addr is required")
|
||||
}
|
||||
if env == "" {
|
||||
return "", errors.New("keys.LoadMnemonicFromKMS: KMS env is required")
|
||||
}
|
||||
if path == "" {
|
||||
return "", errors.New("keys.LoadMnemonicFromKMS: KMS path is required")
|
||||
}
|
||||
|
||||
c, err := dialKMS(ctx, addr, identity)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("dial KMS %s: %w", addr, err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
dir, name := SplitSecretPath(path)
|
||||
v, err := c.GetAt(ctx, dir, name, env)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read mnemonic (path=%q name=%q env=%q): %w",
|
||||
dir, name, env, err)
|
||||
}
|
||||
m := strings.TrimSpace(v)
|
||||
if m == "" {
|
||||
return "", fmt.Errorf("mnemonic at %q is empty", path)
|
||||
}
|
||||
if !bip39.IsMnemonicValid(m) {
|
||||
return "", errors.New("mnemonic from KMS is not a valid BIP-39 phrase")
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// SplitSecretPath turns "/foo/bar/baz" into ("/foo/bar/", "baz"). When
|
||||
// there is no '/' or only a leading one, the directory is "" and the
|
||||
// whole remainder is the name (e.g., "/mnemonic" → ("", "mnemonic")).
|
||||
//
|
||||
// Exported so every consumer can address secrets with the same
|
||||
// convention — one and only one addressing scheme across mnemonic,
|
||||
// staking keys, gas-payer keys, etc.
|
||||
func SplitSecretPath(full string) (dir, name string) {
|
||||
full = strings.TrimSpace(full)
|
||||
full = strings.TrimPrefix(full, "/")
|
||||
i := strings.LastIndex(full, "/")
|
||||
if i < 0 {
|
||||
return "", full
|
||||
}
|
||||
return "/" + full[:i+1], full[i+1:]
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD-3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD-3-Clause License terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="math" width="880"></p>
|
||||
|
||||
# Lux Math Library
|
||||
|
||||
A comprehensive mathematical utilities library for the Lux ecosystem.
|
||||
|
||||
+1
-1
@@ -73,5 +73,5 @@ GOWORK=off go test ./...
|
||||
* [`lattice/v7/ring`](https://github.com/luxfi/lattice/tree/main/ring)
|
||||
— canonical Lattigo-derived NTT/Montgomery body that this module
|
||||
delegates to.
|
||||
* [`luxcpp/crypto/ringtail`](https://github.com/luxcpp/crypto/tree/main/ringtail)
|
||||
* [`luxcpp/crypto/corona`](https://github.com/luxcpp/crypto/tree/main/corona)
|
||||
— native C++ Montgomery NTT that Phase 6 mirrors as `math` C++.
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="metric" width="880"></p>
|
||||
|
||||
# Lux Metrics Library
|
||||
|
||||
`github.com/luxfi/metric` is the native metrics library for Lux. It produces a
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package metric
|
||||
|
||||
// Currying support for the *Vec families. Mirrors prometheus/client_golang's
|
||||
// MetricVec.MustCurryWith: it binds a subset of label values up front and
|
||||
// returns a vec that only needs the remaining labels. Implemented natively —
|
||||
// the returned vec delegates to the base vec with the fixed labels merged in,
|
||||
// so children are still created and registered on the base registry exactly
|
||||
// once per full label set.
|
||||
|
||||
// mergeLabels returns a new Labels with b overlaid on a.
|
||||
func mergeLabels(a, b Labels) Labels {
|
||||
out := make(Labels, len(a)+len(b))
|
||||
for k, v := range a {
|
||||
out[k] = v
|
||||
}
|
||||
for k, v := range b {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// curryRemaining returns the label names from all that are not fixed, in
|
||||
// declared order — these are the names WithLabelValues maps positionally.
|
||||
func curryRemaining(all []string, fixed Labels) []string {
|
||||
rem := make([]string, 0, len(all))
|
||||
for _, n := range all {
|
||||
if _, ok := fixed[n]; !ok {
|
||||
rem = append(rem, n)
|
||||
}
|
||||
}
|
||||
return rem
|
||||
}
|
||||
|
||||
// --- counter ---
|
||||
|
||||
func (v *counterVec) MustCurryWith(labels Labels) CounterVec {
|
||||
return &curriedCounterVec{base: v, fixed: cloneLabels(labels), remaining: curryRemaining(v.labelNames, labels)}
|
||||
}
|
||||
|
||||
type curriedCounterVec struct {
|
||||
base *counterVec
|
||||
fixed Labels
|
||||
remaining []string
|
||||
}
|
||||
|
||||
func (c *curriedCounterVec) With(labels Labels) Counter {
|
||||
return c.base.With(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedCounterVec) WithLabelValues(values ...string) Counter {
|
||||
return c.base.With(mergeLabels(c.fixed, labelsFromValues(c.remaining, values)))
|
||||
}
|
||||
func (c *curriedCounterVec) MustCurryWith(labels Labels) CounterVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedCounterVec) Reset() { c.base.Reset() }
|
||||
|
||||
// --- gauge ---
|
||||
|
||||
func (v *gaugeVec) MustCurryWith(labels Labels) GaugeVec {
|
||||
return &curriedGaugeVec{base: v, fixed: cloneLabels(labels), remaining: curryRemaining(v.labelNames, labels)}
|
||||
}
|
||||
|
||||
type curriedGaugeVec struct {
|
||||
base *gaugeVec
|
||||
fixed Labels
|
||||
remaining []string
|
||||
}
|
||||
|
||||
func (c *curriedGaugeVec) With(labels Labels) Gauge {
|
||||
return c.base.With(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedGaugeVec) WithLabelValues(values ...string) Gauge {
|
||||
return c.base.With(mergeLabels(c.fixed, labelsFromValues(c.remaining, values)))
|
||||
}
|
||||
func (c *curriedGaugeVec) MustCurryWith(labels Labels) GaugeVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedGaugeVec) Reset() { c.base.Reset() }
|
||||
|
||||
// --- histogram ---
|
||||
|
||||
func (v *histogramVec) MustCurryWith(labels Labels) HistogramVec {
|
||||
return &curriedHistogramVec{base: v, fixed: cloneLabels(labels), remaining: curryRemaining(v.labelNames, labels)}
|
||||
}
|
||||
|
||||
type curriedHistogramVec struct {
|
||||
base *histogramVec
|
||||
fixed Labels
|
||||
remaining []string
|
||||
}
|
||||
|
||||
func (c *curriedHistogramVec) With(labels Labels) Histogram {
|
||||
return c.base.With(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedHistogramVec) WithLabelValues(values ...string) Histogram {
|
||||
return c.base.With(mergeLabels(c.fixed, labelsFromValues(c.remaining, values)))
|
||||
}
|
||||
func (c *curriedHistogramVec) MustCurryWith(labels Labels) HistogramVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedHistogramVec) Reset() { c.base.Reset() }
|
||||
|
||||
// --- summary ---
|
||||
|
||||
func (v *summaryVec) MustCurryWith(labels Labels) SummaryVec {
|
||||
return &curriedSummaryVec{base: v, fixed: cloneLabels(labels), remaining: curryRemaining(v.labelNames, labels)}
|
||||
}
|
||||
|
||||
type curriedSummaryVec struct {
|
||||
base *summaryVec
|
||||
fixed Labels
|
||||
remaining []string
|
||||
}
|
||||
|
||||
func (c *curriedSummaryVec) With(labels Labels) Summary {
|
||||
return c.base.With(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedSummaryVec) WithLabelValues(values ...string) Summary {
|
||||
return c.base.With(mergeLabels(c.fixed, labelsFromValues(c.remaining, values)))
|
||||
}
|
||||
func (c *curriedSummaryVec) MustCurryWith(labels Labels) SummaryVec {
|
||||
return c.base.MustCurryWith(mergeLabels(c.fixed, labels))
|
||||
}
|
||||
func (c *curriedSummaryVec) Reset() { c.base.Reset() }
|
||||
+4
-1
@@ -6,6 +6,9 @@
|
||||
package metric
|
||||
|
||||
func init() {
|
||||
DefaultRegistry = NewNoOpRegistry()
|
||||
r := NewNoOpRegistry()
|
||||
DefaultRegistry = r
|
||||
DefaultRegisterer = r
|
||||
DefaultGatherer = r
|
||||
defaultFactory = NewNoOpFactory()
|
||||
}
|
||||
|
||||
+21
@@ -39,3 +39,24 @@ type MetricFamilies = []*MetricFamily
|
||||
func NewHTTPHandler(gatherer Gatherer, opts HandlerOpts) http.Handler {
|
||||
return HandlerForWithOpts(gatherer, opts)
|
||||
}
|
||||
|
||||
// InstrumentMetricHandler wraps handler so each scrape is observed on reg.
|
||||
// It registers promhttp_metric_handler_requests_total (scrape count) and
|
||||
// promhttp_metric_handler_requests_in_flight (concurrent scrapes) and updates
|
||||
// them around each call. Mirrors prometheus/client_golang/promhttp.InstrumentMetricHandler.
|
||||
func InstrumentMetricHandler(reg Registerer, handler http.Handler) http.Handler {
|
||||
requests := reg.NewCounter(
|
||||
"promhttp_metric_handler_requests_total",
|
||||
"Total number of scrapes served.",
|
||||
)
|
||||
inFlight := reg.NewGauge(
|
||||
"promhttp_metric_handler_requests_in_flight",
|
||||
"Current number of scrapes being served.",
|
||||
)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
inFlight.Inc()
|
||||
defer inFlight.Dec()
|
||||
requests.Inc()
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
+4
-4
@@ -47,10 +47,10 @@ const MsgMetricBatch uint16 = 2
|
||||
// payload field. One batch per Export call (typically one Gather()
|
||||
// snapshot per scrape interval).
|
||||
type MetricBatch struct {
|
||||
AppName string `json:"appName,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Resource map[string]string `json:"resource,omitempty"`
|
||||
TimestampNs int64 `json:"timestampNs"`
|
||||
AppName string `json:"appName,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Resource map[string]string `json:"resource,omitempty"`
|
||||
TimestampNs int64 `json:"timestampNs"`
|
||||
Families []MetricFamilyWire `json:"families"`
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -32,6 +32,10 @@ type HandlerOpts struct {
|
||||
ErrorHandling HandlerErrorHandling
|
||||
// ErrorLog is used when ErrorHandling is Continue.
|
||||
ErrorLog interface{ Println(...any) }
|
||||
// DisableCompression mirrors prometheus/client_golang/promhttp.HandlerOpts.
|
||||
// The native handler serves uncompressed exposition, so this is always
|
||||
// effectively honored; the field exists for source compatibility.
|
||||
DisableCompression bool
|
||||
}
|
||||
|
||||
// HTTPHandlerOpts is an alias for HandlerOpts for compatibility.
|
||||
|
||||
+13
@@ -106,6 +106,7 @@ type Factory interface {
|
||||
type CounterVec interface {
|
||||
With(Labels) Counter
|
||||
WithLabelValues(...string) Counter
|
||||
MustCurryWith(Labels) CounterVec
|
||||
Reset()
|
||||
}
|
||||
|
||||
@@ -113,6 +114,7 @@ type CounterVec interface {
|
||||
type GaugeVec interface {
|
||||
With(Labels) Gauge
|
||||
WithLabelValues(...string) Gauge
|
||||
MustCurryWith(Labels) GaugeVec
|
||||
Reset()
|
||||
}
|
||||
|
||||
@@ -120,6 +122,7 @@ type GaugeVec interface {
|
||||
type HistogramVec interface {
|
||||
With(Labels) Histogram
|
||||
WithLabelValues(...string) Histogram
|
||||
MustCurryWith(Labels) HistogramVec
|
||||
Reset()
|
||||
}
|
||||
|
||||
@@ -127,6 +130,7 @@ type HistogramVec interface {
|
||||
type SummaryVec interface {
|
||||
With(Labels) Summary
|
||||
WithLabelValues(...string) Summary
|
||||
MustCurryWith(Labels) SummaryVec
|
||||
Reset()
|
||||
}
|
||||
|
||||
@@ -152,6 +156,15 @@ type Request interface {
|
||||
// DefaultRegistry is the default in-process registry used by package-level helpers.
|
||||
var DefaultRegistry Registry = NewRegistry()
|
||||
|
||||
// DefaultRegisterer is the default Registerer, backed by DefaultRegistry.
|
||||
// Mirrors prometheus.DefaultRegisterer so call sites that pass a package-level
|
||||
// registerer (e.g. metric.Register(metric.DefaultRegisterer)) keep working.
|
||||
var DefaultRegisterer Registerer = DefaultRegistry
|
||||
|
||||
// DefaultGatherer is the default Gatherer, backed by DefaultRegistry.
|
||||
// Mirrors prometheus.DefaultGatherer for HTTP exposition of the default registry.
|
||||
var DefaultGatherer Gatherer = DefaultRegistry
|
||||
|
||||
// Global factory instance.
|
||||
var defaultFactory Factory = NewFactoryWithRegistry(DefaultRegistry)
|
||||
|
||||
|
||||
+19
@@ -776,6 +776,25 @@ func (hpr *registry) MustRegister(cs ...Collector) {
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister drops the collector's metric family (all label permutations) and
|
||||
// frees its name for re-registration. Returns true if the name was registered.
|
||||
// Mirrors prometheus.Registerer.Unregister.
|
||||
func (hpr *registry) Unregister(c Collector) bool {
|
||||
name, _, ok := collectorIdentity(c)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
hpr.mu.Lock()
|
||||
defer hpr.mu.Unlock()
|
||||
_, had := hpr.registered[name]
|
||||
delete(hpr.registered, name)
|
||||
delete(hpr.counters, name)
|
||||
delete(hpr.gauges, name)
|
||||
delete(hpr.histograms, name)
|
||||
delete(hpr.summaries, name)
|
||||
return had
|
||||
}
|
||||
|
||||
// Gather returns metric families for all registered metrics.
|
||||
func (hpr *registry) Gather() ([]*MetricFamily, error) {
|
||||
hpr.mu.RLock()
|
||||
|
||||
+52
-13
@@ -3,23 +3,57 @@
|
||||
|
||||
package metric
|
||||
|
||||
// noopCounter is a counter that does nothing.
|
||||
type noopCounter struct{ value float64 }
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func (n *noopCounter) Inc() { n.value++ }
|
||||
func (n *noopCounter) Add(v float64) { n.value += v }
|
||||
func (n *noopCounter) Get() float64 { return n.value }
|
||||
// noopCounter is a counter that does nothing. value is stored as float64 bits
|
||||
// in a uint64 and mutated via CAS so concurrent Inc/Add/Get is race-free —
|
||||
// matches metricCounter's representation (metrics_impl.go).
|
||||
type noopCounter struct{ value uint64 } // atomic float64 bits
|
||||
|
||||
// noopGauge is a gauge that does nothing.
|
||||
type noopGauge struct{ value float64 }
|
||||
func (n *noopCounter) Inc() { n.Add(1) }
|
||||
|
||||
func (n *noopGauge) Set(v float64) { n.value = v }
|
||||
func (n *noopCounter) Add(v float64) {
|
||||
for {
|
||||
oldBits := atomic.LoadUint64(&n.value)
|
||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
||||
if atomic.CompareAndSwapUint64(&n.value, oldBits, newBits) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *noopCounter) Get() float64 {
|
||||
return math.Float64frombits(atomic.LoadUint64(&n.value))
|
||||
}
|
||||
|
||||
// noopGauge is a gauge that does nothing. value is stored as float64 bits in
|
||||
// a uint64 and mutated via CAS so concurrent Set/Inc/Dec/Add/Sub/Get is
|
||||
// race-free — matches metricGauge's representation (metrics_impl.go).
|
||||
type noopGauge struct{ value uint64 } // atomic float64 bits
|
||||
|
||||
func (n *noopGauge) Set(v float64) { atomic.StoreUint64(&n.value, math.Float64bits(v)) }
|
||||
func (n *noopGauge) SetToCurrentTime() {}
|
||||
func (n *noopGauge) Inc() { n.value++ }
|
||||
func (n *noopGauge) Dec() { n.value-- }
|
||||
func (n *noopGauge) Add(v float64) { n.value += v }
|
||||
func (n *noopGauge) Sub(v float64) { n.value -= v }
|
||||
func (n *noopGauge) Get() float64 { return n.value }
|
||||
func (n *noopGauge) Inc() { n.Add(1) }
|
||||
func (n *noopGauge) Dec() { n.Add(-1) }
|
||||
|
||||
func (n *noopGauge) Add(v float64) {
|
||||
for {
|
||||
oldBits := atomic.LoadUint64(&n.value)
|
||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
||||
if atomic.CompareAndSwapUint64(&n.value, oldBits, newBits) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *noopGauge) Sub(v float64) { n.Add(-v) }
|
||||
|
||||
func (n *noopGauge) Get() float64 {
|
||||
return math.Float64frombits(atomic.LoadUint64(&n.value))
|
||||
}
|
||||
|
||||
// noopHistogram is a histogram that does nothing.
|
||||
type noopHistogram struct{}
|
||||
@@ -36,6 +70,7 @@ type noopCounterVec struct{}
|
||||
|
||||
func (n *noopCounterVec) With(Labels) Counter { return &noopCounter{} }
|
||||
func (n *noopCounterVec) WithLabelValues(...string) Counter { return &noopCounter{} }
|
||||
func (n *noopCounterVec) MustCurryWith(Labels) CounterVec { return n }
|
||||
func (n *noopCounterVec) Reset() {}
|
||||
|
||||
// noopGaugeVec is a gauge vector that does nothing.
|
||||
@@ -43,6 +78,7 @@ type noopGaugeVec struct{}
|
||||
|
||||
func (n *noopGaugeVec) With(Labels) Gauge { return &noopGauge{} }
|
||||
func (n *noopGaugeVec) WithLabelValues(...string) Gauge { return &noopGauge{} }
|
||||
func (n *noopGaugeVec) MustCurryWith(Labels) GaugeVec { return n }
|
||||
func (n *noopGaugeVec) Reset() {}
|
||||
|
||||
// noopHistogramVec is a histogram vector that does nothing.
|
||||
@@ -50,6 +86,7 @@ type noopHistogramVec struct{}
|
||||
|
||||
func (n *noopHistogramVec) With(Labels) Histogram { return &noopHistogram{} }
|
||||
func (n *noopHistogramVec) WithLabelValues(...string) Histogram { return &noopHistogram{} }
|
||||
func (n *noopHistogramVec) MustCurryWith(Labels) HistogramVec { return n }
|
||||
func (n *noopHistogramVec) Reset() {}
|
||||
|
||||
// noopSummaryVec is a summary vector that does nothing.
|
||||
@@ -57,6 +94,7 @@ type noopSummaryVec struct{}
|
||||
|
||||
func (n *noopSummaryVec) With(Labels) Summary { return &noopSummary{} }
|
||||
func (n *noopSummaryVec) WithLabelValues(...string) Summary { return &noopSummary{} }
|
||||
func (n *noopSummaryVec) MustCurryWith(Labels) SummaryVec { return n }
|
||||
func (n *noopSummaryVec) Reset() {}
|
||||
|
||||
// noopRegistry provides a registry that gathers nothing.
|
||||
@@ -66,6 +104,7 @@ func newNoopRegistry() Registry { return &noopRegistry{} }
|
||||
|
||||
func (r *noopRegistry) Register(_ Collector) error { return nil }
|
||||
func (r *noopRegistry) MustRegister(_ ...Collector) {}
|
||||
func (r *noopRegistry) Unregister(_ Collector) bool { return false }
|
||||
func (r *noopRegistry) Gather() ([]*MetricFamily, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
+6
-6
@@ -254,7 +254,7 @@
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #f093fb;"></div>
|
||||
<span>Ringtail</span>
|
||||
<span>Corona</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background: #4facfe;"></div>
|
||||
@@ -340,7 +340,7 @@
|
||||
<td>Level 5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Ringtail (Hybrid)</strong></td>
|
||||
<td><strong>Corona (Hybrid)</strong></td>
|
||||
<td class="good">6,780</td>
|
||||
<td class="good">0.15 ms</td>
|
||||
<td class="good">99.97%</td>
|
||||
@@ -435,7 +435,7 @@
|
||||
tension: 0.4
|
||||
},
|
||||
{
|
||||
label: 'Ringtail',
|
||||
label: 'Corona',
|
||||
data: Array(20).fill(0).map(() => 6500 + Math.random() * 500),
|
||||
borderColor: '#f093fb',
|
||||
tension: 0.4
|
||||
@@ -456,7 +456,7 @@
|
||||
const verificationChart = new Chart(verificationCtx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['BLS', 'ML-DSA', 'SLH-DSA', 'Ringtail', 'Verkle'],
|
||||
labels: ['BLS', 'ML-DSA', 'SLH-DSA', 'Corona', 'Verkle'],
|
||||
datasets: [{
|
||||
label: 'Average Verification Time',
|
||||
data: [0.08, 0.12, 0.47, 0.15, 0.05],
|
||||
@@ -488,7 +488,7 @@
|
||||
const gasChart = new Chart(gasCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['BLS', 'ML-DSA', 'SLH-DSA', 'Ringtail', 'Verkle', 'Other'],
|
||||
labels: ['BLS', 'ML-DSA', 'SLH-DSA', 'Corona', 'Verkle', 'Other'],
|
||||
datasets: [{
|
||||
data: [25, 30, 10, 20, 10, 5],
|
||||
backgroundColor: [
|
||||
@@ -556,7 +556,7 @@
|
||||
dataset.data.shift();
|
||||
const base = dataset.label === 'BLS' ? 12000 :
|
||||
dataset.label === 'ML-DSA' ? 8000 :
|
||||
dataset.label === 'Ringtail' ? 6500 : 18000;
|
||||
dataset.label === 'Corona' ? 6500 : 18000;
|
||||
dataset.data.push(base + Math.random() * 1000);
|
||||
});
|
||||
signatureChart.update('none');
|
||||
|
||||
+6
-1
@@ -8,6 +8,10 @@ type Registerer interface {
|
||||
Metrics
|
||||
Register(Collector) error
|
||||
MustRegister(...Collector)
|
||||
// Unregister removes a previously registered collector, freeing its name
|
||||
// to be registered again. Returns true if it was present. Mirrors
|
||||
// prometheus.Registerer.Unregister.
|
||||
Unregister(Collector) bool
|
||||
}
|
||||
|
||||
// Registry is a registerer that can also gather metric families.
|
||||
@@ -41,8 +45,9 @@ type prefixRegisterer struct {
|
||||
next Registerer
|
||||
}
|
||||
|
||||
func (p *prefixRegisterer) Register(c Collector) error { return p.next.Register(c) }
|
||||
func (p *prefixRegisterer) Register(c Collector) error { return p.next.Register(c) }
|
||||
func (p *prefixRegisterer) MustRegister(cs ...Collector) { p.next.MustRegister(cs...) }
|
||||
func (p *prefixRegisterer) Unregister(c Collector) bool { return p.next.Unregister(c) }
|
||||
func (p *prefixRegisterer) NewCounter(name, help string) Counter {
|
||||
return p.next.NewCounter(p.prefix+name, help)
|
||||
}
|
||||
|
||||
Generated
Vendored
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024, Lux Partners Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# Licensing
|
||||
|
||||
This repository is licensed under the **BSD-3-Clause License** (see [LICENSE](LICENSE)). It belongs to the **public** tier of the Lux three-tier IP strategy: anyone may use, fork, or redistribute it, including for commercial purposes, subject to the BSD-3-Clause License terms.
|
||||
|
||||
For the canonical Lux IP and licensing strategy, see:
|
||||
<https://github.com/luxfi/.github/blob/main/profile/README.md>
|
||||
|
||||
For commercial inquiries that go beyond the public tier (e.g. private moat acceleration kernels, Eco-tier patent-protected primitives outside Authorized Networks), contact `licensing@lux.network`.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Hanzo Tls
|
||||
|
||||
## Overview
|
||||
Go module: github.com/luxfi/tls
|
||||
|
||||
## Tech Stack
|
||||
- **Language**: Go
|
||||
|
||||
## Build & Run
|
||||
```bash
|
||||
go build ./...
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Structure
|
||||
```
|
||||
tls/
|
||||
LICENSE
|
||||
asn1.go
|
||||
certificate.go
|
||||
go.mod
|
||||
go.sum
|
||||
parse.go
|
||||
tls.go
|
||||
verify.go
|
||||
```
|
||||
|
||||
## Key Files
|
||||
- `go.mod` -- Go module definition
|
||||
+178
-1
@@ -1,4 +1,4 @@
|
||||
# LLM.md - Hanzo Zap
|
||||
# Hanzo Zap
|
||||
|
||||
## Overview
|
||||
Go module: github.com/luxfi/zap
|
||||
@@ -71,3 +71,180 @@ The QUIC transport adds, beyond what TCP+TLS gives:
|
||||
|
||||
See `quic/README.md` for details, defaults, deployment notes, and the
|
||||
threat-model discussion.
|
||||
|
||||
### GPU-aware `transport/` subpackage (LP-203 zero-copy)
|
||||
|
||||
`github.com/luxfi/zap/transport` registers four implementations behind
|
||||
the same `Transport` interface:
|
||||
|
||||
| Name | Build tags | Wire | GPU-resident bytes |
|
||||
|-------------|-------------------------------------|----------------|--------------------|
|
||||
| `default` | always | in-proc / TCP | no |
|
||||
| `uma` | `cgo,linux,cuda` or `cgo,darwin` | NIC → managed | yes (cudaMallocManaged on linux, MTLBuffer on darwin) |
|
||||
| `gpudirect` | `cgo,linux,gpudirect,cuda` | NIC → GPU VRAM | yes (DMA-buf MR) |
|
||||
| `dpdk` | `cgo,linux,dpdk` + `pkg-config libdpdk` | NIC poll loop | no (CPU hugepage) |
|
||||
|
||||
`transport.Pick("")` selects in order `gpudirect > dpdk > uma >
|
||||
default`. An operator override via `ZAP_TRANSPORT={default,uma,gpudirect,
|
||||
dpdk}` returns ErrNotAvailable on a host that can't provide it (no
|
||||
silent fallback when the operator was explicit).
|
||||
|
||||
Transports that can hand out GPU-resident buffers also implement
|
||||
`BufferAllocator`:
|
||||
|
||||
```go
|
||||
tr, _ := transport.Pick("uma")
|
||||
alloc, ok := tr.(transport.BufferAllocator)
|
||||
if !ok { /* default transport — heap allocate */ }
|
||||
buf, _ := alloc.AllocBuffer(1 << 20) // 1 MiB managed slab
|
||||
copy(buf.Bytes(), payload) // CPU writes
|
||||
gpuKernel.launch(buf.DevicePtr(), 1<<20) // GPU reads same bytes
|
||||
buf.Release() // back to slab pool
|
||||
```
|
||||
|
||||
UMA pool is slab-allocated (`slabClasses = 256B, 1KiB, 4KiB, 16KiB,
|
||||
64KiB, 1MiB`). Pool budget defaults to 4 GiB via `ZAP_UMA_POOL_BYTES`.
|
||||
Sizes above 1 MiB take a cold-path `cudaMallocManaged` per call.
|
||||
|
||||
Boot emits one `slog.Info` line listing the chosen transport + probe
|
||||
errors for the others. Silence with `ZAP_TRANSPORT_QUIET=1`.
|
||||
|
||||
Probe gaps are reported honestly: on a host with libibverbs but no
|
||||
Mellanox HCA visible, `Pick("gpudirect")` returns
|
||||
`missing prereq(s): [ibverbs-device raw-packet-cap nvidia-peermem]`
|
||||
— never a fake success.
|
||||
|
||||
### Read-buffer pool (bufpool.go)
|
||||
|
||||
The TCP dispatch read path sources frame buffers from a quantized
|
||||
`sync.Pool` (`64B / 256B / 1 KiB / 4 KiB / 16 KiB / 64 KiB`) via
|
||||
`readMessageRawPooled` + refcounted `*bufRef`. `Message` carries an
|
||||
optional `refs *bufRef`; `Message.Release()` decrements the refcount
|
||||
and (at zero) returns the slab. `Message.Retain()` extends lifetime
|
||||
across goroutine boundaries (e.g. when a Call response is delivered
|
||||
via channel).
|
||||
|
||||
Wire format is byte-identical — only the buffer lifecycle changed.
|
||||
Frames over 64 KiB fall back to a one-off heap alloc transparent to
|
||||
the caller. `Message.Release()` on a Builder-built or unpooled
|
||||
message is a no-op so existing callers keep working unmodified.
|
||||
|
||||
Read benchmarks (Apple M1 Max, `go test -bench='BenchmarkRead'
|
||||
-benchtime=2s -count=2`):
|
||||
|
||||
| Size | Legacy ns/op | Pooled ns/op | Speedup | Allocs (legacy → pooled) |
|
||||
|-------|--------------|--------------|---------|--------------------------|
|
||||
| 64B | 38 | 40 | 1.0x | 2 → 1, 68 → 4 B/op |
|
||||
| 256B | 68 | 43 | 1.6x | 2 → 1, 260 → 4 B/op |
|
||||
| 1KB | 284 | 54 | 5.3x | 2 → 1, 1028 → 4 B/op |
|
||||
| 4KB | 716 | 125 | 5.7x | 2 → 1, 4100 → 4 B/op |
|
||||
| 16KB | 2244 | 355 | 6.3x | 2 → 1, 16388 → 4 B/op |
|
||||
| 64KB | 4802 | 1142 | 4.2x | 2 → 1, 65540 → 4 B/op |
|
||||
|
||||
### Per-Call QUIC streams (TransportStreamer)
|
||||
|
||||
`Node.Call` over QUIC opens a fresh per-Call bidirectional stream via
|
||||
`TransportStreamer.OpenCallStream` instead of serializing on the
|
||||
shared control stream's `ctrlMu`. The server-side `acceptCallStreams`
|
||||
goroutine spawns one handler goroutine per accepted stream so
|
||||
concurrent Calls execute in parallel up to QUIC's stream limit (1024
|
||||
by default).
|
||||
|
||||
Wire format on each per-Call stream is one length-prefixed ZAP frame
|
||||
in each direction — no correlation header needed since each stream
|
||||
carries exactly one request + one response.
|
||||
|
||||
Per-stream is the only Call path on QUIC; there is no fallback. The
|
||||
control stream carries one-way Sends only.
|
||||
|
||||
End-to-end Call throughput (2 ms simulated handler):
|
||||
|
||||
| Concurrency | Per-stream | Reference | Speedup |
|
||||
|-------------|-------------|-------------|---------|
|
||||
| 1 worker | 3.19 ms/op | 3.17 ms/op | 1.0x |
|
||||
| 10 workers | 1.24 ms/op | 3.01 ms/op | 2.4x |
|
||||
| 100 workers | 1.06 ms/op | 3.01 ms/op | 2.85x |
|
||||
|
||||
Reference = handler latency on a serialized control-stream path
|
||||
(pre-optimization measurement, retained for context). Per-stream
|
||||
overlaps handlers in flight (peak inflight = 32 observed in
|
||||
`TestQUICCallConcurrent`).
|
||||
|
||||
## Locality-adaptive Router (P1) — `router.go`, `interface.go`
|
||||
|
||||
The one-Send-API router that decouples WHO (`Destination`) from HOW
|
||||
(`Interface`) from the message (`Payload`), so a caller never names a port or a
|
||||
transport:
|
||||
|
||||
```go
|
||||
r := zap.NewRouter()
|
||||
r.Register(zap.NewNodeInterface(node)) // network fallback, Cost 10 (conn_pq wire)
|
||||
inproc := zap.NewInProcessInterface() // Cost 0
|
||||
inproc.Register("hanzo.o11y.traces", handler)
|
||||
r.Register(inproc)
|
||||
|
||||
r.Send(ctx, "hanzo.o11y.traces", zap.Payload{Value: spans, Encode: encodeFn})
|
||||
```
|
||||
|
||||
**The routing rule is a Cost table, not a caller branch.** `Send` picks the
|
||||
cheapest `Interface` whose `CanReach(dst)` is true. When the destination lives in
|
||||
THIS binary, `InProcessInterface` (Cost 0) wins and the handler is called with
|
||||
the LIVE `Payload.Value` — zero serialization, zero copy, zero socket. `Encode`
|
||||
(the zero-copy wire form) is invoked ONLY when a network `Interface` is chosen,
|
||||
and only then. "If in-process skip TCP; else use the wire" falls out for free.
|
||||
|
||||
Three "transport"-ish names, kept orthogonal — do not conflate:
|
||||
- `zap.Transport` (int enum `TransportTCP`/`TransportQUIC`) — which wire ONE Node
|
||||
speaks. `NodeInterface` adapts such a Node into the router.
|
||||
- `zap.Router` — this P1 locality-adaptive router over many `Interface`s.
|
||||
- `github.com/luxfi/zap/transport` — the GPU-aware buffer subpackage (LP-203).
|
||||
|
||||
This is Reticulum's model made post-quantum + zero-copy: `Destination` is a
|
||||
capability aspect today; the announce-routed PQ-identity destination hash
|
||||
(multi-hop, mDNS/K8s-SRV discovery interfaces, UDP/QUIC/radio interfaces) refines
|
||||
it in later phases WITHOUT moving the `Send` call site. `Node` is unchanged;
|
||||
`NodeInterface` adapts it. First consumer: hanzo/cloud o11y telemetry (cloud's own
|
||||
spans → in-process → ClickHouse sink, never the wire). See `router_test.go`
|
||||
(7 tests: cost-preference, zero-serialize proof, fallback, no-route, sort,
|
||||
re-register, non-encodable-refused).
|
||||
|
||||
## DexSession orchestration (`dexsession/`)
|
||||
|
||||
ZAP/Cap'n-Proto orchestration layer for the Lux DEX: capability transport +
|
||||
promise pipelining that makes the native C↔D atomic settlement flow FEEL
|
||||
synchronous, while the value boundary stays 100% native.
|
||||
|
||||
THE HARD INVARIANT: a ZAP response is NEVER sufficient to move money. Money
|
||||
moves ONLY when C consumes a real D→C atomic export object, or D consumes a real
|
||||
C→D object. The orchestration layer holds no StateDB/AtomicState/shared-memory
|
||||
and has NO compile edge to `precompile/dex` or the EVM — it cannot import the C
|
||||
Verify path and Verify cannot import it. It traffics in VALUES (`[20]byte`
|
||||
accounts, `[32]byte` ids, `uint64` amounts) and bytes only:
|
||||
|
||||
- `prepareSwapIntent` → 0x9999 CALLDATA the USER signs (no funds reserved, no
|
||||
enforceable amountOut — the floor rides MinAmountOut in the signed tx).
|
||||
- `notifyIntent` → tells D to scan/import the C→D object the signed tx created;
|
||||
cannot make a D match valid without a committed D block; returns a watch.
|
||||
- `importSettlement` → finalization CALLDATA / a tx that POINTS at a `DExportRef`;
|
||||
the chain re-reads the real D→C object and binds recipient/asset/amount to it.
|
||||
|
||||
`DExportRef` is a POINTER `{sourceChainID, sourceTxID, outputIndex, intentID}`,
|
||||
NOT a DFillReceipt. "No capability moves money" is provable by EXHAUSTION: the
|
||||
surface has no creditBalance/settleFill/overrideMatch/adminWithdraw method.
|
||||
Capabilities (QuoteCap/IntentCap/WatchCap/SettlementCap/AdminCap) are unforgeable
|
||||
tokens scoped to a bootstrap grant; a session cannot widen its own authority.
|
||||
|
||||
The 60-byte atomic object wire, `DeriveIntentID`, `DeriveUTXOID`, the V4 swap
|
||||
selector (0xF3CD914C), and the Phase-A/B hookData tags are REPRODUCED as pure
|
||||
values (not imported) and pinned byte-identical to `precompile/dex` +
|
||||
`chains/dexvm` by `parity_test.go` — the "three homes" pattern, keeping the
|
||||
transport module free of the EVM/cgo dep graph.
|
||||
|
||||
Promise pipelining (`promise.go`/`pipeline.go`): dependent calls dispatch the
|
||||
instant their inputs resolve (`thenAsync`), so `quote→prepare→notify→
|
||||
onCommitted→import` overlaps network latency; `SwapFlow`/`SwapFlowWithSender`
|
||||
wire the declarative end-to-end path. The watch streams D's result (poll/push)
|
||||
and resolves to a pointer on commit.
|
||||
|
||||
Build/test (pure Go): `CGO_ENABLED=0 GOWORK=off go test ./dexsession/`. The
|
||||
critical adversarial proof is `TestRED_ZAP_ResponseAloneCannotMoveMoney`.
|
||||
|
||||
+38
@@ -5,6 +5,44 @@ The canonical KV + transport for Lux is `luxfi/zap` (this repo) backed by
|
||||
**and** cold ancient stores. PebbleDB / leveldb remain only as legacy
|
||||
in-place upgrade paths.
|
||||
|
||||
## ZAP runtime consolidation (serialization core)
|
||||
|
||||
`github.com/zap-proto/go` is the canonical pure-stdlib runtime for the ZAP
|
||||
wire format (`Parse`/`Message`/`Object`/`List` + `Builder`/`ObjectBuilder`/
|
||||
`ListBuilder` + `Schema`). This repo (`luxfi/zap`) historically duplicated
|
||||
that core in its own `zap.go`/`builder.go`/`schema.go` while adding the
|
||||
network layer (Node mesh, QUIC/TCP, mDNS, PQ-TLS handshake, EVM types, the
|
||||
`forward` HTTP-over-ZAP contract). The duplication is being collapsed so
|
||||
there is ONE serialization runtime; `zap-proto/go` is the source of truth and
|
||||
this repo will ride it.
|
||||
|
||||
**Status (first safe step landed):** the two cores are now proven to emit a
|
||||
byte-identical data segment, and `zap-proto/go` has absorbed the two deltas
|
||||
that previously diverged it from this repo's hardened core, so the readers now
|
||||
agree on accept/reject for every input.
|
||||
|
||||
- **Wire proof.** `zap_crosswire_test.go` (here) and the file of the same
|
||||
name in `zap-proto/go` pin the SAME `goldenV1Hex` constant. This repo's
|
||||
`NewBuilderV1` emits it byte-for-byte; `zap-proto/go`'s default `NewBuilder`
|
||||
emits it too. The ONLY header difference between this repo's default
|
||||
(Version2) output and the v1 golden vector is byte 4 (the version). A live
|
||||
cross-encode in a throwaway `go.work` joining both modules confirmed every
|
||||
field round-trips both directions.
|
||||
- **Deltas reconciled in `zap-proto/go`** (additive — no honest wire changed):
|
||||
(1) it now accepts `Version1` AND `Version2` on read (was v1-only → it would
|
||||
reject this repo's default v2 buffers); (2) its reader now applies the same
|
||||
hardening this repo already had — unsigned-forward `Bytes` with
|
||||
`absPos < HeaderSize` rejection, `Object`/`List` `absOffset < HeaderSize`
|
||||
rejection, and `List` `length ≤ msgsize` clamp.
|
||||
|
||||
**Remaining steps** (see `zap-proto/go`'s LLM.md for the canonical list):
|
||||
`schema.go` is already byte-identical and is the first alias target; the
|
||||
reader/builder alias follows once `cmd/zapgen` lands (the read-buffer pool —
|
||||
`Message.refs`/`Release`/`Retain` — is network-local and stays here); the
|
||||
`StartObject` pre-reserve discipline is a documented, test-covered delta to
|
||||
unify when the builder is aliased (adopt `zap-proto/go`'s pre-reserve — it is
|
||||
strictly more reserving and byte-identical for every honest layout).
|
||||
|
||||
This doc enumerates every storage surface in luxd and the migration path
|
||||
per backend.
|
||||
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="zap" width="880"></p>
|
||||
|
||||
# ZAP
|
||||
|
||||
**Z**ero-copy **A**pplication **P**rotocol for Lux.
|
||||
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Pooled read-buffer slabs.
|
||||
//
|
||||
// readMessageRaw used to do `make([]byte, length)` on every frame, which
|
||||
// allocates GC-tracked memory each read. The buffer's lifetime extends
|
||||
// from one Read call to wherever the caller releases the *Message —
|
||||
// typically the end of a handler invocation, or the moment a Call
|
||||
// goroutine consumes its response from the response channel.
|
||||
//
|
||||
// We quantize buffer sizes into six classes (64B, 256B, 1 KiB, 4 KiB,
|
||||
// 16 KiB, 64 KiB) so each `sync.Pool` holds slabs of one shape only
|
||||
// — that avoids the variable-size pool defeat where a 1 KiB Get
|
||||
// returns a 64 KiB slab that's never reused at its real shape.
|
||||
//
|
||||
// A `bufRef` is a refcount wrapper around the slab. dispatchLoop
|
||||
// increments the count when a *Message escapes to a channel (so the
|
||||
// awaiting Call goroutine can Release) and decrements once after the
|
||||
// handler/parse path is done. When refs hits zero the slab returns
|
||||
// to its pool.
|
||||
//
|
||||
// Anything larger than the largest class (> 64 KiB) falls through to
|
||||
// a one-off heap allocation with a nil pool — Release on those is a
|
||||
// no-op so the GC reclaims them naturally. Three reasons we don't pool
|
||||
// >64 KiB:
|
||||
// - Large frames are rare on the steady-state hot path (most
|
||||
// dispatchLoop frames are control + correlated RPCs).
|
||||
// - sync.Pool fairness under contention degrades with slab size:
|
||||
// keeping >64 KiB slabs hot can pin tens of MiB per process.
|
||||
// - The 10 MiB MaxFrameSize ceiling already caps the worst case
|
||||
// to a single heap allocation per giant frame.
|
||||
|
||||
// bufSizes lists the quantization in ascending order. Anything between
|
||||
// classes rounds UP to the next class — that's the only way to keep
|
||||
// a class invariant ("a 256B slab is exactly 256 bytes of cap").
|
||||
var bufSizes = [...]int{64, 256, 1024, 4 * 1024, 16 * 1024, 64 * 1024}
|
||||
|
||||
// bufPools[i] holds slabs of bufSizes[i] capacity.
|
||||
//
|
||||
// Declared as a pointer-to-array so the package-level initializer
|
||||
// doesn't copy a `sync.Pool` value (which contains sync.noCopy). The
|
||||
// pools themselves never move after init.
|
||||
var bufPools = func() *[len(bufSizes)]sync.Pool {
|
||||
pools := new([len(bufSizes)]sync.Pool)
|
||||
for i, sz := range bufSizes {
|
||||
size := sz // capture before closure binding
|
||||
pools[i].New = func() any {
|
||||
b := make([]byte, size)
|
||||
return &b
|
||||
}
|
||||
}
|
||||
return pools
|
||||
}()
|
||||
|
||||
// pickBufClass returns the pool index for a buffer of length n,
|
||||
// or -1 if n exceeds the largest class.
|
||||
//
|
||||
// Branchless ladder beats binary search at this scale (6 classes) —
|
||||
// the predicted compare-and-jump is one pipeline-friendly cycle on
|
||||
// modern superscalars. We tested both; the ladder won by 3-4 ns.
|
||||
func pickBufClass(n int) int {
|
||||
switch {
|
||||
case n <= bufSizes[0]:
|
||||
return 0
|
||||
case n <= bufSizes[1]:
|
||||
return 1
|
||||
case n <= bufSizes[2]:
|
||||
return 2
|
||||
case n <= bufSizes[3]:
|
||||
return 3
|
||||
case n <= bufSizes[4]:
|
||||
return 4
|
||||
case n <= bufSizes[5]:
|
||||
return 5
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// bufRef is a refcounted slab. The refcount is set to 1 by getBuf
|
||||
// and decremented by release. When it hits zero the underlying slab
|
||||
// is returned to its pool (if any).
|
||||
//
|
||||
// One bufRef tracks one slab; we pool the bufRef struct itself via a
|
||||
// dedicated pool so the refcount header doesn't churn the heap either.
|
||||
//
|
||||
// We hold the slab as *[]byte (not []byte) so release can Put the
|
||||
// same pointer that Get returned — sync.Pool deduplicates on pointer
|
||||
// identity. Putting a fresh-stack &buf forces a new heap alloc for
|
||||
// the slice header on every release, which was a 24-byte/op overhead
|
||||
// on the first iteration.
|
||||
type bufRef struct {
|
||||
// bufp is the pooled slice header. nil for over-class one-offs.
|
||||
bufp *[]byte
|
||||
|
||||
// fallback holds the slab when bufp is nil (over-class). Sized
|
||||
// exactly to n; reclaimed by GC.
|
||||
fallback []byte
|
||||
|
||||
// n is the frame length the caller asked for (≤ len(buf)).
|
||||
n int
|
||||
|
||||
// class is the index into bufPools, or -1 if buf was a one-off
|
||||
// heap allocation (frame > largest pool class).
|
||||
class int
|
||||
|
||||
// refs counts live references to buf. Starts at 1, +1 on retain,
|
||||
// -1 on release. When it reaches 0 the slab returns to its pool.
|
||||
refs atomic.Int32
|
||||
}
|
||||
|
||||
// refPool pools the bufRef header itself. bufRefs are tiny (40 bytes
|
||||
// on 64-bit) but they appear on every frame — pooling them takes them
|
||||
// off the GC's plate the same way the slab pool removes the buffer.
|
||||
var refPool = sync.Pool{
|
||||
New: func() any { return new(bufRef) },
|
||||
}
|
||||
|
||||
// getBuf returns a refcounted slab of at least n bytes, with refs=1.
|
||||
//
|
||||
// Caller must call release() (or attach the ref to a *Message which
|
||||
// will release on its own teardown). Failure to release leaks the
|
||||
// slab — sync.Pool will eventually reclaim it via its own GC hook,
|
||||
// but the steady-state allocation savings depend on prompt release.
|
||||
func getBuf(n int) *bufRef {
|
||||
r := refPool.Get().(*bufRef)
|
||||
r.n = n
|
||||
r.refs.Store(1)
|
||||
|
||||
class := pickBufClass(n)
|
||||
r.class = class
|
||||
if class < 0 {
|
||||
// Anything larger than the largest class: one-off heap alloc.
|
||||
// Release returns the bufRef header to refPool but lets GC
|
||||
// reclaim the buffer itself.
|
||||
r.bufp = nil
|
||||
r.fallback = make([]byte, n)
|
||||
return r
|
||||
}
|
||||
r.bufp = bufPools[class].Get().(*[]byte)
|
||||
r.fallback = nil
|
||||
return r
|
||||
}
|
||||
|
||||
// rawBuf returns the underlying slab as a slice of class capacity
|
||||
// (or n bytes for over-class fallbacks). io.ReadFull writes into the
|
||||
// first n bytes.
|
||||
func (r *bufRef) rawBuf() []byte {
|
||||
if r.bufp != nil {
|
||||
return (*r.bufp)[:bufSizes[r.class]]
|
||||
}
|
||||
return r.fallback
|
||||
}
|
||||
|
||||
// Bytes returns the slab sub-slice the caller asked for. The capacity
|
||||
// is the pool class size; the length is the requested n. Treat as
|
||||
// read-only after the underlying frame has been parsed.
|
||||
func (r *bufRef) Bytes() []byte {
|
||||
if r.bufp != nil {
|
||||
return (*r.bufp)[:r.n]
|
||||
}
|
||||
return r.fallback[:r.n]
|
||||
}
|
||||
|
||||
// retain increments the refcount. Called when a *Message constructed
|
||||
// over this slab escapes from the dispatch loop to a goroutine that
|
||||
// will release it later (e.g. a Call response delivered via channel).
|
||||
func (r *bufRef) retain() {
|
||||
r.refs.Add(1)
|
||||
}
|
||||
|
||||
// release decrements the refcount. When it hits zero the slab is
|
||||
// returned to its pool (or simply dropped to GC if it was an
|
||||
// over-class one-off allocation).
|
||||
//
|
||||
// release on a nil bufRef is a no-op — Message's release field is
|
||||
// nil when the message wasn't built over a pooled buffer, and we
|
||||
// don't want callers to have to branch on that.
|
||||
func (r *bufRef) release() {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
if r.refs.Add(-1) != 0 {
|
||||
return
|
||||
}
|
||||
if r.bufp != nil {
|
||||
bufPools[r.class].Put(r.bufp)
|
||||
r.bufp = nil
|
||||
}
|
||||
r.fallback = nil
|
||||
r.n = 0
|
||||
r.class = 0
|
||||
refPool.Put(r)
|
||||
}
|
||||
+150
-66
@@ -6,6 +6,7 @@ package zap
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Builder constructs ZAP messages.
|
||||
@@ -94,27 +95,30 @@ type ObjectBuilder struct {
|
||||
b *Builder
|
||||
startPos int
|
||||
dataSize int
|
||||
offsets []offsetEntry
|
||||
}
|
||||
|
||||
type offsetEntry struct {
|
||||
fieldOffset int
|
||||
targetPos int // positive = known position, negative = deferred write
|
||||
data []byte // actual bytes to write (deferred text/bytes)
|
||||
}
|
||||
|
||||
// StartObject starts building an object with the given data size.
|
||||
func (b *Builder) StartObject(dataSize int) *ObjectBuilder {
|
||||
// StartObject starts building an object with the given data size, RESERVING the
|
||||
// full fixed section up front (zero-filled). Reserving eagerly means a variable
|
||||
// field (SetBytes/SetText) can append its tail data immediately after the fixed
|
||||
// section and patch its own relative pointer on the spot — there is no deferred
|
||||
// offset list to record intentions and replay in Finish. Callers already build
|
||||
// child objects/lists BEFORE StartObject (the writeXxxList-then-StartObject
|
||||
// pattern), so the object's own Set* calls are the only buffer writes between
|
||||
// StartObject and Finish; appending tail bytes immediately is safe and produces
|
||||
// byte-identical wire.
|
||||
func (b *Builder) StartObject(dataSize int) ObjectBuilder {
|
||||
b.align(Alignment)
|
||||
return &ObjectBuilder{
|
||||
ob := ObjectBuilder{
|
||||
b: b,
|
||||
startPos: b.pos,
|
||||
dataSize: dataSize,
|
||||
}
|
||||
ob.ensureField(dataSize)
|
||||
return ob
|
||||
}
|
||||
|
||||
// SetBool sets a bool field.
|
||||
func (ob *ObjectBuilder) SetBool(fieldOffset int, v bool) {
|
||||
func (ob ObjectBuilder) SetBool(fieldOffset int, v bool) {
|
||||
if v {
|
||||
ob.SetUint8(fieldOffset, 1)
|
||||
} else {
|
||||
@@ -123,88 +127,111 @@ func (ob *ObjectBuilder) SetBool(fieldOffset int, v bool) {
|
||||
}
|
||||
|
||||
// SetUint8 sets a uint8 field.
|
||||
func (ob *ObjectBuilder) SetUint8(fieldOffset int, v uint8) {
|
||||
func (ob ObjectBuilder) SetUint8(fieldOffset int, v uint8) {
|
||||
ob.ensureField(fieldOffset + 1)
|
||||
ob.b.buf[ob.startPos+fieldOffset] = v
|
||||
}
|
||||
|
||||
// SetUint16 sets a uint16 field.
|
||||
func (ob *ObjectBuilder) SetUint16(fieldOffset int, v uint16) {
|
||||
func (ob ObjectBuilder) SetUint16(fieldOffset int, v uint16) {
|
||||
ob.ensureField(fieldOffset + 2)
|
||||
binary.LittleEndian.PutUint16(ob.b.buf[ob.startPos+fieldOffset:], v)
|
||||
}
|
||||
|
||||
// SetUint32 sets a uint32 field.
|
||||
func (ob *ObjectBuilder) SetUint32(fieldOffset int, v uint32) {
|
||||
func (ob ObjectBuilder) SetUint32(fieldOffset int, v uint32) {
|
||||
ob.ensureField(fieldOffset + 4)
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[ob.startPos+fieldOffset:], v)
|
||||
}
|
||||
|
||||
// SetUint64 sets a uint64 field.
|
||||
func (ob *ObjectBuilder) SetUint64(fieldOffset int, v uint64) {
|
||||
func (ob ObjectBuilder) SetUint64(fieldOffset int, v uint64) {
|
||||
ob.ensureField(fieldOffset + 8)
|
||||
binary.LittleEndian.PutUint64(ob.b.buf[ob.startPos+fieldOffset:], v)
|
||||
}
|
||||
|
||||
// SetInt8 sets an int8 field.
|
||||
func (ob *ObjectBuilder) SetInt8(fieldOffset int, v int8) {
|
||||
func (ob ObjectBuilder) SetInt8(fieldOffset int, v int8) {
|
||||
ob.SetUint8(fieldOffset, uint8(v))
|
||||
}
|
||||
|
||||
// SetInt16 sets an int16 field.
|
||||
func (ob *ObjectBuilder) SetInt16(fieldOffset int, v int16) {
|
||||
func (ob ObjectBuilder) SetInt16(fieldOffset int, v int16) {
|
||||
ob.SetUint16(fieldOffset, uint16(v))
|
||||
}
|
||||
|
||||
// SetInt32 sets an int32 field.
|
||||
func (ob *ObjectBuilder) SetInt32(fieldOffset int, v int32) {
|
||||
func (ob ObjectBuilder) SetInt32(fieldOffset int, v int32) {
|
||||
ob.SetUint32(fieldOffset, uint32(v))
|
||||
}
|
||||
|
||||
// SetInt64 sets an int64 field.
|
||||
func (ob *ObjectBuilder) SetInt64(fieldOffset int, v int64) {
|
||||
func (ob ObjectBuilder) SetInt64(fieldOffset int, v int64) {
|
||||
ob.SetUint64(fieldOffset, uint64(v))
|
||||
}
|
||||
|
||||
// SetFloat32 sets a float32 field.
|
||||
func (ob *ObjectBuilder) SetFloat32(fieldOffset int, v float32) {
|
||||
func (ob ObjectBuilder) SetFloat32(fieldOffset int, v float32) {
|
||||
ob.SetUint32(fieldOffset, float32bits(v))
|
||||
}
|
||||
|
||||
// SetFloat64 sets a float64 field.
|
||||
func (ob *ObjectBuilder) SetFloat64(fieldOffset int, v float64) {
|
||||
func (ob ObjectBuilder) SetFloat64(fieldOffset int, v float64) {
|
||||
ob.SetUint64(fieldOffset, float64bits(v))
|
||||
}
|
||||
|
||||
// SetBytesFixed copies len(v) bytes inline at fieldOffset within the
|
||||
// object's fixed payload. This is the generic-width counterpart of
|
||||
// SetHash (32 bytes) and SetAddress (20 bytes); it covers any N>0
|
||||
// inline byte-array slot (e.g., LP-201 SessionID [16]byte, LP-208
|
||||
// QuasarWitness [96]byte).
|
||||
//
|
||||
// Unlike SetBytes (which writes a variable-length tail pointer
|
||||
// {relOffset uint32, length uint32}), SetBytesFixed writes the bytes
|
||||
// IN PLACE in the fixed payload. Use this when the schema declares a
|
||||
// fixed-width byte field; use SetBytes when the schema declares a
|
||||
// variable-length tail.
|
||||
//
|
||||
// Panics on len(v) == 0 are avoided: a zero-length argument is a
|
||||
// no-op (the slot is left as the zero value).
|
||||
func (ob ObjectBuilder) SetBytesFixed(fieldOffset int, v []byte) {
|
||||
if len(v) == 0 {
|
||||
return
|
||||
}
|
||||
ob.ensureField(fieldOffset + len(v))
|
||||
copy(ob.b.buf[ob.startPos+fieldOffset:], v)
|
||||
}
|
||||
|
||||
// SetText sets a text (string) field.
|
||||
func (ob *ObjectBuilder) SetText(fieldOffset int, v string) {
|
||||
func (ob ObjectBuilder) SetText(fieldOffset int, v string) {
|
||||
ob.SetBytes(fieldOffset, []byte(v))
|
||||
}
|
||||
|
||||
// SetBytes sets a bytes field.
|
||||
// The data is written after the object's fixed section during Finish().
|
||||
func (ob *ObjectBuilder) SetBytes(fieldOffset int, v []byte) {
|
||||
ob.ensureField(fieldOffset + 8) // offset + length
|
||||
|
||||
func (ob ObjectBuilder) SetBytes(fieldOffset int, v []byte) {
|
||||
if len(v) == 0 {
|
||||
// Null pointer
|
||||
// Null pointer (offset 0, length 0).
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[ob.startPos+fieldOffset:], 0)
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[ob.startPos+fieldOffset+4:], 0)
|
||||
return
|
||||
}
|
||||
// The fixed section is fully reserved by StartObject, so b.pos is at the
|
||||
// tail: append the data now and patch this field's (relOffset, length) pair
|
||||
// immediately — no deferred offset list, no per-object slice allocation.
|
||||
dataPos := ob.b.pos
|
||||
ob.b.grow(len(v))
|
||||
copy(ob.b.buf[ob.b.pos:ob.b.pos+len(v)], v)
|
||||
ob.b.pos += len(v)
|
||||
|
||||
// Store the data and length; the relative offset is patched in Finish()
|
||||
ob.offsets = append(ob.offsets, offsetEntry{
|
||||
fieldOffset: fieldOffset,
|
||||
data: append([]byte(nil), v...), // copy the data
|
||||
})
|
||||
|
||||
// Write the length now
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[ob.startPos+fieldOffset+4:], uint32(len(v)))
|
||||
fieldAbsPos := ob.startPos + fieldOffset
|
||||
relOffset := int32(dataPos - fieldAbsPos)
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[fieldAbsPos:], uint32(relOffset))
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[fieldAbsPos+4:], uint32(len(v)))
|
||||
}
|
||||
|
||||
// SetObject sets a nested object field (by offset).
|
||||
func (ob *ObjectBuilder) SetObject(fieldOffset int, objOffset int) {
|
||||
func (ob ObjectBuilder) SetObject(fieldOffset int, objOffset int) {
|
||||
ob.ensureField(fieldOffset + 4)
|
||||
|
||||
if objOffset == 0 {
|
||||
@@ -218,7 +245,7 @@ func (ob *ObjectBuilder) SetObject(fieldOffset int, objOffset int) {
|
||||
}
|
||||
|
||||
// SetList sets a list field.
|
||||
func (ob *ObjectBuilder) SetList(fieldOffset int, listOffset int, length int) {
|
||||
func (ob ObjectBuilder) SetList(fieldOffset int, listOffset int, length int) {
|
||||
ob.ensureField(fieldOffset + 8)
|
||||
|
||||
if listOffset == 0 || length == 0 {
|
||||
@@ -232,7 +259,7 @@ func (ob *ObjectBuilder) SetList(fieldOffset int, listOffset int, length int) {
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[ob.startPos+fieldOffset+4:], uint32(length))
|
||||
}
|
||||
|
||||
func (ob *ObjectBuilder) ensureField(endOffset int) {
|
||||
func (ob ObjectBuilder) ensureField(endOffset int) {
|
||||
needed := ob.startPos + endOffset
|
||||
if needed > ob.b.pos {
|
||||
ob.b.grow(needed - ob.b.pos)
|
||||
@@ -244,35 +271,38 @@ func (ob *ObjectBuilder) ensureField(endOffset int) {
|
||||
}
|
||||
}
|
||||
|
||||
// Finish finalizes the object and returns its offset.
|
||||
// Writes deferred text/bytes data after the object's fixed section
|
||||
// and patches relative offsets.
|
||||
func (ob *ObjectBuilder) Finish() int {
|
||||
// Ensure minimum size for fixed fields
|
||||
ob.ensureField(ob.dataSize)
|
||||
|
||||
// Write deferred text/bytes data and patch relative offsets
|
||||
for _, entry := range ob.offsets {
|
||||
if entry.data == nil {
|
||||
continue
|
||||
}
|
||||
// Write the data at current position (after fixed section)
|
||||
dataPos := ob.b.pos
|
||||
ob.b.grow(len(entry.data))
|
||||
copy(ob.b.buf[ob.b.pos:ob.b.pos+len(entry.data)], entry.data)
|
||||
ob.b.pos += len(entry.data)
|
||||
|
||||
// Patch the relative offset: relOffset = dataPos - fieldAbsPos
|
||||
fieldAbsPos := ob.startPos + entry.fieldOffset
|
||||
relOffset := int32(dataPos - fieldAbsPos)
|
||||
binary.LittleEndian.PutUint32(ob.b.buf[fieldAbsPos:], uint32(relOffset))
|
||||
}
|
||||
// ReserveFixed extends the builder's write cursor so the object's
|
||||
// entire fixed payload of dataSize bytes is materialized (zero-filled
|
||||
// up to startPos+dataSize). Use this BEFORE writing any variable-
|
||||
// length tail (list elements, deferred bytes) that should live AFTER
|
||||
// the fixed section.
|
||||
//
|
||||
// Without this call, list elements or deferred-data writes interleave
|
||||
// with the unreserved tail of the fixed section, producing incorrect
|
||||
// wire bytes when later Set* calls patch fields that overlap with
|
||||
// already-written variable data.
|
||||
//
|
||||
// Idempotent: a second call with the same dataSize is a no-op. A call
|
||||
// with a smaller dataSize is also a no-op (the cursor never moves
|
||||
// backwards).
|
||||
//
|
||||
// This is the exported counterpart to the internal ensureField helper.
|
||||
// It is used by zapv1.WriteList to keep the parent's payload reserved
|
||||
// before list elements are appended.
|
||||
func (ob ObjectBuilder) ReserveFixed(dataSize int) {
|
||||
ob.ensureField(dataSize)
|
||||
}
|
||||
|
||||
// Finish finalizes the object and returns its offset. Every field (fixed and
|
||||
// variable) is written eagerly by its Set* call — the fixed section is reserved
|
||||
// in StartObject and SetBytes/SetText append their tail immediately — so there
|
||||
// is nothing to replay here.
|
||||
func (ob ObjectBuilder) Finish() int {
|
||||
return ob.startPos
|
||||
}
|
||||
|
||||
// FinishAsRoot finalizes and sets as the message root.
|
||||
func (ob *ObjectBuilder) FinishAsRoot() int {
|
||||
func (ob ObjectBuilder) FinishAsRoot() int {
|
||||
offset := ob.Finish()
|
||||
ob.b.rootOffset = offset
|
||||
return offset
|
||||
@@ -300,17 +330,22 @@ func (b *Builder) WriteText(s string) int {
|
||||
type ListBuilder struct {
|
||||
b *Builder
|
||||
startPos int
|
||||
elemSize int
|
||||
count int
|
||||
}
|
||||
|
||||
// StartList starts building a list.
|
||||
func (b *Builder) StartList(elemSize int) *ListBuilder {
|
||||
// StartList starts building a list. Returns a VALUE (not a heap pointer): the
|
||||
// Add*/Finish methods take pointer receivers, and every caller binds the result
|
||||
// to an addressable local (`lb := b.StartList(...)`), so Go auto-addresses the
|
||||
// local and count mutations accumulate on it — no per-list heap allocation.
|
||||
// Mirrors the value-typed StartObject. elemSize is unused (the stride is
|
||||
// implicit in which Add* the caller invokes) and kept only as a documenting
|
||||
// param.
|
||||
func (b *Builder) StartList(elemSize int) ListBuilder {
|
||||
_ = elemSize
|
||||
b.align(Alignment)
|
||||
return &ListBuilder{
|
||||
return ListBuilder{
|
||||
b: b,
|
||||
startPos: b.pos,
|
||||
elemSize: elemSize,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,11 +381,60 @@ func (lb *ListBuilder) AddBytes(data []byte) {
|
||||
lb.count += len(data)
|
||||
}
|
||||
|
||||
// AddObjectPtr appends a 4-byte SIGNED relative pointer to an object at
|
||||
// absolute position targetPos (pass 0 for a null element). This is the
|
||||
// element kind for a list of out-of-line objects — the wire encoding of a
|
||||
// "repeated message" field, where each fixed-stride slot points to a tailed
|
||||
// object elsewhere in the buffer rather than inlining a fixed-size value.
|
||||
//
|
||||
// The relative offset is computed against THIS slot's position, so the
|
||||
// reader resolves it the same way [Object.Object] does: absOffset = slotPos
|
||||
// + int32(rel). Targets may precede the slot (negative rel) — the common
|
||||
// case, since a builder writes the objects first and the pointer array
|
||||
// after.
|
||||
func (lb *ListBuilder) AddObjectPtr(targetPos int) {
|
||||
lb.b.grow(4)
|
||||
if targetPos == 0 {
|
||||
binary.LittleEndian.PutUint32(lb.b.buf[lb.b.pos:], 0)
|
||||
} else {
|
||||
rel := int32(targetPos - lb.b.pos)
|
||||
binary.LittleEndian.PutUint32(lb.b.buf[lb.b.pos:], uint32(rel))
|
||||
}
|
||||
lb.b.pos += 4
|
||||
lb.count++
|
||||
}
|
||||
|
||||
// Finish returns the list offset and length.
|
||||
func (lb *ListBuilder) Finish() (offset int, length int) {
|
||||
return lb.startPos, lb.count
|
||||
}
|
||||
|
||||
// ---- Builder pool (write-side counterpart to the read bufpool) ----
|
||||
|
||||
// builderPool recycles Builders across messages. A pooled Builder keeps its
|
||||
// grown backing array, so steady-state message construction allocates zero
|
||||
// builder buffers. Byte-safety on reuse is unconditional: ensureField
|
||||
// zero-fills every extended span and align zero-fills padding, so a reused
|
||||
// (dirty) buffer emits bytes identical to a fresh one.
|
||||
var builderPool = sync.Pool{
|
||||
New: func() any { return NewBuilder(512) },
|
||||
}
|
||||
|
||||
// GetBuilder returns a pooled Builder, reset and ready to write a Version2
|
||||
// message. Callers MUST copy out the bytes returned by Finish (which aliases
|
||||
// the Builder's buffer) before calling PutBuilder.
|
||||
func GetBuilder() *Builder {
|
||||
b := builderPool.Get().(*Builder)
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
|
||||
// PutBuilder returns a Builder to the pool. The slice previously returned by
|
||||
// b.Finish() must no longer be referenced (it aliases b's buffer).
|
||||
func PutBuilder(b *Builder) {
|
||||
builderPool.Put(b)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func float32bits(f float32) uint32 {
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package zap (v1) is deprecated for new schema authoring.
|
||||
//
|
||||
// New schemas MUST be authored via codegen (~/work/lux/zap/v2/codegen)
|
||||
// which emits v1-equivalent fast paths from a declarative .zap source.
|
||||
//
|
||||
// Consumer dispatch for ad-hoc / dynamic schemas goes through the v2
|
||||
// generic API (github.com/luxfi/zap/v1).
|
||||
//
|
||||
// The v1 hand-rolled code in this package remains for in-flight
|
||||
// migration of legacy callers (luxfi/node/vms/platformvm/txs/zap_native,
|
||||
// parts of luxfi/consensus/protocol/quasar, etc.); no new v1 schemas
|
||||
// are accepted.
|
||||
//
|
||||
// The wire format is unchanged across v1 and v2 — this is a code-level
|
||||
// decomplection of the authoring surface only. Existing v1 buffers
|
||||
// remain parseable by both v1 and v2.
|
||||
//
|
||||
// See ~/work/lux/zap/v2/README.md for the canonical authoring path.
|
||||
package zap
|
||||
+10
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
@@ -61,6 +62,15 @@ func (i *Initiator) Run(conn io.ReadWriter) (*Session, error) {
|
||||
return nil, errors.New("zap-pq: initiator requires Local with private key")
|
||||
}
|
||||
|
||||
// Bound the whole handshake by HandshakeTimeoutSec so a peer that
|
||||
// connects and then stalls (or dribbles bytes) cannot pin this goroutine
|
||||
// and FD indefinitely. Applies only when conn is a net.Conn; callers
|
||||
// passing a bare io.ReadWriter remain responsible for their own timeout.
|
||||
if nc, ok := conn.(net.Conn); ok {
|
||||
_ = nc.SetReadDeadline(time.Now().Add(HandshakeTimeoutSec * time.Second))
|
||||
defer func() { _ = nc.SetReadDeadline(time.Time{}) }()
|
||||
}
|
||||
|
||||
suite := i.Suite
|
||||
if suite == 0 {
|
||||
suite = SuiteX25519MLKEM
|
||||
|
||||
+10
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
@@ -62,6 +63,15 @@ func (rs *Responder) Run(conn io.ReadWriter) (*Session, error) {
|
||||
return nil, errors.New("zap-pq: ReplayCache is required under StrictPQ/FIPS profile")
|
||||
}
|
||||
|
||||
// Bound the whole handshake by HandshakeTimeoutSec so an unauthenticated
|
||||
// peer that connects and then stalls cannot pin this goroutine and FD
|
||||
// indefinitely (pre-auth slowloris). Applies only when conn is a net.Conn;
|
||||
// bare io.ReadWriter callers own their own timeout.
|
||||
if nc, ok := conn.(net.Conn); ok {
|
||||
_ = nc.SetReadDeadline(time.Now().Add(HandshakeTimeoutSec * time.Second))
|
||||
defer func() { _ = nc.SetReadDeadline(time.Time{}) }()
|
||||
}
|
||||
|
||||
r := rs.Rand
|
||||
if r == nil {
|
||||
r = rand.Reader
|
||||
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// InProcessInterface is the cost-0 interface: it delivers to destinations that
|
||||
// live in THIS address space by calling their handler directly. No serialization,
|
||||
// no copy, no socket, no loopback TCP — a call is a call. It is the primitive
|
||||
// behind "if the sink is in my own binary, don't touch the network."
|
||||
//
|
||||
// A Payload's live Value is handed straight to the LocalHandler; the payload's
|
||||
// Encode (the wire form) is never invoked on this path, so an in-process delivery
|
||||
// pays nothing for encoding. When sender and receiver are folded into one binary
|
||||
// (the unified-cloud monolith), this is the interface that always wins the Cost
|
||||
// race, and the ZAP wire never enters the picture.
|
||||
type InProcessInterface struct {
|
||||
mu sync.RWMutex
|
||||
handlers map[Destination]LocalHandler
|
||||
}
|
||||
|
||||
// NewInProcessInterface returns an empty in-process interface. Register the
|
||||
// destinations this binary serves onto it, then Register it on a Router.
|
||||
func NewInProcessInterface() *InProcessInterface {
|
||||
return &InProcessInterface{handlers: make(map[Destination]LocalHandler)}
|
||||
}
|
||||
|
||||
// Register binds a destination (capability aspect) to a local handler. Calling it
|
||||
// again for the same dst replaces the handler (last writer wins) — a subsystem
|
||||
// re-mounting its sink is idempotent. A nil handler unregisters the destination
|
||||
// so CanReach falls through to the next (network) interface.
|
||||
func (p *InProcessInterface) Register(dst Destination, h LocalHandler) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if h == nil {
|
||||
delete(p.handlers, dst)
|
||||
return
|
||||
}
|
||||
p.handlers[dst] = h
|
||||
}
|
||||
|
||||
// Name identifies this interface.
|
||||
func (p *InProcessInterface) Name() string { return "inprocess" }
|
||||
|
||||
// Cost is 0 — a same-address-space handoff is the cheapest possible delivery, so
|
||||
// Router prefers it over every wire interface whenever the dst is local.
|
||||
func (p *InProcessInterface) Cost() int { return 0 }
|
||||
|
||||
// CanReach reports whether dst has a handler registered in THIS process.
|
||||
func (p *InProcessInterface) CanReach(dst Destination) bool {
|
||||
p.mu.RLock()
|
||||
_, ok := p.handlers[dst]
|
||||
p.mu.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// Deliver dispatches the live payload to the local handler. The sender identity
|
||||
// is left empty for a same-process call (the caller is us); a handler that needs
|
||||
// provenance reads it from the Payload.Value it defines. Returns the handler's
|
||||
// reply Payload (nil-Value ⇒ none).
|
||||
func (p *InProcessInterface) Deliver(ctx context.Context, dst Destination, pl Payload) (Payload, error) {
|
||||
p.mu.RLock()
|
||||
h, ok := p.handlers[dst]
|
||||
p.mu.RUnlock()
|
||||
if !ok {
|
||||
// CanReach guards this; a lost race (handler unregistered between the
|
||||
// two calls) is reported honestly rather than silently dropped.
|
||||
return Payload{}, fmt.Errorf("zap: in-process destination %q not registered", dst)
|
||||
}
|
||||
return h(ctx, "", pl)
|
||||
}
|
||||
|
||||
// NodeInterface adapts the existing *Node (TCP/QUIC + conn_pq PQ session + mDNS
|
||||
// discovery) to the Interface seam, WITHOUT changing Node. It is the network
|
||||
// fallback: whenever a destination is not served in-process, Router routes here
|
||||
// and the Node discovers/dials the peer and ships the ZAP wire Message.
|
||||
//
|
||||
// This is where the wire actually happens — and ONLY here. The Payload's live
|
||||
// Value never crosses a socket; Deliver calls Payload.Encode() to materialize the
|
||||
// zero-copy Message exactly once, at the moment a network hop is unavoidable.
|
||||
//
|
||||
// P1 scope: one-way delivery (Node.Send) — sufficient for telemetry and event
|
||||
// fan-out, which is what folds first. Request/reply over the wire (Node.Call) and
|
||||
// announce-driven multi-hop reachability are P2 refinements that slot in behind
|
||||
// this same Interface without moving the Router.Send call site.
|
||||
type NodeInterface struct {
|
||||
node *Node
|
||||
cost int
|
||||
// connectedOnly restricts CanReach to peers with a live connection. Default
|
||||
// (false) is optimistic: the interface is the network catch-all and lets
|
||||
// Node.Send discover+dial on demand, surfacing a real error if the peer is
|
||||
// genuinely unreachable rather than silently declining the route.
|
||||
connectedOnly bool
|
||||
}
|
||||
|
||||
// compile-time proof the interfaces satisfy the seam.
|
||||
var (
|
||||
_ Interface = (*InProcessInterface)(nil)
|
||||
_ Interface = (*NodeInterface)(nil)
|
||||
)
|
||||
|
||||
// DefaultNodeCost is the Cost of a LAN/cluster TCP hop — above in-process (0) and
|
||||
// same-host IPC (1), below multi-hop routed (100+).
|
||||
const DefaultNodeCost = 10
|
||||
|
||||
// NewNodeInterface wraps n as the network interface at DefaultNodeCost.
|
||||
func NewNodeInterface(n *Node) *NodeInterface {
|
||||
return &NodeInterface{node: n, cost: DefaultNodeCost}
|
||||
}
|
||||
|
||||
// WithCost overrides the interface cost (e.g. a metered/slow link ranks higher).
|
||||
func (i *NodeInterface) WithCost(c int) *NodeInterface { i.cost = c; return i }
|
||||
|
||||
// ConnectedOnly makes CanReach report true only for already-connected peers,
|
||||
// turning the interface from an optimistic catch-all into a strict "route only
|
||||
// where a session already exists" interface. Useful when a higher-cost interface
|
||||
// should own first-contact.
|
||||
func (i *NodeInterface) ConnectedOnly() *NodeInterface { i.connectedOnly = true; return i }
|
||||
|
||||
// Name identifies this interface.
|
||||
func (i *NodeInterface) Name() string { return "node-tcp" }
|
||||
|
||||
// Cost ranks this interface (default DefaultNodeCost).
|
||||
func (i *NodeInterface) Cost() int { return i.cost }
|
||||
|
||||
// CanReach reports whether the Node can deliver to dst. Optimistic by default (the
|
||||
// network catch-all); strict when ConnectedOnly is set.
|
||||
func (i *NodeInterface) CanReach(dst Destination) bool {
|
||||
if i.node == nil {
|
||||
return false
|
||||
}
|
||||
if !i.connectedOnly {
|
||||
return true // catch-all: let Deliver discover+dial and error honestly
|
||||
}
|
||||
for _, p := range i.node.Peers() {
|
||||
if p == string(dst) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrNotEncodable is returned when a network interface is asked to deliver a
|
||||
// payload that carries no wire form (Encode == nil) — an in-process-only value
|
||||
// that must not silently cross a socket.
|
||||
var ErrNotEncodable = errors.New("zap: payload has no wire encoding (in-process only)")
|
||||
|
||||
// Deliver ships the payload to dst (peer ID) over the Node's ZAP wire. It
|
||||
// materializes the wire Message lazily — the encode cost is paid here and nowhere
|
||||
// else — then hands it to Node.Send. A nil Encode is a hard error, never a silent
|
||||
// drop: an in-process-only payload reaching the wire is a routing bug.
|
||||
func (i *NodeInterface) Deliver(ctx context.Context, dst Destination, p Payload) (Payload, error) {
|
||||
if p.Encode == nil {
|
||||
return Payload{}, ErrNotEncodable
|
||||
}
|
||||
if err := i.node.Send(ctx, string(dst), p.Encode()); err != nil {
|
||||
return Payload{}, err
|
||||
}
|
||||
return Payload{}, nil // one-way delivery; no reply in P1
|
||||
}
|
||||
+221
-29
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -36,13 +37,11 @@ type Node struct {
|
||||
discovery *mdns.Discovery
|
||||
|
||||
// Network
|
||||
listener net.Listener
|
||||
transports map[string]TransportConn // peerID -> transport conn (QUIC path)
|
||||
transClose func() error // closer for the QUIC listener
|
||||
reqIDQuic uint32 // QUIC-path request-ID counter
|
||||
reqIDQuicMu sync.Mutex
|
||||
conns map[string]*Conn
|
||||
connsMu sync.RWMutex
|
||||
listener net.Listener
|
||||
transports map[string]TransportConn // peerID -> transport conn (QUIC path)
|
||||
transClose func() error // closer for the QUIC listener
|
||||
conns map[string]*Conn
|
||||
connsMu sync.RWMutex
|
||||
|
||||
// Handlers
|
||||
handlers map[uint16]Handler
|
||||
@@ -145,9 +144,18 @@ func (n *Node) Start() error {
|
||||
|
||||
n.discovery.OnPeer(n.handlePeerEvent)
|
||||
|
||||
// mDNS advertise/browse is BEST-EFFORT. It lets peers find each
|
||||
// other zero-config on a LAN, but it is not required for service:
|
||||
// the node is already accepting on its listener, and in-cluster
|
||||
// peers reach it by address (Service DNS at the fixed port). On
|
||||
// networks without multicast (most Kubernetes pod overlays,
|
||||
// restricted hosts) discovery fails to start — the node MUST keep
|
||||
// serving regardless. Log and continue rather than tearing down
|
||||
// the listener.
|
||||
if err := n.discovery.Start(); err != nil {
|
||||
n.listener.Close()
|
||||
return fmt.Errorf("failed to start discovery: %w", err)
|
||||
n.logger.Warn("ZAP mDNS discovery unavailable; serving without it (peers reach this node by address)",
|
||||
"nodeID", n.nodeID, "service", n.serviceType, "err", err)
|
||||
n.discovery = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,9 +262,11 @@ func (n *Node) Call(ctx context.Context, peerID string, msg *Message) (*Message,
|
||||
conn.pendMu.Unlock()
|
||||
}()
|
||||
|
||||
// Send wrapped request (one canonical encoder via WrapCorrelated).
|
||||
// Send wrapped request via writeCorrelated — scatter-gather
|
||||
// (header + body) so the body slice is never copied on the hot
|
||||
// Call path.
|
||||
conn.mu.Lock()
|
||||
err = writeMessage(conn.conn, WrapCorrelated(reqID, ReqFlagReq, msg.Bytes()))
|
||||
err = writeCorrelated(conn.conn, reqID, ReqFlagReq, msg.Bytes())
|
||||
conn.mu.Unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -409,6 +419,19 @@ func (n *Node) handleConn(netConn net.Conn) {
|
||||
// Returns when the underlying conn errors (non-timeout) or ctx is
|
||||
// cancelled. The caller is responsible for the per-connection
|
||||
// cleanup (conns-map delete, log).
|
||||
//
|
||||
// Buffer lifecycle (post pool-aware read path):
|
||||
// - Each iteration pulls one frame into a pooled *bufRef. The frame
|
||||
// payload is sliced (no copy) into the Message returned by Parse.
|
||||
// - For uncorrelated frames and Call requests (ReqFlagReq), the
|
||||
// Message is consumed inside the iteration — handler runs, response
|
||||
// is written, and the bufRef is released before the next read.
|
||||
// - For Call responses (ReqFlagResp), the Message is handed off to a
|
||||
// channel the Call goroutine awaits. We Retain the ref before the
|
||||
// send and the receiver Releases when it consumes the response.
|
||||
//
|
||||
// The result is two allocations saved per dispatch: the body slab and
|
||||
// the bufRef header (both pooled).
|
||||
func (n *Node) dispatchLoop(netConn net.Conn, conn *Conn, peerID string) {
|
||||
for {
|
||||
select {
|
||||
@@ -418,7 +441,7 @@ func (n *Node) dispatchLoop(netConn net.Conn, conn *Conn, peerID string) {
|
||||
}
|
||||
|
||||
netConn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
data, err := readMessageRaw(netConn)
|
||||
ref, err := readMessageRawPooled(netConn)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return
|
||||
@@ -429,23 +452,39 @@ func (n *Node) dispatchLoop(netConn net.Conn, conn *Conn, peerID string) {
|
||||
n.logger.Debug("Read error", "peerID", peerID, "error", err)
|
||||
return
|
||||
}
|
||||
data := ref.Bytes()
|
||||
|
||||
if reqID, flag, body, isCall := UnwrapCorrelated(data); isCall {
|
||||
switch flag {
|
||||
case ReqFlagResp:
|
||||
if msg, err := Parse(body); err == nil {
|
||||
// Hand the slab off to the awaiting Call goroutine.
|
||||
// Retain holds the refcount across the channel; the
|
||||
// receiver Releases on consumption. If nobody is
|
||||
// listening we Release here to avoid leaking.
|
||||
attachToMessage(msg, ref)
|
||||
ref.retain() // matched by msg.Release() at Call site
|
||||
conn.pendMu.Lock()
|
||||
if ch, ok := conn.pending[reqID]; ok {
|
||||
ch, ok := conn.pending[reqID]
|
||||
if ok {
|
||||
select {
|
||||
case ch <- msg:
|
||||
default:
|
||||
ok = false // delivery failed
|
||||
}
|
||||
}
|
||||
conn.pendMu.Unlock()
|
||||
if !ok {
|
||||
msg.Release() // drop the retain we just took
|
||||
}
|
||||
}
|
||||
// Drop our local hold; the retain above keeps the slab
|
||||
// alive for the channel consumer.
|
||||
ref.release()
|
||||
case ReqFlagReq:
|
||||
msg, err := Parse(body)
|
||||
if err != nil {
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
msgType := msg.Flags() >> 8
|
||||
@@ -453,22 +492,28 @@ func (n *Node) dispatchLoop(netConn net.Conn, conn *Conn, peerID string) {
|
||||
handler, ok := n.handlers[msgType]
|
||||
n.handlersMu.RUnlock()
|
||||
if !ok {
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
resp, herr := handler(n.ctx, peerID, msg)
|
||||
resp, herr := n.safeHandle(handler, peerID, msgType, msg)
|
||||
if herr != nil {
|
||||
n.logger.Error("Handler error", "peerID", peerID, "msgType", msgType, "error", herr)
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
if resp != nil {
|
||||
conn.mu.Lock()
|
||||
writeErr := writeMessage(netConn, WrapCorrelated(reqID, ReqFlagResp, resp.Bytes()))
|
||||
writeErr := writeCorrelated(netConn, reqID, ReqFlagResp, resp.Bytes())
|
||||
conn.mu.Unlock()
|
||||
if writeErr != nil {
|
||||
n.logger.Debug("Write error", "peerID", peerID, "error", writeErr)
|
||||
ref.release()
|
||||
return
|
||||
}
|
||||
}
|
||||
ref.release()
|
||||
default:
|
||||
ref.release()
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -476,6 +521,7 @@ func (n *Node) dispatchLoop(netConn net.Conn, conn *Conn, peerID string) {
|
||||
// Uncorrelated message — direct handler dispatch.
|
||||
msg, err := Parse(data)
|
||||
if err != nil {
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
msgType := msg.Flags() >> 8
|
||||
@@ -483,11 +529,13 @@ func (n *Node) dispatchLoop(netConn net.Conn, conn *Conn, peerID string) {
|
||||
handler, ok := n.handlers[msgType]
|
||||
n.handlersMu.RUnlock()
|
||||
if !ok {
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
resp, herr := handler(n.ctx, peerID, msg)
|
||||
resp, herr := n.safeHandle(handler, peerID, msgType, msg)
|
||||
if herr != nil {
|
||||
n.logger.Error("Handler error", "peerID", peerID, "msgType", msgType, "error", herr)
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
if resp != nil {
|
||||
@@ -496,9 +544,11 @@ func (n *Node) dispatchLoop(netConn net.Conn, conn *Conn, peerID string) {
|
||||
conn.mu.Unlock()
|
||||
if writeErr != nil {
|
||||
n.logger.Debug("Write error", "peerID", peerID, "error", writeErr)
|
||||
ref.release()
|
||||
return
|
||||
}
|
||||
}
|
||||
ref.release()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,15 +673,19 @@ func (n *Node) getOrConnect(peerID string) (*Conn, error) {
|
||||
default:
|
||||
}
|
||||
|
||||
// Set read deadline so we can check for context cancellation
|
||||
// Set read deadline so we can check for context cancellation.
|
||||
// Buffer lifecycle mirrors dispatchLoop: response frames hand
|
||||
// off the slab to the awaiting Call goroutine via Retain;
|
||||
// everything else releases at end of iteration.
|
||||
netConn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
data, err := readMessageRaw(netConn)
|
||||
ref, err := readMessageRawPooled(netConn)
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
data := ref.Bytes()
|
||||
|
||||
// Check if this is a Call response (has 8-byte header with response flag)
|
||||
if len(data) >= 8 {
|
||||
@@ -641,15 +695,23 @@ func (n *Node) getOrConnect(peerID string) (*Conn, error) {
|
||||
reqID := binary.LittleEndian.Uint32(data[0:4])
|
||||
msg, err := Parse(data[8:])
|
||||
if err == nil {
|
||||
attachToMessage(msg, ref)
|
||||
ref.retain() // matched by msg.Release() at Call site
|
||||
conn.pendMu.Lock()
|
||||
if ch, ok := conn.pending[reqID]; ok {
|
||||
ch, ok := conn.pending[reqID]
|
||||
if ok {
|
||||
select {
|
||||
case ch <- msg:
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
conn.pendMu.Unlock()
|
||||
if !ok {
|
||||
msg.Release()
|
||||
}
|
||||
}
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -657,6 +719,7 @@ func (n *Node) getOrConnect(peerID string) (*Conn, error) {
|
||||
// Regular message - use standard handler
|
||||
msg, err := Parse(data)
|
||||
if err != nil {
|
||||
ref.release()
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -666,22 +729,40 @@ func (n *Node) getOrConnect(peerID string) (*Conn, error) {
|
||||
n.handlersMu.RUnlock()
|
||||
|
||||
if ok {
|
||||
handler(n.ctx, peerID, msg)
|
||||
// Guarded: a handler panic here must not kill this receive
|
||||
// goroutine / the node. The error return is intentionally
|
||||
// dropped — this is the fire-and-forget receive path for an
|
||||
// outbound-dialed peer; safeHandle has already logged.
|
||||
_, _ = n.safeHandle(handler, peerID, msgType, msg)
|
||||
}
|
||||
ref.release()
|
||||
}
|
||||
}()
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// ConnectDirect connects directly to a peer at the given address (bypasses mDNS).
|
||||
// ConnectDirect connects directly to a peer at the given address
|
||||
// (bypasses mDNS). Use ConnectDirectID when you need the handshake-
|
||||
// learned peer NodeID back — e.g. to address subsequent Calls to a
|
||||
// static peer whose advertised NodeID is only a placeholder.
|
||||
func (n *Node) ConnectDirect(addr string) error {
|
||||
_, err := n.ConnectDirectID(addr)
|
||||
return err
|
||||
}
|
||||
|
||||
// ConnectDirectID dials addr, performs the NodeID handshake, registers
|
||||
// the connection, and returns the peer's learned NodeID. Idempotent: if
|
||||
// a connection to that peer already exists it returns the existing
|
||||
// peer's NodeID and drops the duplicate dial. (TCP transport; the QUIC
|
||||
// path registers the peer internally and returns an empty id.)
|
||||
func (n *Node) ConnectDirectID(addr string) (string, error) {
|
||||
if n.transport == TransportQUIC {
|
||||
return n.quicConnectDirect(n.ctx, addr)
|
||||
return "", n.quicConnectDirect(n.ctx, addr)
|
||||
}
|
||||
netConn, err := net.DialTimeout("tcp", addr, 5*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to %s: %w", addr, err)
|
||||
return "", fmt.Errorf("failed to connect to %s: %w", addr, err)
|
||||
}
|
||||
if n.tlsCfg != nil {
|
||||
netConn = tls.Client(netConn, n.tlsCfg)
|
||||
@@ -690,19 +771,19 @@ func (n *Node) ConnectDirect(addr string) error {
|
||||
// Send handshake.
|
||||
if err := writeMessage(netConn, EncodeNodeIDHandshake(n.nodeID)); err != nil {
|
||||
netConn.Close()
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Read handshake response.
|
||||
data, err := readMessageRaw(netConn)
|
||||
if err != nil {
|
||||
netConn.Close()
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
peerID, ok := DecodeNodeIDHandshake(data)
|
||||
if !ok {
|
||||
netConn.Close()
|
||||
return fmt.Errorf("invalid peer handshake")
|
||||
return "", fmt.Errorf("invalid peer handshake")
|
||||
}
|
||||
|
||||
conn := &Conn{
|
||||
@@ -717,7 +798,7 @@ func (n *Node) ConnectDirect(addr string) error {
|
||||
if _, ok := n.conns[peerID]; ok {
|
||||
n.connsMu.Unlock()
|
||||
netConn.Close()
|
||||
return nil // Already connected, that's fine
|
||||
return peerID, nil // Already connected, that's fine
|
||||
}
|
||||
n.conns[peerID] = conn
|
||||
n.connsMu.Unlock()
|
||||
@@ -741,7 +822,7 @@ func (n *Node) ConnectDirect(addr string) error {
|
||||
n.dispatchLoop(netConn, conn, peerID)
|
||||
}()
|
||||
|
||||
return nil
|
||||
return peerID, nil
|
||||
}
|
||||
|
||||
// Send sends a message over the connection.
|
||||
@@ -758,11 +839,23 @@ func (c *Conn) Recv() (*Message, error) {
|
||||
return readMessage(c.conn)
|
||||
}
|
||||
|
||||
// Wire format: [4 bytes length][message bytes]
|
||||
// Wire format: [4 bytes length][message bytes].
|
||||
//
|
||||
// writeMessage emits the frame using a single (*net.Buffers).WriteTo when
|
||||
// w is a *net.TCPConn — that's one writev(2) syscall instead of two Write
|
||||
// calls, and the body slice is referenced rather than copied. For other
|
||||
// writers (TLS, pipes, tests) the path falls back to two sequential writes,
|
||||
// which is correctness-equivalent.
|
||||
func writeMessage(w io.Writer, data []byte) error {
|
||||
var lenBuf [4]byte
|
||||
binary.LittleEndian.PutUint32(lenBuf[:], uint32(len(data)))
|
||||
|
||||
if tc, ok := w.(*net.TCPConn); ok {
|
||||
bufs := net.Buffers{lenBuf[:], data}
|
||||
_, err := bufs.WriteTo(tc)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(lenBuf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -770,6 +863,36 @@ func writeMessage(w io.Writer, data []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// writeCorrelated emits a Call request/response frame without copying
|
||||
// the body buffer. Equivalent to writeMessage(w, WrapCorrelated(...)) but
|
||||
// the 8-byte correlation header is sent as a separate IO vector — for
|
||||
// TCPConn this is a single writev(2), for other writers it's three
|
||||
// sequential writes (still no body copy). This replaces the per-Call
|
||||
// allocation + copy WrapCorrelated() performs.
|
||||
//
|
||||
// reqID and flag are LE uint32 (matches WrapCorrelated wire layout).
|
||||
func writeCorrelated(w io.Writer, reqID uint32, flag uint32, body []byte) error {
|
||||
var hdr [12]byte
|
||||
binary.LittleEndian.PutUint32(hdr[0:4], uint32(correlatedHeaderSize+len(body)))
|
||||
binary.LittleEndian.PutUint32(hdr[4:8], reqID)
|
||||
binary.LittleEndian.PutUint32(hdr[8:12], flag)
|
||||
|
||||
if tc, ok := w.(*net.TCPConn); ok {
|
||||
bufs := net.Buffers{hdr[:], body}
|
||||
_, err := bufs.WriteTo(tc)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(hdr[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := w.Write(body)
|
||||
return err
|
||||
}
|
||||
|
||||
func readMessage(r io.Reader) (*Message, error) {
|
||||
data, err := readMessageRaw(r)
|
||||
if err != nil {
|
||||
@@ -796,3 +919,72 @@ func readMessageRaw(r io.Reader) ([]byte, error) {
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// readMessageRawPooled is the pool-aware variant of readMessageRaw.
|
||||
//
|
||||
// It reads one length-prefixed ZAP frame from r and returns a *bufRef
|
||||
// whose Bytes() slice is the payload. The slab is sourced from a
|
||||
// quantized sync.Pool — see bufpool.go for the size classes. Frames
|
||||
// larger than the largest pool class fall back to a one-off heap
|
||||
// allocation, transparent to the caller.
|
||||
//
|
||||
// Caller MUST eventually release() the *bufRef (directly, or by
|
||||
// transferring ownership to a *Message via attachToMessage and then
|
||||
// calling Message.Release()). Failure to release leaks the slab into
|
||||
// sync.Pool's GC reclaim cycle — correct but defeats the pool.
|
||||
//
|
||||
// Wire format is byte-identical to readMessageRaw. The only difference
|
||||
// is the buffer's lifecycle.
|
||||
func readMessageRawPooled(r io.Reader) (*bufRef, error) {
|
||||
var lenBuf [4]byte
|
||||
if _, err := io.ReadFull(r, lenBuf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint32(lenBuf[:])
|
||||
if length > 10*1024*1024 { // 10MB max
|
||||
return nil, errors.New("message too large")
|
||||
}
|
||||
|
||||
ref := getBuf(int(length))
|
||||
if _, err := io.ReadFull(r, ref.rawBuf()[:length]); err != nil {
|
||||
ref.release()
|
||||
return nil, err
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
// attachToMessage stamps the *bufRef onto a *Message so that
|
||||
// Message.Release() returns the slab to the pool. Used by dispatch
|
||||
// loops that build a Message from a pooled buffer they're handing
|
||||
// off to a response channel.
|
||||
func attachToMessage(m *Message, ref *bufRef) {
|
||||
if m != nil {
|
||||
m.refs = ref
|
||||
}
|
||||
}
|
||||
|
||||
// safeHandle invokes a registered handler with a recover() guard. A handler
|
||||
// is application code (e.g. forward.Serve's HTTP bridge) reachable directly
|
||||
// from attacker-controlled bytes on the wire; a panic inside it — out-of-
|
||||
// range index on a malformed envelope, a nil deref, a third-party library
|
||||
// fault — must NEVER unwind into the per-connection dispatch goroutine and
|
||||
// crash the whole node/process. On panic we log with the peer and message
|
||||
// type and return an error so the dispatch loop drops that one connection;
|
||||
// every other connection and the node itself survive.
|
||||
//
|
||||
// This is the single recover boundary for ALL handler dispatch in this
|
||||
// node (correlated Call requests and uncorrelated messages alike). It is
|
||||
// the one-and-only place a handler panic is contained.
|
||||
func (n *Node) safeHandle(handler Handler, peerID string, msgType uint16, msg *Message) (resp *Message, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
n.logger.Error("ZAP handler panic recovered",
|
||||
"peerID", peerID, "msgType", msgType, "panic", r,
|
||||
"stack", string(debug.Stack()))
|
||||
resp = nil
|
||||
err = fmt.Errorf("handler panic (msgType=%d): %v", msgType, r)
|
||||
}
|
||||
}()
|
||||
return handler(n.ctx, peerID, msg)
|
||||
}
|
||||
|
||||
+16
@@ -101,3 +101,19 @@ func UnwrapCorrelated(data []byte) (reqID uint32, flag uint32, body []byte, ok b
|
||||
}
|
||||
return reqID, flag, data[correlatedHeaderSize:], true
|
||||
}
|
||||
|
||||
// makeCorrelatedFrame builds a single-slice correlation frame
|
||||
// (header + body) for transports that take []byte (notably the QUIC
|
||||
// TransportConn.Send shape). TCP callers should prefer writeCorrelated
|
||||
// which avoids the body copy entirely via scatter-gather.
|
||||
//
|
||||
// One allocation per call. A pooled variant requires the transport
|
||||
// interface to expose a "buffer consumed" callback so the slab can
|
||||
// be returned safely; that's a cross-module change and is deferred.
|
||||
func makeCorrelatedFrame(reqID uint32, flag uint32, body []byte) []byte {
|
||||
buf := make([]byte, correlatedHeaderSize+len(body))
|
||||
binary.LittleEndian.PutUint32(buf[0:4], reqID)
|
||||
binary.LittleEndian.PutUint32(buf[4:8], flag)
|
||||
copy(buf[correlatedHeaderSize:], body)
|
||||
return buf
|
||||
}
|
||||
|
||||
+143
-113
@@ -5,12 +5,10 @@ package zap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/mdns"
|
||||
@@ -91,8 +89,22 @@ func (n *Node) onAcceptedTransportConn(peerID string, tc TransportConn) {
|
||||
go n.serveTransportConn(peerID, tc)
|
||||
}
|
||||
|
||||
// serveTransportConn is the QUIC equivalent of handleConn — it
|
||||
// drives the read loop and dispatches incoming frames to handlers.
|
||||
// serveTransportConn drives the read loops on a QUIC peer connection.
|
||||
//
|
||||
// Two parallel read loops run on each connection:
|
||||
//
|
||||
// 1. Control-stream loop (tc.Recv): handles one-way Sends only.
|
||||
// 2. Per-Call stream accept loop (AcceptCallStream): handles every
|
||||
// Call. Each accepted stream carries exactly one request + one
|
||||
// response, so the dispatch goroutine reads, handles, writes,
|
||||
// and closes the stream in one shot.
|
||||
//
|
||||
// The two loops are independent: each runs in its own goroutine, and
|
||||
// both terminate when the connection closes or n.ctx is cancelled.
|
||||
//
|
||||
// The QUIC transport always implements TransportStreamer; if a
|
||||
// connection somehow lacks it we close it — the per-stream Call path
|
||||
// is the only Call path.
|
||||
func (n *Node) serveTransportConn(peerID string, tc TransportConn) {
|
||||
defer n.wg.Done()
|
||||
defer func() {
|
||||
@@ -105,6 +117,14 @@ func (n *Node) serveTransportConn(peerID string, tc TransportConn) {
|
||||
n.logger.Info("Peer disconnected (QUIC)", "peerID", peerID)
|
||||
}()
|
||||
|
||||
streamer, ok := tc.(TransportStreamer)
|
||||
if !ok {
|
||||
n.logger.Error("QUIC TransportConn missing TransportStreamer", "peerID", peerID)
|
||||
return
|
||||
}
|
||||
n.wg.Add(1)
|
||||
go n.acceptCallStreams(peerID, streamer)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-n.ctx.Done():
|
||||
@@ -123,41 +143,81 @@ func (n *Node) serveTransportConn(peerID string, tc TransportConn) {
|
||||
n.logger.Debug("QUIC Recv error", "peerID", peerID, "error", err)
|
||||
return
|
||||
}
|
||||
n.dispatchFrame(peerID, data, tc)
|
||||
// Control stream carries one-way Sends only. Calls ride
|
||||
// per-Call streams.
|
||||
msg, err := Parse(data)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
n.invokeHandlerOneWay(peerID, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// dispatchFrame routes a single ZAP frame to either the pending-Call
|
||||
// channel (if it has a response correlation header) or to the
|
||||
// registered handler. Mirrors the TCP path in node.go's handleConn.
|
||||
func (n *Node) dispatchFrame(peerID string, data []byte, tc TransportConn) {
|
||||
if len(data) >= 8 {
|
||||
reqFlag := binary.LittleEndian.Uint32(data[4:8])
|
||||
if reqFlag == ReqFlagResp {
|
||||
reqID := binary.LittleEndian.Uint32(data[0:4])
|
||||
msg, err := Parse(data[8:])
|
||||
if err == nil {
|
||||
n.routeQUICResponse(peerID, reqID, msg)
|
||||
}
|
||||
// acceptCallStreams runs in its own goroutine for each QUIC peer and
|
||||
// processes inbound per-Call streams. Each stream gets its own handler
|
||||
// goroutine so multiple in-flight Calls from the same peer execute
|
||||
// concurrently — the headline parallelism win.
|
||||
//
|
||||
// Termination: AcceptCallStream returns a non-nil err when the
|
||||
// connection is torn down. The loop exits without logging — the
|
||||
// peer-disconnected line is already emitted by serveTransportConn's
|
||||
// deferred close.
|
||||
func (n *Node) acceptCallStreams(peerID string, s TransportStreamer) {
|
||||
defer n.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-n.ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
if reqFlag == ReqFlagReq {
|
||||
reqID := binary.LittleEndian.Uint32(data[0:4])
|
||||
msg, err := Parse(data[8:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n.handleQUICRequest(peerID, tc, reqID, msg)
|
||||
stream, err := s.AcceptCallStream(n.ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n.wg.Add(1)
|
||||
go n.handleCallStream(peerID, stream)
|
||||
}
|
||||
}
|
||||
|
||||
// Regular one-way message.
|
||||
msg, err := Parse(data)
|
||||
// handleCallStream serves exactly one inbound per-Call stream:
|
||||
// read one frame as the request, run the registered handler, and
|
||||
// write the response back on the same stream. The stream is closed
|
||||
// when this function returns.
|
||||
func (n *Node) handleCallStream(peerID string, stream TransportStream) {
|
||||
defer n.wg.Done()
|
||||
defer stream.Close()
|
||||
|
||||
reqBytes, err := stream.ReadFrame()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n.invokeHandlerOneWay(peerID, msg)
|
||||
msg, err := Parse(reqBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
msgType := msg.Flags() >> 8
|
||||
n.handlersMu.RLock()
|
||||
handler, ok := n.handlers[msgType]
|
||||
n.handlersMu.RUnlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Guarded: a handler panic on the QUIC per-Call stream must not crash
|
||||
// the node any more than on the TCP path. safeHandle recovers, logs,
|
||||
// and converts the panic to an error; the stream is then closed.
|
||||
resp, herr := n.safeHandle(handler, peerID, msgType, msg)
|
||||
if herr != nil {
|
||||
n.logger.Error("Handler error (stream)", "peerID", peerID, "msgType", msgType, "error", herr)
|
||||
return
|
||||
}
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
if err := stream.WriteFrame(resp.Bytes()); err != nil {
|
||||
n.logger.Debug("QUIC stream write error", "peerID", peerID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// invokeHandlerOneWay runs the registered handler and discards the
|
||||
@@ -170,96 +230,77 @@ func (n *Node) invokeHandlerOneWay(peerID string, msg *Message) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
_, _ = handler(n.ctx, peerID, msg)
|
||||
}
|
||||
|
||||
// handleQUICRequest dispatches a Call request and writes back the
|
||||
// correlation-tagged response on the same transport conn.
|
||||
func (n *Node) handleQUICRequest(peerID string, tc TransportConn, reqID uint32, msg *Message) {
|
||||
msgType := msg.Flags() >> 8
|
||||
n.handlersMu.RLock()
|
||||
handler, ok := n.handlers[msgType]
|
||||
n.handlersMu.RUnlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
resp, err := handler(n.ctx, peerID, msg)
|
||||
if err != nil {
|
||||
n.logger.Error("Handler error", "peerID", peerID, "msgType", msgType, "error", err)
|
||||
return
|
||||
}
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
respBytes := resp.Bytes()
|
||||
wrapped := make([]byte, len(respBytes)+8)
|
||||
binary.LittleEndian.PutUint32(wrapped[0:4], reqID)
|
||||
binary.LittleEndian.PutUint32(wrapped[4:8], ReqFlagResp)
|
||||
copy(wrapped[8:], respBytes)
|
||||
if err := tc.Send(wrapped); err != nil {
|
||||
n.logger.Debug("QUIC Send error", "peerID", peerID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// quicPendingMu / quicPending hold the per-peer pending Call response
|
||||
// channels for the QUIC transport. We keep them on the Node (not on
|
||||
// each TransportConn) because TransportConn is an interface and we
|
||||
// don't want to require implementors to carry the pending map.
|
||||
var (
|
||||
quicPendingMu sync.Mutex
|
||||
quicPending = map[string]map[uint32]chan *Message{}
|
||||
)
|
||||
|
||||
func (n *Node) routeQUICResponse(peerID string, reqID uint32, msg *Message) {
|
||||
quicPendingMu.Lock()
|
||||
defer quicPendingMu.Unlock()
|
||||
if peerMap, ok := quicPending[n.nodeID+":"+peerID]; ok {
|
||||
if ch, ok := peerMap[reqID]; ok {
|
||||
select {
|
||||
case ch <- msg:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
// Guarded: same recover boundary as every other dispatch path so a
|
||||
// panicking one-way handler cannot kill the node.
|
||||
_, _ = n.safeHandle(handler, peerID, msgType, msg)
|
||||
}
|
||||
|
||||
// quicCall is the QUIC path for Node.Call.
|
||||
//
|
||||
// Every Call gets its own fresh bidirectional QUIC stream. The
|
||||
// request goes out on the new stream, the response comes back on the
|
||||
// same stream, and the stream is torn down. Concurrent Calls run in
|
||||
// parallel up to the peer-advertised stream limit (1024 for ZAP's
|
||||
// QUIC config) — no shared write mutex, no correlation header.
|
||||
//
|
||||
// QUIC's TransportConn always implements TransportStreamer; if it
|
||||
// somehow does not, that is a programmer error and we surface it.
|
||||
func (n *Node) quicCall(ctx context.Context, peerID string, msg *Message) (*Message, error) {
|
||||
tc, err := n.getOrConnectQUIC(ctx, peerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqID := nextReqID(n)
|
||||
|
||||
respCh := make(chan *Message, 1)
|
||||
key := n.nodeID + ":" + peerID
|
||||
quicPendingMu.Lock()
|
||||
if quicPending[key] == nil {
|
||||
quicPending[key] = make(map[uint32]chan *Message)
|
||||
streamer, ok := tc.(TransportStreamer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("zap: QUIC TransportConn for %s missing TransportStreamer", peerID)
|
||||
}
|
||||
quicPending[key][reqID] = respCh
|
||||
quicPendingMu.Unlock()
|
||||
return n.quicCallStream(ctx, streamer, msg)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
quicPendingMu.Lock()
|
||||
delete(quicPending[key], reqID)
|
||||
quicPendingMu.Unlock()
|
||||
}()
|
||||
// quicCallStream runs one Call on a fresh per-Call QUIC stream.
|
||||
//
|
||||
// The stream lifecycle is:
|
||||
// 1. OpenCallStream — server peer's AcceptCallStream wakes.
|
||||
// 2. WriteFrame(req) — single length-prefixed ZAP frame.
|
||||
// 3. ReadFrame() — single length-prefixed ZAP frame (response).
|
||||
// 4. Close — stream ID returns to the QUIC pool.
|
||||
//
|
||||
// No correlation header is needed on the wire: each stream carries
|
||||
// exactly one request + one response. The response payload is the raw
|
||||
// ZAP frame; we Parse it directly.
|
||||
//
|
||||
// Each stream gets cancelled if ctx fires before the response
|
||||
// arrives; the QUIC layer will surface ErrStreamCanceled into
|
||||
// ReadFrame so the goroutine exits cleanly.
|
||||
func (n *Node) quicCallStream(ctx context.Context, s TransportStreamer, msg *Message) (*Message, error) {
|
||||
stream, err := s.OpenCallStream(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
orig := msg.Bytes()
|
||||
wrapped := make([]byte, len(orig)+8)
|
||||
binary.LittleEndian.PutUint32(wrapped[0:4], reqID)
|
||||
binary.LittleEndian.PutUint32(wrapped[4:8], ReqFlagReq)
|
||||
copy(wrapped[8:], orig)
|
||||
|
||||
if err := tc.Send(wrapped); err != nil {
|
||||
if err := stream.WriteFrame(msg.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read response on this stream. The QUIC layer respects the
|
||||
// stream's own deadline; we set one from ctx so a cancelled
|
||||
// context tears the read down.
|
||||
type result struct {
|
||||
resp []byte
|
||||
err error
|
||||
}
|
||||
out := make(chan result, 1)
|
||||
go func() {
|
||||
resp, err := stream.ReadFrame()
|
||||
out <- result{resp, err}
|
||||
}()
|
||||
select {
|
||||
case resp := <-respCh:
|
||||
return resp, nil
|
||||
case r := <-out:
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
return Parse(r.resp)
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
@@ -343,14 +384,3 @@ func (n *Node) getOrConnectQUIC(ctx context.Context, peerID string) (TransportCo
|
||||
go n.serveTransportConn(peerID, newTC)
|
||||
return newTC, nil
|
||||
}
|
||||
|
||||
// nextReqID returns the next request ID for a QUIC peer. We piggy-
|
||||
// back on the existing Conn.reqID counter pattern but use a per-node
|
||||
// global because the TransportConn interface is opaque about its own
|
||||
// state. Atomicity is provided by the Node struct's reqIDMu.
|
||||
func nextReqID(n *Node) uint32 {
|
||||
n.reqIDQuicMu.Lock()
|
||||
defer n.reqIDQuicMu.Unlock()
|
||||
n.reqIDQuic++
|
||||
return n.reqIDQuic
|
||||
}
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package zap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Router is ZAP's locality-adaptive router: ONE Send API over many interfaces.
|
||||
//
|
||||
// The complected thing in classic RPC is `Send(host:port, msg)` — WHO you are
|
||||
// talking to, HOW the bytes travel, and WHERE the peer lives are braided into a
|
||||
// single address. Router pulls them apart (Reticulum's model, made
|
||||
// post-quantum and zero-copy):
|
||||
//
|
||||
// - Destination — WHO/WHAT (a capability aspect, later a PQ-identity hash).
|
||||
// - Interface — HOW one hop travels (in-process memory, TCP, UDP, radio…).
|
||||
// - Payload — the message, dual-form so each medium pays only for itself.
|
||||
// - Router — routes: the cheapest Interface that can currently reach dst.
|
||||
//
|
||||
// "If in-process, skip TCP; else use the network" is therefore NOT a branch any
|
||||
// caller writes — it is the Cost table (InProcess 0 < UDS 1 < TCP 10 < routed
|
||||
// 100+). A caller only ever says:
|
||||
//
|
||||
// router.Send(ctx, dst, payload)
|
||||
//
|
||||
// and never names a port or a transport again. Router is distinct from the
|
||||
// wire-level Transport enum (TransportTCP/TransportQUIC), which selects HOW one
|
||||
// Node's socket speaks; a NodeInterface adapts such a Node into this router.
|
||||
type Router struct {
|
||||
mu sync.RWMutex
|
||||
ifaces []Interface // kept sorted ascending by Cost()
|
||||
}
|
||||
|
||||
// Destination names WHO/WHAT a message is for, decoupled from HOW it is reached.
|
||||
// In P1 it is a dotted capability aspect ("hanzo.o11y.traces"); the announce-
|
||||
// routed PQ-identity destination hash (multi-hop) refines this in the transport
|
||||
// HIP WITHOUT changing this call site — that is the whole point of the seam.
|
||||
type Destination string
|
||||
|
||||
// Payload is the value a Router delivers. It is deliberately dual-form so the
|
||||
// interface the router picks pays only for what that medium needs:
|
||||
//
|
||||
// - Value is the LIVE native value. The in-process interface hands it to the
|
||||
// local handler by pointer — zero serialization, zero copy, zero TCP. This
|
||||
// is the "skip the wire entirely when we share an address space" fast path.
|
||||
// - Encode lazily produces the zero-copy wire Message; a NETWORK interface
|
||||
// calls it (and only then, so an in-process delivery never encodes). A nil
|
||||
// Encode means the payload is in-process-only and cannot cross a wire.
|
||||
type Payload struct {
|
||||
Value any
|
||||
Encode func() *Message
|
||||
}
|
||||
|
||||
// LocalHandler consumes a Payload delivered IN-PROCESS. It receives the live
|
||||
// Value (type-assert to the concrete type the aspect defines) and may return a
|
||||
// reply Payload (nil Value ⇒ no reply). This is the handler an InProcessInterface
|
||||
// dispatches to — distinct from Node's wire Handler, which decodes bytes first.
|
||||
type LocalHandler func(ctx context.Context, from Destination, p Payload) (Payload, error)
|
||||
|
||||
// Interface moves a Payload one hop toward a Destination over a single medium.
|
||||
// Interfaces are ranked by Cost; Router always prefers the cheapest one that
|
||||
// can currently reach the destination. Implementations are the ONLY place a
|
||||
// transport detail (a socket, a serial line, a memory handoff) lives.
|
||||
type Interface interface {
|
||||
// Name identifies the interface for logs/metrics ("inprocess", "node-tcp").
|
||||
Name() string
|
||||
// Cost ranks interfaces; lower is preferred. Convention: in-process 0,
|
||||
// same-host IPC 1, LAN/cluster TCP 10, multi-hop routed 100+.
|
||||
Cost() int
|
||||
// CanReach reports whether this interface can deliver to dst RIGHT NOW
|
||||
// (destination registered locally, peer connected/discovered, path known).
|
||||
CanReach(dst Destination) bool
|
||||
// Deliver hands the payload to dst over this medium and returns an optional
|
||||
// reply (nil-Value Payload ⇒ none). It must not be called unless CanReach.
|
||||
Deliver(ctx context.Context, dst Destination, p Payload) (Payload, error)
|
||||
}
|
||||
|
||||
// ErrNoRoute is returned when no registered interface can reach the destination.
|
||||
var ErrNoRoute = errors.New("zap: no interface can reach destination")
|
||||
|
||||
// NewRouter returns an empty Router. Register interfaces onto it; the cheapest
|
||||
// reachable one wins per Send. A Router with no interfaces routes nothing (Send
|
||||
// returns ErrNoRoute) — safe, never panics.
|
||||
func NewRouter() *Router { return &Router{} }
|
||||
|
||||
// Register adds an interface, keeping the set sorted ascending by Cost so Send is
|
||||
// a linear scan that stops at the first (cheapest) interface that CanReach. Ties
|
||||
// keep registration order (stable), so an earlier-registered interface of equal
|
||||
// cost is preferred.
|
||||
func (t *Router) Register(i Interface) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.ifaces = append(t.ifaces, i)
|
||||
sort.SliceStable(t.ifaces, func(a, b int) bool {
|
||||
return t.ifaces[a].Cost() < t.ifaces[b].Cost()
|
||||
})
|
||||
}
|
||||
|
||||
// Interfaces returns a snapshot of the registered interfaces, cheapest first.
|
||||
func (t *Router) Interfaces() []Interface {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
out := make([]Interface, len(t.ifaces))
|
||||
copy(out, t.ifaces)
|
||||
return out
|
||||
}
|
||||
|
||||
// Send routes payload to dst over the cheapest interface that can reach it. It is
|
||||
// the whole public contract: the caller expresses intent (dst + payload) and the
|
||||
// locality decision (memory vs. wire) is the router's, made from the live Cost/
|
||||
// CanReach table — never the caller's branch. Returns ErrNoRoute if nothing can
|
||||
// currently reach dst.
|
||||
func (t *Router) Send(ctx context.Context, dst Destination, p Payload) (Payload, error) {
|
||||
t.mu.RLock()
|
||||
ifaces := t.ifaces // slice header copy under lock; entries are immutable
|
||||
t.mu.RUnlock()
|
||||
for _, i := range ifaces {
|
||||
if i.CanReach(dst) {
|
||||
return i.Deliver(ctx, dst, p)
|
||||
}
|
||||
}
|
||||
return Payload{}, ErrNoRoute
|
||||
}
|
||||
+37
@@ -91,6 +91,43 @@ type TransportConn interface {
|
||||
RemoteAddr() string
|
||||
}
|
||||
|
||||
// TransportStreamer is an optional capability extension to
|
||||
// TransportConn for transports that natively multiplex independent
|
||||
// bidirectional streams (notably QUIC). When a TransportConn also
|
||||
// implements TransportStreamer, Node.Call routes each request onto a
|
||||
// fresh per-Call stream instead of serializing on the shared control
|
||||
// stream — this lets concurrent Calls progress in parallel up to the
|
||||
// peer-advertised stream limit (1024 for ZAP's QUIC config).
|
||||
//
|
||||
// TCP transport does NOT implement TransportStreamer; Node.Call
|
||||
// transparently falls back to control-stream serialization on TCP.
|
||||
type TransportStreamer interface {
|
||||
// OpenCallStream opens a fresh bidirectional stream for a single
|
||||
// request/response exchange. The caller writes exactly one frame
|
||||
// (request) then reads exactly one frame (response) then closes.
|
||||
OpenCallStream(ctx context.Context) (TransportStream, error)
|
||||
|
||||
// AcceptCallStream blocks until the peer opens a per-Call stream.
|
||||
// Used by the server-side dispatch loop to fan out to handlers
|
||||
// without blocking on a single serialized read.
|
||||
AcceptCallStream(ctx context.Context) (TransportStream, error)
|
||||
}
|
||||
|
||||
// TransportStream is one per-Call stream. Stream lifecycle:
|
||||
//
|
||||
// WriteFrame(req) // single request
|
||||
// ReadFrame() // single response
|
||||
// Close() // release stream ID back to the QUIC pool
|
||||
//
|
||||
// Both directions are length-prefixed ZAP frames identical to the
|
||||
// control-stream wire format — so byte-for-byte interop with the
|
||||
// TCP transport and with control-stream Call paths is preserved.
|
||||
type TransportStream interface {
|
||||
WriteFrame(frame []byte) error
|
||||
ReadFrame() ([]byte, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// transportRegistry holds the registered factories, keyed by
|
||||
// Transport. Read-only after init.
|
||||
var transportRegistry = map[Transport]TransportFactory{}
|
||||
|
||||
+176
-1
@@ -96,8 +96,15 @@ var (
|
||||
)
|
||||
|
||||
// Message is a ZAP message that can be read zero-copy.
|
||||
//
|
||||
// When the message's backing storage was sourced from the pooled read
|
||||
// buffer (see bufpool.go), refs is non-nil and Release returns the
|
||||
// slab to its pool. For messages built via Builder.Finish() / Parse()
|
||||
// of caller-owned bytes, refs is nil and Release is a no-op — those
|
||||
// buffers are GC-managed as before.
|
||||
type Message struct {
|
||||
data []byte
|
||||
refs *bufRef // optional pool handle; nil = GC-managed
|
||||
}
|
||||
|
||||
// Parse parses a ZAP message from bytes without copying.
|
||||
@@ -143,11 +150,109 @@ func (m *Message) Version() uint16 {
|
||||
return binary.LittleEndian.Uint16(m.data[4:6])
|
||||
}
|
||||
|
||||
// ParseHeader validates a ZAP wire frame and returns the (validated
|
||||
// data slice, root offset) WITHOUT allocating a [*Message]. Same checks
|
||||
// as [Parse] — magic, version, size — but the result is two values, not
|
||||
// a pointer. Intended for generic wrappers (zapv1) that build their own
|
||||
// value-typed accessors and never need a *Message.
|
||||
//
|
||||
// Returns (data[:size], rootOff, nil) on success.
|
||||
//
|
||||
// The wire validation steps mirror [Parse] exactly (magic + version +
|
||||
// size + bounds). The implementation is intentionally written as one
|
||||
// linear sequence (no intermediate function calls) so the inliner
|
||||
// folds the whole body into the caller. Combined with the value-
|
||||
// typed [zapv1.View], this is what makes the v2 read path match v1's
|
||||
// 2 ns hand-rolled per-Read cost — zero function calls, zero heap.
|
||||
func ParseHeader(data []byte) ([]byte, int, error) {
|
||||
return parseHeaderImpl(data)
|
||||
}
|
||||
|
||||
// parseHeaderImpl is the actual validation body. Kept as a separate
|
||||
// function so ParseHeader stays inlineable (delegates a single call)
|
||||
// and per-schema WrapX shims can absorb the whole pipeline. The body
|
||||
// itself is one cost unit over the inline budget — that's fine; it
|
||||
// becomes ONE real function call per Wrap, comparable to v1's pattern
|
||||
// where Parse runs inline and the only call is the implicit message
|
||||
// constructor.
|
||||
func parseHeaderImpl(data []byte) ([]byte, int, error) {
|
||||
if len(data) < HeaderSize {
|
||||
return nil, 0, ErrBufferTooSmall
|
||||
}
|
||||
if string(data[0:4]) != Magic {
|
||||
return nil, 0, ErrInvalidMagic
|
||||
}
|
||||
version := binary.LittleEndian.Uint16(data[4:6])
|
||||
if version != Version1 && version != Version2 {
|
||||
return nil, 0, ErrInvalidVersion
|
||||
}
|
||||
size := binary.LittleEndian.Uint32(data[12:16])
|
||||
if int(size) < HeaderSize || int(size) > len(data) {
|
||||
return nil, 0, ErrBufferTooSmall
|
||||
}
|
||||
rootOff := int(binary.LittleEndian.Uint32(data[8:12]))
|
||||
return data[:size], rootOff, nil
|
||||
}
|
||||
|
||||
// WrapBuffer constructs a [*Message] over an already-built ZAP buffer
|
||||
// WITHOUT re-running [Parse]'s validation checks (magic, version,
|
||||
// size bounds). Use ONLY for buffers you just emitted via [Builder]
|
||||
// — they are valid by construction, and skipping the recheck is a
|
||||
// 14ns hot-path saving per Build call.
|
||||
//
|
||||
// External / untrusted bytes MUST go through Parse, never WrapBuffer.
|
||||
func WrapBuffer(data []byte) *Message {
|
||||
return &Message{data: data}
|
||||
}
|
||||
|
||||
// RootObjectAt returns a [zap.Object] anchored at absolute offset
|
||||
// `off` within this message. Used by zapv1.Build to avoid a redundant
|
||||
// header-read after [Builder.FinishAsRoot] already returned the root
|
||||
// position.
|
||||
func (m *Message) RootObjectAt(off int) Object {
|
||||
return Object{msg: m, offset: off}
|
||||
}
|
||||
|
||||
// Bytes returns the underlying byte slice.
|
||||
func (m *Message) Bytes() []byte {
|
||||
return m.data
|
||||
}
|
||||
|
||||
// Release returns this message's backing slab to the read-buffer pool
|
||||
// when the message was sourced via the pooled read path. Safe to call
|
||||
// on any *Message — when the buffer is GC-managed (Builder, Parse of
|
||||
// caller bytes) Release is a no-op.
|
||||
//
|
||||
// After Release the underlying bytes MAY be overwritten by the next
|
||||
// pool consumer. Callers must not use Bytes() / Root() / accessors on
|
||||
// a released message.
|
||||
//
|
||||
// Release on a nil receiver is a no-op so callers can defer it
|
||||
// without nil-checking.
|
||||
func (m *Message) Release() {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
r := m.refs
|
||||
m.refs = nil
|
||||
m.data = nil
|
||||
r.release()
|
||||
}
|
||||
|
||||
// Retain increments the reference count on this message's backing
|
||||
// slab, allowing the caller to hand the message off to a goroutine
|
||||
// that will Release it later. No-op when refs is nil.
|
||||
//
|
||||
// Pair every Retain with exactly one Release. The dispatch loop calls
|
||||
// Retain before pushing a response *Message onto a Call's response
|
||||
// channel; the Call goroutine then Releases when it consumes the
|
||||
// response.
|
||||
func (m *Message) Retain() {
|
||||
if m != nil && m.refs != nil {
|
||||
m.refs.retain()
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the total message size.
|
||||
func (m *Message) Size() int {
|
||||
return len(m.data)
|
||||
@@ -175,6 +280,26 @@ func (o Object) IsNull() bool {
|
||||
return o.offset == 0
|
||||
}
|
||||
|
||||
// Offset returns the object's absolute byte offset within its
|
||||
// underlying message. Exposed so external generic wrappers (zapv1)
|
||||
// can construct typed sub-views into the same buffer without
|
||||
// re-walking the parent pointers.
|
||||
//
|
||||
// SAFETY: callers MUST treat the returned offset as opaque — it's
|
||||
// load-bearing for unsafe-pointer arithmetic, so any out-of-range
|
||||
// usage is the caller's responsibility. The matching message bytes
|
||||
// are reachable via Message.Bytes() on this object's message.
|
||||
func (o Object) Offset() int {
|
||||
return o.offset
|
||||
}
|
||||
|
||||
// Message returns the underlying [*Message] this Object is a view
|
||||
// into. Used by zapv1 to alias the message's bytes for direct
|
||||
// payload indexing.
|
||||
func (o Object) Message() *Message {
|
||||
return o.msg
|
||||
}
|
||||
|
||||
// Bool reads a bool at the given field offset.
|
||||
func (o Object) Bool(fieldOffset int) bool {
|
||||
return o.Uint8(fieldOffset) != 0
|
||||
@@ -246,6 +371,28 @@ func (o Object) Float64(fieldOffset int) float64 {
|
||||
return math.Float64frombits(o.Uint64(fieldOffset))
|
||||
}
|
||||
|
||||
// BytesFixedSlice returns a zero-copy slice of n inline bytes at
|
||||
// fieldOffset within the object's fixed payload. This is the generic-
|
||||
// width counterpart of HashSlice (32 bytes) and AddressSlice (20
|
||||
// bytes); it covers any N>0 inline byte-array slot (e.g., LP-201
|
||||
// SessionID [16]byte, LP-208 QuasarWitness [96]byte).
|
||||
//
|
||||
// Returns nil if the requested span falls outside the buffer. The
|
||||
// returned slice aliases the underlying message data; the caller MUST
|
||||
// NOT mutate it.
|
||||
//
|
||||
// This is the symmetric reader for ObjectBuilder.SetBytesFixed.
|
||||
func (o Object) BytesFixedSlice(fieldOffset, n int) []byte {
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
pos := o.offset + fieldOffset
|
||||
if pos+n > len(o.msg.data) {
|
||||
return nil
|
||||
}
|
||||
return o.msg.data[pos : pos+n]
|
||||
}
|
||||
|
||||
// Text reads a string at the given field offset (zero-copy).
|
||||
func (o Object) Text(fieldOffset int) string {
|
||||
b := o.Bytes(fieldOffset)
|
||||
@@ -490,7 +637,8 @@ func (l List) Uint64(i int) uint64 {
|
||||
return binary.LittleEndian.Uint64(l.msg.data[pos:])
|
||||
}
|
||||
|
||||
// Object returns an object list element.
|
||||
// Object returns an INLINE object list element — element i is a fixed-stride
|
||||
// object of elemSize bytes living at l.offset + i*elemSize.
|
||||
func (l List) Object(i int, elemSize int) Object {
|
||||
if i < 0 || i >= l.length {
|
||||
return Object{}
|
||||
@@ -498,6 +646,33 @@ func (l List) Object(i int, elemSize int) Object {
|
||||
return Object{msg: l.msg, offset: l.offset + i*elemSize}
|
||||
}
|
||||
|
||||
// ObjectPtr returns the i'th element of an OUT-OF-LINE object list — the read
|
||||
// counterpart to [ListBuilder.AddObjectPtr]. Element i is a 4-byte SIGNED
|
||||
// relative pointer at l.offset + i*4; it is dereferenced as absOffset =
|
||||
// slotPos + int32(rel), exactly like [Object.Object]. A null (0) element or an
|
||||
// out-of-range target yields the zero Object (IsNull). This is the canonical
|
||||
// way to read a "repeated message" field where each element is a tailed object
|
||||
// rather than a fixed-size inline value — the decomplected replacement for the
|
||||
// byte-blob envelope-concat pattern.
|
||||
func (l List) ObjectPtr(i int) Object {
|
||||
if i < 0 || i >= l.length {
|
||||
return Object{}
|
||||
}
|
||||
pos := l.offset + i*4
|
||||
if pos+4 > len(l.msg.data) {
|
||||
return Object{}
|
||||
}
|
||||
rel := int32(binary.LittleEndian.Uint32(l.msg.data[pos:]))
|
||||
if rel == 0 {
|
||||
return Object{} // null element
|
||||
}
|
||||
absOffset := pos + int(rel)
|
||||
if absOffset < HeaderSize || absOffset >= len(l.msg.data) {
|
||||
return Object{}
|
||||
}
|
||||
return Object{msg: l.msg, offset: absOffset}
|
||||
}
|
||||
|
||||
// Bytes returns the raw bytes of the list (for byte lists).
|
||||
func (l List) Bytes() []byte {
|
||||
if l.msg == nil || l.offset+l.length > len(l.msg.data) {
|
||||
|
||||
+26
-2
@@ -131,11 +131,35 @@ func rwMap(dst jsWriter, src *Reader) (n int, err error) {
|
||||
n++
|
||||
}
|
||||
|
||||
field, err = src.ReadMapKeyPtr()
|
||||
var kt Type
|
||||
kt, err = src.NextType()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nn, err = rwquoted(dst, field)
|
||||
switch kt {
|
||||
case IntType:
|
||||
var i64 int64
|
||||
i64, err = src.ReadInt64()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
src.scratch = strconv.AppendInt(src.scratch[:0], i64, 10)
|
||||
nn, err = rwquoted(dst, src.scratch)
|
||||
case UintType:
|
||||
var u64 uint64
|
||||
u64, err = src.ReadUint64()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
src.scratch = strconv.AppendUint(src.scratch[:0], u64, 10)
|
||||
nn, err = rwquoted(dst, src.scratch)
|
||||
default:
|
||||
field, err = src.ReadMapKeyPtr()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nn, err = rwquoted(dst, field)
|
||||
}
|
||||
n += nn
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
+21
@@ -144,6 +144,27 @@ func rwMapBytes(w jsWriter, msg []byte, scratch []byte, depth int) ([]byte, []by
|
||||
}
|
||||
|
||||
func rwMapKeyBytes(w jsWriter, msg []byte, scratch []byte, depth int) ([]byte, []byte, error) {
|
||||
if len(msg) < 1 {
|
||||
return msg, scratch, ErrShortBytes
|
||||
}
|
||||
switch getType(msg[0]) {
|
||||
case IntType:
|
||||
i, msg, err := ReadInt64Bytes(msg)
|
||||
if err != nil {
|
||||
return msg, scratch, err
|
||||
}
|
||||
scratch = strconv.AppendInt(scratch[:0], i, 10)
|
||||
_, err = rwquoted(w, scratch)
|
||||
return msg, scratch, err
|
||||
case UintType:
|
||||
u, msg, err := ReadUint64Bytes(msg)
|
||||
if err != nil {
|
||||
return msg, scratch, err
|
||||
}
|
||||
scratch = strconv.AppendUint(scratch[:0], u, 10)
|
||||
_, err = rwquoted(w, scratch)
|
||||
return msg, scratch, err
|
||||
}
|
||||
msg, scratch, err := rwStringBytes(w, msg, scratch, depth)
|
||||
if err != nil {
|
||||
if tperr, ok := err.(TypeError); ok && tperr.Encoded == BinType {
|
||||
|
||||
+4
@@ -121,6 +121,10 @@ func Decode(r io.Reader, d Decodable) error {
|
||||
// reader will be buffered.
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
p := readerPool.Get().(*Reader)
|
||||
p.recursionDepth = 0
|
||||
p.maxElements = 0
|
||||
p.maxRecursionDepth = 0
|
||||
p.maxStrLen = 0
|
||||
if p.R == nil {
|
||||
p.R = fwd.NewReader(r)
|
||||
} else {
|
||||
|
||||
+11
-7
@@ -96,9 +96,9 @@ linters:
|
||||
- "!**/exporters/zipkin/**"
|
||||
deny:
|
||||
- pkg: go.opentelemetry.io/otel/semconv
|
||||
desc: "Use go.opentelemetry.io/otel/semconv/v1.40.0 instead. If a newer semconv version has been released, update the depguard rule."
|
||||
desc: "Use go.opentelemetry.io/otel/semconv/v1.41.0 instead. If a newer semconv version has been released, update the depguard rule."
|
||||
allow:
|
||||
- go.opentelemetry.io/otel/semconv/v1.40.0
|
||||
- go.opentelemetry.io/otel/semconv/v1.41.0
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- appendAssign
|
||||
@@ -134,13 +134,16 @@ linters:
|
||||
strconcat: true
|
||||
revive:
|
||||
confidence: 0.01
|
||||
enable-all-rules: false
|
||||
enable-default-rules: true
|
||||
max-open-files: 2048
|
||||
rules:
|
||||
- name: blank-imports
|
||||
- name: bool-literal-in-expr
|
||||
- name: constant-logical-expr
|
||||
- name: context-as-argument
|
||||
arguments:
|
||||
- allowTypesBefore: '*testing.T'
|
||||
- allow-types-before: '*testing.T'
|
||||
disabled: true
|
||||
- name: context-keys-type
|
||||
- name: deep-exit
|
||||
@@ -152,7 +155,7 @@ linters:
|
||||
- name: duplicated-imports
|
||||
- name: early-return
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: empty-block
|
||||
- name: empty-lines
|
||||
- name: error-naming
|
||||
@@ -161,7 +164,7 @@ linters:
|
||||
- name: errorf
|
||||
- name: exported
|
||||
arguments:
|
||||
- sayRepetitiveInsteadOfStutters
|
||||
- say-repetitive-instead-of-stutters
|
||||
- name: flag-parameter
|
||||
- name: identical-branches
|
||||
- name: if-return
|
||||
@@ -169,11 +172,12 @@ linters:
|
||||
- name: increment-decrement
|
||||
- name: indent-error-flow
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: range-val-in-closure
|
||||
- name: range-val-address
|
||||
- name: receiver-naming
|
||||
- name: redefines-builtin-id
|
||||
- name: string-format
|
||||
arguments:
|
||||
@@ -183,7 +187,7 @@ linters:
|
||||
- name: struct-tag
|
||||
- name: superfluous-else
|
||||
arguments:
|
||||
- preserveScope
|
||||
- preserve-scope
|
||||
- name: time-equal
|
||||
- name: unconditional-recursion
|
||||
- name: unexported-return
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Agent Guide for opentelemetry-go
|
||||
|
||||
This file contains active, task-oriented instructions for autonomous and semi-autonomous coding agents working in this repository.
|
||||
|
||||
Before starting any task, read `.github/copilot-instructions.md`, `CONTRIBUTING.md`, and this file.
|
||||
Treat `.github/copilot-instructions.md` as global passive guidance for every task, including docs-only and review-only work.
|
||||
|
||||
## Core expectations
|
||||
|
||||
- Preserve OpenTelemetry specification compliance, API stability, and idiomatic Go.
|
||||
- Prefer minimal, surgical changes over broad refactors or speculative cleanup.
|
||||
- Read the package you are editing and match its existing naming, option types, error handling, comments, tests, and concurrency patterns.
|
||||
- Keep public APIs backward compatible unless the task explicitly requires a breaking change.
|
||||
- Keep telemetry resilient and loosely coupled. Do not introduce behavior that can unexpectedly interfere with host applications.
|
||||
- Inspect boundaries carefully: input validation, resource limits, cancellation, shutdown, error propagation, concurrency, and memory growth.
|
||||
- Prefer fail-safe behavior and explicit invariants over implicit assumptions.
|
||||
- Keep dependencies minimal and justified.
|
||||
- Preserve host-application safety: telemetry should not panic, block indefinitely, or amplify attacker-controlled input.
|
||||
- Be conservative on hot paths. Avoid unnecessary allocations, reflection, interface churn, blocking, global state, and high-cardinality telemetry.
|
||||
- Write comments only for intent, invariants, and non-obvious constraints. Do not add comments that restate the code.
|
||||
|
||||
## Default workflow
|
||||
|
||||
For new features and behavior changes, use this order unless the task explicitly says otherwise:
|
||||
|
||||
1. Read the relevant package, its tests, and any package docs or `README.md`.
|
||||
2. Add or update a failing unit test that captures the required behavior or regression.
|
||||
3. Implement the smallest change that makes the test pass.
|
||||
4. Refactor only after the behavior is locked in, and only if the refactor keeps the diff focused.
|
||||
5. If the changed code is on a hot path or performance-sensitive, inspect existing benchmarks and run them. Add a benchmark if coverage is missing.
|
||||
6. Update documentation artifacts as needed while the context is fresh. Follow the documentation and changelog conventions below for the specific updates required.
|
||||
7. Run `make precommit` each time before considering the work complete.
|
||||
|
||||
For docs-only, test-only, or review-only tasks, still start with the required repository guidance above, then skip the workflow steps that do not apply while keeping the same discipline around scope, verification, and repository conventions.
|
||||
|
||||
## Verification
|
||||
|
||||
- Use `make` as the canonical repository verification command. The default target is `precommit`.
|
||||
- `make precommit` is the expected final verification step for linting, generation, README checks, module checks, and tests.
|
||||
- During iteration, targeted commands are fine for fast feedback, but do not stop there if the task changes code.
|
||||
- If you touch performance-sensitive code, run focused benchmarks and compare the results using `benchstat` in addition to `make`.
|
||||
|
||||
## Documentation and changelog
|
||||
|
||||
- Non-internal, non-test packages should have Go doc comments, usually in `doc.go`.
|
||||
- Non-internal, non-test, non-documentation packages should also have a `README.md` with at least a title and a `pkg.go.dev` badge.
|
||||
- Prefer examples over long code snippets in GoDoc when practical.
|
||||
- Keep docs aligned with actual behavior. Do not leave stale comments, stale examples, or stale package documentation behind.
|
||||
- For user-visible changes, update `CHANGELOG.md` under the appropriate `Added`, `Changed`, `Deprecated`, `Fixed`, or `Removed` section within `## [Unreleased]`.
|
||||
|
||||
## Repository habits
|
||||
|
||||
- Prefer focused diffs. Avoid drive-by cleanup.
|
||||
- Follow existing option patterns and exported API conventions instead of inventing new abstractions.
|
||||
- Generated files are checked in. If your change affects generation, keep generated output up to date.
|
||||
- Prefer fast local search tools such as `rg` when exploring the repository.
|
||||
- When changing behavior, make the invariants explicit in tests.
|
||||
|
||||
## Personas
|
||||
|
||||
### Feature Agent
|
||||
|
||||
Use this persona for new behavior, new API surface, or spec-driven feature work.
|
||||
|
||||
- Start with a failing unit test.
|
||||
- Confirm the expected behavior against the spec, existing package behavior, and public API compatibility.
|
||||
- Implement the smallest viable change.
|
||||
- Update GoDoc, examples, `README.md`, and `CHANGELOG.md` when the change is user-visible.
|
||||
- If the feature touches a hot path, check benchmarks and add one if the coverage is missing.
|
||||
|
||||
### Refactoring Agent
|
||||
|
||||
Use this persona when improving structure without intentionally changing behavior.
|
||||
|
||||
- Treat behavior preservation as the default contract.
|
||||
- Add or tighten tests before moving code if current behavior is not already pinned down.
|
||||
- Avoid broad rewrites, clever abstractions, or package-wide cleanup unless explicitly requested.
|
||||
- If a refactor touches a hot path, benchmark before and after.
|
||||
- Keep API shape, semantics, concurrency guarantees, and failure modes unchanged unless the task says otherwise.
|
||||
|
||||
### Test Agent
|
||||
|
||||
Use this persona when adding missing coverage, reproducing bugs, or hardening regressions.
|
||||
|
||||
- Reproduce the bug or missing behavior with the smallest failing test you can.
|
||||
- Prefer testing public behavior and externally visible invariants.
|
||||
- Add targeted regression tests before changing production code.
|
||||
- Only change production code when it is required to make the tested behavior correct or testable.
|
||||
- Keep tests deterministic, readable, and aligned with package patterns.
|
||||
|
||||
### Performance Agent
|
||||
|
||||
Use this persona for hot-path work, allocation reduction, or throughput and latency improvements.
|
||||
|
||||
- Benchmark first to establish a baseline.
|
||||
- Prefer changes that reduce allocations, copying, interface churn, and unnecessary synchronization.
|
||||
- Do not trade away correctness, spec compliance, or API stability for micro-optimizations.
|
||||
- Add or update benchmarks when performance-sensitive coverage is missing.
|
||||
- If you materially change a hot path, capture before-and-after results, preferably with `benchstat`.
|
||||
|
||||
### Review Agent
|
||||
|
||||
Use this persona when asked to review code, patches, or pull requests.
|
||||
|
||||
- Lead with findings, not summaries.
|
||||
- Order findings by severity and include precise file and line references when available.
|
||||
- Focus on correctness, spec compliance, API compatibility, concurrency safety, resilience, performance regressions, missing tests, missing benchmarks, documentation gaps, and changelog gaps.
|
||||
- Call out when a diff is broader than necessary.
|
||||
- If you find no issues, say that explicitly and note any residual risks or verification gaps.
|
||||
+96
-1
@@ -11,6 +11,100 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
<!-- Released section -->
|
||||
<!-- Don't change this section unless doing release -->
|
||||
|
||||
## [1.44.0/0.66.0/0.20.0/0.0.17] 2026-05-27
|
||||
|
||||
### Added
|
||||
|
||||
- Add `ByteSlice` and `ByteSliceValue` functions for new `BYTESLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#7948)
|
||||
- Apply attribute value limit to the `KindBytes` attribute type in `go.opentelemetry.io/otel/sdk/log`. (#7990)
|
||||
- Apply attribute value limit to the `BYTESLICE` attribute type in `go.opentelemetry.io/otel/sdk/trace`. (#7990)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/trace`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8153)
|
||||
- Support `BYTESLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8153)
|
||||
- Add `String` method for `Value` type in `go.opentelemetry.io/otel/attribute`. (#8142)
|
||||
- Add `Slice` and `SliceValue` functions for new `SLICE` attribute type in `go.opentelemetry.io/otel/attribute`. (#8166)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlptrace`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlplog`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric`. (#8216)
|
||||
- Support `SLICE` attributes in `go.opentelemetry.io/otel/exporters/zipkin`. (#8216)
|
||||
- Apply `AttributeValueLengthLimit` to `attribute.SLICE` type attribute values in `go.opentelemetry.io/otel/sdk/trace`, recursively truncating contained string values. (#8217)
|
||||
- Add `Error` field on `Record` type in `go.opentelemetry.io/otel/log/logtest`. (#8148)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`. (#8157)
|
||||
- Add `WithMaxRequestSize` option in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8157)
|
||||
- Add `Settable` to `go.opentelemetry.io/otel/metric/x` to allow reusing attribute options. (#8178)
|
||||
- Add experimental support for splitting metric data across multiple batches in `go.opentelemetry.io/otel/sdk/metric`.
|
||||
Set `OTEL_GO_X_METRIC_EXPORT_BATCH_SIZE=<max_size>` to enable for all periodic readers.
|
||||
See `go.opentelemetry.io/otel/sdk/metric/internal/x` for feature documentation. (#8071)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/x` for feature documentation. (#8192)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/x` for feature documentation. (#8194)
|
||||
- Add experimental self-observability metrics in `go.opentelemetry.io/otel/exporters/stdout/stdoutlog`.
|
||||
Enable with `OTEL_GO_X_SELF_OBSERVABILITY=true` environment variable.
|
||||
See `go.opentelemetry.io/otel/stdout/stdoutlog/internal/x` for feature documentation. (#8263)
|
||||
- Add `WithDefaultAttributes` to `go.opentelemetry.io/otel/metric/x` to support setting default attributes on instruments. (#8135)
|
||||
- Add `go.opentelemetry.io/otel/semconv/v1.41.0` package.
|
||||
The package contains semantic conventions from the `v1.41.0` version of the OpenTelemetry Semantic Conventions.
|
||||
See the [migration documentation](./semconv/v1.41.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.40.0`. (#8324)
|
||||
- Add Observable variants of instruments to `go.opentelemetry.io/otel/semconv/v1.41.0` package. (#8350)
|
||||
- Generate explicit histogram bucket boundaries from weaver configuration for HTTP and RPC duration instruments in `go.opentelemetry.io/otel/semconv/v1.41.0`. (#8002)
|
||||
|
||||
### Changed
|
||||
|
||||
- ⚠️ **Breaking Change:** `go.opentelemetry.io/otel/sdk/metric` now applies a default cardinality limit of 2000 to comply with the Metrics SDK specification recommendation.
|
||||
New attribute sets are dropped when the cardinality limit is reached. The measurement of these sets are aggregated into a special attribute set containing `attribute.Bool("otel.metric.overflow", true)`.
|
||||
This can break users who relied on the previous unlimited default.
|
||||
Set `WithCardinalityLimit(0)` or the deprecated `OTEL_GO_X_CARDINALITY_LIMIT=0` environment variable to preserve unlimited cardinality.
|
||||
Note that support for `OTEL_GO_X_CARDINALITY_LIMIT` may be removed in a future release. (#8247)
|
||||
- `ErrorType` in `go.opentelemetry.io/otel/semconv` now unwraps errors created with `fmt.Errorf` when deriving the `error.type` attribute. (#8133)
|
||||
- `go.opentelemetry.io/otel/sdk/log` now unwraps error chains created with `fmt.Errorf` when deriving the `error.type` attribute from errors on log records. (#8133)
|
||||
- `Set.MarshalLog` method in `go.opentelemetry.io/otel/attribute` now uses `Value.String` formatting following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8169)
|
||||
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir and short-circuit `Offer` calls to the exemplar reservoir when `exemplar.AlwaysOffFilter` is configured. (#8211) (#8267)
|
||||
- Optimize `go.opentelemetry.io/otel/sdk/metric` to return a drop reservoir for asynchronous instruments when `exemplar.TraceBasedFilter` is configured. (#8286)
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecate `Value.Emit` method in `go.opentelemetry.io/otel/attribute`.
|
||||
Use `Value.String` instead. (#8176)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Limit OTLP request size to 64 MiB by default in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`.
|
||||
The limit applies before compression, oversized requests are treated as non-retryable errors, and the limit can be configured with the new `WithMaxRequestSize` option. (#8157, #8365)
|
||||
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8135)
|
||||
- Fix gzipped request body replay on redirect in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8152)
|
||||
- `go.opentelemetry.io/otel/exporters/prometheus` now uses `Value.String` formatting for label values following the [OpenTelemetry AnyValue representation for non-OTLP protocols](https://opentelemetry.io/docs/specs/otel/common/#anyvalue). (#8170)
|
||||
- Propagate errors from the exporter when calling `Shutdown` on `BatchSpanProcessor` in `go.opentelemetry.io/otel/sdk/trace`. (#8197)
|
||||
- Fix stale status code reporting on self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8226)
|
||||
- Fix a concurrent `Collect` data race and potential panic in `go.opentelemetry.io/otel/exporters/prometheus` when `WithResourceAsConstantLabels` option is used. (#8227)
|
||||
- Fix race condition in `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` by reverting #7447. (#8249)
|
||||
- Fix `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` to safely handle zero size.
|
||||
A capacity check in the constructor initializes the reservoir safely and skips initialization for zero-cap; early returns in `Offer()` and `Collect()` ensure no-op behavior. (#8295)
|
||||
- Fix counting of spans and logs in self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`, and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8254)
|
||||
- Drop conflicting scope attributes named `name`, `version`, or `schema_url` from metric labels in `go.opentelemetry.io/otel/exporters/prometheus`, preserving the dedicated `otel_scope_name`, `otel_scope_version`, and `otel_scope_schema_url` labels. (#8264)
|
||||
- Close schema files opened by `ParseFile` in `go.opentelemetry.io/otel/schema/v1.0` and `go.opentelemetry.io/otel/schema/v1.1`. ([GHSA-995v-fvrw-c78m](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-995v-fvrw-c78m))
|
||||
- Enforce the 8192-byte baggage size limit during extraction/parsing, changing behavior when the limit is exceeded in `go.opentelemetry.io/otel/baggage` and `go.opentelemetry.io/otel/propagation`. (#8222)
|
||||
- Fix `go.opentelemetry.io/otel/semconv/v1.41.0` to include `Attr*` helper methods for required attributes on observable instruments. (#8361)
|
||||
- Limit baggage extraction error reporting in `go.opentelemetry.io/otel/propagation` to prevent malformed or oversized baggage headers from flooding logs. ([GHSA-5wrp-cwcj-q835](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-5wrp-cwcj-q835))
|
||||
|
||||
## [1.43.0/0.65.0/0.19.0] 2026-04-02
|
||||
|
||||
### Added
|
||||
@@ -3619,7 +3713,8 @@ It contains api and sdk for trace and meter.
|
||||
- CircleCI build CI manifest files.
|
||||
- CODEOWNERS file to track owners of this project.
|
||||
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.43.0...HEAD
|
||||
[Unreleased]: https://github.com/open-telemetry/opentelemetry-go/compare/v1.44.0...HEAD
|
||||
[1.44.0/0.66.0/0.20.0/0.0.17]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.44.0
|
||||
[1.43.0/0.65.0/0.19.0]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.43.0
|
||||
[1.42.0/0.64.0/0.18.0/0.0.16]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.42.0
|
||||
[1.41.0/0.63.0/0.17.0/0.0.15]: https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.41.0
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Instructions for Claude Code
|
||||
|
||||
@AGENTS.md
|
||||
+89
-12
@@ -11,6 +11,12 @@ for a summary description of past meetings. To request edit access,
|
||||
join the meeting or get in touch on
|
||||
[Slack](https://cloud-native.slack.com/archives/C01NPAXACKT).
|
||||
|
||||
The meeting is open for all to join. We invite everyone to join our
|
||||
meeting, regardless of your experience level. Whether you're a
|
||||
seasoned OpenTelemetry developer, just starting your journey, or
|
||||
simply curious about the work we do, you're more than welcome to
|
||||
participate!
|
||||
|
||||
## Development
|
||||
|
||||
You can view and edit the source code by cloning this repository:
|
||||
@@ -746,8 +752,8 @@ Encapsulate setup in constructor functions, ensuring clear ownership and scope:
|
||||
import (
|
||||
"errors"
|
||||
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.41.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.41.0/otelconv"
|
||||
)
|
||||
|
||||
type SDKComponent struct {
|
||||
@@ -808,11 +814,11 @@ func (c *Component) initObservability() {
|
||||
|
||||
#### Performance
|
||||
|
||||
When observability is disabled there should be little to no overhead.
|
||||
When observability is disabled or the instrument is not `Enabled`, there should be little to no overhead.
|
||||
|
||||
```go
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
||||
if e.inst != nil {
|
||||
if e.inst != nil && e.inst.Enabled(ctx) {
|
||||
attrs := expensiveOperation()
|
||||
e.inst.recordSpanInflight(ctx, int64(len(spans)), attrs...)
|
||||
}
|
||||
@@ -829,7 +835,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
||||
}
|
||||
|
||||
func (i *instrumentation) recordSpanInflight(ctx context.Context, count int64, attrs ...attribute.KeyValue) {
|
||||
if i == nil || i.inflight == nil {
|
||||
if i == nil || i.inflight == nil || !i.inflight.Enabled(ctx) {
|
||||
return
|
||||
}
|
||||
i.inflight.Add(ctx, count, metric.WithAttributes(attrs...))
|
||||
@@ -865,8 +871,12 @@ var (
|
||||
)
|
||||
|
||||
func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...attribute.KeyValue) {
|
||||
if !i.counter.Enabled(ctx) {
|
||||
return
|
||||
}
|
||||
attrs := attrPool.Get().(*[]attribute.KeyValue)
|
||||
defer func() {
|
||||
clear(*attrs) // Clear references to strings/etc to let GC collect them.
|
||||
*attrs = (*attrs)[:0] // Reset.
|
||||
attrPool.Put(attrs)
|
||||
}()
|
||||
@@ -877,6 +887,7 @@ func (i *instrumentation) record(ctx context.Context, value int64, baseAttrs ...
|
||||
|
||||
addOpt := addOptPool.Get().(*[]metric.AddOption)
|
||||
defer func() {
|
||||
clear(*addOpt)
|
||||
*addOpt = (*addOpt)[:0]
|
||||
addOptPool.Put(addOpt)
|
||||
}()
|
||||
@@ -1007,16 +1018,20 @@ Ensure observability measurements receive the correct context, especially for tr
|
||||
```go
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) error {
|
||||
// Use the provided context for observability measurements
|
||||
e.inst.recordSpanExportStarted(ctx, len(spans))
|
||||
if e.inst.Enabled(ctx) {
|
||||
e.inst.recordSpanExportStarted(ctx, len(spans))
|
||||
}
|
||||
|
||||
err := e.doExport(ctx, spans)
|
||||
|
||||
if err != nil {
|
||||
e.inst.recordSpanExportFailed(ctx, len(spans), err)
|
||||
} else {
|
||||
e.inst.recordSpanExportSucceeded(ctx, len(spans))
|
||||
if e.inst.Enabled(ctx) {
|
||||
if err != nil {
|
||||
e.inst.recordSpanExportFailed(ctx, len(spans), err)
|
||||
} else {
|
||||
e.inst.recordSpanExportSucceeded(ctx, len(spans))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return err
|
||||
}
|
||||
```
|
||||
@@ -1039,7 +1054,7 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
|
||||
|
||||
All observability metrics should follow the [OpenTelemetry Semantic Conventions for SDK metrics](https://github.com/open-telemetry/semantic-conventions/blob/1cf2476ae5e518225a766990a28a6d5602bd5a30/docs/otel/sdk-metrics.md).
|
||||
|
||||
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.40.0/otelconv/metric.go).
|
||||
Use the metric semantic conventions convenience package [otelconv](./semconv/v1.41.0/otelconv/metric.go).
|
||||
|
||||
##### Component Identification
|
||||
|
||||
@@ -1109,6 +1124,68 @@ func TestObservability(t *testing.T) {
|
||||
Test order should not affect results.
|
||||
Ensure that any global state (e.g. component ID counters) is reset between tests.
|
||||
|
||||
### Experimental Features
|
||||
|
||||
To support the development of new features in the specification, we use the following patterns to implement in-development features without adding new public artifacts in stable modules.
|
||||
|
||||
#### Experimental behavior with no API artifacts
|
||||
|
||||
Features that change behavior without changing the API (e.g., exemplar collection, auto-generation of identifiers) are implemented behind a feature gate.
|
||||
The implementation resides in an `/internal/x` package and is activated through environment variables with the `OTEL_GO_X_` prefix (e.g., `OTEL_GO_X_OBSERVABILITY`).
|
||||
The feature must be documented in a `README.md` file in the `/internal/x` package.
|
||||
|
||||
#### Experimental methods on SDK-only interfaces
|
||||
|
||||
Features that require new methods on SDK interfaces are defined as a new interface in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
|
||||
The SDK must not depend on the experimental module.
|
||||
|
||||
#### Experimental structs, functions, or interfaces
|
||||
|
||||
Features that don't need any changes to the existing stable package are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
|
||||
#### Experimental signals and components
|
||||
|
||||
New telemetry signals (e.g., Logs before stabilization) and components (e.g. bridges) are hosted in new, unstable modules (e.g., `go.opentelemetry.io/otel/log` before 1.0.0).
|
||||
The package should have the final name it will use once stabilized (i.e. not `/x`), and is released at a v0.x.y version to indicate it is not stable.
|
||||
Most new components are hosted in [opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib).
|
||||
|
||||
#### Experimental options for API or SDK functions
|
||||
|
||||
Experimental Options functions are implemented in an experimental module (e.g., `go.opentelemetry.io/otel/sdk/x`).
|
||||
The return type of the Option function must embed the option's type (e.g. `metric.InstrumentOption`), and have an `Experimental()` method to prevent the API from panicking when the option is used.
|
||||
The SDK uses type assertions (without importing the unstable package) to check if passing types implement these experimental interfaces.
|
||||
The SDK must not depend on the experimental module.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
type myOption struct {
|
||||
// Embed the stable option type.
|
||||
metric.InstrumentOption
|
||||
value string
|
||||
}
|
||||
|
||||
// Experimental prevents the API from panicking when the option is used.
|
||||
func (o myOption) Experimental() {}
|
||||
|
||||
// The SDK can use type assertions to use this function.
|
||||
func (o myOption) Value() string { return o.value }
|
||||
|
||||
func WithMyOption(value string) metric.InstrumentOption {
|
||||
return myOption{value: value}
|
||||
}
|
||||
```
|
||||
|
||||
#### Not Supported
|
||||
|
||||
The following kinds of experimental features are **not currently supported** on stable interfaces:
|
||||
|
||||
- Experimental methods on API interfaces
|
||||
- Experimental fields for API or SDK exported structs
|
||||
|
||||
In some cases forks or long-lived branches may be used for prototyping these features.
|
||||
|
||||
## Approvers and Maintainers
|
||||
|
||||
### Maintainers
|
||||
|
||||
+9
-1
@@ -191,8 +191,16 @@ benchmark: $(OTEL_GO_MOD_DIRS:%=benchmark/%)
|
||||
benchmark/%:
|
||||
cd $* && $(GO) test -run='^$$' -bench=. $(ARGS) ./...
|
||||
|
||||
# sdk/metric is split into two shards to work around CodSpeed limitations.
|
||||
# See https://github.com/CodSpeedHQ/codspeed-go/issues/56
|
||||
BENCHMARK_SHARDS := $(filter-out ./sdk/metric,$(OTEL_GO_MOD_DIRS)) ./sdk/metric/root ./sdk/metric/internal
|
||||
benchmark/./sdk/metric/root:
|
||||
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) . ./exemplar/...
|
||||
benchmark/./sdk/metric/internal:
|
||||
cd ./sdk/metric && $(GO) test -run='^$$' -bench=. $(ARGS) ./internal/...
|
||||
|
||||
print-sharded-benchmarks:
|
||||
@echo $(OTEL_GO_MOD_DIRS) | jq -cR 'split(" ")'
|
||||
@echo $(BENCHMARK_SHARDS) | jq -cR 'split(" ")'
|
||||
|
||||
.PHONY: golangci-lint golangci-lint-fix
|
||||
golangci-lint-fix: ARGS=--fix
|
||||
|
||||
+3
-1
@@ -105,7 +105,9 @@ func (d *defaultAttrEncoder) Encode(iter Iterator) string {
|
||||
if keyValue.Value.Type() == STRING {
|
||||
copyAndEscape(buf, keyValue.Value.AsString())
|
||||
} else {
|
||||
_, _ = buf.WriteString(keyValue.Value.Emit())
|
||||
_, _ = buf.WriteString(
|
||||
keyValue.Value.Emit(),
|
||||
) //nolint:staticcheck // Preserve the existing default encoder output.
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
|
||||
+47
-11
@@ -27,6 +27,8 @@ const (
|
||||
int64SliceID uint64 = 3762322556277578591 // "_[]int64" (little endian)
|
||||
float64SliceID uint64 = 7308324551835016539 // "[]double" (little endian)
|
||||
stringSliceID uint64 = 7453010373645655387 // "[]string" (little endian)
|
||||
byteSliceID uint64 = 6874028470941080415 // "_[]byte_" (little endian)
|
||||
sliceID uint64 = 7883494272577650031 // "__slice_" (little endian)
|
||||
emptyID uint64 = 7305809155345288421 // "__empty_" (little endian)
|
||||
)
|
||||
|
||||
@@ -42,53 +44,87 @@ func hashKVs(kvs []KeyValue) uint64 {
|
||||
// hashKV returns the xxHash64 hash of kv with h as the base.
|
||||
func hashKV(h xxhash.Hash, kv KeyValue) xxhash.Hash {
|
||||
h = h.String(string(kv.Key))
|
||||
return hashValue(h, kv.Value)
|
||||
}
|
||||
|
||||
switch kv.Value.Type() {
|
||||
func hashValue(h xxhash.Hash, v Value) xxhash.Hash {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
h = h.Uint64(boolID)
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case INT64:
|
||||
h = h.Uint64(int64ID)
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case FLOAT64:
|
||||
h = h.Uint64(float64ID)
|
||||
// Assumes numeric stored with math.Float64bits.
|
||||
h = h.Uint64(kv.Value.numeric)
|
||||
h = h.Uint64(v.numeric)
|
||||
case STRING:
|
||||
h = h.Uint64(stringID)
|
||||
h = h.String(kv.Value.stringly)
|
||||
h = h.String(v.stringly)
|
||||
case BOOLSLICE:
|
||||
h = h.Uint64(boolSliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Bool(rv.Index(i).Bool())
|
||||
}
|
||||
case INT64SLICE:
|
||||
h = h.Uint64(int64SliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Int64(rv.Index(i).Int())
|
||||
}
|
||||
case FLOAT64SLICE:
|
||||
h = h.Uint64(float64SliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.Float64(rv.Index(i).Float())
|
||||
}
|
||||
case STRINGSLICE:
|
||||
h = h.Uint64(stringSliceID)
|
||||
rv := reflect.ValueOf(kv.Value.slice)
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = h.String(rv.Index(i).String())
|
||||
}
|
||||
case BYTESLICE:
|
||||
h = h.Uint64(byteSliceID)
|
||||
h = h.String(v.stringly)
|
||||
case SLICE:
|
||||
h = h.Uint64(sliceID)
|
||||
switch vals := v.slice.(type) {
|
||||
case [0]Value:
|
||||
// No values to hash, but the type identifier is still hashed above.
|
||||
case [1]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [2]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [3]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [4]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
case [5]Value:
|
||||
h = hashValueSlice(h, vals[:])
|
||||
default:
|
||||
rv := reflect.ValueOf(v.slice)
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
h = hashValue(h, rv.Index(i).Interface().(Value))
|
||||
}
|
||||
}
|
||||
case EMPTY:
|
||||
h = h.Uint64(emptyID)
|
||||
default:
|
||||
// Logging is an alternative, but using the internal logger here
|
||||
// causes an import cycle so it is not done.
|
||||
v := kv.Value.AsInterface()
|
||||
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", v)
|
||||
val := v.AsInterface()
|
||||
msg := fmt.Sprintf("unknown value type: %[1]v (%[1]T)", val)
|
||||
panic(msg)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func hashValueSlice(h xxhash.Hash, vals []Value) xxhash.Hash {
|
||||
for _, v := range vals {
|
||||
h = hashValue(h, v)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
+22
@@ -117,6 +117,28 @@ func (k Key) StringSlice(v []string) KeyValue {
|
||||
}
|
||||
}
|
||||
|
||||
// ByteSlice creates a KeyValue instance with a BYTESLICE Value.
|
||||
//
|
||||
// If creating both a key and value at the same time, use the provided
|
||||
// convenience function instead -- ByteSlice(name, value).
|
||||
func (k Key) ByteSlice(v []byte) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: ByteSliceValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Slice creates a KeyValue instance with a SLICE Value.
|
||||
//
|
||||
// If creating both a key and value at the same time, use the provided
|
||||
// convenience function instead -- Slice(name, values...).
|
||||
func (k Key) Slice(v ...Value) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: SliceValue(v...),
|
||||
}
|
||||
}
|
||||
|
||||
// Defined reports whether the key is not empty.
|
||||
func (k Key) Defined() bool {
|
||||
return len(k) != 0
|
||||
|
||||
+10
@@ -68,6 +68,16 @@ func StringSlice(k string, v []string) KeyValue {
|
||||
return Key(k).StringSlice(v)
|
||||
}
|
||||
|
||||
// ByteSlice creates a KeyValue with a BYTESLICE Value type.
|
||||
func ByteSlice(k string, v []byte) KeyValue {
|
||||
return Key(k).ByteSlice(v)
|
||||
}
|
||||
|
||||
// Slice creates a KeyValue with a SLICE Value type.
|
||||
func Slice(k string, v ...Value) KeyValue {
|
||||
return Key(k).Slice(v...)
|
||||
}
|
||||
|
||||
// Stringer creates a new key-value pair with a passed name and a string
|
||||
// value generated by the passed Stringer interface.
|
||||
func Stringer(k string, v fmt.Stringer) KeyValue {
|
||||
|
||||
+2
-2
@@ -401,7 +401,7 @@ func computeDataFixed(kvs []KeyValue) any {
|
||||
func computeDataReflect(kvs []KeyValue) any {
|
||||
at := reflect.New(reflect.ArrayOf(len(kvs), keyValueType)).Elem()
|
||||
for i, keyValue := range kvs {
|
||||
*(at.Index(i).Addr().Interface().(*KeyValue)) = keyValue
|
||||
*at.Index(i).Addr().Interface().(*KeyValue) = keyValue
|
||||
}
|
||||
return at.Interface()
|
||||
}
|
||||
@@ -415,7 +415,7 @@ func (l *Set) MarshalJSON() ([]byte, error) {
|
||||
func (l Set) MarshalLog() any {
|
||||
kvs := make(map[string]string)
|
||||
for _, kv := range l.ToSlice() {
|
||||
kvs[string(kv.Key)] = kv.Value.Emit()
|
||||
kvs[string(kv.Key)] = kv.Value.String()
|
||||
}
|
||||
return kvs
|
||||
}
|
||||
|
||||
+4
-2
@@ -17,11 +17,13 @@ func _() {
|
||||
_ = x[INT64SLICE-6]
|
||||
_ = x[FLOAT64SLICE-7]
|
||||
_ = x[STRINGSLICE-8]
|
||||
_ = x[BYTESLICE-9]
|
||||
_ = x[SLICE-10]
|
||||
}
|
||||
|
||||
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE"
|
||||
const _Type_name = "EMPTYBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICEBYTESLICESLICE"
|
||||
|
||||
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69}
|
||||
var _Type_index = [...]uint8{0, 5, 9, 14, 21, 27, 36, 46, 58, 69, 78, 83}
|
||||
|
||||
func (i Type) String() string {
|
||||
idx := int(i) - 0
|
||||
|
||||
+742
@@ -4,9 +4,14 @@
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
attribute "go.opentelemetry.io/otel/attribute/internal"
|
||||
)
|
||||
@@ -45,6 +50,10 @@ const (
|
||||
FLOAT64SLICE
|
||||
// STRINGSLICE is a slice of strings Type Value.
|
||||
STRINGSLICE
|
||||
// BYTESLICE is a slice of bytes Type Value.
|
||||
BYTESLICE
|
||||
// SLICE is a slice of Value Type values.
|
||||
SLICE
|
||||
// INVALID is used for a Value with no value set.
|
||||
//
|
||||
// Deprecated: Use EMPTY instead as an empty value is a valid value.
|
||||
@@ -134,6 +143,19 @@ func StringSliceValue(v []string) Value {
|
||||
return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
|
||||
}
|
||||
|
||||
// ByteSliceValue creates a BYTESLICE Value.
|
||||
func ByteSliceValue(v []byte) Value {
|
||||
return Value{
|
||||
vtype: BYTESLICE,
|
||||
stringly: string(v),
|
||||
}
|
||||
}
|
||||
|
||||
// SliceValue creates a SLICE Value.
|
||||
func SliceValue(v ...Value) Value {
|
||||
return Value{vtype: SLICE, slice: sliceValue(v)}
|
||||
}
|
||||
|
||||
// Type returns a type of the Value.
|
||||
func (v Value) Type() Type {
|
||||
return v.vtype
|
||||
@@ -215,6 +237,59 @@ func (v Value) asStringSlice() []string {
|
||||
return attribute.AsSlice[string](v.slice)
|
||||
}
|
||||
|
||||
// AsSlice returns the []Value value. Make sure that the Value's type is
|
||||
// SLICE.
|
||||
func (v Value) AsSlice() []Value {
|
||||
if v.vtype != SLICE {
|
||||
return nil
|
||||
}
|
||||
return v.asSlice()
|
||||
}
|
||||
|
||||
func (v Value) asSlice() []Value {
|
||||
switch vals := v.slice.(type) {
|
||||
case [0]Value:
|
||||
return []Value{}
|
||||
case [1]Value:
|
||||
return []Value{vals[0]}
|
||||
case [2]Value:
|
||||
return []Value{vals[0], vals[1]}
|
||||
case [3]Value:
|
||||
return []Value{vals[0], vals[1], vals[2]}
|
||||
case [4]Value:
|
||||
return []Value{vals[0], vals[1], vals[2], vals[3]}
|
||||
case [5]Value:
|
||||
return []Value{vals[0], vals[1], vals[2], vals[3], vals[4]}
|
||||
default:
|
||||
return asValueSliceReflect(v.slice)
|
||||
}
|
||||
}
|
||||
|
||||
func asValueSliceReflect(v any) []Value {
|
||||
rv := reflect.ValueOf(v)
|
||||
if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[Value]() {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]Value, rv.Len())
|
||||
if len(cpy) > 0 {
|
||||
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// AsByteSlice returns the bytes value. Make sure that the Value's type
|
||||
// is BYTESLICE.
|
||||
func (v Value) AsByteSlice() []byte {
|
||||
if v.vtype != BYTESLICE {
|
||||
return nil
|
||||
}
|
||||
return v.asByteSlice()
|
||||
}
|
||||
|
||||
func (v Value) asByteSlice() []byte {
|
||||
return []byte(v.stringly)
|
||||
}
|
||||
|
||||
type unknownValueType struct{}
|
||||
|
||||
// AsInterface returns Value's data as any.
|
||||
@@ -236,13 +311,60 @@ func (v Value) AsInterface() any {
|
||||
return v.stringly
|
||||
case STRINGSLICE:
|
||||
return v.asStringSlice()
|
||||
case BYTESLICE:
|
||||
return v.asByteSlice()
|
||||
case SLICE:
|
||||
return v.asSlice()
|
||||
case EMPTY:
|
||||
return nil
|
||||
}
|
||||
return unknownValueType{}
|
||||
}
|
||||
|
||||
// String returns a string representation of Value using the
|
||||
// [OpenTelemetry AnyValue representation for non-OTLP protocols] rules.
|
||||
//
|
||||
// Strings are returned as-is without JSON quoting, booleans and integers use
|
||||
// JSON literals, floating-point values use JSON numbers except that NaN and
|
||||
// ±Inf are rendered as NaN, Infinity, and -Infinity, byte slices are
|
||||
// base64-encoded, empty values are the empty string, and slices are encoded as
|
||||
// JSON arrays. String, byte, and special floating-point values inside arrays
|
||||
// are encoded as JSON strings, and empty values inside arrays are encoded as
|
||||
// null.
|
||||
//
|
||||
// [OpenTelemetry AnyValue representation for non-OTLP protocols]: https://opentelemetry.io/docs/specs/otel/common/#anyvalue-representation-for-non-otlp-protocols
|
||||
func (v Value) String() string {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
return strconv.FormatBool(v.AsBool())
|
||||
case BOOLSLICE:
|
||||
return formatBoolSliceValue(v.slice)
|
||||
case INT64:
|
||||
return strconv.FormatInt(v.AsInt64(), 10)
|
||||
case INT64SLICE:
|
||||
return formatInt64SliceValue(v.slice)
|
||||
case FLOAT64:
|
||||
return formatFloat64(v.AsFloat64())
|
||||
case FLOAT64SLICE:
|
||||
return formatFloat64SliceValue(v.slice)
|
||||
case STRING:
|
||||
return v.stringly
|
||||
case STRINGSLICE:
|
||||
return formatStringSliceValue(v.slice)
|
||||
case BYTESLICE:
|
||||
return formatByteSlice(v.stringly)
|
||||
case SLICE:
|
||||
return formatValueSliceValue(v.slice)
|
||||
case EMPTY:
|
||||
return ""
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Emit returns a string representation of Value's data.
|
||||
//
|
||||
// Deprecated: Use [Value.String] instead.
|
||||
func (v Value) Emit() string {
|
||||
switch v.Type() {
|
||||
case BOOLSLICE:
|
||||
@@ -273,6 +395,10 @@ func (v Value) Emit() string {
|
||||
return string(j)
|
||||
case STRING:
|
||||
return v.stringly
|
||||
case BYTESLICE:
|
||||
return formatByteSlice(v.stringly)
|
||||
case SLICE:
|
||||
return formatValueSliceValue(v.slice)
|
||||
case EMPTY:
|
||||
return ""
|
||||
default:
|
||||
@@ -280,6 +406,622 @@ func (v Value) Emit() string {
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
jsonArrayBracketsLen = len("[]")
|
||||
boolArrayElemMaxLen = len("false")
|
||||
int64ArrayElemMaxLen = len("-9223372036854775808")
|
||||
float64ArrayElemMaxLen = len("-1.7976931348623157e+308")
|
||||
commaLen = len(",")
|
||||
)
|
||||
|
||||
func sliceValue(v []Value) any {
|
||||
switch len(v) {
|
||||
case 0:
|
||||
return [0]Value{}
|
||||
case 1:
|
||||
return [1]Value{v[0]}
|
||||
case 2:
|
||||
return [2]Value{v[0], v[1]}
|
||||
case 3:
|
||||
return [3]Value{v[0], v[1], v[2]}
|
||||
case 4:
|
||||
return [4]Value{v[0], v[1], v[2], v[3]}
|
||||
case 5:
|
||||
return [5]Value{v[0], v[1], v[2], v[3], v[4]}
|
||||
default:
|
||||
return sliceValueReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func sliceValueReflect(v []Value) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[Value]())).Elem()
|
||||
reflect.Copy(cp, reflect.ValueOf(v))
|
||||
return cp.Interface()
|
||||
}
|
||||
|
||||
func formatBoolSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]bool:
|
||||
return "[]"
|
||||
case [1]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
case [2]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
case [3]bool:
|
||||
return formatBoolSlice(vals[:])
|
||||
default:
|
||||
return formatBoolSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatBoolSlice(vals []bool) string {
|
||||
var b strings.Builder
|
||||
appendBoolSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatBoolSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendBoolSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendBoolSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]bool:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
case [2]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
case [3]bool:
|
||||
appendBoolSlice(dst, vals[:])
|
||||
default:
|
||||
appendBoolSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendBoolSlice(dst *strings.Builder, vals []bool) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(boolArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
if val {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendBoolSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(boolArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
if rv.Index(i).Bool() {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatInt64SliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]int64:
|
||||
return "[]"
|
||||
case [1]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
case [2]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
case [3]int64:
|
||||
return formatInt64Slice(vals[:])
|
||||
default:
|
||||
return formatInt64SliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatInt64Slice(vals []int64) string {
|
||||
var b strings.Builder
|
||||
appendInt64Slice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatInt64SliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendInt64SliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendInt64SliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]int64:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
case [2]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
case [3]int64:
|
||||
appendInt64Slice(dst, vals[:])
|
||||
default:
|
||||
appendInt64SliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendInt64Slice(dst *strings.Builder, vals []int64) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(int64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var buf [int64ArrayElemMaxLen]byte
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
out := strconv.AppendInt(buf[:0], val, 10)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendInt64SliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(int64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var scratch [int64ArrayElemMaxLen]byte
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
out := strconv.AppendInt(scratch[:0], rv.Index(i).Int(), 10)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatFloat64(v float64) string {
|
||||
switch {
|
||||
case math.IsNaN(v):
|
||||
return "NaN"
|
||||
case math.IsInf(v, 1):
|
||||
return "Infinity"
|
||||
case math.IsInf(v, -1):
|
||||
return "-Infinity"
|
||||
default:
|
||||
return strconv.FormatFloat(v, 'g', -1, 64)
|
||||
}
|
||||
}
|
||||
|
||||
func formatFloat64SliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]float64:
|
||||
return "[]"
|
||||
case [1]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
case [2]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
case [3]float64:
|
||||
return formatFloat64Slice(vals[:])
|
||||
default:
|
||||
return formatFloat64SliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatFloat64Slice(vals []float64) string {
|
||||
var b strings.Builder
|
||||
appendFloat64Slice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatFloat64SliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendFloat64SliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendFloat64SliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]float64:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
case [2]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
case [3]float64:
|
||||
appendFloat64Slice(dst, vals[:])
|
||||
default:
|
||||
appendFloat64SliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendFloat64Slice(dst *strings.Builder, vals []float64) {
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*(float64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var buf [float64ArrayElemMaxLen]byte
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
_, _ = dst.WriteString(`"NaN"`)
|
||||
case math.IsInf(val, 1):
|
||||
_, _ = dst.WriteString(`"Infinity"`)
|
||||
case math.IsInf(val, -1):
|
||||
_, _ = dst.WriteString(`"-Infinity"`)
|
||||
default:
|
||||
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendFloat64SliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*(float64ArrayElemMaxLen+commaLen))
|
||||
_ = dst.WriteByte('[')
|
||||
|
||||
var scratch [float64ArrayElemMaxLen]byte
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
val := rv.Index(i).Float()
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
_, _ = dst.WriteString(`"NaN"`)
|
||||
case math.IsInf(val, 1):
|
||||
_, _ = dst.WriteString(`"Infinity"`)
|
||||
case math.IsInf(val, -1):
|
||||
_, _ = dst.WriteString(`"-Infinity"`)
|
||||
default:
|
||||
out := strconv.AppendFloat(scratch[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatStringSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]string:
|
||||
return "[]"
|
||||
case [1]string:
|
||||
return formatStringSlice(vals[:])
|
||||
case [2]string:
|
||||
return formatStringSlice(vals[:])
|
||||
case [3]string:
|
||||
return formatStringSlice(vals[:])
|
||||
default:
|
||||
return formatStringSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatStringSlice(vals []string) string {
|
||||
var b strings.Builder
|
||||
appendStringSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatStringSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendStringSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendStringSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]string:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
case [2]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
case [3]string:
|
||||
appendStringSlice(dst, vals[:])
|
||||
default:
|
||||
appendStringSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendStringSlice(dst *strings.Builder, vals []string) {
|
||||
size := jsonArrayBracketsLen
|
||||
for _, val := range vals {
|
||||
size += len(val) + commaLen + 2 // Account for JSON string quotes and comma.
|
||||
}
|
||||
|
||||
dst.Grow(size)
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONString(dst, val)
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendStringSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
size := jsonArrayBracketsLen
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
size += len(rv.Index(i).String()) + commaLen + 2 // Account for JSON string quotes and comma.
|
||||
}
|
||||
|
||||
dst.Grow(size)
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONString(dst, rv.Index(i).String())
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func formatByteSlice(v string) string {
|
||||
var b strings.Builder
|
||||
appendBase64(&b, v)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatValueSliceValue(v any) string {
|
||||
switch vals := v.(type) {
|
||||
case [0]Value:
|
||||
return "[]"
|
||||
case [1]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [2]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [3]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [4]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
case [5]Value:
|
||||
return formatValueSlice(vals[:])
|
||||
default:
|
||||
return formatValueSliceReflect(v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatValueSlice(vals []Value) string {
|
||||
var b strings.Builder
|
||||
appendValueSlice(&b, vals)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func formatValueSliceReflect(v any) string {
|
||||
var b strings.Builder
|
||||
appendValueSliceReflect(&b, reflect.ValueOf(v))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func appendValueSliceValue(dst *strings.Builder, v any) {
|
||||
switch vals := v.(type) {
|
||||
case [0]Value:
|
||||
_, _ = dst.WriteString("[]")
|
||||
case [1]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [2]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [3]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [4]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
case [5]Value:
|
||||
appendValueSlice(dst, vals[:])
|
||||
default:
|
||||
appendValueSliceReflect(dst, reflect.ValueOf(v))
|
||||
}
|
||||
}
|
||||
|
||||
func appendValueSlice(dst *strings.Builder, vals []Value) {
|
||||
// Estimate 10 bytes per value for small values and commas.
|
||||
dst.Grow(jsonArrayBracketsLen + len(vals)*commaLen + len(vals)*10)
|
||||
_ = dst.WriteByte('[')
|
||||
for i, val := range vals {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONValue(dst, val)
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendValueSliceReflect(dst *strings.Builder, rv reflect.Value) {
|
||||
// Estimate 10 bytes per value for small values and commas.
|
||||
dst.Grow(jsonArrayBracketsLen + rv.Len()*commaLen + rv.Len()*10)
|
||||
_ = dst.WriteByte('[')
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
if i > 0 {
|
||||
_ = dst.WriteByte(',')
|
||||
}
|
||||
appendJSONValue(dst, rv.Index(i).Interface().(Value))
|
||||
}
|
||||
_ = dst.WriteByte(']')
|
||||
}
|
||||
|
||||
func appendJSONValue(dst *strings.Builder, v Value) {
|
||||
switch v.Type() {
|
||||
case BOOL:
|
||||
if v.AsBool() {
|
||||
_, _ = dst.WriteString("true")
|
||||
} else {
|
||||
_, _ = dst.WriteString("false")
|
||||
}
|
||||
case BOOLSLICE:
|
||||
appendBoolSliceValue(dst, v.slice)
|
||||
case INT64:
|
||||
var buf [int64ArrayElemMaxLen]byte
|
||||
out := strconv.AppendInt(buf[:0], v.AsInt64(), 10)
|
||||
_, _ = dst.Write(out)
|
||||
case INT64SLICE:
|
||||
appendInt64SliceValue(dst, v.slice)
|
||||
case FLOAT64:
|
||||
val := v.AsFloat64()
|
||||
switch {
|
||||
case math.IsNaN(val):
|
||||
appendJSONString(dst, "NaN")
|
||||
case math.IsInf(val, 1):
|
||||
appendJSONString(dst, "Infinity")
|
||||
case math.IsInf(val, -1):
|
||||
appendJSONString(dst, "-Infinity")
|
||||
default:
|
||||
var buf [float64ArrayElemMaxLen]byte
|
||||
out := strconv.AppendFloat(buf[:0], val, 'g', -1, 64)
|
||||
_, _ = dst.Write(out)
|
||||
}
|
||||
case FLOAT64SLICE:
|
||||
appendFloat64SliceValue(dst, v.slice)
|
||||
case STRING:
|
||||
appendJSONString(dst, v.stringly)
|
||||
case STRINGSLICE:
|
||||
appendStringSliceValue(dst, v.slice)
|
||||
case BYTESLICE:
|
||||
_ = dst.WriteByte('"')
|
||||
appendBase64(dst, v.stringly)
|
||||
_ = dst.WriteByte('"')
|
||||
case SLICE:
|
||||
appendValueSliceValue(dst, v.slice)
|
||||
case EMPTY:
|
||||
_, _ = dst.WriteString("null")
|
||||
default:
|
||||
appendJSONString(dst, "unknown")
|
||||
}
|
||||
}
|
||||
|
||||
// appendJSONString appends s to dst as a JSON string literal.
|
||||
//
|
||||
// This is adapted from the Go standard library's encoding/json
|
||||
// [appendString implementation]. It keeps the same escaping behavior we need
|
||||
// here, but writes directly into a strings.Builder and intentionally does not
|
||||
// apply HTML escaping because the OpenTelemetry non-OTLP AnyValue representation
|
||||
// only requires JSON array string encoding. We inline this instead of using
|
||||
// encoding/json so slice formatting avoids allocations and reflection.
|
||||
//
|
||||
// [appendString implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/json/encode.go#L998-L1064
|
||||
func appendJSONString(dst *strings.Builder, s string) {
|
||||
const hex = "0123456789abcdef" // For escaping bytes to hex.
|
||||
|
||||
_ = dst.WriteByte('"')
|
||||
start := 0
|
||||
|
||||
for i := 0; i < len(s); {
|
||||
if c := s[i]; c < utf8.RuneSelf {
|
||||
if c >= 0x20 && c != '\\' && c != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '\\', '"':
|
||||
_ = dst.WriteByte('\\')
|
||||
_ = dst.WriteByte(c)
|
||||
case '\b':
|
||||
_, _ = dst.WriteString(`\b`)
|
||||
case '\f':
|
||||
_, _ = dst.WriteString(`\f`)
|
||||
case '\n':
|
||||
_, _ = dst.WriteString(`\n`)
|
||||
case '\r':
|
||||
_, _ = dst.WriteString(`\r`)
|
||||
case '\t':
|
||||
_, _ = dst.WriteString(`\t`)
|
||||
default:
|
||||
_, _ = dst.WriteString(`\u00`)
|
||||
_ = dst.WriteByte(hex[c>>4])
|
||||
_ = dst.WriteByte(hex[c&0x0f])
|
||||
}
|
||||
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
// Match encoding/json by replacing invalid UTF-8 with U+FFFD.
|
||||
_, _ = dst.WriteString(`\ufffd`)
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\u2028' || r == '\u2029' {
|
||||
if start < i {
|
||||
_, _ = dst.WriteString(s[start:i])
|
||||
}
|
||||
// Escape JSONP-sensitive separators unconditionally, like encoding/json.
|
||||
_, _ = dst.WriteString(`\u202`)
|
||||
_ = dst.WriteByte(hex[r&0x0f])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
|
||||
i += size
|
||||
}
|
||||
|
||||
if start < len(s) {
|
||||
_, _ = dst.WriteString(s[start:])
|
||||
}
|
||||
_ = dst.WriteByte('"')
|
||||
}
|
||||
|
||||
// This is adapted from the Go standard library's encoding/base64
|
||||
// [Encoding.Encode implementation]. It keeps the same encoding behavior we need
|
||||
// here, but writes directly into a strings.Builder. We inline this instead of using
|
||||
// encoding/base64 to avoid allocations.
|
||||
//
|
||||
// [Encoding.Encode implementation]: https://github.com/golang/go/blob/3b5954c6349d31465dca409b45ab6597e0942d9f/src/encoding/base64/base64.go#L139-L189
|
||||
func appendBase64(dst *strings.Builder, s string) {
|
||||
const encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
dst.Grow(base64.StdEncoding.EncodedLen(len(s)))
|
||||
|
||||
i := 0
|
||||
for ; i+2 < len(s); i += 3 {
|
||||
n := uint32(s[i])<<16 | uint32(s[i+1])<<8 | uint32(s[i+2])
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>6&0x3f])
|
||||
_ = dst.WriteByte(encode[n&0x3f])
|
||||
}
|
||||
|
||||
switch len(s) - i {
|
||||
case 1:
|
||||
n := uint32(s[i]) << 16
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte('=')
|
||||
_ = dst.WriteByte('=')
|
||||
case 2:
|
||||
n := uint32(s[i])<<16 | uint32(s[i+1])<<8
|
||||
_ = dst.WriteByte(encode[n>>18&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>12&0x3f])
|
||||
_ = dst.WriteByte(encode[n>>6&0x3f])
|
||||
_ = dst.WriteByte('=')
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON returns the JSON encoding of the Value.
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
var jsonVal struct {
|
||||
|
||||
+26
-4
@@ -14,6 +14,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxParseErrors = 5
|
||||
|
||||
// W3C Baggage specification limits.
|
||||
// https://www.w3.org/TR/baggage/#limits
|
||||
maxMembers = 64
|
||||
maxBytesPerBaggageString = 8192
|
||||
|
||||
@@ -493,9 +497,15 @@ func New(members ...Member) (Baggage, error) {
|
||||
// from the W3C Baggage specification which allows duplicate list-members, but
|
||||
// conforms to the OpenTelemetry Baggage specification.
|
||||
//
|
||||
// If the baggage-string exceeds the maximum allowed members (64) or bytes
|
||||
// (8192), members are dropped until the limits are satisfied and an error is
|
||||
// returned along with the partial result.
|
||||
// If the raw baggage-string exceeds the maximum allowed bytes (8192), an
|
||||
// empty Baggage and an error are returned.
|
||||
//
|
||||
// Otherwise, members are parsed left-to-right and accumulated until one of
|
||||
// the following conditions is reached, at which point parsing stops and an
|
||||
// error is returned alongside the partial result:
|
||||
// - accepting the next member would cause the encoded baggage to exceed
|
||||
// 8192 bytes, or
|
||||
// - the baggage already contains 64 distinct keys.
|
||||
//
|
||||
// Invalid members are skipped and the error is returned along with the
|
||||
// partial result containing the valid members.
|
||||
@@ -504,9 +514,14 @@ func Parse(bStr string) (Baggage, error) {
|
||||
return Baggage{}, nil
|
||||
}
|
||||
|
||||
if n := len(bStr); n > maxBytesPerBaggageString {
|
||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||
}
|
||||
|
||||
b := make(baggage.List)
|
||||
sizes := make(map[string]int) // Track per-key byte sizes
|
||||
var totalBytes int
|
||||
var parseErrors int
|
||||
var truncateErr error
|
||||
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
|
||||
// Check member count limit.
|
||||
@@ -517,7 +532,10 @@ func Parse(bStr string) (Baggage, error) {
|
||||
|
||||
m, err := parseMember(memberStr)
|
||||
if err != nil {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
parseErrors++
|
||||
if parseErrors <= maxParseErrors {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
}
|
||||
continue // skip invalid member, keep processing
|
||||
}
|
||||
|
||||
@@ -553,6 +571,10 @@ func Parse(bStr string) (Baggage, error) {
|
||||
totalBytes = newTotalBytes
|
||||
}
|
||||
|
||||
if dropped := parseErrors - maxParseErrors; dropped > 0 {
|
||||
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more invalid member(s)", dropped))
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return Baggage{}, truncateErr
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# This is a renovate-friendly source of Docker images.
|
||||
FROM python:3.13.6-slim-bullseye@sha256:e98b521460ee75bca92175c16247bdf7275637a8faaeb2bcfa19d879ae5c4b9a AS python
|
||||
FROM otel/weaver:v0.22.1@sha256:33ae522ae4b71c1c562563c1d81f46aa0f79f088a0873199143a1f11ac30e5c9 AS weaver
|
||||
FROM otel/weaver:v0.23.0@sha256:7984ecb55b859eb3034ae9d836c4eeda137e2bdd0873b7ba2bb6c3d24d6ff457 AS weaver
|
||||
FROM avtodev/markdown-lint:v1@sha256:6aeedc2f49138ce7a1cd0adffc1b1c0321b841dc2102408967d9301c031949ee AS markdown
|
||||
|
||||
+9
@@ -51,6 +51,9 @@ type Float64ObservableCounterConfig struct {
|
||||
func NewFloat64ObservableCounterConfig(opts ...Float64ObservableCounterOption) Float64ObservableCounterConfig {
|
||||
var config Float64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -111,6 +114,9 @@ func NewFloat64ObservableUpDownCounterConfig(
|
||||
) Float64ObservableUpDownCounterConfig {
|
||||
var config Float64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -168,6 +174,9 @@ type Float64ObservableGaugeConfig struct {
|
||||
func NewFloat64ObservableGaugeConfig(opts ...Float64ObservableGaugeOption) Float64ObservableGaugeConfig {
|
||||
var config Float64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+9
@@ -50,6 +50,9 @@ type Int64ObservableCounterConfig struct {
|
||||
func NewInt64ObservableCounterConfig(opts ...Int64ObservableCounterOption) Int64ObservableCounterConfig {
|
||||
var config Int64ObservableCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -110,6 +113,9 @@ func NewInt64ObservableUpDownCounterConfig(
|
||||
) Int64ObservableUpDownCounterConfig {
|
||||
var config Int64ObservableUpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableUpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -167,6 +173,9 @@ type Int64ObservableGaugeConfig struct {
|
||||
func NewInt64ObservableGaugeConfig(opts ...Int64ObservableGaugeOption) Int64ObservableGaugeConfig {
|
||||
var config Int64ObservableGaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64ObservableGauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+7
@@ -42,11 +42,18 @@ type MeterOption interface {
|
||||
applyMeter(MeterConfig) MeterConfig
|
||||
}
|
||||
|
||||
type experimentalOption interface {
|
||||
Experimental()
|
||||
}
|
||||
|
||||
// NewMeterConfig creates a new MeterConfig and applies
|
||||
// all the given options.
|
||||
func NewMeterConfig(opts ...MeterOption) MeterConfig {
|
||||
var config MeterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyMeter(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+39
-12
@@ -24,10 +24,10 @@ all instruments fall into two overlapping logical categories: asynchronous or
|
||||
synchronous, and int64 or float64.
|
||||
|
||||
All synchronous instruments ([Int64Counter], [Int64UpDownCounter],
|
||||
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
|
||||
[Float64Histogram]) are used to measure the operation and performance of source
|
||||
code during the source code execution. These instruments only make measurements
|
||||
when the source code they instrument is run.
|
||||
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
|
||||
[Float64Histogram], and [Float64Gauge]) are used to measure the operation and
|
||||
performance of source code during the source code execution. These instruments
|
||||
only make measurements when the source code they instrument is run.
|
||||
|
||||
All asynchronous instruments ([Int64ObservableCounter],
|
||||
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
|
||||
@@ -50,9 +50,11 @@ incrementally increase in value. UpDownCounters ([Int64UpDownCounter],
|
||||
values that can increase and decrease. When more information needs to be
|
||||
conveyed about all the synchronous measurements made during a collection cycle,
|
||||
a Histogram ([Int64Histogram] and [Float64Histogram]) should be used. Finally,
|
||||
when just the most recent measurement needs to be conveyed about an
|
||||
asynchronous measurement, a Gauge ([Int64ObservableGauge] and
|
||||
[Float64ObservableGauge]) should be used.
|
||||
when just the most recent measurement needs to be conveyed, a Gauge
|
||||
([Int64Gauge], [Float64Gauge], [Int64ObservableGauge], and
|
||||
[Float64ObservableGauge]) should be used: the synchronous variants record an
|
||||
instantaneous value at a specific point in code, while the observable variants
|
||||
sample the value via a callback once per collection cycle.
|
||||
|
||||
See the [OpenTelemetry documentation] for more information about instruments
|
||||
and their intended use.
|
||||
@@ -80,11 +82,11 @@ Measurements are made by recording values and information about the values with
|
||||
an instrument. How these measurements are recorded depends on the instrument.
|
||||
|
||||
Measurements for synchronous instruments ([Int64Counter], [Int64UpDownCounter],
|
||||
[Int64Histogram], [Float64Counter], [Float64UpDownCounter], and
|
||||
[Float64Histogram]) are recorded using the instrument methods directly. All
|
||||
counter instruments have an Add method that is used to measure an increment
|
||||
value, and all histogram instruments have a Record method to measure a data
|
||||
point.
|
||||
[Int64Histogram], [Int64Gauge], [Float64Counter], [Float64UpDownCounter],
|
||||
[Float64Histogram], and [Float64Gauge]) are recorded using the instrument
|
||||
methods directly. All counter instruments have an Add method that is used to
|
||||
measure an increment value, and all histogram and synchronous gauge
|
||||
instruments have a Record method to measure a data point.
|
||||
|
||||
Asynchronous instruments ([Int64ObservableCounter],
|
||||
[Int64ObservableUpDownCounter], [Int64ObservableGauge],
|
||||
@@ -107,6 +109,31 @@ respectively):
|
||||
If the criteria are not met, use the RegisterCallback method of the [Meter] that
|
||||
created the instrument to register a [Callback].
|
||||
|
||||
# Avoiding Expensive Computations
|
||||
|
||||
All synchronous instruments provide an Enabled method that reports whether the
|
||||
instrument will process measurements for the given context. When no SDK is
|
||||
registered or the instrument is otherwise disabled, Enabled returns false. This
|
||||
can be used to avoid expensive measurement work when a measurement will not be
|
||||
recorded:
|
||||
|
||||
if counter.Enabled(ctx) {
|
||||
counter.Add(ctx, 1, metric.WithAttributes(expensiveAttributes()...))
|
||||
}
|
||||
|
||||
This is especially valuable when computing attributes is expensive.
|
||||
[WithAttributes] performs non-trivial work on every call to build an
|
||||
[attribute.Set] from the provided attributes, and that work is wasted if the
|
||||
measurement is not recorded.
|
||||
|
||||
For performance sensitive code where the same attribute set is used repeatedly,
|
||||
prefer [WithAttributeSet]. It accepts a pre-built [attribute.Set], letting you
|
||||
pay the construction cost once and reuse it across many measurements:
|
||||
|
||||
attrs := attribute.NewSet(attribute.String("key", "val"))
|
||||
// ... later, on each call:
|
||||
counter.Add(ctx, 1, metric.WithAttributeSet(attrs))
|
||||
|
||||
# API Implementations
|
||||
|
||||
This package does not conform to the standard Go versioning policy, all of its
|
||||
|
||||
+33
-6
@@ -3,7 +3,9 @@
|
||||
|
||||
package metric // import "go.opentelemetry.io/otel/metric"
|
||||
|
||||
import "go.opentelemetry.io/otel/attribute"
|
||||
import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
// Observable is used as a grouping mechanism for all instruments that are
|
||||
// updated within a Callback.
|
||||
@@ -228,6 +230,9 @@ type AddConfig struct {
|
||||
func NewAddConfig(opts []AddOption) AddConfig {
|
||||
config := AddConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyAdd(config)
|
||||
}
|
||||
return config
|
||||
@@ -253,6 +258,9 @@ type RecordConfig struct {
|
||||
func NewRecordConfig(opts []RecordOption) RecordConfig {
|
||||
config := RecordConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyRecord(config)
|
||||
}
|
||||
return config
|
||||
@@ -278,6 +286,9 @@ type ObserveConfig struct {
|
||||
func NewObserveConfig(opts []ObserveOption) ObserveConfig {
|
||||
config := ObserveConfig{attrs: *attribute.EmptySet()}
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyObserve(config)
|
||||
}
|
||||
return config
|
||||
@@ -299,6 +310,10 @@ type attrOpt struct {
|
||||
set attribute.Set
|
||||
}
|
||||
|
||||
func (o *attrOpt) Set(set attribute.Set) {
|
||||
o.set = set
|
||||
}
|
||||
|
||||
// mergeSets returns the union of keys between a and b. Any duplicate keys will
|
||||
// use the value associated with b.
|
||||
func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
@@ -311,7 +326,7 @@ func mergeSets(a, b attribute.Set) attribute.Set {
|
||||
return attribute.NewSet(merged...)
|
||||
}
|
||||
|
||||
func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
func (o *attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -322,7 +337,7 @@ func (o attrOpt) applyAdd(c AddConfig) AddConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
func (o *attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -333,7 +348,7 @@ func (o attrOpt) applyRecord(c RecordConfig) RecordConfig {
|
||||
return c
|
||||
}
|
||||
|
||||
func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
func (o *attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
switch {
|
||||
case o.set.Len() == 0:
|
||||
case c.attrs.Len() == 0:
|
||||
@@ -350,8 +365,14 @@ func (o attrOpt) applyObserve(c ObserveConfig) ObserveConfig {
|
||||
// If multiple WithAttributeSet or WithAttributes options are passed the
|
||||
// attributes will be merged together in the order they are passed. Attributes
|
||||
// with duplicate keys will use the last value passed.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][attribute.Set], which can be
|
||||
// used to replace the option's attribute set and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
return attrOpt{set: attributes}
|
||||
return &attrOpt{set: attributes}
|
||||
}
|
||||
|
||||
// WithAttributes converts attributes into an attribute Set and sets the Set to
|
||||
@@ -369,8 +390,14 @@ func WithAttributeSet(attributes attribute.Set) MeasurementOption {
|
||||
//
|
||||
// See [WithAttributeSet] for information about how multiple WithAttributes are
|
||||
// merged.
|
||||
//
|
||||
// Experimental: The returned option may implement
|
||||
// [go.opentelemetry.io/otel/metric/x.Settable][[]attribute.KeyValue], which can be
|
||||
// used to replace the option's attributes and reuse the option without
|
||||
// additional allocations. This behavior is experimental and may be changed or
|
||||
// removed in a future release without notice.
|
||||
func WithAttributes(attributes ...attribute.KeyValue) MeasurementOption {
|
||||
cp := make([]attribute.KeyValue, len(attributes))
|
||||
copy(cp, attributes)
|
||||
return attrOpt{set: attribute.NewSet(cp...)}
|
||||
return &attrOpt{set: attribute.NewSet(cp...)}
|
||||
}
|
||||
|
||||
+12
@@ -51,6 +51,9 @@ type Float64CounterConfig struct {
|
||||
func NewFloat64CounterConfig(opts ...Float64CounterOption) Float64CounterConfig {
|
||||
var config Float64CounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Counter(config)
|
||||
}
|
||||
return config
|
||||
@@ -116,6 +119,9 @@ type Float64UpDownCounterConfig struct {
|
||||
func NewFloat64UpDownCounterConfig(opts ...Float64UpDownCounterOption) Float64UpDownCounterConfig {
|
||||
var config Float64UpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64UpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -182,6 +188,9 @@ type Float64HistogramConfig struct {
|
||||
func NewFloat64HistogramConfig(opts ...Float64HistogramOption) Float64HistogramConfig {
|
||||
var config Float64HistogramConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Histogram(config)
|
||||
}
|
||||
return config
|
||||
@@ -251,6 +260,9 @@ type Float64GaugeConfig struct {
|
||||
func NewFloat64GaugeConfig(opts ...Float64GaugeOption) Float64GaugeConfig {
|
||||
var config Float64GaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyFloat64Gauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+12
@@ -51,6 +51,9 @@ type Int64CounterConfig struct {
|
||||
func NewInt64CounterConfig(opts ...Int64CounterOption) Int64CounterConfig {
|
||||
var config Int64CounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Counter(config)
|
||||
}
|
||||
return config
|
||||
@@ -116,6 +119,9 @@ type Int64UpDownCounterConfig struct {
|
||||
func NewInt64UpDownCounterConfig(opts ...Int64UpDownCounterOption) Int64UpDownCounterConfig {
|
||||
var config Int64UpDownCounterConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64UpDownCounter(config)
|
||||
}
|
||||
return config
|
||||
@@ -182,6 +188,9 @@ type Int64HistogramConfig struct {
|
||||
func NewInt64HistogramConfig(opts ...Int64HistogramOption) Int64HistogramConfig {
|
||||
var config Int64HistogramConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Histogram(config)
|
||||
}
|
||||
return config
|
||||
@@ -251,6 +260,9 @@ type Int64GaugeConfig struct {
|
||||
func NewInt64GaugeConfig(opts ...Int64GaugeOption) Int64GaugeConfig {
|
||||
var config Int64GaugeConfig
|
||||
for _, o := range opts {
|
||||
if _, ok := o.(experimentalOption); ok {
|
||||
continue
|
||||
}
|
||||
config = o.applyInt64Gauge(config)
|
||||
}
|
||||
return config
|
||||
|
||||
+60
-12
@@ -5,6 +5,9 @@ package propagation // import "go.opentelemetry.io/otel/propagation"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/baggage"
|
||||
"go.opentelemetry.io/otel/internal/errorhandler"
|
||||
@@ -13,11 +16,18 @@ import (
|
||||
const (
|
||||
baggageHeader = "baggage"
|
||||
|
||||
maxParseErrors = 5
|
||||
|
||||
// W3C Baggage specification limits.
|
||||
// https://www.w3.org/TR/baggage/#limits
|
||||
maxMembers = 64
|
||||
maxMembers = 64
|
||||
maxBytesPerBaggageString = 8192
|
||||
)
|
||||
|
||||
// handleExtractErrOnce limits error reporting for attacker-controlled baggage headers
|
||||
// to one process-wide emission, preventing repeated extraction from flooding logs.
|
||||
var handleExtractErrOnce sync.Once
|
||||
|
||||
// Baggage is a propagator that supports the W3C Baggage format.
|
||||
//
|
||||
// This propagates user-defined baggage associated with a trace. The complete
|
||||
@@ -57,7 +67,9 @@ func extractSingleBaggage(parent context.Context, carrier TextMapCarrier) contex
|
||||
|
||||
bag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
handleExtractErrOnce.Do(func() {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
})
|
||||
}
|
||||
if bag.Len() == 0 {
|
||||
return parent
|
||||
@@ -72,24 +84,60 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
|
||||
}
|
||||
|
||||
var members []baggage.Member
|
||||
for _, bStr := range bVals {
|
||||
currBag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
var totalBytes int
|
||||
var parseErrors int
|
||||
var truncateErr error
|
||||
for i, bStr := range bVals {
|
||||
if i > 0 {
|
||||
totalBytes++ // comma separator between combined header values
|
||||
}
|
||||
if currBag.Len() == 0 {
|
||||
continue
|
||||
totalBytes += len(bStr)
|
||||
if totalBytes > maxBytesPerBaggageString {
|
||||
// Per the W3C Baggage spec, the byte limit applies to the
|
||||
// combination of all baggage headers, not each header
|
||||
// individually. Mirror the single-header behavior of
|
||||
// reporting the error and returning the parent context
|
||||
// with no baggage attached.
|
||||
handleExtractErrOnce.Do(func() {
|
||||
errorhandler.GetErrorHandler().Handle(fmt.Errorf(
|
||||
"baggage: aggregate header size %d exceeds %d byte limit",
|
||||
totalBytes,
|
||||
maxBytesPerBaggageString,
|
||||
))
|
||||
})
|
||||
return parent
|
||||
}
|
||||
members = append(members, currBag.Members()...)
|
||||
if len(members) >= maxMembers {
|
||||
break
|
||||
|
||||
// If members exceed the limit, stop parsing baggage.
|
||||
if len(members) <= maxMembers {
|
||||
currBag, err := baggage.Parse(bStr)
|
||||
if err != nil {
|
||||
parseErrors++
|
||||
if parseErrors <= maxParseErrors {
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
}
|
||||
}
|
||||
if currBag.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
members = append(members, currBag.Members()...)
|
||||
}
|
||||
}
|
||||
|
||||
if dropped := parseErrors - maxParseErrors; dropped > 0 {
|
||||
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more error(s)", dropped))
|
||||
}
|
||||
|
||||
b, err := baggage.New(members...)
|
||||
if err != nil {
|
||||
errorhandler.GetErrorHandler().Handle(err)
|
||||
truncateErr = errors.Join(truncateErr, err)
|
||||
}
|
||||
if truncateErr != nil {
|
||||
handleExtractErrOnce.Do(func() {
|
||||
errorhandler.GetErrorHandler().Handle(truncateErr)
|
||||
})
|
||||
}
|
||||
|
||||
if b.Len() == 0 {
|
||||
return parent
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user