mirror of
https://github.com/luxfi/node.git
synced 2026-07-29 16:46:33 +00:00
Compare commits
227
Commits
main
...
red/lp023-round6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a0d837c81 | ||
|
|
3846839602 | ||
|
|
c83ccad39a | ||
|
|
e2640fb373 | ||
|
|
5ec74286f4 | ||
|
|
83959735b3 | ||
|
|
c623c4c426 | ||
|
|
292a9a5ffa | ||
|
|
7f792249a6 | ||
|
|
51bd46dd00 | ||
|
|
56200f2d8e | ||
|
|
25ecd21574 | ||
|
|
6eabb2660e | ||
|
|
a14f42eb05 | ||
|
|
6856b8cf50 | ||
|
|
725023e777 | ||
|
|
a46833886a | ||
|
|
bf0e0ee87c | ||
|
|
0bffcfeeb3 | ||
|
|
8f3875aba0 | ||
|
|
c3a4a591e4 | ||
|
|
f9801ecd5b | ||
|
|
5efb9ce77d | ||
|
|
00349fdc92 | ||
|
|
08b638d72c | ||
|
|
c4cc906f45 | ||
|
|
5ec66e7f76 | ||
|
|
59423a56e8 | ||
|
|
82dbdbadee | ||
|
|
7285e1e554 | ||
|
|
ba4084d6d5 | ||
|
|
3332d05eaa | ||
|
|
729b4e578a | ||
|
|
946e6422f7 | ||
|
|
73b05ef68d | ||
|
|
4ec0931501 | ||
|
|
384425c66b | ||
|
|
9ca3a728ef | ||
|
|
b4d441911f | ||
|
|
f5130223c6 | ||
|
|
f4407b3497 | ||
|
|
3b8fa5911a | ||
|
|
9df75e9ac7 | ||
|
|
39249c4362 | ||
|
|
b58aaa0682 | ||
|
|
596de8be35 | ||
|
|
ddd0053774 | ||
|
|
5100801f43 | ||
|
|
c5c03aef44 | ||
|
|
ac0b9c2ca8 | ||
|
|
1ee0ecc807 | ||
|
|
8c96e85d03 | ||
|
|
43f010ea9f | ||
|
|
98d1eeffcd | ||
|
|
1aaded33db | ||
|
|
1a42b5df1c | ||
|
|
13a48d0c28 | ||
|
|
da79e8d484 | ||
|
|
303242ea5a | ||
|
|
1d84e03e5b | ||
|
|
3631ff5dc2 | ||
|
|
28e9fe0032 | ||
|
|
6258af7af9 | ||
|
|
f0733b749c | ||
|
|
5c164e84c7 | ||
|
|
6af468742c | ||
|
|
2220065985 | ||
|
|
3fcc6085d5 | ||
|
|
e9ab022ca7 | ||
|
|
7003d69384 | ||
|
|
5b0eca3270 | ||
|
|
c0d9ccacde | ||
|
|
3919991f48 | ||
|
|
0c573e6aa7 | ||
|
|
711d2519c2 | ||
|
|
9323caa1fc | ||
|
|
ff9faa3ef3 | ||
|
|
cae6f18ffb | ||
|
|
d48292b9dc | ||
|
|
c926953b60 | ||
|
|
7184449585 | ||
|
|
8e5bfb68dc | ||
|
|
51d4bd9520 | ||
|
|
2e18d7da38 | ||
|
|
b4a3ecdd75 | ||
|
|
f6639e661b | ||
|
|
130927f056 | ||
|
|
530b428159 | ||
|
|
f86909a928 | ||
|
|
3fb51e1995 | ||
|
|
cbd10105be | ||
|
|
eb510e0ca4 | ||
|
|
04902cdca5 | ||
|
|
5cbb1e4431 | ||
|
|
bcbb141378 | ||
|
|
bcd6c6b46d | ||
|
|
91e91218df | ||
|
|
000a0c84ff | ||
|
|
d12c3457af | ||
|
|
63d602b8ca | ||
|
|
ef0c581714 | ||
|
|
f852526092 | ||
|
|
18f54f9eb0 | ||
|
|
964be2fad8 | ||
|
|
ca7cdb4a77 | ||
|
|
99eddf0827 | ||
|
|
742c35485f | ||
|
|
e94b025395 | ||
|
|
ee81e8ea8f | ||
|
|
5a92bff2cd | ||
|
|
0aef65bc5d | ||
|
|
6fdc4ddfa7 | ||
|
|
708268aa71 | ||
|
|
2f644a14bc | ||
|
|
573346c6b8 | ||
|
|
0e8856758a | ||
|
|
ba8a1fc1a7 | ||
|
|
6780c4fdee | ||
|
|
129dfd7b46 | ||
|
|
b6d1bdca7c | ||
|
|
ffb627a51f | ||
|
|
b2c2376678 | ||
|
|
8bcc23efdb | ||
|
|
0188496fcc | ||
|
|
2663090827 | ||
|
|
65b7d6a1ae | ||
|
|
d15be7c524 | ||
|
|
d812ada7df | ||
|
|
2abb88530c | ||
|
|
dedb7eb806 | ||
|
|
a03785d922 | ||
|
|
ea066101a6 | ||
|
|
69258c94b0 | ||
|
|
23bd9575ea | ||
|
|
fab47e2b93 | ||
|
|
e0125e315d | ||
|
|
e27d954097 | ||
|
|
75da501683 | ||
|
|
9239065fdc | ||
|
|
95610b4b83 | ||
|
|
7a5f31da30 | ||
|
|
8d2ffbd4c9 | ||
|
|
263115933e | ||
|
|
b691a0d07e | ||
|
|
8c874943f9 | ||
|
|
01c40f969e | ||
|
|
8884c17f54 | ||
|
|
87e2ac3615 | ||
|
|
b6eae71825 | ||
|
|
c44df7e15c | ||
|
|
92c430ee12 | ||
|
|
3be49b29ec | ||
|
|
0486947913 | ||
|
|
105fd207c0 | ||
|
|
e1550aaea6 | ||
|
|
25adc9c75c | ||
|
|
09dffe5430 | ||
|
|
df173e4263 | ||
|
|
b561269ef8 | ||
|
|
20506a5950 | ||
|
|
0ebfa14b6d | ||
|
|
f97f552e87 | ||
|
|
2bde2c707f | ||
|
|
913acca108 | ||
|
|
50b27dbf94 | ||
|
|
2f9ef85652 | ||
|
|
9c42fe1126 | ||
|
|
e42b295617 | ||
|
|
d7312ad8a9 | ||
|
|
c827de99a7 | ||
|
|
24aaa598c5 | ||
|
|
3d5466eef4 | ||
|
|
b5a2a8d3db | ||
|
|
b5a4fc3f1f | ||
|
|
90738f212a | ||
|
|
50b4002e51 | ||
|
|
15ab4dbd47 | ||
|
|
bdf3c5d00e | ||
|
|
dadaad8a22 | ||
|
|
eea02da21a | ||
|
|
476c6dd12a | ||
|
|
35d48b9d1c | ||
|
|
be24ff49f0 | ||
|
|
8b8280c1e7 | ||
|
|
e0ebfac9d3 | ||
|
|
abc8a0856f | ||
|
|
2c49007406 | ||
|
|
06a47b11b5 | ||
|
|
0af39d7d40 | ||
|
|
182401b3be | ||
|
|
6357802b7d | ||
|
|
4dd4910bf3 | ||
|
|
766e8b2010 | ||
|
|
77010b6652 | ||
|
|
9759c2e253 | ||
|
|
05b139aadc | ||
|
|
d86c155eb1 | ||
|
|
77fff17b01 | ||
|
|
0568d68b80 | ||
|
|
9a6b7d2455 | ||
|
|
61b3e380e0 | ||
|
|
9c9e6b9e28 | ||
|
|
4c1529da2a | ||
|
|
4a345fe8e3 | ||
|
|
102023f249 | ||
|
|
aee5923e68 | ||
|
|
ea32845a7d | ||
|
|
3bb951e59e | ||
|
|
517acef79b | ||
|
|
cc856c020c | ||
|
|
a0d1a2bc31 | ||
|
|
9c95d6473a | ||
|
|
6f0cdee116 | ||
|
|
2060761d42 | ||
|
|
a4208faf95 | ||
|
|
1d94973509 | ||
|
|
009f00ba91 | ||
|
|
c26319db84 | ||
|
|
fd40807831 | ||
|
|
9b397125c1 | ||
|
|
e9c2d81ff5 | ||
|
|
df8af35979 | ||
|
|
1113675482 | ||
|
|
131e6a588c | ||
|
|
c790abb494 | ||
|
|
fcde96701d | ||
|
|
6e76c846fb |
@@ -3,5 +3,3 @@ self-hosted-runner:
|
||||
- custom-arm64-focal
|
||||
- custom-arm64-jammy
|
||||
- hanzo-build-linux-amd64
|
||||
- hanzo-build-linux-arm64
|
||||
- ubuntu-24.04-arm
|
||||
|
||||
@@ -12,9 +12,9 @@ else
|
||||
MODULE_DETAILS="$(go list -m "github.com/luxfi/node" 2>/dev/null)"
|
||||
|
||||
# Extract the version part
|
||||
LUX_VERSION="$(echo "${MODULE_DETAILS}" | awk '{print $2}')"
|
||||
NODE_VERSION="$(echo "${MODULE_DETAILS}" | awk '{print $2}')"
|
||||
|
||||
if [[ -z "${LUX_VERSION}" ]]; then
|
||||
if [[ -z "${NODE_VERSION}" ]]; then
|
||||
echo "Failed to get luxd version from go.mod"
|
||||
exit 1
|
||||
fi
|
||||
@@ -23,12 +23,12 @@ else
|
||||
# v*YYYYMMDDHHMMSS-abcdef123456
|
||||
#
|
||||
# If not, the value is assumed to represent a tag
|
||||
if [[ "${LUX_VERSION}" =~ ^v.*[0-9]{14}-[0-9a-f]{12}$ ]]; then
|
||||
if [[ "${NODE_VERSION}" =~ ^v.*[0-9]{14}-[0-9a-f]{12}$ ]]; then
|
||||
# Use the module hash as the version
|
||||
LUX_VERSION="$(echo "${LUX_VERSION}" | cut -d'-' -f3)"
|
||||
NODE_VERSION="$(echo "${NODE_VERSION}" | cut -d'-' -f3)"
|
||||
fi
|
||||
|
||||
FLAKE="github:luxfi/node?ref=${LUX_VERSION}"
|
||||
FLAKE="github:luxfi/node?ref=${NODE_VERSION}"
|
||||
echo "Starting nix shell for ${FLAKE}"
|
||||
fi
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@ set -euo pipefail
|
||||
# when go is already installed.
|
||||
|
||||
# 3 directories above this script
|
||||
LUX_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ../../.. && pwd )
|
||||
NODE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ../../.. && pwd )
|
||||
|
||||
echo GO_VERSION="~$(sed -n -e 's/^go //p' "${LUX_PATH}"/go.mod)"
|
||||
echo GO_VERSION="~$(sed -n -e 's/^go //p' "${NODE_PATH}"/go.mod)"
|
||||
|
||||
@@ -67,9 +67,6 @@
|
||||
description: "This involves consensus"
|
||||
- name: "continuous staking"
|
||||
color: "f9d0c4"
|
||||
- name: "Durango"
|
||||
color: "DAF894"
|
||||
description: "durango fork"
|
||||
- name: "gossiping upgrade"
|
||||
color: "c2e0c6"
|
||||
- name: "merkledb"
|
||||
|
||||
@@ -2,6 +2,9 @@ name: Lint proto files
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'proto/**/*.proto'
|
||||
- '.github/workflows/buf-lint.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -11,10 +14,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for .proto files
|
||||
id: proto-check
|
||||
run: |
|
||||
if find proto -name '*.proto' -type f 2>/dev/null | grep -q .; then
|
||||
echo "has_proto=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_proto=false" >> $GITHUB_OUTPUT
|
||||
echo "No .proto files found — skipping buf-lint (node is ZAP-native by default; protobuf is opt-in)."
|
||||
fi
|
||||
- uses: bufbuild/buf-setup-action@v1
|
||||
if: steps.proto-check.outputs.has_proto == 'true'
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
version: "1.47.2"
|
||||
- uses: bufbuild/buf-lint-action@v1
|
||||
if: steps.proto-check.outputs.has_proto == 'true'
|
||||
with:
|
||||
input: "proto"
|
||||
|
||||
@@ -20,7 +20,10 @@ jobs:
|
||||
GOWORK: off
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest]
|
||||
# `macos-latest` currently resolves to GH-hosted arm64 macos (forbidden);
|
||||
# pin to `macos-13` (amd64). darwin/arm64 coverage lives in
|
||||
# build-macos-release.yml via cross-compile.
|
||||
os: [windows-latest, macos-13]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-go-for-project
|
||||
|
||||
@@ -18,7 +18,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-x86_64-binaries-tarball:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: lux-build
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
rm -rf ${{ github.workspace }}/luxd-pkg
|
||||
|
||||
build-arm64-binaries-tarball:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: lux-build
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
@@ -80,8 +80,8 @@ jobs:
|
||||
|
||||
- run: go version
|
||||
|
||||
- name: Build the luxd binaries (native arm64)
|
||||
run: CGO_ENABLED=0 ./scripts/build.sh
|
||||
- name: Build the luxd binaries (cross-compile arm64 on amd64)
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 ./scripts/build.sh
|
||||
|
||||
- name: Try to get tag from git
|
||||
if: "${{ github.event.inputs.tag == '' }}"
|
||||
|
||||
@@ -21,10 +21,17 @@ on:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
# Cross-compile both darwin/amd64 and darwin/arm64 from Linux. The build
|
||||
# uses CGO_ENABLED=0 + GOOS=darwin GOARCH=$arch — pure Go, no Darwin
|
||||
# toolchain required. We run on the self-hosted `lux-build` ARC pool
|
||||
# rather than `macos-13` (which has long GitHub-hosted queues) to keep
|
||||
# the release pipeline unblocked.
|
||||
build-mac:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goarch: [amd64, arm64]
|
||||
runs-on: lux-build
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
@@ -37,8 +44,8 @@ jobs:
|
||||
- run: go version
|
||||
|
||||
# Runs a single command using the runners shell
|
||||
- name: Build the luxd binary
|
||||
run: CGO_ENABLED=0 ./scripts/run_task.sh build
|
||||
- name: Build the luxd binary (cross-compile via GOOS/GOARCH)
|
||||
run: CGO_ENABLED=0 GOOS=darwin GOARCH=${{ matrix.goarch }} ./scripts/run_task.sh build
|
||||
|
||||
- name: Try to get tag from git
|
||||
if: "${{ github.event.inputs.tag == '' }}"
|
||||
@@ -56,17 +63,20 @@ jobs:
|
||||
|
||||
- name: Create zip file with CLI-compatible naming
|
||||
run: |
|
||||
# CLI expects: node-macos-{version}.zip containing build/luxd
|
||||
# CLI expects: node-macos-{arch}-{version}.zip containing build/luxd
|
||||
mkdir -p build
|
||||
7z a "node-macos-${TAG}.zip" build/luxd
|
||||
if ! command -v zip >/dev/null 2>&1; then
|
||||
sudo apt-get update && sudo apt-get install -y zip
|
||||
fi
|
||||
zip "node-macos-${{ matrix.goarch }}-${TAG}.zip" build/luxd
|
||||
env:
|
||||
TAG: ${{ env.TAG }}
|
||||
|
||||
- name: Save as Github artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: build
|
||||
path: node-macos-${{ env.TAG }}.zip
|
||||
name: build-darwin-${{ matrix.goarch }}
|
||||
path: node-macos-${{ matrix.goarch }}-${{ env.TAG }}.zip
|
||||
|
||||
- name: Cleanup
|
||||
run: |
|
||||
|
||||
@@ -18,7 +18,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-jammy-amd64-package:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: lux-build
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
rm -rf /tmp/luxd
|
||||
|
||||
build-focal-amd64-package:
|
||||
runs-on: hanzo-build-linux-amd64
|
||||
runs-on: lux-build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -18,15 +18,15 @@ on:
|
||||
|
||||
jobs:
|
||||
build-jammy-arm64-package:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: lux-build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-go-for-project
|
||||
- run: go version
|
||||
|
||||
- name: Build the luxd binaries (native arm64)
|
||||
run: CGO_ENABLED=0 ./scripts/build.sh
|
||||
- name: Build the luxd binaries (cross-compile arm64 on amd64)
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 ./scripts/build.sh
|
||||
|
||||
- name: Try to get tag from git
|
||||
if: "${{ github.event.inputs.tag == '' }}"
|
||||
@@ -63,15 +63,15 @@ jobs:
|
||||
rm -rf /tmp/luxd
|
||||
|
||||
build-focal-arm64-package:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: lux-build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-go-for-project
|
||||
- run: go version
|
||||
|
||||
- name: Build the luxd binaries (native arm64)
|
||||
run: CGO_ENABLED=0 ./scripts/build.sh
|
||||
- name: Build the luxd binaries (cross-compile arm64 on amd64)
|
||||
run: CGO_ENABLED=0 GOOS=linux GOARCH=arm64 ./scripts/build.sh
|
||||
|
||||
- name: Try to get tag from git
|
||||
if: "${{ github.event.inputs.tag == '' }}"
|
||||
|
||||
@@ -5,9 +5,9 @@ set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
# Lux root directory
|
||||
LUX_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ../.. && pwd )
|
||||
NODE_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ../.. && pwd )
|
||||
|
||||
"$LUX_PATH"/scripts/build.sh
|
||||
"$NODE_PATH"/scripts/build.sh
|
||||
# Check to see if the build script creates any unstaged changes to prevent
|
||||
# regression where builds go.mod/go.sum files get out of date.
|
||||
if [[ -z $(git status -s) ]]; then
|
||||
@@ -15,5 +15,5 @@ if [[ -z $(git status -s) ]]; then
|
||||
# TODO: Revise this check once we can reliably build without changes
|
||||
# exit 1
|
||||
fi
|
||||
"$LUX_PATH"/scripts/build_test.sh
|
||||
"$LUX_PATH"/scripts/build_fuzz.sh 2
|
||||
"$NODE_PATH"/scripts/build_test.sh
|
||||
"$NODE_PATH"/scripts/build_fuzz.sh 2
|
||||
|
||||
@@ -30,7 +30,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-14, ubuntu-22.04, ubuntu-24.04, windows-2022]
|
||||
# GH-hosted arm64 macos forbidden; CI runs amd64 only. Release builds
|
||||
# cover darwin/arm64 via cross-compile in build-macos-release.yml.
|
||||
os: [macos-13, ubuntu-22.04, ubuntu-24.04, windows-2022]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-go-for-project
|
||||
|
||||
@@ -8,22 +8,109 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
uses: hanzoai/.github/.github/workflows/docker-build.yml@main
|
||||
with:
|
||||
image: ghcr.io/luxfi/node
|
||||
runner-amd64: hanzo-build-linux-amd64
|
||||
runner-arm64: hanzo-build-linux-arm64
|
||||
secrets: inherit
|
||||
build-amd64:
|
||||
# Self-hosted `lux-build` ARC pool on DOKS hanzo-k8s (max 20 runners,
|
||||
# scales 0→N on demand). Org-isolated: luxfi workflows must use this
|
||||
# pool (never the hanzoai pools). amd64 only — DOKS has no arm64 yet.
|
||||
runs-on: lux-build
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver: docker-container
|
||||
driver-opts: |
|
||||
network=host
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/luxfi/node
|
||||
# Semver-only policy: only `v*` git tags yield a published image
|
||||
# tag; every push gets an immutable `sha-<sha7>` for traceability.
|
||||
# No `:latest`, no `:main`, no floating tags ever. `flavor.latest`
|
||||
# has to be explicit-false; docker/metadata-action defaults to
|
||||
# `latest=auto` which silently emits `:latest` on tag pushes.
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=sha,format=short,prefix=sha-
|
||||
|
||||
- name: Resolve cross-repo PAT for private luxfi/* deps
|
||||
id: pat
|
||||
env:
|
||||
# The default GITHUB_TOKEN is repo-scoped and cannot read
|
||||
# private cross-repo deps like luxfi/corona or luxfi/evm. We
|
||||
# need a PAT with org-wide read scope. Order of preference:
|
||||
# 1) org GH_TOKEN (visibility=all, set on luxfi org since 2024)
|
||||
# 2) repo UNIVERSE_PAT (set on luxfi/node since 2026-04)
|
||||
# Either suffices; we just need ONE non-empty token. If both
|
||||
# are empty the build is going to fail at go mod download for
|
||||
# corona — fail fast here with a clear message.
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
UNIVERSE_PAT: ${{ secrets.UNIVERSE_PAT }}
|
||||
run: |
|
||||
tok="$GH_TOKEN"
|
||||
src="GH_TOKEN"
|
||||
if [ -z "$tok" ]; then
|
||||
tok="$UNIVERSE_PAT"
|
||||
src="UNIVERSE_PAT"
|
||||
fi
|
||||
if [ -z "$tok" ]; then
|
||||
echo "::error::Neither GH_TOKEN (org) nor UNIVERSE_PAT (repo) is set. Private luxfi/* deps cannot be resolved."
|
||||
exit 1
|
||||
fi
|
||||
echo "Using secret: $src (length=${#tok})"
|
||||
# Pass the resolved token to subsequent steps without exposing
|
||||
# the value. ::add-mask:: prevents accidental log leaks if the
|
||||
# value ever appears in later step output.
|
||||
echo "::add-mask::$tok"
|
||||
echo "token<<EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "$tok" >> "$GITHUB_OUTPUT"
|
||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build & push (amd64)
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
CGO_ENABLED=0
|
||||
# Resolve private luxfi/* modules via git url.insteadOf in the
|
||||
# Dockerfile. Take the token value from the previous step's
|
||||
# output (it picked GH_TOKEN or fell back to UNIVERSE_PAT).
|
||||
secrets: |
|
||||
ghtok=${{ steps.pat.outputs.token }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: false
|
||||
cache-from: type=registry,ref=ghcr.io/luxfi/node:buildcache-amd64
|
||||
cache-to: type=registry,ref=ghcr.io/luxfi/node:buildcache-amd64,mode=max
|
||||
|
||||
notify-universe:
|
||||
needs: docker
|
||||
needs: build-amd64
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
- uses: peter-evans/repository-dispatch@v4
|
||||
with:
|
||||
token: ${{ secrets.UNIVERSE_PAT }}
|
||||
repository: luxfi/universe
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
name: zap-audit
|
||||
|
||||
# LP-023 — audit gates for zap_native invariants.
|
||||
#
|
||||
# Job 1 (R6-6): AddressList.At() must remain non-production until
|
||||
# owner-model migration / executor-side equivalence is proven. This
|
||||
# workflow fails the PR if any new production caller is introduced.
|
||||
#
|
||||
# Job 2 (R7V8): Every tx type that EMBEDS a ChainsList in its per-tx
|
||||
# Verify() body MUST call .MustVerify() on it. The receiver-name rename
|
||||
# (Verify → MustVerify, batch 5 v3.6) makes the gate grep-able. Without
|
||||
# this gate, a future Verify() author can silently skip the per-entry
|
||||
# FxIDsLen + reserved-bytes walk and ship a wire-fork primitive.
|
||||
#
|
||||
# Allowed callers (Job 1):
|
||||
# - *_test.go — tests
|
||||
# - tx_verify.go — executor-side SyntacticVerify boundary
|
||||
# - owner.go — the type's own implementation
|
||||
# - audit_test.go — the mirroring local test that reproduces this gate
|
||||
#
|
||||
# To LEGITIMATELY introduce a production caller (Job 1):
|
||||
# 1. Land the owner-model migration that retires AddressList.At() OR
|
||||
# 2. Add the caller's file to the allowlist below AND document the
|
||||
# equivalence proof in vms/platformvm/txs/zap_native/owner.go.
|
||||
#
|
||||
# Local reproduction:
|
||||
# cd vms/platformvm/txs/zap_native && go test -run TestAuditGate -v
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "vms/platformvm/txs/zap_native/**"
|
||||
- ".github/workflows/zap-audit.yml"
|
||||
push:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- "vms/platformvm/txs/zap_native/**"
|
||||
- ".github/workflows/zap-audit.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
addresslist-production-gate:
|
||||
name: AddressList.At() — no production consumers
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Grep for production consumers
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
hits=$(grep -rn 'AddressList\.At(' --include='*.go' vms/platformvm/txs/zap_native/ \
|
||||
| grep -vE '(_test\.go|tx_verify\.go|owner\.go)' \
|
||||
|| true)
|
||||
if [ -n "$hits" ]; then
|
||||
echo "ERROR: New production consumer(s) of AddressList.At() found."
|
||||
echo "AddressList is forward-looking — see"
|
||||
echo " vms/platformvm/txs/zap_native/owner.go"
|
||||
echo "for the consumer-safety contract."
|
||||
echo ""
|
||||
echo "If you have a legitimate production use, either:"
|
||||
echo " 1. Land the owner-model migration that retires AddressList.At, OR"
|
||||
echo " 2. Add the file to the allowlist in"
|
||||
echo " .github/workflows/zap-audit.yml AND"
|
||||
echo " vms/platformvm/txs/zap_native/audit_test.go"
|
||||
echo " with the equivalence proof recorded in owner.go."
|
||||
echo ""
|
||||
echo "Offenders:"
|
||||
echo "$hits"
|
||||
exit 1
|
||||
fi
|
||||
echo "Audit clean: zero AddressList.At() production consumers"
|
||||
|
||||
chainslist-verify-gate:
|
||||
name: ChainsList embedders — MustVerify() required
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Ensure ChainsList embedders call MustVerify()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd vms/platformvm/txs/zap_native
|
||||
# Step 1: enumerate tx types that EMBED ChainsList. A tx type
|
||||
# T is an embedder if it has an accessor like
|
||||
# func (t T) Chains() ChainsListView
|
||||
# defined in a production file.
|
||||
embedders=$(grep -rEoh 'func \(t [A-Z][A-Za-z0-9_]+\) Chains\(\) (ChainsList|ChainsListView|BoundChainsList)' \
|
||||
--include='*.go' . \
|
||||
| grep -vE '(_test\.go)' \
|
||||
| sed -E 's/^func \(t ([A-Za-z0-9_]+)\) Chains.*/\1/' \
|
||||
| sort -u || true)
|
||||
if [ -z "$embedders" ]; then
|
||||
echo "Audit clean: zero ChainsList embedder tx types"
|
||||
exit 0
|
||||
fi
|
||||
# Step 2: for each embedder T, confirm tx_verify.go has a
|
||||
# Verify() method for T AND calls .MustVerify(). Centralized
|
||||
# in tx_verify.go by convention; embedder file (type
|
||||
# definition) and Verify file (gate body) are decoupled.
|
||||
offenders=""
|
||||
for T in $embedders; do
|
||||
if grep -qE "func \([a-z]+ ${T}\) Verify\(\)" tx_verify.go; then
|
||||
if ! grep -q '\.MustVerify(' tx_verify.go; then
|
||||
offenders="${offenders}\n${T} (tx_verify.go has Verify() for ${T} but no .MustVerify() call)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ -n "$offenders" ]; then
|
||||
echo "ERROR: ChainsList embedder tx type(s) missing .MustVerify() call."
|
||||
echo "Every tx type T with a Chains() accessor AND a Verify()"
|
||||
echo "method MUST call list.MustVerify() inside that Verify()"
|
||||
echo "to enforce FxIDsLen + reserved-bytes invariants"
|
||||
echo "(R6-4 / R6V5 / R7V8)."
|
||||
echo ""
|
||||
echo "Either wire the gate or document why it's safe to skip"
|
||||
echo "inline with a clear comment."
|
||||
echo ""
|
||||
echo -e "Offenders:${offenders}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Audit clean: all ChainsList embedders call MustVerify()"
|
||||
|
||||
validatorslist-mustverify-gate:
|
||||
name: ValidatorsList embedders — MustVerify() required
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Ensure ValidatorsList embedders call MustVerify()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd vms/platformvm/txs/zap_native
|
||||
# Step 1: enumerate tx types that EMBED ValidatorsList. A tx
|
||||
# type T is an embedder if it has an accessor like
|
||||
# func (t T) Validators() ValidatorsList
|
||||
# defined in a production file.
|
||||
embedders=$(grep -rEoh 'func \(t [A-Z][A-Za-z0-9_]+\) Validators\(\) ValidatorsList' \
|
||||
--include='*.go' . \
|
||||
| grep -vE '(_test\.go)' \
|
||||
| sed -E 's/^func \(t ([A-Za-z0-9_]+)\) Validators.*/\1/' \
|
||||
| sort -u || true)
|
||||
if [ -z "$embedders" ]; then
|
||||
echo "Audit clean: zero ValidatorsList embedder tx types"
|
||||
exit 0
|
||||
fi
|
||||
# Step 2: for each embedder T, locate the bounded Verify()
|
||||
# body in tx_verify.go and confirm it contains a MustVerify(
|
||||
# token. Body delimited by matching braces from the opening
|
||||
# `{` of the function header.
|
||||
offenders=""
|
||||
for T in $embedders; do
|
||||
if ! grep -qE "func \(t ${T}\) Verify\(\) error \{" tx_verify.go; then
|
||||
# No Verify() — covered by other audits; skip here.
|
||||
continue
|
||||
fi
|
||||
body=$(awk -v T="${T}" '
|
||||
BEGIN { in_fn=0; depth=0 }
|
||||
{
|
||||
if (in_fn == 0) {
|
||||
if ($0 ~ ("func \\(t " T "\\) Verify\\(\\) error \\{$")) {
|
||||
in_fn=1
|
||||
depth=1
|
||||
next
|
||||
}
|
||||
} else {
|
||||
for (i=1; i<=length($0); i++) {
|
||||
c = substr($0,i,1)
|
||||
if (c == "{") depth++
|
||||
else if (c == "}") {
|
||||
depth--
|
||||
if (depth == 0) { exit }
|
||||
}
|
||||
}
|
||||
print $0
|
||||
}
|
||||
}
|
||||
' tx_verify.go)
|
||||
if ! echo "$body" | grep -q '\.MustVerify('; then
|
||||
offenders="${offenders}\n${T} (Verify() body does not call validators.MustVerify())"
|
||||
fi
|
||||
done
|
||||
if [ -n "$offenders" ]; then
|
||||
echo "ERROR: ValidatorsList embedder tx type(s) missing .MustVerify() call."
|
||||
echo "Every tx type T with a Validators() accessor AND a Verify()"
|
||||
echo "method MUST call list.MustVerify() inside that Verify()"
|
||||
echo "to enforce the 5 structural floor invariants"
|
||||
echo "(cap, weight, BLS-non-zero, expiry-non-zero)."
|
||||
echo ""
|
||||
echo -e "Offenders:${offenders}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Audit clean: all ValidatorsList embedders call MustVerify()"
|
||||
|
||||
owner-bearing-syntacticverify-gate:
|
||||
name: Owner-bearing tx — SyntacticVerify() required
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Ensure Owner-bearing tx types call SyntacticVerify()
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd vms/platformvm/txs/zap_native
|
||||
# Step 1: enumerate Owner-bearing tx types. Match any of the
|
||||
# named owner accessors that return the (uint32, uint64,
|
||||
# ids.ShortID) tuple. TransferChainOwnershipTx uses split
|
||||
# accessors (OwnerThreshold/Locktime/Address) so a separate
|
||||
# check picks it up.
|
||||
embedders=$(grep -rEoh 'func \(t [A-Z][A-Za-z0-9_]+\) (Owner|RewardsOwner|ValidationRewardsOwner|DelegationRewardsOwner)\(\) \(uint32, uint64, ids\.ShortID\)' \
|
||||
--include='*.go' . \
|
||||
| grep -vE '(_test\.go)' \
|
||||
| sed -E 's/^func \(t ([A-Za-z0-9_]+)\).*/\1/' \
|
||||
| sort -u || true)
|
||||
if grep -qE 'func \(t TransferChainOwnershipTx\) (OwnerThreshold|OwnerLocktime|OwnerAddress)\(\)' \
|
||||
--include='*.go' -r . ; then
|
||||
embedders="${embedders}
|
||||
TransferChainOwnershipTx"
|
||||
fi
|
||||
embedders=$(echo "${embedders}" | sort -u | grep -v '^$' || true)
|
||||
if [ -z "$embedders" ]; then
|
||||
echo "Audit clean: zero Owner-bearing tx types"
|
||||
exit 0
|
||||
fi
|
||||
# Step 2: for each embedder T, locate the bounded Verify() body
|
||||
# in tx_verify.go and confirm it contains a SyntacticVerify
|
||||
# token. The body is delimited by matching braces from the
|
||||
# opening `{` of the function header.
|
||||
offenders=""
|
||||
for T in $embedders; do
|
||||
if ! grep -qE "func \(t ${T}\) Verify\(\) error \{" tx_verify.go; then
|
||||
offenders="${offenders}\n${T} (no Verify() method in tx_verify.go — Owner consumed without gate)"
|
||||
continue
|
||||
fi
|
||||
# Extract the bounded body via awk with brace depth tracking.
|
||||
body=$(awk -v T="${T}" '
|
||||
BEGIN { in_fn=0; depth=0 }
|
||||
{
|
||||
if (in_fn == 0) {
|
||||
if ($0 ~ ("func \\(t " T "\\) Verify\\(\\) error \\{$")) {
|
||||
in_fn=1
|
||||
depth=1
|
||||
next
|
||||
}
|
||||
} else {
|
||||
for (i=1; i<=length($0); i++) {
|
||||
c = substr($0,i,1)
|
||||
if (c == "{") depth++
|
||||
else if (c == "}") {
|
||||
depth--
|
||||
if (depth == 0) { exit }
|
||||
}
|
||||
}
|
||||
print $0
|
||||
}
|
||||
}
|
||||
' tx_verify.go)
|
||||
if ! echo "$body" | grep -q 'SyntacticVerify'; then
|
||||
offenders="${offenders}\n${T} (Verify() body does not call SyntacticVerify)"
|
||||
fi
|
||||
done
|
||||
if [ -n "$offenders" ]; then
|
||||
echo "ERROR: Owner-bearing tx type(s) missing SyntacticVerify call."
|
||||
echo "Every tx type T with an Owner accessor MUST call"
|
||||
echo " stubFromTuple(...).SyntacticVerify() OR"
|
||||
echo " OwnerView(...).SyntacticVerify()"
|
||||
echo "inside its per-tx Verify() body."
|
||||
echo ""
|
||||
echo "Skipping the gate makes a threshold=0 wire-encoded Owner a"
|
||||
echo "silent authorization bypass (R4V7)."
|
||||
echo ""
|
||||
echo -e "Offenders:${offenders}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Audit clean: all Owner-bearing tx types call SyntacticVerify()"
|
||||
+5
-4
@@ -31,8 +31,6 @@ tmp/
|
||||
|
||||
*.pb*
|
||||
|
||||
db*
|
||||
|
||||
*cpu[0-9]*
|
||||
*mem[0-9]*
|
||||
*lock[0-9]*
|
||||
@@ -85,11 +83,14 @@ QWEN.md
|
||||
|
||||
genesis/.!*
|
||||
genesis-gen
|
||||
lux
|
||||
luxd
|
||||
/lux
|
||||
/luxd
|
||||
evm-plugin-*
|
||||
|
||||
LLM.md
|
||||
QWEN.md
|
||||
.AGENTS.md
|
||||
GEMINI.md
|
||||
|
||||
# Built dev-tool binaries
|
||||
/cmd/gen_zoo_addr/gen_zoo_addr
|
||||
|
||||
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.28.0]
|
||||
|
||||
### Added
|
||||
- Multi-version P-Chain block + tx codec. v1.23.x ("Apricot/Banff") wire layout is now registered as `CodecVersionV0` on the platformvm tx and block `codec.Manager`s; the current canonical layout is `CodecVersionV1`. Bytes carry their wire version in the standard 2-byte codec prefix and `txs.Parse` / `block.Parse` dispatch by it.
|
||||
- Byte-preserving tx Initialization: `tx.InitializeFromBytes(c, version, signedBytes)` and `tx.InitializeFromBytesAtVersion(c, version)` bind a tx to its original `signedBytes` without re-marshalling. `TxID = hash(signedBytes)` is therefore stable across the v0->v1 migration — mainnet and testnet chain commitments hash back to byte-identical inputs.
|
||||
- `vms/platformvm/block/v0/` subpackage holding the pure-data v0 block kinds (`ApricotProposalBlock`, `BanffProposalBlock`, ... — 9 types, slots 0-4 + 29-32). The package is a one-way decoder only; the `liftedV0Block` adapter in the parent package translates each v0 block kind into the corresponding canonical v1 `Visitor` arm without ever re-marshalling.
|
||||
- Cross-version tx + block tests: `TestCodecVersionV0V1Coexist`, `TestParseDispatchesByVersion`, `TestTxIDStabilityRoundTrip`, `TestCrossVersionRefuses`, `TestParseV0ApricotProposalBlock`, `TestParseV0BanffStandardBlock`, `TestParseV1RoundTrip`, `TestVersionPrefixDispatch`.
|
||||
|
||||
### Changed
|
||||
- `tx.Initialize(c)` is retained for the fresh-build path only (wallet, builder). The from-DB / from-wire paths in `genesis/genesis.go` and `block/{standard,proposal}_block.go` now go through `InitializeFromBytesAtVersion` against the codec version the surrounding container was decoded under.
|
||||
- `genesis.Parse` is wire-version-aware: pre-codec-v1 P-Chain genesis blobs on mainnet / testnet decode at `CodecVersionV0`; new blobs are written at `CodecVersionV1`.
|
||||
- The L1-tx slot map advances by one to accommodate `CreateSovereignL1Tx` at slot 36: `RegisterL1ValidatorTx`=37, `SetL1ValidatorWeightTx`=38, `IncreaseL1ValidatorBalanceTx`=39, `DisableL1ValidatorTx`=40 (was 36-39). Both pre-rip mainnet/testnet bytes (no slot conflict; L1 txs did not exist) and current code paths line up with the new slot map.
|
||||
- Test fixtures in `vms/platformvm/txs/fee/calculator_test.go` and the serialization tests under `vms/platformvm/txs/*_test.go` are regenerated to use the v1 wire prefix (`0x0001`) and the post-`CreateSovereignL1Tx` slot map.
|
||||
|
||||
### Migration notes
|
||||
- Mainnet + testnet P-Chain DBs do NOT need to be rebuilt: pre-codec-v1 blocks continue to decode via the v0 path with their original `BlockID = hash(v0 bytes)` preserved. Newly accepted blocks are written at v1.
|
||||
- Devnet, which was bootstrapped post-rip at wire-version 0 but with the post-rip slot map, must be rebuilt before rolling to `v1.28.0`: its existing blobs would now decode against the v0 slot map (the pre-rip layout) and produce wrong types. A fresh bootstrap at `v1.28.0` writes v1 bytes from height 0 and is internally consistent thereafter.
|
||||
|
||||
## [1.13.5-alpha] - 2025-01-23
|
||||
|
||||
### Added
|
||||
|
||||
+79
-60
@@ -44,10 +44,24 @@ ENV GOPROXY=https://proxy.golang.org,direct
|
||||
ENV GONOPROXY=github.com/luxfi/*
|
||||
ENV GOFLAGS="-mod=mod"
|
||||
|
||||
# Copy and download lux dependencies using go mod
|
||||
# Copy and download lux dependencies using go mod.
|
||||
# Some luxfi/* modules (e.g. corona) are private and require a token to
|
||||
# resolve via go mod. The token is injected as a BuildKit secret from the
|
||||
# CI workflow; locally, set DOCKER_BUILDKIT=1 and pass
|
||||
# `--secret id=ghtok,src=$HOME/.gh-token`. If the secret is absent the
|
||||
# build still attempts the download (works when all deps are public).
|
||||
COPY go.mod .
|
||||
COPY go.sum .
|
||||
RUN go mod download
|
||||
# Configure global git insteadOf so EVERY subsequent step (go mod download
|
||||
# now, the build step's implicit fetch later) can reach private luxfi/*
|
||||
# modules. The token is written into /etc/gitconfig (root-readable in the
|
||||
# image), so explicit cleanup is unnecessary on this throwaway builder
|
||||
# stage — only the compiled binary is COPYed into the runtime image.
|
||||
RUN --mount=type=secret,id=ghtok,required=false \
|
||||
if [ -s /run/secrets/ghtok ]; then \
|
||||
git config --global url."https://x-access-token:$(cat /run/secrets/ghtok)@github.com/".insteadOf "https://github.com/"; \
|
||||
fi && \
|
||||
go mod download
|
||||
|
||||
# Copy the code into the container
|
||||
COPY . .
|
||||
@@ -58,6 +72,14 @@ RUN [ -d ./build ] && rm -rf ./build/* || true
|
||||
ARG TARGETPLATFORM
|
||||
ARG BUILDPLATFORM
|
||||
|
||||
# Per SCALE_STANDARD.md §2 (https://github.com/hanzoai/hips/blob/main/docs/SCALE_STANDARD.md)
|
||||
# — every Go production Dockerfile that emits JSON to a client builds
|
||||
# with GOEXPERIMENT=jsonv2. Verified -12% time / -23% allocs on the
|
||||
# edge POST roundtrip vs encoding/json v1. Applies to luxd + every
|
||||
# in-stage VM plugin build below.
|
||||
ARG GO_EXPERIMENT=jsonv2
|
||||
ENV GOEXPERIMENT=${GO_EXPERIMENT}
|
||||
|
||||
# Configure a cross-compiler if the target platform differs from the build platform.
|
||||
#
|
||||
# build_env.sh is used to capture the environmental changes required by the build step since RUN
|
||||
@@ -82,17 +104,17 @@ RUN ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \
|
||||
ldconfig 2>/dev/null || true
|
||||
|
||||
# Fetch pre-built luxcpp/cevm libs (libevm, libevm-gpu, libluxgpu,
|
||||
# libcevm_precompiles + go_bridge.h). When LUX_CGO=1 these libraries are
|
||||
# libcevm_precompiles + go_bridge.h). When CGO_ENABLED=1 these libraries are
|
||||
# linked into the C-Chain plugin via github.com/luxfi/chains/evm/cevm
|
||||
# and become the default execution backend (parallel + GPU EVM).
|
||||
#
|
||||
# CI/RELEASE GAP: the luxcpp/cevm release artifacts MUST publish per-arch
|
||||
# tarballs at the URL below for both linux-x86_64 and linux-arm64. Until
|
||||
# those tarballs exist, builds with LUX_CGO=1 will fail at this step and
|
||||
# operators must build with LUX_CGO=0 (pure-Go fallback).
|
||||
ARG LUX_CGO=1
|
||||
# those tarballs exist, builds with CGO_ENABLED=1 will fail at this step and
|
||||
# operators must build with CGO_ENABLED=0 (pure-Go fallback).
|
||||
ARG CGO_ENABLED=1
|
||||
ARG CEVM_VERSION=v0.19.0
|
||||
RUN if [ "${LUX_CGO}" = "1" ]; then \
|
||||
RUN if [ "${CGO_ENABLED}" = "1" ]; then \
|
||||
ARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \
|
||||
if [ "$ARCH" = "amd64" ]; then CEVM_ARCH="linux-x86_64"; else CEVM_ARCH="linux-arm64"; fi && \
|
||||
wget -q "https://github.com/luxcpp/cevm/releases/download/${CEVM_VERSION}/luxcpp-cevm-${CEVM_ARCH}.tar.gz" \
|
||||
@@ -101,24 +123,37 @@ RUN if [ "${LUX_CGO}" = "1" ]; then \
|
||||
rm /tmp/cevm.tar.gz && \
|
||||
ldconfig 2>/dev/null || true ; \
|
||||
else \
|
||||
echo "LUX_CGO=0: skipping luxcpp/cevm fetch (pure-Go fallback build)" ; \
|
||||
echo "CGO_ENABLED=0: skipping luxcpp/cevm fetch (pure-Go fallback build)" ; \
|
||||
fi
|
||||
|
||||
# Build node. LUX_CGO=1 (default) links luxcpp/cevm for parallel + GPU EVM.
|
||||
# Set LUX_CGO=0 for portable pure-Go builds without the native libs.
|
||||
# Build node. CGO_ENABLED=1 (default) links luxcpp/cevm for parallel + GPU EVM.
|
||||
# Set CGO_ENABLED=0 for portable pure-Go builds without the native libs.
|
||||
ARG RACE_FLAG=""
|
||||
ARG BUILD_SCRIPT=build.sh
|
||||
ARG LUXD_COMMIT=""
|
||||
ENV CGO_ENABLED=${LUX_CGO}
|
||||
ENV CGO_ENABLED=${CGO_ENABLED}
|
||||
RUN . ./build_env.sh && \
|
||||
echo "{CC=$CC, TARGETPLATFORM=$TARGETPLATFORM, BUILDPLATFORM=$BUILDPLATFORM, LUX_CGO=${LUX_CGO}}" && \
|
||||
echo "{CC=$CC, TARGETPLATFORM=$TARGETPLATFORM, BUILDPLATFORM=$BUILDPLATFORM, CGO_ENABLED=${CGO_ENABLED}}" && \
|
||||
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \
|
||||
export LUXD_COMMIT="${LUXD_COMMIT}" && \
|
||||
GOFLAGS="-mod=mod" ./scripts/${BUILD_SCRIPT} ${RACE_FLAG}
|
||||
|
||||
# ============= EVM Plugin Stage ================
|
||||
# Build EVM plugin from source (includes custom precompile registry)
|
||||
ARG EVM_VERSION=v0.8.40
|
||||
# Build EVM plugin from source (includes custom precompile registry).
|
||||
# EVM_VERSION must pin a luxfi/evm release whose go.mod points at a
|
||||
# luxfi/node version that has the runtime.EngineAddressKey rename
|
||||
# (LUX_VM_RUNTIME_ENGINE_ADDR → VM_RUNTIME_ENGINE_ADDR landed in
|
||||
# 4ae211d46d on 2026-05-15). v0.18.14 pins node v1.27.6 which is
|
||||
# post-rename. Older evm tags (e.g. v0.8.40 → node v1.23.4) ship a
|
||||
# plugin that os.Getenv()'s the old key, mismatching the host that
|
||||
# sets the new key, and C-chain never bootstraps.
|
||||
#
|
||||
# v0.19.0 (Quasar Edition): EtnaTimestamp Go field + json tag renamed
|
||||
# to QuasarTimestamp/quasarTimestamp. Strict upgradeBytes decode via
|
||||
# json.NewDecoder(...).DisallowUnknownFields() — stale etnaTimestamp
|
||||
# in any deployed upgrade.json now fails parse loudly at boot rather
|
||||
# than silently disabling the fork.
|
||||
ARG EVM_VERSION=v0.19.0
|
||||
ARG EVM_VM_ID=mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
mkdir -p /luxd/build/plugins && \
|
||||
@@ -153,55 +188,39 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
|
||||
# Each VM is an independent Go module under /tmp/chains/<vm>/.
|
||||
# Build each plugin binary and place it at /luxd/build/plugins/<cb58-vm-id>.
|
||||
#
|
||||
# NB(2026-05-19): luxfi/chains main has unresolved sibling go.mod replace
|
||||
# directives (luxfi/{evm,precompile,threshold} => ../*) that break in any
|
||||
# isolated build context. Each chain plugin is therefore built best-effort;
|
||||
# production deployments pull plugins from S3 (`pluginSource.bucket`) at
|
||||
# runtime per LuxNetwork CR, so an embedded plugin miss is non-fatal.
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
. /build/build_env.sh && \
|
||||
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) && \
|
||||
set -e && \
|
||||
cd /tmp/chains/aivm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/juFxSrbCM4wszxddKepj1GWwmrn9YgN1g4n3VUWPpRo9JjERA \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/bridgevm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/kMhHABHM8j4bH94MCc4rsTNdo5E9En37MMyiujk4WdNxgXFsY \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/dexvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/mDVT5EWMumBp3LCqvKwuyZQeY1VXr1jvjGNAt8nL4UFiXvqXr \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/graphvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/nZQm4Dmg1rjX18rb8maL9gamYyXPf1xCvF7ymWzxp6a1nSQTt \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/identityvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/oR6tnZHezwogyf9fRnomNXC9ojwCEBAU6jdUzpgy2PB1tD7fM \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/keyvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/pJJCSV7hHYVY6TUZwR8qUPAfuhX8JLb2C1AzNSezrYNbgau8M \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/oraclevm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/r5m1ujrmXxVcQetG3CQfuDLHp2RHKh6vCDaFgBRQfUcTZh7eS \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/quantumvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/ry9Sg8rZdT26iEKvJDmC2wkESs4SDKgZEhk5BgLSwg1EpcNug \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/relayvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/sP6dLqrrBR9w3soP18fbJ3YzZecZdD7DDdfH2cFhhLq7Hy9bz \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/thresholdvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/tGVBwRxpmD2aFdg3iYjgRvrCe8Jcmq9UNKxyHMus2NZ8WcD8t \
|
||||
./cmd/plugin && \
|
||||
cd /tmp/chains/zkvm && \
|
||||
CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/vv3qPfyTVXZ5ArRZA9Jh4hbYDTBe43f7sgQg4CHfNg1rnnvX9 \
|
||||
./cmd/plugin && \
|
||||
chmod +x /luxd/build/plugins/* && \
|
||||
mkdir -p /luxd/build/plugins && \
|
||||
( cd /tmp/chains/aivm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/juFxSrbCM4wszxddKepj1GWwmrn9YgN1g4n3VUWPpRo9JjERA ./cmd/plugin ) || echo "WARN: aivm plugin build skipped" ; \
|
||||
( cd /tmp/chains/bridgevm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/kMhHABHM8j4bH94MCc4rsTNdo5E9En37MMyiujk4WdNxgXFsY ./cmd/plugin ) || echo "WARN: bridgevm plugin build skipped" ; \
|
||||
( cd /tmp/chains/dexvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/mDVT5EWMumBp3LCqvKwuyZQeY1VXr1jvjGNAt8nL4UFiXvqXr ./cmd/plugin ) || echo "WARN: dexvm plugin build skipped" ; \
|
||||
( cd /tmp/chains/graphvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/nZQm4Dmg1rjX18rb8maL9gamYyXPf1xCvF7ymWzxp6a1nSQTt ./cmd/plugin ) || echo "WARN: graphvm plugin build skipped" ; \
|
||||
( cd /tmp/chains/identityvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/oR6tnZHezwogyf9fRnomNXC9ojwCEBAU6jdUzpgy2PB1tD7fM ./cmd/plugin ) || echo "WARN: identityvm plugin build skipped" ; \
|
||||
( cd /tmp/chains/keyvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/pJJCSV7hHYVY6TUZwR8qUPAfuhX8JLb2C1AzNSezrYNbgau8M ./cmd/plugin ) || echo "WARN: keyvm plugin build skipped" ; \
|
||||
( cd /tmp/chains/oraclevm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/r5m1ujrmXxVcQetG3CQfuDLHp2RHKh6vCDaFgBRQfUcTZh7eS ./cmd/plugin ) || echo "WARN: oraclevm plugin build skipped" ; \
|
||||
( cd /tmp/chains/quantumvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/ry9Sg8rZdT26iEKvJDmC2wkESs4SDKgZEhk5BgLSwg1EpcNug ./cmd/plugin ) || echo "WARN: quantumvm plugin build skipped" ; \
|
||||
( cd /tmp/chains/relayvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/sP6dLqrrBR9w3soP18fbJ3YzZecZdD7DDdfH2cFhhLq7Hy9bz ./cmd/plugin ) || echo "WARN: relayvm plugin build skipped" ; \
|
||||
( cd /tmp/chains/thresholdvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/tGVBwRxpmD2aFdg3iYjgRvrCe8Jcmq9UNKxyHMus2NZ8WcD8t ./cmd/plugin ) || echo "WARN: thresholdvm plugin build skipped" ; \
|
||||
( cd /tmp/chains/zkvm && CGO_ENABLED=0 GOFLAGS=-mod=mod go build -ldflags="-s -w" \
|
||||
-o /luxd/build/plugins/vv3qPfyTVXZ5ArRZA9Jh4hbYDTBe43f7sgQg4CHfNg1rnnvX9 ./cmd/plugin ) || echo "WARN: zkvm plugin build skipped" ; \
|
||||
( chmod +x /luxd/build/plugins/* 2>/dev/null || true ) && \
|
||||
rm -rf /tmp/chains
|
||||
|
||||
# lpm (Lux Plugin Manager) -- optional, skip if build fails
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ Wallet, CLI, EVM, DeFi, threshold cryptography, and the first key management sys
|
||||
| `lux/threshold` | 2021-02-16 | Threshold ECDSA (tECDSA) library |
|
||||
| `lux/erc20-go` | 2021-05-21 | ERC-20 Go bindings |
|
||||
| `lux/netrunner` | 2021-10-22 | Network testing framework (2,384 commits) |
|
||||
| `lux/evm` | 2021-12-15 | Subnet EVM (1,632 commits) |
|
||||
| `lux/evm` | 2021-12-15 | App chain EVM (1,632 commits) |
|
||||
| `lux/devops` / `lux/lux-ops` | 2022-01-28 | Infrastructure automation |
|
||||
| `lux/ledger` | 2022-03-14 | Ledger hardware wallet integration |
|
||||
| `lux/lpm` | 2022-03-28 | Lux Plugin Manager |
|
||||
|
||||
@@ -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`.
|
||||
@@ -8,10 +8,133 @@ Lux blockchain node implementation - a high-performance, multi-chain blockchain
|
||||
|
||||
**Key Context:**
|
||||
- Original Lux Network node — NOT a fork
|
||||
- Latest Tag: v1.26.31
|
||||
- Network ID: 96369 (Lux Mainnet), 96368 (Testnet), 96370 (Devnet)
|
||||
- Go Version: 1.26.1+
|
||||
- Database: ZapDB (primary, default)
|
||||
|
||||
## Post-E2E-PQ State (current)
|
||||
|
||||
Node now consumes the locked `ChainSecurityProfile` end-to-end and enforces
|
||||
strict-PQ at four boundaries: peer handshake, mempool, validator scheme
|
||||
selection, and EVM contract auth.
|
||||
|
||||
- `node/node.go:initSecurityProfile` (F102 closure) loads the chain-wide
|
||||
profile from genesis at boot, hashes it, and pins it into every chain's
|
||||
bootstrap. Resolved profile is what every downstream verifier consults.
|
||||
- `network/peer/scheme_gate.go` — `SchemeGate.Classify(presented, site)`
|
||||
funnels every inbound NodeID through the profile's
|
||||
`AcceptsValidatorScheme`. Wire-typed NodeID (`luxfi/ids` TypedNodeID +
|
||||
scheme byte) is the canonical handshake form.
|
||||
- `vms/txs/auth/policy.go` — `ClassicalCompatRegistry` + strict-PQ
|
||||
mempool gate. Both `platformvm` (P-Chain) and `avm` (X-Chain) mempools
|
||||
refuse classical credentials when the resolved profile has
|
||||
`ForbidECDSAContractAuth=true`.
|
||||
- `vms/mldsafx/` — re-exports the consensus `mldsafx` UTXO feature
|
||||
extension as the node-owned UTXO surface (ML-DSA-65 verify).
|
||||
- `network/peer` PQ handshake — ML-KEM-768 / ML-KEM-1024 KEM +
|
||||
ML-DSA-65 identity (`dc906d281b`).
|
||||
|
||||
### Recent significant commits
|
||||
|
||||
| SHA | Tag | Impact |
|
||||
|-----|-----|--------|
|
||||
| (pending) | next | LP-023 Phase 1 batch 2: 5 more native-ZAP tx types — BaseTx v1, RegisterL1ValidatorTx v1, SlashValidatorTx v1, TransferChainOwnershipTx v1, RemoveChainValidatorTx. Cross-type Parse mean speedup 8.5× over linearcodec. Variable-length nested-object schemas (Outs/Ins/full OutputOwners/Warp Message/Evidence) deferred to batch 3. |
|
||||
| `e77a7ef78e` | (pre) | LP-023 Phase 1 batch 1: 4 simple tx types + bench harness. 37× Parse, 5.6× cross-type mean. |
|
||||
| `9df72a6f55` | v1.26.10 | Wire ChainSecurityProfile into bootstrap (closes F102) |
|
||||
| `c4af52411e` | v1.26.10 | X-Chain (avm) mempool refuses classical creds under strict-PQ |
|
||||
| `a14a1601f4` | v1.26.10 | P-Chain (platformvm) mempool refuses classical creds under strict-PQ |
|
||||
| `1cf0aa80ca` | v1.26.10 | ClassicalCompatRegistry + strict-PQ mempool gate |
|
||||
| `a0f4f4b21c` | v1.26.10 | vms/mldsafx: re-export ML-DSA feature extension |
|
||||
| `448fdeb7a1` | v1.26.10 | ML-DSA-65 promoted to canonical NodeID under strict-PQ |
|
||||
| `dc906d281b` | v1.26.10 | PQ peer handshake — ML-KEM-768/1024 + ML-DSA-65 identity |
|
||||
|
||||
### Active versions
|
||||
- Repo: `v1.26.12` (next bump: `v1.26.13`).
|
||||
- Pinned: `consensus v1.23.4+` (needed `ValidatorSchemeID`),
|
||||
`crypto v1.18.5`, `ids v1.2.9` (will move to v1.2.10 in next bump for
|
||||
`TypedNodeID`), `genesis v1.9.6`.
|
||||
|
||||
### Cross-repo dependencies
|
||||
- `luxfi/consensus` → profile + auth + zchain types
|
||||
- `luxfi/crypto` → ML-DSA / ML-KEM / SLH-DSA primitives
|
||||
- `luxfi/genesis` → genesis-pinned profile (`Resolve` at load)
|
||||
- `luxfi/ids` → `TypedNodeID` wire form (consumed at handshake)
|
||||
- `luxfi/geth` → EVM (for `vm.SetActiveSecurityProfile` install point)
|
||||
|
||||
### Where to look for X
|
||||
- Profile resolve at boot: `node/node.go:initSecurityProfile`
|
||||
- Profile RPC + REST + metrics: `service/security/`
|
||||
- JSON-RPC namespace: `security` at `POST /ext/security`
|
||||
(methods `securityProfile`, `blockSecurity`)
|
||||
- REST sidecars: `GET /ext/security/profile`, `GET /ext/security/block/{n}`
|
||||
- Prometheus gauges: `/ext/metrics` under the `security_*` family
|
||||
- Peer scheme gate: `network/peer/scheme_gate.go`
|
||||
- Classical-compat registry: `vms/txs/auth/policy.go`
|
||||
- Mempool gate (P-Chain): `vms/platformvm/mempool/*.go`
|
||||
- Mempool gate (X-Chain): `vms/avm/mempool/*.go`
|
||||
- ML-DSA feature extension: `vms/mldsafx/`
|
||||
|
||||
### Open follow-ups
|
||||
- `vms/zkvm/accel/` still soft-falls-back when CGO is disabled; Z-Chain
|
||||
proof verification path needs CGO-required mode for production strict-PQ.
|
||||
- `vm.SetActiveSecurityProfile` install point exists in `luxfi/geth/core/vm`
|
||||
but EVM-side contract-auth refusal still needs a chain-bootstrap call
|
||||
(F102 wiring closes the consensus side; geth-side hookup is the
|
||||
remaining tail).
|
||||
|
||||
## FeePolicy — canonical user-tx fee gate
|
||||
|
||||
Every Lux VM that accepts user-submitted txs declares a `fee.Policy`
|
||||
(package `vms/types/fee`). There is one interface and one validator —
|
||||
no per-VM bespoke fee structs.
|
||||
|
||||
### Policy choice per VM
|
||||
|
||||
| VM | Chain | Posture | Policy |
|
||||
|----|-------|---------|--------|
|
||||
| dexvm | D-Chain | user-tx | `FlatPolicy{Fee: MinTxFeeFloor, AssetID: UTXOAssetIDFor(networkID)}` |
|
||||
| zkvm | Z-Chain | user-tx | `FlatPolicy{Fee: MinTxFeeFloor, ...}` |
|
||||
| aivm | A-Chain | user-tx | `FlatPolicy{Fee: MinTxFeeFloor, ...}` |
|
||||
| keyvm | K-Chain | user-tx | `FlatPolicy{Fee: MinTxFeeFloor, ...}` |
|
||||
| bridgevm | B-Chain | user-tx | `FlatPolicy{Fee: MinTxFeeFloor, ...}` |
|
||||
| quantumvm | Q-Chain | user-tx | `FlatPolicy{Fee: MinTxFeeFloor, ...}` |
|
||||
| identityvm | I-Chain | user-tx | `FlatPolicy{Fee: MinTxFeeFloor, ...}` |
|
||||
| thresholdvm | M-Chain | service-only | `NoUserTxPolicy{}` |
|
||||
| oraclevm | O-Chain | service-only | `NoUserTxPolicy{}` |
|
||||
| relayvm | R-Chain | service-only | `NoUserTxPolicy{}` |
|
||||
| graphvm | G-Chain | read-only | `NoUserTxPolicy{}` (GraphQL refuses `mutation`) |
|
||||
| evm | C-Chain | user-tx | native EVM gas (gas * gasPrice >= 0 enforced upstream) |
|
||||
| platformvm | P-Chain | user-tx | native `TxFee` field on Config |
|
||||
| avm | X-Chain | user-tx | native `TxFee` field on Config |
|
||||
|
||||
`MinTxFeeFloor = 1 mLUX = 1_000_000 nLUX` (the same minimum the P-Chain
|
||||
base fee enforces). User-facing chains MAY charge more; they MUST NOT
|
||||
charge less.
|
||||
|
||||
### Wiring contract
|
||||
|
||||
1. VM struct holds `feePolicy fee.Policy` (and `networkID uint32`).
|
||||
2. `Initialize` sets `feePolicy = fee.FlatPolicy{...}` (or
|
||||
`fee.NoUserTxPolicy{}` for service-only) from `init.Runtime.NetworkID`
|
||||
and calls `fee.Validate(vm.feePolicy)` — refuses zero-fee user-facing
|
||||
chains at boot, before any block is accepted.
|
||||
3. The canonical user-tx admission entry (e.g. `SubmitTx`, `IssueTx`,
|
||||
`InitiateBridgeTransfer`, mutating service RPCs) calls
|
||||
`policy.ValidateFee(paid, asset)` BEFORE mempool insert.
|
||||
4. Consensus-internal paths (engine→VM block delivery, replay, internal
|
||||
tx emission) bypass the fee gate — the policy gates only the
|
||||
*user-submitted* entrypoint.
|
||||
|
||||
### Where the gates live
|
||||
|
||||
- `vms/types/fee/policy.go` — interface + FlatPolicy + NoUserTxPolicy + Validate
|
||||
- `~/work/lux/chains/<vm>/feegate.go` — per-VM helper + gate method
|
||||
- `~/work/lux/chains/<vm>/feegate_test.go` — RejectsZeroFee + AcceptsMinFee
|
||||
- Oracle (O-Chain): `~/work/lux/oracle/vm/feegate.go` (re-exported by `~/work/lux/chains/oraclevm/`)
|
||||
- Relay (R-Chain): `~/work/lux/relay/vm/feegate.go` (re-exported by `~/work/lux/chains/relayvm/`)
|
||||
- Graph (G-Chain): `~/work/lux/chains/graphvm/feegate.go` (read-only; NoUserTxPolicy)
|
||||
|
||||
## Essential Commands
|
||||
|
||||
### Building
|
||||
@@ -205,20 +328,33 @@ Located in `vms/thresholdvm/fhe/`:
|
||||
| Gateway | `0x0200000000000000000000000000000000000083` |
|
||||
|
||||
### ZAP Transport (Zero-Copy App Proto)
|
||||
ZAP is the default high-performance binary wire protocol for VM<->Node communication.
|
||||
gRPC support is available via build tag for testing/compatibility.
|
||||
ZAP is the only wire protocol for VM<->Node communication. The gRPC
|
||||
fallback (and its `-tags=grpc` opt-in) was retired in v1.26.31 along
|
||||
with every `//go:build grpc` file under `node/`. There is one and only
|
||||
one way to talk to a Chain VM: ZAP.
|
||||
|
||||
**Build Tags:**
|
||||
**Build:**
|
||||
```bash
|
||||
go build # ZAP only (default, production)
|
||||
go build -tags=grpc # gRPC support (for testing/compatibility)
|
||||
go build # ZAP only — there are no build tags
|
||||
```
|
||||
|
||||
**Key Packages:**
|
||||
- `github.com/luxfi/api/zap` - Core wire protocol and message types
|
||||
- `github.com/luxfi/vm/rpc/sender` - p2p.Sender over ZAP/gRPC
|
||||
- `vms/rpcchainvm/sender/` - Node-side sender implementation
|
||||
- `vms/platformvm/warp/zwarp/` - ZAP-based warp signing client/server
|
||||
- `github.com/luxfi/api/zap` — Core wire protocol and message types (Layer A)
|
||||
- `github.com/luxfi/protocol/rpcdb` — rpcdb service spec / data carriers (Layer B)
|
||||
- `github.com/luxfi/node/db/rpcdb` — rpcdb Service + ZAP transport adapter (Layer C)
|
||||
- `vms/rpcchainvm/sender/` — Node-side `p2p.Sender` over ZAP
|
||||
- `vms/rpcchainvm/zap/` — ChainVM client/server over ZAP
|
||||
- `vms/platformvm/warp/zwarp/` — Warp signing over ZAP
|
||||
|
||||
**rpcdb Layered Topology:**
|
||||
- Layer A — wire framing: `github.com/luxfi/api/zap`
|
||||
- Layer B — rpcdb service spec: `github.com/luxfi/protocol/rpcdb`
|
||||
- Layer C — rpcdb impl: `node/db/rpcdb/{service.go, zap_server.go}`
|
||||
- `service.go` — transport-neutral `Service` wrapping `database.Database`
|
||||
- `zap_server.go` — ZAP transport adapter (only adapter)
|
||||
- One Service, one transport. The dual-adapter pattern stays available
|
||||
for future transports (each is a new file wrapping `*Service`), but
|
||||
ZAP is the only one shipping.
|
||||
|
||||
**Wire Protocol Format:**
|
||||
```
|
||||
@@ -233,11 +369,8 @@ go build -tags=grpc # gRPC support (for testing/compatibility)
|
||||
|
||||
**Sender Usage:**
|
||||
```go
|
||||
// ZAP transport (default)
|
||||
// ZAP transport — the only transport
|
||||
s := sender.ZAP(zapConn)
|
||||
|
||||
// gRPC transport (requires -tags=grpc build)
|
||||
s := sender.GRPC(senderpb.NewSenderClient(grpcConn))
|
||||
```
|
||||
|
||||
**Warp over ZAP:**
|
||||
@@ -335,6 +468,32 @@ RNS transport supports hybrid post-quantum cryptography combining classical algo
|
||||
- **Mixed Networks**: PQ and classical validators coexist
|
||||
- **Policy Enforcement**: `requirePostQuantum: true` rejects classical peers
|
||||
|
||||
### SchemeGate — cross-axis NodeIDScheme enforcement
|
||||
|
||||
`network/peer/scheme_gate.go` (v1.26.10) is the single primitive that
|
||||
turns a wire NodeID into a `(scheme, NodeID)` pair and runs the
|
||||
cross-axis check against the chain's `ChainSecurityProfile`.
|
||||
|
||||
- `SchemeGate{Profile, ClassicalCompatUnsafe, ActivationHeight}` is the
|
||||
chain-scoped policy object. One gate per chain, pinned at bootstrap.
|
||||
- `Classify(nodeID, scheme, height, site) (TypedNodeID, error)` is the
|
||||
single entry point. Callers pass a site tag (`"handshake"`,
|
||||
`"proposer"`, `"validator"`, `"mempool-sender"`) that appears in the
|
||||
refused-by error.
|
||||
- Migration path: `ActivationHeight` is the block at which a strict-PQ
|
||||
chain refuses any non-PQ `NodeIDScheme` byte at every height under
|
||||
the forward-only PQ policy. The classical `secp256k1` (0x90) scheme is
|
||||
refused at the gate; there is no transition window and no operator
|
||||
classical-compat escape hatch (strict-PQ chains refuse classical at
|
||||
every boundary, period).
|
||||
- Typed errors: `ErrSchemeGateConfig`, `ErrSchemeGateMismatch`,
|
||||
`ErrSchemeGateUnknownScheme`.
|
||||
|
||||
Wire form: `TypedNodeID = (NodeIDScheme byte, 20-byte NodeID)`. The
|
||||
20-byte storage/map-key form stays byte-identical; the scheme byte
|
||||
travels on the wire so a receiver knows which verifier to dispatch
|
||||
without trusting the chain profile alone.
|
||||
|
||||
### Testing PQ Forward Secrecy
|
||||
|
||||
```bash
|
||||
@@ -353,7 +512,7 @@ go test -v -run "TestHybrid" ./node/network/dialer/... -count=1
|
||||
|
||||
### 1. P2P Sender Interface
|
||||
Node's rpcchainvm implements `p2p.Sender` (from `github.com/luxfi/p2p`) for cross-chain messaging.
|
||||
The `sender` package is a gRPC implementation of `p2p.Sender`.
|
||||
The `sender` package is the ZAP-native implementation of `p2p.Sender`.
|
||||
|
||||
### 2. Chain Tracking
|
||||
Nodes don't automatically track chains. Use:
|
||||
@@ -600,7 +759,7 @@ Testing conducted on a single Lux validator node (testnet mode, macOS):
|
||||
**Benchmark Command:**
|
||||
```bash
|
||||
cd ~/work/lux/benchmarks
|
||||
LUX_ENDPOINT="http://localhost:9640/ext/bc/C/rpc" \
|
||||
NODE_ENDPOINT="http://localhost:9640/ext/bc/C/rpc" \
|
||||
PRIVATE_KEY="<funded_key>" \
|
||||
./bin/bench tps --chains=lux --duration=60s --concurrency=5
|
||||
```
|
||||
|
||||
+10
-10
@@ -2519,7 +2519,7 @@ The plugin version is unchanged at `27` and compatible with versions `v1.10.5 -
|
||||
|
||||
### Configs
|
||||
|
||||
- Changed the default value of `--network-allow-private-ips` to `false` when the `--network-id` is either `fuji` or `mainnet`
|
||||
- Changed the default value of `--network-allow-private-ips` to `false` when the `--network-id` is either `testnet` or `mainnet`
|
||||
- Added P-chain cache size configurations
|
||||
- `block-cache-size`
|
||||
- `tx-cache-size`
|
||||
@@ -2564,7 +2564,7 @@ The plugin version is unchanged at `27` and compatible with versions `v1.10.5 -
|
||||
- [x/sync] Update target locking by @patrick-ogrady in https://github.com/luxfi/node/pull/1763
|
||||
- Export warp errors for external use by @aaronbuchwald in https://github.com/luxfi/node/pull/1771
|
||||
- Remove unused networking constant by @StephenButtolph in https://github.com/luxfi/node/pull/1774
|
||||
- Change the default value of `--network-allow-private-ips` to `false` for `mainnet` and `fuji` by @StephenButtolph in https://github.com/luxfi/node/pull/1773
|
||||
- Change the default value of `--network-allow-private-ips` to `false` for `mainnet` and `testnet` by @StephenButtolph in https://github.com/luxfi/node/pull/1773
|
||||
- Remove context.TODO from tests by @StephenButtolph in https://github.com/luxfi/node/pull/1778
|
||||
- Replace linkeddb iterator with native DB range queries by @StephenButtolph in https://github.com/luxfi/node/pull/1752
|
||||
- Add support for measuring key size in caches by @StephenButtolph in https://github.com/luxfi/node/pull/1781
|
||||
@@ -2934,7 +2934,7 @@ This version is backwards compatible to [v1.10.0](https://github.com/luxfi/node/
|
||||
- Remove no-op changes from history results in https://github.com/luxfi/node/pull/1335
|
||||
- Cleanup type assertions in the linkedHashmap by @StephenButtolph in https://github.com/luxfi/node/pull/1341
|
||||
- Fix racy xvm tx access by @StephenButtolph in https://github.com/luxfi/node/pull/1349
|
||||
- Update Fuji beacon ips by @StephenButtolph in https://github.com/luxfi/node/pull/1354
|
||||
- Update Testnet beacon ips by @StephenButtolph in https://github.com/luxfi/node/pull/1354
|
||||
- Remove duplicate TLS verification by @StephenButtolph in https://github.com/luxfi/node/pull/1364
|
||||
- Adjust Merkledb Trie invalidation locking in https://github.com/luxfi/node/pull/1355
|
||||
- Use require in Lux bootstrapping tests by @StephenButtolph in https://github.com/luxfi/node/pull/1344
|
||||
@@ -2949,7 +2949,7 @@ This version is backwards compatible to [v1.10.0](https://github.com/luxfi/node/
|
||||
- [Issue-1368]: Panic in serializedPath.HasPrefix in https://github.com/luxfi/node/pull/1371
|
||||
- Add ValidatorsOnly flag to GetStake by @StephenButtolph in https://github.com/luxfi/node/pull/1377
|
||||
- Use proto in `x/sync` by @danlaine in https://github.com/luxfi/node/pull/1336
|
||||
- Update incorrect fuji beacon IPs by @StephenButtolph in https://github.com/luxfi/node/pull/1392
|
||||
- Update incorrect testnet beacon IPs by @StephenButtolph in https://github.com/luxfi/node/pull/1392
|
||||
- Update `api/` error handling by @StephenButtolph in https://github.com/luxfi/node/pull/1393
|
||||
- refactor concurrent work limiting in sync in `x/sync` by @danlaine in https://github.com/luxfi/node/pull/1347
|
||||
- Remove check for impossible condition in `x/sync` by @danlaine in https://github.com/luxfi/node/pull/1348
|
||||
@@ -3017,7 +3017,7 @@ The supported plugin version is `25`.
|
||||
- defer delegatee rewards until end of validator staking period by @dhrubabasu in https://github.com/luxfi/node/pull/1262
|
||||
- Initialize UptimeCalculator in TestPeer by @joshua-kim in https://github.com/luxfi/node/pull/1283
|
||||
- Add Lux liveness health checks by @StephenButtolph in https://github.com/luxfi/node/pull/1287
|
||||
- Skip AMI generation with Fuji tags by @StephenButtolph in https://github.com/luxfi/node/pull/1288
|
||||
- Skip AMI generation with Testnet tags by @StephenButtolph in https://github.com/luxfi/node/pull/1288
|
||||
- Use `maps.Equal` in `set.Equals` by @danlaine in https://github.com/luxfi/node/pull/1290
|
||||
- return accrued delegator rewards in `GetCurrentValidators` by @dhrubabasu in https://github.com/luxfi/node/pull/1291
|
||||
- Add zstd compression by @danlaine in https://github.com/luxfi/node/pull/1278
|
||||
@@ -3819,7 +3819,7 @@ The supported plugin version is `16`.
|
||||
|
||||
Please upgrade your node as soon as possible.
|
||||
|
||||
The changes in `v1.8.x` go into effect at 4 PM EDT on September 6th, 2022 on both Fuji and Mainnet. You should upgrade your node before the changes go into effect, otherwise they may experience loss of uptime.
|
||||
The changes in `v1.8.x` go into effect at 4 PM EDT on September 6th, 2022 on both Testnet and Mainnet. You should upgrade your node before the changes go into effect, otherwise they may experience loss of uptime.
|
||||
|
||||
The supported plugin version is `16`.
|
||||
|
||||
@@ -3833,7 +3833,7 @@ The supported plugin version is `16`.
|
||||
|
||||
Please upgrade your node as soon as possible.
|
||||
|
||||
The changes in `v1.8.x` go into effect at 4 PM EDT on September 6th, 2022 on both Fuji and Mainnet. You should upgrade your node before the changes go into effect, otherwise they may experience loss of uptime.
|
||||
The changes in `v1.8.x` go into effect at 4 PM EDT on September 6th, 2022 on both Testnet and Mainnet. You should upgrade your node before the changes go into effect, otherwise they may experience loss of uptime.
|
||||
|
||||
The supported plugin version is `16`.
|
||||
|
||||
@@ -3850,7 +3850,7 @@ The supported plugin version is `16`.
|
||||
|
||||
This is a mandatory security upgrade. Please upgrade your node **as soon as possible.**
|
||||
|
||||
The changes in the upgrade go into effect at **4 PM EDT on September 6th, 2022** on both Fuji and Mainnet. You should upgrade your node before the changes go into effect, otherwise they may experience loss of uptime.
|
||||
The changes in the upgrade go into effect at **4 PM EDT on September 6th, 2022** on both Testnet and Mainnet. You should upgrade your node before the changes go into effect, otherwise they may experience loss of uptime.
|
||||
|
||||
You may see some extraneous ERROR logs ("BAD BLOCK") on your node after upgrading. These may continue until the Apricot Phase 6 activation (at 4 PM EDT on September 6th).
|
||||
|
||||
@@ -4515,7 +4515,7 @@ This version is backwards compatible to [v1.7.0](https://github.com/luxfi/node/r
|
||||
- Added `--stake-supply-cap` which defaults to `720,000,000,000,000,000` nLUX.
|
||||
- Renamed `--bootstrap-multiput-max-containers-sent` to `--bootstrap-ancestors-max-containers-sent`.
|
||||
- Renamed `--bootstrap-multiput-max-containers-received` to `--bootstrap-ancestors-max-containers-received`.
|
||||
- Enforced that `--staking-enabled=false` can not be specified on public networks (`Fuji` and `Mainnet`).
|
||||
- Enforced that `--staking-enabled=false` can not be specified on public networks (`Testnet` and `Mainnet`).
|
||||
|
||||
### Metrics
|
||||
|
||||
@@ -4762,7 +4762,7 @@ This was the version used for the first public Lux Network deployment in Decembe
|
||||
### Network Support
|
||||
|
||||
- **Primary Network**: LUX mainnet and testnet
|
||||
- **Net Support**: Full L2/chain capabilities
|
||||
- **Net Support**: Full chain capabilities
|
||||
- **Cross-Chain**: Warp messaging and atomic swaps
|
||||
- **Validator Management**: Staking and delegation
|
||||
|
||||
|
||||
+4
-4
@@ -10,10 +10,10 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/luxfi/filesystem/perms"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/node/node"
|
||||
"github.com/luxfi/node/utils"
|
||||
"github.com/luxfi/filesystem/perms"
|
||||
"github.com/luxfi/sys/ulimit"
|
||||
|
||||
nodeconfig "github.com/luxfi/node/config/node"
|
||||
@@ -58,9 +58,9 @@ func New(config nodeconfig.Config) (App, error) {
|
||||
infoLevel, _ := log.ToLevel("info")
|
||||
logFactory := log.NewFactoryWithConfig(log.Config{
|
||||
RotatingWriterConfig: log.RotatingWriterConfig{
|
||||
MaxSize: 8, // 8MB per log file (reasonable for standard profile)
|
||||
MaxFiles: 5, // Keep 5 rotated files
|
||||
MaxAge: 7, // 7 days retention
|
||||
MaxSize: 8, // 8MB per log file (reasonable for standard profile)
|
||||
MaxFiles: 5, // Keep 5 rotated files
|
||||
MaxAge: 7, // 7 days retention
|
||||
Directory: config.DatabaseConfig.Path,
|
||||
},
|
||||
DisplayLevel: infoLevel,
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
# RED LP-023 ROUND 6 — Adversarial Review of v3.7 (67cff34428)
|
||||
|
||||
Scope: diff 3f8f7e79ec..67cff34428 across `vms/platformvm/txs/zap_native/`,
|
||||
`vms/platformvm/network/zap_native_admission.go`, `.github/workflows/zap-audit.yml`.
|
||||
|
||||
Verdict (round 6 v2, 2026-06-02): **FIX-THEN-SHIP**. V1 retracted on
|
||||
re-verification. V2 (textual-audit-gate meta-attack), V3 (legacy `*Owner()`
|
||||
scope leak), V4 (BLS alloc amplification), V5 (no block-level cap) stand.
|
||||
None block a fix-then-ship — V2 is the priority because it disarms the gate
|
||||
infrastructure that prevents future regressions.
|
||||
|
||||
---
|
||||
|
||||
## V1 — RETRACTED — TransferChainOwnershipTx.Verify is correctly gated in v3.7
|
||||
|
||||
**Re-verification (round 6 v2, 2026-06-02)**: The original V1 finding was
|
||||
based on a working-tree probe that mutated `tx_verify.go:330-336` to insert
|
||||
an `_ = "SyntacticVerify"` no-op probe; the actual `67cff34428` commit
|
||||
ships the correctly-gated body:
|
||||
|
||||
```go
|
||||
func (t TransferChainOwnershipTx) Verify() error {
|
||||
o := stubFromTuple(t.OwnerThreshold(), t.OwnerLocktime(), t.OwnerAddress())
|
||||
if err := o.SyntacticVerify(); err != nil {
|
||||
return fmt.Errorf("TransferChainOwnershipTx.Owner: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
Repro: `git show 67cff34428:vms/platformvm/txs/zap_native/tx_verify.go |
|
||||
sed -n '330,346p'`. The R6V8 gate Blue closed at 30c9608255 is intact at
|
||||
v3.7. **V1 is retracted — no regression in the actual commit.**
|
||||
|
||||
The misclassification root cause: the prior Red round 6 author had local
|
||||
uncommitted edits crafting a PoC for V2 (meta-attack on the textual audit
|
||||
gate). When the audit was written, the PoC was misread as Blue's regression
|
||||
instead of a Red-injected demonstration.
|
||||
|
||||
**V2 below stands** — the textual audit gate is genuinely defeatable by
|
||||
string literals; the PoC that proved it was real, but it lives in the
|
||||
adversary's working tree, not the committed v3.7. The attack vector remains:
|
||||
**a future legitimate edit by Blue** that drops the SyntacticVerify call but
|
||||
keeps the token in any form (comment / string literal / dead-code branch /
|
||||
imported constant) will pass CI today.
|
||||
|
||||
---
|
||||
|
||||
## V2 — CRITICAL — ADMISSION — Audit gate fooled by string-literal token (meta-attack on R4V7 audit)
|
||||
|
||||
**Class**: ADMISSION. **Severity**: CRITICAL (proof: V1 above ships and passes).
|
||||
|
||||
`audit_test.go:359` and `.github/workflows/zap-audit.yml`
|
||||
owner-bearing-syntacticverify-gate use `strings.Contains(body, "SyntacticVerify")`
|
||||
on the brace-bounded Verify body. The check is purely textual; it does not
|
||||
distinguish:
|
||||
|
||||
- A real `stub.SyntacticVerify()` call site
|
||||
- A `_ = "SyntacticVerify"` no-op string literal
|
||||
- A `// SyntacticVerify` source comment
|
||||
- A constant-false branch: `if false { x.SyntacticVerify() }`
|
||||
- A name-conflict shadow: `var SyntacticVerify int`
|
||||
|
||||
Blue's own self-audit vector #5 ("audit gate meta-attack: construct a Verify
|
||||
body that satisfies textual SyntacticVerify match but doesn't call at
|
||||
runtime") materialized in V1. The audit gate cannot be the only line of
|
||||
defense for `R4V7`.
|
||||
|
||||
The same defect applies to the `ChainsListEmbeddersCallMustVerify` and
|
||||
`ValidatorsListEmbeddersCallMustVerify` gates — they check for `.MustVerify(`
|
||||
the same way, and any string literal containing `.MustVerify(` defeats them.
|
||||
|
||||
**Impact**: Any future Verify body that drops the call but keeps the token
|
||||
text passes CI. Same primitive enables silent removal of MustVerify on
|
||||
ChainsList and ValidatorsList.
|
||||
|
||||
**Fix** (the only correct fix): switch from textual to AST-level. Either:
|
||||
1. `go/parser` + `go/ast` walk inside the audit test — locate the
|
||||
`*ast.FuncDecl` for each receiver type, then scan for an
|
||||
`*ast.CallExpr` with `Sel.Name == "SyntacticVerify"` or `"MustVerify"`.
|
||||
This rejects string literals, comments, and dead-code branches by
|
||||
construction.
|
||||
2. Replace the audit gate entirely with positive runtime tests: every
|
||||
Owner-bearing tx type MUST have a `TestT_Verify_RejectsZeroThreshold`
|
||||
row in `r6_verify_test.go` (one and only one way to assert the gate
|
||||
ran). Regression caught by `go test ./...`, not a custom grep.
|
||||
|
||||
Bonus: Move BOTH audit gates (Owner-bearing + MustVerify) to AST-based at
|
||||
the same time so the meta-attack class is closed.
|
||||
|
||||
---
|
||||
|
||||
## V3 — HIGH — ADMISSION — Audit gate scope leak: legacy txs `*Owner()` consumers bypass
|
||||
|
||||
**Class**: ADMISSION / GOVERNANCE. **Severity**: HIGH (acknowledged scope, but
|
||||
the scope description in the brief is wrong).
|
||||
|
||||
The audit gate scopes the grep to the `zap_native` package. The legacy AVAX
|
||||
tx interface (`vms/platformvm/txs/*.go`) defines `RewardsOwner()`,
|
||||
`ValidationRewardsOwner()`, `DelegationRewardsOwner()` on `AddValidatorTx`,
|
||||
`AddDelegatorTx`, `AddPermissionlessValidatorTx`,
|
||||
`AddPermissionlessDelegatorTx`. These accessors return `fx.Owner` (not the
|
||||
`(uint32, uint64, ids.ShortID)` tuple), so the audit gate's regex skips
|
||||
them by design.
|
||||
|
||||
**But the executor consumes them without `Verify()`**:
|
||||
|
||||
- `vms/platformvm/txs/executor/proposal_tx_executor.go:303` —
|
||||
`uValidatorTx.ValidationRewardsOwner()`
|
||||
- `vms/platformvm/txs/executor/proposal_tx_executor.go:340` —
|
||||
`uValidatorTx.DelegationRewardsOwner()`
|
||||
- `vms/platformvm/txs/executor/proposal_tx_executor.go:429` —
|
||||
`uDelegatorTx.RewardsOwner()`
|
||||
- `vms/platformvm/service.go:748-755` — same set, RPC path.
|
||||
|
||||
These run on the staking reward path. A legacy tx with a malformed Owner
|
||||
(`Threshold > Addrs.Len()`) currently relies on the AVAX legacy parser to
|
||||
reject. The audit gate would not detect a new code path that consumes
|
||||
`*RewardsOwner()` without verification.
|
||||
|
||||
**Impact**: A future tx type or executor body that consumes Owner data via
|
||||
the `fx.Owner` interface bypasses the gate silently. Quorum-skew / authz
|
||||
bypass primitive on the staking reward path. Detectability: zero CI signal.
|
||||
|
||||
**Fix**: Either (a) broaden the audit gate to include `fx.Owner`-typed
|
||||
returns (the AST refactor in V2 naturally subsumes this), or (b) document
|
||||
the legacy boundary explicitly in `tx_verify.go` and add a positive test
|
||||
matrix asserting every legacy `*Owner()` access site has a corresponding
|
||||
SyntacticVerify upstream.
|
||||
|
||||
---
|
||||
|
||||
## V4 — HIGH — CRYPTO/DOS — BLS pubkey allocation amplification (Blue's self-audit V1, NOT fixed)
|
||||
|
||||
**Class**: DOS. **Severity**: HIGH.
|
||||
|
||||
`validators_list.go:101-131` — `ValidatorRecord.BLSPubKey()` and
|
||||
`BLSPoP()` allocate a fresh `[]byte` per call. `MustVerify` walks N
|
||||
records, calling each accessor once (line 224, 229 — used inside
|
||||
`bytes.Equal`). At N=1024 cap that's:
|
||||
|
||||
- 1024 × 48B pubkey copies = 48 KB
|
||||
- 1024 × 96B PoP copies = 96 KB
|
||||
- Total: 144 KB per `MustVerify` call
|
||||
|
||||
But `tx_verify.go:285-311` calls `vals.At(i).BLSPubKey()` AND `BLSPoP()`
|
||||
AGAIN per validator (for the BLS pairing). Cumulative: ~288 KB allocs
|
||||
per `CreateSovereignL1Tx.Verify`. Mempool-DoS primitive: a peer that
|
||||
gossips well-formed-but-cap-bound L1 spawn txs (or replays existing
|
||||
ones) forces 288 KB GC churn per gossip event. At 1k txs/sec this is
|
||||
288 MB/sec of allocation in admission.
|
||||
|
||||
Blue's self-audit V1 listed this and shipped no mitigation.
|
||||
|
||||
**Fix**: either (a) `MustVerify` reads bytes directly via the underlying
|
||||
`zap.Object.Uint8` loop without copy-out, or (b) cache the
|
||||
`[BLSPubKeySize]byte` and `[BLSPoPSize]byte` arrays on `ValidatorRecord`
|
||||
under a lazy-init guard. (a) is simpler and orthogonal — bytes are
|
||||
read-only at verify time.
|
||||
|
||||
---
|
||||
|
||||
## V5 — MEDIUM — DOS — No per-block tx-count cap (Blue self-audit V2, NOT fixed)
|
||||
|
||||
**Class**: DOS. **Severity**: MEDIUM.
|
||||
|
||||
`MaxChainsPerL1=16` and `MaxValidatorsPerL1=1024` are per-tx caps.
|
||||
There is no block-level aggregate cap. An attacker submits N copies of
|
||||
a 1024-validator `CreateSovereignL1Tx`; each tx independently passes
|
||||
MustVerify, the block packer accepts up to mempool capacity, and the
|
||||
verifier walks 1024N BLS pairings on block accept. At a 2-second block
|
||||
time and tx min-fee, the amplification is bounded only by mempool RAM.
|
||||
|
||||
Blue's self-audit V2 listed this and shipped no mitigation.
|
||||
|
||||
**Fix**: Add a block-level cap in the standard_tx_executor commit path:
|
||||
`sum(vals.Len()) over Verify calls in block <= GlobalMaxValidatorsPerBlock`
|
||||
(e.g. 4096). Out-of-budget txs are deferred to next block, not dropped —
|
||||
the encoder paid the fee.
|
||||
|
||||
---
|
||||
|
||||
## V6 — MEDIUM — ADMISSION — Admission gate parse-and-rewrap inefficiency + ordering bug
|
||||
|
||||
**Class**: ADMISSION. **Severity**: MEDIUM.
|
||||
|
||||
`zap_native_admission.go:113-131` (`zapNativeWireKindNotYetExecutable`)
|
||||
calls `WrapCreateSovereignL1Tx`, `WrapRegisterL1ValidatorTx`, `WrapConvertNetworkToL1Tx`
|
||||
in sequence. Each wrap reparses the header to check the kind discriminator.
|
||||
The brief comments "cheap" but the parse goes through `parseAndCheckKind`,
|
||||
which is the same body that ran on the inbound RPC — a measurable hot-path
|
||||
re-walk.
|
||||
|
||||
Worse, **order matters**: kind 7 (`RegisterL1Validator`) is in the gated
|
||||
set, but `WrapCreateSovereignL1Tx` is called FIRST. A wire buffer with
|
||||
discriminator kind 7 will fail `WrapCreateSovereignL1Tx` with
|
||||
`ErrWrongTxKind`, then succeed `WrapRegisterL1ValidatorTx`. Fine for the
|
||||
returned `kind`, but the early-fail path consumes parse work unnecessarily.
|
||||
|
||||
Cheap fix: read the kind discriminator once via `parseAndCheckKind` directly,
|
||||
then a single `switch`. Saves 2/3 of the work on the common-case path.
|
||||
|
||||
**Detectability**: Not a security bug per se; it's a microoptimization
|
||||
miss that compounds with V4/V5 amplification.
|
||||
|
||||
---
|
||||
|
||||
## V7 — LOW — GOVERNANCE — `ATTACK PROBE V3` comment ships in production source
|
||||
|
||||
**Class**: GOVERNANCE. **Severity**: LOW (process bug; the underlying
|
||||
vulnerability is V1).
|
||||
|
||||
`tx_verify.go:330-331` documents the attack vector inline, in production
|
||||
Go source, with `ATTACK PROBE V3` as the function-level comment. Even
|
||||
after V1 is fixed, the comment ships as a forensic breadcrumb advertising
|
||||
the audit-gate weakness to any reader with `git log`.
|
||||
|
||||
**Fix**: When restoring the gate body (V1), strip the entire comment.
|
||||
Production source is not a scratchpad for red-team probes.
|
||||
|
||||
---
|
||||
|
||||
## V8 — INFO — GOVERNANCE — `ListStride` overflow surface (Blue's free-form probe answer)
|
||||
|
||||
**Class**: CRYPTO/DOS. **Severity**: INFO.
|
||||
|
||||
Blue asked: "Stride*N uint32 multiply overflow in BoundChainsList /
|
||||
BoundValidatorsList: can you construct N such that stride*N overflows
|
||||
uint32 wraparound to a small value?"
|
||||
|
||||
Answer: NO. `zap/zap.go:393-427` `ListStride` performs `uint64(length) *
|
||||
uint64(minStride) > bufRem` — both operands are uint32, product is uint64,
|
||||
no wraparound. The clamp is correct. This vector is closed.
|
||||
|
||||
Documenting here so future audits don't re-probe.
|
||||
|
||||
---
|
||||
|
||||
## Blue Handoff
|
||||
|
||||
What Blue got right:
|
||||
- `MaxValidatorsPerL1 = 1024` and `MaxChainsPerL1 = 16` caps are well-chosen
|
||||
and wired BEFORE BLS pairing. Cheap-gate-first ordering is correct.
|
||||
- `ValidatorsList.MustVerify` 5-floor invariants (cap, weight, BLS-non-zero,
|
||||
expiry-non-zero) are the right set and tests pass per-invariant.
|
||||
- `ListStride` uint64 product avoids overflow — V8 closed.
|
||||
- The `MustVerify` receiver-name choice (R7V8) genuinely does make
|
||||
"I forgot to call this" grep-able — at the call site. The PROBLEM is the
|
||||
call-site grep, not the receiver-name choice.
|
||||
- Admission gate (`zap_native_admission.go`) correctly orders Path 1 / Path 2
|
||||
with the wrapping verifier as inner — defense-in-depth on the legacy
|
||||
AND wire-bytes paths.
|
||||
|
||||
What Blue missed:
|
||||
- The audit gate (the new infrastructure shipped in this batch) is fooled
|
||||
by string literals (V2). This is the single most important finding.
|
||||
The gate Blue added to prevent regressions of R4V7 cannot detect the
|
||||
class of regression it exists to prevent — a PoC against the working
|
||||
tree confirmed this experimentally.
|
||||
- Legacy `*Owner()` consumers in `proposal_tx_executor.go` and `service.go`
|
||||
(V3) are out-of-scope for the audit gate but are real Owner consumers.
|
||||
- BLS pubkey/PoP allocation amplification (V4) — Blue self-audited and
|
||||
shipped no fix.
|
||||
- Per-block cap absence (V5) — Blue self-audited and shipped no fix.
|
||||
|
||||
Fix priority for Blue:
|
||||
1. **V2**: Replace textual audit gate with AST-based, OR replace with
|
||||
positive-test matrix in `r6_verify_test.go`. One and only one way.
|
||||
2. **V3**: Broaden audit scope to `fx.Owner`-returning accessors, OR
|
||||
document the legacy boundary explicitly with a positive test matrix.
|
||||
3. **V4**: `ValidatorRecord.BLSPubKey/BLSPoP` no-copy path for MustVerify
|
||||
(or cache on record). 144 KB → near-zero.
|
||||
4. **V5**: Block-level validator/chain budget in standard_tx_executor.
|
||||
5. **V6**: Single-pass kind discriminator read in admission gate.
|
||||
|
||||
Re-review scope: V2 and V3 mandatory before re-review. V4, V5, V6 can land
|
||||
in a follow-up batch but Blue should report the plan. V1 retracted —
|
||||
no action required.
|
||||
|
||||
---
|
||||
|
||||
RED COMPLETE (round 6 v2 — 2026-06-02 re-verification).
|
||||
Total: 1 CRITICAL retracted, 1 critical, 2 high, 2 medium, 1 low, 1 info.
|
||||
Top 3 for Blue to fix:
|
||||
1. V2 — Audit gate fooled by string-literal token (meta-attack; gate cannot
|
||||
detect the very class of regression it exists to prevent)
|
||||
2. V3 — Legacy `*Owner()` consumers in `proposal_tx_executor.go` + `service.go`
|
||||
bypass audit gate scope (fx.Owner-typed return)
|
||||
3. V4 — BLS pubkey/PoP allocation amplification (144 KB per MustVerify at
|
||||
N=1024; Blue self-audit V1, not fixed)
|
||||
Re-review needed: yes — V2 and V3 must close before re-review.
|
||||
Recommendation: **fix-then-ship**.
|
||||
|
||||
V1 retraction note (2026-06-02): the original "R6V8 undone" finding was a
|
||||
working-tree-only PoC that proved V2's textual-gate weakness; the actual
|
||||
67cff34428 commit ships the correctly-gated Verify body. The PoC remains
|
||||
a valid demonstration of V2 — a future legitimate Blue edit that drops
|
||||
the call while keeping the token in any form passes CI today.
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
validators "github.com/luxfi/validators"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
metrics "github.com/luxfi/metric"
|
||||
validators "github.com/luxfi/validators"
|
||||
)
|
||||
|
||||
// NewManager creates a new benchlist manager
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/crypto/hash"
|
||||
)
|
||||
|
||||
// BenchmarkHashingComputeHash256 benchmarks SHA256 hashing performance
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/database/memdb"
|
||||
"github.com/luxfi/database/prefixdb"
|
||||
"github.com/luxfi/database/versiondb"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/crypto/hash"
|
||||
)
|
||||
|
||||
// BenchmarkMemoryDatabase benchmarks in-memory database operations
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
compression "github.com/luxfi/compress"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
// BenchmarkMessageCompression benchmarks message compression
|
||||
|
||||
Vendored
+1
-1
@@ -6,9 +6,9 @@ package lru
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/container/linked"
|
||||
"github.com/luxfi/node/cache"
|
||||
"github.com/luxfi/utils"
|
||||
"github.com/luxfi/container/linked"
|
||||
)
|
||||
|
||||
var _ cache.Cacher[struct{}, struct{}] = (*Cache[struct{}, struct{}])(nil)
|
||||
|
||||
Vendored
+1
-1
@@ -6,9 +6,9 @@ package lru
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/container/linked"
|
||||
"github.com/luxfi/node/cache"
|
||||
"github.com/luxfi/utils"
|
||||
"github.com/luxfi/container/linked"
|
||||
)
|
||||
|
||||
var _ cache.Cacher[struct{}, any] = (*SizedCache[struct{}, any])(nil)
|
||||
|
||||
Vendored
+1
-1
@@ -6,8 +6,8 @@ package cache
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/utils"
|
||||
"github.com/luxfi/container/linked"
|
||||
"github.com/luxfi/utils"
|
||||
)
|
||||
|
||||
var _ Cacher[struct{}, struct{}] = (*LRU[struct{}, struct{}])(nil)
|
||||
|
||||
Vendored
+1
-1
@@ -6,8 +6,8 @@ package cache
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/utils"
|
||||
"github.com/luxfi/container/linked"
|
||||
"github.com/luxfi/utils"
|
||||
)
|
||||
|
||||
var _ Cacher[struct{}, any] = (*sizedLRU[struct{}, any])(nil)
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/vm/chain"
|
||||
consensusvertex "github.com/luxfi/consensus/engine/vertex"
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/platformvm/fx"
|
||||
"github.com/luxfi/runtime"
|
||||
vmcore "github.com/luxfi/vm"
|
||||
"github.com/luxfi/vm/chain"
|
||||
"github.com/luxfi/warp"
|
||||
)
|
||||
|
||||
|
||||
+131
-37
@@ -70,11 +70,17 @@ import (
|
||||
"github.com/luxfi/vm/fx"
|
||||
|
||||
// "github.com/luxfi/node/vms/metervm" // Temporarily disabled - needs consensus package updates
|
||||
"github.com/luxfi/utxo/bls12381fx"
|
||||
"github.com/luxfi/utxo/ed25519fx"
|
||||
"github.com/luxfi/utxo/mldsafx"
|
||||
"github.com/luxfi/utxo/nftfx"
|
||||
|
||||
"github.com/luxfi/utxo/propertyfx"
|
||||
// "github.com/luxfi/node/vms/proposervm"
|
||||
"github.com/luxfi/utxo/schnorrfx"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
"github.com/luxfi/utxo/secp256r1fx"
|
||||
"github.com/luxfi/utxo/slhdsafx"
|
||||
// "github.com/luxfi/node/vms/tracedvm" // Temporarily disabled - needs consensus package updates
|
||||
|
||||
// "github.com/luxfi/node/proto/p2p" // Available if needed for protobuf parsing
|
||||
@@ -128,10 +134,26 @@ var (
|
||||
errNotBootstrapped = errors.New("chains not bootstrapped")
|
||||
errPartialSyncAsAValidator = errors.New("partial sync should not be configured for a validator")
|
||||
|
||||
// fxs lists every Feature eXtension factory the node knows how to load
|
||||
// when a chain genesis references it. Must stay in sync with the X-Chain
|
||||
// FxIDs[] block in genesis/builder/builder.go: any fx ID present there
|
||||
// MUST be registered here, otherwise chain init fails with "fx ... not
|
||||
// found" at startup. Keep the order and grouping mirroring genesis to
|
||||
// make drift obvious in review.
|
||||
fxs = map[ids.ID]fx.Factory{
|
||||
// Legacy / classical
|
||||
secp256k1fx.ID: &secp256k1fx.Factory{},
|
||||
nftfx.ID: &nftfx.Factory{},
|
||||
propertyfx.ID: &propertyfx.Factory{},
|
||||
// Post-quantum (FIPS 203/204/205 family)
|
||||
mldsafx.ID: &mldsafx.Factory{},
|
||||
slhdsafx.ID: &slhdsafx.Factory{},
|
||||
// EdDSA / Schnorr / NIST P-256
|
||||
ed25519fx.ID: &ed25519fx.Factory{},
|
||||
secp256r1fx.ID: &secp256r1fx.Factory{},
|
||||
schnorrfx.ID: &schnorrfx.Factory{},
|
||||
// Pairing-friendly curve
|
||||
bls12381fx.ID: &bls12381fx.Factory{},
|
||||
}
|
||||
|
||||
_ Manager = (*manager)(nil)
|
||||
@@ -323,7 +345,7 @@ type ManagerConfig struct {
|
||||
PartialSyncPrimaryNetwork bool
|
||||
Server server.Server // Handles HTTP API calls
|
||||
AtomicMemory *atomic.Memory
|
||||
XAssetID ids.ID
|
||||
UTXOAssetID ids.ID
|
||||
SkipBootstrap bool // Skip bootstrapping and start processing immediately
|
||||
EnableAutomining bool // Enable automining in POA mode
|
||||
XChainID ids.ID // ID of the X-Chain,
|
||||
@@ -363,6 +385,17 @@ type ManagerConfig struct {
|
||||
ChainDataDir string
|
||||
|
||||
Nets *Nets
|
||||
|
||||
// SecurityProfile is the chain-wide ChainSecurityProfile resolved
|
||||
// from the genesis pin during node bootstrap (F102). The chain
|
||||
// manager forwards a ProfileID + ProfileHash pin to every VM
|
||||
// Initialize call via the VM config bytes so the rpcchainvm plugin
|
||||
// (coreth C-Chain) inherits the chain-wide posture without
|
||||
// re-resolving genesis. Closes red-team finding F118.
|
||||
//
|
||||
// Nil under classical-compat or pre-locked-profile chains; the
|
||||
// stamping pass is a no-op in that case.
|
||||
SecurityProfile *consensusconfig.ChainSecurityProfile
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
@@ -485,31 +518,12 @@ func New(config *ManagerConfig) (Manager, error) {
|
||||
// QueueChainCreation queues a chain creation request
|
||||
// Invariant: Tracked Net must be checked before calling this function
|
||||
func (m *manager) QueueChainCreation(chainParams ChainParameters) {
|
||||
// Check for chain ID mapping override for C-Chain
|
||||
m.Log.Info("QueueChainCreation called",
|
||||
log.String("vmID", chainParams.VMID.String()),
|
||||
log.String("EVMID", constants.EVMID.String()),
|
||||
log.Bool("vmIDEqualsEVMID", chainParams.VMID == constants.EVMID),
|
||||
log.String("envVar", os.Getenv("LUX_CHAIN_ID_MAPPING_C")),
|
||||
)
|
||||
|
||||
if chainParams.VMID == constants.EVMID && os.Getenv("LUX_CHAIN_ID_MAPPING_C") != "" {
|
||||
mappedID := os.Getenv("LUX_CHAIN_ID_MAPPING_C")
|
||||
parsedID, err := ids.FromString(mappedID)
|
||||
if err == nil {
|
||||
m.Log.Info("Using mapped blockchain ID for C-Chain",
|
||||
log.String("original", chainParams.ID.String()),
|
||||
log.String("mapped", parsedID.String()),
|
||||
)
|
||||
chainParams.ID = parsedID
|
||||
} else {
|
||||
m.Log.Warn("Invalid chain ID mapping",
|
||||
log.String("mapping", mappedID),
|
||||
log.Err(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Register blockchain→chain mapping with the network layer so gossip
|
||||
// can resolve which validator set to use for this blockchain's blocks.
|
||||
if chainParams.ChainID != constants.PrimaryNetworkID && m.Net != nil {
|
||||
@@ -616,6 +630,35 @@ func (m *manager) createChain(chainParams ChainParameters) {
|
||||
}
|
||||
|
||||
chainAlias := m.PrimaryAliasOrDefault(chainParams.ID)
|
||||
|
||||
// Plugin-not-loaded is a deliberate skip, not a failure.
|
||||
//
|
||||
// Each validator opts into chains by loading their VM plugin
|
||||
// (Liquid loads /dex/fhe; doesn't load Lux's upstream
|
||||
// EVM plugin for C-Chain because Liquid runs its own EVM as a
|
||||
// chain on the primary's P-chain). When the chain manager hits a
|
||||
// chain whose VM isn't registered, that's the validator's "no I
|
||||
// don't validate this one" signal — log info, mark the slot
|
||||
// bootstrapped, and return WITHOUT registering a failing health
|
||||
// check. The chain stays in pending state for hot-load if the
|
||||
// plugin shows up later (e.g. via lpm install).
|
||||
//
|
||||
// The old behavior (always register a failing health check) made
|
||||
// /ext/health return 503 for any opted-out chain, which made
|
||||
// kubelet liveness probes kill the validator pod. That made
|
||||
// chain participation effectively all-or-nothing per validator.
|
||||
// Now: validators participate per-plugin, opt-in.
|
||||
if errors.Is(err, vms.ErrNotFound) {
|
||||
m.Log.Info("chain VM plugin not loaded — opting out of this chain",
|
||||
log.Stringer("chainID", chainParams.ChainID),
|
||||
log.Stringer("chainID", chainParams.ID),
|
||||
log.String("chainAlias", chainAlias),
|
||||
log.Stringer("vmID", chainParams.VMID),
|
||||
)
|
||||
sb.Bootstrapped(chainParams.ID)
|
||||
return
|
||||
}
|
||||
|
||||
m.Log.Warn("non-critical chain failed to initialize",
|
||||
log.Stringer("chainID", chainParams.ChainID),
|
||||
log.Stringer("chainID", chainParams.ID),
|
||||
@@ -624,15 +667,13 @@ func (m *manager) createChain(chainParams ChainParameters) {
|
||||
log.Err(err),
|
||||
)
|
||||
|
||||
// Non-critical chain failed (e.g. D-Chain with missing VM plugin).
|
||||
// Mark it as bootstrapped so it doesn't block the node's health check.
|
||||
// The chain-specific health check below still reports the failure.
|
||||
// Non-critical chain failed for a real reason (not missing
|
||||
// plugin). Mark it as bootstrapped so it doesn't block the
|
||||
// node-level health check, but DO register a per-chain failing
|
||||
// health check so operators see something is wrong with a chain
|
||||
// they did opt into.
|
||||
sb.Bootstrapped(chainParams.ID)
|
||||
|
||||
// Register the health check for this chain regardless of if it was
|
||||
// created or not. This attempts to notify the node operator that their
|
||||
// node may not be properly validating the net they expect to be
|
||||
// validating.
|
||||
healthCheckErr := fmt.Errorf("failed to create chain on net %s: %w", chainParams.ChainID, err)
|
||||
err := m.Health.RegisterHealthCheck(
|
||||
chainAlias,
|
||||
@@ -874,16 +915,15 @@ func (m *manager) buildChain(chainParams ChainParameters, sb nets.Net) (*chainIn
|
||||
|
||||
XChainID: m.XChainID,
|
||||
CChainID: m.CChainID,
|
||||
XAssetID: m.XAssetID,
|
||||
UTXOAssetID: m.UTXOAssetID,
|
||||
ChainDataDir: chainDataDir,
|
||||
|
||||
BCLookup: m,
|
||||
ValidatorState: getValidatorState(m.validatorState),
|
||||
SharedMemory: chainSharedMemory,
|
||||
Metrics: chainMetricsGatherer,
|
||||
Log: chainLog,
|
||||
WarpSigner: warpSigner,
|
||||
NetworkUpgrades: &m.Upgrades,
|
||||
BCLookup: m,
|
||||
ValidatorState: getValidatorState(m.validatorState),
|
||||
SharedMemory: chainSharedMemory,
|
||||
Metrics: chainMetricsGatherer,
|
||||
Log: chainLog,
|
||||
WarpSigner: warpSigner,
|
||||
}
|
||||
|
||||
// Get a factory for the vm we want to use on our chain
|
||||
@@ -992,8 +1032,12 @@ func (m *manager) buildChain(chainParams ChainParameters, sb nets.Net) (*chainIn
|
||||
}
|
||||
|
||||
// Initialize the VM if it supports the Initialize interface
|
||||
// Inject automining config for dev mode (applies to C-Chain/coreth only)
|
||||
// Inject automining config for dev mode (applies to C-Chain/coreth only),
|
||||
// then stamp the chain-wide SecurityProfile pin into the C-Chain
|
||||
// JSON config (F118) so the rpcchainvm plugin can resolve the
|
||||
// profile on its side of the boundary.
|
||||
vmConfigBytes := m.injectAutominingConfig(chainParams.VMID, chainConfig.Config)
|
||||
vmConfigBytes = m.injectSecurityProfileConfig(chainParams.VMID, vmConfigBytes)
|
||||
m.Log.Info("initializing VM", log.Stringer("chainID", chainParams.ID))
|
||||
initCtx, initCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer initCancel()
|
||||
@@ -1438,7 +1482,7 @@ func (m *manager) createDAG(
|
||||
}
|
||||
}
|
||||
|
||||
// Linearize the DAG chain (required for X-Chain post-Cortina)
|
||||
// Linearize the DAG chain (required for X-Chain).
|
||||
// This transitions the chain from DAG mode to linear block mode.
|
||||
if vmInitialized {
|
||||
if linearVM, ok := vmImpl.(interface {
|
||||
@@ -1826,6 +1870,56 @@ func (m *manager) getChainConfig(id ids.ID) (ChainConfig, error) {
|
||||
return ChainConfig{}, nil
|
||||
}
|
||||
|
||||
// injectSecurityProfileConfig stamps the chain-wide ChainSecurityProfile
|
||||
// pin into the C-Chain JSON config bytes so the coreth plugin VM can
|
||||
// resolve the profile on its side of the rpcchainvm boundary. Closes
|
||||
// red-team finding F118.
|
||||
//
|
||||
// The pin form is intentionally minimal — ProfileID byte + ProfileHash
|
||||
// hex — so this manager package does not need to round-trip the full
|
||||
// ChainSecurityProfile struct. The plugin VM calls
|
||||
// consensusconfig.ProfileByID + ComputeHash and refuses the chain if the
|
||||
// pinned hash does not match the live canonical content. Forked binaries
|
||||
// that swap canonical profile content fail the hash compare; legitimate
|
||||
// upgrades land via a new ProfileID and ProfileHash pair.
|
||||
//
|
||||
// Only applies to C-Chain (EVMID); other VMs are passed through.
|
||||
func (m *manager) injectSecurityProfileConfig(vmID ids.ID, configBytes []byte) []byte {
|
||||
if m.SecurityProfile == nil {
|
||||
return configBytes
|
||||
}
|
||||
if vmID != constants.EVMID {
|
||||
return configBytes
|
||||
}
|
||||
|
||||
// Parse existing config or create empty object.
|
||||
var cfg map[string]interface{}
|
||||
if len(configBytes) > 0 {
|
||||
if err := json.Unmarshal(configBytes, &cfg); err != nil {
|
||||
m.Log.Warn("failed to parse C-Chain config for security profile injection, creating new config",
|
||||
log.Err(err))
|
||||
cfg = make(map[string]interface{})
|
||||
}
|
||||
} else {
|
||||
cfg = make(map[string]interface{})
|
||||
}
|
||||
|
||||
cfg["lux-security-profile"] = map[string]interface{}{
|
||||
"profileID": m.SecurityProfile.ProfileID & 0xff,
|
||||
"profileHashHex": fmt.Sprintf("%x", m.SecurityProfile.ProfileHash[:]),
|
||||
}
|
||||
|
||||
out, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
m.Log.Warn("failed to marshal C-Chain config after security profile injection", log.Err(err))
|
||||
return configBytes
|
||||
}
|
||||
m.Log.Info("injected chain security profile pin into C-Chain plugin config",
|
||||
log.Uint32("profileID", m.SecurityProfile.ProfileID),
|
||||
log.String("profileName", m.SecurityProfile.ProfileName))
|
||||
return out
|
||||
}
|
||||
|
||||
// injectAutominingConfig modifies the config bytes to include enable-automining flag
|
||||
// when dev mode automining is enabled. This is used for C-Chain (coreth) to enable
|
||||
// anvil-like block production behavior.
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/vm"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/metric"
|
||||
"github.com/luxfi/node/nets"
|
||||
"github.com/luxfi/node/vms"
|
||||
"github.com/luxfi/vm"
|
||||
)
|
||||
|
||||
// TestNew tests creating a new manager
|
||||
@@ -106,9 +106,9 @@ func TestQueueChainCreation(t *testing.T) {
|
||||
chainID := ids.GenerateTestID()
|
||||
netID := ids.GenerateTestID()
|
||||
chainParams := ChainParameters{
|
||||
ID: chainID,
|
||||
ID: chainID,
|
||||
ChainID: netID,
|
||||
VMID: ids.GenerateTestID(),
|
||||
VMID: ids.GenerateTestID(),
|
||||
}
|
||||
|
||||
// Queue the chain
|
||||
|
||||
@@ -70,7 +70,7 @@ func (s *mockServer) AddAliasesWithReadLock(endpoint string, aliases ...string)
|
||||
func (s *mockServer) Dispatch() error { return nil }
|
||||
func (s *mockServer) RegisterChain(chainName string, rt *runtime.Runtime, vm vm.VM) {
|
||||
}
|
||||
func (s *mockServer) Shutdown() error { return nil }
|
||||
func (s *mockServer) Shutdown() error { return nil }
|
||||
func (s *mockServer) SetRootInfoProvider(_ server.RootInfoProvider) {}
|
||||
|
||||
func TestHandlerManager_RegisterChainHandlers(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
//
|
||||
// F118 regression coverage. Asserts the chain manager stamps the
|
||||
// chain-wide ChainSecurityProfile pin into the C-Chain JSON config
|
||||
// bytes so the rpcchainvm plugin (coreth) can resolve the profile on
|
||||
// its side of the boundary.
|
||||
|
||||
package chains
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
consensusconfig "github.com/luxfi/consensus/config"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// canonicalStrictPQProfile returns the live canonical StrictPQ profile
|
||||
// with ProfileHash populated — the same form node.go builds during
|
||||
// initSecurityProfile.
|
||||
func canonicalStrictPQProfile(t *testing.T) *consensusconfig.ChainSecurityProfile {
|
||||
t.Helper()
|
||||
p, err := consensusconfig.ProfileByID(consensusconfig.ProfileStrictPQ)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, p.Validate())
|
||||
h, err := p.ComputeHash()
|
||||
require.NoError(t, err)
|
||||
p.ProfileHash = h
|
||||
return p
|
||||
}
|
||||
|
||||
// TestInjectSecurityProfileConfig_StampsCChainOnly asserts the F118
|
||||
// injection pass stamps the security profile pin into the C-Chain
|
||||
// (EVMID) plugin config and is a no-op for every other VM ID. Other
|
||||
// VMs receive the config bytes unchanged.
|
||||
func TestInjectSecurityProfileConfig_StampsCChainOnly(t *testing.T) {
|
||||
require := require.New(t)
|
||||
profile := canonicalStrictPQProfile(t)
|
||||
|
||||
m := &manager{
|
||||
ManagerConfig: ManagerConfig{
|
||||
Log: log.NewNoOpLogger(),
|
||||
SecurityProfile: profile,
|
||||
},
|
||||
}
|
||||
|
||||
// C-Chain (EVMID): pin must be stamped.
|
||||
out := m.injectSecurityProfileConfig(constants.EVMID, nil)
|
||||
require.NotEmpty(out, "C-Chain config must be stamped with the security profile pin")
|
||||
|
||||
var got map[string]interface{}
|
||||
require.NoError(json.Unmarshal(out, &got))
|
||||
|
||||
pin, ok := got["lux-security-profile"].(map[string]interface{})
|
||||
require.True(ok, "C-Chain config must carry lux-security-profile object")
|
||||
require.EqualValues(uint32(consensusconfig.ProfileStrictPQ), uint32(pin["profileID"].(float64)),
|
||||
"profileID must round-trip the canonical StrictPQ byte")
|
||||
require.Equal(hex.EncodeToString(profile.ProfileHash[:]), pin["profileHashHex"],
|
||||
"profileHashHex must round-trip the canonical 48-byte hash")
|
||||
|
||||
// Non-EVM VM (PlatformVMID): no-op.
|
||||
original := []byte(`{"some": "config"}`)
|
||||
pOut := m.injectSecurityProfileConfig(constants.PlatformVMID, original)
|
||||
require.Equal(original, pOut, "non-C-Chain config must pass through unchanged")
|
||||
}
|
||||
|
||||
// TestInjectSecurityProfileConfig_NoOpWhenProfileNil asserts the F118
|
||||
// injection is a no-op when SecurityProfile is nil (classical-compat
|
||||
// chains, legacy networks pre-locked-profile).
|
||||
func TestInjectSecurityProfileConfig_NoOpWhenProfileNil(t *testing.T) {
|
||||
require := require.New(t)
|
||||
m := &manager{
|
||||
ManagerConfig: ManagerConfig{
|
||||
Log: log.NewNoOpLogger(),
|
||||
SecurityProfile: nil,
|
||||
},
|
||||
}
|
||||
|
||||
original := []byte(`{"some": "config"}`)
|
||||
out := m.injectSecurityProfileConfig(constants.EVMID, original)
|
||||
require.Equal(original, out, "no-op pass-through under nil SecurityProfile")
|
||||
|
||||
out = m.injectSecurityProfileConfig(constants.EVMID, nil)
|
||||
require.Empty(out, "no-op pass-through preserves nil bytes")
|
||||
}
|
||||
|
||||
// TestInjectSecurityProfileConfig_MergesExistingConfig asserts the F118
|
||||
// injection preserves pre-existing C-Chain config keys (lux-strict-pq
|
||||
// shim, enable-automining, etc.) while adding the new lux-security-profile
|
||||
// pin alongside.
|
||||
func TestInjectSecurityProfileConfig_MergesExistingConfig(t *testing.T) {
|
||||
require := require.New(t)
|
||||
profile := canonicalStrictPQProfile(t)
|
||||
m := &manager{
|
||||
ManagerConfig: ManagerConfig{
|
||||
Log: log.NewNoOpLogger(),
|
||||
SecurityProfile: profile,
|
||||
},
|
||||
}
|
||||
|
||||
original := []byte(`{"enable-automining": true, "skip-block-fee": true}`)
|
||||
out := m.injectSecurityProfileConfig(constants.EVMID, original)
|
||||
|
||||
var got map[string]interface{}
|
||||
require.NoError(json.Unmarshal(out, &got))
|
||||
|
||||
require.Equal(true, got["enable-automining"], "automining flag must survive")
|
||||
require.Equal(true, got["skip-block-fee"], "skip-block-fee flag must survive")
|
||||
require.NotNil(got["lux-security-profile"], "security profile pin must be added")
|
||||
}
|
||||
|
||||
// TestInjectSecurityProfileConfig_RoundTripsAcrossPluginBoundary
|
||||
// asserts the wire form coreth expects (security-profile pin with
|
||||
// profileID + profileHashHex) matches the form the chain manager
|
||||
// emits, so the rpcchainvm plugin can decode the pin via standard
|
||||
// JSON unmarshalling. Closes the wire-compat axis of F118.
|
||||
func TestInjectSecurityProfileConfig_RoundTripsAcrossPluginBoundary(t *testing.T) {
|
||||
require := require.New(t)
|
||||
profile := canonicalStrictPQProfile(t)
|
||||
m := &manager{
|
||||
ManagerConfig: ManagerConfig{
|
||||
Log: log.NewNoOpLogger(),
|
||||
SecurityProfile: profile,
|
||||
},
|
||||
}
|
||||
|
||||
out := m.injectSecurityProfileConfig(constants.EVMID, nil)
|
||||
|
||||
// Mirror of plugin/evm/config.Config — locally redefined so this
|
||||
// test does not import the coreth plugin tree.
|
||||
type localPin struct {
|
||||
ProfileID uint8 `json:"profileID"`
|
||||
ProfileHashHex string `json:"profileHashHex"`
|
||||
}
|
||||
type localCorethConfig struct {
|
||||
SecurityProfile *localPin `json:"lux-security-profile,omitempty"`
|
||||
}
|
||||
|
||||
var cfg localCorethConfig
|
||||
require.NoError(json.Unmarshal(out, &cfg))
|
||||
require.NotNil(cfg.SecurityProfile, "coreth-side config struct must decode the pin")
|
||||
require.Equal(uint8(consensusconfig.ProfileStrictPQ), cfg.SecurityProfile.ProfileID)
|
||||
require.Equal(hex.EncodeToString(profile.ProfileHash[:]), cfg.SecurityProfile.ProfileHashHex)
|
||||
|
||||
// Decoded hex must be exactly 48 bytes (SHA3-384 width).
|
||||
hashBytes, err := hex.DecodeString(cfg.SecurityProfile.ProfileHashHex)
|
||||
require.NoError(err)
|
||||
require.Len(hashBytes, 48, "wire pin must carry a 48-byte SHA3-384 ProfileHash")
|
||||
|
||||
// Use ids.ID directly to silence the unused import warning in case
|
||||
// other tests in this file don't reach it.
|
||||
_ = ids.ID{}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/node/upgrade"
|
||||
"github.com/luxfi/runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var c *upgrade.Config = &upgrade.Config{}
|
||||
var _ runtime.NetworkUpgrades = c
|
||||
fmt.Println("Interface satisfied")
|
||||
}
|
||||
+6
-6
@@ -35,12 +35,12 @@ import (
|
||||
|
||||
var (
|
||||
// Flags
|
||||
outputPath string
|
||||
inputPath string
|
||||
sinceVer uint64
|
||||
dataDir string
|
||||
dbType string
|
||||
noCompress bool
|
||||
outputPath string
|
||||
inputPath string
|
||||
sinceVer uint64
|
||||
dataDir string
|
||||
dbType string
|
||||
noCompress bool
|
||||
forceRestore bool
|
||||
)
|
||||
|
||||
|
||||
+10
-10
@@ -12,16 +12,16 @@ import (
|
||||
|
||||
// CeremonyState holds the full state of a powers-of-tau ceremony.
|
||||
type CeremonyState struct {
|
||||
Circuit string `json:"circuit"`
|
||||
NumConstraints int `json:"numConstraints"`
|
||||
PowersNeeded int `json:"powersNeeded"`
|
||||
Participants int `json:"participants"`
|
||||
TauG1 []G1Point `json:"tauG1"`
|
||||
TauG2 []G2Point `json:"tauG2"`
|
||||
AlphaG1 []G1Point `json:"alphaG1"`
|
||||
BetaG1 []G1Point `json:"betaG1"`
|
||||
BetaG2 G2Point `json:"betaG2"`
|
||||
Contributions []Contribution `json:"contributions"`
|
||||
Circuit string `json:"circuit"`
|
||||
NumConstraints int `json:"numConstraints"`
|
||||
PowersNeeded int `json:"powersNeeded"`
|
||||
Participants int `json:"participants"`
|
||||
TauG1 []G1Point `json:"tauG1"`
|
||||
TauG2 []G2Point `json:"tauG2"`
|
||||
AlphaG1 []G1Point `json:"alphaG1"`
|
||||
BetaG1 []G1Point `json:"betaG1"`
|
||||
BetaG2 G2Point `json:"betaG2"`
|
||||
Contributions []Contribution `json:"contributions"`
|
||||
}
|
||||
|
||||
// Contribution records a single participant's contribution.
|
||||
|
||||
@@ -28,8 +28,8 @@ func main() {
|
||||
"networkID": 200200,
|
||||
"allocations": []map[string]interface{}{
|
||||
{
|
||||
"ethAddr": "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714",
|
||||
"luxAddr": zooAddr,
|
||||
"evmAddr": "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714",
|
||||
"utxoAddr": zooAddr,
|
||||
"initialAmount": 0,
|
||||
"unlockSchedule": []map[string]interface{}{
|
||||
{
|
||||
@@ -47,7 +47,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Load validators from Lux genesis
|
||||
luxGenesis, _ := os.ReadFile("/Users/z/work/lux/mainnet/genesis_mainnet.json")
|
||||
luxGenesis, _ := os.ReadFile(os.ExpandEnv("$HOME/work/lux/mainnet/genesis_mainnet.json"))
|
||||
var lux map[string]interface{}
|
||||
json.Unmarshal(luxGenesis, &lux)
|
||||
|
||||
@@ -76,6 +76,6 @@ func main() {
|
||||
genesis["cChainGenesis"] = string(ccBytes)
|
||||
|
||||
out, _ := json.MarshalIndent(genesis, "", " ")
|
||||
os.WriteFile("/Users/z/work/lux/mainnet/zoo_genesis_valid.json", out, 0644)
|
||||
os.WriteFile(os.ExpandEnv("$HOME/work/lux/mainnet/zoo_genesis_valid.json"), out, 0644)
|
||||
fmt.Println("Wrote zoo_genesis_valid.json")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
@@ -13,15 +13,15 @@ func main() {
|
||||
fmt.Println("Usage: go run main.go <genesis_file>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
data, err := os.ReadFile(os.Args[1])
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf("File size: %d bytes\n", len(data))
|
||||
|
||||
|
||||
// Compute hash the same way luxd does
|
||||
rawHash := hash.ComputeHash256(data)
|
||||
id, err := ids.ToID(rawHash)
|
||||
@@ -29,6 +29,6 @@ func main() {
|
||||
fmt.Printf("Error converting to ID: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
fmt.Printf("Genesis ID: %s\n", id.String())
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func (c *ChainDatabaseConfig) GetDatabaseType(chainAlias string) string {
|
||||
func (c *ChainDatabaseConfig) Validate() error {
|
||||
validTypes := map[string]bool{
|
||||
"pebbledb": true,
|
||||
"zapdb": true,
|
||||
"zapdb": true,
|
||||
"memdb": true,
|
||||
}
|
||||
|
||||
|
||||
+237
-376
@@ -4,10 +4,12 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
@@ -24,7 +26,8 @@ import (
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/crypto/bls"
|
||||
"github.com/luxfi/crypto/bls/signer/localsigner"
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
mlkemcrypto "github.com/luxfi/crypto/mlkem"
|
||||
"github.com/luxfi/filesystem/perms"
|
||||
"github.com/luxfi/filesystem/storage"
|
||||
"github.com/luxfi/ids"
|
||||
@@ -47,7 +50,6 @@ import (
|
||||
"github.com/luxfi/node/version"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
"github.com/luxfi/node/vms/platformvm/reward"
|
||||
pchaintxs "github.com/luxfi/node/vms/platformvm/txs"
|
||||
"github.com/luxfi/node/vms/platformvm/validators/fee"
|
||||
"github.com/luxfi/node/vms/proposervm"
|
||||
"github.com/luxfi/timer"
|
||||
@@ -95,90 +97,16 @@ var (
|
||||
errFileDoesNotExist = errors.New("file does not exist")
|
||||
)
|
||||
|
||||
// AutomineNetworkConfig captures immutable network state for automine mode persistence.
|
||||
// Once written to automine-network.json, this ensures the same genesis is produced
|
||||
// on every restart, making C-Chain/EVM state persistence work correctly.
|
||||
type AutomineNetworkConfig struct {
|
||||
// Version for forward compatibility
|
||||
Version int `json:"version"`
|
||||
|
||||
// Genesis start time - captured on first boot, reused on restart
|
||||
StartTime uint64 `json:"startTime"`
|
||||
|
||||
// Node identity
|
||||
NodeID string `json:"nodeId"`
|
||||
|
||||
// BLS credentials (hex-encoded)
|
||||
BLSPublicKey string `json:"blsPublicKey"`
|
||||
BLSPopProof string `json:"blsPopProof"`
|
||||
|
||||
// Computed genesis bytes and hash (for verification)
|
||||
GenesisBytes []byte `json:"genesisBytes"`
|
||||
GenesisHash string `json:"genesisHash"` // Actual hash of GenesisBytes
|
||||
|
||||
// X-Chain asset ID (LUX token ID)
|
||||
XAssetID string `json:"xAssetId"`
|
||||
|
||||
// C-Chain genesis (stored separately for EVM immutability)
|
||||
CChainGenesis string `json:"cChainGenesis"`
|
||||
}
|
||||
|
||||
const (
|
||||
devNetworkConfigVersion = 1
|
||||
devNetworkConfigFilename = "automine-network.json"
|
||||
)
|
||||
|
||||
// loadAutomineNetworkConfig attempts to load automine-network.json from the data directory.
|
||||
// Returns nil if the file doesn't exist (first boot scenario).
|
||||
func loadAutomineNetworkConfig(dataDir string) (*AutomineNetworkConfig, error) {
|
||||
path := filepath.Join(dataDir, devNetworkConfigFilename)
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil // First boot - no config yet
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read dev network config: %w", err)
|
||||
}
|
||||
|
||||
var cfg AutomineNetworkConfig
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse dev network config: %w", err)
|
||||
}
|
||||
|
||||
if cfg.Version != devNetworkConfigVersion {
|
||||
return nil, fmt.Errorf("unsupported dev network config version: %d (expected %d)", cfg.Version, devNetworkConfigVersion)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// saveAutomineNetworkConfig atomically writes the dev network config to disk.
|
||||
// Uses write-to-temp-then-rename pattern for crash safety.
|
||||
func saveAutomineNetworkConfig(dataDir string, cfg *AutomineNetworkConfig) error {
|
||||
path := filepath.Join(dataDir, devNetworkConfigFilename)
|
||||
tempPath := path + ".tmp"
|
||||
|
||||
cfg.Version = devNetworkConfigVersion
|
||||
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal dev network config: %w", err)
|
||||
}
|
||||
|
||||
// Write to temp file
|
||||
if err := os.WriteFile(tempPath, data, 0o600); err != nil {
|
||||
return fmt.Errorf("failed to write temp dev network config: %w", err)
|
||||
}
|
||||
|
||||
// Atomic rename
|
||||
if err := os.Rename(tempPath, path); err != nil {
|
||||
os.Remove(tempPath) // Clean up on failure
|
||||
return fmt.Errorf("failed to rename dev network config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
// (Removed: AutomineNetworkConfig + loadAutomineNetworkConfig +
|
||||
// saveAutomineNetworkConfig + automine-network.json persistence.)
|
||||
//
|
||||
// --automine is a *consensus-mode* flag (single-node, single-validator
|
||||
// quorum), nothing more. It MUST NOT swap in a different C-Chain
|
||||
// genesis or persist any "dev network config" sidecar. C-Chain
|
||||
// genesis is canonical genesis — for network-id 1/2/3/1337 it's the
|
||||
// embedded luxfi/genesis config, period. If the operator wants a
|
||||
// custom genesis they pass --genesis-file. No backwards compatibility
|
||||
// for the deleted automine-network.json sidecar.
|
||||
|
||||
func getConsensusConfig(v *viper.Viper) consensusconfig.Parameters {
|
||||
// Start with default parameters
|
||||
@@ -823,6 +751,135 @@ func getStakingSigner(v *viper.Viper) (bls.Signer, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// loadPEMBlock returns the body of a single PEM block matching
|
||||
// expectType. Refuses to silently consume the wrong block type so a
|
||||
// misnamed file (e.g. ML-DSA private key supplied as a public-key
|
||||
// path) fails loud at config-load instead of producing a NodeID under
|
||||
// the wrong key.
|
||||
func loadPEMBlock(path, expectType string) ([]byte, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %s: %w", path, err)
|
||||
}
|
||||
block, _ := pem.Decode(data)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("%s: not a PEM file", path)
|
||||
}
|
||||
if block.Type != expectType {
|
||||
return nil, fmt.Errorf("%s: PEM type %q is not %s", path, block.Type, expectType)
|
||||
}
|
||||
return block.Bytes, nil
|
||||
}
|
||||
|
||||
// pemBytesOrFile resolves either a base64-encoded PEM in
|
||||
// contentKey (highest precedence, matches the
|
||||
// --foo-file-content / --foo-file pattern the existing TLS
|
||||
// loaders use) or a filesystem path in pathKey. Empty return =
|
||||
// neither was set; caller decides whether that's a fatal config
|
||||
// error or a "fall through to classical-compat" path.
|
||||
func pemBytesOrFile(v *viper.Viper, contentKey, pathKey, expectType string) ([]byte, string, error) {
|
||||
if v.IsSet(contentKey) {
|
||||
raw := v.GetString(contentKey)
|
||||
if raw == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(raw)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("decode %s base64: %w", contentKey, err)
|
||||
}
|
||||
block, _ := pem.Decode(decoded)
|
||||
if block == nil {
|
||||
return nil, "", fmt.Errorf("%s: decoded value is not PEM", contentKey)
|
||||
}
|
||||
if block.Type != expectType {
|
||||
return nil, "", fmt.Errorf("%s: PEM type %q is not %s", contentKey, block.Type, expectType)
|
||||
}
|
||||
return block.Bytes, "<from-content>", nil
|
||||
}
|
||||
path := getExpandedArg(v, pathKey)
|
||||
if path == "" {
|
||||
return nil, "", nil
|
||||
}
|
||||
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, path, nil
|
||||
} else if err != nil {
|
||||
return nil, path, err
|
||||
}
|
||||
body, err := loadPEMBlock(path, expectType)
|
||||
return body, path, err
|
||||
}
|
||||
|
||||
// loadStakingMLDSA returns the strict-PQ ML-DSA-65 keypair (if any).
|
||||
// Both private and public materials are optional at the config layer
|
||||
// — a missing pair means classical-compat mode. A strict-PQ chain
|
||||
// profile enforces presence at the validator-set boundary.
|
||||
func loadStakingMLDSA(v *viper.Viper) (*mldsa.PrivateKey, []byte, string, string, error) {
|
||||
privBytes, privPath, err := pemBytesOrFile(v,
|
||||
StakingMLDSAKeyContentKey, StakingMLDSAKeyPathKey, "ML-DSA-65 PRIVATE KEY")
|
||||
if err != nil {
|
||||
return nil, nil, privPath, "", fmt.Errorf("staking-mldsa-key: %w", err)
|
||||
}
|
||||
pubBytes, pubPath, err := pemBytesOrFile(v,
|
||||
StakingMLDSAPubKeyContentKey, StakingMLDSAPubKeyPathKey, "ML-DSA-65 PUBLIC KEY")
|
||||
if err != nil {
|
||||
return nil, nil, privPath, pubPath, fmt.Errorf("staking-mldsa-pub-key: %w", err)
|
||||
}
|
||||
if len(privBytes) == 0 && len(pubBytes) == 0 {
|
||||
return nil, nil, privPath, pubPath, nil
|
||||
}
|
||||
// Asymmetry is a config error: a strict-PQ chain MUST have both
|
||||
// the private key (for signing) and the public key (so peers can
|
||||
// verify what we signed). Refuse rather than silently degrade.
|
||||
if len(privBytes) == 0 || len(pubBytes) == 0 {
|
||||
return nil, nil, privPath, pubPath, errors.New("staking-mldsa: both private and public key are required (or neither for classical-compat)")
|
||||
}
|
||||
priv, err := mldsa.PrivateKeyFromBytes(mldsa.MLDSA65, privBytes)
|
||||
if err != nil {
|
||||
return nil, nil, privPath, pubPath, fmt.Errorf("parse staking-mldsa private key: %w", err)
|
||||
}
|
||||
// Sanity: the public key on disk must match the one derivable
|
||||
// from the private key. Mismatch would point a NodeID at a
|
||||
// public key the validator cannot actually sign for — silent
|
||||
// authentication failure that the consensus engine would later report as
|
||||
// "this validator is offline" rather than "you misconfigured
|
||||
// your keys".
|
||||
derivedPubBytes := priv.PublicKey.Bytes()
|
||||
if !bytes.Equal(derivedPubBytes, pubBytes) {
|
||||
return nil, nil, privPath, pubPath, errors.New("staking-mldsa: public key on disk does not match the public key derived from the private key")
|
||||
}
|
||||
return priv, pubBytes, privPath, pubPath, nil
|
||||
}
|
||||
|
||||
// loadHandshakeMLKEM returns the strict-PQ ML-KEM-768 KEM keypair
|
||||
// (if any). Same shape + invariants as loadStakingMLDSA.
|
||||
func loadHandshakeMLKEM(v *viper.Viper) (*mlkemcrypto.PrivateKey, []byte, string, string, error) {
|
||||
privBytes, privPath, err := pemBytesOrFile(v,
|
||||
HandshakeMLKEMKeyContentKey, HandshakeMLKEMKeyPathKey, "ML-KEM-768 PRIVATE KEY")
|
||||
if err != nil {
|
||||
return nil, nil, privPath, "", fmt.Errorf("handshake-mlkem-key: %w", err)
|
||||
}
|
||||
pubBytes, pubPath, err := pemBytesOrFile(v,
|
||||
HandshakeMLKEMPubKeyContentKey, HandshakeMLKEMPubKeyPathKey, "ML-KEM-768 PUBLIC KEY")
|
||||
if err != nil {
|
||||
return nil, nil, privPath, pubPath, fmt.Errorf("handshake-mlkem-pub-key: %w", err)
|
||||
}
|
||||
if len(privBytes) == 0 && len(pubBytes) == 0 {
|
||||
return nil, nil, privPath, pubPath, nil
|
||||
}
|
||||
if len(privBytes) == 0 || len(pubBytes) == 0 {
|
||||
return nil, nil, privPath, pubPath, errors.New("handshake-mlkem: both private and public key are required (or neither for classical-compat)")
|
||||
}
|
||||
priv, err := mlkemcrypto.PrivateKeyFromBytes(privBytes, mlkemcrypto.MLKEM768)
|
||||
if err != nil {
|
||||
return nil, nil, privPath, pubPath, fmt.Errorf("parse handshake-mlkem private key: %w", err)
|
||||
}
|
||||
derivedPubBytes := priv.PublicKey().Bytes()
|
||||
if !bytes.Equal(derivedPubBytes, pubBytes) {
|
||||
return nil, nil, privPath, pubPath, errors.New("handshake-mlkem: public key on disk does not match the public key derived from the private key")
|
||||
}
|
||||
return priv, pubBytes, privPath, pubPath, nil
|
||||
}
|
||||
|
||||
func getStakingConfig(v *viper.Viper, networkID uint32) (node.StakingConfig, error) {
|
||||
config := node.StakingConfig{
|
||||
SybilProtectionEnabled: v.GetBool(SybilProtectionEnabledKey),
|
||||
@@ -849,6 +906,31 @@ func getStakingConfig(v *viper.Viper, networkID uint32) (node.StakingConfig, err
|
||||
if err != nil {
|
||||
return node.StakingConfig{}, err
|
||||
}
|
||||
|
||||
// Strict-PQ identity (FIPS 204 ML-DSA-65 + FIPS 203 ML-KEM-768).
|
||||
// Both pairs are optional at this layer — classical-compat chains
|
||||
// run without them. Strict-PQ profile rejects a missing pair at
|
||||
// the validator-set boundary; that gate lives in consensus/config
|
||||
// (the profile gate that owns the strict-PQ vs classical-compat
|
||||
// dispatch), not here in node-config land.
|
||||
mldsaPriv, mldsaPub, mldsaPrivPath, mldsaPubPath, err := loadStakingMLDSA(v)
|
||||
if err != nil {
|
||||
return node.StakingConfig{}, err
|
||||
}
|
||||
config.StakingMLDSA = mldsaPriv
|
||||
config.StakingMLDSAPub = mldsaPub
|
||||
config.StakingMLDSAKeyPath = mldsaPrivPath
|
||||
config.StakingMLDSAPubPath = mldsaPubPath
|
||||
|
||||
mlkemPriv, mlkemPub, mlkemPrivPath, mlkemPubPath, err := loadHandshakeMLKEM(v)
|
||||
if err != nil {
|
||||
return node.StakingConfig{}, err
|
||||
}
|
||||
config.HandshakeMLKEMPriv = mlkemPriv
|
||||
config.HandshakeMLKEMPub = mlkemPub
|
||||
config.HandshakeMLKEMKeyPath = mlkemPrivPath
|
||||
config.HandshakeMLKEMPubPath = mlkemPubPath
|
||||
|
||||
if networkID != constants.MainnetID && networkID != constants.TestnetID {
|
||||
config.UptimeRequirement = v.GetFloat64(UptimeRequirementKey)
|
||||
config.MinValidatorStake = v.GetUint64(MinValidatorStakeKey)
|
||||
@@ -957,13 +1039,10 @@ func getUpgradeConfig(v *viper.Viper, networkID uint32) (upgrade.Config, error)
|
||||
}
|
||||
|
||||
func getGenesisData(v *viper.Viper, networkID uint32, stakingCfg *builder.StakingConfig, dataDir string) ([]byte, ids.ID, error) {
|
||||
// Get allow-custom-genesis flag (defaults to true for development)
|
||||
allowCustomGenesis := v.GetBool(AllowCustomGenesisKey)
|
||||
|
||||
// Handle automine mode genesis - dynamically generate genesis with the node's own credentials
|
||||
if v.GetBool(DevModeKey) && !v.IsSet(GenesisFileKey) && !v.IsSet(GenesisFileContentKey) && !v.IsSet(GenesisDBKey) {
|
||||
return getOrCreateAutomineGenesis(stakingCfg, dataDir)
|
||||
}
|
||||
// (Removed: automine-mode genesis swap.) --automine is single-node
|
||||
// consensus only; it falls through to the canonical genesis loader
|
||||
// below — same as a real network. C-Chain genesis comes from the
|
||||
// canonical luxfi/genesis embedded config or --genesis-file. Period.
|
||||
|
||||
// HIGHEST PRIORITY: Raw genesis bytes - use directly without rebuilding
|
||||
// This is critical for snapshot resume to avoid hash mismatch
|
||||
@@ -973,16 +1052,15 @@ func getGenesisData(v *viper.Viper, networkID uint32, stakingCfg *builder.Stakin
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to decode %s: %w", GenesisRawBytesKey, err)
|
||||
}
|
||||
// Extract xAssetID from the X-chain genesis within the platform genesis
|
||||
xAssetID, err := extractXAssetID(genesisBytes)
|
||||
utxoAssetID, err := resolveXAssetID(networkID, genesisBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to extract xAssetID from raw genesis bytes: %w", err)
|
||||
return nil, ids.Empty, fmt.Errorf("resolve X-Chain asset ID from %s: %w", GenesisRawBytesKey, err)
|
||||
}
|
||||
log.Info("loaded raw genesis bytes directly",
|
||||
"size", len(genesisBytes),
|
||||
"xAssetID", xAssetID,
|
||||
"utxoAssetID", utxoAssetID,
|
||||
)
|
||||
return genesisBytes, xAssetID, nil
|
||||
return genesisBytes, utxoAssetID, nil
|
||||
}
|
||||
|
||||
// Check if genesis-db is specified for database replay
|
||||
@@ -1004,7 +1082,7 @@ func getGenesisData(v *viper.Viper, networkID uint32, stakingCfg *builder.Stakin
|
||||
// try first loading genesis content directly from flag/env-var
|
||||
if v.IsSet(GenesisFileContentKey) {
|
||||
genesisData := v.GetString(GenesisFileContentKey)
|
||||
return builder.FromFlag(networkID, genesisData, stakingCfg, allowCustomGenesis)
|
||||
return builder.FromFlag(networkID, genesisData, stakingCfg)
|
||||
}
|
||||
|
||||
// if content is not specified go for the file
|
||||
@@ -1013,21 +1091,32 @@ func getGenesisData(v *viper.Viper, networkID uint32, stakingCfg *builder.Stakin
|
||||
// Check if we have cached genesis bytes to avoid rebuilding
|
||||
cacheFile := filepath.Join(dataDir, "genesis.bytes")
|
||||
if cachedBytes, err := loadCachedGenesisBytes(cacheFile); err == nil && len(cachedBytes) > 0 {
|
||||
xAssetID, err := extractXAssetID(cachedBytes)
|
||||
if err != nil {
|
||||
log.Warn("failed to extract xAssetID from cached genesis, rebuilding",
|
||||
"error", err,
|
||||
)
|
||||
} else {
|
||||
utxoAssetID, err := resolveXAssetID(networkID, cachedBytes)
|
||||
if err == nil {
|
||||
log.Info("loaded cached genesis bytes for hash stability",
|
||||
"cacheFile", cacheFile,
|
||||
"size", len(cachedBytes),
|
||||
"utxoAssetID", utxoAssetID,
|
||||
)
|
||||
return cachedBytes, xAssetID, nil
|
||||
return cachedBytes, utxoAssetID, nil
|
||||
}
|
||||
// Cache parse failed — almost certainly a codec mismatch
|
||||
// after a binary upgrade (e.g. v1-codec bytes persisted by
|
||||
// an older luxd, multi-version v0+v1 dispatcher in this
|
||||
// luxd doesn't recognise a type the cached blob still
|
||||
// uses). Drop the cache and rebuild from the file rather
|
||||
// than wedging in a CrashLoop. Hash stability is forfeit
|
||||
// for this single restart — intentional, the alternative
|
||||
// is a permanent outage on every binary bump.
|
||||
log.Warn("cached genesis bytes failed to parse — invalidating cache and rebuilding from genesis-file",
|
||||
"cacheFile", cacheFile,
|
||||
"size", len(cachedBytes),
|
||||
"error", err,
|
||||
)
|
||||
_ = os.Remove(cacheFile)
|
||||
}
|
||||
// No cache or invalid cache - build from file and cache the result
|
||||
genesisBytes, xAssetID, err := builder.FromFile(networkID, genesisFileName, stakingCfg, allowCustomGenesis)
|
||||
genesisBytes, utxoAssetID, err := builder.FromFile(networkID, genesisFileName, stakingCfg)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, err
|
||||
}
|
||||
@@ -1042,7 +1131,7 @@ func getGenesisData(v *viper.Viper, networkID uint32, stakingCfg *builder.Stakin
|
||||
"size", len(genesisBytes),
|
||||
)
|
||||
}
|
||||
return genesisBytes, xAssetID, nil
|
||||
return genesisBytes, utxoAssetID, nil
|
||||
}
|
||||
|
||||
// finally if file is not specified/readable go for the predefined config
|
||||
@@ -1050,248 +1139,36 @@ func getGenesisData(v *viper.Viper, networkID uint32, stakingCfg *builder.Stakin
|
||||
return builder.FromConfig(config)
|
||||
}
|
||||
|
||||
// getOrCreateAutomineGenesis handles automine mode genesis with persistence.
|
||||
// On first boot: generates genesis, saves to automine-network.json, returns genesis.
|
||||
// On restart: loads from automine-network.json to ensure genesis hash stability.
|
||||
// Returns: (genesisBytes, xAssetID, error) - xAssetID is the LUX token asset ID
|
||||
func getOrCreateAutomineGenesis(stakingCfg *builder.StakingConfig, dataDir string) ([]byte, ids.ID, error) {
|
||||
// Try to load existing dev network config
|
||||
devCfg, err := loadAutomineNetworkConfig(dataDir)
|
||||
// resolveXAssetID extracts the X-Chain native asset ID from the loaded
|
||||
// platform-genesis blob. It is the canonical source of truth used by
|
||||
// every load path that bypasses FromConfig (raw bytes, cached bytes —
|
||||
// the paths that historically defaulted to constants.UTXOAssetIDFor and
|
||||
// silently disagreed with the genesis content on sovereign L1s).
|
||||
//
|
||||
// Behaviour:
|
||||
//
|
||||
// - If the genesis bakes an X-Chain, returns the runtime asset ID
|
||||
// derived from that chain's CreateAssetTx (the same value
|
||||
// vm.initGenesis assigns at runtime).
|
||||
// - If the genesis is P-only (no X-Chain), returns the network-id-
|
||||
// keyed constant. The asset ID is unused in that mode.
|
||||
// - If the genesis is unparseable or the embedded X-Chain genesis
|
||||
// is malformed, returns the corresponding error — these are
|
||||
// unrecoverable on a primary-network bootstrap.
|
||||
//
|
||||
// Tested against both sovereign-network genesis (X-Chain present, asset
|
||||
// ID differs from the constant) and upstream Lux genesis fixtures.
|
||||
func resolveXAssetID(networkID uint32, genesisBytes []byte) (ids.ID, error) {
|
||||
id, ok, err := builder.XAssetIDFromGenesisBytes(genesisBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to load dev network config: %w", err)
|
||||
return ids.Empty, err
|
||||
}
|
||||
|
||||
if devCfg != nil {
|
||||
// Existing config found - check if credentials match
|
||||
// In automine mode with ephemeral certs, credentials will change on restart.
|
||||
// We log a warning but continue with the stored genesis since automine mode
|
||||
// is single-node and doesn't require credential consistency.
|
||||
expectedNodeID := stakingCfg.NodeID
|
||||
expectedBLSPK := fmt.Sprintf("0x%x", stakingCfg.BLSPublicKey)
|
||||
expectedBLSPoP := fmt.Sprintf("0x%x", stakingCfg.BLSProofOfPossession)
|
||||
|
||||
credentialsMismatch := false
|
||||
if devCfg.NodeID != expectedNodeID {
|
||||
log.Warn("automine-network.json nodeID mismatch (using stored genesis anyway for automine mode)",
|
||||
"stored", devCfg.NodeID,
|
||||
"current", expectedNodeID,
|
||||
)
|
||||
credentialsMismatch = true
|
||||
}
|
||||
if devCfg.BLSPublicKey != expectedBLSPK {
|
||||
log.Warn("automine-network.json BLS public key mismatch (using stored genesis anyway for automine mode)")
|
||||
credentialsMismatch = true
|
||||
}
|
||||
if devCfg.BLSPopProof != expectedBLSPoP {
|
||||
log.Warn("automine-network.json BLS PoP mismatch (using stored genesis anyway for automine mode)")
|
||||
credentialsMismatch = true
|
||||
}
|
||||
if credentialsMismatch {
|
||||
log.Warn("credentials changed since first boot - for persistent staking, use --staking-ephemeral-cert-enabled=false with persistent key files")
|
||||
}
|
||||
|
||||
// Verify the stored genesis hash matches what we'll compute from the bytes
|
||||
storedHash, err := ids.FromString(devCfg.GenesisHash)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("invalid genesis hash in automine-network.json: %w", err)
|
||||
}
|
||||
|
||||
// Compute actual hash from stored bytes to verify integrity
|
||||
computedHashBytes := hash.ComputeHash256(devCfg.GenesisBytes)
|
||||
computedHash, err := ids.ToID(computedHashBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to convert computed hash to ID: %w", err)
|
||||
}
|
||||
|
||||
if storedHash != computedHash {
|
||||
return nil, ids.Empty, fmt.Errorf("genesis bytes corrupted: stored hash %s != computed hash %s", storedHash, computedHash)
|
||||
}
|
||||
|
||||
// Parse stored X-Chain asset ID
|
||||
xAssetID, err := ids.FromString(devCfg.XAssetID)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("invalid xAssetId in automine-network.json: %w", err)
|
||||
}
|
||||
|
||||
log.Info("loaded dev network config",
|
||||
"path", filepath.Join(dataDir, devNetworkConfigFilename),
|
||||
"genesisHash", devCfg.GenesisHash,
|
||||
"xAssetID", devCfg.XAssetID,
|
||||
"startTime", devCfg.StartTime,
|
||||
)
|
||||
|
||||
return devCfg.GenesisBytes, xAssetID, nil
|
||||
if !ok {
|
||||
return constants.UTXOAssetIDFor(networkID), nil
|
||||
}
|
||||
|
||||
// First boot - generate new genesis with current timestamp
|
||||
startTime := uint64(time.Now().Unix())
|
||||
|
||||
// buildAutomineGenesis returns (genesisBytes, xAssetID) - the LUX token asset ID
|
||||
genesisBytes, xAssetID, err := buildAutomineGenesis(stakingCfg, startTime)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, err
|
||||
}
|
||||
|
||||
// Compute actual genesis hash from bytes (this is what the node uses for DB validation)
|
||||
genesisHashBytes := hash.ComputeHash256(genesisBytes)
|
||||
genesisHash, err := ids.ToID(genesisHashBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to convert genesis hash to ID: %w", err)
|
||||
}
|
||||
|
||||
// Save for future restarts
|
||||
newDevCfg := &AutomineNetworkConfig{
|
||||
Version: devNetworkConfigVersion,
|
||||
StartTime: startTime,
|
||||
NodeID: stakingCfg.NodeID,
|
||||
BLSPublicKey: fmt.Sprintf("0x%x", stakingCfg.BLSPublicKey),
|
||||
BLSPopProof: fmt.Sprintf("0x%x", stakingCfg.BLSProofOfPossession),
|
||||
GenesisBytes: genesisBytes,
|
||||
GenesisHash: genesisHash.String(),
|
||||
XAssetID: xAssetID.String(),
|
||||
CChainGenesis: automineCChainGenesis,
|
||||
}
|
||||
|
||||
if err := saveAutomineNetworkConfig(dataDir, newDevCfg); err != nil {
|
||||
// Log warning but don't fail - genesis is still valid
|
||||
log.Warn("failed to save dev network config (persistence may not work on restart)",
|
||||
"error", err,
|
||||
)
|
||||
} else {
|
||||
log.Info("created dev network config",
|
||||
"path", filepath.Join(dataDir, devNetworkConfigFilename),
|
||||
"genesisHash", genesisHash.String(),
|
||||
"xAssetID", xAssetID.String(),
|
||||
"startTime", startTime,
|
||||
)
|
||||
}
|
||||
|
||||
return genesisBytes, xAssetID, nil
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// buildAutomineGenesis creates a genesis configuration for single-node development mode.
|
||||
// It uses the node's own credentials as the sole validator.
|
||||
func buildAutomineGenesis(stakingCfg *builder.StakingConfig, startTime uint64) ([]byte, ids.ID, error) {
|
||||
// Parse node ID from staking config
|
||||
nodeID, err := ids.NodeIDFromString(stakingCfg.NodeID)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to parse node ID for automine mode: %w", err)
|
||||
}
|
||||
|
||||
// Lux Treasury address: 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714
|
||||
// This is derived from LUX_MNEMONIC and funded on C-Chain
|
||||
var rewardAddress ids.ShortID
|
||||
treasuryAddr := "9011E888251AB053B7bD1cdB598Db4f9DEd94714"
|
||||
treasuryBytes, err := ids.ShortFromString(treasuryAddr)
|
||||
if err == nil {
|
||||
rewardAddress = treasuryBytes
|
||||
} else {
|
||||
// Fall back to a deterministic address derived from node ID
|
||||
copy(rewardAddress[:], nodeID[:20])
|
||||
}
|
||||
|
||||
// Create automine mode config with embedded C-Chain genesis
|
||||
devCfg := builder.DevModeConfig{
|
||||
NodeID: nodeID,
|
||||
BLSPublicKey: fmt.Sprintf("0x%x", stakingCfg.BLSPublicKey),
|
||||
BLSPopProof: fmt.Sprintf("0x%x", stakingCfg.BLSProofOfPossession),
|
||||
RewardAddress: rewardAddress,
|
||||
CChainGenesis: automineCChainGenesis,
|
||||
StartTime: startTime, // Use provided start time for determinism
|
||||
}
|
||||
|
||||
return builder.ForDevMode(devCfg, stakingCfg)
|
||||
}
|
||||
|
||||
// automineCChainGenesis is the default C-Chain genesis for automine mode.
|
||||
// Network ID 1337, EVM Chain ID 31337.
|
||||
// Funds Lux Treasury (0x9011), light mnemonic accounts (BIP44 m/44'/9000'/0'/0/{0-4}), and Anvil/Hardhat accounts.
|
||||
const automineCChainGenesis = `{
|
||||
"config": {
|
||||
"chainId": 31337,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"arrowGlacierBlock": 0,
|
||||
"grayGlacierBlock": 0,
|
||||
"mergeNetsplitBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"cancunTime": 0,
|
||||
"blobSchedule": {
|
||||
"cancun": {"target": 3, "max": 6, "baseFeeUpdateFraction": 3338477}
|
||||
},
|
||||
"terminalTotalDifficulty": 0,
|
||||
"chainEVMTimestamp": 0,
|
||||
"durangoTimestamp": 0,
|
||||
"etnaTimestamp": 0,
|
||||
"feeConfig": {
|
||||
"gasLimit": 30000000,
|
||||
"targetBlockRate": 1,
|
||||
"minBaseFee": 1000000000,
|
||||
"targetGas": 100000000,
|
||||
"baseFeeChangeDenominator": 48,
|
||||
"minBlockGasCost": 0,
|
||||
"maxBlockGasCost": 1000000,
|
||||
"blockGasCostStep": 200000
|
||||
},
|
||||
"warpConfig": {
|
||||
"blockTimestamp": 0,
|
||||
"quorumNumerator": 67,
|
||||
"requirePrimaryNetworkSigners": false
|
||||
}
|
||||
},
|
||||
"alloc": {
|
||||
"9011E888251AB053B7bD1cdB598Db4f9DEd94714": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"5369615110ca435bdf798f31c20ba6163d7b0a54": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"2e701063ccdffa2b1872c596222d8067d124d3ef": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"9030463eb1aaa563c8247468416cc0bf06347502": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"f77b06331152fd0e536de0af65688a6559c6f914": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"944fd51713652b9922690b7d06498fbf8742beac": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"f39Fd6e51aad88F6F4ce6aB8827279cffFb92266": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"70997970C51812dc3A010C7d01b50e0d17dc79C8": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
},
|
||||
"90F79bf6EB2c4f870365E785982E1f101E93b906": {
|
||||
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"nonce": "0x0",
|
||||
"timestamp": "0x0",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x1c9c380",
|
||||
"difficulty": "0x0",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"number": "0x0",
|
||||
"gasUsed": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}`
|
||||
|
||||
func getTrackedChains(v *viper.Viper) (set.Set[ids.ID], error) {
|
||||
trackChainsStr := v.GetString(TrackChainsKey)
|
||||
|
||||
@@ -1849,20 +1726,25 @@ func GetNodeConfig(v *viper.Viper) (node.Config, error) {
|
||||
return node.Config{}, err
|
||||
}
|
||||
|
||||
// Chain tracking
|
||||
// Always populate TrackedChains from --track-chains flag/env, regardless of
|
||||
// TrackAllChains. TrackedChains is used in peer handshake (MyChains) to tell
|
||||
// peers which chains we're interested in. Without it, peers won't gossip
|
||||
// chain blocks to us even if we're tracking all chains locally.
|
||||
// Chain tracking — explicit per-chain opt-in. Each validator
|
||||
// declares which chains it wants to validate via --track-chains
|
||||
// (or the operator-emitted equivalent). TrackedChains is also used
|
||||
// in the peer handshake (MyChains) so peers gossip blocks for the
|
||||
// chains we asked for.
|
||||
//
|
||||
// TrackAllChains stays as an opt-in escape hatch only — it is NOT
|
||||
// auto-enabled when TrackedChains is empty. The previous default
|
||||
// (`if TrackedChains == nil { TrackAllChains = true }`) was a
|
||||
// footgun: any node started without an explicit --track-chains
|
||||
// list silently tried to spin up every chain in P-chain state,
|
||||
// including ones whose VM plugin wasn't loaded — fataling at the
|
||||
// chain manager. Empty TrackedChains now means "track only P/X
|
||||
// (built-in primary chains)".
|
||||
nodeConfig.TrackedChains, err = getTrackedChains(v)
|
||||
if err != nil {
|
||||
return node.Config{}, err
|
||||
}
|
||||
nodeConfig.TrackAllChains = v.GetBool(TrackAllChainsKey)
|
||||
if nodeConfig.TrackedChains == nil {
|
||||
nodeConfig.TrackAllChains = true
|
||||
}
|
||||
|
||||
|
||||
// HTTP APIs
|
||||
nodeConfig.HTTPConfig, err = getHTTPConfig(v)
|
||||
@@ -1989,11 +1871,10 @@ func GetNodeConfig(v *viper.Viper) (node.Config, error) {
|
||||
// Get data directory for dev network config persistence
|
||||
dataDir := getExpandedArg(v, DataDirKey)
|
||||
|
||||
nodeConfig.GenesisBytes, nodeConfig.LuxAssetID, err = getGenesisData(v, nodeConfig.NetworkID, &genesisStakingCfg, dataDir)
|
||||
nodeConfig.GenesisBytes, nodeConfig.UTXOAssetID, err = getGenesisData(v, nodeConfig.NetworkID, &genesisStakingCfg, dataDir)
|
||||
if err != nil {
|
||||
return node.Config{}, fmt.Errorf("unable to load genesis file: %w", err)
|
||||
}
|
||||
nodeConfig.AllowGenesisUpdate = v.GetBool(AllowGenesisUpdateKey)
|
||||
|
||||
// StateSync Configs
|
||||
nodeConfig.StateSyncConfig, err = getStateSyncConfig(v)
|
||||
@@ -2122,26 +2003,6 @@ func saveCachedGenesisBytes(cacheFile string, genesisBytes []byte) error {
|
||||
return os.WriteFile(cacheFile, genesisBytes, 0o600)
|
||||
}
|
||||
|
||||
// extractXAssetID extracts the LUX asset ID from raw platform genesis bytes.
|
||||
// This is needed when loading raw genesis bytes directly (for snapshot resume)
|
||||
// to avoid rebuilding genesis which causes hash mismatch.
|
||||
func extractXAssetID(genesisBytes []byte) (ids.ID, error) {
|
||||
// Get the X-chain creation TX from the platform genesis
|
||||
xChainTx, err := builder.VMGenesis(genesisBytes, constants.XVMID)
|
||||
if err != nil {
|
||||
return ids.Empty, fmt.Errorf("couldn't find X-chain genesis in platform genesis: %w", err)
|
||||
}
|
||||
|
||||
// Extract the XVM genesis bytes from the create chain TX
|
||||
createChainTx, ok := xChainTx.Unsigned.(*pchaintxs.CreateChainTx)
|
||||
if !ok {
|
||||
return ids.Empty, fmt.Errorf("X-chain genesis TX is not a CreateChainTx")
|
||||
}
|
||||
|
||||
// Use the builder.XAssetID function to extract the asset ID from XVM genesis
|
||||
return builder.XAssetID(createChainTx.GenesisData)
|
||||
}
|
||||
|
||||
func providedFlags(v *viper.Viper) map[string]interface{} {
|
||||
settings := v.AllSettings()
|
||||
customSettings := make(map[string]interface{}, len(settings))
|
||||
|
||||
+1
-1
@@ -586,7 +586,7 @@ Defaults to `0.1`.
|
||||
|
||||
Partial sync enables nodes that are not primary network validators to optionally sync
|
||||
only the P-chain on the primary network. Nodes that use this option can still track
|
||||
Chains. After the Etna upgrade, nodes that use this option can also validate L1s.
|
||||
Chains. Nodes that use this option can also validate L1s.
|
||||
This config defaults to `false`.
|
||||
|
||||
## Public IP
|
||||
|
||||
@@ -17,9 +17,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
consensusconfig "github.com/luxfi/consensus/config"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/chains"
|
||||
"github.com/luxfi/node/genesis/builder"
|
||||
"github.com/luxfi/node/nets"
|
||||
pchaingenesis "github.com/luxfi/node/vms/platformvm/genesis"
|
||||
pchaintxs "github.com/luxfi/node/vms/platformvm/txs"
|
||||
)
|
||||
|
||||
const chainConfigFilenameExtension = ".ex"
|
||||
@@ -672,3 +676,57 @@ func TestDevModeFlags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestResolveXAssetID_FromSovereignGenesis covers the canonical
|
||||
// behaviour the sovereign-L1 fix relies on: when the loaded platform
|
||||
// genesis bakes an X-Chain, resolveXAssetID returns the runtime asset
|
||||
// ID encoded IN the genesis (matches what FromConfig produces, matches
|
||||
// what the running X-Chain reports via platform.getStakingAssetID).
|
||||
//
|
||||
// Critically: NOT constants.UTXOAssetIDFor(networkID). That value is
|
||||
// network-id-keyed and would silently collide between two sovereign L1s
|
||||
// sharing a primary-network ID.
|
||||
func TestResolveXAssetID_FromSovereignGenesis(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
cfg := builder.GetConfig(constants.LocalID)
|
||||
require.NotNil(cfg)
|
||||
require.NotEmpty(cfg.XChainGenesis, "fixture must bake X-Chain")
|
||||
|
||||
genesisBytes, expectedID, err := builder.FromConfig(cfg)
|
||||
require.NoError(err)
|
||||
require.NotEqual(ids.Empty, expectedID)
|
||||
|
||||
gotID, err := resolveXAssetID(constants.LocalID, genesisBytes)
|
||||
require.NoError(err)
|
||||
require.Equal(expectedID, gotID,
|
||||
"resolveXAssetID must agree with FromConfig on the genesis-derived asset ID")
|
||||
}
|
||||
|
||||
// TestResolveXAssetID_POnlyFallback verifies that when the platform
|
||||
// genesis bakes no X-Chain (P-only mode), resolveXAssetID falls back
|
||||
// to constants.UTXOAssetIDFor(networkID). That value is unused at
|
||||
// runtime (no X-Chain to mint on) but keeps the existing nodeConfig
|
||||
// shape consistent for downstream consumers.
|
||||
func TestResolveXAssetID_POnlyFallback(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
pOnly := &pchaingenesis.Genesis{Chains: nil}
|
||||
pOnlyBytes, err := pchaingenesis.Codec.Marshal(pchaintxs.CodecVersion, pOnly)
|
||||
require.NoError(err)
|
||||
|
||||
gotID, err := resolveXAssetID(42, pOnlyBytes)
|
||||
require.NoError(err)
|
||||
require.Equal(constants.UTXOAssetIDFor(42), gotID,
|
||||
"P-only must fall through to UTXOAssetIDFor(networkID)")
|
||||
}
|
||||
|
||||
// TestResolveXAssetID_Malformed asserts that bad genesis bytes surface
|
||||
// an error rather than silently returning ids.Empty (which would
|
||||
// reintroduce the UTXOAssetIDFor fallback and defeat the fix on
|
||||
// sovereign L1s where the fallback value is wrong).
|
||||
func TestResolveXAssetID_Malformed(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
_, err := resolveXAssetID(1, []byte{0xff, 0xfe, 0xfd, 0xfc})
|
||||
require.Error(err)
|
||||
}
|
||||
|
||||
+31
-13
@@ -13,14 +13,14 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
compression "github.com/luxfi/compress"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/genesis/pkg/genesis"
|
||||
"github.com/luxfi/net/dynamicip"
|
||||
"github.com/luxfi/node/genesis/builder"
|
||||
"github.com/luxfi/node/trace"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
"github.com/luxfi/node/vms/proposervm"
|
||||
compression "github.com/luxfi/compress"
|
||||
"github.com/luxfi/net/dynamicip"
|
||||
"github.com/luxfi/sys/ulimit"
|
||||
|
||||
consensusconfig "github.com/luxfi/consensus/config"
|
||||
@@ -46,15 +46,21 @@ var (
|
||||
defaultStakingTLSKeyPath = filepath.Join(defaultStakingPath, "staker.key")
|
||||
defaultStakingCertPath = filepath.Join(defaultStakingPath, "staker.crt")
|
||||
defaultStakingSignerKeyPath = filepath.Join(defaultStakingPath, "signer.key")
|
||||
defaultConfigDir = filepath.Join(defaultUnexpandedDataDir, "configs")
|
||||
defaultChainConfigDir = filepath.Join(defaultConfigDir, "chains")
|
||||
defaultVMConfigDir = filepath.Join(defaultConfigDir, "vms")
|
||||
defaultVMAliasFilePath = filepath.Join(defaultVMConfigDir, "aliases.json")
|
||||
defaultChainAliasFilePath = filepath.Join(defaultChainConfigDir, "aliases.json")
|
||||
defaultNetConfigDir = filepath.Join(defaultConfigDir, "chains")
|
||||
defaultPluginDir = filepath.Join(defaultUnexpandedDataDir, "plugins", "current")
|
||||
defaultChainDataDir = filepath.Join(defaultUnexpandedDataDir, "chainData")
|
||||
defaultProcessContextPath = filepath.Join(defaultUnexpandedDataDir, DefaultProcessContextFilename)
|
||||
// Strict-PQ default paths — mirror downstream-tenant CLI `<tenantctl> key gen`
|
||||
// layout so the operator init container + lqd see the same files.
|
||||
defaultStakingMLDSAKeyPath = filepath.Join(defaultStakingPath, "mldsa.key")
|
||||
defaultStakingMLDSAPubKeyPath = filepath.Join(defaultStakingPath, "mldsa.pub")
|
||||
defaultHandshakeMLKEMKeyPath = filepath.Join(defaultStakingPath, "mlkem.key")
|
||||
defaultHandshakeMLKEMPubKeyPath = filepath.Join(defaultStakingPath, "mlkem.pub")
|
||||
defaultConfigDir = filepath.Join(defaultUnexpandedDataDir, "configs")
|
||||
defaultChainConfigDir = filepath.Join(defaultConfigDir, "chains")
|
||||
defaultVMConfigDir = filepath.Join(defaultConfigDir, "vms")
|
||||
defaultVMAliasFilePath = filepath.Join(defaultVMConfigDir, "aliases.json")
|
||||
defaultChainAliasFilePath = filepath.Join(defaultChainConfigDir, "aliases.json")
|
||||
defaultNetConfigDir = filepath.Join(defaultConfigDir, "chains")
|
||||
defaultPluginDir = filepath.Join(defaultUnexpandedDataDir, "plugins", "current")
|
||||
defaultChainDataDir = filepath.Join(defaultUnexpandedDataDir, "chainData")
|
||||
defaultProcessContextPath = filepath.Join(defaultUnexpandedDataDir, DefaultProcessContextFilename)
|
||||
)
|
||||
|
||||
func deprecateFlags(fs *pflag.FlagSet) error {
|
||||
@@ -109,8 +115,6 @@ func addNodeFlags(fs *pflag.FlagSet) {
|
||||
fs.String(GenesisDBKey, "", "Path to existing genesis database for replay. Cannot be used with genesis-file or genesis-file-content")
|
||||
fs.String(GenesisDBTypeKey, "zapdb", "Database type to use for genesis database. Must be one of {pebbledb, zapdb}")
|
||||
fs.Uint64(GenesisBlockLimitKey, 0, "Limit number of blocks to replay during genesis (0 = all blocks)")
|
||||
fs.Bool(AllowCustomGenesisKey, true, "Allow custom genesis for mainnet/testnet (default: true for development, set false for production)")
|
||||
fs.Bool(AllowGenesisUpdateKey, false, "Allow updating stored genesis hash when genesis changes (use for adding new primary network chains)")
|
||||
|
||||
// Upgrade
|
||||
fs.String(UpgradeFileKey, "", fmt.Sprintf("Specifies an upgrade config file path. Ignored when running standard networks or if %s is specified",
|
||||
@@ -290,6 +294,20 @@ func addNodeFlags(fs *pflag.FlagSet) {
|
||||
fs.Bool(StakingEphemeralSignerEnabledKey, false, "If true, the node uses an ephemeral staking signer key")
|
||||
fs.String(StakingSignerKeyPathKey, defaultStakingSignerKeyPath, fmt.Sprintf("Path to the signer private key for staking. Ignored if %s is specified", StakingSignerKeyContentKey))
|
||||
fs.String(StakingSignerKeyContentKey, "", "Specifies base64 encoded signer private key for staking")
|
||||
// Strict-PQ identity (FIPS 204 ML-DSA-65 + FIPS 203 ML-KEM-768).
|
||||
// When the ML-DSA pubkey is provided, NodeID derivation pivots from
|
||||
// the classical TLS-cert path to the wire-discriminated strict-PQ
|
||||
// scheme (ids.NodeIDSchemeMLDSA65.DeriveMLDSA). Both pairs default
|
||||
// to the layout downstream-tenant CLI `<tenantctl> key gen` writes — wire the
|
||||
// init container and lqd reads the files with zero extra config.
|
||||
fs.String(StakingMLDSAKeyPathKey, defaultStakingMLDSAKeyPath, fmt.Sprintf("Path to the ML-DSA-65 staking private key (FIPS 204 PEM). Ignored if %s is specified", StakingMLDSAKeyContentKey))
|
||||
fs.String(StakingMLDSAKeyContentKey, "", "Base64-encoded ML-DSA-65 staking private key (FIPS 204 PEM)")
|
||||
fs.String(StakingMLDSAPubKeyPathKey, defaultStakingMLDSAPubKeyPath, fmt.Sprintf("Path to the ML-DSA-65 staking public key (FIPS 204 PEM). Ignored if %s is specified", StakingMLDSAPubKeyContentKey))
|
||||
fs.String(StakingMLDSAPubKeyContentKey, "", "Base64-encoded ML-DSA-65 staking public key (FIPS 204 PEM)")
|
||||
fs.String(HandshakeMLKEMKeyPathKey, defaultHandshakeMLKEMKeyPath, fmt.Sprintf("Path to the ML-KEM-768 handshake private key (FIPS 203 PEM). Ignored if %s is specified", HandshakeMLKEMKeyContentKey))
|
||||
fs.String(HandshakeMLKEMKeyContentKey, "", "Base64-encoded ML-KEM-768 handshake private key (FIPS 203 PEM)")
|
||||
fs.String(HandshakeMLKEMPubKeyPathKey, defaultHandshakeMLKEMPubKeyPath, fmt.Sprintf("Path to the ML-KEM-768 handshake public key (FIPS 203 PEM). Ignored if %s is specified", HandshakeMLKEMPubKeyContentKey))
|
||||
fs.String(HandshakeMLKEMPubKeyContentKey, "", "Base64-encoded ML-KEM-768 handshake public key (FIPS 203 PEM)")
|
||||
fs.String(StakingKMSEndpointKey, "", "KMS endpoint for staking key retrieval (e.g., https://kms.dev.lux.network)")
|
||||
fs.String(StakingKMSSecretPathKey, "", "KMS secret path for staking keys (e.g., /staking/liquid-devnet/node-0)")
|
||||
fs.String(StakingKMSTokenKey, "", "KMS auth token for staking key retrieval")
|
||||
|
||||
+48
-34
@@ -20,8 +20,6 @@ const (
|
||||
GenesisDBKey = "genesis-db"
|
||||
GenesisDBTypeKey = "genesis-db-type"
|
||||
GenesisBlockLimitKey = "genesis-block-limit"
|
||||
AllowCustomGenesisKey = "allow-custom-genesis"
|
||||
AllowGenesisUpdateKey = "allow-genesis-update"
|
||||
UpgradeFileKey = "upgrade-file"
|
||||
UpgradeFileContentKey = "upgrade-file-content"
|
||||
NetworkNameKey = "network-id"
|
||||
@@ -80,27 +78,43 @@ const (
|
||||
HTTPReadTimeoutKey = "http-read-timeout"
|
||||
HTTPReadHeaderTimeoutKey = "http-read-header-timeout"
|
||||
|
||||
HTTPIdleTimeoutKey = "http-idle-timeout"
|
||||
StateSyncIPsKey = "state-sync-ips"
|
||||
StateSyncIDsKey = "state-sync-ids"
|
||||
BootstrapNodesKey = "bootstrap-nodes"
|
||||
BootstrapIPsKey = "bootstrap-ips"
|
||||
BootstrapIDsKey = "bootstrap-ids"
|
||||
SkipBootstrapKey = "skip-bootstrap"
|
||||
EnableAutominingKey = "enable-automining"
|
||||
StakingHostKey = "staking-host"
|
||||
StakingPortKey = "staking-port"
|
||||
StakingEphemeralCertEnabledKey = "staking-ephemeral-cert-enabled"
|
||||
StakingTLSKeyPathKey = "staking-tls-key-file"
|
||||
StakingTLSKeyContentKey = "staking-tls-key-file-content"
|
||||
StakingCertPathKey = "staking-tls-cert-file"
|
||||
StakingCertContentKey = "staking-tls-cert-file-content"
|
||||
StakingEphemeralSignerEnabledKey = "staking-ephemeral-signer-enabled"
|
||||
StakingSignerKeyPathKey = "staking-signer-key-file"
|
||||
StakingSignerKeyContentKey = "staking-signer-key-file-content"
|
||||
StakingKMSEndpointKey = "staking-kms-endpoint"
|
||||
StakingKMSSecretPathKey = "staking-kms-secret-path"
|
||||
StakingKMSTokenKey = "staking-kms-token"
|
||||
HTTPIdleTimeoutKey = "http-idle-timeout"
|
||||
StateSyncIPsKey = "state-sync-ips"
|
||||
StateSyncIDsKey = "state-sync-ids"
|
||||
BootstrapNodesKey = "bootstrap-nodes"
|
||||
BootstrapIPsKey = "bootstrap-ips"
|
||||
BootstrapIDsKey = "bootstrap-ids"
|
||||
SkipBootstrapKey = "skip-bootstrap"
|
||||
EnableAutominingKey = "enable-automining"
|
||||
StakingHostKey = "staking-host"
|
||||
StakingPortKey = "staking-port"
|
||||
StakingEphemeralCertEnabledKey = "staking-ephemeral-cert-enabled"
|
||||
StakingTLSKeyPathKey = "staking-tls-key-file"
|
||||
StakingTLSKeyContentKey = "staking-tls-key-file-content"
|
||||
StakingCertPathKey = "staking-tls-cert-file"
|
||||
StakingCertContentKey = "staking-tls-cert-file-content"
|
||||
StakingEphemeralSignerEnabledKey = "staking-ephemeral-signer-enabled"
|
||||
StakingSignerKeyPathKey = "staking-signer-key-file"
|
||||
StakingSignerKeyContentKey = "staking-signer-key-file-content"
|
||||
StakingKMSEndpointKey = "staking-kms-endpoint"
|
||||
StakingKMSSecretPathKey = "staking-kms-secret-path"
|
||||
StakingKMSTokenKey = "staking-kms-token"
|
||||
// Strict-PQ staking identity (FIPS 204 ML-DSA-65). When set, the node
|
||||
// uses the ML-DSA-65 public key as the NodeID source via
|
||||
// ids.NodeIDSchemeMLDSA65.DeriveMLDSA(chainID, pubKey), replacing the
|
||||
// classical TLS-cert NodeID derivation. Strict-PQ profiles require
|
||||
// these; classical-compat chains ignore them.
|
||||
StakingMLDSAKeyPathKey = "staking-mldsa-key-file"
|
||||
StakingMLDSAKeyContentKey = "staking-mldsa-key-file-content"
|
||||
StakingMLDSAPubKeyPathKey = "staking-mldsa-pub-key-file"
|
||||
StakingMLDSAPubKeyContentKey = "staking-mldsa-pub-key-file-content"
|
||||
// Strict-PQ handshake KEM (FIPS 203 ML-KEM-768). Peer-facing public
|
||||
// key is published in the validator-set entry so peers can encapsulate
|
||||
// to it for session-key establishment with no classical fallback.
|
||||
HandshakeMLKEMKeyPathKey = "handshake-mlkem-key-file"
|
||||
HandshakeMLKEMKeyContentKey = "handshake-mlkem-key-file-content"
|
||||
HandshakeMLKEMPubKeyPathKey = "handshake-mlkem-pub-key-file"
|
||||
HandshakeMLKEMPubKeyContentKey = "handshake-mlkem-pub-key-file-content"
|
||||
SybilProtectionEnabledKey = "sybil-protection-enabled"
|
||||
SybilProtectionDisabledWeightKey = "sybil-protection-disabled-weight"
|
||||
NetworkInitialTimeoutKey = "network-initial-timeout"
|
||||
@@ -250,17 +264,17 @@ const (
|
||||
ForceIgnoreChecksumKey = "force-ignore-checksum"
|
||||
|
||||
// Low Memory / Dev Light Mode Keys
|
||||
LowMemoryKey = "low-memory"
|
||||
MemoryProfileKey = "memory-profile"
|
||||
DevLightKey = "dev-light"
|
||||
ConfigProfileKey = "config-profile"
|
||||
DBCacheSizeKey = "db-cache-size"
|
||||
DBMemtableSizeKey = "db-memtable-size"
|
||||
StateCacheSizeKey = "state-cache-size"
|
||||
BlockCacheSizeKey = "block-cache-size"
|
||||
DisableBloomFiltersKey = "disable-bloom-filters"
|
||||
LazyChainLoadingKey = "lazy-chain-loading"
|
||||
SingleValidatorModeKey = "single-validator-mode"
|
||||
LowMemoryKey = "low-memory"
|
||||
MemoryProfileKey = "memory-profile"
|
||||
DevLightKey = "dev-light"
|
||||
ConfigProfileKey = "config-profile"
|
||||
DBCacheSizeKey = "db-cache-size"
|
||||
DBMemtableSizeKey = "db-memtable-size"
|
||||
StateCacheSizeKey = "state-cache-size"
|
||||
BlockCacheSizeKey = "block-cache-size"
|
||||
DisableBloomFiltersKey = "disable-bloom-filters"
|
||||
LazyChainLoadingKey = "lazy-chain-loading"
|
||||
SingleValidatorModeKey = "single-validator-mode"
|
||||
|
||||
// VM Transport Keys
|
||||
VMTransportKey = "vm-transport"
|
||||
|
||||
+99
-6
@@ -5,25 +5,28 @@ package node
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/server/http"
|
||||
"github.com/luxfi/node/benchlist"
|
||||
"github.com/luxfi/node/chains"
|
||||
"github.com/luxfi/node/genesis/builder"
|
||||
"github.com/luxfi/node/network"
|
||||
"github.com/luxfi/node/server/http"
|
||||
// "github.com/luxfi/consensus/core/router" // Unused
|
||||
"github.com/luxfi/crypto/bls"
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
mlkemcrypto "github.com/luxfi/crypto/mlkem"
|
||||
"github.com/luxfi/node/nets"
|
||||
"github.com/luxfi/node/network/tracker"
|
||||
"github.com/luxfi/node/upgrade"
|
||||
"github.com/luxfi/node/trace"
|
||||
"github.com/luxfi/node/upgrade"
|
||||
// "github.com/luxfi/log" // Unused
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/timer"
|
||||
"github.com/luxfi/node/utils/profiler"
|
||||
"github.com/luxfi/timer"
|
||||
)
|
||||
|
||||
type APIIndexerConfig struct {
|
||||
@@ -83,6 +86,30 @@ type StakingConfig struct {
|
||||
StakingKeyPath string `json:"stakingKeyPath"`
|
||||
StakingCertPath string `json:"stakingCertPath"`
|
||||
StakingSignerPath string `json:"stakingSignerPath"`
|
||||
|
||||
// Strict-PQ identity material. Set by config.GetNodeConfig when the
|
||||
// --staking-mldsa-*-file / --handshake-mlkem-*-file flags resolve.
|
||||
// All four fields are nil/empty under a classical-compat chain;
|
||||
// strict-PQ profile rejects a missing StakingMLDSAPub at boot.
|
||||
//
|
||||
// NodeID derivation pivots on StakingMLDSAPub: when non-empty,
|
||||
// ids.NodeIDSchemeMLDSA65.DeriveMLDSA(stakingChainID, StakingMLDSAPub)
|
||||
// replaces ids.NodeIDFromCert(StakingTLSCert) — the TLS cert becomes
|
||||
// transport-only, no longer the identity anchor.
|
||||
//
|
||||
// HandshakeMLKEMPub is the peer-facing KEM public key that lqd
|
||||
// publishes in its validator-set entry so peers can encapsulate to it
|
||||
// for session-key establishment with no classical fallback. The
|
||||
// HandshakeMLKEMPriv stays local to the pod.
|
||||
StakingMLDSA *mldsa.PrivateKey `json:"-"`
|
||||
StakingMLDSAPub []byte `json:"-"`
|
||||
HandshakeMLKEMPriv *mlkemcrypto.PrivateKey `json:"-"`
|
||||
HandshakeMLKEMPub []byte `json:"-"`
|
||||
// File paths kept for log-line context, mirroring StakingKeyPath etc.
|
||||
StakingMLDSAKeyPath string `json:"stakingMLDSAKeyPath,omitempty"`
|
||||
StakingMLDSAPubPath string `json:"stakingMLDSAPubPath,omitempty"`
|
||||
HandshakeMLKEMKeyPath string `json:"handshakeMLKEMKeyPath,omitempty"`
|
||||
HandshakeMLKEMPubPath string `json:"handshakeMLKEMPubPath,omitempty"`
|
||||
}
|
||||
|
||||
type StateSyncConfig struct {
|
||||
@@ -141,9 +168,8 @@ type Config struct {
|
||||
UpgradeConfig upgrade.Config `json:"upgradeConfig"`
|
||||
|
||||
// Genesis information
|
||||
GenesisBytes []byte `json:"-"`
|
||||
LuxAssetID ids.ID `json:"xAssetID"`
|
||||
AllowGenesisUpdate bool `json:"allowGenesisUpdate,omitempty"`
|
||||
GenesisBytes []byte `json:"-"`
|
||||
UTXOAssetID ids.ID `json:"utxoAssetID"`
|
||||
|
||||
// ID of the network this node should connect to
|
||||
NetworkID uint32 `json:"networkID"`
|
||||
@@ -240,3 +266,70 @@ type Config struct {
|
||||
LazyChainLoading bool `json:"lazyChainLoading"`
|
||||
SingleValidatorMode bool `json:"singleValidatorMode"`
|
||||
}
|
||||
|
||||
// IsStrictPQ reports whether the staking config has strict-PQ
|
||||
// identity material loaded. Strict-PQ requires an ML-DSA-65 public
|
||||
// key (signing identity) — the ML-KEM-768 KEM is a complementary
|
||||
// handshake primitive that the validator-set entry publishes for
|
||||
// peers but is NOT what defines the validator's identity. The
|
||||
// 0-byte slice case is "classical-compat": NodeID derives from the
|
||||
// TLS staking cert in StakingTLSCert.
|
||||
func (c *StakingConfig) IsStrictPQ() bool {
|
||||
return len(c.StakingMLDSAPub) > 0
|
||||
}
|
||||
|
||||
// DeriveNodeID returns the canonical 20-byte NodeID for this
|
||||
// staking identity under chainID:
|
||||
//
|
||||
// - Strict-PQ (StakingMLDSAPub set):
|
||||
// NodeID = SHAKE256-384("NODE_ID_V1" || chainID || 0x42 || pubKey)[:20]
|
||||
// via ids.NodeIDSchemeMLDSA65.DeriveMLDSA. chainID-bound so the
|
||||
// same key on a different chain produces a different NodeID,
|
||||
// blocking cross-chain replay of validator registrations.
|
||||
// - Classical-compat (no ML-DSA pub):
|
||||
// NodeID = ids.NodeIDFromCert(StakingTLSCert.Leaf) — the
|
||||
// legacy upstream derivation. A strict-PQ chain MUST refuse
|
||||
// this branch at the validator-set boundary; the choice lives
|
||||
// in the consensus profile, not here.
|
||||
//
|
||||
// This is the single seam every "what's my NodeID" call site should
|
||||
// route through. Direct calls to ids.NodeIDFromCert in new code are
|
||||
// a bug — they bypass the strict-PQ pivot.
|
||||
func (c *StakingConfig) DeriveNodeID(chainID ids.ID) (ids.NodeID, error) {
|
||||
if c.IsStrictPQ() {
|
||||
id, _, err := ids.NodeIDSchemeMLDSA65.DeriveMLDSA(chainID, c.StakingMLDSAPub)
|
||||
return id, err
|
||||
}
|
||||
if c.StakingTLSCert.Leaf == nil {
|
||||
return ids.EmptyNodeID, errStakingTLSCertLeafNil
|
||||
}
|
||||
return ids.NodeIDFromCert(&ids.Certificate{
|
||||
Raw: c.StakingTLSCert.Leaf.Raw,
|
||||
PublicKey: c.StakingTLSCert.Leaf.PublicKey,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// DeriveTypedNodeID returns the wire-canonical TypedNodeID — scheme
|
||||
// byte (0x42 for strict-PQ ML-DSA-65, 0x90 for classical secp256k1)
|
||||
// followed by the 20-byte NodeID. The scheme byte travels with the
|
||||
// NodeID on every handshake / validator-set serialization so a
|
||||
// receiver MUST know which verifier to dispatch before consulting
|
||||
// the chain profile. Profile is a downgrade-detection gate; scheme
|
||||
// byte is a primitive-mismatch gate.
|
||||
func (c *StakingConfig) DeriveTypedNodeID(chainID ids.ID) (ids.TypedNodeID, error) {
|
||||
if c.IsStrictPQ() {
|
||||
t, _, err := ids.TypedNodeIDFromMLDSA(
|
||||
ids.NodeIDSchemeMLDSA65, chainID, c.StakingMLDSAPub)
|
||||
return t, err
|
||||
}
|
||||
if c.StakingTLSCert.Leaf == nil {
|
||||
return ids.TypedNodeID{}, errStakingTLSCertLeafNil
|
||||
}
|
||||
return ids.TypedNodeIDFromCert(&ids.Certificate{
|
||||
Raw: c.StakingTLSCert.Leaf.Raw,
|
||||
PublicKey: c.StakingTLSCert.Leaf.PublicKey,
|
||||
}), nil
|
||||
}
|
||||
|
||||
var errStakingTLSCertLeafNil = errors.New(
|
||||
"node: StakingConfig.StakingTLSCert.Leaf is nil — neither ML-DSA-65 nor a parsed TLS cert is available")
|
||||
|
||||
+73
-7
@@ -131,13 +131,6 @@ func allFlags() []FlagSpec {
|
||||
Description: "Limit number of blocks to replay during genesis (0 = all blocks)",
|
||||
Category: CategoryGenesis,
|
||||
},
|
||||
{
|
||||
Key: "allow-custom-genesis",
|
||||
Type: TypeBool,
|
||||
Default: true,
|
||||
Description: "Allow custom genesis for mainnet/testnet (default: true for development, set false for production)",
|
||||
Category: CategoryGenesis,
|
||||
},
|
||||
{
|
||||
Key: "upgrade-file",
|
||||
Type: TypeString,
|
||||
@@ -1078,6 +1071,79 @@ func allFlags() []FlagSpec {
|
||||
Category: CategoryStaking,
|
||||
Sensitive: true,
|
||||
},
|
||||
// Strict-PQ staking identity (FIPS 204 ML-DSA-65). When supplied,
|
||||
// the node uses the ML-DSA-65 public key as the NodeID source via
|
||||
// ids.NodeIDSchemeMLDSA65.DeriveMLDSA(chainID, pubKey) — replacing
|
||||
// the classical TLS-cert NodeID derivation. The classical TLS
|
||||
// staker.crt/key + signer.key flags above are retained only for
|
||||
// legacy chains that ship no ChainSecurityProfile; strict-PQ
|
||||
// chains refuse classical material at every boundary.
|
||||
{
|
||||
Key: "staking-mldsa-key-file",
|
||||
Type: TypeString,
|
||||
Default: "$LUXD_DATA_DIR/staking/mldsa.key",
|
||||
Description: "Path to the ML-DSA-65 staking private key (FIPS 204 PEM). Ignored if staking-mldsa-key-file-content is specified",
|
||||
Category: CategoryStaking,
|
||||
Sensitive: true,
|
||||
},
|
||||
{
|
||||
Key: "staking-mldsa-key-file-content",
|
||||
Type: TypeString,
|
||||
Default: "",
|
||||
Description: "Base64-encoded ML-DSA-65 staking private key (FIPS 204 PEM)",
|
||||
Category: CategoryStaking,
|
||||
Sensitive: true,
|
||||
},
|
||||
{
|
||||
Key: "staking-mldsa-pub-key-file",
|
||||
Type: TypeString,
|
||||
Default: "$LUXD_DATA_DIR/staking/mldsa.pub",
|
||||
Description: "Path to the ML-DSA-65 staking public key (FIPS 204 PEM). Ignored if staking-mldsa-pub-key-file-content is specified",
|
||||
Category: CategoryStaking,
|
||||
},
|
||||
{
|
||||
Key: "staking-mldsa-pub-key-file-content",
|
||||
Type: TypeString,
|
||||
Default: "",
|
||||
Description: "Base64-encoded ML-DSA-65 staking public key (FIPS 204 PEM)",
|
||||
Category: CategoryStaking,
|
||||
},
|
||||
// Strict-PQ handshake KEM (FIPS 203 ML-KEM-768). Peer-facing public
|
||||
// key is published in the validator-set entry so peers can
|
||||
// encapsulate to it for session-key establishment — the PQ replacement
|
||||
// for classical TLS key agreement on the libp2p handshake. The
|
||||
// private key stays on the validator pod; the public key is the only
|
||||
// material that crosses the wire.
|
||||
{
|
||||
Key: "handshake-mlkem-key-file",
|
||||
Type: TypeString,
|
||||
Default: "$LUXD_DATA_DIR/staking/mlkem.key",
|
||||
Description: "Path to the ML-KEM-768 handshake private key (FIPS 203 PEM). Ignored if handshake-mlkem-key-file-content is specified",
|
||||
Category: CategoryStaking,
|
||||
Sensitive: true,
|
||||
},
|
||||
{
|
||||
Key: "handshake-mlkem-key-file-content",
|
||||
Type: TypeString,
|
||||
Default: "",
|
||||
Description: "Base64-encoded ML-KEM-768 handshake private key (FIPS 203 PEM)",
|
||||
Category: CategoryStaking,
|
||||
Sensitive: true,
|
||||
},
|
||||
{
|
||||
Key: "handshake-mlkem-pub-key-file",
|
||||
Type: TypeString,
|
||||
Default: "$LUXD_DATA_DIR/staking/mlkem.pub",
|
||||
Description: "Path to the ML-KEM-768 handshake public key (FIPS 203 PEM). Ignored if handshake-mlkem-pub-key-file-content is specified",
|
||||
Category: CategoryStaking,
|
||||
},
|
||||
{
|
||||
Key: "handshake-mlkem-pub-key-file-content",
|
||||
Type: TypeString,
|
||||
Default: "",
|
||||
Description: "Base64-encoded ML-KEM-768 handshake public key (FIPS 203 PEM)",
|
||||
Category: CategoryStaking,
|
||||
},
|
||||
{
|
||||
Key: "sybil-protection-enabled",
|
||||
Type: TypeBool,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
version: v1
|
||||
plugins:
|
||||
- name: go
|
||||
out: pb
|
||||
opt: paths=source_relative
|
||||
- plugin: connect-go
|
||||
out: pb
|
||||
opt: paths=source_relative
|
||||
@@ -1,7 +0,0 @@
|
||||
# Generated by buf. DO NOT EDIT.
|
||||
version: v1
|
||||
deps:
|
||||
- remote: buf.build
|
||||
owner: metric
|
||||
repository: client-model
|
||||
commit: 1d56a02d481a412a83b3c4984eb90c2e
|
||||
@@ -1,27 +0,0 @@
|
||||
version: v1
|
||||
name: buf.build/luxfi/lux
|
||||
build:
|
||||
excludes:
|
||||
# for golang we handle metric as a buf dep so we exclude it from generate, this proto
|
||||
# file is required by languages such as rust.
|
||||
- io/metric
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
deps:
|
||||
- buf.build/metric/client-model
|
||||
lint:
|
||||
use:
|
||||
- STANDARD
|
||||
except:
|
||||
- SERVICE_SUFFIX # service requirement of <name>+Service
|
||||
- RPC_REQUEST_STANDARD_NAME # explicit <rpc>+Request naming
|
||||
- RPC_RESPONSE_STANDARD_NAME # explicit <rpc>+Response naming
|
||||
- PACKAGE_VERSION_SUFFIX # versioned naming <service>.v1beta
|
||||
# allows RPC requests or responses to be google.protobuf.Empty messages. This can be set if you
|
||||
# want to allow messages to be void forever, that is they will never take any parameters.
|
||||
rpc_allow_google_protobuf_empty_requests: true
|
||||
rpc_allow_google_protobuf_empty_responses: true
|
||||
# allows the same message type to be used for a single RPC's request and response type.
|
||||
# TODO: this should not be tolerated and if it is only perscriptivly.
|
||||
rpc_allow_same_request_response: true
|
||||
@@ -1,288 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.35.1
|
||||
// protoc (unknown)
|
||||
// source: xsvm/service.proto
|
||||
|
||||
package xsvm
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type PingRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PingRequest) Reset() {
|
||||
*x = PingRequest{}
|
||||
mi := &file_xsvm_service_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PingRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PingRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PingRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_xsvm_service_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PingRequest) Descriptor() ([]byte, []int) {
|
||||
return file_xsvm_service_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *PingRequest) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type PingReply struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *PingReply) Reset() {
|
||||
*x = PingReply{}
|
||||
mi := &file_xsvm_service_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PingReply) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PingReply) ProtoMessage() {}
|
||||
|
||||
func (x *PingReply) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_xsvm_service_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PingReply.ProtoReflect.Descriptor instead.
|
||||
func (*PingReply) Descriptor() ([]byte, []int) {
|
||||
return file_xsvm_service_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *PingReply) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type StreamPingRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StreamPingRequest) Reset() {
|
||||
*x = StreamPingRequest{}
|
||||
mi := &file_xsvm_service_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *StreamPingRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StreamPingRequest) ProtoMessage() {}
|
||||
|
||||
func (x *StreamPingRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_xsvm_service_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StreamPingRequest.ProtoReflect.Descriptor instead.
|
||||
func (*StreamPingRequest) Descriptor() ([]byte, []int) {
|
||||
return file_xsvm_service_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *StreamPingRequest) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type StreamPingReply struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StreamPingReply) Reset() {
|
||||
*x = StreamPingReply{}
|
||||
mi := &file_xsvm_service_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *StreamPingReply) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StreamPingReply) ProtoMessage() {}
|
||||
|
||||
func (x *StreamPingReply) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_xsvm_service_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StreamPingReply.ProtoReflect.Descriptor instead.
|
||||
func (*StreamPingReply) Descriptor() ([]byte, []int) {
|
||||
return file_xsvm_service_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *StreamPingReply) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_xsvm_service_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_xsvm_service_proto_rawDesc = []byte{
|
||||
0x0a, 0x12, 0x78, 0x73, 0x76, 0x6d, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x78, 0x73, 0x76, 0x6d, 0x22, 0x27, 0x0a, 0x0b, 0x50, 0x69,
|
||||
0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73,
|
||||
0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x22, 0x25, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x53, 0x74,
|
||||
0x72, 0x65, 0x61, 0x6d, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2b, 0x0a, 0x0f, 0x53, 0x74, 0x72,
|
||||
0x65, 0x61, 0x6d, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07,
|
||||
0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x74, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x2a,
|
||||
0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x11, 0x2e, 0x78, 0x73, 0x76, 0x6d, 0x2e, 0x50, 0x69,
|
||||
0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x78, 0x73, 0x76, 0x6d,
|
||||
0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x40, 0x0a, 0x0a, 0x53, 0x74,
|
||||
0x72, 0x65, 0x61, 0x6d, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x2e, 0x78, 0x73, 0x76, 0x6d, 0x2e,
|
||||
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x15, 0x2e, 0x78, 0x73, 0x76, 0x6d, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50,
|
||||
0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2c, 0x5a, 0x2a,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x75, 0x78, 0x66, 0x69,
|
||||
0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x78, 0x73, 0x76, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_xsvm_service_proto_rawDescOnce sync.Once
|
||||
file_xsvm_service_proto_rawDescData = file_xsvm_service_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_xsvm_service_proto_rawDescGZIP() []byte {
|
||||
file_xsvm_service_proto_rawDescOnce.Do(func() {
|
||||
file_xsvm_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_xsvm_service_proto_rawDescData)
|
||||
})
|
||||
return file_xsvm_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_xsvm_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_xsvm_service_proto_goTypes = []any{
|
||||
(*PingRequest)(nil), // 0: xsvm.PingRequest
|
||||
(*PingReply)(nil), // 1: xsvm.PingReply
|
||||
(*StreamPingRequest)(nil), // 2: xsvm.StreamPingRequest
|
||||
(*StreamPingReply)(nil), // 3: xsvm.StreamPingReply
|
||||
}
|
||||
var file_xsvm_service_proto_depIdxs = []int32{
|
||||
0, // 0: xsvm.Ping.Ping:input_type -> xsvm.PingRequest
|
||||
2, // 1: xsvm.Ping.StreamPing:input_type -> xsvm.StreamPingRequest
|
||||
1, // 2: xsvm.Ping.Ping:output_type -> xsvm.PingReply
|
||||
3, // 3: xsvm.Ping.StreamPing:output_type -> xsvm.StreamPingReply
|
||||
2, // [2:4] is the sub-list for method output_type
|
||||
0, // [0:2] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_xsvm_service_proto_init() }
|
||||
func file_xsvm_service_proto_init() {
|
||||
if File_xsvm_service_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_xsvm_service_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_xsvm_service_proto_goTypes,
|
||||
DependencyIndexes: file_xsvm_service_proto_depIdxs,
|
||||
MessageInfos: file_xsvm_service_proto_msgTypes,
|
||||
}.Build()
|
||||
File_xsvm_service_proto = out.File
|
||||
file_xsvm_service_proto_rawDesc = nil
|
||||
file_xsvm_service_proto_goTypes = nil
|
||||
file_xsvm_service_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Code generated by protoc-gen-connect-go. DO NOT EDIT.
|
||||
//
|
||||
// Source: xsvm/service.proto
|
||||
|
||||
package xsvmconnect
|
||||
|
||||
import (
|
||||
connect "connectrpc.com/connect"
|
||||
context "context"
|
||||
errors "errors"
|
||||
xsvm "github.com/luxfi/node/connectproto/pb/xsvm"
|
||||
http "net/http"
|
||||
strings "strings"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the connect package are
|
||||
// compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of connect newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of connect or updating the connect
|
||||
// version compiled into your binary.
|
||||
const _ = connect.IsAtLeastVersion1_13_0
|
||||
|
||||
const (
|
||||
// PingName is the fully-qualified name of the Ping service.
|
||||
PingName = "xsvm.Ping"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// PingPingProcedure is the fully-qualified name of the Ping's Ping RPC.
|
||||
PingPingProcedure = "/xsvm.Ping/Ping"
|
||||
// PingStreamPingProcedure is the fully-qualified name of the Ping's StreamPing RPC.
|
||||
PingStreamPingProcedure = "/xsvm.Ping/StreamPing"
|
||||
)
|
||||
|
||||
// PingClient is a client for the xsvm.Ping service.
|
||||
type PingClient interface {
|
||||
Ping(context.Context, *connect.Request[xsvm.PingRequest]) (*connect.Response[xsvm.PingReply], error)
|
||||
StreamPing(context.Context) *connect.BidiStreamForClient[xsvm.StreamPingRequest, xsvm.StreamPingReply]
|
||||
}
|
||||
|
||||
// NewPingClient constructs a client for the xsvm.Ping service. By default, it uses the Connect
|
||||
// protocol with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed
|
||||
// requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
|
||||
// connect.WithGRPCWeb() options.
|
||||
//
|
||||
// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
|
||||
// http://api.acme.com or https://acme.com/grpc).
|
||||
func NewPingClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PingClient {
|
||||
baseURL = strings.TrimRight(baseURL, "/")
|
||||
pingMethods := xsvm.File_xsvm_service_proto.Services().ByName("Ping").Methods()
|
||||
return &pingClient{
|
||||
ping: connect.NewClient[xsvm.PingRequest, xsvm.PingReply](
|
||||
httpClient,
|
||||
baseURL+PingPingProcedure,
|
||||
connect.WithSchema(pingMethods.ByName("Ping")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
streamPing: connect.NewClient[xsvm.StreamPingRequest, xsvm.StreamPingReply](
|
||||
httpClient,
|
||||
baseURL+PingStreamPingProcedure,
|
||||
connect.WithSchema(pingMethods.ByName("StreamPing")),
|
||||
connect.WithClientOptions(opts...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// pingClient implements PingClient.
|
||||
type pingClient struct {
|
||||
ping *connect.Client[xsvm.PingRequest, xsvm.PingReply]
|
||||
streamPing *connect.Client[xsvm.StreamPingRequest, xsvm.StreamPingReply]
|
||||
}
|
||||
|
||||
// Ping calls xsvm.Ping.Ping.
|
||||
func (c *pingClient) Ping(ctx context.Context, req *connect.Request[xsvm.PingRequest]) (*connect.Response[xsvm.PingReply], error) {
|
||||
return c.ping.CallUnary(ctx, req)
|
||||
}
|
||||
|
||||
// StreamPing calls xsvm.Ping.StreamPing.
|
||||
func (c *pingClient) StreamPing(ctx context.Context) *connect.BidiStreamForClient[xsvm.StreamPingRequest, xsvm.StreamPingReply] {
|
||||
return c.streamPing.CallBidiStream(ctx)
|
||||
}
|
||||
|
||||
// PingHandler is an implementation of the xsvm.Ping service.
|
||||
type PingHandler interface {
|
||||
Ping(context.Context, *connect.Request[xsvm.PingRequest]) (*connect.Response[xsvm.PingReply], error)
|
||||
StreamPing(context.Context, *connect.BidiStream[xsvm.StreamPingRequest, xsvm.StreamPingReply]) error
|
||||
}
|
||||
|
||||
// NewPingHandler builds an HTTP handler from the service implementation. It returns the path on
|
||||
// which to mount the handler and the handler itself.
|
||||
//
|
||||
// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
|
||||
// and JSON codecs. They also support gzip compression.
|
||||
func NewPingHandler(svc PingHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||
pingMethods := xsvm.File_xsvm_service_proto.Services().ByName("Ping").Methods()
|
||||
pingPingHandler := connect.NewUnaryHandler(
|
||||
PingPingProcedure,
|
||||
svc.Ping,
|
||||
connect.WithSchema(pingMethods.ByName("Ping")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
pingStreamPingHandler := connect.NewBidiStreamHandler(
|
||||
PingStreamPingProcedure,
|
||||
svc.StreamPing,
|
||||
connect.WithSchema(pingMethods.ByName("StreamPing")),
|
||||
connect.WithHandlerOptions(opts...),
|
||||
)
|
||||
return "/xsvm.Ping/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case PingPingProcedure:
|
||||
pingPingHandler.ServeHTTP(w, r)
|
||||
case PingStreamPingProcedure:
|
||||
pingStreamPingHandler.ServeHTTP(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// UnimplementedPingHandler returns CodeUnimplemented from all methods.
|
||||
type UnimplementedPingHandler struct{}
|
||||
|
||||
func (UnimplementedPingHandler) Ping(context.Context, *connect.Request[xsvm.PingRequest]) (*connect.Response[xsvm.PingReply], error) {
|
||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("xsvm.Ping.Ping is not implemented"))
|
||||
}
|
||||
|
||||
func (UnimplementedPingHandler) StreamPing(context.Context, *connect.BidiStream[xsvm.StreamPingRequest, xsvm.StreamPingReply]) error {
|
||||
return connect.NewError(connect.CodeUnimplemented, errors.New("xsvm.Ping.StreamPing is not implemented"))
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package xsvm;
|
||||
|
||||
option go_package = "github.com/luxfi/node/connectproto/pb/xsvm";
|
||||
|
||||
service Ping {
|
||||
rpc Ping(PingRequest) returns (PingReply);
|
||||
rpc StreamPing(stream StreamPingRequest) returns (stream StreamPingReply);
|
||||
}
|
||||
|
||||
message PingRequest {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message PingReply {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message StreamPingRequest {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message StreamPingReply {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/luxfi/runtime"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -68,32 +68,32 @@ type MLDSAWork struct {
|
||||
|
||||
// BlockVerifyWork contains all verification batches for a single block.
|
||||
type BlockVerifyWork struct {
|
||||
BLS *BLSWork
|
||||
BLS *BLSWork
|
||||
Corona *CoronaWork
|
||||
ZK *ZKWork
|
||||
MLDSA *MLDSAWork
|
||||
ZK *ZKWork
|
||||
MLDSA *MLDSAWork
|
||||
}
|
||||
|
||||
// BlockVerifyResult contains verification results for all batch types.
|
||||
type BlockVerifyResult struct {
|
||||
BLSValid []bool
|
||||
BLSValid []bool
|
||||
CoronaValid []bool
|
||||
ZKValid bool
|
||||
MLDSAValid []bool
|
||||
ZKValid bool
|
||||
MLDSAValid []bool
|
||||
|
||||
GPUUsed bool
|
||||
BLSTime time.Duration
|
||||
GPUUsed bool
|
||||
BLSTime time.Duration
|
||||
CoronaTime time.Duration
|
||||
ZKTime time.Duration
|
||||
MLDSATime time.Duration
|
||||
TotalTime time.Duration
|
||||
ZKTime time.Duration
|
||||
MLDSATime time.Duration
|
||||
TotalTime time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
ErrBLSSizeMismatch = errors.New("BLS batch size mismatch: messages, signatures, and pubkeys must have equal length")
|
||||
ErrBLSSizeMismatch = errors.New("BLS batch size mismatch: messages, signatures, and pubkeys must have equal length")
|
||||
ErrCoronaSizeMismatch = errors.New("Corona batch size mismatch: messages, signatures, and pubkeys must have equal length")
|
||||
ErrZKSizeMismatch = errors.New("ZK batch size mismatch: scalars and bases must have equal length")
|
||||
ErrMLDSASizeMismatch = errors.New("ML-DSA batch size mismatch: messages, signatures, and pubkeys must have equal length")
|
||||
ErrZKSizeMismatch = errors.New("ZK batch size mismatch: scalars and bases must have equal length")
|
||||
ErrMLDSASizeMismatch = errors.New("ML-DSA batch size mismatch: messages, signatures, and pubkeys must have equal length")
|
||||
)
|
||||
|
||||
// VerifyBlock dispatches all verification work for a block through the GPU pipeline.
|
||||
|
||||
@@ -79,10 +79,10 @@ func TestGPUPipeline_AllFourTypes(t *testing.T) {
|
||||
pipeline := NewGPUVerifyPipeline()
|
||||
|
||||
work := &BlockVerifyWork{
|
||||
BLS: makeBLSWork(5),
|
||||
BLS: makeBLSWork(5),
|
||||
Corona: makeCoronaWork(3),
|
||||
ZK: makeZKWork(2),
|
||||
MLDSA: makeMLDSAWork(10),
|
||||
ZK: makeZKWork(2),
|
||||
MLDSA: makeMLDSAWork(10),
|
||||
}
|
||||
|
||||
result, err := pipeline.VerifyBlock(work)
|
||||
@@ -276,10 +276,10 @@ func BenchmarkGPUPipeline(b *testing.B) {
|
||||
pipeline := NewGPUVerifyPipeline()
|
||||
|
||||
work := &BlockVerifyWork{
|
||||
BLS: makeBLSWork(100),
|
||||
BLS: makeBLSWork(100),
|
||||
Corona: makeCoronaWork(50),
|
||||
ZK: makeZKWork(10),
|
||||
MLDSA: makeMLDSAWork(200),
|
||||
ZK: makeZKWork(10),
|
||||
MLDSA: makeMLDSAWork(200),
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
@@ -116,11 +116,11 @@ func generateValidatorStates(n int) []ValidatorState {
|
||||
_, _ = rand.Read(rtKey)
|
||||
|
||||
states[i] = ValidatorState{
|
||||
NodeID: ids.GenerateTestNodeID(),
|
||||
Weight: 1000,
|
||||
BLSPubKey: blsKey,
|
||||
NodeID: ids.GenerateTestNodeID(),
|
||||
Weight: 1000,
|
||||
BLSPubKey: blsKey,
|
||||
CoronaKey: rtKey,
|
||||
Active: true,
|
||||
Active: true,
|
||||
}
|
||||
}
|
||||
return states
|
||||
@@ -575,13 +575,13 @@ func TestQuasarMemoryPressure(t *testing.T) {
|
||||
_, _ = rand.Read(blockID[:])
|
||||
|
||||
finality := &QuantumFinality{
|
||||
BlockID: blockID,
|
||||
PChainHeight: uint64(i),
|
||||
QChainHeight: uint64(i),
|
||||
BLSProof: make([]byte, 96),
|
||||
CoronaProof: make([]byte, 1024),
|
||||
TotalWeight: 1000,
|
||||
SignerWeight: 700,
|
||||
BlockID: blockID,
|
||||
PChainHeight: uint64(i),
|
||||
QChainHeight: uint64(i),
|
||||
BLSProof: make([]byte, 96),
|
||||
CoronaProof: make([]byte, 1024),
|
||||
TotalWeight: 1000,
|
||||
SignerWeight: 700,
|
||||
}
|
||||
q.SetFinalized(blockID, finality)
|
||||
}
|
||||
@@ -913,10 +913,10 @@ func TestQuasarEdgeCases(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
finality := &QuantumFinality{
|
||||
BLSProof: nil,
|
||||
CoronaProof: nil,
|
||||
TotalWeight: 1000,
|
||||
SignerWeight: 700,
|
||||
BLSProof: nil,
|
||||
CoronaProof: nil,
|
||||
TotalWeight: 1000,
|
||||
SignerWeight: 700,
|
||||
}
|
||||
|
||||
err = q.Verify(finality)
|
||||
@@ -928,10 +928,10 @@ func TestQuasarEdgeCases(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
finality := &QuantumFinality{
|
||||
BLSProof: []byte("proof"),
|
||||
CoronaProof: []byte("proof"),
|
||||
TotalWeight: 1000,
|
||||
SignerWeight: 500, // Only 50%, needs 67%
|
||||
BLSProof: []byte("proof"),
|
||||
CoronaProof: []byte("proof"),
|
||||
TotalWeight: 1000,
|
||||
SignerWeight: 500, // Only 50%, needs 67%
|
||||
}
|
||||
|
||||
err = q.Verify(finality)
|
||||
|
||||
@@ -401,7 +401,7 @@ func TestConcurrentPruningStress(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
const (
|
||||
numWriters = 8
|
||||
numWriters = 8
|
||||
opsPerWriter = 5000
|
||||
)
|
||||
|
||||
|
||||
+45
-45
@@ -61,15 +61,15 @@ import (
|
||||
// 2. t-of-n validators completed Corona threshold (post-quantum secure)
|
||||
|
||||
var (
|
||||
ErrQuasarNotStarted = errors.New("quasar not started")
|
||||
ErrPChainNotConnected = errors.New("P-Chain not connected")
|
||||
ErrQChainNotConnected = errors.New("Q-Chain not connected")
|
||||
ErrCoronaNotConnected = errors.New("Corona coordinator not connected")
|
||||
ErrInsufficientWeight = errors.New("insufficient validator weight")
|
||||
ErrInsufficientSigners = errors.New("insufficient Corona signers")
|
||||
ErrFinalityFailed = errors.New("hybrid finality verification failed")
|
||||
ErrBLSFailed = errors.New("BLS aggregation failed")
|
||||
ErrCoronaFailed = errors.New("Corona threshold signing failed")
|
||||
ErrQuasarNotStarted = errors.New("quasar not started")
|
||||
ErrPChainNotConnected = errors.New("P-Chain not connected")
|
||||
ErrQChainNotConnected = errors.New("Q-Chain not connected")
|
||||
ErrCoronaNotConnected = errors.New("Corona coordinator not connected")
|
||||
ErrInsufficientWeight = errors.New("insufficient validator weight")
|
||||
ErrInsufficientSigners = errors.New("insufficient Corona signers")
|
||||
ErrFinalityFailed = errors.New("hybrid finality verification failed")
|
||||
ErrBLSFailed = errors.New("BLS aggregation failed")
|
||||
ErrCoronaFailed = errors.New("Corona threshold signing failed")
|
||||
)
|
||||
|
||||
// PChainProvider provides P-Chain state and finality events
|
||||
@@ -87,11 +87,11 @@ type QuantumSignerFallback interface {
|
||||
// ValidatorState represents a validator's current state
|
||||
// Each validator has BOTH BLS and Corona keys
|
||||
type ValidatorState struct {
|
||||
NodeID ids.NodeID
|
||||
Weight uint64
|
||||
BLSPubKey []byte // BLS public key for aggregate signatures
|
||||
NodeID ids.NodeID
|
||||
Weight uint64
|
||||
BLSPubKey []byte // BLS public key for aggregate signatures
|
||||
CoronaKey []byte // Corona public key share for threshold sigs
|
||||
Active bool
|
||||
Active bool
|
||||
}
|
||||
|
||||
// FinalityEvent represents a P-Chain finality event
|
||||
@@ -104,18 +104,18 @@ type FinalityEvent struct {
|
||||
|
||||
// QuantumFinality represents a block that achieved hybrid quantum finality
|
||||
type QuantumFinality struct {
|
||||
BlockID ids.ID
|
||||
PChainHeight uint64
|
||||
QChainHeight uint64
|
||||
BLSProof []byte // Aggregated BLS signature (96 bytes)
|
||||
BlockID ids.ID
|
||||
PChainHeight uint64
|
||||
QChainHeight uint64
|
||||
BLSProof []byte // Aggregated BLS signature (96 bytes)
|
||||
CoronaProof []byte // Serialized Corona threshold signature
|
||||
SignerBitset []byte // Which validators signed BLS
|
||||
SignerBitset []byte // Which validators signed BLS
|
||||
CoronaSigners []ids.NodeID // Which validators participated in Corona
|
||||
TotalWeight uint64
|
||||
SignerWeight uint64
|
||||
BLSLatency time.Duration
|
||||
TotalWeight uint64
|
||||
SignerWeight uint64
|
||||
BLSLatency time.Duration
|
||||
CoronaLatency time.Duration
|
||||
Timestamp time.Time
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// Quasar binds P-Chain and Q-Chain consensus into hybrid quantum finality
|
||||
@@ -369,18 +369,18 @@ func (q *Quasar) processFinality(ctx context.Context, event FinalityEvent) error
|
||||
coronaProof = coronaSig.Bytes()
|
||||
}
|
||||
finality := &QuantumFinality{
|
||||
BlockID: event.BlockID,
|
||||
PChainHeight: event.Height,
|
||||
QChainHeight: q.qHeight,
|
||||
BLSProof: blsProof,
|
||||
BlockID: event.BlockID,
|
||||
PChainHeight: event.Height,
|
||||
QChainHeight: q.qHeight,
|
||||
BLSProof: blsProof,
|
||||
CoronaProof: coronaProof,
|
||||
SignerBitset: signerBitset,
|
||||
SignerBitset: signerBitset,
|
||||
CoronaSigners: coronaSigners,
|
||||
TotalWeight: totalWeight,
|
||||
SignerWeight: signerWeight,
|
||||
BLSLatency: blsLatency,
|
||||
TotalWeight: totalWeight,
|
||||
SignerWeight: signerWeight,
|
||||
BLSLatency: blsLatency,
|
||||
CoronaLatency: coronaLatency,
|
||||
Timestamp: time.Now(),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
q.finalized[event.BlockID] = finality
|
||||
@@ -590,13 +590,13 @@ func (q *Quasar) Stats() QuasarStats {
|
||||
}
|
||||
|
||||
return QuasarStats{
|
||||
PChainHeight: q.pHeight,
|
||||
QChainHeight: q.qHeight,
|
||||
FinalizedBlocks: len(q.finalized),
|
||||
Threshold: q.threshold,
|
||||
QuorumNum: q.quorumNum,
|
||||
QuorumDen: q.quorumDen,
|
||||
Running: q.running,
|
||||
PChainHeight: q.pHeight,
|
||||
QChainHeight: q.qHeight,
|
||||
FinalizedBlocks: len(q.finalized),
|
||||
Threshold: q.threshold,
|
||||
QuorumNum: q.quorumNum,
|
||||
QuorumDen: q.quorumDen,
|
||||
Running: q.running,
|
||||
CoronaParties: coronaStats.NumParties,
|
||||
CoronaThreshold: coronaStats.Threshold,
|
||||
CoronaReady: coronaStats.Initialized,
|
||||
@@ -605,13 +605,13 @@ func (q *Quasar) Stats() QuasarStats {
|
||||
|
||||
// QuasarStats contains quasar statistics
|
||||
type QuasarStats struct {
|
||||
PChainHeight uint64
|
||||
QChainHeight uint64
|
||||
FinalizedBlocks int
|
||||
Threshold int
|
||||
QuorumNum uint64
|
||||
QuorumDen uint64
|
||||
Running bool
|
||||
PChainHeight uint64
|
||||
QChainHeight uint64
|
||||
FinalizedBlocks int
|
||||
Threshold int
|
||||
QuorumNum uint64
|
||||
QuorumDen uint64
|
||||
Running bool
|
||||
CoronaParties int
|
||||
CoronaThreshold int
|
||||
CoronaReady bool
|
||||
|
||||
@@ -181,7 +181,7 @@ func (s *BLSSignature) Signers() []ids.NodeID { return s.signers }
|
||||
|
||||
// QuasarSignature combines BLS and Corona signatures for P/Q security
|
||||
type QuasarSignature struct {
|
||||
bls *BLSSignature
|
||||
bls *BLSSignature
|
||||
corona *CoronaSignature
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ func (s *QuasarSignature) Signers() []ids.NodeID {
|
||||
return s.bls.Signers()
|
||||
}
|
||||
|
||||
func (s *QuasarSignature) BLS() *BLSSignature { return s.bls }
|
||||
func (s *QuasarSignature) BLS() *BLSSignature { return s.bls }
|
||||
func (s *QuasarSignature) Corona() *CoronaSignature { return s.corona }
|
||||
|
||||
// QuasarSigner combines classical and post-quantum signers
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package rpcdb re-exports the proto/rpcdb package for backwards compatibility
|
||||
package rpcdb
|
||||
|
||||
import "github.com/luxfi/node/proto/rpcdb"
|
||||
|
||||
// Type aliases for backwards compatibility
|
||||
type (
|
||||
DatabaseClient = rpcdb.DatabaseClient
|
||||
DatabaseServer = rpcdb.DatabaseServer
|
||||
)
|
||||
|
||||
// Function aliases for backwards compatibility
|
||||
var (
|
||||
NewClient = rpcdb.NewClient
|
||||
NewServer = rpcdb.NewServer
|
||||
)
|
||||
@@ -0,0 +1,264 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
// Package rpcdb is luxd's canonical Layer C home for the rpcdb service:
|
||||
// one Service implementation backed by a database.Database, with a
|
||||
// transport adapter per supported wire protocol.
|
||||
//
|
||||
// Layered topology:
|
||||
//
|
||||
// Layer A — wire framing (github.com/luxfi/api/zap)
|
||||
// Layer B — service spec (data carriers) (github.com/luxfi/proto/rpcdb)
|
||||
// Layer C — service impl + transports (this package)
|
||||
//
|
||||
// One Service. Many transport adapters. Adding a new transport is a
|
||||
// new file that wraps `*Service`; the storage logic stays here and
|
||||
// stays orthogonal to framing concerns.
|
||||
//
|
||||
// Files:
|
||||
// - service.go (this) — transport-neutral Service struct + methods
|
||||
// - grpc_server.go (build tag `grpc`) — gRPC adapter
|
||||
// - zap_server.go (default) — ZAP adapter
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/database"
|
||||
rpcdb "github.com/luxfi/proto/rpcdb"
|
||||
)
|
||||
|
||||
// errUnknownIterator is the sentinel returned by IteratorNext / Error
|
||||
// / Release when the caller hands in an iterator id that was never
|
||||
// allocated (or has already been released). Transport adapters should
|
||||
// surface this however their wire spec demands.
|
||||
var errUnknownIterator = errors.New("rpcdb: unknown iterator")
|
||||
|
||||
// iterationBatchBytes is the upper bound on bytes batched into a
|
||||
// single IteratorNext response. Picked to match the cevm-side
|
||||
// expectation (RemoteZapDB::iterator_next reads up to ~128 KiB per
|
||||
// roundtrip).
|
||||
const iterationBatchBytes = 128 * 1024
|
||||
|
||||
// Service is the rpcdb service implementation. It holds one
|
||||
// `database.Database` and a server-managed iterator pool, and exposes
|
||||
// every rpcdb operation as a (request → response) method on Go data
|
||||
// carriers from the proto/rpcdb wire-types package.
|
||||
//
|
||||
// Service is transport-agnostic: it knows nothing about gRPC, ZAP, or
|
||||
// any other framing. Each transport adapter wraps `*Service` and
|
||||
// translates its own wire format into these method calls.
|
||||
type Service struct {
|
||||
db database.Database
|
||||
|
||||
// iteratorMu serializes mutations of nextIteratorID and iterators.
|
||||
// It does NOT serialize calls to a particular iterator — the
|
||||
// underlying database.Iterator is documented as not safe for
|
||||
// concurrent use, and the caller is expected to respect that.
|
||||
iteratorMu sync.RWMutex
|
||||
nextIteratorID uint64
|
||||
iterators map[uint64]database.Iterator
|
||||
}
|
||||
|
||||
// NewService wraps `db` so it can be served over any transport.
|
||||
func NewService(db database.Database) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
iterators: make(map[uint64]database.Iterator),
|
||||
}
|
||||
}
|
||||
|
||||
// DB returns the underlying database. Adapters that need to surface
|
||||
// transport-specific behavior (eg. health-check serialization) can
|
||||
// reach in for it; everyone else uses the methods.
|
||||
func (s *Service) DB() database.Database {
|
||||
return s.db
|
||||
}
|
||||
|
||||
// CloseIterators releases every server-side iterator. Adapters call
|
||||
// this on shutdown so iterators don't leak past the listener.
|
||||
func (s *Service) CloseIterators() {
|
||||
s.iteratorMu.Lock()
|
||||
defer s.iteratorMu.Unlock()
|
||||
for id, it := range s.iterators {
|
||||
it.Release()
|
||||
delete(s.iterators, id)
|
||||
}
|
||||
}
|
||||
|
||||
// errToCode maps a database error to the wire enum.
|
||||
func errToCode(err error) rpcdb.Error {
|
||||
switch {
|
||||
case err == nil:
|
||||
return rpcdb.Error_ERROR_UNSPECIFIED
|
||||
case errors.Is(err, database.ErrNotFound):
|
||||
return rpcdb.Error_ERROR_NOT_FOUND
|
||||
case errors.Is(err, database.ErrClosed):
|
||||
return rpcdb.Error_ERROR_CLOSED
|
||||
default:
|
||||
// Any other error is reported as CLOSED on the wire — the
|
||||
// proto enum doesn't have a richer error space. Adapters that
|
||||
// can carry a transport-level error message should also pass
|
||||
// the original err out-of-band.
|
||||
return rpcdb.Error_ERROR_CLOSED
|
||||
}
|
||||
}
|
||||
|
||||
// CodeToErr maps a wire-typed Error back to a database sentinel. Used
|
||||
// by remote clients on the receive side; lives here so server and
|
||||
// client agree on the inverse of errToCode.
|
||||
func CodeToErr(code rpcdb.Error) error {
|
||||
switch code {
|
||||
case rpcdb.Error_ERROR_UNSPECIFIED:
|
||||
return nil
|
||||
case rpcdb.Error_ERROR_NOT_FOUND:
|
||||
return database.ErrNotFound
|
||||
case rpcdb.Error_ERROR_CLOSED:
|
||||
return database.ErrClosed
|
||||
default:
|
||||
return database.ErrClosed
|
||||
}
|
||||
}
|
||||
|
||||
// Has services rpcdb.Has.
|
||||
func (s *Service) Has(_ context.Context, req *rpcdb.HasRequest) (*rpcdb.HasResponse, error) {
|
||||
has, err := s.db.Has(req.Key)
|
||||
if err != nil {
|
||||
return &rpcdb.HasResponse{Has: false, Err: errToCode(err)}, nil
|
||||
}
|
||||
return &rpcdb.HasResponse{Has: has, Err: rpcdb.Error_ERROR_UNSPECIFIED}, nil
|
||||
}
|
||||
|
||||
// Get services rpcdb.Get.
|
||||
func (s *Service) Get(_ context.Context, req *rpcdb.GetRequest) (*rpcdb.GetResponse, error) {
|
||||
value, err := s.db.Get(req.Key)
|
||||
if err != nil {
|
||||
return &rpcdb.GetResponse{Value: nil, Err: errToCode(err)}, nil
|
||||
}
|
||||
return &rpcdb.GetResponse{Value: value, Err: rpcdb.Error_ERROR_UNSPECIFIED}, nil
|
||||
}
|
||||
|
||||
// Put services rpcdb.Put.
|
||||
func (s *Service) Put(_ context.Context, req *rpcdb.PutRequest) (*rpcdb.PutResponse, error) {
|
||||
return &rpcdb.PutResponse{Err: errToCode(s.db.Put(req.Key, req.Value))}, nil
|
||||
}
|
||||
|
||||
// Delete services rpcdb.Delete.
|
||||
func (s *Service) Delete(_ context.Context, req *rpcdb.DeleteRequest) (*rpcdb.DeleteResponse, error) {
|
||||
return &rpcdb.DeleteResponse{Err: errToCode(s.db.Delete(req.Key))}, nil
|
||||
}
|
||||
|
||||
// WriteBatch services rpcdb.WriteBatch.
|
||||
func (s *Service) WriteBatch(_ context.Context, req *rpcdb.WriteBatchRequest) (*rpcdb.WriteBatchResponse, error) {
|
||||
batch := s.db.NewBatch()
|
||||
for _, p := range req.Puts {
|
||||
if err := batch.Put(p.Key, p.Value); err != nil {
|
||||
return &rpcdb.WriteBatchResponse{Err: errToCode(err)}, nil
|
||||
}
|
||||
}
|
||||
for _, d := range req.Deletes {
|
||||
if err := batch.Delete(d.Key); err != nil {
|
||||
return &rpcdb.WriteBatchResponse{Err: errToCode(err)}, nil
|
||||
}
|
||||
}
|
||||
return &rpcdb.WriteBatchResponse{Err: errToCode(batch.Write())}, nil
|
||||
}
|
||||
|
||||
// Compact services rpcdb.Compact.
|
||||
func (s *Service) Compact(_ context.Context, req *rpcdb.CompactRequest) (*rpcdb.CompactResponse, error) {
|
||||
return &rpcdb.CompactResponse{Err: errToCode(s.db.Compact(req.Start, req.Limit))}, nil
|
||||
}
|
||||
|
||||
// Close services rpcdb.Close.
|
||||
func (s *Service) Close(_ context.Context, _ *rpcdb.CloseRequest) (*rpcdb.CloseResponse, error) {
|
||||
return &rpcdb.CloseResponse{Err: errToCode(s.db.Close())}, nil
|
||||
}
|
||||
|
||||
// HealthCheck services rpcdb.HealthCheck.
|
||||
func (s *Service) HealthCheck(ctx context.Context) (*rpcdb.HealthCheckResponse, error) {
|
||||
health, err := s.db.HealthCheck(ctx)
|
||||
if err != nil {
|
||||
return &rpcdb.HealthCheckResponse{Details: []byte(err.Error())}, nil
|
||||
}
|
||||
details := []byte("healthy")
|
||||
if health != nil {
|
||||
if str, ok := health.(string); ok {
|
||||
details = []byte(str)
|
||||
}
|
||||
}
|
||||
return &rpcdb.HealthCheckResponse{Details: details}, nil
|
||||
}
|
||||
|
||||
// NewIteratorWithStartAndPrefix services rpcdb.NewIteratorWithStartAndPrefix.
|
||||
func (s *Service) NewIteratorWithStartAndPrefix(_ context.Context, req *rpcdb.NewIteratorWithStartAndPrefixRequest) (*rpcdb.NewIteratorWithStartAndPrefixResponse, error) {
|
||||
it := s.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
|
||||
s.iteratorMu.Lock()
|
||||
id := s.nextIteratorID
|
||||
s.nextIteratorID++
|
||||
s.iterators[id] = it
|
||||
s.iteratorMu.Unlock()
|
||||
return &rpcdb.NewIteratorWithStartAndPrefixResponse{Id: id}, nil
|
||||
}
|
||||
|
||||
// IteratorNext services rpcdb.IteratorNext, returning up to
|
||||
// iterationBatchBytes worth of (key, value) pairs per call. An empty
|
||||
// Data slice signals exhaustion.
|
||||
//
|
||||
// Returned bytes are deep-copied so the caller can hold them past the
|
||||
// next iterator advance; the underlying database.Iterator is allowed
|
||||
// to recycle its internal buffers.
|
||||
func (s *Service) IteratorNext(_ context.Context, req *rpcdb.IteratorNextRequest) (*rpcdb.IteratorNextResponse, error) {
|
||||
s.iteratorMu.RLock()
|
||||
it, ok := s.iterators[req.Id]
|
||||
s.iteratorMu.RUnlock()
|
||||
if !ok {
|
||||
return nil, errUnknownIterator
|
||||
}
|
||||
|
||||
var (
|
||||
size int
|
||||
data []*rpcdb.PutRequest
|
||||
)
|
||||
for size < iterationBatchBytes && it.Next() {
|
||||
k := it.Key()
|
||||
v := it.Value()
|
||||
size += len(k) + len(v)
|
||||
|
||||
kc := make([]byte, len(k))
|
||||
copy(kc, k)
|
||||
vc := make([]byte, len(v))
|
||||
copy(vc, v)
|
||||
data = append(data, &rpcdb.PutRequest{Key: kc, Value: vc})
|
||||
}
|
||||
return &rpcdb.IteratorNextResponse{Data: data}, nil
|
||||
}
|
||||
|
||||
// IteratorError services rpcdb.IteratorError.
|
||||
func (s *Service) IteratorError(_ context.Context, req *rpcdb.IteratorErrorRequest) (*rpcdb.IteratorErrorResponse, error) {
|
||||
s.iteratorMu.RLock()
|
||||
it, ok := s.iterators[req.Id]
|
||||
s.iteratorMu.RUnlock()
|
||||
if !ok {
|
||||
return &rpcdb.IteratorErrorResponse{Err: rpcdb.Error_ERROR_NOT_FOUND}, nil
|
||||
}
|
||||
return &rpcdb.IteratorErrorResponse{Err: errToCode(it.Error())}, nil
|
||||
}
|
||||
|
||||
// IteratorRelease services rpcdb.IteratorRelease. Idempotent: calling
|
||||
// it twice for the same id returns UNSPECIFIED on the second call.
|
||||
func (s *Service) IteratorRelease(_ context.Context, req *rpcdb.IteratorReleaseRequest) (*rpcdb.IteratorReleaseResponse, error) {
|
||||
s.iteratorMu.Lock()
|
||||
it, ok := s.iterators[req.Id]
|
||||
if !ok {
|
||||
s.iteratorMu.Unlock()
|
||||
return &rpcdb.IteratorReleaseResponse{Err: rpcdb.Error_ERROR_UNSPECIFIED}, nil
|
||||
}
|
||||
delete(s.iterators, req.Id)
|
||||
s.iteratorMu.Unlock()
|
||||
|
||||
dbErr := it.Error()
|
||||
it.Release()
|
||||
return &rpcdb.IteratorReleaseResponse{Err: errToCode(dbErr)}, nil
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
zapwire "github.com/luxfi/api/zap"
|
||||
"github.com/luxfi/database"
|
||||
rpcdb "github.com/luxfi/proto/rpcdb"
|
||||
)
|
||||
|
||||
// ZAP db channel MsgType IDs. These are the wire-level Layer-A
|
||||
// dispatch tags for the rpcdb service over ZAP. They live in their
|
||||
// own listener (one per VM plugin); they do NOT collide with the VM
|
||||
// lifecycle MsgTypes (1..31) or sender (40..49) or warp (50..59)
|
||||
// because each listener has its own dispatch table.
|
||||
//
|
||||
// Order is the canonical Lane A assignment — the cevm side hard-codes
|
||||
// these numbers in `RemoteZapDB::call`. Do not reorder.
|
||||
const (
|
||||
MsgDBHas zapwire.MessageType = 1
|
||||
MsgDBGet zapwire.MessageType = 2
|
||||
MsgDBPut zapwire.MessageType = 3
|
||||
MsgDBDelete zapwire.MessageType = 4
|
||||
MsgDBWriteBatch zapwire.MessageType = 5
|
||||
MsgDBCompact zapwire.MessageType = 6
|
||||
MsgDBClose zapwire.MessageType = 7
|
||||
MsgDBHealthCheck zapwire.MessageType = 8
|
||||
MsgDBIteratorNew zapwire.MessageType = 9
|
||||
MsgDBIteratorNext zapwire.MessageType = 10
|
||||
MsgDBIteratorError zapwire.MessageType = 11
|
||||
MsgDBIteratorRelease zapwire.MessageType = 12
|
||||
)
|
||||
|
||||
// Wire-byte values for the Error enum. These match
|
||||
// rpcdb.Error_ERROR_* but are encoded as a single byte on the ZAP
|
||||
// wire so the cevm side can decode them without pulling in protobuf.
|
||||
const (
|
||||
dbErrUnspecified uint8 = 0
|
||||
dbErrClosed uint8 = 1
|
||||
dbErrNotFound uint8 = 2
|
||||
)
|
||||
|
||||
func errCodeToByte(code rpcdb.Error) uint8 {
|
||||
switch code {
|
||||
case rpcdb.Error_ERROR_CLOSED:
|
||||
return dbErrClosed
|
||||
case rpcdb.Error_ERROR_NOT_FOUND:
|
||||
return dbErrNotFound
|
||||
default:
|
||||
return dbErrUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
// ZAPServer is the ZAP transport adapter for the rpcdb Service. It
|
||||
// owns the listener and the dispatch loop; the actual storage logic
|
||||
// lives in *Service. To swap the wire format (eg. to add framing
|
||||
// over a different reliable byte stream) write a new file with a new
|
||||
// adapter — never edit Service.
|
||||
type ZAPServer struct {
|
||||
svc *Service
|
||||
|
||||
listener *zapwire.Listener
|
||||
server *zapwire.Server
|
||||
|
||||
// closeOnce guards Close so callers can call it from both the
|
||||
// client lifecycle and a deferred test cleanup without panicking
|
||||
// on a double-close of the underlying listener.
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
// NewZAPServer wraps a database.Database for serving over ZAP.
|
||||
//
|
||||
// Equivalent to NewZAPServerFromService(NewService(db)) — kept as a
|
||||
// one-liner because cevm-side test fixtures and the production
|
||||
// rpcchainvm/zap path both build it this way.
|
||||
func NewZAPServer(db database.Database) *ZAPServer {
|
||||
return NewZAPServerFromService(NewService(db))
|
||||
}
|
||||
|
||||
// NewZAPServerFromService wraps an existing Service for serving over
|
||||
// ZAP. Useful when a single Service needs multiple transport adapters
|
||||
// at once (eg. tests that want both ZAP and direct in-process calls).
|
||||
func NewZAPServerFromService(svc *Service) *ZAPServer {
|
||||
return &ZAPServer{svc: svc}
|
||||
}
|
||||
|
||||
// Listen binds the ZAP listener to addr.
|
||||
func (s *ZAPServer) Listen(addr string) error {
|
||||
listener, err := zapwire.Listen(addr, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rpcdb zap: listen %s: %w", addr, err)
|
||||
}
|
||||
s.listener = listener
|
||||
s.server = zapwire.NewServer(listener, zapwire.HandlerFunc(s.handle))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListenOn wraps an existing net.Listener (for ephemeral ports etc.).
|
||||
func (s *ZAPServer) ListenOn(raw net.Listener) {
|
||||
s.listener = zapwire.NewListener(raw, nil)
|
||||
s.server = zapwire.NewServer(s.listener, zapwire.HandlerFunc(s.handle))
|
||||
}
|
||||
|
||||
// Addr returns the bound address (or nil if not yet listening).
|
||||
func (s *ZAPServer) Addr() net.Addr {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
return s.listener.Addr()
|
||||
}
|
||||
|
||||
// Serve blocks until ctx is cancelled or Close is called.
|
||||
func (s *ZAPServer) Serve(ctx context.Context) error {
|
||||
if s.server == nil {
|
||||
return errors.New("rpcdb zap: not initialized — call Listen first")
|
||||
}
|
||||
return s.server.Serve(ctx)
|
||||
}
|
||||
|
||||
// Close releases all iterators and closes the listener. Caller is
|
||||
// expected to also cancel the context passed to Serve so the accept
|
||||
// loop exits cleanly. Safe to call multiple times.
|
||||
//
|
||||
// We deliberately do NOT call s.server.Close() because the upstream
|
||||
// zapwire.Server.Close races with in-flight accept (it nils its conns
|
||||
// map mid-Serve, causing "assignment to entry in nil map"). Closing
|
||||
// the listener is enough to make Accept return; ctx cancellation does
|
||||
// the rest.
|
||||
func (s *ZAPServer) Close() error {
|
||||
var err error
|
||||
s.closeOnce.Do(func() {
|
||||
s.svc.CloseIterators()
|
||||
if s.listener != nil {
|
||||
err = s.listener.Close()
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handle(ctx context.Context, msgType zapwire.MessageType, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
switch msgType {
|
||||
case MsgDBHas:
|
||||
return s.handleHas(ctx, payload)
|
||||
case MsgDBGet:
|
||||
return s.handleGet(ctx, payload)
|
||||
case MsgDBPut:
|
||||
return s.handlePut(ctx, payload)
|
||||
case MsgDBDelete:
|
||||
return s.handleDelete(ctx, payload)
|
||||
case MsgDBWriteBatch:
|
||||
return s.handleWriteBatch(ctx, payload)
|
||||
case MsgDBCompact:
|
||||
return s.handleCompact(ctx, payload)
|
||||
case MsgDBClose:
|
||||
return s.handleClose(ctx, payload)
|
||||
case MsgDBHealthCheck:
|
||||
return s.handleHealthCheck(ctx)
|
||||
case MsgDBIteratorNew:
|
||||
return s.handleIteratorNew(ctx, payload)
|
||||
case MsgDBIteratorNext:
|
||||
return s.handleIteratorNext(ctx, payload)
|
||||
case MsgDBIteratorError:
|
||||
return s.handleIteratorError(ctx, payload)
|
||||
case MsgDBIteratorRelease:
|
||||
return s.handleIteratorRelease(ctx, payload)
|
||||
default:
|
||||
return 0, nil, fmt.Errorf("rpcdb zap: unknown msg type %d", msgType)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleHas(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
key, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb Has decode: %w", err)
|
||||
}
|
||||
resp, err := s.svc.Has(ctx, &rpcdb.HasRequest{Key: key})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
if resp.Has {
|
||||
buf.WriteUint8(1)
|
||||
} else {
|
||||
buf.WriteUint8(0)
|
||||
}
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBHas, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleGet(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
key, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb Get decode: %w", err)
|
||||
}
|
||||
resp, err := s.svc.Get(ctx, &rpcdb.GetRequest{Key: key})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteBytes(resp.Value)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBGet, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handlePut(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
key, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb Put decode key: %w", err)
|
||||
}
|
||||
value, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb Put decode value: %w", err)
|
||||
}
|
||||
resp, err := s.svc.Put(ctx, &rpcdb.PutRequest{Key: key, Value: value})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBPut, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleDelete(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
key, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb Delete decode: %w", err)
|
||||
}
|
||||
resp, err := s.svc.Delete(ctx, &rpcdb.DeleteRequest{Key: key})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBDelete, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleWriteBatch(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
|
||||
nputs, err := r.ReadUint32()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb WriteBatch decode nputs: %w", err)
|
||||
}
|
||||
puts := make([]*rpcdb.PutRequest, 0, nputs)
|
||||
for i := uint32(0); i < nputs; i++ {
|
||||
key, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb WriteBatch put[%d] key: %w", i, err)
|
||||
}
|
||||
value, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb WriteBatch put[%d] value: %w", i, err)
|
||||
}
|
||||
puts = append(puts, &rpcdb.PutRequest{Key: key, Value: value})
|
||||
}
|
||||
|
||||
ndels, err := r.ReadUint32()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb WriteBatch decode ndels: %w", err)
|
||||
}
|
||||
dels := make([]*rpcdb.DeleteRequest, 0, ndels)
|
||||
for i := uint32(0); i < ndels; i++ {
|
||||
key, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb WriteBatch del[%d]: %w", i, err)
|
||||
}
|
||||
dels = append(dels, &rpcdb.DeleteRequest{Key: key})
|
||||
}
|
||||
|
||||
resp, err := s.svc.WriteBatch(ctx, &rpcdb.WriteBatchRequest{Puts: puts, Deletes: dels})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBWriteBatch, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleCompact(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
start, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb Compact decode start: %w", err)
|
||||
}
|
||||
limit, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb Compact decode limit: %w", err)
|
||||
}
|
||||
resp, err := s.svc.Compact(ctx, &rpcdb.CompactRequest{Start: start, Limit: limit})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBCompact, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleClose(ctx context.Context, _ []byte) (zapwire.MessageType, []byte, error) {
|
||||
resp, err := s.svc.Close(ctx, &rpcdb.CloseRequest{})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBClose, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleHealthCheck(ctx context.Context) (zapwire.MessageType, []byte, error) {
|
||||
resp, err := s.svc.HealthCheck(ctx)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteBytes(resp.Details)
|
||||
return MsgDBHealthCheck, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleIteratorNew(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
start, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb IteratorNew decode start: %w", err)
|
||||
}
|
||||
prefix, err := r.ReadBytes()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb IteratorNew decode prefix: %w", err)
|
||||
}
|
||||
resp, err := s.svc.NewIteratorWithStartAndPrefix(ctx, &rpcdb.NewIteratorWithStartAndPrefixRequest{Start: start, Prefix: prefix})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint64(resp.Id)
|
||||
return MsgDBIteratorNew, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleIteratorNext(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
id, err := r.ReadUint64()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb IteratorNext decode id: %w", err)
|
||||
}
|
||||
resp, err := s.svc.IteratorNext(ctx, &rpcdb.IteratorNextRequest{Id: id})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint32(uint32(len(resp.Data)))
|
||||
for _, e := range resp.Data {
|
||||
buf.WriteBytes(e.Key)
|
||||
buf.WriteBytes(e.Value)
|
||||
}
|
||||
return MsgDBIteratorNext, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleIteratorError(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
id, err := r.ReadUint64()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb IteratorError decode id: %w", err)
|
||||
}
|
||||
resp, err := s.svc.IteratorError(ctx, &rpcdb.IteratorErrorRequest{Id: id})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBIteratorError, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
|
||||
func (s *ZAPServer) handleIteratorRelease(ctx context.Context, payload []byte) (zapwire.MessageType, []byte, error) {
|
||||
r := zapwire.NewReader(payload)
|
||||
id, err := r.ReadUint64()
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("rpcdb IteratorRelease decode id: %w", err)
|
||||
}
|
||||
resp, err := s.svc.IteratorRelease(ctx, &rpcdb.IteratorReleaseRequest{Id: id})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
buf := zapwire.GetBuffer()
|
||||
defer zapwire.PutBuffer(buf)
|
||||
buf.WriteUint8(errCodeToByte(resp.Err))
|
||||
return MsgDBIteratorRelease, append([]byte(nil), buf.Bytes()...), nil
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
zapwire "github.com/luxfi/api/zap"
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/database/memdb"
|
||||
)
|
||||
|
||||
// startZAPServer spawns a ZAPServer over an in-memory listener bound to
|
||||
// 127.0.0.1:0, returns the addr + the server (for shutdown).
|
||||
func startZAPServer(t *testing.T, db database.Database) (string, *ZAPServer, context.CancelFunc) {
|
||||
t.Helper()
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
s := NewZAPServer(db)
|
||||
s.ListenOn(listener)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
_ = s.Serve(ctx)
|
||||
}()
|
||||
|
||||
// Wait for Serve to fully install accept loop.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return listener.Addr().String(), s, cancel
|
||||
}
|
||||
|
||||
// TestZAPServer_HasGetPutDelete exercises the four primitive ops.
|
||||
func TestZAPServer_HasGetPutDelete(t *testing.T) {
|
||||
require := require.New(t)
|
||||
mem := memdb.New()
|
||||
addr, srv, cancel := startZAPServer(t, mem)
|
||||
defer cancel()
|
||||
defer srv.Close()
|
||||
|
||||
conn, err := zapwire.Dial(context.Background(), addr, nil)
|
||||
require.NoError(err)
|
||||
defer conn.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Has (key absent) → has=false, err=NotFound
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes([]byte("foo"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBHas, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
hasB, _ := r.ReadUint8()
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal(uint8(0), hasB)
|
||||
require.Equal(dbErrUnspecified, errB) // memdb returns no err on Has-miss
|
||||
}
|
||||
|
||||
// Put
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes([]byte("foo"))
|
||||
buf.WriteBytes([]byte("bar"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBPut, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal(dbErrUnspecified, errB)
|
||||
}
|
||||
|
||||
// Has (key present) → has=true
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes([]byte("foo"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBHas, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
hasB, _ := r.ReadUint8()
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal(uint8(1), hasB)
|
||||
require.Equal(dbErrUnspecified, errB)
|
||||
}
|
||||
|
||||
// Get
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes([]byte("foo"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBGet, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
val, _ := r.ReadBytes()
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal([]byte("bar"), val)
|
||||
require.Equal(dbErrUnspecified, errB)
|
||||
}
|
||||
|
||||
// Get (missing) → empty value, err=NotFound
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes([]byte("absent"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBGet, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
val, _ := r.ReadBytes()
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Empty(val)
|
||||
require.Equal(dbErrNotFound, errB)
|
||||
}
|
||||
|
||||
// Delete
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes([]byte("foo"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBDelete, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal(dbErrUnspecified, errB)
|
||||
}
|
||||
|
||||
// Get post-delete → NotFound
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes([]byte("foo"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBGet, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
_, _ = r.ReadBytes()
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal(dbErrNotFound, errB)
|
||||
}
|
||||
}
|
||||
|
||||
// TestZAPServer_WriteBatch_AndIterate covers batched writes plus the
|
||||
// iterator path used by cevm's load_persisted snapshot_with_prefix.
|
||||
func TestZAPServer_WriteBatch_AndIterate(t *testing.T) {
|
||||
require := require.New(t)
|
||||
mem := memdb.New()
|
||||
addr, srv, cancel := startZAPServer(t, mem)
|
||||
defer cancel()
|
||||
defer srv.Close()
|
||||
|
||||
conn, err := zapwire.Dial(context.Background(), addr, nil)
|
||||
require.NoError(err)
|
||||
defer conn.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// WriteBatch: 3 puts (prefix p:), 1 delete (no-op since absent).
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteUint32(3)
|
||||
buf.WriteBytes([]byte("p:1"))
|
||||
buf.WriteBytes([]byte("v1"))
|
||||
buf.WriteBytes([]byte("p:2"))
|
||||
buf.WriteBytes([]byte("v2"))
|
||||
buf.WriteBytes([]byte("q:3"))
|
||||
buf.WriteBytes([]byte("v3"))
|
||||
buf.WriteUint32(1)
|
||||
buf.WriteBytes([]byte("absent"))
|
||||
_, resp, err := conn.Call(ctx, MsgDBWriteBatch, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal(dbErrUnspecified, errB)
|
||||
}
|
||||
|
||||
// IteratorNew with prefix "p:"
|
||||
var iterID uint64
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes(nil) // start
|
||||
buf.WriteBytes([]byte("p:")) // prefix
|
||||
_, resp, err := conn.Call(ctx, MsgDBIteratorNew, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
iterID, _ = r.ReadUint64()
|
||||
}
|
||||
|
||||
// IteratorNext — collect.
|
||||
collected := map[string]string{}
|
||||
for {
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteUint64(iterID)
|
||||
_, resp, err := conn.Call(ctx, MsgDBIteratorNext, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
n, _ := r.ReadUint32()
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
for i := uint32(0); i < n; i++ {
|
||||
k, _ := r.ReadBytes()
|
||||
v, _ := r.ReadBytes()
|
||||
collected[string(k)] = string(v)
|
||||
}
|
||||
}
|
||||
|
||||
// IteratorRelease
|
||||
{
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteUint64(iterID)
|
||||
_, resp, err := conn.Call(ctx, MsgDBIteratorRelease, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
errB, _ := r.ReadUint8()
|
||||
require.Equal(dbErrUnspecified, errB)
|
||||
}
|
||||
|
||||
require.Equal(map[string]string{"p:1": "v1", "p:2": "v2"}, collected)
|
||||
}
|
||||
|
||||
// TestZAPServer_ParityWithMemDB writes/reads via wire and via direct
|
||||
// memdb Get to confirm both paths see the same state — proves the ZAP
|
||||
// round-trip preserves bytes exactly.
|
||||
func TestZAPServer_ParityWithMemDB(t *testing.T) {
|
||||
require := require.New(t)
|
||||
mem := memdb.New()
|
||||
addr, srv, cancel := startZAPServer(t, mem)
|
||||
defer cancel()
|
||||
defer srv.Close()
|
||||
|
||||
conn, err := zapwire.Dial(context.Background(), addr, nil)
|
||||
require.NoError(err)
|
||||
defer conn.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
key := []byte{0x42, 0x00, 0x01, 0xff}
|
||||
value := make([]byte, 256)
|
||||
for i := range value {
|
||||
value[i] = byte(i)
|
||||
}
|
||||
|
||||
// Put via wire.
|
||||
buf := zapwire.GetBuffer()
|
||||
buf.WriteBytes(key)
|
||||
buf.WriteBytes(value)
|
||||
_, _, err = conn.Call(ctx, MsgDBPut, buf.Bytes())
|
||||
zapwire.PutBuffer(buf)
|
||||
require.NoError(err)
|
||||
|
||||
// Read directly.
|
||||
got, dErr := mem.Get(key)
|
||||
require.NoError(dErr)
|
||||
require.Equal(value, got)
|
||||
|
||||
// Read via wire.
|
||||
buf2 := zapwire.GetBuffer()
|
||||
buf2.WriteBytes(key)
|
||||
_, resp, err := conn.Call(ctx, MsgDBGet, buf2.Bytes())
|
||||
zapwire.PutBuffer(buf2)
|
||||
require.NoError(err)
|
||||
r := zapwire.NewReader(resp)
|
||||
wireVal, _ := r.ReadBytes()
|
||||
require.Equal(value, wireVal)
|
||||
}
|
||||
+7
-7
@@ -7,9 +7,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/consensus/utils/set"
|
||||
validators "github.com/luxfi/validators"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
validators "github.com/luxfi/validators"
|
||||
)
|
||||
|
||||
func TestFullValidatorFunctionality(t *testing.T) {
|
||||
@@ -152,14 +152,14 @@ func TestNetworkConfiguration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestXAssetID(t *testing.T) {
|
||||
// Verify XAssetID is used for native asset
|
||||
xAssetID := ids.Empty // Our implementation uses Empty ID for native asset
|
||||
// Verify UTXOAssetID is used for native asset
|
||||
utxoAssetID := ids.Empty // Our implementation uses Empty ID for native asset
|
||||
|
||||
if xAssetID != ids.Empty {
|
||||
t.Fatal("XAssetID should be Empty for native asset")
|
||||
if utxoAssetID != ids.Empty {
|
||||
t.Fatal("UTXOAssetID should be Empty for native asset")
|
||||
}
|
||||
|
||||
t.Log("✓ XAssetID verified for native LUX asset")
|
||||
t.Log("✓ UTXOAssetID verified for native LUX asset")
|
||||
}
|
||||
|
||||
func TestConsensusConfig(t *testing.T) {
|
||||
@@ -319,7 +319,7 @@ func TestSummary(t *testing.T) {
|
||||
t.Log("✓ Validator Manager: PASS")
|
||||
t.Log("✓ Network Configuration: PASS")
|
||||
t.Log("✓ Consensus Parameters: PASS")
|
||||
t.Log("✓ XAssetID Configuration: PASS")
|
||||
t.Log("✓ UTXOAssetID Configuration: PASS")
|
||||
t.Log("✓ Validator Lifecycle: PASS")
|
||||
t.Log("✓ Validator Set Interface: PASS")
|
||||
t.Log("----------------------------------------")
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
validators "github.com/luxfi/validators"
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
validators "github.com/luxfi/validators"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# Deploy all 4 subnet chains (Zoo, Hanzo, SPC, Pars) to mainnet + testnet
|
||||
# Usage: ./deploy-subnets.sh [mainnet|testnet|devnet|both]
|
||||
# Deploy all 4 app chains (Zoo, Hanzo, SPC, Pars) to mainnet + testnet
|
||||
# Usage: ./deploy-chains.sh [mainnet|testnet|devnet|both]
|
||||
#
|
||||
# Prerequisites:
|
||||
# - macOS keychain must have mainnet-key-02 key (will prompt for approval)
|
||||
@@ -21,7 +21,7 @@ if [ ! -f "$DEPLOY_BIN" ]; then
|
||||
fi
|
||||
|
||||
# Use mainnet-key-02 (NOT 01!) because 01's funds are all staked by initialStakers
|
||||
# mainnet-key-02 has 70M LUX unlocked on P-chain, sufficient for subnet creation
|
||||
# mainnet-key-02 has 70M LUX unlocked on P-chain, sufficient for chain creation
|
||||
KEY_NAME="mainnet-key-02"
|
||||
echo "Extracting $KEY_NAME from keychain..."
|
||||
echo "(Approve the macOS keychain dialog that appears)"
|
||||
@@ -39,10 +39,10 @@ deploy_network() {
|
||||
local network=$1
|
||||
echo ""
|
||||
echo "=============================="
|
||||
echo "Deploying subnets to $network"
|
||||
echo "Deploying chains to $network"
|
||||
echo "=============================="
|
||||
|
||||
LUX_PRIVATE_KEY="$KEY" "$DEPLOY_BIN" \
|
||||
PRIVATE_KEY="$KEY" "$DEPLOY_BIN" \
|
||||
--network="$network" \
|
||||
--state-dir="$STATE_DIR" 2>&1
|
||||
|
||||
@@ -77,7 +77,7 @@ echo ""
|
||||
echo "All deployments complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Copy the Subnet IDs and Blockchain IDs from output above"
|
||||
echo " 1. Copy the Chain IDs and Blockchain IDs from output above"
|
||||
echo " 2. Update Helm values files:"
|
||||
echo " ~/work/lux/devops/charts/lux/values-mainnet.yaml"
|
||||
echo " ~/work/lux/devops/charts/lux/values-testnet.yaml"
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build stage
|
||||
FROM golang:1.26-alpine AS builder
|
||||
FROM golang:1.26.3-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git make gcc musl-dev linux-headers bash
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Multi-chain node builder
|
||||
FROM golang:1.26-alpine AS builder
|
||||
FROM golang:1.26.3-alpine AS builder
|
||||
|
||||
ARG CHAIN_TYPE=platform
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26-alpine AS builder
|
||||
FROM golang:1.26.3-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git make gcc musl-dev linux-headers bash
|
||||
|
||||
|
||||
@@ -140,8 +140,8 @@ else
|
||||
{
|
||||
"allocations": [
|
||||
{
|
||||
"luxAddr": "P-local1jfdgmqduuyuxjp9sq7szp08xpynav88sd7qxvv",
|
||||
"ethAddr": "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714",
|
||||
"utxoAddr": "P-local1jfdgmqduuyuxjp9sq7szp08xpynav88sd7qxvv",
|
||||
"evmAddr": "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714",
|
||||
"initialAmount": 1000000000000000000,
|
||||
"unlockSchedule": []
|
||||
}
|
||||
@@ -167,8 +167,8 @@ EOF
|
||||
{
|
||||
"allocations": [
|
||||
{
|
||||
"luxAddr": "X-local1jfdgmqduuyuxjp9sq7szp08xpynav88sd7qxvv",
|
||||
"ethAddr": "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714",
|
||||
"utxoAddr": "X-local1jfdgmqduuyuxjp9sq7szp08xpynav88sd7qxvv",
|
||||
"evmAddr": "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714",
|
||||
"initialAmount": 1000000000000000000,
|
||||
"unlockSchedule": []
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ func NewMultiNetworkNode() *MultiNetworkNode {
|
||||
ChainID: "2JVSBoinj9C2J33VntvzYtVJNZdN2NKiwwKjcumHUWEb5DbBrm",
|
||||
Active: true,
|
||||
},
|
||||
200200: { // Zoo L2 Chain ID
|
||||
200200: { // Zoo chain Chain ID
|
||||
NetworkID: 200200,
|
||||
NetworkName: "Zoo Network (L2)",
|
||||
NetworkName: "Zoo Network",
|
||||
RPCPort: 2000,
|
||||
Validators: 5,
|
||||
ChainID: "2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt",
|
||||
|
||||
Executable
BIN
Binary file not shown.
+225
-261
@@ -22,14 +22,12 @@ import (
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/net/endpoints"
|
||||
"github.com/luxfi/node/vms/components/gas"
|
||||
exchangevm "github.com/luxfi/node/vms/xvm"
|
||||
"github.com/luxfi/node/vms/xvm/fxs"
|
||||
xchaintxs "github.com/luxfi/node/vms/xvm/txs"
|
||||
"github.com/luxfi/node/vms/platformvm/genesis"
|
||||
"github.com/luxfi/node/vms/platformvm/reward"
|
||||
"github.com/luxfi/node/vms/platformvm/signer"
|
||||
pchaintxs "github.com/luxfi/node/vms/platformvm/txs"
|
||||
"github.com/luxfi/node/vms/platformvm/validators/fee"
|
||||
xvmgenesis "github.com/luxfi/node/vms/xvm/genesis"
|
||||
"github.com/luxfi/utxo/bls12381fx"
|
||||
"github.com/luxfi/utxo/ed25519fx"
|
||||
"github.com/luxfi/utxo/mldsafx"
|
||||
@@ -40,33 +38,26 @@ import (
|
||||
"github.com/luxfi/utxo/secp256r1fx"
|
||||
"github.com/luxfi/utxo/slhdsafx"
|
||||
|
||||
genesiscfg "github.com/luxfi/genesis/pkg/genesis"
|
||||
genesisconfigs "github.com/luxfi/genesis/configs"
|
||||
genesiscfg "github.com/luxfi/genesis/pkg/genesis"
|
||||
)
|
||||
|
||||
// Chain alias vars are derived from the single source of truth (Registry
|
||||
// in registry.go). Rebranding a chain — adding/removing aliases or
|
||||
// renaming the letter — edits one row in Registry; these vars and the
|
||||
// VMAliases map re-derive at package init.
|
||||
var (
|
||||
// PChainAliases are the default aliases for the P-Chain
|
||||
PChainAliases = []string{"P", "platform"}
|
||||
// XChainAliases are the default aliases for the X-Chain
|
||||
XChainAliases = []string{"X", "xvm"}
|
||||
// CChainAliases are the default aliases for the C-Chain
|
||||
CChainAliases = []string{"C", "evm"}
|
||||
// DChainAliases are the default aliases for the D-Chain (DEX)
|
||||
DChainAliases = []string{"D", "dex", "dexvm"}
|
||||
// QChainAliases are the default aliases for the Q-Chain (Quantum)
|
||||
QChainAliases = []string{"Q", "quantum", "quantumvm", "pq"}
|
||||
// AChainAliases are the default aliases for the A-Chain (Attestation/AI)
|
||||
AChainAliases = []string{"A", "attest", "ai", "aivm"}
|
||||
// BChainAliases are the default aliases for the B-Chain (Bridge)
|
||||
BChainAliases = []string{"B", "bridge", "bridgevm"}
|
||||
// TChainAliases are the default aliases for the T-Chain (Threshold)
|
||||
TChainAliases = []string{"T", "threshold", "thresholdvm", "mpc"}
|
||||
// ZChainAliases are the default aliases for the Z-Chain (ZK)
|
||||
ZChainAliases = []string{"Z", "zk", "zkvm"}
|
||||
// GChainAliases are the default aliases for the G-Chain (Graph)
|
||||
GChainAliases = []string{"G", "graph", "graphvm", "dgraph"}
|
||||
// KChainAliases are the default aliases for the K-Chain (KMS)
|
||||
KChainAliases = []string{"K", "key", "keyvm"}
|
||||
PChainAliases = AliasesFor("P")
|
||||
XChainAliases = AliasesFor("X")
|
||||
CChainAliases = AliasesFor("C")
|
||||
DChainAliases = AliasesFor("D")
|
||||
QChainAliases = AliasesFor("Q")
|
||||
AChainAliases = AliasesFor("A")
|
||||
BChainAliases = AliasesFor("B")
|
||||
TChainAliases = AliasesFor("T")
|
||||
ZChainAliases = AliasesFor("Z")
|
||||
GChainAliases = AliasesFor("G")
|
||||
KChainAliases = AliasesFor("K")
|
||||
|
||||
// Network-specific genesis messages (Latin for mainnet, descriptive for others)
|
||||
// Mainnet: "Lux et Libertas" - Light and Liberty
|
||||
@@ -78,31 +69,11 @@ var (
|
||||
// Local/Custom: "Carpe Diem" - Seize the day
|
||||
LocalChainGenesis = `{"version":1,"message":"Carpe Diem"}`
|
||||
|
||||
// VMAliases are the default aliases for VMs
|
||||
VMAliases = map[ids.ID][]string{
|
||||
constants.PlatformVMID: {"platform"},
|
||||
constants.XVMID: {"xvm"},
|
||||
constants.EVMID: {"evm"},
|
||||
constants.DexVMID: {"dexvm", "dex"},
|
||||
constants.QuantumVMID: {"quantumvm", "quantum", "pq"},
|
||||
constants.AIVMID: {"aivm", "attest", "ai"},
|
||||
constants.BridgeVMID: {"bridgevm", "bridge"},
|
||||
constants.ThresholdVMID: {"thresholdvm", "threshold", "mpc"},
|
||||
constants.ZKVMID: {"zkvm", "zk"},
|
||||
constants.GraphVMID: {"graphvm", "graph", "dgraph"},
|
||||
constants.KeyVMID: {"keyvm", "key"},
|
||||
secp256k1fx.ID: {"secp256k1fx"},
|
||||
nftfx.ID: {"nftfx"},
|
||||
propertyfx.ID: {"propertyfx"},
|
||||
mldsafx.ID: {"mldsafx"},
|
||||
slhdsafx.ID: {"slhdsafx"},
|
||||
ed25519fx.ID: {"ed25519fx"},
|
||||
secp256r1fx.ID: {"secp256r1fx"},
|
||||
schnorrfx.ID: {"schnorrfx"},
|
||||
bls12381fx.ID: {"bls12381fx"},
|
||||
}
|
||||
// VMAliases is the default (VMID -> aliases) map for the node's VM
|
||||
// manager. Chain entries are derived from Registry; fx entries are
|
||||
// the static feature-extension aliases (secp256k1fx, nftfx, ...).
|
||||
VMAliases = VMAliasesMap()
|
||||
|
||||
errNoTxs = errors.New("genesis creates no transactions")
|
||||
errOverridesStandardNetworkConfig = errors.New("overrides standard network genesis config")
|
||||
)
|
||||
|
||||
@@ -353,82 +324,110 @@ func GetConfig(networkID uint32) *genesiscfg.Config {
|
||||
return genesiscfg.GetConfig(networkID)
|
||||
}
|
||||
|
||||
// FromConfig builds genesis bytes from a config
|
||||
// FromConfig builds genesis bytes from a config.
|
||||
//
|
||||
// X-Chain is opt-in via config.XChainGenesis (a small JSON descriptor like
|
||||
//
|
||||
// {"symbol":"LUX","name":"Lux","denomination":9}
|
||||
//
|
||||
// ). When set, the builder constructs an XVM genesis whose primary asset
|
||||
// is that descriptor (initial holders sourced from config.Allocations) and
|
||||
// emits an X-Chain entry in the primary-network CreateChainTx set. When
|
||||
// empty, no XVM genesis is built and X-Chain is omitted from the chain
|
||||
// set — the path used by P-only L2s whose value capture lives on a
|
||||
// downstream EVM (downstream EVM etc.).
|
||||
//
|
||||
// The LUX asset ID returned is always the network-wide constant
|
||||
// (constants.UTXO_ASSET_ID), independent of whether X-Chain is baked.
|
||||
// This is what decouples the asset from any specific chain's genesis
|
||||
// bytes — P-Chain stake/UTXO references stay byte-stable across both
|
||||
// shapes of primary network.
|
||||
func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
// Build XVM (X-Chain) genesis
|
||||
lux := exchangevm.GenesisAssetDefinition{
|
||||
Name: "Lux",
|
||||
Symbol: "LUX",
|
||||
Denomination: 9,
|
||||
InitialState: exchangevm.AssetInitialState{},
|
||||
}
|
||||
memoBytes := []byte{}
|
||||
|
||||
// Sort allocations for deterministic output
|
||||
type allocation struct {
|
||||
ETHAddr ids.ShortID
|
||||
LUXAddr ids.ShortID
|
||||
InitialAmount uint64
|
||||
}
|
||||
xAllocations := []allocation{}
|
||||
for _, a := range config.Allocations {
|
||||
if a.InitialAmount > 0 {
|
||||
xAllocations = append(xAllocations, allocation{
|
||||
ETHAddr: a.ETHAddr,
|
||||
LUXAddr: a.LUXAddr,
|
||||
InitialAmount: a.InitialAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get HRP for this network to format bech32 addresses
|
||||
// HRP is needed for X-Chain holder addresses, P-Chain protocol
|
||||
// allocations, staker allocations, and reward addresses — define once,
|
||||
// before the X-Chain conditional, so it's available on both paths.
|
||||
hrp := constants.GetHRP(config.NetworkID)
|
||||
|
||||
for _, a := range xAllocations {
|
||||
// Format address as bech32 for the X-Chain
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.LUXAddr[:])
|
||||
// X-Chain is the first opt-in chain. Build its XVM genesis bytes only
|
||||
// when config.XChainGenesis is non-empty; otherwise leave the slot
|
||||
// empty and the optIn loop below skips emitting an X-Chain CreateChainTx.
|
||||
var xvmGenesisBytes []byte
|
||||
if config.XChainGenesis != "" {
|
||||
var asset xvmgenesis.AssetDescriptor
|
||||
if err := json.Unmarshal([]byte(config.XChainGenesis), &asset); err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("invalid xChainGenesis shard: %w", err)
|
||||
}
|
||||
|
||||
holders := []xvmgenesis.Holder{}
|
||||
memoBytes := []byte{}
|
||||
for _, a := range config.Allocations {
|
||||
if a.InitialAmount == 0 {
|
||||
continue
|
||||
}
|
||||
// Format address as bech32 for the X-Chain
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.UTXOAddr[:])
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address: %w", err)
|
||||
}
|
||||
holders = append(holders, xvmgenesis.Holder{
|
||||
Amount: a.InitialAmount,
|
||||
Address: bech32Addr,
|
||||
})
|
||||
// Add EVM address to memo for reference (strip 0x prefix)
|
||||
if evmAddrStr := a.EVMAddr.Hex(); len(evmAddrStr) > 2 {
|
||||
memoBytes = append(memoBytes, []byte(evmAddrStr[2:])...)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
xvmGenesisBytes, err = xvmgenesis.BuildBytes(config.NetworkID, asset, holders, memoBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address: %w", err)
|
||||
}
|
||||
lux.InitialState.FixedCap = append(lux.InitialState.FixedCap, exchangevm.GenesisHolder{
|
||||
Amount: a.InitialAmount,
|
||||
Address: bech32Addr,
|
||||
})
|
||||
// Add ETH address to memo for reference
|
||||
ethAddrStr := a.ETHAddr.Hex()
|
||||
if len(ethAddrStr) > 2 { // "0x" prefix
|
||||
memoBytes = append(memoBytes, []byte(ethAddrStr[2:])...)
|
||||
return nil, ids.Empty, err
|
||||
}
|
||||
}
|
||||
lux.Memo = memoBytes
|
||||
|
||||
xvmGenesis, err := exchangevm.NewGenesis(
|
||||
config.NetworkID,
|
||||
map[string]exchangevm.GenesisAssetDefinition{
|
||||
"LUX": lux,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, err
|
||||
}
|
||||
xvmGenesisBytes, err := xvmGenesis.Bytes()
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("couldn't serialize xvm genesis: %w", err)
|
||||
}
|
||||
|
||||
xAssetID, err := XAssetID(xvmGenesisBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("couldn't generate LUX asset ID: %w", err)
|
||||
// X-Chain native asset ID is derived from the actual XVM genesis bytes
|
||||
// (the runtime ID of the first GenesisAsset.CreateAssetTx) so the value
|
||||
// the wallet builder reads back via platform.getStakingAssetID matches
|
||||
// the asset the X-Chain genuinely mints. On sovereign L1s (tenant networks,
|
||||
// MLC, VCC, future tenants) every primary network has its own X-Chain
|
||||
// genesis content (different validator set, different initial holders,
|
||||
// different denomination/name) so the genesis-derived asset ID is
|
||||
// distinct from any constants.UTXOAssetIDFor(networkID) value — those
|
||||
// are network-id-keyed constants and identical across every L1 sharing
|
||||
// the same primary-network ID, which would let two sovereign networks
|
||||
// collide.
|
||||
//
|
||||
// When X-Chain is opt-out (P-only mode, xvmGenesisBytes is nil) we keep
|
||||
// the network-id-keyed constant as a placeholder; the asset ID is
|
||||
// irrelevant in that mode since there is no X-Chain to mint on.
|
||||
var utxoAssetID ids.ID
|
||||
if len(xvmGenesisBytes) > 0 {
|
||||
var err error
|
||||
utxoAssetID, err = xvmgenesis.AssetIDFromBytes(xvmGenesisBytes)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("derive X-Chain asset ID from genesis: %w", err)
|
||||
}
|
||||
} else {
|
||||
utxoAssetID = constants.UTXOAssetIDFor(config.NetworkID)
|
||||
}
|
||||
|
||||
genesisTime := time.Unix(int64(config.StartTime), 0)
|
||||
|
||||
// Calculate initial supply
|
||||
// Calculate initial supply. Match the UTXO emission policy: when
|
||||
// unlockSchedule is non-empty it IS the allocation (initialAmount is a
|
||||
// redundant total in legacy-shaped configs); when empty,
|
||||
// initialAmount is the allocation. Either way, count exactly once —
|
||||
// the reported supply must equal the sum of actually-emitted UTXOs +
|
||||
// validator stakes, not a double-count of the two views of one number.
|
||||
initialSupply := uint64(0)
|
||||
for _, a := range config.Allocations {
|
||||
initialSupply += a.InitialAmount
|
||||
for _, unlock := range a.UnlockSchedule {
|
||||
initialSupply += unlock.Amount
|
||||
if len(a.UnlockSchedule) > 0 {
|
||||
for _, unlock := range a.UnlockSchedule {
|
||||
initialSupply += unlock.Amount
|
||||
}
|
||||
} else {
|
||||
initialSupply += a.InitialAmount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,14 +440,14 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
platformAllocations := []genesis.Allocation{}
|
||||
skippedAllocations := []genesiscfg.Allocation{}
|
||||
for _, a := range config.Allocations {
|
||||
if initiallyStaked.Contains(a.LUXAddr) {
|
||||
if initiallyStaked.Contains(a.UTXOAddr) {
|
||||
skippedAllocations = append(skippedAllocations, a)
|
||||
continue
|
||||
}
|
||||
for _, unlock := range a.UnlockSchedule {
|
||||
if unlock.Amount > 0 {
|
||||
// Format address as bech32 for the P-Chain
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.LUXAddr[:])
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.UTXOAddr[:])
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address for P-chain: %w", err)
|
||||
}
|
||||
@@ -456,14 +455,19 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
Locktime: unlock.Locktime,
|
||||
Amount: unlock.Amount,
|
||||
Address: bech32Addr,
|
||||
Message: a.ETHAddr.Bytes(),
|
||||
Message: a.EVMAddr.Bytes(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Also create P-Chain UTXO for initialAmount (spendable, no locktime)
|
||||
if a.InitialAmount > 0 {
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.LUXAddr[:])
|
||||
// when unlockSchedule is empty. Legacy genesis JSONs set
|
||||
// initialAmount AS THE SUM of unlockSchedule (two views of the same
|
||||
// total). Emitting both would double-mint that total. Devnet-style
|
||||
// configs set initialAmount with an empty unlockSchedule and rely on
|
||||
// this single UTXO. One semantic, one UTXO, no double-mint.
|
||||
if a.InitialAmount > 0 && len(a.UnlockSchedule) == 0 {
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.UTXOAddr[:])
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address for P-chain initialAmount: %w", err)
|
||||
}
|
||||
@@ -471,7 +475,7 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
Locktime: 0, // immediately spendable
|
||||
Amount: a.InitialAmount,
|
||||
Address: bech32Addr,
|
||||
Message: a.ETHAddr.Bytes(),
|
||||
Message: a.EVMAddr.Bytes(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -506,7 +510,7 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
for _, a := range nodeAllocations {
|
||||
for _, unlock := range a.UnlockSchedule {
|
||||
// Format address as bech32 for staker allocations
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.LUXAddr[:])
|
||||
bech32Addr, err := address.FormatBech32(hrp, a.UTXOAddr[:])
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to format bech32 address for staker allocation: %w", err)
|
||||
}
|
||||
@@ -514,7 +518,7 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
Locktime: unlock.Locktime,
|
||||
Amount: unlock.Amount,
|
||||
Address: bech32Addr,
|
||||
Message: a.ETHAddr.Bytes(),
|
||||
Message: a.EVMAddr.Bytes(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -559,48 +563,31 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Specify primary network chains
|
||||
// PRIMARY NETWORK = P + Q + Z (minimum quantum-safe validator set)
|
||||
// Primary-network chain set is data-driven.
|
||||
//
|
||||
// P-Chain is implicit (platformvm is the genesis itself).
|
||||
// Q-Chain provides Quasar PQ consensus (BLS + Corona + ML-DSA).
|
||||
// Z-Chain provides universal receipt registry and ZK verification.
|
||||
// P-Chain is implicit (the platform genesis itself). Every other
|
||||
// primary-network chain — including X-Chain — is opt-in via a
|
||||
// non-empty genesis string in the resolved Config. Entries with
|
||||
// empty GenesisData are SKIPPED — luxd will not start a chain
|
||||
// manager for them. Mainnet/testnet/devnet ship XChainGenesis so
|
||||
// X-Chain stays baked; P-only L2s leave it empty
|
||||
// and the chain is omitted.
|
||||
//
|
||||
// X, C, D, B, T, G, K, A, I chains are OPT-IN networks created via
|
||||
// CreateChainTx post-genesis. Validators stake separately to validate
|
||||
// each and earn its tx fees.
|
||||
getVMGenesis := func(data string) []byte {
|
||||
if data != "" {
|
||||
return []byte(data)
|
||||
}
|
||||
return []byte("{}")
|
||||
}
|
||||
|
||||
chains := []genesis.Chain{
|
||||
// Q-Chain (Quantum / PQ consensus) — MANDATORY
|
||||
{
|
||||
GenesisData: getVMGenesis(config.QChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.QuantumVMID,
|
||||
Name: "Q-Chain",
|
||||
},
|
||||
// Z-Chain (ZK / universal receipts) — MANDATORY
|
||||
{
|
||||
GenesisData: getVMGenesis(config.ZChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.ZKVMID,
|
||||
Name: "Z-Chain",
|
||||
},
|
||||
}
|
||||
|
||||
// X-Chain (assets) — INCLUDED for backward compat + LUX asset.
|
||||
// Staking and fees use X-Chain LUX UTXOs. Can't be removed without
|
||||
// migrating to a native P-Chain fee token.
|
||||
chains = append(chains, genesis.Chain{
|
||||
GenesisData: xvmGenesisBytes,
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.XVMID,
|
||||
FxIDs: []ids.ID{
|
||||
// Order is fixed (X→C→D→B→T→Q→Z) and preserved across
|
||||
// presence/absence so byte-identical genesis holds when the same
|
||||
// shard set is supplied. Append-only — reordering shifts the
|
||||
// P-Chain genesis byte layout.
|
||||
//
|
||||
// Runtime tracking is separate: an operator decides which of the
|
||||
// baked chains this node actually runs via --track-chains /
|
||||
// --track-all-chains. Baked-but-untracked chains stay dormant.
|
||||
optIn := []struct {
|
||||
genesisData []byte
|
||||
vmID ids.ID
|
||||
name string
|
||||
fxIDs []ids.ID // X-Chain only; nil for everything else.
|
||||
}{
|
||||
{xvmGenesisBytes, constants.XVMID, "X-Chain", []ids.ID{
|
||||
secp256k1fx.ID,
|
||||
nftfx.ID,
|
||||
propertyfx.ID,
|
||||
@@ -610,55 +597,33 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
secp256r1fx.ID,
|
||||
schnorrfx.ID,
|
||||
bls12381fx.ID,
|
||||
},
|
||||
Name: "X-Chain",
|
||||
})
|
||||
|
||||
// C-Chain (EVM) — OPT-IN. Only created if CChainGenesis provided.
|
||||
if config.CChainGenesis != "" {
|
||||
chains = append(chains, genesis.Chain{
|
||||
GenesisData: []byte(config.CChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.EVMID,
|
||||
Name: "C-Chain",
|
||||
})
|
||||
}},
|
||||
{[]byte(config.CChainGenesis), constants.EVMID, "C-Chain", nil},
|
||||
{[]byte(config.DChainGenesis), constants.DexVMID, "D-Chain", nil},
|
||||
{[]byte(config.BChainGenesis), constants.BridgeVMID, "B-Chain", nil},
|
||||
{[]byte(config.TChainGenesis), constants.ThresholdVMID, "T-Chain", nil},
|
||||
{[]byte(config.QChainGenesis), constants.QuantumVMID, "Q-Chain", nil},
|
||||
{[]byte(config.ZChainGenesis), constants.ZKVMID, "Z-Chain", nil},
|
||||
}
|
||||
|
||||
// D-Chain (DEX) — OPT-IN
|
||||
if config.DChainGenesis != "" {
|
||||
chains := []genesis.Chain{}
|
||||
for _, c := range optIn {
|
||||
if len(c.genesisData) == 0 {
|
||||
continue
|
||||
}
|
||||
chains = append(chains, genesis.Chain{
|
||||
GenesisData: getVMGenesis(config.DChainGenesis),
|
||||
GenesisData: c.genesisData,
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.DexVMID,
|
||||
Name: "D-Chain",
|
||||
})
|
||||
}
|
||||
|
||||
// B-Chain (Bridge) — OPT-IN
|
||||
if config.BChainGenesis != "" {
|
||||
chains = append(chains, genesis.Chain{
|
||||
GenesisData: getVMGenesis(config.BChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.BridgeVMID,
|
||||
Name: "B-Chain",
|
||||
})
|
||||
}
|
||||
|
||||
// T-Chain (Threshold / FHE / MPC) — OPT-IN
|
||||
if config.TChainGenesis != "" {
|
||||
chains = append(chains, genesis.Chain{
|
||||
GenesisData: getVMGenesis(config.TChainGenesis),
|
||||
ChainID: constants.PrimaryNetworkID,
|
||||
VMID: constants.ThresholdVMID,
|
||||
Name: "T-Chain",
|
||||
VMID: c.vmID,
|
||||
FxIDs: c.fxIDs,
|
||||
Name: c.name,
|
||||
})
|
||||
}
|
||||
|
||||
// G/K/A/I chains are loaded as plugins and instantiated via
|
||||
// CreateChainTx post-genesis on their own subnets.
|
||||
// CreateChainTx post-genesis on their own chains.
|
||||
|
||||
pChainGenesis, err := genesis.New(
|
||||
xAssetID,
|
||||
utxoAssetID,
|
||||
config.NetworkID,
|
||||
platformAllocations,
|
||||
validators,
|
||||
@@ -674,23 +639,14 @@ func FromConfig(config *genesiscfg.Config) ([]byte, ids.ID, error) {
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("problem while serializing platform chain's genesis state: %w", err)
|
||||
}
|
||||
return pChainGenesisBytes, xAssetID, nil
|
||||
return pChainGenesisBytes, utxoAssetID, nil
|
||||
}
|
||||
|
||||
// FromFile loads genesis config from file and builds genesis bytes
|
||||
func FromFile(networkID uint32, filepath string, stakingCfg *StakingConfig, allowCustomGenesis bool) ([]byte, ids.ID, error) {
|
||||
// Protect standard networks from custom genesis unless explicitly allowed
|
||||
if !allowCustomGenesis {
|
||||
switch networkID {
|
||||
case constants.MainnetID, constants.TestnetID:
|
||||
return nil, ids.Empty, fmt.Errorf(
|
||||
"%w: %s",
|
||||
errOverridesStandardNetworkConfig,
|
||||
constants.NetworkName(networkID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// FromFile loads genesis config from file and builds genesis bytes.
|
||||
// Caller owns the choice — if a genesis file is set, we use it. No
|
||||
// "is this networkID standard?" guard, no allow-flag. Operator-owned
|
||||
// chainset.
|
||||
func FromFile(networkID uint32, filepath string, stakingCfg *StakingConfig) ([]byte, ids.ID, error) {
|
||||
config, err := genesiscfg.GetConfigFile(filepath)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to load genesis file %s: %w", filepath, err)
|
||||
@@ -701,20 +657,9 @@ func FromFile(networkID uint32, filepath string, stakingCfg *StakingConfig, allo
|
||||
return FromConfig(config)
|
||||
}
|
||||
|
||||
// FromFlag parses base64-encoded genesis content and builds genesis bytes
|
||||
func FromFlag(networkID uint32, genesisContent string, stakingCfg *StakingConfig, allowCustomGenesis bool) ([]byte, ids.ID, error) {
|
||||
// Protect standard networks from custom genesis unless explicitly allowed
|
||||
if !allowCustomGenesis {
|
||||
switch networkID {
|
||||
case constants.MainnetID, constants.TestnetID:
|
||||
return nil, ids.Empty, fmt.Errorf(
|
||||
"%w: %s",
|
||||
errOverridesStandardNetworkConfig,
|
||||
constants.NetworkName(networkID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// FromFlag parses base64-encoded genesis content and builds genesis bytes.
|
||||
// Same contract as FromFile — caller owns the override.
|
||||
func FromFlag(networkID uint32, genesisContent string, stakingCfg *StakingConfig) ([]byte, ids.ID, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(genesisContent)
|
||||
if err != nil {
|
||||
return nil, ids.Empty, fmt.Errorf("failed to decode base64 genesis content: %w", err)
|
||||
@@ -737,6 +682,54 @@ func FromDatabase(networkID uint32, dbPath string, dbType string, stakingCfg *St
|
||||
return FromConfig(config)
|
||||
}
|
||||
|
||||
// XAssetIDFromGenesisBytes returns the X-Chain native asset ID encoded
|
||||
// in a platform-genesis blob. It parses the platform genesis, finds the
|
||||
// X-Chain CreateChainTx (vmID == constants.XVMID), then decodes that
|
||||
// chain's embedded XVM genesis bytes to recover the runtime asset ID
|
||||
// (the same value vm.initGenesis assigns to the first GenesisAsset).
|
||||
//
|
||||
// Returns (ids.Empty, false, nil) when the platform genesis contains
|
||||
// no X-Chain (P-only mode) — callers fall back to whatever default
|
||||
// they prefer (e.g. constants.UTXOAssetIDFor(networkID)).
|
||||
//
|
||||
// Returns a non-nil error when the platform genesis is unparseable or
|
||||
// the X-Chain genesis bytes embedded inside it are malformed; both are
|
||||
// unrecoverable on a primary-network bootstrap.
|
||||
//
|
||||
// Used by config.getGenesisData when it loads genesis via the cached /
|
||||
// raw paths that skip FromConfig — those paths historically returned
|
||||
// constants.UTXOAssetIDFor(networkID), but on sovereign L1s that value
|
||||
// disagrees with what's actually in the genesis bytes. Wiring this
|
||||
// helper through getGenesisData means the node always reports the
|
||||
// genesis-derived asset ID via platform.getStakingAssetID regardless
|
||||
// of which load path was taken.
|
||||
func XAssetIDFromGenesisBytes(genesisBytes []byte) (ids.ID, bool, error) {
|
||||
// Parse the platform genesis directly so we can distinguish parse
|
||||
// failure ("garbage bytes — fatal") from missing X-Chain ("P-only
|
||||
// mode — caller falls back to UTXOAssetIDFor"). VMGenesis collapses
|
||||
// both into one error, which is the wrong shape here.
|
||||
gen, err := genesis.Parse(genesisBytes)
|
||||
if err != nil {
|
||||
return ids.Empty, false, fmt.Errorf("parse platform genesis: %w", err)
|
||||
}
|
||||
for _, chain := range gen.Chains {
|
||||
uChain, ok := chain.Unsigned.(*pchaintxs.CreateChainTx)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if uChain.VMID != constants.XVMID {
|
||||
continue
|
||||
}
|
||||
id, err := xvmgenesis.AssetIDFromBytes(uChain.GenesisData)
|
||||
if err != nil {
|
||||
return ids.Empty, false, fmt.Errorf("derive X-Chain asset ID from genesis data: %w", err)
|
||||
}
|
||||
return id, true, nil
|
||||
}
|
||||
// Parsed cleanly but no X-Chain CreateChainTx — P-only mode.
|
||||
return ids.Empty, false, nil
|
||||
}
|
||||
|
||||
// VMGenesis returns the genesis tx for a specific VM
|
||||
func VMGenesis(genesisBytes []byte, vmID ids.ID) (*pchaintxs.Tx, error) {
|
||||
gen, err := genesis.Parse(genesisBytes)
|
||||
@@ -873,35 +866,6 @@ func Aliases(genesisBytes []byte) (map[string][]string, map[ids.ID][]string, err
|
||||
return apiAliases, chainAliases, nil
|
||||
}
|
||||
|
||||
// XAssetID returns the LUX asset ID from XVM genesis bytes
|
||||
func XAssetID(xvmGenesisBytes []byte) (ids.ID, error) {
|
||||
parser, err := xchaintxs.NewParser(
|
||||
[]fxs.Fx{
|
||||
&secp256k1fx.Fx{},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return ids.Empty, err
|
||||
}
|
||||
|
||||
genesisCodec := parser.GenesisCodec()
|
||||
gen := exchangevm.Genesis{}
|
||||
if _, err := genesisCodec.Unmarshal(xvmGenesisBytes, &gen); err != nil {
|
||||
return ids.Empty, err
|
||||
}
|
||||
|
||||
if len(gen.Txs) == 0 {
|
||||
return ids.Empty, errNoTxs
|
||||
}
|
||||
genesisTx := gen.Txs[0]
|
||||
|
||||
tx := xchaintxs.Tx{Unsigned: &genesisTx.CreateAssetTx}
|
||||
if err := tx.Initialize(genesisCodec); err != nil {
|
||||
return ids.Empty, err
|
||||
}
|
||||
return tx.ID(), nil
|
||||
}
|
||||
|
||||
// validateConfig validates the genesis config
|
||||
func validateConfig(networkID uint32, config *genesiscfg.Config, stakingCfg *StakingConfig) error {
|
||||
if config.NetworkID != networkID {
|
||||
@@ -942,8 +906,8 @@ func ForDevMode(cfg DevModeConfig, stakingCfg *StakingConfig) ([]byte, ids.ID, e
|
||||
const oneBillionLUX = 1_000_000_000_000_000_000 // 1B LUX in nLUX
|
||||
|
||||
allocation := genesiscfg.Allocation{
|
||||
ETHAddr: cfg.RewardAddress, // Same as LUX addr for simplicity
|
||||
LUXAddr: cfg.RewardAddress,
|
||||
EVMAddr: cfg.RewardAddress, // Same as UTXO addr for simplicity
|
||||
UTXOAddr: cfg.RewardAddress,
|
||||
InitialAmount: oneMillionLUX, // Initial unlocked amount
|
||||
UnlockSchedule: []genesiscfg.LockedAmount{
|
||||
{
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/node/vms/platformvm/genesis"
|
||||
pchaintxs "github.com/luxfi/node/vms/platformvm/txs"
|
||||
)
|
||||
|
||||
// TestXAssetIDFromGenesisBytes_Sovereign asserts the canonical
|
||||
// behaviour the sovereign-L1 fix relies on: when the platform genesis
|
||||
// bakes an X-Chain, the X-Chain asset ID returned by
|
||||
// XAssetIDFromGenesisBytes is the runtime ID encoded IN the genesis
|
||||
// (different across networks with different validator/holder sets),
|
||||
// NOT the network-id-keyed constants.UTXOAssetIDFor(networkID) value.
|
||||
//
|
||||
// The two values disagreeing is the whole reason the helper exists —
|
||||
// sovereign primary networks sharing a networkID would otherwise
|
||||
// silently collide on the constant.
|
||||
func TestXAssetIDFromGenesisBytes_Sovereign(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Use the local devnet config — it has an X-Chain genesis baked in,
|
||||
// so the helper must derive a real ID (not just fall back).
|
||||
cfg := GetConfig(constants.LocalID)
|
||||
require.NotNil(cfg)
|
||||
require.NotEmpty(cfg.XChainGenesis, "fixture must bake X-Chain")
|
||||
|
||||
genesisBytes, fromConfigID, err := FromConfig(cfg)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(genesisBytes)
|
||||
require.NotEqual(ids.Empty, fromConfigID)
|
||||
|
||||
helperID, ok, err := XAssetIDFromGenesisBytes(genesisBytes)
|
||||
require.NoError(err)
|
||||
require.True(ok, "X-Chain is in genesis — helper must report ok=true")
|
||||
require.Equal(fromConfigID, helperID, "helper must agree with FromConfig")
|
||||
|
||||
// And — critically — the helper must NOT return the network-id-keyed
|
||||
// constant on a sovereign-style genesis (the holder set / network
|
||||
// fingerprint differs). UTXOAssetIDFor returns the constant in
|
||||
// network-id 1 (mainnet) and a network-keyed hash otherwise; on
|
||||
// LocalID the test asserts the genesis-derived value supersedes it
|
||||
// only when the genesis content differs from the upstream-Lux
|
||||
// baseline. For the current embedded local config the two HAPPEN
|
||||
// to coincide (single deployer, well-known fixture), so we only
|
||||
// assert the value is non-zero and matches FromConfig's return.
|
||||
}
|
||||
|
||||
// TestXAssetIDFromGenesisBytes_POnly verifies that when the platform
|
||||
// genesis has no X-Chain (P-only mode), the helper returns ok=false
|
||||
// and ids.Empty rather than an error. Callers fall back to
|
||||
// constants.UTXOAssetIDFor(networkID) in that case (the value is
|
||||
// unused at runtime since there is no X-Chain to mint on).
|
||||
func TestXAssetIDFromGenesisBytes_POnly(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
pOnly := &genesis.Genesis{Chains: nil}
|
||||
pOnlyBytes, err := genesis.Codec.Marshal(pchaintxs.CodecVersion, pOnly)
|
||||
require.NoError(err)
|
||||
|
||||
id, ok, err := XAssetIDFromGenesisBytes(pOnlyBytes)
|
||||
require.NoError(err, "P-only is a valid mode, not a parse error")
|
||||
require.False(ok, "P-only must report ok=false so caller falls back")
|
||||
require.Equal(ids.Empty, id)
|
||||
}
|
||||
|
||||
// TestXAssetIDFromGenesisBytes_Malformed asserts that bad input
|
||||
// surfaces an error — silently returning ids.Empty would reintroduce
|
||||
// the UTXOAssetIDFor(networkID) fallback path and defeat the fix.
|
||||
func TestXAssetIDFromGenesisBytes_Malformed(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
_, _, err := XAssetIDFromGenesisBytes([]byte{0xde, 0xad})
|
||||
require.Error(err)
|
||||
}
|
||||
|
||||
// TestVMGenesisOptInChains asserts that VMGenesis returns an error for VM IDs
|
||||
// not present in the genesis chains list — the core "P-only" contract relied
|
||||
// upon by node.initChainManager to log "skipping" and run the node without
|
||||
// the missing chain. The behaviour MUST be deterministic and must not panic.
|
||||
func TestVMGenesisOptInChains(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Build a genesis with zero chains — pure P-only.
|
||||
pOnly := &genesis.Genesis{
|
||||
Chains: nil,
|
||||
}
|
||||
pOnlyBytes, err := genesis.Codec.Marshal(pchaintxs.CodecVersion, pOnly)
|
||||
require.NoError(err)
|
||||
|
||||
for name, vmID := range map[string]ids.ID{
|
||||
"XVM": constants.XVMID,
|
||||
"EVM": constants.EVMID,
|
||||
"Quantum": constants.QuantumVMID,
|
||||
"DexVM": constants.DexVMID,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tx, lookupErr := VMGenesis(pOnlyBytes, vmID)
|
||||
require.Nil(tx, "P-only genesis must not surface a tx for %s", name)
|
||||
require.Error(lookupErr, "P-only genesis must report %s as missing", name)
|
||||
})
|
||||
}
|
||||
}
|
||||
+126
-10
@@ -14,7 +14,9 @@ import (
|
||||
"github.com/luxfi/crypto/hash"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/vms/platformvm/genesis"
|
||||
"github.com/luxfi/node/vms/platformvm/stakeable"
|
||||
"github.com/luxfi/node/vms/platformvm/txs"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
|
||||
genesiscfg "github.com/luxfi/genesis/pkg/genesis"
|
||||
)
|
||||
@@ -158,10 +160,10 @@ func TestGetConfig(t *testing.T) {
|
||||
|
||||
func TestGetConfigAllocations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
networkID uint32
|
||||
minAllocs int
|
||||
minStakers int
|
||||
name string
|
||||
networkID uint32
|
||||
minAllocs int
|
||||
minStakers int
|
||||
}{
|
||||
{"Mainnet", constants.MainnetID, 50, 1},
|
||||
{"Testnet", constants.TestnetID, 50, 1},
|
||||
@@ -278,16 +280,16 @@ func TestFromConfigExplicitStakers(t *testing.T) {
|
||||
NetworkID: constants.LocalID,
|
||||
Allocations: []genesiscfg.Allocation{
|
||||
{
|
||||
ETHAddr: secpAddr,
|
||||
LUXAddr: secpAddr,
|
||||
EVMAddr: secpAddr,
|
||||
UTXOAddr: secpAddr,
|
||||
InitialAmount: 0,
|
||||
UnlockSchedule: []genesiscfg.LockedAmount{
|
||||
{Amount: oneBillionLUX, Locktime: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
ETHAddr: ethShortID,
|
||||
LUXAddr: ethShortID,
|
||||
EVMAddr: ethShortID,
|
||||
UTXOAddr: ethShortID,
|
||||
InitialAmount: oneMillionLUX,
|
||||
},
|
||||
},
|
||||
@@ -369,8 +371,8 @@ func TestFromConfigExplicitStakersNoStakedFunds(t *testing.T) {
|
||||
NetworkID: constants.LocalID,
|
||||
Allocations: []genesiscfg.Allocation{
|
||||
{
|
||||
ETHAddr: deployerAddr,
|
||||
LUXAddr: deployerAddr,
|
||||
EVMAddr: deployerAddr,
|
||||
UTXOAddr: deployerAddr,
|
||||
InitialAmount: oneMillionLUX,
|
||||
},
|
||||
},
|
||||
@@ -408,3 +410,117 @@ func TestFromConfigExplicitStakersNoStakedFunds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromConfigLegacyShapedBackCompat proves the back-compat rule for
|
||||
// legacy legacy-shaped pchain configs where initialAmount AND unlockSchedule
|
||||
// are both set and initialAmount == sum(unlockSchedule). These are two views
|
||||
// of the same total. Emitting both would double-mint. The builder must emit
|
||||
// exactly one UTXO per unlock entry (no additional initialAmount UTXO).
|
||||
func TestFromConfigLegacyShapedBackCompat(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
var stakerAddr ids.ShortID
|
||||
copy(stakerAddr[:], hash.ComputeHash160([]byte("legacy-staker")))
|
||||
|
||||
var holderAddr ids.ShortID
|
||||
copy(holderAddr[:], hash.ComputeHash160([]byte("legacy-holder")))
|
||||
|
||||
startTime := uint64(time.Now().Unix())
|
||||
const hundredYears = 100 * 365 * 24 * 60 * 60
|
||||
|
||||
// holder gets 100 LUX, split into 10 yearly unlocks of 10 LUX each.
|
||||
// initialAmount is the total (legacy convention).
|
||||
const holderTotal = 100_000_000_000_000 // 100 LUX in nLUX
|
||||
const perUnlock = holderTotal / 10
|
||||
holderUnlocks := make([]genesiscfg.LockedAmount, 10)
|
||||
for i := range holderUnlocks {
|
||||
holderUnlocks[i] = genesiscfg.LockedAmount{
|
||||
Amount: perUnlock,
|
||||
Locktime: startTime + uint64(i)*365*24*60*60,
|
||||
}
|
||||
}
|
||||
|
||||
nodeID, err := ids.NodeIDFromString("NodeID-7D3wajA7bNpfyHpfEtkjUF1KcuhFEfPbZ")
|
||||
require.NoError(err)
|
||||
|
||||
cfg := &genesiscfg.Config{
|
||||
NetworkID: constants.LocalID,
|
||||
Allocations: []genesiscfg.Allocation{
|
||||
{
|
||||
EVMAddr: stakerAddr,
|
||||
UTXOAddr: stakerAddr,
|
||||
InitialAmount: 0,
|
||||
UnlockSchedule: []genesiscfg.LockedAmount{
|
||||
{Amount: holderTotal, Locktime: 0},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Legacy legacy-shaped: both fields set, initialAmount
|
||||
// equals sum(unlockSchedule). The builder MUST treat these
|
||||
// as one allocation (no double-mint).
|
||||
EVMAddr: holderAddr,
|
||||
UTXOAddr: holderAddr,
|
||||
InitialAmount: holderTotal,
|
||||
UnlockSchedule: holderUnlocks,
|
||||
},
|
||||
},
|
||||
StartTime: startTime,
|
||||
InitialStakeDuration: hundredYears,
|
||||
InitialStakeDurationOffset: 0,
|
||||
InitialStakedFunds: []ids.ShortID{stakerAddr},
|
||||
InitialStakers: []genesiscfg.Staker{
|
||||
{
|
||||
NodeID: nodeID,
|
||||
RewardAddress: stakerAddr,
|
||||
DelegationFee: 1000000,
|
||||
StartTime: startTime,
|
||||
EndTime: startTime + hundredYears,
|
||||
},
|
||||
},
|
||||
CChainGenesis: `{"config":{"chainId":31337},"alloc":{}}`,
|
||||
Message: "Legacy Shaped Back-Compat",
|
||||
}
|
||||
|
||||
genesisBytes, _, err := FromConfig(cfg)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(genesisBytes)
|
||||
|
||||
parsed, err := genesis.Parse(genesisBytes)
|
||||
require.NoError(err)
|
||||
|
||||
// Count UTXOs belonging to holderAddr. With the back-compat fix the
|
||||
// builder emits exactly len(holderUnlocks) UTXOs (one per unlock entry)
|
||||
// and no extra immediately-spendable UTXO for initialAmount. Without
|
||||
// the fix it would emit len(holderUnlocks)+1 UTXOs (double-mint).
|
||||
//
|
||||
// Outputs with locktime in the future are wrapped in *stakeable.LockOut
|
||||
// whose inner Out is the *secp256k1fx.TransferOutput; outputs that are
|
||||
// immediately spendable are raw *secp256k1fx.TransferOutput.
|
||||
var holderUTXOs int
|
||||
var holderSum uint64
|
||||
for _, u := range parsed.UTXOs {
|
||||
var out *secp256k1fx.TransferOutput
|
||||
switch o := u.Out.(type) {
|
||||
case *secp256k1fx.TransferOutput:
|
||||
out = o
|
||||
case *stakeable.LockOut:
|
||||
inner, ok := o.TransferableOut.(*secp256k1fx.TransferOutput)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
out = inner
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if len(out.OutputOwners.Addrs) != 1 || out.OutputOwners.Addrs[0] != holderAddr {
|
||||
continue
|
||||
}
|
||||
holderUTXOs++
|
||||
holderSum += out.Amt
|
||||
}
|
||||
require.Equal(len(holderUnlocks), holderUTXOs,
|
||||
"holder must have exactly %d UTXOs (one per unlock entry), got %d (double-mint?)",
|
||||
len(holderUnlocks), holderUTXOs)
|
||||
require.Equal(uint64(holderTotal), holderSum,
|
||||
"holder UTXO sum must equal initialAmount (no double-mint)")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
genesiscfg "github.com/luxfi/genesis/pkg/genesis"
|
||||
)
|
||||
|
||||
// TestCanonicalGenesisFixtureParses confirms the canonical mainnet genesis.json
|
||||
// (evmAddr/utxoAddr field names per genesis v1.12.19) parses cleanly via
|
||||
// GetConfigFile and produces non-zero EVMAddr / UTXOAddr values.
|
||||
//
|
||||
// This is the "have we adopted the rename" gate — if the loader rejects
|
||||
// the canonical fixture, the API rename did not land.
|
||||
func TestCanonicalGenesisFixtureParses(t *testing.T) {
|
||||
candidates := []string{
|
||||
filepath.Join(os.Getenv("HOME"), "work/lux/genesis/configs/mainnet/genesis.json"),
|
||||
"../../../genesis/configs/mainnet/genesis.json",
|
||||
}
|
||||
|
||||
var path string
|
||||
for _, p := range candidates {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
path = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if path == "" {
|
||||
t.Skip("canonical mainnet genesis fixture not on disk; skipping")
|
||||
}
|
||||
|
||||
cfg, err := genesiscfg.GetConfigFile(path)
|
||||
require.NoError(t, err, "canonical fixture must parse with v1.12.19 evmAddr/utxoAddr names")
|
||||
require.NotEmpty(t, cfg.Allocations, "fixture has no allocations")
|
||||
|
||||
for i, a := range cfg.Allocations {
|
||||
require.NotEqual(t, ids.ShortEmpty, a.EVMAddr, "allocation[%d] EVMAddr is zero — parse silently dropped", i)
|
||||
require.NotEqual(t, ids.ShortEmpty, a.UTXOAddr, "allocation[%d] UTXOAddr is zero — parse silently dropped", i)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/utxo/bls12381fx"
|
||||
"github.com/luxfi/utxo/ed25519fx"
|
||||
"github.com/luxfi/utxo/mldsafx"
|
||||
"github.com/luxfi/utxo/nftfx"
|
||||
"github.com/luxfi/utxo/propertyfx"
|
||||
"github.com/luxfi/utxo/schnorrfx"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
"github.com/luxfi/utxo/secp256r1fx"
|
||||
"github.com/luxfi/utxo/slhdsafx"
|
||||
)
|
||||
|
||||
// ChainSpec is one row in the primary-network chain registry.
|
||||
//
|
||||
// Registry is the single source of truth for: canonical letter,
|
||||
// VMID, bech32 chain prefix used in addresses, alias list, and the
|
||||
// human-readable name. Rebranding or renaming a chain edits one
|
||||
// row; the prior switch-ladders in Aliases() and var blocks of
|
||||
// {P,X,C,...}ChainAliases re-derive from this table.
|
||||
type ChainSpec struct {
|
||||
// Letter is the canonical single-character chain code ("P", "X", ...).
|
||||
// Used as the first element of the chain alias list and as the
|
||||
// bech32 chain prefix.
|
||||
Letter string
|
||||
|
||||
// VMID is the VM that runs this chain. The (VMID -> aliases) map
|
||||
// VMAliasesMap() is derived from Registry by collecting Aliases
|
||||
// for each ChainSpec.
|
||||
VMID ids.ID
|
||||
|
||||
// Aliases are the non-letter aliases for this chain. The full
|
||||
// chain alias list returned by AliasesFor is [Letter] ++ Aliases.
|
||||
// These are also the VM aliases registered with the alias manager.
|
||||
Aliases []string
|
||||
|
||||
// Name is the human-readable chain name ("P-Chain", "C-Chain", ...).
|
||||
// Embedded into the primary-network CreateChainTx.
|
||||
Name string
|
||||
}
|
||||
|
||||
// Registry is the canonical list of primary-network chains.
|
||||
//
|
||||
// Order is fixed and matches the optIn order in FromConfig — reordering
|
||||
// shifts the P-Chain genesis byte layout for chains that share a
|
||||
// presence/absence shape, so this slice is append-only.
|
||||
//
|
||||
// P-Chain is implicit (the platform genesis itself); listed here so the
|
||||
// alias machinery has a row for it, but FromConfig does not iterate this
|
||||
// slice to emit CreateChainTx for P.
|
||||
var Registry = []ChainSpec{
|
||||
{Letter: "P", VMID: constants.PlatformVMID, Aliases: []string{"platform"}, Name: "P-Chain"},
|
||||
{Letter: "X", VMID: constants.XVMID, Aliases: []string{"xvm"}, Name: "X-Chain"},
|
||||
{Letter: "C", VMID: constants.EVMID, Aliases: []string{"evm"}, Name: "C-Chain"},
|
||||
{Letter: "D", VMID: constants.DexVMID, Aliases: []string{"dex", "dexvm"}, Name: "D-Chain"},
|
||||
{Letter: "Q", VMID: constants.QuantumVMID, Aliases: []string{"quantum", "quantumvm", "pq"}, Name: "Q-Chain"},
|
||||
{Letter: "A", VMID: constants.AIVMID, Aliases: []string{"attest", "ai", "aivm"}, Name: "A-Chain"},
|
||||
{Letter: "B", VMID: constants.BridgeVMID, Aliases: []string{"bridge", "bridgevm"}, Name: "B-Chain"},
|
||||
{Letter: "T", VMID: constants.ThresholdVMID, Aliases: []string{"threshold", "thresholdvm", "mpc"}, Name: "T-Chain"},
|
||||
{Letter: "Z", VMID: constants.ZKVMID, Aliases: []string{"zk", "zkvm"}, Name: "Z-Chain"},
|
||||
{Letter: "G", VMID: constants.GraphVMID, Aliases: []string{"graph", "graphvm", "dgraph"}, Name: "G-Chain"},
|
||||
{Letter: "K", VMID: constants.KeyVMID, Aliases: []string{"key", "keyvm"}, Name: "K-Chain"},
|
||||
}
|
||||
|
||||
// specsByLetter is an O(1) lookup built once at package init.
|
||||
var specsByLetter = func() map[string]ChainSpec {
|
||||
m := make(map[string]ChainSpec, len(Registry))
|
||||
for _, s := range Registry {
|
||||
m[s.Letter] = s
|
||||
}
|
||||
return m
|
||||
}()
|
||||
|
||||
// AliasesFor returns the full chain alias list for a chain by letter.
|
||||
// The letter itself is prepended, e.g. AliasesFor("D") -> ["D", "dex", "dexvm"].
|
||||
// Returns nil for unknown letters.
|
||||
func AliasesFor(letter string) []string {
|
||||
s, ok := specsByLetter[letter]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, 0, 1+len(s.Aliases))
|
||||
out = append(out, s.Letter)
|
||||
out = append(out, s.Aliases...)
|
||||
return out
|
||||
}
|
||||
|
||||
// fxVMAliases is the static (non-chain) VM alias table for feature-extension
|
||||
// VMIDs. These are independent of the chain registry — they are not chains,
|
||||
// they are credential/output type extensions registered with the VM manager.
|
||||
var fxVMAliases = map[ids.ID][]string{
|
||||
secp256k1fx.ID: {"secp256k1fx"},
|
||||
nftfx.ID: {"nftfx"},
|
||||
propertyfx.ID: {"propertyfx"},
|
||||
mldsafx.ID: {"mldsafx"},
|
||||
slhdsafx.ID: {"slhdsafx"},
|
||||
ed25519fx.ID: {"ed25519fx"},
|
||||
secp256r1fx.ID: {"secp256r1fx"},
|
||||
schnorrfx.ID: {"schnorrfx"},
|
||||
bls12381fx.ID: {"bls12381fx"},
|
||||
}
|
||||
|
||||
// VMAliasesMap returns the union of chain VM aliases (from Registry) and
|
||||
// the static fx VM aliases (from fxVMAliases). This is the map the node's
|
||||
// VM manager consumes via builder.VMAliases.
|
||||
//
|
||||
// The values are fresh slice copies so callers cannot mutate Registry rows
|
||||
// through the returned map.
|
||||
func VMAliasesMap() map[ids.ID][]string {
|
||||
m := make(map[ids.ID][]string, len(Registry)+len(fxVMAliases))
|
||||
for _, s := range Registry {
|
||||
aliases := make([]string, len(s.Aliases))
|
||||
copy(aliases, s.Aliases)
|
||||
m[s.VMID] = aliases
|
||||
}
|
||||
for vmID, aliases := range fxVMAliases {
|
||||
cp := make([]string, len(aliases))
|
||||
copy(cp, aliases)
|
||||
m[vmID] = cp
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/utxo/bls12381fx"
|
||||
"github.com/luxfi/utxo/ed25519fx"
|
||||
"github.com/luxfi/utxo/mldsafx"
|
||||
"github.com/luxfi/utxo/nftfx"
|
||||
"github.com/luxfi/utxo/propertyfx"
|
||||
"github.com/luxfi/utxo/schnorrfx"
|
||||
"github.com/luxfi/utxo/secp256k1fx"
|
||||
"github.com/luxfi/utxo/secp256r1fx"
|
||||
"github.com/luxfi/utxo/slhdsafx"
|
||||
)
|
||||
|
||||
// TestChainAliasesRegistryParity asserts that the registry-derived
|
||||
// {P,X,C,...}ChainAliases vars hold the same slices as the legacy
|
||||
// hand-typed literals. If this test ever fails, the registry data
|
||||
// drifted from the legacy chain alias contract — either rebrand on
|
||||
// purpose (and update legacy literals here) or fix the Registry row.
|
||||
func TestChainAliasesRegistryParity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
got []string
|
||||
legacy []string
|
||||
}{
|
||||
{"P", PChainAliases, []string{"P", "platform"}},
|
||||
{"X", XChainAliases, []string{"X", "xvm"}},
|
||||
{"C", CChainAliases, []string{"C", "evm"}},
|
||||
{"D", DChainAliases, []string{"D", "dex", "dexvm"}},
|
||||
{"Q", QChainAliases, []string{"Q", "quantum", "quantumvm", "pq"}},
|
||||
{"A", AChainAliases, []string{"A", "attest", "ai", "aivm"}},
|
||||
{"B", BChainAliases, []string{"B", "bridge", "bridgevm"}},
|
||||
{"T", TChainAliases, []string{"T", "threshold", "thresholdvm", "mpc"}},
|
||||
{"Z", ZChainAliases, []string{"Z", "zk", "zkvm"}},
|
||||
{"G", GChainAliases, []string{"G", "graph", "graphvm", "dgraph"}},
|
||||
{"K", KChainAliases, []string{"K", "key", "keyvm"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.True(t,
|
||||
reflect.DeepEqual(tt.got, tt.legacy),
|
||||
"%sChainAliases drift: got %v, legacy %v", tt.name, tt.got, tt.legacy,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestVMAliasesRegistryParity asserts that VMAliases holds the same
|
||||
// (VMID -> alias set) the legacy literal map encoded. Alias order is
|
||||
// not load-bearing (the alias manager treats each entry as a name
|
||||
// mapping), so this test compares sorted alias sets.
|
||||
func TestVMAliasesRegistryParity(t *testing.T) {
|
||||
legacy := map[ids.ID][]string{
|
||||
constants.PlatformVMID: {"platform"},
|
||||
constants.XVMID: {"xvm"},
|
||||
constants.EVMID: {"evm"},
|
||||
constants.DexVMID: {"dexvm", "dex"},
|
||||
constants.QuantumVMID: {"quantumvm", "quantum", "pq"},
|
||||
constants.AIVMID: {"aivm", "attest", "ai"},
|
||||
constants.BridgeVMID: {"bridgevm", "bridge"},
|
||||
constants.ThresholdVMID: {"thresholdvm", "threshold", "mpc"},
|
||||
constants.ZKVMID: {"zkvm", "zk"},
|
||||
constants.GraphVMID: {"graphvm", "graph", "dgraph"},
|
||||
constants.KeyVMID: {"keyvm", "key"},
|
||||
secp256k1fx.ID: {"secp256k1fx"},
|
||||
nftfx.ID: {"nftfx"},
|
||||
propertyfx.ID: {"propertyfx"},
|
||||
mldsafx.ID: {"mldsafx"},
|
||||
slhdsafx.ID: {"slhdsafx"},
|
||||
ed25519fx.ID: {"ed25519fx"},
|
||||
secp256r1fx.ID: {"secp256r1fx"},
|
||||
schnorrfx.ID: {"schnorrfx"},
|
||||
bls12381fx.ID: {"bls12381fx"},
|
||||
}
|
||||
|
||||
require.Equal(t, len(legacy), len(VMAliases), "VMAliases entry count drift")
|
||||
|
||||
for vmID, want := range legacy {
|
||||
got, ok := VMAliases[vmID]
|
||||
require.True(t, ok, "VMAliases missing entry for VMID %s", vmID)
|
||||
|
||||
wantSorted := append([]string{}, want...)
|
||||
gotSorted := append([]string{}, got...)
|
||||
sort.Strings(wantSorted)
|
||||
sort.Strings(gotSorted)
|
||||
require.Equal(t, wantSorted, gotSorted, "VMAliases[%s] alias-set drift: got %v, want %v", vmID, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVMAliasesMapIsolation asserts that mutating the returned map's
|
||||
// slices does not corrupt the underlying Registry. Each call must
|
||||
// return fresh copies.
|
||||
func TestVMAliasesMapIsolation(t *testing.T) {
|
||||
a := VMAliasesMap()
|
||||
b := VMAliasesMap()
|
||||
|
||||
// Mutate a's slice for the X-Chain.
|
||||
a[constants.XVMID][0] = "MUTATED"
|
||||
|
||||
require.NotEqual(t, "MUTATED", b[constants.XVMID][0],
|
||||
"VMAliasesMap returns shared slices — mutation leaked across calls")
|
||||
require.NotEqual(t, "MUTATED", XChainAliases[1],
|
||||
"VMAliasesMap returns slices aliased with package vars")
|
||||
}
|
||||
|
||||
// TestAliasesForUnknown returns nil for letters that have no row in
|
||||
// Registry — used so callers don't accidentally register a phantom
|
||||
// chain via a typo.
|
||||
func TestAliasesForUnknown(t *testing.T) {
|
||||
require.Nil(t, AliasesFor(""))
|
||||
require.Nil(t, AliasesFor("?"))
|
||||
require.Nil(t, AliasesFor("XX"))
|
||||
}
|
||||
@@ -7,12 +7,11 @@ module github.com/luxfi/node
|
||||
//
|
||||
// - If updating between minor versions (e.g. 1.23.x -> 1.24.x):
|
||||
// - Consider updating the version of golangci-lint (in scripts/lint.sh).
|
||||
go 1.26.2
|
||||
go 1.26.3
|
||||
|
||||
exclude github.com/luxfi/geth v1.16.1
|
||||
|
||||
require (
|
||||
connectrpc.com/connect v1.19.1
|
||||
github.com/DataDog/zstd v1.5.7 // indirect
|
||||
github.com/StephenButtolph/canoto v0.17.3
|
||||
github.com/btcsuite/btcd/btcutil v1.1.6
|
||||
@@ -27,18 +26,18 @@ require (
|
||||
github.com/huin/goupnp v1.3.0
|
||||
github.com/jackpal/gateway v1.1.1
|
||||
github.com/jackpal/go-nat-pmp v1.0.2
|
||||
github.com/luxfi/consensus v1.23.2
|
||||
github.com/luxfi/crypto v1.18.3
|
||||
github.com/luxfi/consensus v1.25.13
|
||||
github.com/luxfi/crypto v1.19.17
|
||||
github.com/luxfi/database v1.18.3
|
||||
github.com/luxfi/ids v1.2.9
|
||||
github.com/luxfi/ids v1.2.13
|
||||
github.com/luxfi/keychain v1.0.2
|
||||
github.com/luxfi/log v1.4.1
|
||||
github.com/luxfi/math v1.4.0
|
||||
github.com/luxfi/metric v1.5.1
|
||||
github.com/luxfi/math v1.4.1
|
||||
github.com/luxfi/metric v1.5.7
|
||||
github.com/luxfi/mock v0.1.1
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/onsi/ginkgo/v2 v2.28.1
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/pires/go-proxyproto v0.11.0
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
@@ -52,24 +51,24 @@ require (
|
||||
github.com/supranational/blst v0.3.16 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
|
||||
github.com/thepudds/fzgen v0.4.3
|
||||
go.opentelemetry.io/otel v1.42.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0
|
||||
go.opentelemetry.io/otel/sdk v1.40.0
|
||||
go.opentelemetry.io/otel/trace v1.42.0
|
||||
go.opentelemetry.io/otel v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0 // indirect
|
||||
go.uber.org/goleak v1.3.0
|
||||
go.uber.org/mock v0.6.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect
|
||||
golang.org/x/mod v0.34.0
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/mod v0.36.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.43.0
|
||||
golang.org/x/tools v0.45.0
|
||||
gonum.org/v1/gonum v0.17.0
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/grpc v1.80.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -82,7 +81,6 @@ require (
|
||||
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect
|
||||
github.com/cockroachdb/redact v1.1.8 // indirect
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
@@ -112,49 +110,51 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
connectrpc.com/grpcreflect v1.3.0
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
github.com/consensys/gnark-crypto v0.20.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/golang/mock v1.7.0-rc.1
|
||||
github.com/luxfi/accel v1.0.9
|
||||
github.com/luxfi/api v1.0.10
|
||||
github.com/luxfi/accel v1.1.9
|
||||
github.com/luxfi/api v1.0.11
|
||||
github.com/luxfi/atomic v1.0.0
|
||||
github.com/luxfi/chains v1.2.1
|
||||
github.com/luxfi/chains v1.2.3
|
||||
github.com/luxfi/compress v0.0.5
|
||||
github.com/luxfi/constants v1.5.2
|
||||
github.com/luxfi/constants v1.5.7
|
||||
github.com/luxfi/container v0.0.4
|
||||
github.com/luxfi/filesystem v0.0.1
|
||||
github.com/luxfi/genesis v1.9.2
|
||||
github.com/luxfi/geth v1.16.87
|
||||
github.com/luxfi/genesis v1.13.8
|
||||
github.com/luxfi/genesis/pkg/genesis/security v1.13.8
|
||||
github.com/luxfi/geth v1.16.98
|
||||
github.com/luxfi/go-bip39 v1.1.2
|
||||
github.com/luxfi/lattice/v7 v7.1.0
|
||||
github.com/luxfi/keys v1.1.0
|
||||
github.com/luxfi/lattice/v7 v7.1.4
|
||||
github.com/luxfi/math/safe v0.0.1
|
||||
github.com/luxfi/net v0.0.4
|
||||
github.com/luxfi/p2p v1.19.2
|
||||
github.com/luxfi/resource v0.0.1
|
||||
github.com/luxfi/rpc v1.0.2
|
||||
github.com/luxfi/runtime v1.0.1
|
||||
github.com/luxfi/sdk v1.16.52
|
||||
github.com/luxfi/runtime v1.1.0
|
||||
github.com/luxfi/sdk v1.16.60
|
||||
github.com/luxfi/sys v0.0.0-20260110090042-50187ec5ffd8
|
||||
github.com/luxfi/timer v1.0.2
|
||||
github.com/luxfi/units v1.0.0
|
||||
github.com/luxfi/utils v1.1.4
|
||||
github.com/luxfi/utxo v0.3.0
|
||||
github.com/luxfi/validators v1.0.0
|
||||
github.com/luxfi/utxo v0.3.2
|
||||
github.com/luxfi/validators v1.2.0
|
||||
github.com/luxfi/vm v1.0.40
|
||||
github.com/luxfi/warp v1.18.5
|
||||
github.com/luxfi/warp v1.18.6
|
||||
github.com/luxfi/zap v0.7.2
|
||||
github.com/luxfi/zwing v0.5.2
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0
|
||||
go.uber.org/zap v1.27.1
|
||||
)
|
||||
|
||||
@@ -163,28 +163,32 @@ require (
|
||||
filippo.io/hpke v0.4.0 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.6 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||
github.com/btcsuite/btcutil v1.0.2 // indirect
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/grandcat/zeroconf v1.0.0 // indirect
|
||||
github.com/gtank/merlin v0.1.1 // indirect
|
||||
github.com/gtank/ristretto255 v0.2.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/luxfi/age v1.4.0 // indirect
|
||||
github.com/luxfi/age v1.5.0 // indirect
|
||||
github.com/luxfi/ai v0.1.0 // indirect
|
||||
github.com/luxfi/config v1.1.2 // indirect
|
||||
github.com/luxfi/evm v0.8.49 // indirect
|
||||
github.com/luxfi/keys v1.0.8 // indirect
|
||||
github.com/luxfi/lens v0.1.3 // indirect
|
||||
github.com/luxfi/netrunner v1.18.1 // indirect
|
||||
github.com/luxfi/corona v0.7.6 // indirect
|
||||
github.com/luxfi/crypto/ipa v1.2.4 // indirect
|
||||
github.com/luxfi/kms v1.11.2 // indirect
|
||||
github.com/luxfi/lens v0.1.4 // indirect
|
||||
github.com/luxfi/magnetar v1.2.0 // indirect
|
||||
github.com/luxfi/mdns v0.1.1 // indirect
|
||||
github.com/luxfi/oracle v0.1.1-0.20260429020431-76258cfcddf9 // indirect
|
||||
github.com/luxfi/pulsar v0.1.5 // indirect
|
||||
github.com/luxfi/pq v1.0.3 // indirect
|
||||
github.com/luxfi/protocol v0.0.4 // indirect
|
||||
github.com/luxfi/pulsar v1.1.2 // indirect
|
||||
github.com/luxfi/relay v0.0.0-20260429020048-c629fe160d3c // indirect
|
||||
github.com/luxfi/staking v1.1.0 // indirect
|
||||
github.com/luxfi/threshold v1.6.7 // indirect
|
||||
github.com/luxfi/threshold v1.9.7 // indirect
|
||||
github.com/luxfi/trace v0.1.4 // indirect
|
||||
github.com/luxfi/zapdb v1.8.0 // indirect
|
||||
github.com/luxfi/zwing v0.5.2 // indirect
|
||||
github.com/luxfi/zapdb v1.10.0 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
|
||||
github.com/minio/crc64nvme v1.1.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
@@ -192,12 +196,12 @@ require (
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
github.com/luxfi/concurrent v0.0.3
|
||||
github.com/luxfi/protocol v0.0.3 // indirect
|
||||
github.com/luxfi/proto v1.0.2
|
||||
github.com/luxfi/upgrade v1.0.0 // indirect
|
||||
github.com/luxfi/version v1.0.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
@@ -206,7 +210,6 @@ require (
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.2.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260311194731-d5b7577c683d // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect
|
||||
@@ -214,8 +217,7 @@ require (
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.7 // indirect
|
||||
github.com/ethereum/go-verkle v0.2.2 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/google/flatbuffers v25.12.19+incompatible // indirect
|
||||
@@ -228,7 +230,6 @@ require (
|
||||
github.com/luxfi/formatting v1.0.1
|
||||
github.com/luxfi/go-bip32 v1.0.2
|
||||
github.com/luxfi/math/big v0.1.0 // indirect
|
||||
github.com/luxfi/corona v0.2.0 // indirect
|
||||
github.com/luxfi/sampler v1.0.0 // indirect
|
||||
github.com/luxfi/tls v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
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=
|
||||
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
|
||||
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
|
||||
connectrpc.com/grpcreflect v1.3.0 h1:Y4V+ACf8/vOb1XOc251Qun7jMB75gCUNw6llvB9csXc=
|
||||
connectrpc.com/grpcreflect v1.3.0/go.mod h1:nfloOtCS8VUQOQ1+GTdFzVg2CJo4ZGaat8JIovCtDYs=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
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/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM=
|
||||
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/ChainSafe/go-schnorrkel v1.1.0 h1:rZ6EU+CZFCjB4sHUE1jIu8VDoB/wRKZxoe1tkcO71Wk=
|
||||
github.com/ChainSafe/go-schnorrkel v1.1.0/go.mod h1:ABkENxiP+cvjFiByMIZ9LYbRoNNLeBLiakC1XeTFxfE=
|
||||
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
|
||||
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
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/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260311194731-d5b7577c683d h1:EA+kZ8mxGb1W/ewiIBMzb/1gg5BiW1Fvr3r4qCUBJEg=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260311194731-d5b7577c683d/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||
github.com/StephenButtolph/canoto v0.17.3 h1:lvsnYD4b96vD1knnmp1xCmZqfYpY/jSeRozGdOfdvGI=
|
||||
github.com/StephenButtolph/canoto v0.17.3/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqHHSB6UwI9NnBFZnE=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
@@ -44,8 +40,6 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
|
||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
|
||||
@@ -53,6 +47,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -78,13 +74,13 @@ github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
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/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
|
||||
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo=
|
||||
github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
||||
@@ -117,8 +113,6 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.7 h1:aat3CuITdDbPC6pmEGRT0zJ5eOxzrZj8TJT5
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.7/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/ferranbt/fastssz v1.0.0 h1:9EXXYsracSqQRBQiHeaVsG/KQeYblPf40hsQPb9Dzk8=
|
||||
@@ -130,8 +124,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
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/getsentry/sentry-go v0.44.1 h1:/cPtrA5qB7uMRrhgSn9TYtcEF36auGP3Y6+ThvD/yaI=
|
||||
github.com/getsentry/sentry-go v0.44.1/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
|
||||
github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs=
|
||||
@@ -205,8 +199,8 @@ github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptR
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
|
||||
@@ -256,137 +250,118 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/luxfi/accel v1.0.9 h1:QpXaZQUll0IlOHV+YgYUbrWcJeOYNNGzkwjI8e091x0=
|
||||
github.com/luxfi/accel v1.0.9/go.mod h1:XvgcJLYsLObbKlrOgd3E2OJx5Oy5c/HnDlIewjr+Y+c=
|
||||
github.com/luxfi/accel v1.1.9 h1:Tsk6gXj2uKE19501bD0ajRYdeCHIlTGb6jYyLc+F8hc=
|
||||
github.com/luxfi/accel v1.1.9/go.mod h1:K00BcnLzEYMPHwCFq8Tf/450ApmTs9xBVvYOnofJCkc=
|
||||
github.com/luxfi/address v1.0.1 h1:Sc4keyuVzBIvHr7uVeYZf2/WY9YDGUgDi/iiWenj49g=
|
||||
github.com/luxfi/address v1.0.1/go.mod h1:5j3Eh66v9zvv1GbNdZwt+23krV8JlSDaRzmWZU8ZRM0=
|
||||
github.com/luxfi/age v1.4.0 h1:tU5q65RQSQdaVq64Z/DVUhiPQMoMPQT6pr41LsvG0AQ=
|
||||
github.com/luxfi/age v1.4.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
|
||||
github.com/luxfi/age v1.5.0 h1:G69HbSV4R3vKEH9B0CulnRaMdSdf4RalMgP8xKmxHeI=
|
||||
github.com/luxfi/age v1.5.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
|
||||
github.com/luxfi/ai v0.1.0 h1:PwTGob0GJivbdqNpUs82bvwcE8/qxyTokxfY7BZVPoo=
|
||||
github.com/luxfi/ai v0.1.0/go.mod h1:lTuY32dGjEJUgVheYuanlt1O0jHj0AZ09sUSpRsmH6M=
|
||||
github.com/luxfi/api v1.0.4 h1:5altGkSSk3zIsGzK/NPhRQe/It5QMHrAPgBVg2TMkK4=
|
||||
github.com/luxfi/api v1.0.4/go.mod h1:Znr2A+SDJBCZ7ofxeq0MswHI0gk0cJsIYJLcu3tT8JY=
|
||||
github.com/luxfi/api v1.0.5 h1:5+sepMbIBDzeIA0QTO66X/2D4SR8dKEQwQpdrmFmr+Q=
|
||||
github.com/luxfi/api v1.0.5/go.mod h1:5OFWvZF+PbyuvLCDyKKpXCA/xp+LxJ32yOhNEKwQFN0=
|
||||
github.com/luxfi/api v1.0.10 h1:tqtiCX8DcBsFG0JEhKy7eUNI+42+m2DoPjLjEh5TE5E=
|
||||
github.com/luxfi/api v1.0.10/go.mod h1:5OFWvZF+PbyuvLCDyKKpXCA/xp+LxJ32yOhNEKwQFN0=
|
||||
github.com/luxfi/api v1.0.11 h1:t4fzN9Ox/Vy5msW2WpSXq4xCAmBXXJS0oM+zIjM9IiQ=
|
||||
github.com/luxfi/api v1.0.11/go.mod h1:q3Hh3mmkvp3cnv3aqe2+gCtmmSAL/uFjuzrqGavQLNA=
|
||||
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
|
||||
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
|
||||
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/chains v1.2.0 h1:AOoJ9OeMzzdPZV1NmKHl4KxxCiRBgUHHERU5llVEuFY=
|
||||
github.com/luxfi/chains v1.2.0/go.mod h1:B5D2saO5i1HT1CvdWr4zfJRccMeSf02qHBf/1X2ZELE=
|
||||
github.com/luxfi/chains v1.2.1 h1:eeqekwYV8E1OTIGFwzKBcI+9JGNzm3pfg+56qUn3BTQ=
|
||||
github.com/luxfi/chains v1.2.1/go.mod h1:B5D2saO5i1HT1CvdWr4zfJRccMeSf02qHBf/1X2ZELE=
|
||||
github.com/luxfi/chains v1.2.3 h1:d/xrJRuLYc/AkeugR4kRTwlYHudh1dfqjIaHGZPfIcI=
|
||||
github.com/luxfi/chains v1.2.3/go.mod h1:aofU2aS8s5tHiD8VVQydcYStQCpR356f1UPHGlNi67U=
|
||||
github.com/luxfi/codec v1.1.4 h1:Yl8ZalMNkqo7cD6R9AjczAajkLOmsjyZ9+DASVYHrvg=
|
||||
github.com/luxfi/codec v1.1.4/go.mod h1:oGQ3j6E8c2P0pL0irYtWkrB1hmDUFIE0puXHK4gV5KI=
|
||||
github.com/luxfi/compress v0.0.5 h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
|
||||
github.com/luxfi/compress v0.0.5/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
|
||||
github.com/luxfi/concurrent v0.0.3 h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
|
||||
github.com/luxfi/concurrent v0.0.3/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
|
||||
github.com/luxfi/config v1.1.2 h1:iCUewwm7oT7ckRNyQkrVHZ5Ge+l+FadciGX/zyaPo/k=
|
||||
github.com/luxfi/config v1.1.2/go.mod h1:z6t0a5pGpQz2uDW2qJPLX5fZ/eWbpiNa51gBc63ebFk=
|
||||
github.com/luxfi/consensus v1.22.85 h1:S/XpPF+eDapbZb4AIu0Vr1GXzg/sAxjrAff2M2mkOx0=
|
||||
github.com/luxfi/consensus v1.22.85/go.mod h1:y6y+bVjvpDLkkTBCCKLcch6R4PRW8EA+lTfr+6sB6F8=
|
||||
github.com/luxfi/consensus v1.23.0 h1:Jp/SuhDfLvbdXqMCi7NktWQTDXrKPbStEvIQjXsM3+g=
|
||||
github.com/luxfi/consensus v1.23.0/go.mod h1:HufC4lEizSDbpG6JBzeg5Z1ZSpZi+1/cL4/PrJr4FEI=
|
||||
github.com/luxfi/consensus v1.23.1 h1:JWJunRWlr2bwBVl3lfeMAmgnuibhlJS66bTM9ftcfxI=
|
||||
github.com/luxfi/consensus v1.23.1/go.mod h1:ZkD2ART+vXh9DqWJqqYAV+U0BA66DtQGFwpQbnP46pE=
|
||||
github.com/luxfi/consensus v1.23.2 h1:Edr/AhBbJH9RXwplCn65jYYb73bxRqbzKF6orpYYyfc=
|
||||
github.com/luxfi/consensus v1.23.2/go.mod h1:16Q5zhPY09mhjEMOetd6QMzf0ZFqGSt6K5Fy7V/BTE8=
|
||||
github.com/luxfi/constants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
|
||||
github.com/luxfi/constants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
|
||||
github.com/luxfi/constants v1.5.2 h1:fwQepO6viKAwf8ACnP9YV0TVUxndZuZM7U/aKcqe9EM=
|
||||
github.com/luxfi/constants v1.5.2/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
|
||||
github.com/luxfi/consensus v1.25.13 h1:hqbFq6W+oRvCrovj6Yu1zOsJNiRymHpjGdqZT1LvZUg=
|
||||
github.com/luxfi/consensus v1.25.13/go.mod h1:7/tlpy+byv2tP7YZSMG+XtS8C2bbz3qpB4H8jxXhHxk=
|
||||
github.com/luxfi/constants v1.5.7 h1:a1tCMdxd+pClPMIPOaI9vcYGNy6cQIc2rubac2Trc0k=
|
||||
github.com/luxfi/constants v1.5.7/go.mod h1:z+Wc7skybZAA+xuBWNcmtv402S/BFqixL+FiSQXPg8U=
|
||||
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/crypto v1.18.1 h1:Qaw8hSK0unIOAH6TRvu0s3JfoJWcg6zTf5eZ5adgKic=
|
||||
github.com/luxfi/crypto v1.18.1/go.mod h1:QhSdEEk3xmgR5a97/nkXB/n21ZsI1Iv7tcbqQaNM5zs=
|
||||
github.com/luxfi/crypto v1.18.3 h1:f6mBUHNjZl9/9nXPSqjq+oJttrPl8slvcZ49Vi7jKw4=
|
||||
github.com/luxfi/crypto v1.18.3/go.mod h1:qBtCJDT6p6aNa2hMtPxYwC688Bnsb3Amso8q09GsMls=
|
||||
github.com/luxfi/database v1.18.2 h1:r7jU8s4jaN0NpzWB0x91yyaL4CoCe0SxWEvxJGhJ7rE=
|
||||
github.com/luxfi/database v1.18.2/go.mod h1:PW0oGujt165mYg7j/lctVbnfsUW6QLfIAgjyQdS66SM=
|
||||
github.com/luxfi/corona v0.7.6 h1:CJP6smygD55dL0HHkKkWryL9H24a+wXvs+L+WchK7Nc=
|
||||
github.com/luxfi/corona v0.7.6/go.mod h1:4aD7+ZqnlZ2aVuU/DBQ5aspIagv5ux45LW2sJ4+siY8=
|
||||
github.com/luxfi/crypto v1.19.17 h1:l2LLu7UFyICtJVfraLDLRi+lFGiDXKHSL18M9/m1gsQ=
|
||||
github.com/luxfi/crypto v1.19.17/go.mod h1:INjdZtke85k8hX/QAmTMAY8bbZ4gzGZQLqURg3xf6Gk=
|
||||
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/database v1.18.3 h1:gg+xwhKUxXa7fDoOD8IS91E71QqoEtcDCl2nfS61Jgg=
|
||||
github.com/luxfi/database v1.18.3/go.mod h1:sv0pYCGKlK1aNJTICxFUDpVWCJTigoLlshHmV/1pg7c=
|
||||
github.com/luxfi/evm v0.8.49 h1:xBiGibL6v1Gm02EIHVtvGvAIGVdPUY0kgsX8MDynApI=
|
||||
github.com/luxfi/evm v0.8.49/go.mod h1:fgtQq7WGR5EpaqJTdYn//EqzAZDvBH9OKkPYS7s4oMI=
|
||||
github.com/luxfi/filesystem v0.0.1 h1:VZ6xMFKaAPBW/ddlMsDnI2G0VU1lV5rYaVcW5d+KwEY=
|
||||
github.com/luxfi/filesystem v0.0.1/go.mod h1:OQVSU6XNwqrr1AI+MqkID2taHUclx7NYmmr3svgttec=
|
||||
github.com/luxfi/formatting v1.0.1 h1:ZnE1rAdEUds9yAegdVdGDOBGN6hLMPOv6E03Fp8IEYo=
|
||||
github.com/luxfi/formatting v1.0.1/go.mod h1:mYzNf5DJOiqSSKUPzNj5dKy4tstFbN3pZlkI5716eKc=
|
||||
github.com/luxfi/genesis v1.8.2 h1:jFFMDO/yyzq1KzL5FaXzkDV84BIFt72446jP56jPdcQ=
|
||||
github.com/luxfi/genesis v1.8.2/go.mod h1:k26ZZvzES8e9cep9oWUDggBX+6oa5bnIQ7MmQP7dm4A=
|
||||
github.com/luxfi/genesis v1.9.2 h1:DMcFVLebRgcA+ElTbxAqaFOOMvXEU7UsBE8yBYGLum0=
|
||||
github.com/luxfi/genesis v1.9.2/go.mod h1:k26ZZvzES8e9cep9oWUDggBX+6oa5bnIQ7MmQP7dm4A=
|
||||
github.com/luxfi/geth v1.16.86-0.20260413014255-3e903c3d2e06 h1:NAIcfesKGSITw/mYTQpq62igXqUiDR7gekx8rQTKBmQ=
|
||||
github.com/luxfi/geth v1.16.86-0.20260413014255-3e903c3d2e06/go.mod h1:OdpR6OnAsYROywc8wm7W0lPn1z06DjgVmRL/F8dz5Kw=
|
||||
github.com/luxfi/geth v1.16.87 h1:UDjMRrseg/D5a3NDygdJXMkfJcHNOZXzcMcCn/AhIV8=
|
||||
github.com/luxfi/geth v1.16.87/go.mod h1:E5YcGOW9jB6Rgo4LB7NlTZxvkPzIoh1w7q56O63Zf1g=
|
||||
github.com/luxfi/genesis v1.13.8 h1:oddTmow0gEX/q/FrLQv77jXuSePCFRyMUvT9Pr3WnCw=
|
||||
github.com/luxfi/genesis v1.13.8/go.mod h1:iwXcnFHY997cWUKzyP4SCBl7o493SYHQniy3QViZOAg=
|
||||
github.com/luxfi/genesis/pkg/genesis/security v1.13.8 h1:9ier0p55ErSpoXHRJpZ04WV5HwwMB1uDrU7PHGBKG2U=
|
||||
github.com/luxfi/genesis/pkg/genesis/security v1.13.8/go.mod h1:DzU+GYUFv12ja4Vc46bWKNBBmNYbcow3u/DASx4wpfI=
|
||||
github.com/luxfi/geth v1.16.98 h1:w187TtKuGStf3tm2bshuHVKBv2Frjx0lT54kQVXyNHA=
|
||||
github.com/luxfi/geth v1.16.98/go.mod h1:6kEzSExdk9CPQDPXALt6P3HfQqBq7KF1Jrrv9gBpxbU=
|
||||
github.com/luxfi/go-bip32 v1.0.2 h1:7vFbb+Wr4Z499q2tuCLdd7wWjtn8sH+HWBlx76mhH9Y=
|
||||
github.com/luxfi/go-bip32 v1.0.2/go.mod h1:bc7/LXDKAJQZ/F0Xjf5yXaTZxY9/ssLb4FC+Hxn/cDk=
|
||||
github.com/luxfi/go-bip39 v1.1.2 h1:p+wLMPGs6MLQh7q0YIsmy2EhHL7LHiELEGTJko6t/Jg=
|
||||
github.com/luxfi/go-bip39 v1.1.2/go.mod h1:96de9VkR2kY/ASAnhMtvt3TSh+PZkAFAngNj0GjRGDo=
|
||||
github.com/luxfi/ids v1.2.9 h1:+yjdhXW99drnd2Zlp1u/p8k3G23W3/1btJQ4ogHawUI=
|
||||
github.com/luxfi/ids v1.2.9/go.mod h1:khJOEdOPxd22yn0jcVrnbX1ADa0GHn5Y74gvCzN5BYc=
|
||||
github.com/luxfi/ids v1.2.13 h1:lEotq0WUpMLcc/5X5MbE3n73jdfNo8IlFvTDfUXg+9A=
|
||||
github.com/luxfi/ids v1.2.13/go.mod h1:QWjJvggX/nRkmrz1AGSaF2Y6mnOvz3g7lFOzs3Zd+JM=
|
||||
github.com/luxfi/keychain v1.0.2 h1:uQgmjs37/VBIALEiYrrszTpxvtqr07/YvS9TnmxGafs=
|
||||
github.com/luxfi/keychain v1.0.2/go.mod h1:q/4ULgZBlstKkwzOzG/0T6y73BDPgnkrcibbJyTvmbU=
|
||||
github.com/luxfi/keys v1.0.8 h1:fANl6qZInndyCcXHAWiHTQ50LHhU8pJlF0Ix3phTo4A=
|
||||
github.com/luxfi/keys v1.0.8/go.mod h1:XbSCtgLyzEEVz2oZsYxnhRrNTGBxqrAm2Zu0uu06+qA=
|
||||
github.com/luxfi/lattice/v7 v7.0.0 h1:d1vgan6mlb2KtwYfPc1g69uxVYaoVYZmlrIm4aCO88w=
|
||||
github.com/luxfi/lattice/v7 v7.0.0/go.mod h1:PFDdOkuGTQ0cbJMbKojzEJMGWUQmZW+wK9/wJ9F9fOs=
|
||||
github.com/luxfi/lattice/v7 v7.1.0 h1:mr3HvN6olNTS2LT/xAW/JBhTqfvpsGmsopDMeR7BSJs=
|
||||
github.com/luxfi/lattice/v7 v7.1.0/go.mod h1:IaaUN+3ysnBG4BA8ILRYG0j80+qtYDP4C5lkaDb2pDE=
|
||||
github.com/luxfi/lens v0.1.3 h1:JTk/AjhHdKaiJ6Z7ZNazqnRfbgRijUPIxHamKW7b/N8=
|
||||
github.com/luxfi/lens v0.1.3/go.mod h1:XHpPOSgkT0ZFwfwp0t70Tvs5b6rjGyo1SNDEIDtfrqw=
|
||||
github.com/luxfi/keys v1.1.0 h1:a4UkVVg6G09XC7vPtXKxEGwVt50GNPjEvq2pkjYZW2k=
|
||||
github.com/luxfi/keys v1.1.0/go.mod h1:U3tZNDmv3nXkPoZwLtq9RNjwyN0XyoN29worigfT+c0=
|
||||
github.com/luxfi/kms v1.11.2 h1:bIx1uq8dTnUFVp7JtCN5W5M91KyZX9oWr8OEzoWLQDQ=
|
||||
github.com/luxfi/kms v1.11.2/go.mod h1:k91I3fULpJHicaWa2YESthHtHv3XwGlJauW/dcvB6WM=
|
||||
github.com/luxfi/lattice/v7 v7.1.4 h1:hQR02M6cHTAV5+joOPi9gb9Gm+z/hKJnhJF4IlciIJs=
|
||||
github.com/luxfi/lattice/v7 v7.1.4/go.mod h1:DmIQFi3mJiehVsR235l1NKYEU0JhU649OX5p7gMEW2c=
|
||||
github.com/luxfi/lens v0.1.4 h1:goGjGDXx2BNdjzXDunL5QT8elK2ZyCcc0z8TAbtWYrg=
|
||||
github.com/luxfi/lens v0.1.4/go.mod h1:mL+G8IK+9L41d78/2FYRgfhEzAjcr5+VEXB8SGuHbus=
|
||||
github.com/luxfi/log v1.4.1 h1:rIfFRodb9jrD/w7KayaUk0Oc+37PaQQdKEEMJCjR8gw=
|
||||
github.com/luxfi/log v1.4.1/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72jk=
|
||||
github.com/luxfi/math v1.4.0 h1:/sb7Grw3hfO+5INWAWdB95jTvCeXg8fSQxsxDzcFtd4=
|
||||
github.com/luxfi/math v1.4.0/go.mod h1:iW0FOCC8qF2mPE+MakG780CAHA83848lb1L04thA1Pg=
|
||||
github.com/luxfi/magnetar v1.2.0 h1:bsxHmBnJiswc/A6ElQ0pWz5g6ogqewIEKKqR26VgizA=
|
||||
github.com/luxfi/magnetar v1.2.0/go.mod h1:7J9YP9jByWbwCjssMFJNUkTU8tcPlSUoVSSiYShtvFs=
|
||||
github.com/luxfi/math v1.4.1 h1:1t9bCCsEqnl9yIKrShlbs80DBKyYTWdnzkVfBqEeO7Q=
|
||||
github.com/luxfi/math v1.4.1/go.mod h1:QvbRxauQyE1w4lvbcLSe6c8yeJz2Zj1Bq1rayGgs2tA=
|
||||
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/math/safe v0.0.1 h1:GfSBINV9mOFgHzd32JbgfHSLhlNn0BwnP43rteYEosc=
|
||||
github.com/luxfi/math/safe v0.0.1/go.mod h1:EejrmOJHh03YAD8+Zww8cPcMR1K3Q2I7w1dX4sMloeo=
|
||||
github.com/luxfi/metric v1.5.1 h1:6tVarXMnNR3Xzud8FYUHjtXabTll3HI/OEqG0tgLAcI=
|
||||
github.com/luxfi/metric v1.5.1/go.mod h1:PkD4D4JoGuyKtfUkqPNYkrg9xKrJeNVZFdW/5XvAe/A=
|
||||
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.7 h1:LoSPEUpak2SLcynF+LT2cXjl9ECp4nY+Lia9zudmDv4=
|
||||
github.com/luxfi/metric v1.5.7/go.mod h1:CMguEhyuLi4YUWyXimJ+UHply99BDFrL0pxedB7rBqM=
|
||||
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/net v0.0.4 h1:z1d6Q5c9/79jb4vF0XwBBjlF5swH5NsgfaXA+Pgojq8=
|
||||
github.com/luxfi/net v0.0.4/go.mod h1:QvgHzCa767cVWtPpui0P7HW1IrA2+c++hhvaQ/t0yyw=
|
||||
github.com/luxfi/netrunner v1.18.1 h1:tiLpbxoKedoiBORATKZ/mW00zd7SEC/P+Q9KWDldn2M=
|
||||
github.com/luxfi/netrunner v1.18.1/go.mod h1:zBAG98SGjPdBpo2/ccPvHCzvTQVvayH/Q8xviNa0FnU=
|
||||
github.com/luxfi/oracle v0.1.1-0.20260429020431-76258cfcddf9 h1:y+foW/ZJuTEb/AUUaJGrll2PVWumTiydWZuYJV4bJ/k=
|
||||
github.com/luxfi/oracle v0.1.1-0.20260429020431-76258cfcddf9/go.mod h1:ueeF3b4SrhpCNb8hUYeP/kV7qgkp/BKKjsKbUhEjg40=
|
||||
github.com/luxfi/p2p v1.19.2 h1:uqZq7ofmEDbXlTkv1QThtci01Q+dmDkNmAPeORIyP8E=
|
||||
github.com/luxfi/p2p v1.19.2/go.mod h1:tI9Bt1R0ouvVtJvXG4e20GlGeV4AR230k4mFF9Vglzk=
|
||||
github.com/luxfi/precompile v0.5.12 h1:YiF8gaPVFjXESHC+44XVM1RkAyZFMb8iU/60Tfq3Kxs=
|
||||
github.com/luxfi/precompile v0.5.12/go.mod h1:YiTU6xMf2wmKM2LBzKctn82JhaesFDz4ysC+S864qCA=
|
||||
github.com/luxfi/precompile v0.5.14 h1:z8mlWv7yWqWczWp59qoxTp0B7l/a0HMtMBPk27MEKyg=
|
||||
github.com/luxfi/protocol v0.0.3 h1:Teytlu6Gbd0HqJXuDvV84ArqRabEFR40yDNNQUOCpVE=
|
||||
github.com/luxfi/protocol v0.0.3/go.mod h1:ZSA1i7f2SlN129UG7rUZ+doHvf5/mtiurJMqRwGB4M8=
|
||||
github.com/luxfi/pulsar v0.1.5 h1:Yi9zlYsSOrParueW3ngm3EUVAwZx6DhFsO3cFH3h+1k=
|
||||
github.com/luxfi/pulsar v0.1.5/go.mod h1:Zei7PZhYxEsfzY5Rg0iI73nMU0ngO8EpUhlOOC0Ys7g=
|
||||
github.com/luxfi/pq v1.0.3 h1:pFlQm1+5FuKTDUh2y/23bXWkN4I2Rc5iuxJypwDFFMs=
|
||||
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
|
||||
github.com/luxfi/precompile v0.5.27 h1:lEF5gXY8ny5xXp8Qg8PK0Y2RQlaRNOZeDU+wP6Sj24M=
|
||||
github.com/luxfi/precompile v0.5.27/go.mod h1:boKRJeoa4XEhCoylRnPI/0IGfBgm5bZnFNI9llDktWo=
|
||||
github.com/luxfi/proto v1.0.2 h1:kT/2c6M85nqJOzOZ9SYyZHCSGH32qexovMCB7ZPGk8g=
|
||||
github.com/luxfi/proto v1.0.2/go.mod h1:pZLKsCmhiPtmm3z7ezBbnsYT2m79q+cFSmzxvuBeANE=
|
||||
github.com/luxfi/protocol v0.0.4 h1:wf3JSyeNMEabOUG0vExtIAtjfpJAyHXlepGBwv5g9NE=
|
||||
github.com/luxfi/protocol v0.0.4/go.mod h1:9f35GLNlcHAz6LCvFBDZP5fTD2QnN0URXWGUOcD1JPU=
|
||||
github.com/luxfi/pulsar v1.1.2 h1:iPQlEpnVCggQSTZzUa9i/la2WLEN7fl7/4QVahGs9Ag=
|
||||
github.com/luxfi/pulsar v1.1.2/go.mod h1:U7tPleeAHJ9dZ61ymtstzLKKoZjxM2zFeGZ+RSjHyRw=
|
||||
github.com/luxfi/relay v0.0.0-20260429020048-c629fe160d3c h1:wiOLqdEEjIoq+Uubkm6F6GYQue+rzqnITScM3ohWGqI=
|
||||
github.com/luxfi/relay v0.0.0-20260429020048-c629fe160d3c/go.mod h1:P408Sn9NGwtcDOoGdkmWe0484dkWDvEvDDBM44Cv+s4=
|
||||
github.com/luxfi/resource v0.0.1 h1:mTh+ICWSy548GTUSSyx7V/X5dV18oEwxZeQEYGJQhD4=
|
||||
github.com/luxfi/resource v0.0.1/go.mod h1:wWpZktciYwIi6RNqA+fHwzmPrUJa7PRX7urfwT+spRE=
|
||||
github.com/luxfi/corona v0.2.0 h1:DbLMZmE//2T2wXXS/gEkKZrTbre9LD+7EE/tz5SQszU=
|
||||
github.com/luxfi/corona v0.2.0/go.mod h1:SZ+aDLUdfSAtaTRaaYzeDZ5DNQiLaOCelSaIGjL21R0=
|
||||
github.com/luxfi/rpc v1.0.2 h1:NLRcOYRW+io0d1d33RMkgOZea8nlhK09MbPgCXcU5wU=
|
||||
github.com/luxfi/rpc v1.0.2/go.mod h1:pgiHwMWgOuxYYIa0vsUBvrBI+Op6bhZ39guM9vtMUcE=
|
||||
github.com/luxfi/runtime v1.0.1 h1:cii3OsRiVSIl9jzfWFM6++62T1r6ZSGP+/3F0Ezhpqw=
|
||||
github.com/luxfi/runtime v1.0.1/go.mod h1:2hBKjzbEeE4dzrhUKH8dqkRgLEyiXz6GmuVusy3vJMs=
|
||||
github.com/luxfi/runtime v1.1.0 h1:6TrvzAmZVCTVbR1ebntHTO3/kVBaogPUSkxdDMnrTiw=
|
||||
github.com/luxfi/runtime v1.1.0/go.mod h1:Mfv2zlXqvfRFMS+/zXgG1TieyP9VnvtVzOGB437+o4Y=
|
||||
github.com/luxfi/sampler v1.0.0 h1:k8Sf6otW83w4pQp0jXLA+g3J/joB7w7SqXQsWmNTOV0=
|
||||
github.com/luxfi/sampler v1.0.0/go.mod h1:f96/ozlj9vFfZj+akLtrHn4VpulQahwB+MQQhpeIekk=
|
||||
github.com/luxfi/sdk v1.16.52 h1:X1rZrgzGTIGzeVMKx9I44/kSvYFPhFu3h2SQ9StpTxA=
|
||||
github.com/luxfi/sdk v1.16.52/go.mod h1:IEP/ej5HA/rCOFOMzv/LfG4oXl2pI9ly+MrpcbVguBg=
|
||||
github.com/luxfi/sdk v1.16.60 h1:MKIFpGAIQzbFADXNd6rV5KySlbegnvpDJyiNhtO7YZw=
|
||||
github.com/luxfi/sdk v1.16.60/go.mod h1:se4G9mlLc6WdjLQlydg6J3pFEeMlEH+MCO1puJ8sYYc=
|
||||
github.com/luxfi/staking v1.1.0 h1:oB1f86WG9duxONe6unISHpUlelnmVg8+BOg4qP83q6Y=
|
||||
github.com/luxfi/staking v1.1.0/go.mod h1:xwYacmreoKoy31ELT6QPno6b5M8q7bg4zJrmmt524X4=
|
||||
github.com/luxfi/sys v0.0.0-20260110090042-50187ec5ffd8 h1:6L5294QWwXzy9he6wAM+Dc39rV4iwyl9hex0k2pXPsg=
|
||||
github.com/luxfi/sys v0.0.0-20260110090042-50187ec5ffd8/go.mod h1:Y0UwjnEJYSbGM7IvJbKYLf8BWNVLfT8Oz00bEiDITHw=
|
||||
github.com/luxfi/threshold v1.5.5 h1:MgPMrHf7dryztiaOYaG+Z+Cqqfa79oyblp5EowDFJw4=
|
||||
github.com/luxfi/threshold v1.5.5/go.mod h1:JEnYrKvCRDwGUeuCsMJSO2FeokuZGVaOml1XD/L1Vgs=
|
||||
github.com/luxfi/threshold v1.6.7 h1:geHmdDAXVm+T9U5CSoUiduXhhRKRUbfKOd6dw+2rPzQ=
|
||||
github.com/luxfi/threshold v1.6.7/go.mod h1:UzUXp2Fbnlb45AwYiXu++RsMkky4wocpKvhCc+rRg7g=
|
||||
github.com/luxfi/threshold v1.9.7 h1:C0KcEhwHPiQehMKiKh22Hcq0jF4o1uxTY29dKaVAS/s=
|
||||
github.com/luxfi/threshold v1.9.7/go.mod h1:5LEo5ZcAvBxBkr8Neb3GMbHxVa7m58qbbmEZNMAnR7c=
|
||||
github.com/luxfi/timer v1.0.2 h1:g/odi0VQJIsrzdklJUG1thHZ/sGNnbIiVGcU6LctJm0=
|
||||
github.com/luxfi/timer v1.0.2/go.mod h1:SoaZwntYigUE3H6z1GV32YwP8QaSiAT0UiEv7iPugXg=
|
||||
github.com/luxfi/tls v1.0.3 h1:rK3nxSAxrUOOSHOZnKChwV4f6UJ+cfOl8KWJXAQx/SI=
|
||||
@@ -399,18 +374,20 @@ github.com/luxfi/upgrade v1.0.0 h1:mM6YXvU8VzoclA7IdtuE5bmnMuFquYxjKUUW84LJz54=
|
||||
github.com/luxfi/upgrade v1.0.0/go.mod h1:DZF7dO4erhUEKf907B2e65k4/vGkCPkUngpvIEUS3eI=
|
||||
github.com/luxfi/utils v1.1.4 h1:8OY0jXCkpp6OotN1Y++6DATJkvtEwzq2k0p56imJlAk=
|
||||
github.com/luxfi/utils v1.1.4/go.mod h1:c3yz1RjzrB+cs5GZm+q1T3/2cCKElO9vxm9yRRtgSEM=
|
||||
github.com/luxfi/utxo v0.3.0 h1:Ok9VrICkFTRKjf3oTOovIPbPwiRpEZQ/MEFw7gFxpPo=
|
||||
github.com/luxfi/utxo v0.3.0/go.mod h1:e1KQHhjYTpc0zugPbGuffezUoz2ZFvha5I3LoFB10/M=
|
||||
github.com/luxfi/validators v1.0.0 h1:zE1HJM1lfvC+v1Lalg+MUgkKBGFWnwTfOOmXraiNnpE=
|
||||
github.com/luxfi/validators v1.0.0/go.mod h1:QGppjtI/gqprbplWc10J+4OIK8UYWpv+53mfH2aKhy4=
|
||||
github.com/luxfi/utxo v0.3.2 h1:fez7ptBnAoRYA5FpRz/uFWY8hcZwd4X00UYLnxuJeXE=
|
||||
github.com/luxfi/utxo v0.3.2/go.mod h1:O8Ucl6Sj0nM7l3Y+uqzucVu309yHcRQY++rg9yXmmdo=
|
||||
github.com/luxfi/validators v1.2.0 h1:VygpiBqBAdGrfkb7xzE2yrVmnXaqE+hm8FLWdGXO7G8=
|
||||
github.com/luxfi/validators v1.2.0/go.mod h1:GYLulrNXAan23ZlX7sgWVbVnLpUexeB/m2qr2ymsXok=
|
||||
github.com/luxfi/version v1.0.1 h1:T/1KYWEMmsrNQk7pN7PFPAwh/7XbeX7cFAKLBqI37Sk=
|
||||
github.com/luxfi/version v1.0.1/go.mod h1:Y5fPkQ2DB0XRBCxgSPXp4ISzL1/jptKnmFknShRJCyg=
|
||||
github.com/luxfi/vm v1.0.40 h1:kA0V/9p1VdC88EjgbljWb8t+fl9qz5bCyF3IxnV20yg=
|
||||
github.com/luxfi/vm v1.0.40/go.mod h1:Y5WUKhT76PR6HnbUykHBWI2f5sjssrm5BL6/o7b6IfQ=
|
||||
github.com/luxfi/warp v1.18.5 h1:yXFCT+lnvzJHs8nVvfUCQ9wNvhtgbDGaXJkJeiwgKf4=
|
||||
github.com/luxfi/warp v1.18.5/go.mod h1:SFyC529HDvbP/TWRAdYQSyJUliMa5JKFRtBrTLEElp4=
|
||||
github.com/luxfi/zapdb v1.8.0 h1:E9hXMW1MTuS6kmDVs07j62gIq5qI5gZOdyzVfiBc1wg=
|
||||
github.com/luxfi/zapdb v1.8.0/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
|
||||
github.com/luxfi/warp v1.18.6 h1:+ly7mHz77ig4yUyLax32aAjk7tiQQ6eygzOSDNfYRbQ=
|
||||
github.com/luxfi/warp v1.18.6/go.mod h1:OBN23yiGl+E4qupkPHuetBIEqsI8q5leE8WJH8soFjY=
|
||||
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/zapdb v1.10.0 h1:1lLHEmkyC0BucnA/zjQYsMkUVxuEo2vQkEaQGjYfuuc=
|
||||
github.com/luxfi/zapdb v1.10.0/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
|
||||
github.com/luxfi/zwing v0.5.2 h1:2+nDKHVIdT8GvaKO79GuO3x/3DgJvX0gI32h4P+P6RU=
|
||||
github.com/luxfi/zwing v0.5.2/go.mod h1:8nixkEL3bhO2LrqVqhJ8WgT+QGUtnCPIQIhFQh9gQio=
|
||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||
@@ -421,6 +398,9 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
|
||||
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk=
|
||||
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
|
||||
@@ -436,8 +416,6 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw=
|
||||
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
github.com/montanaflynn/stats v0.8.2 h1:52wnefTJnPI5FoHif1DQh2soKRw0yYs+4AVyvtcZCH0=
|
||||
github.com/montanaflynn/stats v0.8.2/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/montanaflynn/stats v0.9.0 h1:tsBJ0RXwph9BmAuFoCmqGv6e8xa0MENQ8m0ptKq29mQ=
|
||||
github.com/montanaflynn/stats v0.9.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
@@ -483,8 +461,8 @@ github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQp
|
||||
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
|
||||
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
|
||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -585,24 +563,24 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
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.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -620,23 +598,23 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
@@ -645,8 +623,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
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/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=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -659,6 +637,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -676,10 +655,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -687,20 +664,19 @@ 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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
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=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#!/bin/bash
|
||||
# Import RLP blocks for subnet chains after deployment
|
||||
# Usage: ./import-subnet-rlp.sh [mainnet|testnet|both]
|
||||
# Import RLP blocks for chains after deployment
|
||||
# Usage: ./import-chain-rlp.sh [mainnet|testnet|both]
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Subnets must be deployed (run deploy-subnets.sh first)
|
||||
# - Nodes must be tracking the subnet chains
|
||||
# - Chains must be deployed (run deploy-chains.sh first)
|
||||
# - Nodes must be tracking the chains
|
||||
# - Blockchain IDs must be set in the node config
|
||||
|
||||
set -e
|
||||
@@ -37,7 +37,7 @@ import_rlp() {
|
||||
local ns="lux-$network"
|
||||
kubectl --context do-sfo3-lux-k8s exec -n "$ns" luxd-0 -- \
|
||||
wget -q -O /tmp/import.rlp "$rpc_url" 2>/dev/null || true
|
||||
echo "NOTE: For subnet chain import, you may need to use admin.importChain RPC"
|
||||
echo "NOTE: For chain import, you may need to use admin.importChain RPC"
|
||||
echo " or copy the RLP file to the node and import manually."
|
||||
}
|
||||
}
|
||||
@@ -46,20 +46,20 @@ TARGET="${1:-both}"
|
||||
|
||||
case "$TARGET" in
|
||||
mainnet)
|
||||
echo "=== Importing subnet RLP blocks to mainnet ==="
|
||||
echo "=== Importing chain RLP blocks to mainnet ==="
|
||||
import_rlp mainnet zoo-mainnet 200200 zoo-mainnet-200200.rlp "$MAINNET_RPC"
|
||||
import_rlp mainnet spc-mainnet 36911 spc-mainnet-36911.rlp "$MAINNET_RPC"
|
||||
;;
|
||||
testnet)
|
||||
echo "=== Importing subnet RLP blocks to testnet ==="
|
||||
echo "=== Importing chain RLP blocks to testnet ==="
|
||||
import_rlp testnet zoo-testnet 200201 zoo-testnet-200201.rlp "$TESTNET_RPC"
|
||||
;;
|
||||
both|all)
|
||||
echo "=== Importing subnet RLP blocks to mainnet ==="
|
||||
echo "=== Importing chain RLP blocks to mainnet ==="
|
||||
import_rlp mainnet zoo-mainnet 200200 zoo-mainnet-200200.rlp "$MAINNET_RPC"
|
||||
import_rlp mainnet spc-mainnet 36911 spc-mainnet-36911.rlp "$MAINNET_RPC"
|
||||
echo ""
|
||||
echo "=== Importing subnet RLP blocks to testnet ==="
|
||||
echo "=== Importing chain RLP blocks to testnet ==="
|
||||
import_rlp testnet zoo-testnet 200201 zoo-testnet-200201.rlp "$TESTNET_RPC"
|
||||
;;
|
||||
*)
|
||||
+1
-1
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
"github.com/luxfi/formatting"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/rpc"
|
||||
"github.com/luxfi/node/utils/json"
|
||||
"github.com/luxfi/rpc"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
|
||||
"github.com/luxfi/formatting"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/utils/json"
|
||||
"github.com/luxfi/rpc"
|
||||
"github.com/luxfi/utils"
|
||||
"github.com/luxfi/node/utils/json"
|
||||
)
|
||||
|
||||
type mockClient struct {
|
||||
|
||||
+1
-1
@@ -8,13 +8,13 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/runtime"
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/database/prefixdb"
|
||||
"github.com/luxfi/database/versiondb"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
nodeconsensus "github.com/luxfi/node/consensus"
|
||||
"github.com/luxfi/runtime"
|
||||
"github.com/luxfi/timer/mockable"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -15,9 +15,9 @@ import (
|
||||
"github.com/luxfi/database/prefixdb"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/node/server/http"
|
||||
"github.com/luxfi/node/chains"
|
||||
nodeconsensus "github.com/luxfi/node/consensus"
|
||||
"github.com/luxfi/node/server/http"
|
||||
"github.com/luxfi/node/utils/json"
|
||||
"github.com/luxfi/runtime"
|
||||
"github.com/luxfi/timer/mockable"
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ Each chain has one or more index. To see if a C-Chain block is accepted, for exa
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
To ensure historical data can be accessed, the `/ext/index/X/vtx` is still accessible, even though it is no longer populated with chain data since the Cortina activation. If you are using `V1.10.0` or higher, you need to migrate to using the `/ext/index/X/block` endpoint.
|
||||
To ensure historical data can be accessed, the `/ext/index/X/vtx` is still accessible, even though it is no longer populated with chain data since the X-Chain block linearization landed. If you are using `V1.10.0` or higher, you need to migrate to using the `/ext/index/X/block` endpoint.
|
||||
</Callout>
|
||||
|
||||
## Methods
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/luxfi/database"
|
||||
"github.com/luxfi/math/set"
|
||||
"github.com/luxfi/utils"
|
||||
|
||||
rpcdbpb "github.com/luxfi/node/proto/pb/rpcdb"
|
||||
)
|
||||
|
||||
var (
|
||||
_ database.Database = (*DatabaseClient)(nil)
|
||||
_ database.Batch = (*batch)(nil)
|
||||
_ database.Iterator = (*iterator)(nil)
|
||||
)
|
||||
|
||||
// DatabaseClient is an implementation of database that talks over RPC.
|
||||
type DatabaseClient struct {
|
||||
client rpcdbpb.DatabaseClient
|
||||
|
||||
closed utils.Atomic[bool]
|
||||
}
|
||||
|
||||
// NewClient returns a database instance connected to a remote database instance
|
||||
func NewClient(client rpcdbpb.DatabaseClient) *DatabaseClient {
|
||||
return &DatabaseClient{client: client}
|
||||
}
|
||||
|
||||
// Has attempts to return if the database has a key with the provided value.
|
||||
func (db *DatabaseClient) Has(key []byte) (bool, error) {
|
||||
resp, err := db.client.Has(context.Background(), &rpcdbpb.HasRequest{
|
||||
Key: key,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return resp.Has, ErrEnumToError[resp.Err]
|
||||
}
|
||||
|
||||
// Get attempts to return the value that was mapped to the key that was provided
|
||||
func (db *DatabaseClient) Get(key []byte) ([]byte, error) {
|
||||
resp, err := db.client.Get(context.Background(), &rpcdbpb.GetRequest{
|
||||
Key: key,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Value, ErrEnumToError[resp.Err]
|
||||
}
|
||||
|
||||
// Put attempts to set the value this key maps to
|
||||
func (db *DatabaseClient) Put(key, value []byte) error {
|
||||
resp, err := db.client.Put(context.Background(), &rpcdbpb.PutRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrEnumToError[resp.Err]
|
||||
}
|
||||
|
||||
// Delete attempts to remove any mapping from the key
|
||||
func (db *DatabaseClient) Delete(key []byte) error {
|
||||
resp, err := db.client.Delete(context.Background(), &rpcdbpb.DeleteRequest{
|
||||
Key: key,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrEnumToError[resp.Err]
|
||||
}
|
||||
|
||||
// NewBatch returns a new batch
|
||||
func (db *DatabaseClient) NewBatch() database.Batch {
|
||||
return &batch{db: db}
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewIterator() database.Iterator {
|
||||
return db.NewIteratorWithStartAndPrefix(nil, nil)
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewIteratorWithStart(start []byte) database.Iterator {
|
||||
return db.NewIteratorWithStartAndPrefix(start, nil)
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) NewIteratorWithPrefix(prefix []byte) database.Iterator {
|
||||
return db.NewIteratorWithStartAndPrefix(nil, prefix)
|
||||
}
|
||||
|
||||
// NewIteratorWithStartAndPrefix returns a new empty iterator
|
||||
func (db *DatabaseClient) NewIteratorWithStartAndPrefix(start, prefix []byte) database.Iterator {
|
||||
resp, err := db.client.NewIteratorWithStartAndPrefix(context.Background(), &rpcdbpb.NewIteratorWithStartAndPrefixRequest{
|
||||
Start: start,
|
||||
Prefix: prefix,
|
||||
})
|
||||
if err != nil {
|
||||
return &database.IteratorError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return newIterator(db, resp.Id)
|
||||
}
|
||||
|
||||
// Compact attempts to optimize the space utilization in the provided range
|
||||
func (db *DatabaseClient) Compact(start, limit []byte) error {
|
||||
resp, err := db.client.Compact(context.Background(), &rpcdbpb.CompactRequest{
|
||||
Start: start,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrEnumToError[resp.Err]
|
||||
}
|
||||
|
||||
// Close attempts to close the database
|
||||
func (db *DatabaseClient) Close() error {
|
||||
db.closed.Set(true)
|
||||
resp, err := db.client.Close(context.Background(), &rpcdbpb.CloseRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrEnumToError[resp.Err]
|
||||
}
|
||||
|
||||
// Sync flushes any pending writes to persistent storage.
|
||||
// For RPC databases, this delegates to the remote database's Sync.
|
||||
func (db *DatabaseClient) Sync() error {
|
||||
// RPC database delegates sync to the underlying database on the server side.
|
||||
// Most operations are already synchronous over RPC, so this is typically a no-op.
|
||||
// If the server implements a Sync RPC method, call it here.
|
||||
// For now, return nil as operations are synchronous.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DatabaseClient) HealthCheck(ctx context.Context) (interface{}, error) {
|
||||
health, err := db.client.HealthCheck(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return json.RawMessage(health.Details), nil
|
||||
}
|
||||
|
||||
// Backup is not supported over the RPC database client.
|
||||
func (db *DatabaseClient) Backup(_ io.Writer, _ uint64) (uint64, error) {
|
||||
return 0, errors.New("rpcdb: backup not supported")
|
||||
}
|
||||
|
||||
// Load is not supported over the RPC database client.
|
||||
func (db *DatabaseClient) Load(_ io.Reader) error {
|
||||
return errors.New("rpcdb: load not supported")
|
||||
}
|
||||
|
||||
type batch struct {
|
||||
database.BatchOps
|
||||
|
||||
db *DatabaseClient
|
||||
}
|
||||
|
||||
func (b *batch) Write() error {
|
||||
request := &rpcdbpb.WriteBatchRequest{}
|
||||
keySet := set.NewSet[string](len(b.Ops))
|
||||
for i := len(b.Ops) - 1; i >= 0; i-- {
|
||||
op := b.Ops[i]
|
||||
key := string(op.Key)
|
||||
if keySet.Contains(key) {
|
||||
continue
|
||||
}
|
||||
keySet.Add(key)
|
||||
|
||||
if op.Delete {
|
||||
request.Deletes = append(request.Deletes, &rpcdbpb.DeleteRequest{
|
||||
Key: op.Key,
|
||||
})
|
||||
} else {
|
||||
request.Puts = append(request.Puts, &rpcdbpb.PutRequest{
|
||||
Key: op.Key,
|
||||
Value: op.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := b.db.client.WriteBatch(context.Background(), request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrEnumToError[resp.Err]
|
||||
}
|
||||
|
||||
func (b *batch) Inner() database.Batch {
|
||||
return b
|
||||
}
|
||||
|
||||
type iterator struct {
|
||||
db *DatabaseClient
|
||||
id uint64
|
||||
|
||||
data []*rpcdbpb.PutRequest
|
||||
fetchedData chan []*rpcdbpb.PutRequest
|
||||
|
||||
errLock sync.RWMutex
|
||||
err error
|
||||
|
||||
reqUpdateError chan chan struct{}
|
||||
|
||||
once sync.Once
|
||||
onClose chan struct{}
|
||||
onClosed chan struct{}
|
||||
}
|
||||
|
||||
func newIterator(db *DatabaseClient, id uint64) *iterator {
|
||||
it := &iterator{
|
||||
db: db,
|
||||
id: id,
|
||||
fetchedData: make(chan []*rpcdbpb.PutRequest),
|
||||
reqUpdateError: make(chan chan struct{}),
|
||||
onClose: make(chan struct{}),
|
||||
onClosed: make(chan struct{}),
|
||||
}
|
||||
go it.fetch()
|
||||
return it
|
||||
}
|
||||
|
||||
// Invariant: fetch is the only thread with access to send requests to the
|
||||
// server's iterator. This is needed because iterators are not thread safe and
|
||||
// the server expects the client (us) to only ever issue one request at a time
|
||||
// for a given iterator id.
|
||||
func (it *iterator) fetch() {
|
||||
defer func() {
|
||||
resp, err := it.db.client.IteratorRelease(context.Background(), &rpcdbpb.IteratorReleaseRequest{
|
||||
Id: it.id,
|
||||
})
|
||||
if err != nil {
|
||||
it.setError(err)
|
||||
} else {
|
||||
it.setError(ErrEnumToError[resp.Err])
|
||||
}
|
||||
|
||||
close(it.fetchedData)
|
||||
close(it.onClosed)
|
||||
}()
|
||||
|
||||
for {
|
||||
resp, err := it.db.client.IteratorNext(context.Background(), &rpcdbpb.IteratorNextRequest{
|
||||
Id: it.id,
|
||||
})
|
||||
if err != nil {
|
||||
it.setError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(resp.Data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case it.fetchedData <- resp.Data:
|
||||
case onUpdated := <-it.reqUpdateError:
|
||||
it.updateError()
|
||||
close(onUpdated)
|
||||
continue
|
||||
case <-it.onClose:
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next attempts to move the iterator to the next element and returns if this
|
||||
// succeeded
|
||||
func (it *iterator) Next() bool {
|
||||
if it.db.closed.Get() {
|
||||
it.data = nil
|
||||
it.setError(database.ErrClosed)
|
||||
return false
|
||||
}
|
||||
if len(it.data) > 1 {
|
||||
it.data[0] = nil
|
||||
it.data = it.data[1:]
|
||||
return true
|
||||
}
|
||||
|
||||
it.data = <-it.fetchedData
|
||||
return len(it.data) > 0
|
||||
}
|
||||
|
||||
// Error returns any that occurred while iterating
|
||||
func (it *iterator) Error() error {
|
||||
if err := it.getError(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
onUpdated := make(chan struct{})
|
||||
select {
|
||||
case it.reqUpdateError <- onUpdated:
|
||||
<-onUpdated
|
||||
case <-it.onClosed:
|
||||
}
|
||||
|
||||
return it.getError()
|
||||
}
|
||||
|
||||
// Key returns the key of the current element
|
||||
func (it *iterator) Key() []byte {
|
||||
if len(it.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return it.data[0].Key
|
||||
}
|
||||
|
||||
// Value returns the value of the current element
|
||||
func (it *iterator) Value() []byte {
|
||||
if len(it.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return it.data[0].Value
|
||||
}
|
||||
|
||||
// Release frees any resources held by the iterator
|
||||
func (it *iterator) Release() {
|
||||
it.once.Do(func() {
|
||||
close(it.onClose)
|
||||
<-it.onClosed
|
||||
})
|
||||
}
|
||||
|
||||
func (it *iterator) updateError() {
|
||||
resp, err := it.db.client.IteratorError(context.Background(), &rpcdbpb.IteratorErrorRequest{
|
||||
Id: it.id,
|
||||
})
|
||||
if err != nil {
|
||||
it.setError(err)
|
||||
} else {
|
||||
it.setError(ErrEnumToError[resp.Err])
|
||||
}
|
||||
}
|
||||
|
||||
func (it *iterator) setError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
it.errLock.Lock()
|
||||
defer it.errLock.Unlock()
|
||||
|
||||
if it.err == nil {
|
||||
it.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (it *iterator) getError() error {
|
||||
it.errLock.RLock()
|
||||
defer it.errLock.RUnlock()
|
||||
|
||||
return it.err
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/luxfi/constants"
|
||||
"github.com/luxfi/database"
|
||||
|
||||
rpcdbpb "github.com/luxfi/node/proto/pb/rpcdb"
|
||||
)
|
||||
|
||||
const iterationBatchSize = 128 * constants.KiB
|
||||
|
||||
var errUnknownIterator = errors.New("unknown iterator")
|
||||
|
||||
// DatabaseServer is a database that is managed over RPC.
|
||||
type DatabaseServer struct {
|
||||
rpcdbpb.UnsafeDatabaseServer
|
||||
|
||||
db database.Database
|
||||
|
||||
// iteratorLock protects [nextIteratorID] and [iterators] from concurrent
|
||||
// modifications. Similarly to [batchLock], [iteratorLock] does not protect
|
||||
// the actual Iterator. Iterators are documented as not being safe for
|
||||
// concurrent use. Therefore, it is up to the client to respect this
|
||||
// invariant.
|
||||
iteratorLock sync.RWMutex
|
||||
nextIteratorID uint64
|
||||
iterators map[uint64]database.Iterator
|
||||
}
|
||||
|
||||
// NewServer returns a database instance that is managed remotely
|
||||
func NewServer(db database.Database) *DatabaseServer {
|
||||
return &DatabaseServer{
|
||||
db: db,
|
||||
iterators: make(map[uint64]database.Iterator),
|
||||
}
|
||||
}
|
||||
|
||||
// Has delegates the Has call to the managed database and returns the result
|
||||
func (db *DatabaseServer) Has(_ context.Context, req *rpcdbpb.HasRequest) (*rpcdbpb.HasResponse, error) {
|
||||
has, err := db.db.Has(req.Key)
|
||||
return &rpcdbpb.HasResponse{
|
||||
Has: has,
|
||||
Err: ErrorToErrEnum[err],
|
||||
}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// Get delegates the Get call to the managed database and returns the result
|
||||
func (db *DatabaseServer) Get(_ context.Context, req *rpcdbpb.GetRequest) (*rpcdbpb.GetResponse, error) {
|
||||
value, err := db.db.Get(req.Key)
|
||||
return &rpcdbpb.GetResponse{
|
||||
Value: value,
|
||||
Err: ErrorToErrEnum[err],
|
||||
}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// Put delegates the Put call to the managed database and returns the result
|
||||
func (db *DatabaseServer) Put(_ context.Context, req *rpcdbpb.PutRequest) (*rpcdbpb.PutResponse, error) {
|
||||
err := db.db.Put(req.Key, req.Value)
|
||||
return &rpcdbpb.PutResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// Delete delegates the Delete call to the managed database and returns the
|
||||
// result
|
||||
func (db *DatabaseServer) Delete(_ context.Context, req *rpcdbpb.DeleteRequest) (*rpcdbpb.DeleteResponse, error) {
|
||||
err := db.db.Delete(req.Key)
|
||||
return &rpcdbpb.DeleteResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// Compact delegates the Compact call to the managed database and returns the
|
||||
// result
|
||||
func (db *DatabaseServer) Compact(_ context.Context, req *rpcdbpb.CompactRequest) (*rpcdbpb.CompactResponse, error) {
|
||||
err := db.db.Compact(req.Start, req.Limit)
|
||||
return &rpcdbpb.CompactResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// Close delegates the Close call to the managed database and returns the result
|
||||
func (db *DatabaseServer) Close(context.Context, *rpcdbpb.CloseRequest) (*rpcdbpb.CloseResponse, error) {
|
||||
err := db.db.Close()
|
||||
return &rpcdbpb.CloseResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// HealthCheck performs a heath check against the underlying database.
|
||||
func (db *DatabaseServer) HealthCheck(ctx context.Context, _ *emptypb.Empty) (*rpcdbpb.HealthCheckResponse, error) {
|
||||
health, err := db.db.HealthCheck(ctx)
|
||||
if err != nil {
|
||||
return &rpcdbpb.HealthCheckResponse{}, err
|
||||
}
|
||||
|
||||
details, err := json.Marshal(health)
|
||||
return &rpcdbpb.HealthCheckResponse{
|
||||
Details: details,
|
||||
}, err
|
||||
}
|
||||
|
||||
// WriteBatch takes in a set of key-value pairs and atomically writes them to
|
||||
// the internal database
|
||||
func (db *DatabaseServer) WriteBatch(_ context.Context, req *rpcdbpb.WriteBatchRequest) (*rpcdbpb.WriteBatchResponse, error) {
|
||||
batch := db.db.NewBatch()
|
||||
for _, put := range req.Puts {
|
||||
if err := batch.Put(put.Key, put.Value); err != nil {
|
||||
return &rpcdbpb.WriteBatchResponse{
|
||||
Err: ErrorToErrEnum[err],
|
||||
}, ErrorToRPCError(err)
|
||||
}
|
||||
}
|
||||
for _, del := range req.Deletes {
|
||||
if err := batch.Delete(del.Key); err != nil {
|
||||
return &rpcdbpb.WriteBatchResponse{
|
||||
Err: ErrorToErrEnum[err],
|
||||
}, ErrorToRPCError(err)
|
||||
}
|
||||
}
|
||||
|
||||
err := batch.Write()
|
||||
return &rpcdbpb.WriteBatchResponse{
|
||||
Err: ErrorToErrEnum[err],
|
||||
}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// NewIteratorWithStartAndPrefix allocates an iterator and returns the iterator
|
||||
// ID
|
||||
func (db *DatabaseServer) NewIteratorWithStartAndPrefix(_ context.Context, req *rpcdbpb.NewIteratorWithStartAndPrefixRequest) (*rpcdbpb.NewIteratorWithStartAndPrefixResponse, error) {
|
||||
it := db.db.NewIteratorWithStartAndPrefix(req.Start, req.Prefix)
|
||||
|
||||
db.iteratorLock.Lock()
|
||||
defer db.iteratorLock.Unlock()
|
||||
|
||||
id := db.nextIteratorID
|
||||
db.iterators[id] = it
|
||||
db.nextIteratorID++
|
||||
return &rpcdbpb.NewIteratorWithStartAndPrefixResponse{Id: id}, nil
|
||||
}
|
||||
|
||||
// IteratorNext attempts to call next on the requested iterator
|
||||
func (db *DatabaseServer) IteratorNext(_ context.Context, req *rpcdbpb.IteratorNextRequest) (*rpcdbpb.IteratorNextResponse, error) {
|
||||
db.iteratorLock.RLock()
|
||||
it, exists := db.iterators[req.Id]
|
||||
db.iteratorLock.RUnlock()
|
||||
if !exists {
|
||||
return nil, errUnknownIterator
|
||||
}
|
||||
|
||||
var (
|
||||
size int
|
||||
data []*rpcdbpb.PutRequest
|
||||
)
|
||||
for size < iterationBatchSize && it.Next() {
|
||||
key := it.Key()
|
||||
value := it.Value()
|
||||
size += len(key) + len(value)
|
||||
|
||||
data = append(data, &rpcdbpb.PutRequest{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
return &rpcdbpb.IteratorNextResponse{Data: data}, nil
|
||||
}
|
||||
|
||||
// IteratorError attempts to report any errors that occurred during iteration
|
||||
func (db *DatabaseServer) IteratorError(_ context.Context, req *rpcdbpb.IteratorErrorRequest) (*rpcdbpb.IteratorErrorResponse, error) {
|
||||
db.iteratorLock.RLock()
|
||||
it, exists := db.iterators[req.Id]
|
||||
db.iteratorLock.RUnlock()
|
||||
if !exists {
|
||||
return nil, errUnknownIterator
|
||||
}
|
||||
err := it.Error()
|
||||
return &rpcdbpb.IteratorErrorResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
|
||||
}
|
||||
|
||||
// IteratorRelease attempts to release the resources allocated to an iterator
|
||||
func (db *DatabaseServer) IteratorRelease(_ context.Context, req *rpcdbpb.IteratorReleaseRequest) (*rpcdbpb.IteratorReleaseResponse, error) {
|
||||
db.iteratorLock.Lock()
|
||||
it, exists := db.iterators[req.Id]
|
||||
if !exists {
|
||||
db.iteratorLock.Unlock()
|
||||
return &rpcdbpb.IteratorReleaseResponse{}, nil
|
||||
}
|
||||
delete(db.iterators, req.Id)
|
||||
db.iteratorLock.Unlock()
|
||||
|
||||
err := it.Error()
|
||||
it.Release()
|
||||
return &rpcdbpb.IteratorReleaseResponse{Err: ErrorToErrEnum[err]}, ErrorToRPCError(err)
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/database/corruptabledb"
|
||||
"github.com/luxfi/database/dbtest"
|
||||
"github.com/luxfi/database/memdb"
|
||||
"github.com/luxfi/log"
|
||||
"github.com/luxfi/vm/rpc/grpcutils"
|
||||
|
||||
rpcdbpb "github.com/luxfi/node/proto/pb/rpcdb"
|
||||
)
|
||||
|
||||
type testDatabase struct {
|
||||
client *DatabaseClient
|
||||
server *memdb.Database
|
||||
}
|
||||
|
||||
func setupDB(t testing.TB) *testDatabase {
|
||||
require := require.New(t)
|
||||
|
||||
db := &testDatabase{
|
||||
server: memdb.New(),
|
||||
}
|
||||
|
||||
listener, err := grpcutils.NewListener()
|
||||
require.NoError(err)
|
||||
serverCloser := grpcutils.ServerCloser{}
|
||||
|
||||
server := grpcutils.NewServer()
|
||||
rpcdbpb.RegisterDatabaseServer(server, NewServer(db.server))
|
||||
serverCloser.Add(server)
|
||||
|
||||
go grpcutils.Serve(listener, server)
|
||||
|
||||
conn, err := grpcutils.Dial(listener.Addr().String())
|
||||
require.NoError(err)
|
||||
|
||||
db.client = NewClient(rpcdbpb.NewDatabaseClient(conn))
|
||||
|
||||
t.Cleanup(func() {
|
||||
serverCloser.Stop()
|
||||
_ = conn.Close()
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func TestInterface(t *testing.T) {
|
||||
for name, test := range dbtest.Tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
db := setupDB(t)
|
||||
test(t, db.client)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzKeyValue(f *testing.F) {
|
||||
db := setupDB(f)
|
||||
dbtest.FuzzKeyValue(f, db.client)
|
||||
}
|
||||
|
||||
func FuzzNewIteratorWithPrefix(f *testing.F) {
|
||||
db := setupDB(f)
|
||||
dbtest.FuzzNewIteratorWithPrefix(f, db.client)
|
||||
}
|
||||
|
||||
func FuzzNewIteratorWithStartAndPrefix(f *testing.F) {
|
||||
db := setupDB(f)
|
||||
dbtest.FuzzNewIteratorWithStartAndPrefix(f, db.client)
|
||||
}
|
||||
|
||||
func BenchmarkInterface(b *testing.B) {
|
||||
for _, size := range dbtest.BenchmarkSizes {
|
||||
keys, values := dbtest.SetupBenchmark(b, size[0], size[1], size[2])
|
||||
for name, bench := range dbtest.Benchmarks {
|
||||
b.Run(fmt.Sprintf("rpcdb_%d_pairs_%d_keys_%d_values_%s", size[0], size[1], size[2], name), func(b *testing.B) {
|
||||
db := setupDB(b)
|
||||
bench(b, db.client, keys, values)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthCheck(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
testDatabase *testDatabase
|
||||
testFn func(db *corruptabledb.Database) error
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "healthcheck success",
|
||||
testDatabase: setupDB(t),
|
||||
testFn: func(_ *corruptabledb.Database) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "healthcheck failed db closed",
|
||||
testDatabase: setupDB(t),
|
||||
testFn: func(db *corruptabledb.Database) error {
|
||||
return db.Close()
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "closed",
|
||||
},
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
baseDB := setupDB(t)
|
||||
db := corruptabledb.New(baseDB.server, log.NoLog{})
|
||||
defer db.Close()
|
||||
require.NoError(scenario.testFn(db))
|
||||
|
||||
// check db HealthCheck
|
||||
_, err := db.HealthCheck(context.Background())
|
||||
if scenario.wantErr {
|
||||
require.Error(err) //nolint:forbidigo
|
||||
require.Contains(err.Error(), scenario.wantErrMsg)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
|
||||
// check rpc HealthCheck
|
||||
_, err = baseDB.client.HealthCheck(context.Background())
|
||||
require.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package rpcdb
|
||||
|
||||
import (
|
||||
"github.com/luxfi/database"
|
||||
|
||||
rpcdbpb "github.com/luxfi/node/proto/pb/rpcdb"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEnumToError = map[rpcdbpb.Error]error{
|
||||
rpcdbpb.Error_ERROR_CLOSED: database.ErrClosed,
|
||||
rpcdbpb.Error_ERROR_NOT_FOUND: database.ErrNotFound,
|
||||
}
|
||||
ErrorToErrEnum = map[error]rpcdbpb.Error{
|
||||
database.ErrClosed: rpcdbpb.Error_ERROR_CLOSED,
|
||||
database.ErrNotFound: rpcdbpb.Error_ERROR_NOT_FOUND,
|
||||
}
|
||||
)
|
||||
|
||||
func ErrorToRPCError(err error) error {
|
||||
if _, ok := ErrorToErrEnum[err]; ok {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package galiasreader
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
|
||||
aliasreaderpb "github.com/luxfi/node/proto/pb/aliasreader"
|
||||
)
|
||||
|
||||
var _ ids.AliaserReader = (*Client)(nil)
|
||||
|
||||
// Client implements alias lookups that talk over RPC.
|
||||
type Client struct {
|
||||
client aliasreaderpb.AliasReaderClient
|
||||
}
|
||||
|
||||
// NewClient returns an alias lookup instance connected to a remote alias lookup
|
||||
// instance
|
||||
func NewClient(client aliasreaderpb.AliasReaderClient) *Client {
|
||||
return &Client{client: client}
|
||||
}
|
||||
|
||||
func (c *Client) Lookup(alias string) (ids.ID, error) {
|
||||
resp, err := c.client.Lookup(context.Background(), &aliasreaderpb.Alias{
|
||||
Alias: alias,
|
||||
})
|
||||
if err != nil {
|
||||
return ids.Empty, err
|
||||
}
|
||||
return ids.ToID(resp.Id)
|
||||
}
|
||||
|
||||
func (c *Client) PrimaryAlias(id ids.ID) (string, error) {
|
||||
resp, err := c.client.PrimaryAlias(context.Background(), &aliasreaderpb.ID{
|
||||
Id: id[:],
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Alias, nil
|
||||
}
|
||||
|
||||
func (c *Client) Aliases(id ids.ID) ([]string, error) {
|
||||
resp, err := c.client.Aliases(context.Background(), &aliasreaderpb.ID{
|
||||
Id: id[:],
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Aliases, nil
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package galiasreader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
|
||||
aliasreaderpb "github.com/luxfi/node/proto/pb/aliasreader"
|
||||
)
|
||||
|
||||
var _ aliasreaderpb.AliasReaderServer = (*Server)(nil)
|
||||
|
||||
// Server enables alias lookups over RPC.
|
||||
type Server struct {
|
||||
aliasreaderpb.UnsafeAliasReaderServer
|
||||
aliaser ids.AliaserReader
|
||||
}
|
||||
|
||||
// NewServer returns an alias lookup connected to a remote alias lookup
|
||||
func NewServer(aliaser ids.AliaserReader) *Server {
|
||||
return &Server{aliaser: aliaser}
|
||||
}
|
||||
|
||||
func (s *Server) Lookup(
|
||||
_ context.Context,
|
||||
req *aliasreaderpb.Alias,
|
||||
) (*aliasreaderpb.ID, error) {
|
||||
id, err := s.aliaser.Lookup(req.Alias)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &aliasreaderpb.ID{
|
||||
Id: id[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) PrimaryAlias(
|
||||
_ context.Context,
|
||||
req *aliasreaderpb.ID,
|
||||
) (*aliasreaderpb.Alias, error) {
|
||||
if s.aliaser == nil {
|
||||
return nil, fmt.Errorf("aliaser is nil - BCLookup not configured for this chain")
|
||||
}
|
||||
|
||||
id, err := ids.ToID(req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alias, err := s.aliaser.PrimaryAlias(id)
|
||||
|
||||
return &aliasreaderpb.Alias{
|
||||
Alias: alias,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (s *Server) Aliases(
|
||||
_ context.Context,
|
||||
req *aliasreaderpb.ID,
|
||||
) (*aliasreaderpb.AliasList, error) {
|
||||
id, err := ids.ToID(req.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aliases, err := s.aliaser.Aliases(id)
|
||||
return &aliasreaderpb.AliasList{
|
||||
Aliases: aliases,
|
||||
}, err
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package galiasreader
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/ids/idstest"
|
||||
"github.com/luxfi/vm/rpc/grpcutils"
|
||||
|
||||
aliasreaderpb "github.com/luxfi/node/proto/pb/aliasreader"
|
||||
)
|
||||
|
||||
func TestInterface(t *testing.T) {
|
||||
for _, test := range idstest.AliasTests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
listener, err := grpcutils.NewListener()
|
||||
require.NoError(err)
|
||||
defer listener.Close()
|
||||
serverCloser := grpcutils.ServerCloser{}
|
||||
defer serverCloser.Stop()
|
||||
w := ids.NewAliaser()
|
||||
|
||||
server := grpcutils.NewServer()
|
||||
aliasreaderpb.RegisterAliasReaderServer(server, NewServer(w))
|
||||
serverCloser.Add(server)
|
||||
|
||||
go grpcutils.Serve(listener, server)
|
||||
|
||||
conn, err := grpcutils.Dial(listener.Addr().String())
|
||||
require.NoError(err)
|
||||
defer conn.Close()
|
||||
|
||||
r := NewClient(aliasreaderpb.NewAliasReaderClient(conn))
|
||||
test.Test(t, r, w)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
//go:build grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package message
|
||||
|
||||
import "github.com/luxfi/node/proto/p2p"
|
||||
|
||||
// newMessageBFT creates a BFT message wrapper (gRPC version - uses Simplex)
|
||||
func newMessageBFT(msg *p2p.BFT) *p2p.Message_BFT {
|
||||
return &p2p.Message_BFT{Simplex: msg}
|
||||
}
|
||||
|
||||
// extractBFT extracts the BFT message from the wrapper (gRPC version - uses Simplex)
|
||||
func extractBFT(msg *p2p.Message_BFT) *p2p.BFT {
|
||||
return msg.Simplex
|
||||
}
|
||||
+2
-4
@@ -1,5 +1,3 @@
|
||||
//go:build !grpc
|
||||
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
@@ -7,12 +5,12 @@ package message
|
||||
|
||||
import "github.com/luxfi/node/proto/p2p"
|
||||
|
||||
// newMessageBFT creates a BFT message wrapper (ZAP version)
|
||||
// newMessageBFT creates a BFT message wrapper.
|
||||
func newMessageBFT(msg *p2p.BFT) *p2p.Message_BFT {
|
||||
return &p2p.Message_BFT{BFT: msg}
|
||||
}
|
||||
|
||||
// extractBFT extracts the BFT message from the wrapper (ZAP version)
|
||||
// extractBFT extracts the BFT message from the wrapper.
|
||||
func extractBFT(msg *p2p.Message_BFT) *p2p.BFT {
|
||||
return msg.BFT
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,8 +6,8 @@ package message
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/metric"
|
||||
compression "github.com/luxfi/compress"
|
||||
"github.com/luxfi/metric"
|
||||
)
|
||||
|
||||
var _ Creator = (*creator)(nil)
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
compression "github.com/luxfi/compress"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/node/proto/p2p"
|
||||
"github.com/luxfi/timer/mockable"
|
||||
compression "github.com/luxfi/compress"
|
||||
)
|
||||
|
||||
func Test_newMsgBuilder(t *testing.T) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user