Compare commits

..

No commits in common. "master" and "v0.8.0" have entirely different histories.

449 changed files with 16793 additions and 36542 deletions

View File

@ -2,5 +2,6 @@ README.md
Dockerfile
.*ignore
LICENSE*
tests
examples
*.db

1
.github/CODEOWNERS vendored
View File

@ -1 +0,0 @@
* @richard-ramos @chaitanyaprem

View File

@ -1,6 +0,0 @@
services:
ganache:
image: "trufflesuite/ganache:latest"
command: ["-m='swim relax risk shy chimney please usual search industry board music segment'"]
ports:
- "8545:8545"

View File

@ -1,13 +0,0 @@
services:
nwaku:
image: "harbor.status.im/wakuorg/nwaku:v0.35.1"
command:
[
"--relay",
"--store",
"--nodekey=1122334455667788990011223344556677889900112233445566778899001122",
"--cluster-id=99",
"--shard=1",
]
ports:
- "60000"

View File

@ -1,7 +0,0 @@
services:
postgres:
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
image: "postgres:9.6-alpine"
ports:
- "5432:5432"

View File

@ -14,4 +14,4 @@ jobs:
- uses: actions/add-to-project@v0.5.0
with:
project-url: https://github.com/orgs/waku-org/projects/2
github-token: ${{ secrets.ADD_TO_PROJECT_20240815 }}
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}

View File

@ -1,12 +0,0 @@
name: Auto Assign PR to Creator
on:
pull_request:
types:
- opened
jobs:
assign_creator:
runs-on: ubuntu-latest
steps:
- uses: toshimaru/auto-author-assign@v1.6.2

View File

@ -1,78 +0,0 @@
name: build_linux_pkgs
on:
push:
tags:
- 'v*' # "e.g. v0.4"
workflow_dispatch:
jobs:
env:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: xom9ikk/dotenv@v2
with:
path: ".github/"
- run: |
echo "go_version=${{ env.GO_VERSION }}" >> $GITHUB_OUTPUT
- run: |
VERSION=$(cat ./VERSION)
echo "waku_version=$VERSION" >> $GITHUB_OUTPUT
build-linux:
needs: env
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
ext: [deb, rpm]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Get submodules hash
id: submodules
run: |
echo "hash=$(git submodule status | awk '{print $1}' | sort | shasum -a 256 | sed 's/[ -]*//g')" >> $GITHUB_OUTPUT
- name: Cache submodules
uses: actions/cache@v3
with:
path: |
vendor/
.git/modules
key: ${{ runner.os }}-vendor-modules-${{ steps.submodules.outputs.hash }}
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ needs.env.outputs.go_version }}
cache: false
- name: Build
id: build
run: |
make build
mkdir ./build/linux
cp ./build/waku ./build/linux/.
strip --strip-unneeded ./build/linux/waku
- name: Package ${{ matrix.ext }}
uses: bpicode/github-action-fpm@master
with:
fpm_args: ./build/linux/waku=/usr/bin/waku
fpm_opts: '-p gowaku-${{ needs.env.outputs.waku_version }}-x86_64.${{ matrix.ext }} -n go-waku -t ${{ matrix.ext }} -s dir --license "MIT, Apache 2.0" --version ${{ needs.env.outputs.waku_version }} --architecture x86_64 --depends libc6 --description "Go implementation of Waku v2 protocols" --url "https://github.com/waku-org/go-waku" --maintainer "Richard Ramos <richard@status.im>"'
- name: Upload ${{ matrix.ext }}
uses: actions/upload-artifact@v3
with:
name: gowaku-${{ needs.env.outputs.waku_version }}-x86_64.${{ matrix.ext }}
path: ./gowaku-${{ needs.env.outputs.waku_version }}-x86_64.${{ matrix.ext }}
if-no-files-found: error

View File

@ -1,110 +0,0 @@
name: build_mobile
on:
push:
tags:
- 'v*' # "e.g. v0.4"
workflow_dispatch:
jobs:
env:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: xom9ikk/dotenv@v2
with:
path: ".github/"
- run: |
echo "go_version=${{ env.GO_VERSION }}" >> $GITHUB_OUTPUT
- run: |
VERSION=$(cat ./VERSION)
echo "waku_version=$VERSION" >> $GITHUB_OUTPUT
build-android:
needs: env
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Get submodules hash
id: submodules
run: |
echo "hash=$(git submodule status | awk '{print $1}' | sort | shasum -a 256 | sed 's/[ -]*//g')" >> $GITHUB_OUTPUT
- name: Cache submodules
uses: actions/cache@v3
with:
path: |
vendor/
.git/modules
key: ${{ runner.os }}-vendor-modules-${{ needs.env.outputs.go_version }}
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ needs.env.outputs.go_version }}
cache: false
- name: Build
run: |
make install-gomobile
make mobile-android || make mobile-android
cd ./build/lib
tar -czvf gowaku-${{ needs.env.outputs.waku_version }}-android.tar.gz gowaku.aar gowaku-sources.jar
- name: Upload asset
uses: actions/upload-artifact@v3
with:
name: gowaku-${{ needs.env.outputs.waku_version }}-android.tar.gz
path: ./build/lib/gowaku-${{ needs.env.outputs.waku_version }}-android.tar.gz
if-no-files-found: error
build-ios:
needs: env
runs-on: macos-latest
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Get submodules hash
id: submodules
run: |
echo "hash=$(git submodule status | awk '{print $1}' | sort | shasum -a 256 | sed 's/[ -]*//g')" >> $GITHUB_OUTPUT
- name: Cache submodules
uses: actions/cache@v3
with:
path: |
vendor/
.git/modules
key: ${{ runner.os }}-vendor-modules-${{ steps.submodules.outputs.hash }}
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ needs.env.outputs.go_version }}
cache: false
- name: Build
run: |
make install-gomobile
make mobile-ios
cd ./build/lib
tar -czvf gowaku-${{ needs.env.outputs.waku_version }}-ios.tar.gz Gowaku.xcframework
- name: Upload asset
uses: actions/upload-artifact@v3
with:
name: gowaku-${{ needs.env.outputs.waku_version }}-ios.tar.gz
path: ./build/lib/gowaku-${{ needs.env.outputs.waku_version }}-ios.tar.gz
if-no-files-found: error

View File

@ -1,169 +0,0 @@
name: ci
on:
pull_request:
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
changes: # changes detection
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- uses: actions/checkout@v3
name: Checkout code
id: checkout
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
common:
- '.github/workflows/**'
- 'Makefile'
- 'libs/**'
- 'go.mod'
- 'go.sum'
- 'flake.nix'
- 'examples/**'
v2:
- 'waku/**'
- 'cmd/**'
- 'library/**'
- 'tests/**'
docker:
- 'docker/**'
- 'Dockerfile'
outputs:
common: ${{ steps.filter.outputs.common }}
v2: ${{ steps.filter.outputs.v2 }}
docker: ${{ steps.filter.outputs.docker }}
env:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
VERSION=$(cat ./VERSION)
echo "waku_version=$VERSION" >> $GITHUB_OUTPUT
golangci:
name: lint
needs: [changes, env]
if: ${{ needs.changes.outputs.v2 == 'true' || needs.changes.outputs.common == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: false
- name: Execute golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.64.6
args: --timeout=5m
build:
needs: [changes, env]
if: ${{ needs.changes.outputs.v2 == 'true' || needs.changes.outputs.common == 'true' }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
name: build-${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Get submodules hash
id: submodules
run: |
echo "hash=$(git submodule status | awk '{print $1}' | sort | shasum -a 256 | sed 's/[ -]*//g')" >> $GITHUB_OUTPUT
- name: Cache submodules
uses: actions/cache@v3
with:
path: |
vendor/
.git/modules
key: ${{ runner.os }}-vendor-modules-${{ steps.submodules.outputs.hash }}
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: false
- name: Build binary
run: make
- name: Build library
run: make static-library dynamic-library
- name: Build examples
run: make build-example
test:
needs: [changes, env]
if: ${{ needs.changes.outputs.v2 == 'true' || needs.changes.outputs.common == 'true' }}
strategy:
matrix:
tests: [test-ci, test-with-race]
runs-on: ubuntu-latest
timeout-minutes: 60
name: ${{ matrix.tests }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Get submodules hash
id: submodules
run: |
echo "hash=$(git submodule status | awk '{print $1}' | sort | shasum -a 256 | sed 's/[ -]*//g')" >> $GITHUB_OUTPUT
- name: Cache submodules
uses: actions/cache@v3
with:
path: |
vendor/
.git/modules
key: ${{ runner.os }}-vendor-modules-${{ steps.submodules.outputs.hash }}
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: false
- name: "Run tests"
run: make ${{ matrix.tests }}
- name: "Run onchain-tests"
run: |
docker compose -f .github/docker-compose/ganache.yml up -d
make test-onchain${{ matrix.tests == 'test-with-race' && '-with-race' || '' }}
- name: "Run storev3 tests"
run: |
docker compose -f .github/docker-compose/nwaku.yml up -d
NWAKU_HOST=$(docker compose -f .github/docker-compose/nwaku.yml port nwaku 60000)
NWAKU_PORT=$(echo $NWAKU_HOST | cut -d ":" -f 2)
sleep 5
make test-storev3 TEST_STOREV3_NODE="/ip4/127.0.0.1/tcp/${NWAKU_PORT}/p2p/16Uiu2HAmMGhfSTUzKbsjMWxc6T1X4wiTWSF1bEWSLjAukCm7KiHV"

View File

@ -1,50 +0,0 @@
name: container-image-build
on:
workflow_call:
inputs:
image_tag:
type: string
default: ${{ github.event.number }}
outputs:
image:
description: The resulting image link
value: ${{ jobs.build-docker-image.outputs.image }}
workflow_dispatch:
jobs:
build-docker-image:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
name: docker-build-${{ matrix.os }}
outputs:
image: ${{ steps.build.outputs.image }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build image
id: build
run: |
SHORT_REF=$(git rev-parse --short HEAD)
TAG=$([ "${PR_NUMBER}" == "" ] && echo "${SHORT_REF}" || echo "${PR_NUMBER}")
IMAGE=quay.io/wakuorg/go-waku-pr:${TAG}
echo "image=${IMAGE}" >> $GITHUB_OUTPUT
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
docker login -u ${QUAY_USER} -p ${QUAY_PASSWORD} quay.io
docker build -t ${IMAGE} -f docker/Dockerfile.test.amd64 --label quay.expires-after=7d .
docker push ${IMAGE}
env:
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
QUAY_USER: ${{ secrets.QUAY_USER }}
PR_NUMBER: ${{ inputs.image_tag }}

View File

@ -1,43 +0,0 @@
name: "Conventional Commits"
on:
pull_request:
types:
- opened
- edited
- synchronize
jobs:
main:
name: Validate format
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: amannn/action-semantic-pull-request@v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: richard-ramos/action-conventional-commits@v1.1.1
id: lint_pr_commits
- uses: marocchino/sticky-pull-request-comment@v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
if: always() && (steps.lint_pr_title.outputs.error_message != null || steps.lint_pr_commits.outputs.error_message != null )
with:
header: pr-title-lint-error
message: |
Thank you for opening this pull request!
We require pull request titles and commits to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your PR needs to be adjusted.
Details:
> ${{ steps.lint_pr_title.outputs.error_message }}
> ${{ steps.lint_pr_commits.outputs.error_message }}
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.lint_pr_title.outputs.error_message == null && steps.lint_pr_commits.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
delete: true

19
.gitignore vendored
View File

@ -4,7 +4,6 @@ rlnKeystore.json
test_onchain.json
*.bkp
*.log
.vscode
# sqlite db
*.db
@ -27,18 +26,22 @@ pkg/*
# output binaries
go-waku
examples/basic-relay/build/basic-relay
examples/filter2/build/filter2
examples/basic2/basic2
examples/filter2/filter2
examples/noise/build/
examples/noise/noise
examples/basic-light-client/basic2
examples/basic-relay/basic2
examples/filter2/filter2
examples/rln/rln
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
*.out.tmp
coverage.html
coverage.json
cc-test-reporter
# Dependency directories (remove the comment below to include it)
# vendor/
@ -183,5 +186,3 @@ iOSInjectionProject/
**/xcshareddata/WorkspaceSettings.xcsettings
# End of https://www.toptal.com/developers/gitignore/api/swift,xcode,Cobjective-c,osx
.idea

9
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "libs/waku-rln-contract"]
path = libs/waku-rln-contract
url = https://github.com/waku-org/waku-rln-contract.git
[submodule "waku/v2/protocol/waku-proto"]
path = waku/v2/protocol/waku-proto
url = https://github.com/waku-org/waku-proto
[submodule "waku/v2/protocol/rln/contracts/waku-rln-contract"]
path = waku/v2/protocol/rln/contracts/waku-rln-contract
url = git@github.com:waku-org/waku-rln-contract.git

View File

@ -1,5 +1,5 @@
# BUILD IMAGE --------------------------------------------------------
FROM golang:1.23 as builder
FROM golang:1.19 as builder
WORKDIR /app
COPY . .

107
Makefile
View File

@ -1,11 +1,14 @@
CC_TEST_REPORTER_ID := c09efa7c67c269bfdc6f8a356785d8f7ed55c9dc2b9a1d07b78c384f55c4e527
GO_HTML_COV := ./coverage.html
GO_TEST_OUTFILE := ./c.out
CC_PREFIX := github.com/waku-org/go-waku
SHELL := bash # the shell used internally by Make
GOCMD ?= $(shell which go)
GOBIN ?= $(shell which go)
.PHONY: all build lint lint-full test build-example static-library dynamic-library test-c test-c-template mobile-android mobile-ios
.PHONY: all build lint lint-full test coverage build-example static-library dynamic-library test-c test-c-template mobile-android mobile-ios
ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10...
detected_OS := Windows
@ -48,61 +51,69 @@ all: build
deps: lint-install
build-with-race:
${GOCMD} build -race -tags="${BUILD_TAGS}" $(BUILD_FLAGS) -o build/waku ./cmd/waku
${GOBIN} build -race -tags="${BUILD_TAGS}" $(BUILD_FLAGS) -o build/waku ./cmd/waku
build:
${GOCMD} build -tags="${BUILD_TAGS}" $(BUILD_FLAGS) -o build/waku ./cmd/waku
${GOBIN} build -tags="${BUILD_TAGS}" $(BUILD_FLAGS) -o build/waku ./cmd/waku
chat2:
pushd ./examples/chat2 && \
${GOCMD} build -o ../../build/chat2 . && \
${GOBIN} build -o ../../build/chat2 . && \
popd
vendor:
${GOCMD} mod tidy
cd examples/basic-light-client && ${GOCMD} mod tidy
cd examples/basic-relay && ${GOCMD} mod tidy
cd examples/chat2-reliable && ${GOCMD} mod tidy
cd examples/chat2 && ${GOCMD} mod tidy
cd examples/filter2 && ${GOCMD} mod tidy
cd examples/noise && ${GOCMD} mod tidy
cd examples/rln && ${GOCMD} mod tidy
${GOBIN} mod tidy
lint-install:
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
bash -s -- -b $(shell ${GOCMD} env GOPATH)/bin v1.59.1
bash -s -- -b $(shell ${GOBIN} env GOPATH)/bin v1.52.2
lint:
@echo "lint"
@golangci-lint run ./...
@golangci-lint run ./... --deadline=5m
lint-full:
@echo "lint"
@golangci-lint run ./... --config=./.golangci.full.yaml
@golangci-lint run ./... --config=./.golangci.full.yaml --deadline=5m
test-with-race:
${GOCMD} test -race -timeout 300s ./waku/... ./cmd/waku/server/...
${GOBIN} test -race -timeout 300s ./waku/...
test:
${GOCMD} test -timeout 300s ./waku/... ./cmd/waku/server/... ./...
${GOBIN} test -timeout 300s ./waku/... -coverprofile=${GO_TEST_OUTFILE}.tmp
cat ${GO_TEST_OUTFILE}.tmp | grep -v ".pb.go" > ${GO_TEST_OUTFILE}
${GOBIN} tool cover -html=${GO_TEST_OUTFILE} -o ${GO_HTML_COV}
test-ci: test
COVERAGE_FILE := ./coverage/cc-test-reporter
$(COVERAGE_FILE):
curl -sfL $(TEST_REPORTER_URL) --output ./coverage/cc-test-reporter #TODO: Support windows
chmod +x ./coverage/cc-test-reporter
_before-cc: $(COVERAGE_FILE)
CC_TEST_REPORTER_ID=${CC_TEST_REPORTER_ID} ./coverage/cc-test-reporter before-build
_after-cc:
GIT_COMMIT=$(git log | grep -m1 -oE '[^ ]+$') CC_TEST_REPORTER_ID=${CC_TEST_REPORTER_ID} ./coverage/cc-test-reporter after-build --prefix ${CC_PREFIX}
test-ci: _before-cc test _after-cc
generate:
${GOCMD} generate ./...
${GOBIN} generate ./...
coverage:
${GOBIN} test -count 1 -coverprofile=coverage.out ./...
${GOBIN} tool cover -html=coverage.out -o=coverage.html
# build a docker image for the fleet
docker-image: DOCKER_IMAGE_TAG ?= latest
docker-image: DOCKER_IMAGE_NAME ?= wakuorg/go-waku:$(DOCKER_IMAGE_TAG)
docker-image: DOCKER_IMAGE_NAME ?= statusteam/go-waku:$(DOCKER_IMAGE_TAG)
docker-image:
docker build --tag $(DOCKER_IMAGE_NAME) \
--build-arg="GIT_COMMIT=$(shell git rev-parse HEAD)" .
build-example-basic-relay:
cd examples/basic-relay && $(MAKE)
build-example-basic-light-client:
cd examples/basic-light-client && $(MAKE)
build-example-basic2:
cd examples/basic2 && $(MAKE)
build-example-chat-2:
cd examples/chat2 && $(MAKE)
@ -113,17 +124,14 @@ build-example-filter2:
build-example-c-bindings:
cd examples/c-bindings && $(MAKE)
build-example-noise:
cd examples/noise && $(MAKE)
build-example-rln:
cd examples/rln && $(MAKE)
build-example: build-example-basic-relay build-example-basic-light-client build-example-chat-2 build-example-filter2 build-example-c-bindings build-example-noise build-example-rln
build-example: build-example-basic2 build-example-chat-2 build-example-filter2 build-example-c-bindings build-example-rln
static-library:
@echo "Building static library..."
${GOCMD} build \
${GOBIN} build \
-buildmode=c-archive \
-tags="${BUILD_TAGS} gowaku_no_rln" \
-o ./build/lib/libgowaku.a \
@ -139,7 +147,7 @@ endif
dynamic-library:
@echo "Building shared library..."
rm -f ./build/lib/libgowaku.$(GOBIN_SHARED_LIB_EXT)*
$(GOBIN_SHARED_LIB_CFLAGS) $(GOBIN_SHARED_LIB_CGO_LDFLAGS) ${GOCMD} build \
$(GOBIN_SHARED_LIB_CFLAGS) $(GOBIN_SHARED_LIB_CGO_LDFLAGS) ${GOBIN} build \
-buildmode=c-shared \
-tags="${BUILD_TAGS} gowaku_no_rln" \
-o ./build/lib/libgowaku.$(GOBIN_SHARED_LIB_EXT) \
@ -160,30 +168,30 @@ endif
mobile-android:
@echo "Android target: ${ANDROID_TARGET} (override with ANDROID_TARGET var)"
gomobile init && \
${GOCMD} get -d golang.org/x/mobile/cmd/gomobile && \
${GOBIN} get -d golang.org/x/mobile/cmd/gomobile && \
CGO=1 gomobile bind -v -target=android -androidapi=${ANDROID_TARGET} -ldflags="-s -w" -tags="${BUILD_TAGS} gowaku_no_rln" $(BUILD_FLAGS) -o ./build/lib/gowaku.aar ./library/mobile
@echo "Android library built:"
@ls -la ./build/lib/*.aar ./build/lib/*.jar
mobile-ios:
gomobile init && \
${GOCMD} get -d golang.org/x/mobile/cmd/gomobile && \
${GOBIN} get -d golang.org/x/mobile/cmd/gomobile && \
gomobile bind -target=ios -ldflags="-s -w" -tags="nowatchdog ${BUILD_TAGS} gowaku_no_rln" $(BUILD_FLAGS) -o ./build/lib/Gowaku.xcframework ./library/mobile
@echo "IOS library built:"
@ls -la ./build/lib/*.xcframework
install-xtools:
${GOCMD} install golang.org/x/tools/...@v0.1.10
${GOBIN} install golang.org/x/tools/...@v0.1.10
install-bindata:
${GOCMD} install github.com/kevinburke/go-bindata/go-bindata@v3.13.0
${GOBIN} install github.com/kevinburke/go-bindata/go-bindata@v3.13.0
install-gomobile: install-xtools
${GOCMD} install golang.org/x/mobile/cmd/gomobile@v0.0.0-20220518205345-8578da9835fd
${GOCMD} install golang.org/x/mobile/cmd/gobind@v0.0.0-20220518205345-8578da9835fd
${GOBIN} install golang.org/x/mobile/cmd/gomobile@v0.0.0-20220518205345-8578da9835fd
${GOBIN} install golang.org/x/mobile/cmd/gobind@v0.0.0-20220518205345-8578da9835fd
build-linux-pkg:
docker build --build-arg UID=${UID} --build-arg GID=${GID} -f ./scripts/linux/Dockerfile -t wakuorg/gowaku-linux-pkgs:latest .
docker build --build-arg UID=${UID} --build-arg GID=${GID} -f ./scripts/linux/Dockerfile -t statusteam/gowaku-linux-pkgs:latest .
./scripts/linux/docker-run.sh
ls -la ./build/*.rpm ./build/*.deb
@ -197,24 +205,7 @@ stop-ganache:
test-onchain: BUILD_TAGS += include_onchain_tests
test-onchain:
${GOCMD} test -v -count 1 -tags="${BUILD_TAGS}" github.com/waku-org/go-waku/waku/v2/protocol/rln
${GOBIN} test -v -count 1 -tags="${BUILD_TAGS}" github.com/waku-org/go-waku/waku/v2/protocol/rln
test-onchain-with-race:
${GOCMD} test -race -v -count 1 -tags="${BUILD_TAGS}" github.com/waku-org/go-waku/waku/v2/protocol/rln
test-postgres: PG_BUILD_TAGS = ${BUILD_TAGS} include_postgres_tests
test-postgres:
${GOCMD} test -p 1 -v -count 1 -tags="${PG_BUILD_TAGS}" github.com/waku-org/go-waku/waku/persistence/...
test-postgres-with-race:
${GOCMD} test -race -p 1 -v -count 1 -tags="${PG_BUILD_TAGS}" github.com/waku-org/go-waku/waku/persistence/...
test-filter:
${GOCMD} test -v github.com/waku-org/go-waku/waku/v2/protocol/filter -run TestFilterSuite -count=1
test-filter-api:
${GOCMD} test -v github.com/waku-org/go-waku/waku/v2/api -run TestFilterApiSuite
TEST_STOREV3_NODE ?=
test-storev3:
TEST_STOREV3_NODE=${TEST_STOREV3_NODE} ${GOCMD} test -p 1 -v -count 1 -tags="${BUILD_TAGS} include_storev3_tests" github.com/waku-org/go-waku/waku/v2/protocol/store/...
${GOBIN} test -race -v -count 1 -tags="${BUILD_TAGS}" github.com/waku-org/go-waku/waku/v2/protocol/rln

View File

@ -5,7 +5,7 @@ A Go implementation of the [Waku v2 protocol](https://rfc.vac.dev/spec/10).
<p align="left">
<a href="https://goreportcard.com/report/github.com/waku-org/go-waku"><img src="https://goreportcard.com/badge/github.com/waku-org/go-waku" /></a>
<a href="https://godoc.org/github.com/waku-org/go-waku"><img src="http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/golang-%3E%3D1.20.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/golang-%3E%3D1.19.0-orange.svg?style=flat-square" /></a>
<a href="https://codeclimate.com/github/waku-org/go-waku/maintainability"><img src="https://api.codeclimate.com/v1/badges/426bdff6a339ff4d536b/maintainability" /></a>
<br>
</p>
@ -38,19 +38,18 @@ nix develop
#### Docker
```
docker run -i -t -p 60000:60000 -p 9000:9000/udp \
wakuorg/go-waku:latest \
--dns-discovery \
--dns-discovery-url enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im \
statusteam/go-waku:latest \ # or, the image:tag of your choice
--dns-discovery:true \
--dns-discovery-url:enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@prod.waku.nodes.status.im \
--discv5-discovery
```
or use the [image:tag](https://hub.docker.com/r/wakuorg/go-waku/tags) of your choice.
or build and run the image with:
```
docker build -t wakuorg/go-waku:latest .
docker build -t statusteam/go-waku:latest .
docker run wakuorg/go-waku:latest --help
docker run statusteam/go-waku:latest --help
```
#### Building on windows
@ -85,7 +84,7 @@ make mobile-ios
- [Send messages using Waku Lightpush](docs/api/lightpush.md)
- [Encrypting and decrypting Waku Messages](docs/api/encoding.md)
- [Retrieve message history using Waku Store](docs/api/store.md)
- [C Bindings](library/c/README.md)
- [C Bindings](library/README.md)
- [Waku Specs](https://rfc.vac.dev/spec), has information of [waku topics](https://rfc.vac.dev/spec/23/), wakuv1/[wakuv2](https://rfc.vac.dev/spec/14/) message, [rln relay](https://rfc.vac.dev/spec/58/) etc.
- [Enr](https://eips.ethereum.org/EIPS/eip-778), [Enrtree](https://eips.ethereum.org/EIPS/eip-1459)
- [devp2p](https://github.com/ethereum/go-ethereum/tree/master/cmd/devp2p) tool for playing with enr/entree sync tree. [Tutorial](https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup)
@ -107,7 +106,7 @@ Thank you for considering to help out with the source code! We welcome contribut
If you'd like to contribute to go-waku, please fork, fix, commit and send a pull request. If you wish to submit more complex changes though, please check up with the core devs first to ensure those changes are in line with the general philosophy of the project and/or get some early feedback which can make both your efforts much lighter as well as our review and merge procedures quick and simple.
To build and test this repository, you need:
- [Go](https://golang.org/) (version 1.20)
- [Go](https://golang.org/) (version 1.19 or 1.20)
- [protoc](https://grpc.io/docs/protoc-installation/)
- [protoc-gen-go](https://protobuf.dev/getting-started/gotutorial/#compiling-protocol-buffers)

View File

@ -1 +1 @@
0.10.1
0.8.0

99
ci/Jenkinsfile vendored Normal file
View File

@ -0,0 +1,99 @@
library 'status-jenkins-lib@v1.7.0'
pipeline {
agent { label 'linux' }
options {
timestamps()
disableConcurrentBuilds()
/* Prevent Jenkins jobs from running forever */
timeout(time: 40, unit: 'MINUTES')
/* Limit builds retained */
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
artifactNumToKeepStr: '10',
))
}
/* WARNING: Defining parameters here with the ?: trick causes them to remember last value. */
parameters {
booleanParam(
name: 'PUBLISH',
description: 'Trigger publishing of build results for nightly or release.',
defaultValue: getPublishDefault(params.PUBLISH),
)
}
stages {
stage('Build') {
parallel {
stage('iOS') { steps { script {
ios = jenkins.Build('go-waku/platforms/ios')
} } }
stage('Android') { steps { script {
android = jenkins.Build('go-waku/platforms/android')
} } }
stage('Linux') { steps { script {
linux = jenkins.Build('go-waku/platforms/linux')
} } }
}
}
stage('Archive') {
steps { script {
sh('rm -f pkg/*')
jenkins.copyArts(ios)
jenkins.copyArts(android)
jenkins.copyArts(linux)
sha = "pkg/${utils.pkgFilename(ext: 'sha256')}"
dir('pkg') {
/* generate sha256 checksums for upload */
sh "sha256sum * | tee ../${sha}"
archiveArtifacts('*')
}
} }
}
stage('Upload') {
steps { script {
/* object for easier URLs handling */
urls = [
/* mobile */
Android: utils.pkgUrl(android),
iOS: utils.pkgUrl(ios),
Linux: utils.pkgUrl(linux),
/* upload the sha256 checksums file too */
SHA: s3.uploadArtifact(sha),
]
/* add URLs to the build description */
jenkins.setBuildDesc(urls)
} }
}
stage('Publish') {
when { expression { params.PUBLISH } }
steps { script {
github.publishReleaseFiles(repo: 'status-desktop');
} }
}
}
}
/* Helper that generates list of available choices for a parameter
* but re-orders them based on the currently set value. First is default. */
def List genChoices(String previousChoice, List defaultChoices) {
if (previousChoice == null) {
return defaultChoices
}
choices = defaultChoices.minus(previousChoice)
choices.add(0, previousChoice)
return choices
}
/* Helper that makes PUBLISH default to 'false' unless:
* - The build is for a release branch
* - A user explicitly specified a value
* Since release builds create and re-create GitHub drafts every time. */
def Boolean getPublishDefault(Boolean previousValue) {
if (env.JOB_NAME.startsWith('go-waku/release')) { return true }
if (previousValue != null) { return previousValue }
return false
}

82
ci/Jenkinsfile.android Normal file
View File

@ -0,0 +1,82 @@
library 'status-jenkins-lib@v1.7.0'
pipeline {
agent { label 'linux && nix-2.11 && x86_64' }
options {
timestamps()
disableConcurrentBuilds()
/* Prevent Jenkins jobs from running forever */
timeout(time: 30, unit: 'MINUTES')
/* Go requires a certain directory structure */
checkoutToSubdirectory('src/github.com/waku-org/go-waku')
/* Limit builds retained */
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '20',
artifactNumToKeepStr: '10',
))
/* Allows combined build to copy */
copyArtifactPermission('/go-waku/*')
}
environment {
CC = "gcc-10"
/* Other stuff */
TARGET = 'android'
REPO = "${env.WORKSPACE}/src/github.com/waku-org/go-waku"
GOCACHE = "${env.WORKSPACE_TMP}/go-build"
GOPATH = "${env.WORKSPACE}"
PATH = "${env.PATH}:${env.GOPATH}/bin"
/* Android SDK */
ANDROID_HOME = '/usr/lib/android-sdk'
ANDROID_SDK_ROOT = '/usr/lib/android-sdk'
/* gomobile requires a specific NDK version */
ANDROID_NDK = "/opt/android-ndk-r23c"
ANDROID_NDK_HOME = "/opt/android-ndk-r23c"
}
stages {
stage('Prep') { steps { script { dir(env.REPO) {
env.ARTIFACT = "${env.REPO}/pkg/" + utils.pkgFilename(
name: "go-waku",
type: "android",
ext: "tar.gz"
)
nix.develop('make install-gomobile', pure: false)
} } } }
stage('Build') { steps { script { dir(env.REPO) {
/* First gomobile run always fails.
* https://github.com/golang/go/issues/37372 */
nix.develop('make mobile-android || make mobile-android', pure: false)
dir('build/lib') {
sh 'tar -czvf gowaku-android.tar.gz gowaku.aar gowaku-sources.jar'
sh "cp gowaku-android.tar.gz ${env.ARTIFACT}"
}
} } } }
stage('Parallel Upload') {
parallel {
stage('Archive') {
steps { script {
archiveArtifacts(env.ARTIFACT.minus("${env.WORKSPACE}/"))
} }
}
stage('Upload') {
steps { script {
env.PKG_URL = s3.uploadArtifact(env.ARTIFACT)
jenkins.setBuildDesc(android: env.PKG_URL)
} }
}
}
}
}
post {
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
always { cleanWs() }
}
}

View File

@ -11,30 +11,18 @@ pipeline {
)
string(
name: 'IMAGE_NAME',
defaultValue: 'statusteam/go-waku',
description: 'Docker image name.',
defaultValue: params.IMAGE_NAME ?: 'waku-org/go-waku',
)
string(
name: 'IMAGE_TAG',
defaultValue: env.JOB_BASE_NAME == 'release' ? 'stable' : 'deploy-test',
description: 'Docker image tag.',
defaultValue: getDefaultImageTag(params.IMAGE_TAG)
)
string(
name: 'DOCKER_CRED',
description: 'Name of Docker Registry credential.',
defaultValue: params.DOCKER_CRED ?: 'harbor-wakuorg-robot',
)
string(
name: 'DOCKER_REGISTRY_URL',
description: 'URL of the Docker Registry',
defaultValue: params.DOCKER_REGISTRY_URL ?: 'https://harbor.status.im'
)
}
options {
timestamps()
disableRestartFromStage()
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
@ -44,38 +32,83 @@ pipeline {
stages {
stage('Build') {
steps { script {
def imageTag = GIT_COMMIT.take(8)
if (env.JOB_BASE_NAME == 'release') {
imageTag = params.GIT_REF
}
image = docker.build(
"${params.IMAGE_NAME}:${params.IMAGE_TAG ?: GIT_COMMIT.take(8)}",
"${params.IMAGE_NAME}:${imageTag}",
"--build-arg='GIT_COMMIT=${GIT_COMMIT.take(8)}' ."
)
} }
}
stage('Push') {
when { expression { params.IMAGE_TAG != '' } }
steps { script {
withDockerRegistry([
credentialsId: params.DOCKER_CRED, url: params.DOCKER_REGISTRY_URL
credentialsId: "dockerhub-statusteam-auto", url: ""
]) {
image.push()
/* If Git ref is a tag push it as Docker tag too. */
if (params.GIT_REF ==~ /v\d+\.\d+\.\d+.*/) {
image.push(params.GIT_REF)
}
}
} }
}
stage('Deploy') {
steps { script {
withDockerRegistry([
credentialsId: "dockerhub-statusteam-auto", url: ""
]) {
image.push(env.IMAGE_TAG)
}
} }
}
}
post {
success { script {
discordNotify(
header: 'Go-Waku build successful!',
cred: 'discord-waku-deployments-webhook',
)
} }
always { cleanWs() }
}
}
def getDefaultImageTag(currentValue) {
switch (env.JOB_BASE_NAME) {
case 'docker-latest': return 'latest'
case 'docker-release': return 'stable'
case 'docker-manual': return ''
default: return currentValue
def discordNotify(Map args=[:]) {
def opts = [
header: args.header ?: 'Deployment successful!',
cred: args.cred ?: null,
]
def repo = [
url: GIT_URL.minus('.git'),
branch: GIT_BRANCH.minus('origin/'),
commit: GIT_COMMIT.take(8),
prev: (
env.GIT_PREVIOUS_SUCCESSFUL_COMMIT ?: env.GIT_PREVIOUS_COMMIT ?: 'master'
).take(8),
]
wrap([$class: 'BuildUser']) {
BUILD_USER_ID = env.BUILD_USER_ID
}
withCredentials([
string(
credentialsId: opts.cred,
variable: 'DISCORD_WEBHOOK',
),
]) {
discordSend(
link: env.BUILD_URL,
result: currentBuild.currentResult,
webhookURL: env.DISCORD_WEBHOOK,
title: "${env.JOB_NAME}#${env.BUILD_NUMBER}",
description: """
${opts.header}
Image: [`${IMAGE_NAME}:${IMAGE_TAG}`](https://hub.docker.com/r/${IMAGE_NAME}/tags?name=${IMAGE_TAG})
Branch: [`${repo.branch}`](${repo.url}/commits/${repo.branch})
Commit: [`${repo.commit}`](${repo.url}/commit/${repo.commit})
Diff: [`${repo.prev}...${repo.commit}`](${repo.url}/compare/${repo.prev}...${repo.commit})
By: [`${BUILD_USER_ID}`](${repo.url}/commits?author=${BUILD_USER_ID})
""",
)
}
}

78
ci/Jenkinsfile.ios Normal file
View File

@ -0,0 +1,78 @@
library 'status-jenkins-lib@v1.7.0'
pipeline {
agent { label 'macos && nix-2.11 && aarch64' }
options {
timestamps()
/* Prevent Jenkins jobs from running forever */
timeout(time: 30, unit: 'MINUTES')
/* Go requires a certain directory structure */
checkoutToSubdirectory('src/github.com/waku-org/go-waku')
/* Limit builds retained */
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '20',
artifactNumToKeepStr: '10',
))
/* Allows combined build to copy */
copyArtifactPermission('/go-waku/*')
}
environment {
TARGET = 'ios'
REPO = "${env.WORKSPACE}/src/github.com/waku-org/go-waku"
GOCACHE = "${env.WORKSPACE_TMP}/go-build"
GOPATH = "${env.WORKSPACE}"
PATH = "${env.PATH}:${env.GOPATH}/bin"
}
stages {
stage('Prep') { steps { script { dir(env.REPO) {
env.ARTIFACT = "${env.REPO}/pkg/" + utils.pkgFilename(
name: "go-waku",
type: "ios",
ext: "tar.gz"
)
sh 'make install-gomobile'
} } } }
stage('Build') {
steps { script { dir(env.REPO) {
nix.develop('which xcodebuild', pure: false)
nix.develop('make mobile-ios', pure: false)
} } }
}
stage('Package') {
steps { dir(env.REPO) {
dir('build/lib') {
sh 'tar -czvf gowaku-ios.tar.gz Gowaku.xcframework'
sh "cp gowaku-ios.tar.gz ${env.ARTIFACT}"
}
} }
}
stage('Parallel Upload') {
parallel {
stage('Archive') {
steps { script {
archiveArtifacts(env.ARTIFACT.minus("${env.WORKSPACE}/"))
} }
}
stage('Upload') {
steps { script {
env.PKG_URL = s3.uploadArtifact(env.ARTIFACT)
jenkins.setBuildDesc(ios: env.PKG_URL)
} }
}
}
}
}
post {
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
always { cleanWs() }
}
}

89
ci/Jenkinsfile.linux Normal file
View File

@ -0,0 +1,89 @@
library 'status-jenkins-lib@v1.7.0'
pipeline {
agent {
label 'linux && nix-2.11 && x86_64'
}
options {
timestamps()
disableConcurrentBuilds()
/* Prevent Jenkins jobs from running forever */
timeout(time: 30, unit: 'MINUTES')
/* Go requires a certain directory structure */
checkoutToSubdirectory('src/github.com/waku-org/go-waku')
/* Limit builds retained */
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '20',
artifactNumToKeepStr: '10',
))
/* Allows combined build to copy */
copyArtifactPermission('/go-waku/*')
}
environment {
TARGET = 'linux'
REPO = "${env.WORKSPACE}/src/github.com/waku-org/go-waku"
GOCACHE = "${env.WORKSPACE_TMP}/go-build"
GOPATH = "${env.WORKSPACE}"
PATH = "${env.PATH}:${env.GOPATH}/bin"
}
stages {
stage('Prep') {
steps { script { dir(env.REPO) {
env.DEB_ARTIFACT = "${env.REPO}/pkg/" + utils.pkgFilename(
name: "go-waku",
type: "x86_64",
ext: "deb"
)
} } }
}
stage('Build') {
steps { script { dir(env.REPO) {
nix.develop('make build')
} } }
}
stage('Build examples') {
steps { script { dir(env.REPO) {
nix.develop('make build-example')
} } }
}
stage('Package') {
steps { script { dir(env.REPO) {
dir('./scripts/linux') {
nix.develop('./fpm-build.sh', attr: 'fpm')
}
dir('build') {
sh "cp gowaku*.deb ${env.DEB_ARTIFACT}"
}
} } }
}
stage('Parallel Upload') {
parallel {
stage('Archive') {
steps { script {
archiveArtifacts(env.DEB_ARTIFACT.minus("${env.WORKSPACE}/"))
} }
}
stage('Upload') {
steps { script {
env.PKG_URL = s3.uploadArtifact(env.DEB_ARTIFACT)
jenkins.setBuildDesc(x86_64_deb: env.PKG_URL)
} }
}
}
}
}
post {
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
always { cleanWs() }
}
}

View File

@ -1,13 +1,12 @@
library 'status-jenkins-lib@v1.9.26'
library 'status-jenkins-lib@v1.7.0'
pipeline {
agent {
label 'linux && nix-2.24 && x86_64'
label 'linux && nix-2.11 && x86_64'
}
options {
timestamps()
disableRestartFromStage()
disableConcurrentBuilds()
/* Prevent Jenkins jobs from running forever */
timeout(time: 30, unit: 'MINUTES')
@ -28,7 +27,10 @@ pipeline {
stages {
stage('Build') {
steps { script {
nix.flake('node')
sh("""#!/usr/bin/env bash
${nix._sourceProfileInline()}
nix build --print-out-paths .#node
""")
} }
}
stage('Check') {
@ -43,12 +45,15 @@ pipeline {
stages {
stage('Build') {
steps { script {
nix.flake('static-library')
sh("""#!/usr/bin/env bash
${nix._sourceProfileInline()}
nix build --print-out-paths .#library
""")
} }
}
stage('Check') {
steps {
sh 'readelf -h ./result/bin/libgowaku.a'
sh 'ldd ./result/bin/c'
}
}
}

102
ci/Jenkinsfile.tests Normal file
View File

@ -0,0 +1,102 @@
library 'status-jenkins-lib@v1.7.0'
pipeline {
agent {
label 'linux && nix-2.11 && x86_64'
}
options {
timestamps()
disableConcurrentBuilds()
/* Prevent Jenkins jobs from running forever */
timeout(time: 30, unit: 'MINUTES')
/* Go requires a certain directory structure */
checkoutToSubdirectory('src/github.com/waku-org/go-waku')
buildDiscarder(logRotator(
numToKeepStr: '10',
daysToKeepStr: '30',
))
}
/* WARNING: Defining parameters here with the ?: trick causes them to remember last value. */
parameters {
booleanParam(
name: 'RACE',
description: 'Run tests with check for race condition.',
defaultValue: getRaceDefault()
)
}
environment {
TARGET = 'tests'
REPO = "${env.WORKSPACE}/src/github.com/waku-org/go-waku"
GOCACHE = "${env.WORKSPACE_TMP}/go-build"
GOPATH = "${env.WORKSPACE}/go"
PATH = "${env.PATH}:${env.GOPATH}/bin"
/* Necesary to avoid cache poisoning by other builds. */
GOLANGCI_LINT_CACHE = "${env.WORKSPACE_TMP}/golangci-lint"
/* Ganache config */
GANACHE_RPC_PORT = "${8989 + env.EXECUTOR_NUMBER.toInteger()}"
GANACHE_MNEMONIC = 'swim relax risk shy chimney please usual search industry board music segment'
}
stages {
stage('Lint') {
steps { script { dir(env.REPO) {
nix.develop('make lint', pure: false)
} } }
}
stage('Test') {
steps { script { dir(env.REPO) {
if (params.RACE) {
nix.develop('make test-with-race', pure: false)
}else {
nix.develop('make test-ci', pure: false)
}
} } }
}
stage('Ganache') {
steps { script {
ganache = docker.image(
'trufflesuite/ganache:v7.4.1'
).run(
"-p 127.0.0.1:${env.GANACHE_RPC_PORT}:8545",
"-m='${GANACHE_MNEMONIC}'"
)
} }
}
stage('On-chain tests') {
environment {
GANACHE_NETWORK_RPC_URL = "ws://localhost:${env.GANACHE_RPC_PORT}"
}
steps { script { dir(env.REPO) {
if (params.RACE) {
nix.develop('make test-onchain-with-race', pure: false)
}else {
nix.develop('make test-onchain', pure: false)
}
} } }
}
}
post {
always { script { /* No artifact but a PKG_URL is necessary. */
env.PKG_URL = "${currentBuild.absoluteUrl}consoleText"
} }
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
cleanup { script {
cleanWs()
catchError {
ganache.stop()
}
} }
}
}
def Boolean getRaceDefault() {
return env.JOB_NAME.split('/').contains('race')
}

View File

@ -6,7 +6,6 @@ import (
cli "github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"github.com/waku-org/go-waku/waku/cliutils"
"github.com/waku-org/go-waku/waku/v2/node"
)
var (
@ -33,12 +32,6 @@ var (
Destination: &options.MaxPeerConnections,
EnvVars: []string{"WAKUNODE2_MAX_CONNECTIONS"},
})
PeerStoreCapacity = altsrc.NewIntFlag(&cli.IntFlag{
Name: "peer-store-capacity",
Usage: "Maximum stored peers in the peerstore.",
Destination: &options.PeerStoreCapacity,
EnvVars: []string{"WAKUNODE2_PEERSTORE_CAPACITY"},
})
WebsocketSupport = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "websocket-support",
Aliases: []string{"ws"},
@ -122,13 +115,6 @@ var (
Destination: &options.KeyPasswd,
EnvVars: []string{"WAKUNODE2_KEY_PASSWORD"},
})
ClusterID = altsrc.NewUintFlag(&cli.UintFlag{
Name: "cluster-id",
Value: 0,
Usage: "Cluster id that the node is running in. Node in a different cluster id is disconnected.",
Destination: &options.ClusterID,
EnvVars: []string{"WAKUNODE2_CLUSTER_ID"},
})
StaticNode = cliutils.NewGenericFlagMultiValue(&cli.GenericFlag{
Name: "staticnode",
Usage: "Multiaddr of peer to directly connect with. Option may be repeated",
@ -185,14 +171,6 @@ var (
Destination: &options.CircuitRelay,
EnvVars: []string{"WAKUNODE2_CIRCUIT_RELAY"},
})
ForceReachability = altsrc.NewStringFlag(&cli.StringFlag{
Name: "force-reachability",
Usage: "Force the node reachability. WARNING: This flag is created for testing circuit relay and is not meant to be used in production. Use 'public' or 'private'",
Value: "",
Hidden: true,
Destination: &options.ForceReachability,
EnvVars: []string{"WAKUNODE2_REACHABILITY"},
})
ResourceScalingMemoryPercent = altsrc.NewFloat64Flag(&cli.Float64Flag{
Name: "resource-scaling-memory-percentage",
Usage: "Determines the percentage of total accessible memory that wil be dedicated to go-waku. A dedicated node with a lot of RAM could allocate 25% or more memory to go-waku",
@ -235,18 +213,11 @@ var (
})
AgentString = altsrc.NewStringFlag(&cli.StringFlag{
Name: "agent-string",
Value: node.UserAgent,
Value: "go-waku",
Usage: "client id to advertise",
Destination: &options.UserAgent,
EnvVars: []string{"WAKUNODE2_AGENT_STRING"},
})
IPColocationLimit = altsrc.NewIntFlag(&cli.IntFlag{
Name: "ip-colocation-limit",
Value: node.DefaultMaxConnectionsPerIP,
Usage: "max number of allowed peers from the same IP. Set it to 0 to remove the limitation.",
Destination: &options.IPColocationLimit,
EnvVars: []string{"WAKUNODE2_IP_COLOCATION_LIMIT"},
})
Relay = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "relay",
Value: true,
@ -294,13 +265,6 @@ var (
Destination: &options.Relay.MinRelayPeersToPublish,
EnvVars: []string{"WAKUNODE2_MIN_RELAY_PEERS_TO_PUBLISH"},
})
MaxRelayMsgSize = altsrc.NewStringFlag(&cli.StringFlag{
Name: "max-msg-size",
Value: "150KB",
Usage: "Maximum message size. Supported formats are B, KiB, KB, MiB. If no suffix, default is bytes",
Destination: &options.Relay.MaxMsgSize,
EnvVars: []string{"WAKUNODE2_MAX_RELAY_MSG_SIZE"},
})
StoreNodeFlag = cliutils.NewGenericFlagMultiValue(&cli.GenericFlag{
Name: "storenode",
Usage: "Multiaddr of a peer that supports store protocol. Option may be repeated",
@ -336,6 +300,12 @@ var (
Destination: &options.Store.DatabaseURL,
EnvVars: []string{"WAKUNODE2_STORE_MESSAGE_DB_URL"},
})
StoreMessageDBVacuum = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "store-message-db-vacuum",
Usage: "Enable database vacuuming at start.",
Destination: &options.Store.Vacuum,
EnvVars: []string{"WAKUNODE2_STORE_MESSAGE_DB_VACUUM"},
})
StoreMessageDBMigration = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "store-message-db-migration",
Usage: "Enable database migration at start.",
@ -364,6 +334,26 @@ var (
Destination: &options.Filter.Timeout,
EnvVars: []string{"WAKUNODE2_FILTER_TIMEOUT"},
})
FilterLegacyFlag = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "legacy-filter",
Usage: "Use filter protocol (legacy)",
Destination: &options.Filter.UseV1,
EnvVars: []string{"WAKUNODE2_USE_LEGACY_FILTER"},
})
FilterLegacyLightClient = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "legacy-filter-light-client",
Usage: "Don't accept legacy filter subscribers",
Destination: &options.Filter.DisableFullNode,
EnvVars: []string{"WAKUNODE2_LEGACY_FILTER_LIGHT_CLIENT"},
})
FilterLegacyNode = cliutils.NewGenericFlagMultiValue(&cli.GenericFlag{
Name: "legacy-filternode",
Usage: "Multiaddr of a peer that supports legacy filter protocol. Option may be repeated",
Value: &cliutils.MultiaddrSlice{
Values: &options.Filter.NodesV1,
},
EnvVars: []string{"WAKUNODE2_LEGACY_FILTERNODE"},
})
LightPush = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "lightpush",
Usage: "Enable lightpush protocol",
@ -445,7 +435,7 @@ var (
})
DNSDiscoveryUrl = altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
Name: "dns-discovery-url",
Usage: "URL for DNS node list in format 'enrtree://<key>@<fqdn>'. Option may be repeated",
Usage: "URL for DNS node list in format 'enrtree://<key>@<fqdn>'",
Destination: &options.DNSDiscovery.URLs,
EnvVars: []string{"WAKUNODE2_DNS_DISCOVERY_URL"},
})
@ -479,6 +469,40 @@ var (
Destination: &options.Metrics.Port,
EnvVars: []string{"WAKUNODE2_METRICS_SERVER_PORT"},
})
RPCFlag = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "rpc",
Usage: "Enable the rpc server",
Destination: &options.RPCServer.Enable,
EnvVars: []string{"WAKUNODE2_RPC"},
})
RPCPort = altsrc.NewIntFlag(&cli.IntFlag{
Name: "rpc-port",
Value: 8545,
Usage: "Listening port of the rpc server",
Destination: &options.RPCServer.Port,
EnvVars: []string{"WAKUNODE2_RPC_PORT"},
})
RPCAddress = altsrc.NewStringFlag(&cli.StringFlag{
Name: "rpc-address",
Value: "127.0.0.1",
Usage: "Listening address of the rpc server",
Destination: &options.RPCServer.Address,
EnvVars: []string{"WAKUNODE2_RPC_ADDRESS"},
})
RPCRelayCacheCapacity = altsrc.NewIntFlag(&cli.IntFlag{
Name: "rpc-relay-cache-capacity",
Value: 30,
Usage: "Capacity of the Relay REST API message cache",
Destination: &options.RPCServer.RelayCacheCapacity,
EnvVars: []string{"WAKUNODE2_RPC_RELAY_CACHE_CAPACITY"},
})
RPCAdmin = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "rpc-admin",
Value: false,
Usage: "Enable access to JSON-RPC Admin API",
Destination: &options.RPCServer.Admin,
EnvVars: []string{"WAKUNODE2_RPC_ADMIN"},
})
RESTFlag = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "rest",
Usage: "Enable Waku REST HTTP server",
@ -501,18 +525,11 @@ var (
})
RESTRelayCacheCapacity = altsrc.NewIntFlag(&cli.IntFlag{
Name: "rest-relay-cache-capacity",
Value: 1000,
Value: 30,
Usage: "Capacity of the Relay REST API message cache",
Destination: &options.RESTServer.RelayCacheCapacity,
EnvVars: []string{"WAKUNODE2_REST_RELAY_CACHE_CAPACITY"},
})
RESTFilterCacheCapacity = altsrc.NewIntFlag(&cli.IntFlag{
Name: "rest-filter-cache-capacity",
Value: 30,
Usage: "Capacity of the Filter REST API message cache",
Destination: &options.RESTServer.FilterCacheCapacity,
EnvVars: []string{"WAKUNODE2_REST_FILTER_CACHE_CAPACITY"},
})
RESTAdmin = altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "rest-admin",
Value: false,

View File

@ -8,8 +8,6 @@ import (
"github.com/waku-org/go-waku/cmd/waku/keygen"
"github.com/waku-org/go-waku/cmd/waku/rlngenerate"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.uber.org/zap"
)
var options NodeOptions
@ -24,7 +22,6 @@ func main() {
TcpPort,
Address,
MaxPeerConnections,
PeerStoreCapacity,
WebsocketSupport,
WebsocketPort,
WebsocketSecurePort,
@ -36,7 +33,6 @@ func main() {
NodeKey,
KeyFile,
KeyPassword,
ClusterID,
StaticNode,
KeepAlive,
PersistPeers,
@ -45,10 +41,8 @@ func main() {
ExtMultiaddresses,
ShowAddresses,
CircuitRelay,
ForceReachability,
ResourceScalingMemoryPercent,
ResourceScalingFDPercent,
IPColocationLimit,
LogLevel,
LogEncoding,
LogOutput,
@ -60,16 +54,19 @@ func main() {
ProtectedTopics,
RelayPeerExchange,
MinRelayPeersToPublish,
MaxRelayMsgSize,
StoreNodeFlag,
StoreFlag,
StoreMessageDBURL,
StoreMessageRetentionTime,
StoreMessageRetentionCapacity,
StoreMessageDBVacuum,
StoreMessageDBMigration,
FilterFlag,
FilterNode,
FilterTimeout,
FilterLegacyFlag,
FilterLegacyNode,
FilterLegacyLightClient,
LightPush,
LightPushNode,
Discv5Discovery,
@ -87,11 +84,15 @@ func main() {
MetricsServer,
MetricsServerAddress,
MetricsServerPort,
RPCFlag,
RPCPort,
RPCAddress,
RPCRelayCacheCapacity,
RPCAdmin,
RESTFlag,
RESTAddress,
RESTPort,
RESTRelayCacheCapacity,
RESTFilterCacheCapacity,
RESTAdmin,
PProf,
}
@ -110,16 +111,7 @@ func main() {
Before: altsrc.InitInputSourceWithContext(cliFlags, altsrc.NewTomlSourceFromFlagFunc("config-file")),
Flags: cliFlags,
Action: func(c *cli.Context) error {
err := Execute(options)
if err != nil {
utils.Logger().Error("failure while executing wakunode", zap.Error(err))
switch e := err.(type) {
case cli.ExitCoder:
return e
case error:
return cli.Exit(err.Error(), 1)
}
}
Execute(options)
return nil
},
Commands: []*cli.Command{

View File

@ -5,6 +5,7 @@ import (
"crypto/ecdsa"
"database/sql"
"encoding/json"
"errors"
"fmt"
"net"
"os"
@ -16,15 +17,15 @@ import (
rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
"github.com/pbnjay/memory"
"github.com/prometheus/client_golang/prometheus"
"github.com/urfave/cli/v2"
"github.com/waku-org/go-waku/waku/persistence/sqlite"
dbutils "github.com/waku-org/go-waku/waku/persistence/utils"
"github.com/waku-org/go-waku/waku/v2/dnsdisc"
wakupeerstore "github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/rendezvous"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode"
dssql "github.com/ipfs/go-ds-sql"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
@ -36,25 +37,32 @@ import (
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds" // nolint: staticcheck
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds"
ws "github.com/libp2p/go-libp2p/p2p/transport/websocket"
"github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/cmd/waku/server/rest"
"github.com/waku-org/go-waku/cmd/waku/server/rpc"
"github.com/waku-org/go-waku/logging"
"github.com/waku-org/go-waku/waku/metrics"
"github.com/waku-org/go-waku/waku/persistence"
"github.com/waku-org/go-waku/waku/v2/dnsdisc"
"github.com/waku-org/go-waku/waku/v2/node"
wprotocol "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter"
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"github.com/waku-org/go-waku/waku/v2/protocol/peer_exchange"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
"github.com/waku-org/go-waku/waku/v2/utils"
humanize "github.com/dustin/go-humanize"
)
func failOnErr(err error, msg string) {
if err != nil {
utils.Logger().Fatal(msg, zap.Error(err))
}
}
func requiresDB(options NodeOptions) bool {
return options.Store.Enable || options.Rendezvous.Enable
}
@ -73,51 +81,31 @@ func scalePerc(value float64) float64 {
const dialTimeout = 7 * time.Second
func nonRecoverErrorMsg(format string, a ...any) error {
err := fmt.Errorf(format, a...)
return nonRecoverError(err)
}
func nonRecoverError(err error) error {
return cli.Exit(err.Error(), 166)
}
// Execute starts a go-waku node with settings determined by the Options parameter
func Execute(options NodeOptions) error {
func Execute(options NodeOptions) {
// Set encoding for logs (console, json, ...)
// Note that libp2p reads the encoding from GOLOG_LOG_FMT env var.
lvl, err := zapcore.ParseLevel(options.LogLevel)
if err != nil {
return err
}
utils.InitLogger(options.LogEncoding, options.LogOutput, "gowaku", lvl)
utils.InitLogger(options.LogEncoding, options.LogOutput)
hostAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", options.Address, options.Port))
if err != nil {
return nonRecoverErrorMsg("invalid host address: %w", err)
}
failOnErr(err, "invalid host address")
prvKey, err := getPrivKey(options)
if err != nil {
return err
}
failOnErr(err, "nodekey error")
p2pPrvKey := utils.EcdsaPrivKeyToSecp256k1PrivKey(prvKey)
id, err := peer.IDFromPublicKey(p2pPrvKey.GetPublic())
if err != nil {
return err
}
failOnErr(err, "deriving peer ID from private key")
logger := utils.Logger().With(logging.HostID("node", id))
var db *sql.DB
var migrationFn func(*sql.DB, *zap.Logger) error
var migrationFn func(*sql.DB) error
if requiresDB(options) && options.Store.Migration {
dbSettings := dbutils.DBSettings{}
db, migrationFn, err = dbutils.ParseURL(options.Store.DatabaseURL, dbSettings, logger)
if err != nil {
return nonRecoverErrorMsg("could not connect to DB: %w", err)
dbSettings := dbutils.DBSettings{
Vacuum: options.Store.Vacuum,
}
db, migrationFn, err = dbutils.ExtractDBAndMigration(options.Store.DatabaseURL, dbSettings, logger)
failOnErr(err, "Could not connect to DB")
}
ctx := context.Background()
@ -128,17 +116,19 @@ func Execute(options NodeOptions) error {
go metricsServer.Start()
}
lvl, err := zapcore.ParseLevel(options.LogLevel)
if err != nil {
failOnErr(err, "log level error")
}
nodeOpts := []node.WakuNodeOption{
node.WithLogger(logger),
node.WithLogLevel(lvl),
node.WithPrivateKey(prvKey),
node.WithHostAddress(hostAddr),
node.WithKeepAlive(10*time.Second, options.KeepAlive),
node.WithKeepAlive(options.KeepAlive),
node.WithMaxPeerConnections(options.MaxPeerConnections),
node.WithPrometheusRegisterer(prometheus.DefaultRegisterer),
node.WithPeerStoreCapacity(options.PeerStoreCapacity),
node.WithMaxConnectionsPerIP(options.IPColocationLimit),
node.WithClusterID(uint16(options.ClusterID)),
}
if len(options.AdvertiseAddresses) != 0 {
nodeOpts = append(nodeOpts, node.WithAdvertiseAddresses(options.AdvertiseAddresses...))
@ -147,9 +137,8 @@ func Execute(options NodeOptions) error {
if options.ExtIP != "" {
ip := net.ParseIP(options.ExtIP)
if ip == nil {
return nonRecoverErrorMsg("could not set external IP address: invalid IP")
failOnErr(errors.New("invalid IP address"), "could not set external IP address")
}
nodeOpts = append(nodeOpts, node.WithExternalIP(ip))
}
@ -166,9 +155,7 @@ func Execute(options NodeOptions) error {
limits := rcmgr.DefaultLimits // Default memory limit: 1/8th of total memory, minimum 128MB, maximum 1GB
scaledLimits := limits.Scale(int64(float64(memory.TotalMemory())*memPerc/100), int(float64(getNumFDs())*fdPerc/100))
resourceManager, err := rcmgr.NewResourceManager(rcmgr.NewFixedLimiter(scaledLimits))
if err != nil {
return fmt.Errorf("could not set resource limits: %w", err)
}
failOnErr(err, "setting resource limits")
libp2pOpts = append(libp2pOpts, libp2p.ResourceManager(resourceManager))
libp2p.SetDefaultServiceLimits(&limits)
@ -182,20 +169,6 @@ func Execute(options NodeOptions) error {
libp2pOpts = append(libp2pOpts, libp2p.EnableRelayService())
}
if options.ForceReachability != "" {
libp2pOpts = append(libp2pOpts, libp2p.EnableRelay())
nodeOpts = append(nodeOpts, node.WithCircuitRelayParams(2*time.Second, 2*time.Second))
if options.ForceReachability == "private" {
logger.Warn("node forced to be unreachable!")
libp2pOpts = append(libp2pOpts, libp2p.ForceReachabilityPrivate())
} else if options.ForceReachability == "public" {
logger.Warn("node forced to be publicly reachable!")
libp2pOpts = append(libp2pOpts, libp2p.ForceReachabilityPublic())
} else {
return nonRecoverErrorMsg("invalid reachability value")
}
}
if options.UserAgent != "" {
libp2pOpts = append(libp2pOpts, libp2p.UserAgent(options.UserAgent))
}
@ -210,23 +183,18 @@ func Execute(options NodeOptions) error {
if options.ShowAddresses {
printListeningAddresses(ctx, nodeOpts, options)
return nil
return
}
if options.Store.Enable && options.PersistPeers {
// Create persistent peerstore
queries, err := dbutils.NewQueries("peerstore", db)
if err != nil {
return nonRecoverErrorMsg("could not setup persistent peerstore database: %w", err)
}
queries, err := sqlite.NewQueries("peerstore", db)
failOnErr(err, "Peerstore")
datastore := dssql.NewDatastore(db, queries)
opts := pstoreds.DefaultOpts()
peerStore, err := pstoreds.NewPeerstore(ctx, datastore, opts)
if err != nil {
return nonRecoverErrorMsg("could not create persistent peerstore: %w", err)
}
failOnErr(err, "Peerstore")
nodeOpts = append(nodeOpts, node.WithPeerStore(peerStore))
}
@ -234,15 +202,10 @@ func Execute(options NodeOptions) error {
nodeOpts = append(nodeOpts, node.WithLibP2POptions(libp2pOpts...))
nodeOpts = append(nodeOpts, node.WithNTP())
maxMsgSize := parseMsgSizeConfig(options.Relay.MaxMsgSize)
if options.Relay.Enable {
var wakurelayopts []pubsub.Option
wakurelayopts = append(wakurelayopts, pubsub.WithPeerExchange(options.Relay.PeerExchange))
wakurelayopts = append(wakurelayopts, pubsub.WithMaxMessageSize(maxMsgSize))
nodeOpts = append(nodeOpts, node.WithWakuRelayAndMinPeers(options.Relay.MinRelayPeersToPublish, wakurelayopts...))
nodeOpts = append(nodeOpts, node.WithMaxMsgSize(maxMsgSize))
}
nodeOpts = append(nodeOpts, node.WithWakuFilterLightNode())
@ -251,6 +214,10 @@ func Execute(options NodeOptions) error {
nodeOpts = append(nodeOpts, node.WithWakuFilterFullNode(filter.WithTimeout(options.Filter.Timeout)))
}
if options.Filter.UseV1 {
nodeOpts = append(nodeOpts, node.WithLegacyWakuFilter(!options.Filter.DisableFullNode, legacy_filter.WithTimeout(options.Filter.Timeout)))
}
var dbStore *persistence.DBStore
if requiresDB(options) {
dbOptions := []persistence.DBOption{
@ -263,10 +230,7 @@ func Execute(options NodeOptions) error {
}
dbStore, err = persistence.NewDBStore(prometheus.DefaultRegisterer, logger, dbOptions...)
if err != nil {
return nonRecoverErrorMsg("error setting up db store: %w", err)
}
failOnErr(err, "DBStore")
nodeOpts = append(nodeOpts, node.WithMessageProvider(dbStore))
}
@ -279,6 +243,47 @@ func Execute(options NodeOptions) error {
nodeOpts = append(nodeOpts, node.WithLightPush())
}
var discoveredNodes []dnsdisc.DiscoveredNode
if options.DNSDiscovery.Enable {
if len(options.DNSDiscovery.URLs.Value()) != 0 {
for _, url := range options.DNSDiscovery.URLs.Value() {
logger.Info("attempting DNS discovery with ", zap.String("URL", url))
nodes, err := dnsdisc.RetrieveNodes(ctx, url, dnsdisc.WithNameserver(options.DNSDiscovery.Nameserver))
if err != nil {
logger.Warn("dns discovery error ", zap.Error(err))
} else {
var discPeerInfo []peer.AddrInfo
for _, n := range nodes {
discPeerInfo = append(discPeerInfo, n.PeerInfo)
}
logger.Info("found dns entries ", zap.Any("nodes", discPeerInfo))
discoveredNodes = append(discoveredNodes, nodes...)
}
}
} else {
logger.Fatal("DNS discovery URL is required")
}
}
if options.DiscV5.Enable {
var bootnodes []*enode.Node
for _, addr := range options.DiscV5.Nodes.Value() {
bootnode, err := enode.Parse(enode.ValidSchemes, addr)
if err != nil {
logger.Fatal("parsing ENR", zap.Error(err))
}
bootnodes = append(bootnodes, bootnode)
}
for _, n := range discoveredNodes {
if n.ENR != nil {
bootnodes = append(bootnodes, n.ENR)
}
}
nodeOpts = append(nodeOpts, node.WithDiscoveryV5(options.DiscV5.Port, bootnodes, options.DiscV5.AutoUpdate))
}
if options.PeerExchange.Enable {
nodeOpts = append(nodeOpts, node.WithPeerExchange())
}
@ -288,80 +293,102 @@ func Execute(options NodeOptions) error {
nodeOpts = append(nodeOpts, node.WithRendezvous(rdb))
}
checkForRLN(logger, options, &nodeOpts)
wakuNode, err := node.New(nodeOpts...)
utils.Logger().Info("Version details ", zap.String("version", node.Version), zap.String("commit", node.GitCommit))
if err = checkForRLN(logger, options, &nodeOpts); err != nil {
return nonRecoverError(err)
}
var discoveredNodes []dnsdisc.DiscoveredNode
if options.DNSDiscovery.Enable {
if len(options.DNSDiscovery.URLs.Value()) == 0 {
return nonRecoverErrorMsg("DNS discovery URL is required")
}
discoveredNodes = node.GetNodesFromDNSDiscovery(logger, ctx, options.DNSDiscovery.Nameserver, options.DNSDiscovery.URLs.Value())
}
if options.DiscV5.Enable {
discv5Opts, err := node.GetDiscv5Option(discoveredNodes, options.DiscV5.Nodes.Value(), options.DiscV5.Port, options.DiscV5.AutoUpdate)
if err != nil {
logger.Fatal("parsing ENR", zap.Error(err))
}
nodeOpts = append(nodeOpts, discv5Opts)
}
failOnErr(err, "Wakunode")
//Process pubSub and contentTopics specified and arrive at all corresponding pubSubTopics
pubSubTopicMap, err := processTopics(options)
if err != nil {
return nonRecoverError(err)
}
pubSubTopicMap := processTopics(options)
pubSubTopicMapKeys := make([]string, 0, len(pubSubTopicMap))
for k := range pubSubTopicMap {
pubSubTopicMapKeys = append(pubSubTopicMapKeys, k)
}
rs, err := wprotocol.TopicsToRelayShards(pubSubTopicMapKeys...)
if err == nil {
if len(rs) == 1 {
nodeOpts = append(nodeOpts, node.WithShards(rs[0].ShardIDs))
} else {
logger.Warn("could not set ENR shard info", zap.String("error", "invalid number of clusters found"), zap.Int("numClusters", len(rs)))
}
} else {
logger.Warn("could not obtain list of shards", zap.Error(err))
}
wakuNode, err := node.New(nodeOpts...)
if err != nil {
return fmt.Errorf("could not instantiate waku: %w", err)
if options.Filter.UseV1 {
addStaticPeers(wakuNode, options.Filter.NodesV1, pubSubTopicMapKeys, legacy_filter.FilterID_v20beta1)
}
if err = wakuNode.Start(ctx); err != nil {
return nonRecoverError(err)
logger.Fatal("starting waku node", zap.Error(err))
}
for _, d := range discoveredNodes {
wakuNode.AddDiscoveredPeer(d.PeerID, d.PeerInfo.Addrs, wakupeerstore.DNSDiscovery, nil, d.ENR, true)
wakuNode.AddDiscoveredPeer(d.PeerID, d.PeerInfo.Addrs, wakupeerstore.DNSDiscovery, nil)
}
//For now assuming that static peers added support/listen on all topics specified via commandLine.
staticPeers := map[protocol.ID][]multiaddr.Multiaddr{
legacy_store.StoreID_v20beta4: options.Store.Nodes,
lightpush.LightPushID_v20beta1: options.LightPush.Nodes,
rendezvous.RendezvousID: options.Rendezvous.Nodes,
filter.FilterSubscribeID_v20beta1: options.Filter.Nodes,
}
for protocolID, peers := range staticPeers {
if err = addStaticPeers(wakuNode, peers, pubSubTopicMapKeys, protocolID); err != nil {
return err
}
}
addStaticPeers(wakuNode, options.Store.Nodes, pubSubTopicMapKeys, store.StoreID_v20beta4)
addStaticPeers(wakuNode, options.LightPush.Nodes, pubSubTopicMapKeys, lightpush.LightPushID_v20beta1)
addStaticPeers(wakuNode, options.Rendezvous.Nodes, pubSubTopicMapKeys, rendezvous.RendezvousID)
addStaticPeers(wakuNode, options.Filter.Nodes, pubSubTopicMapKeys, filter.FilterSubscribeID_v20beta1)
var wg sync.WaitGroup
if options.Relay.Enable {
if err = handleRelayTopics(ctx, &wg, wakuNode, pubSubTopicMap); err != nil {
return err
for nodeTopic := range pubSubTopicMap {
nodeTopic := nodeTopic
sub, err := wakuNode.Relay().SubscribeToTopic(ctx, nodeTopic)
failOnErr(err, "Error subscring to topic")
sub.Unsubscribe()
if len(options.Rendezvous.Nodes) != 0 {
// Register the node in rendezvous point
iter := rendezvous.NewRendezvousPointIterator(options.Rendezvous.Nodes)
wg.Add(1)
go func(nodeTopic string) {
t := time.NewTicker(rendezvous.RegisterDefaultTTL)
defer t.Stop()
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case <-t.C:
// Register in rendezvous points periodically
wakuNode.Rendezvous().RegisterWithNamespace(ctx, nodeTopic, iter.RendezvousPoints())
}
}
}(nodeTopic)
wg.Add(1)
go func(nodeTopic string) {
defer wg.Done()
desiredOutDegree := wakuNode.Relay().Params().D
t := time.NewTicker(7 * time.Second)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
peerCnt := len(wakuNode.Relay().PubSub().ListPeers(nodeTopic))
peersToFind := desiredOutDegree - peerCnt
if peersToFind <= 0 {
continue
}
rp := <-iter.Next(ctx)
if rp == nil {
continue
}
ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
wakuNode.Rendezvous().DiscoverWithNamespace(ctx, nodeTopic, rp, peersToFind)
cancel()
}
}
}(nodeTopic)
}
}
for _, protectedTopic := range options.Relay.ProtectedTopics {
err := wakuNode.Relay().AddSignedTopicValidator(protectedTopic.Topic, protectedTopic.PublicKey)
failOnErr(err, "Error adding signed topic validator")
}
}
@ -386,7 +413,7 @@ func Execute(options NodeOptions) error {
if options.PeerExchange.Enable && options.PeerExchange.Node != nil {
logger.Info("retrieving peer info via peer exchange protocol")
peerID, err := wakuNode.AddPeer([]multiaddr.Multiaddr{*options.PeerExchange.Node}, wakupeerstore.Static,
peerID, err := wakuNode.AddPeer(*options.PeerExchange.Node, wakupeerstore.Static,
pubSubTopicMapKeys, peer_exchange.PeerExchangeID_v20alpha1)
if err != nil {
logger.Error("adding peer exchange peer", logging.MultiAddrs("node", *options.PeerExchange.Node), zap.Error(err))
@ -398,17 +425,30 @@ func Execute(options NodeOptions) error {
}
}
if len(discoveredNodes) != 0 {
for _, n := range discoveredNodes {
go func(ctx context.Context, info peer.AddrInfo) {
ctx, cancel := context.WithTimeout(ctx, dialTimeout)
defer cancel()
err = wakuNode.DialPeerWithInfo(ctx, info)
if err != nil {
logger.Error("dialing peer", logging.HostID("peer", info.ID), zap.Error(err))
}
}(ctx, n.PeerInfo)
}
}
var rpcServer *rpc.WakuRPC
if options.RPCServer.Enable {
rpcServer = rpc.NewWakuRPC(wakuNode, options.RPCServer.Address, options.RPCServer.Port, options.RPCServer.Admin, options.PProf, options.RPCServer.RelayCacheCapacity, logger)
rpcServer.Start()
}
var restServer *rest.WakuRest
if options.RESTServer.Enable {
wg.Add(1)
restConfig := rest.RestConfig{Address: options.RESTServer.Address,
Port: uint(options.RESTServer.Port),
EnablePProf: options.PProf,
EnableAdmin: options.RESTServer.Admin,
RelayCacheCapacity: uint(options.RESTServer.RelayCacheCapacity),
FilterCacheCapacity: uint(options.RESTServer.FilterCacheCapacity)}
restServer = rest.NewWakuRest(wakuNode, restConfig, logger)
restServer = rest.NewWakuRest(wakuNode, options.RESTServer.Address, options.RESTServer.Port, options.PProf, options.RESTServer.RelayCacheCapacity, logger)
restServer.Start(ctx, &wg)
}
@ -424,69 +464,62 @@ func Execute(options NodeOptions) error {
// shut the node down
wakuNode.Stop()
if options.RPCServer.Enable {
err := rpcServer.Stop(ctx)
failOnErr(err, "RPCClose")
}
if options.RESTServer.Enable {
if err := restServer.Stop(ctx); err != nil {
return err
}
err := restServer.Stop(ctx)
failOnErr(err, "RESTClose")
}
if options.Metrics.Enable {
if err = metricsServer.Stop(ctx); err != nil {
return err
}
err = metricsServer.Stop(ctx)
failOnErr(err, "MetricsClose")
}
if db != nil {
if err = db.Close(); err != nil {
return err
}
if options.Store.Enable {
err = db.Close()
failOnErr(err, "DBClose")
}
return nil
}
func processTopics(options NodeOptions) (map[string][]string, error) {
func processTopics(options NodeOptions) map[string]struct{} {
//Using a map to avoid duplicate pub-sub topics that can result from autosharding
// or same-topic being passed twice.
pubSubTopicMap := make(map[string][]string)
pubSubTopicMap := make(map[string]struct{})
for _, topic := range options.Relay.Topics.Value() {
pubSubTopicMap[topic] = []string{}
pubSubTopicMap[topic] = struct{}{}
}
for _, topic := range options.Relay.PubSubTopics.Value() {
pubSubTopicMap[topic] = []string{}
pubSubTopicMap[topic] = struct{}{}
}
//Get pubSub topics from contentTopics if they are as per autosharding
for _, cTopic := range options.Relay.ContentTopics.Value() {
contentTopic, err := wprotocol.StringToContentTopic(cTopic)
if err != nil {
return nil, err
failOnErr(err, "failed to parse content topic")
}
pTopic := wprotocol.GetShardFromContentTopic(contentTopic, wprotocol.GenerationZeroShardsCount)
if _, ok := pubSubTopicMap[pTopic.String()]; !ok {
pubSubTopicMap[pTopic.String()] = []string{}
}
pubSubTopicMap[pTopic.String()] = append(pubSubTopicMap[pTopic.String()], cTopic)
pubSubTopicMap[pTopic.String()] = struct{}{}
}
//If no topics are passed, then use default waku topic.
if len(pubSubTopicMap) == 0 && options.ClusterID == 0 {
pubSubTopicMap[relay.DefaultWakuTopic] = []string{}
if len(pubSubTopicMap) == 0 {
pubSubTopicMap[relay.DefaultWakuTopic] = struct{}{}
}
return pubSubTopicMap, nil
return pubSubTopicMap
}
func addStaticPeers(wakuNode *node.WakuNode, addresses []multiaddr.Multiaddr, pubSubTopics []string, protocols ...protocol.ID) error {
func addStaticPeers(wakuNode *node.WakuNode, addresses []multiaddr.Multiaddr, pubSubTopics []string, protocols ...protocol.ID) {
for _, addr := range addresses {
_, err := wakuNode.AddPeer([]multiaddr.Multiaddr{addr}, wakupeerstore.Static, pubSubTopics, protocols...)
if err != nil {
return fmt.Errorf("could not add static peer: %w", err)
}
_, err := wakuNode.AddPeer(addr, wakupeerstore.Static, pubSubTopics, protocols...)
failOnErr(err, "error adding peer")
}
return nil
}
func loadPrivateKeyFromFile(path string, passwd string) (*ecdsa.PrivateKey, error) {
@ -571,12 +604,3 @@ func printListeningAddresses(ctx context.Context, nodeOpts []node.WakuNodeOption
}
}
func parseMsgSizeConfig(msgSizeConfig string) int {
msgSize, err := humanize.ParseBytes(msgSizeConfig)
if err != nil {
msgSize = 0
}
return int(msgSize)
}

View File

@ -8,7 +8,6 @@ import (
"go.uber.org/zap"
)
func checkForRLN(logger *zap.Logger, options NodeOptions, nodeOpts *[]node.WakuNodeOption) error {
func checkForRLN(logger *zap.Logger, options NodeOptions, nodeOpts *[]node.WakuNodeOption) {
// Do nothing
return nil
}

View File

@ -11,12 +11,11 @@ import (
"go.uber.org/zap"
)
func checkForRLN(logger *zap.Logger, options NodeOptions, nodeOpts *[]node.WakuNodeOption) error {
func checkForRLN(logger *zap.Logger, options NodeOptions, nodeOpts *[]node.WakuNodeOption) {
if options.RLNRelay.Enable {
if !options.Relay.Enable {
return errors.New("waku relay is required to enable RLN relay")
failOnErr(errors.New("relay not available"), "Could not enable RLN Relay")
}
if !options.RLNRelay.Dynamic {
*nodeOpts = append(*nodeOpts, node.WithStaticRLNRelay((*rln.MembershipIndex)(options.RLNRelay.MembershipIndex), nil))
} else {
@ -33,6 +32,4 @@ func checkForRLN(logger *zap.Logger, options NodeOptions, nodeOpts *[]node.WakuN
))
}
}
return nil
}

View File

@ -30,7 +30,6 @@ type RelayOptions struct {
ContentTopics cli.StringSlice
PeerExchange bool
MinRelayPeersToPublish int
MaxMsgSize string
}
// RLNRelayOptions are settings used to enable RLN Relay. This is a protocol
@ -53,8 +52,10 @@ type RLNRelayOptions struct {
// restricted devices.
type FilterOptions struct {
Enable bool
UseV1 bool
DisableFullNode bool
Nodes []multiaddr.Multiaddr
NodesV1 []multiaddr.Multiaddr
Timeout time.Duration
}
@ -79,6 +80,7 @@ type StoreOptions struct {
RetentionMaxMessages int
//ResumeNodes []multiaddr.Multiaddr
Nodes []multiaddr.Multiaddr
Vacuum bool
Migration bool
}
@ -99,14 +101,22 @@ type MetricsOptions struct {
Port int
}
// RPCServerOptions are settings used to start a json rpc server
type RPCServerOptions struct {
Enable bool
Port int
Address string
Admin bool
RelayCacheCapacity int
}
// RESTServerOptions are settings used to start a rest http server
type RESTServerOptions struct {
Enable bool
Port int
Address string
Admin bool
RelayCacheCapacity int
FilterCacheCapacity int
Enable bool
Port int
Address string
Admin bool
RelayCacheCapacity int
}
// WSOptions are settings used for enabling websockets and secure websockets
@ -138,7 +148,6 @@ type RendezvousOptions struct {
type NodeOptions struct {
Port int
Address string
ClusterID uint
DNS4DomainName string
NodeKey *ecdsa.PrivateKey
KeyFile string
@ -148,7 +157,6 @@ type NodeOptions struct {
AdvertiseAddresses []multiaddr.Multiaddr
ShowAddresses bool
CircuitRelay bool
ForceReachability string
ResourceScalingMemoryPercent float64
ResourceScalingFDPercent float64
LogLevel string
@ -160,8 +168,6 @@ type NodeOptions struct {
UserAgent string
PProf bool
MaxPeerConnections int
PeerStoreCapacity int
IPColocationLimit int
PeerExchange PeerExchangeOptions
Websocket WSOptions
@ -174,5 +180,6 @@ type NodeOptions struct {
DNSDiscovery DNSDiscoveryOptions
Rendezvous RendezvousOptions
Metrics MetricsOptions
RPCServer RPCServerOptions
RESTServer RESTServerOptions
}

View File

@ -1,82 +0,0 @@
package main
import (
"context"
"sync"
"time"
"github.com/waku-org/go-waku/waku/v2/node"
wprotocol "github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/rendezvous"
)
func handleRelayTopics(ctx context.Context, wg *sync.WaitGroup, wakuNode *node.WakuNode, pubSubTopicMap map[string][]string) error {
for nodeTopic, cTopics := range pubSubTopicMap {
nodeTopic := nodeTopic
_, err := wakuNode.Relay().Subscribe(ctx, wprotocol.NewContentFilter(nodeTopic, cTopics...), relay.WithoutConsumer())
if err != nil {
return err
}
if len(options.Rendezvous.Nodes) != 0 {
// Register the node in rendezvous point
iter := rendezvous.NewRendezvousPointIterator(options.Rendezvous.Nodes)
wg.Add(1)
go func(nodeTopic string) {
t := time.NewTicker(rendezvous.RegisterDefaultTTL)
defer t.Stop()
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case <-t.C:
// Register in rendezvous points periodically
wakuNode.Rendezvous().RegisterWithNamespace(ctx, nodeTopic, iter.RendezvousPoints())
}
}
}(nodeTopic)
wg.Add(1)
go func(nodeTopic string) {
defer wg.Done()
desiredOutDegree := wakuNode.Relay().Params().D
t := time.NewTicker(7 * time.Second)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
peerCnt := len(wakuNode.Relay().PubSub().ListPeers(nodeTopic))
peersToFind := desiredOutDegree - peerCnt
if peersToFind <= 0 {
continue
}
rp := <-iter.Next(ctx)
if rp == nil {
continue
}
ctx, cancel := context.WithTimeout(ctx, 7*time.Second)
wakuNode.Rendezvous().DiscoverWithNamespace(ctx, nodeTopic, rp, peersToFind)
cancel()
}
}
}(nodeTopic)
}
}
// Protected topics
for _, protectedTopic := range options.Relay.ProtectedTopics {
if err := wakuNode.Relay().AddSignedTopicValidator(protectedTopic.Topic, protectedTopic.PublicKey); err != nil {
return nonRecoverErrorMsg("could not add signed topic validator: %w", err)
}
}
return nil
}

View File

@ -77,14 +77,14 @@ func execute(ctx context.Context) error {
if logger.Level() == zap.DebugLevel {
logger.Info("registered credentials into the membership contract",
logging.HexBytes("IDCommitment", identityCredential.IDCommitment[:]),
logging.HexBytes("IDNullifier", identityCredential.IDNullifier[:]),
logging.HexBytes("IDSecretHash", identityCredential.IDSecretHash[:]),
logging.HexBytes("IDTrapDoor", identityCredential.IDTrapdoor[:]),
logging.HexString("IDCommitment", identityCredential.IDCommitment[:]),
logging.HexString("IDNullifier", identityCredential.IDNullifier[:]),
logging.HexString("IDSecretHash", identityCredential.IDSecretHash[:]),
logging.HexString("IDTrapDoor", identityCredential.IDTrapdoor[:]),
zap.Uint("index", membershipIndex),
)
} else {
logger.Info("registered credentials into the membership contract", logging.HexBytes("idCommitment", identityCredential.IDCommitment[:]), zap.Uint("index", membershipIndex))
logger.Info("registered credentials into the membership contract", logging.HexString("idCommitment", identityCredential.IDCommitment[:]), zap.Uint("index", membershipIndex))
}
web3Config.ETHClient.Close()

View File

@ -124,7 +124,7 @@ func register(ctx context.Context, web3Config *web3.Config, idComm rln.IDCommitm
var eventIDComm rln.IDCommitment = rln.BigIntToBytes32(evt.IdCommitment)
log.Debug("information extracted from tx log", zap.Uint64("blockNumber", evt.Raw.BlockNumber), logging.HexBytes("idCommitment", eventIDComm[:]), zap.Uint64("index", evt.Index.Uint64()))
log.Debug("information extracted from tx log", zap.Uint64("blockNumber", evt.Raw.BlockNumber), logging.HexString("idCommitment", eventIDComm[:]), zap.Uint64("index", evt.Index.Uint64()))
if eventIDComm != idComm {
return 0, errors.New("invalid id commitment key")

View File

@ -1,136 +0,0 @@
package rest
import (
"encoding/json"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/multiformats/go-multiaddr"
ma "github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/cmd/waku/server"
"github.com/waku-org/go-waku/logging"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/peerstore"
waku_proto "github.com/waku-org/go-waku/waku/v2/protocol"
"go.uber.org/zap"
)
type AdminService struct {
node *node.WakuNode
mux *chi.Mux
log *zap.Logger
}
type WakuPeer struct {
ID string `json:"id"`
MultiAddrs []string `json:"multiaddrs"`
Protocols []string `json:"protocols"`
Connected bool `json:"connected"`
PubsubTopics []string `json:"pubsubTopics"`
}
type WakuPeerInfo struct {
MultiAddr string `json:"multiaddr"`
Shards []int `json:"shards"`
Protocols []string `json:"protocols"`
}
const routeAdminV1Peers = "/admin/v1/peers"
func NewAdminService(node *node.WakuNode, m *chi.Mux, log *zap.Logger) *AdminService {
d := &AdminService{
node: node,
mux: m,
log: log,
}
m.Get(routeAdminV1Peers, d.getV1Peers)
m.Post(routeAdminV1Peers, d.postV1Peer)
return d
}
func (a *AdminService) getV1Peers(w http.ResponseWriter, req *http.Request) {
peers, err := a.node.Peers()
if err != nil {
a.log.Error("failed to fetch peers", zap.Error(err))
writeErrOrResponse(w, err, nil)
return
}
a.log.Info("fetched peers", zap.Int("count", len(peers)))
response := make([]WakuPeer, 0)
for _, peer := range peers {
if peer.ID.String() == a.node.Host().ID().String() {
//Skip own node id
continue
}
wPeer := WakuPeer{
ID: peer.ID.String(),
Connected: peer.Connected,
}
for _, addr := range peer.Addrs {
wPeer.MultiAddrs = append(wPeer.MultiAddrs, addr.String())
}
for _, proto := range peer.Protocols {
if !server.IsWakuProtocol(proto) {
a.log.Debug("skipping protocol as it is a non-waku protocol", logging.HostID("peer", peer.ID), zap.String("protocol", string(proto)))
continue
}
wPeer.Protocols = append(wPeer.Protocols, string(proto))
}
wPeer.PubsubTopics = peer.PubsubTopics
response = append(response, wPeer)
}
writeErrOrResponse(w, nil, response)
}
func (a *AdminService) postV1Peer(w http.ResponseWriter, req *http.Request) {
var pInfo WakuPeerInfo
var topics []string
var protos []protocol.ID
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&pInfo); err != nil {
a.log.Error("failed to decode request", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
addr, err := ma.NewMultiaddr(pInfo.MultiAddr)
if err != nil {
a.log.Error("building multiaddr", zap.Error(err))
writeErrOrResponse(w, err, nil)
return
}
for _, shard := range pInfo.Shards {
topic := waku_proto.NewStaticShardingPubsubTopic(a.node.ClusterID(), uint16(shard))
topics = append(topics, topic.String())
}
for _, proto := range pInfo.Protocols {
protos = append(protos, protocol.ID(proto))
}
id, err := a.node.AddPeer([]multiaddr.Multiaddr{addr}, peerstore.Static, topics, protos...)
if err != nil {
a.log.Error("failed to add peer", zap.Error(err))
writeErrOrResponse(w, err, nil)
return
}
a.log.Info("add peer successful", logging.HostID("peerID", id))
pi := peer.AddrInfo{ID: id, Addrs: []ma.Multiaddr{addr}}
err = a.node.Host().Connect(req.Context(), pi)
if err != nil {
a.log.Error("failed to connect to peer", logging.HostID("peerID", id), zap.Error(err))
writeErrOrResponse(w, err, nil)
return
}
writeErrOrResponse(w, nil, nil)
}

View File

@ -1,92 +0,0 @@
openapi: 3.0.3
info:
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team
url: https://forum.vac.dev/
tags:
- name: admin
description: Admin REST API for WakuV2 node
paths:
/admin/v1/peers:
get:
summary: Get connected peers info
description: Retrieve information about connected peers.
operationId: getPeerInfo
tags:
- admin
responses:
'200':
description: Information about a Waku v2 node.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/WakuPeer'
'5XX':
description: Unexpected error.
post:
summary: Adds new peer(s) to connect with
description: Adds new peer(s) to connect with.
operationId: postPeerInfo
tags:
- admin
requestBody:
content:
application/json:
schema:
type: object
items:
$ref: '#/components/schemas/WakuPeerInfo'
responses:
'200':
description: Ok
'400':
description: Cannot connect to one or more peers.
'5XX':
description: Unexpected error.
components:
schemas:
WakuPeerInfo:
type: object
required:
- multiaddr
- shards
- protocols
protocols:
type: array
items:
type: string
shards:
type: array
items:
type: integer
WakuPeer:
type: object
required:
- id
- addrs
- protocols
- connected
properties:
connected:
type: string
addrs:
type: array
items:
type: string
protocols:
type: array
items:
type: string
connected:
type: boolean
pubsubTopics:
type: array
items:
type: string

View File

@ -21,7 +21,7 @@ type InfoReply struct {
}
const routeDebugInfoV1 = "/debug/v1/info"
const routeDebugVersionV1 = "/debug/v1/version"
const routeDebugVersionV1 = "/debug/v1/info"
func NewDebugService(node *node.WakuNode, m *chi.Mux) *DebugService {
d := &DebugService{

View File

@ -1,6 +1,6 @@
openapi: 3.0.3
info:
title: Waku V2 node Debug REST API
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team

View File

@ -1,380 +0,0 @@
package rest
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/waku-org/go-waku/logging"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/peermanager"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"go.uber.org/zap"
)
const filterV2Subscriptions = "/filter/v2/subscriptions"
const filterv2Messages = "/filter/v2/messages"
// FilterService represents the REST service for Filter client
type FilterService struct {
node *node.WakuNode
cancel context.CancelFunc
log *zap.Logger
cache *filterCache
runner *runnerService
}
// Start starts the RelayService
func (s *FilterService) Start(ctx context.Context) {
for _, sub := range s.node.FilterLightnode().Subscriptions() {
s.cache.subscribe(sub.ContentFilter)
}
ctx, cancel := context.WithCancel(ctx)
s.cancel = cancel
s.runner.Start(ctx)
}
// Stop stops the RelayService
func (r *FilterService) Stop() {
if r.cancel == nil {
return
}
r.cancel()
}
// NewFilterService returns an instance of FilterService
func NewFilterService(node *node.WakuNode, m *chi.Mux, cacheCapacity int, log *zap.Logger) *FilterService {
logger := log.Named("filter")
s := &FilterService{
node: node,
log: logger,
cache: newFilterCache(cacheCapacity, logger),
}
m.Route(filterV2Subscriptions, func(r chi.Router) {
r.Get("/", s.ping)
r.Get("/{requestId}", s.ping)
r.Post("/", s.subscribe)
r.Delete("/", s.unsubscribe)
r.Delete("/all", s.unsubscribeAll)
})
m.Route(filterv2Messages, func(r chi.Router) {
r.Get("/{contentTopic}", s.getMessagesByContentTopic)
r.Get("/{pubsubTopic}/{contentTopic}", s.getMessagesByPubsubTopic)
})
s.runner = newRunnerService(node.Broadcaster(), s.cache.addMessage)
return s
}
func convertFilterErrorToHttpStatus(err error) (int, string) {
code := http.StatusInternalServerError
statusDesc := "ping request failed"
filterErrorCode := filter.ExtractCodeFromFilterError(err.Error())
switch filterErrorCode {
case 404:
code = http.StatusNotFound
statusDesc = "peer has no subscription"
case 300:
case 400:
code = http.StatusBadRequest
statusDesc = "bad request format"
case 504:
code = http.StatusGatewayTimeout
case 503:
code = http.StatusServiceUnavailable
}
return code, statusDesc
}
// 400 for bad requestId
// 404 when request failed or no suitable peers
// 200 when ping successful
func (s *FilterService) ping(w http.ResponseWriter, req *http.Request) {
requestID := chi.URLParam(req, "requestId")
if requestID == "" {
writeResponse(w, &filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "bad request id",
}, http.StatusBadRequest)
return
}
// selecting random peer that supports filter protocol
peerId := s.getRandomFilterPeer(req.Context(), requestID, w)
if peerId == "" {
return
}
if err := s.node.FilterLightnode().Ping(req.Context(), peerId, filter.WithPingRequestId([]byte(requestID))); err != nil {
s.log.Error("ping request failed", zap.Error(err))
code, statusDesc := convertFilterErrorToHttpStatus(err)
writeResponse(w, &filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: statusDesc,
}, code)
return
}
// success
writeResponse(w, &filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: http.StatusText(http.StatusOK),
}, http.StatusOK)
}
// same for FilterUnsubscribeRequest
type filterSubscriptionRequest struct {
RequestID string `json:"requestId"`
ContentFilters []string `json:"contentFilters"`
PubsubTopic string `json:"pubsubTopic"`
}
type filterSubscriptionResponse struct {
RequestID string `json:"requestId"`
StatusDesc string `json:"statusDesc"`
}
// 400 on invalid request
// 404 on failed subscription
// 200 on single returned successful subscription
// NOTE: subscribe on filter client randomly selects a peer if missing for given pubSubTopic
func (s *FilterService) subscribe(w http.ResponseWriter, req *http.Request) {
message := filterSubscriptionRequest{}
if !s.readBody(w, req, &message) {
return
}
contentFilter := protocol.NewContentFilter(message.PubsubTopic, message.ContentFilters...)
//
subscriptions, err := s.node.FilterLightnode().Subscribe(req.Context(),
contentFilter,
filter.WithRequestID([]byte(message.RequestID)))
// on partial subscribe failure
if len(subscriptions) > 0 && err != nil {
s.log.Error("partial subscribe failed", zap.Error(err))
// on partial failure
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: err.Error(),
}, http.StatusOK)
}
if err != nil {
s.log.Error("subscription failed", zap.Error(err))
code := filter.ExtractCodeFromFilterError(err.Error())
if code == -1 {
code = http.StatusBadRequest
}
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: "subscription failed",
}, code)
return
}
// on success
s.cache.subscribe(contentFilter)
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: http.StatusText(http.StatusOK),
}, http.StatusOK)
}
// 400 on invalid request
// 500 on failed subscription
// 200 on successful unsubscribe
// NOTE: unsubscribe on filter client will remove subscription from all peers with matching pubSubTopic, if peerId is not provided
// to match functionality in nwaku, we will randomly select a peer that supports filter protocol.
func (s *FilterService) unsubscribe(w http.ResponseWriter, req *http.Request) {
message := filterSubscriptionRequest{} // as pubSubTopics can also be present
if !s.readBody(w, req, &message) {
return
}
peerId := s.getRandomFilterPeer(req.Context(), message.RequestID, w)
if peerId == "" {
return
}
contentFilter := protocol.NewContentFilter(message.PubsubTopic, message.ContentFilters...)
// unsubscribe on filter
result, err := s.node.FilterLightnode().Unsubscribe(
req.Context(),
contentFilter,
filter.WithRequestID([]byte(message.RequestID)),
filter.WithPeer(peerId),
)
if err != nil {
s.log.Error("unsubscribe failed", zap.Error(err))
if result == nil {
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: err.Error(),
}, http.StatusBadRequest)
}
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: err.Error(),
}, http.StatusServiceUnavailable)
return
}
// on success
for cTopic := range contentFilter.ContentTopics {
if !s.node.FilterLightnode().IsListening(contentFilter.PubsubTopic, cTopic) {
s.cache.unsubscribe(contentFilter.PubsubTopic, cTopic)
}
}
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: s.unsubscribeGetMessage(result),
}, http.StatusOK)
}
func (s *FilterService) unsubscribeGetMessage(result *filter.WakuFilterPushResult) string {
if result == nil {
return http.StatusText(http.StatusOK)
}
var peerIds string
ind := 0
for _, entry := range result.Errors() {
if entry.Err != nil {
s.log.Error("can't unsubscribe", logging.HostID("peer", entry.PeerID), zap.Error(entry.Err))
if ind != 0 {
peerIds += ", "
}
peerIds += entry.PeerID.String()
}
ind++
}
if peerIds != "" {
return "can't unsubscribe from " + peerIds
}
return http.StatusText(http.StatusOK)
}
type filterUnsubscribeAllRequest struct {
RequestID string `json:"requestId"`
}
func (s *FilterService) readBody(w http.ResponseWriter, req *http.Request, message interface{}) bool {
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(message); err != nil {
s.log.Error("bad request", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
return false
}
defer req.Body.Close()
return true
}
// 400 on invalid request
// 500 on failed subscription
// 200 on all successful unsubscribe
// unsubscribe all subscriptions for a given peer
func (s *FilterService) unsubscribeAll(w http.ResponseWriter, req *http.Request) {
message := filterUnsubscribeAllRequest{}
if !s.readBody(w, req, &message) {
return
}
peerId := s.getRandomFilterPeer(req.Context(), message.RequestID, w)
if peerId == "" {
return
}
// unsubscribe all subscriptions for a given peer
errCh, err := s.node.FilterLightnode().UnsubscribeAll(
req.Context(),
filter.WithRequestID([]byte(message.RequestID)),
filter.WithPeer(peerId),
)
if err != nil {
s.log.Error("unsubscribeAll failed", zap.Error(err))
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: err.Error(),
}, http.StatusServiceUnavailable)
return
}
// on success
writeResponse(w, filterSubscriptionResponse{
RequestID: message.RequestID,
StatusDesc: s.unsubscribeGetMessage(errCh),
}, http.StatusOK)
}
func (s FilterService) getRandomFilterPeer(ctx context.Context, requestId string, w http.ResponseWriter) peer.ID {
// selecting random peer that supports filter protocol
peerIds, err := s.node.PeerManager().SelectPeers(peermanager.PeerSelectionCriteria{
SelectionType: peermanager.Automatic,
Proto: filter.FilterSubscribeID_v20beta1,
Ctx: ctx,
})
if err != nil {
s.log.Error("selecting peer", zap.Error(err))
writeResponse(w, filterSubscriptionResponse{
RequestID: requestId,
StatusDesc: "No suitable peers",
}, http.StatusServiceUnavailable)
return ""
}
return peerIds[0]
}
func (s *FilterService) getMessagesByContentTopic(w http.ResponseWriter, req *http.Request) {
contentTopic := topicFromPath(w, req, "contentTopic", s.log)
if contentTopic == "" {
return
}
pubsubTopic, err := protocol.GetPubSubTopicFromContentTopic(contentTopic)
if err != nil {
writeGetMessageErr(w, fmt.Errorf("bad content topic"), http.StatusBadRequest, s.log)
return
}
s.getMessages(w, req, pubsubTopic, contentTopic)
}
func (s *FilterService) getMessagesByPubsubTopic(w http.ResponseWriter, req *http.Request) {
contentTopic := topicFromPath(w, req, "contentTopic", s.log)
if contentTopic == "" {
return
}
pubsubTopic := topicFromPath(w, req, "pubsubTopic", s.log)
if pubsubTopic == "" {
return
}
s.getMessages(w, req, pubsubTopic, contentTopic)
}
// 400 on invalid request
// 500 on failed subscription
// 200 on all successful unsubscribe
// unsubscribe all subscriptions for a given peer
func (s *FilterService) getMessages(w http.ResponseWriter, req *http.Request, pubsubTopic, contentTopic string) {
msgs, err := s.cache.getMessages(pubsubTopic, contentTopic)
if err != nil {
writeGetMessageErr(w, err, http.StatusNotFound, s.log)
return
}
writeResponse(w, msgs, http.StatusOK)
}

View File

@ -1,337 +0,0 @@
openapi: 3.0.3
info:
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team
url: https://forum.vac.dev/
tags:
- name: filter
description: Filter REST API for WakuV2 node
paths:
/filter/v2/subscriptions/{requestId}:
get: # get_waku_v2_filter_v2_subscription - ping
summary: Subscriber-ping - a peer can query if there is a registered subscription for it
description: |
Subscriber peer can query its subscription existence on service node.
Returns HTTP200 if exists and HTTP404 if not.
Client must not fill anything but requestId in the request body.
operationId: subscriberPing
tags:
- filter
parameters:
- in: path
name: requestId
required: true
schema:
type: string
description: Id of ping request
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'400':
description: Bad request.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'404':
description: Not found.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'5XX':
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
/filter/v2/subscriptions:
post: # post_waku_v2_filter_v2_subscription
summary: Subscribe a peer to an array of content topics under a pubsubTopic
description: |
Subscribe a peer to an array of content topics under a pubsubTopic.
It is allowed to refresh or add new content topic to an existing subscription.
Fields pubsubTopic and contentFilters must be filled.
operationId: postSubscriptions
tags:
- filter
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscribeRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
# TODO: Review the possible errors of this endpoint
'400':
description: Bad request.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'404':
description: Not found.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'5XX':
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
delete: # delete_waku_v2_filter_v2_subscription
summary: Unsubscribe a peer from content topics
description: |
Unsubscribe a peer from content topics
Only that subscription will be removed which matches existing.
operationId: deleteSubscriptions
tags:
- filter
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/FilterUnsubscribeRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'400':
description: Bad request.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'404':
description: Not found.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'5XX':
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
/filter/v2/subscriptions/all:
delete: # delete_waku_v2_filter_v2_subscription
summary: Unsubscribe a peer from all content topics
description: |
Unsubscribe a peer from all content topics
operationId: deleteAllSubscriptions
tags:
- filter
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/FilterUnsubscribeAllRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'400':
description: Bad request.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'404':
description: Not found.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
'5XX':
description: Unexpected error.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterSubscriptionResponse'
/filter/v2/messages/{contentTopic}:
get: # get_waku_v2_filter_v2_messages
summary: Get the latest messages on the polled content topic
description: Get a list of messages that were received on a subscribed content topic after the last time this method was called.
operationId: getMessagesByTopic
tags:
- filter
parameters:
- in: path
name: contentTopic # Note the name is the same as in the path
required: true
schema:
type: string
description: Content topic of message
responses:
'200':
description: The latest messages on the polled topic.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterGetMessagesResponse'
# TODO: Review the possible errors of this endpoint
'400':
description: Bad request.
content:
text/plain:
schema:
type: string
'404':
description: Not found.
content:
text/plain:
schema:
type: string
'5XX':
description: Unexpected error.
content:
text/plain:
schema:
type: string
/filter/v2/messages/{pubsubTopic}/{contentTopic}:
get: # get_waku_v2_filter_v2_messages
summary: Get the latest messages on the polled pubsub/content topic pair
description: Get a list of messages that were received on a subscribed content topic after the last time this method was called.
operationId: getMessagesByTopic
tags:
- filter
parameters:
- in: path
name: contentTopic # Note the name is the same as in the path
required: true
schema:
type: string
description: Content topic of message
- in: path
name: pubsubTopic # Note the name is the same as in the path
required: true
schema:
type: string
description: pubsub topic of message
responses:
'200':
description: The latest messages on the polled topic.
content:
application/json:
schema:
$ref: '#/components/schemas/FilterGetMessagesResponse'
# TODO: Review the possible errors of this endpoint
'400':
description: Bad request.
content:
text/plain:
schema:
type: string
'404':
description: Not found.
content:
text/plain:
schema:
type: string
'5XX':
description: Unexpected error.
content:
text/plain:
schema:
type: string
components:
PubSubTopic:
type: string
ContentTopic:
type: string
FilterSubscriptionResponse:
type: object
properties:
requestId:
type: string
statusDesc:
type: string
required:
- requestId
FilterSubscribeRequest:
type: object
properties:
requestId:
type: string
contentFilters:
type: array
items:
$ref: '#/components/schemas/ContentTopic'
pubsubTopic:
$ref: "#/components/schemas/PubSubTopic"
required:
- requestId
- contentFilters
- pubsubTopic
FilterUnsubscribeRequest:
type: object
properties:
requestId:
type: string
contentFilters:
type: array
items:
$ref: '#/components/schemas/ContentTopic'
pubsubTopic:
$ref: "#/components/schemas/PubSubTopic"
required:
- requestId
- contentFilters
FilterUnsubscribeAllRequest:
type: object
properties:
requestId:
type: string
required:
- requestId
FilterGetMessagesResponse:
type: array
items:
$ref: '#/components/schemas/FilterWakuMessage'
FilterWakuMessage:
type: object
properties:
payload:
type: string
format: byte
contentTopic:
$ref: '#/components/schemas/ContentTopic'
version:
type: number
timestamp:
type: number
required:
- payload

View File

@ -1,84 +0,0 @@
package rest
import (
"fmt"
"sync"
"github.com/waku-org/go-waku/waku/v2/protocol"
"go.uber.org/zap"
)
type filterCache struct {
capacity int
mu sync.RWMutex
log *zap.Logger
data map[string]map[string][]*RestWakuMessage
}
func newFilterCache(capacity int, log *zap.Logger) *filterCache {
return &filterCache{
capacity: capacity,
data: make(map[string]map[string][]*RestWakuMessage),
log: log.Named("cache"),
}
}
func (c *filterCache) subscribe(contentFilter protocol.ContentFilter) {
c.mu.Lock()
defer c.mu.Unlock()
pubSubTopicMap, _ := protocol.ContentFilterToPubSubTopicMap(contentFilter)
for pubsubTopic, contentTopics := range pubSubTopicMap {
if c.data[pubsubTopic] == nil {
c.data[pubsubTopic] = make(map[string][]*RestWakuMessage)
}
for _, topic := range contentTopics {
if c.data[pubsubTopic][topic] == nil {
c.data[pubsubTopic][topic] = []*RestWakuMessage{}
}
}
}
}
func (c *filterCache) unsubscribe(pubsubTopic string, contentTopic string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.data[pubsubTopic], contentTopic)
}
func (c *filterCache) addMessage(envelope *protocol.Envelope) {
c.mu.Lock()
defer c.mu.Unlock()
pubsubTopic := envelope.PubsubTopic()
contentTopic := envelope.Message().ContentTopic
if c.data[pubsubTopic] == nil || c.data[pubsubTopic][contentTopic] == nil {
return
}
// Keep a specific max number of message per topic
if len(c.data[pubsubTopic][contentTopic]) >= c.capacity {
c.data[pubsubTopic][contentTopic] = c.data[pubsubTopic][contentTopic][1:]
}
message := &RestWakuMessage{}
if err := message.FromProto(envelope.Message()); err != nil {
c.log.Error("converting protobuffer msg into rest msg", zap.Error(err))
return
}
c.data[pubsubTopic][contentTopic] = append(c.data[pubsubTopic][contentTopic], message)
}
func (c *filterCache) getMessages(pubsubTopic string, contentTopic string) ([]*RestWakuMessage, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.data[pubsubTopic] == nil || c.data[pubsubTopic][contentTopic] == nil {
return nil, fmt.Errorf("not subscribed to pubsubTopic:%s contentTopic: %s", pubsubTopic, contentTopic)
}
msgs := c.data[pubsubTopic][contentTopic]
c.data[pubsubTopic][contentTopic] = []*RestWakuMessage{}
return msgs, nil
}

View File

@ -1,393 +0,0 @@
package rest
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/tests"
"github.com/waku-org/go-waku/waku/v2/node"
wakupeerstore "github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/utils"
"golang.org/x/time/rate"
)
func createNode(t *testing.T, opts ...node.WakuNodeOption) *node.WakuNode {
node, err := node.New(opts...)
require.NoError(t, err)
err = node.Start(context.Background())
require.NoError(t, err)
return node
}
// node2 connects to node1
func twoFilterConnectedNodes(t *testing.T, pubSubTopics ...string) (*node.WakuNode, *node.WakuNode) {
node1 := createNode(t, node.WithWakuFilterFullNode(filter.WithFullNodeRateLimiter(rate.Inf, 0))) // full node filter
node2 := createNode(t, node.WithWakuFilterLightNode()) // light node filter
node2.Host().Peerstore().AddAddr(node1.Host().ID(), tests.GetHostAddress(node1.Host()), peerstore.PermanentAddrTTL)
err := node2.Host().Peerstore().AddProtocols(node1.Host().ID(), filter.FilterSubscribeID_v20beta1)
require.NoError(t, err)
err = node2.Host().Peerstore().(*wakupeerstore.WakuPeerstoreImpl).SetPubSubTopics(node1.Host().ID(), pubSubTopics)
require.NoError(t, err)
return node1, node2
}
// test 400, 404 status code for ping rest endpoint
// both requests are not successful
func TestFilterPingFailure(t *testing.T) {
node1, node2 := twoFilterConnectedNodes(t)
defer func() {
node1.Stop()
node2.Stop()
}()
router := chi.NewRouter()
_ = NewFilterService(node2, router, 0, utils.Logger())
// with empty requestID
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/filter/v2/subscriptions/%s", ""), nil)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: "",
StatusDesc: "bad request id",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusBadRequest, rr.Code)
// no subscription with peer
requestID := hex.EncodeToString(protocol.GenerateRequestID())
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("/filter/v2/subscriptions/%s", requestID), nil)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "peer has no subscription",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusNotFound, rr.Code)
}
// create a filter subscription to the peer and try peer that peer
// both steps should be successful
func TestFilterSubscribeAndPing(t *testing.T) {
pubsubTopic := "/waku/2/test/proto"
contentTopics := []string{"test"}
requestID := hex.EncodeToString(protocol.GenerateRequestID())
node1, node2 := twoFilterConnectedNodes(t, pubsubTopic)
defer func() {
node1.Stop()
node2.Stop()
}()
router := chi.NewRouter()
_ = NewFilterService(node2, router, 0, utils.Logger())
// create subscription to peer
rr := httptest.NewRecorder()
reqReader := strings.NewReader(toString(t, filterSubscriptionRequest{
RequestID: requestID,
PubsubTopic: pubsubTopic,
ContentFilters: contentTopics,
}))
req, _ := http.NewRequest(http.MethodPost, filterV2Subscriptions, reqReader)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "OK",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusOK, rr.Code)
// trying pinging the peer once there is subscription to it
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", filterV2Subscriptions, requestID), nil)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "OK",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusOK, rr.Code)
}
// create subscription to peer
// delete the subscription to the peer with matching pubSub and contentTopic
func TestFilterSubscribeAndUnsubscribe(t *testing.T) {
pubsubTopic := "/waku/2/test/proto"
contentTopics := []string{"test"}
requestID := hex.EncodeToString(protocol.GenerateRequestID())
node1, node2 := twoFilterConnectedNodes(t, pubsubTopic)
defer func() {
node1.Stop()
node2.Stop()
}()
router := chi.NewRouter()
_ = NewFilterService(node2, router, 0, utils.Logger())
// create subscription to peer
rr := httptest.NewRecorder()
reqReader := strings.NewReader(toString(t, filterSubscriptionRequest{
RequestID: requestID,
PubsubTopic: pubsubTopic,
ContentFilters: contentTopics,
}))
req, _ := http.NewRequest(http.MethodPost, filterV2Subscriptions, reqReader)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "OK",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusOK, rr.Code)
// delete the subscription to the peer with matching pubSub and contentTopic
requestID = hex.EncodeToString(protocol.GenerateRequestID())
rr = httptest.NewRecorder()
reqReader = strings.NewReader(toString(t, filterSubscriptionRequest{
RequestID: requestID,
PubsubTopic: pubsubTopic,
ContentFilters: contentTopics,
}))
req, _ = http.NewRequest(http.MethodDelete, filterV2Subscriptions, reqReader)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "OK",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusOK, rr.Code)
}
// create 2 subscription from filter client to server
// make a unsubscribeAll request
// try pinging the peer, if 404 is returned then unsubscribeAll was successful
func TestFilterAllUnsubscribe(t *testing.T) {
pubsubTopic := "/waku/2/test/proto"
contentTopics1 := "ct_1"
contentTopics2 := "ct_2"
node1, node2 := twoFilterConnectedNodes(t, pubsubTopic)
defer func() {
node1.Stop()
node2.Stop()
}()
router := chi.NewRouter()
_ = NewFilterService(node2, router, 0, utils.Logger())
// create 2 different subscription to peer
for _, ct := range []string{contentTopics1, contentTopics2} {
requestID := hex.EncodeToString(protocol.GenerateRequestID())
rr := httptest.NewRecorder()
reqReader := strings.NewReader(toString(t, filterSubscriptionRequest{
RequestID: requestID,
PubsubTopic: pubsubTopic,
ContentFilters: []string{ct},
}))
req, _ := http.NewRequest(http.MethodPost, filterV2Subscriptions, reqReader)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "OK",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusOK, rr.Code)
}
// delete all subscription to the peer
requestID := hex.EncodeToString(protocol.GenerateRequestID())
rr := httptest.NewRecorder()
reqReader := strings.NewReader(toString(t, filterUnsubscribeAllRequest{
RequestID: requestID,
}))
req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/all", filterV2Subscriptions), reqReader)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "OK",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusOK, rr.Code)
// check if all subscriptions are deleted to the peer are deleted
requestID = hex.EncodeToString(protocol.GenerateRequestID())
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", filterV2Subscriptions, requestID), nil)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "peer has no subscription",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusNotFound, rr.Code)
}
func checkJSON(t *testing.T, expected, actual interface{}) {
require.JSONEq(t, toString(t, expected), toString(t, actual))
}
func getFilterResponse(t *testing.T, body *bytes.Buffer) filterSubscriptionResponse {
resp := filterSubscriptionResponse{}
err := json.Unmarshal(body.Bytes(), &resp)
require.NoError(t, err)
return resp
}
func getMessageResponse(t *testing.T, body *bytes.Buffer) []*pb.WakuMessage {
resp := []*pb.WakuMessage{}
err := json.Unmarshal(body.Bytes(), &resp)
require.NoError(t, err)
return resp
}
func toString(t *testing.T, data interface{}) string {
bytes, err := json.Marshal(data)
require.NoError(t, err)
return string(bytes)
}
func TestFilterGetMessages(t *testing.T) {
pubsubTopic := "/waku/2/test/proto"
contentTopic := "/waku/2/app/1"
// get nodes add connect them
generatedPubsubTopic, err := protocol.GetPubSubTopicFromContentTopic(contentTopic)
require.NoError(t, err)
node1, node2 := twoFilterConnectedNodes(t, pubsubTopic, generatedPubsubTopic)
defer func() {
node1.Stop()
node2.Stop()
}()
// set router and start filter service
router := chi.NewRouter()
service := NewFilterService(node2, router, 2, utils.Logger())
go service.Start(context.Background())
defer service.Stop()
{ // create subscription so that messages are cached
for _, pubsubTopic := range []string{"", pubsubTopic} {
requestID := hex.EncodeToString(protocol.GenerateRequestID())
rr := httptest.NewRecorder()
reqReader := strings.NewReader(toString(t, filterSubscriptionRequest{
RequestID: requestID,
PubsubTopic: pubsubTopic,
ContentFilters: []string{contentTopic},
}))
req, _ := http.NewRequest(http.MethodPost, filterV2Subscriptions, reqReader)
router.ServeHTTP(rr, req)
checkJSON(t, filterSubscriptionResponse{
RequestID: requestID,
StatusDesc: "OK",
}, getFilterResponse(t, rr.Body))
require.Equal(t, http.StatusOK, rr.Code)
}
}
// submit messages
messageByContentTopic := []*protocol.Envelope{
genMessage("", contentTopic),
genMessage("", contentTopic),
genMessage("", contentTopic),
}
messageByPubsubTopic := []*protocol.Envelope{
genMessage(pubsubTopic, contentTopic),
}
for _, envelope := range append(messageByContentTopic, messageByPubsubTopic...) {
node2.Broadcaster().Submit(envelope)
}
time.Sleep(1 * time.Second)
{ // with malformed contentTopic
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet,
fmt.Sprintf("%s/%s", filterv2Messages, url.QueryEscape("/waku/2/wrongtopic")),
nil,
)
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusBadRequest, rr.Code)
require.Equal(t, "bad content topic", rr.Body.String())
}
{ // with check if the cache is working properly
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet,
fmt.Sprintf("%s/%s", filterv2Messages, url.QueryEscape(contentTopic)),
nil,
)
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
checkJSON(t, toMessage(messageByContentTopic[1:]), getMessageResponse(t, rr.Body))
}
{ // check if pubsubTopic is present in the url
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet,
fmt.Sprintf("%s//%s", filterv2Messages, url.QueryEscape(contentTopic)),
nil,
)
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusBadRequest, rr.Code)
require.Equal(t, "missing pubsubTopic", rr.Body.String())
}
{ // check messages by pubsub/contentTopic pair
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet,
fmt.Sprintf("%s/%s/%s", filterv2Messages, url.QueryEscape(pubsubTopic), url.QueryEscape(contentTopic)),
nil,
)
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
checkJSON(t, toMessage(messageByPubsubTopic), getMessageResponse(t, rr.Body))
}
{ // check if pubsubTopic/contentTOpic is subscribed or not.
rr := httptest.NewRecorder()
notSubscibredPubsubTopic := "/waku/2/test2/proto"
req, _ := http.NewRequest(http.MethodGet,
fmt.Sprintf("%s/%s/%s", filterv2Messages, url.QueryEscape(notSubscibredPubsubTopic), url.QueryEscape(contentTopic)),
nil,
)
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusNotFound, rr.Code)
require.Equal(t,
fmt.Sprintf("not subscribed to pubsubTopic:%s contentTopic: %s", notSubscibredPubsubTopic, contentTopic),
rr.Body.String(),
)
}
}
func toMessage(envs []*protocol.Envelope) []*pb.WakuMessage {
msgs := make([]*pb.WakuMessage, len(envs))
for i, env := range envs {
msgs[i] = env.Message()
}
return msgs
}
func genMessage(pubsubTopic, contentTopic string) *protocol.Envelope {
if pubsubTopic == "" {
pubsubTopic, _ = protocol.GetPubSubTopicFromContentTopic(contentTopic)
}
return protocol.NewEnvelope(
&pb.WakuMessage{
Payload: []byte{1, 2, 3},
ContentTopic: contentTopic,
Timestamp: utils.GetUnixEpoch(),
},
0,
pubsubTopic,
)
}

View File

@ -30,23 +30,19 @@ func NewHealthService(node *node.WakuNode, m *chi.Mux) *HealthService {
type HealthResponse string
func (d *HealthService) getHealth(w http.ResponseWriter, r *http.Request) {
if d.node.RLNRelay() != nil {
isReady, err := d.node.RLNRelay().IsReady(r.Context())
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
writeResponse(w, HealthResponse("Health check timed out"), http.StatusInternalServerError)
} else {
writeResponse(w, HealthResponse(err.Error()), http.StatusInternalServerError)
}
return
}
if isReady {
writeResponse(w, HealthResponse("Node is healthy"), http.StatusOK)
isReady, err := d.node.RLNRelay().IsReady(r.Context())
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
writeResponse(w, HealthResponse("Health check timed out"), http.StatusInternalServerError)
} else {
writeResponse(w, HealthResponse("Node is not ready"), http.StatusInternalServerError)
writeResponse(w, HealthResponse(err.Error()), http.StatusInternalServerError)
}
return
}
if isReady {
writeResponse(w, HealthResponse("Node is healthy"), http.StatusOK)
} else {
writeResponse(w, HealthResponse("Non RLN healthcheck is not implemented"), http.StatusNotImplemented)
writeResponse(w, HealthResponse("Node is not ready"), http.StatusInternalServerError)
}
}

View File

@ -1,6 +1,6 @@
openapi: 3.0.3
info:
title: Waku V2 node Health REST API
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team

View File

@ -1,212 +0,0 @@
package rest
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/go-chi/chi/v5"
"github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store/pb"
)
type LegacyStoreService struct {
node *node.WakuNode
mux *chi.Mux
}
type LegacyStoreResponse struct {
Messages []LegacyStoreWakuMessage `json:"messages"`
Cursor *LegacyHistoryCursor `json:"cursor,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
}
type LegacyHistoryCursor struct {
PubsubTopic string `json:"pubsubTopic"`
SenderTime string `json:"senderTime"`
StoreTime string `json:"storeTime"`
Digest []byte `json:"digest"`
}
type LegacyStoreWakuMessage struct {
Payload []byte `json:"payload"`
ContentTopic string `json:"contentTopic"`
Version *uint32 `json:"version,omitempty"`
Timestamp *int64 `json:"timestamp,omitempty"`
Meta []byte `json:"meta,omitempty"`
}
const routeLegacyStoreMessagesV1 = "/store/v1/messages"
func NewLegacyStoreService(node *node.WakuNode, m *chi.Mux) *LegacyStoreService {
s := &LegacyStoreService{
node: node,
mux: m,
}
m.Get(routeLegacyStoreMessagesV1, s.getV1Messages)
return s
}
func getLegacyStoreParams(r *http.Request) (*legacy_store.Query, []legacy_store.HistoryRequestOption, error) {
query := &legacy_store.Query{}
var options []legacy_store.HistoryRequestOption
var err error
peerAddrStr := r.URL.Query().Get("peerAddr")
var m multiaddr.Multiaddr
if peerAddrStr != "" {
m, err = multiaddr.NewMultiaddr(peerAddrStr)
if err != nil {
return nil, nil, err
}
options = append(options, legacy_store.WithPeerAddr(m))
} else {
// The user didn't specify a peer address and self-node is configured as a store node.
// In this case we assume that the user is willing to retrieve the messages stored by
// the local/self store node.
options = append(options, legacy_store.WithLocalQuery())
}
query.PubsubTopic = r.URL.Query().Get("pubsubTopic")
contentTopics := r.URL.Query().Get("contentTopics")
if contentTopics != "" {
query.ContentTopics = strings.Split(contentTopics, ",")
}
startTimeStr := r.URL.Query().Get("startTime")
if startTimeStr != "" {
startTime, err := strconv.ParseInt(startTimeStr, 10, 64)
if err != nil {
return nil, nil, err
}
query.StartTime = &startTime
}
endTimeStr := r.URL.Query().Get("endTime")
if endTimeStr != "" {
endTime, err := strconv.ParseInt(endTimeStr, 10, 64)
if err != nil {
return nil, nil, err
}
query.EndTime = &endTime
}
var cursor *pb.Index
senderTimeStr := r.URL.Query().Get("senderTime")
storeTimeStr := r.URL.Query().Get("storeTime")
digestStr := r.URL.Query().Get("digest")
if senderTimeStr != "" || storeTimeStr != "" || digestStr != "" {
cursor = &pb.Index{}
if senderTimeStr != "" {
cursor.SenderTime, err = strconv.ParseInt(senderTimeStr, 10, 64)
if err != nil {
return nil, nil, err
}
}
if storeTimeStr != "" {
cursor.ReceiverTime, err = strconv.ParseInt(storeTimeStr, 10, 64)
if err != nil {
return nil, nil, err
}
}
if digestStr != "" {
cursor.Digest, err = base64.URLEncoding.DecodeString(digestStr)
if err != nil {
return nil, nil, err
}
}
cursor.PubsubTopic = query.PubsubTopic
options = append(options, legacy_store.WithCursor(cursor))
}
pageSizeStr := r.URL.Query().Get("pageSize")
ascendingStr := r.URL.Query().Get("ascending")
if ascendingStr != "" || pageSizeStr != "" {
ascending := true
pageSize := uint64(legacy_store.DefaultPageSize)
if ascendingStr != "" {
ascending, err = strconv.ParseBool(ascendingStr)
if err != nil {
return nil, nil, err
}
}
if pageSizeStr != "" {
pageSize, err = strconv.ParseUint(pageSizeStr, 10, 64)
if err != nil {
return nil, nil, err
}
if pageSize > legacy_store.MaxPageSize {
pageSize = legacy_store.MaxPageSize
}
}
options = append(options, legacy_store.WithPaging(ascending, pageSize))
}
return query, options, nil
}
func writeLegacyStoreError(w http.ResponseWriter, code int, err error) {
writeResponse(w, LegacyStoreResponse{ErrorMessage: err.Error()}, code)
}
func toLegacyStoreResponse(result *legacy_store.Result) LegacyStoreResponse {
response := LegacyStoreResponse{}
cursor := result.Cursor()
if cursor != nil {
response.Cursor = &LegacyHistoryCursor{
PubsubTopic: cursor.PubsubTopic,
SenderTime: fmt.Sprintf("%d", cursor.SenderTime),
StoreTime: fmt.Sprintf("%d", cursor.ReceiverTime),
Digest: cursor.Digest,
}
}
for _, m := range result.Messages {
response.Messages = append(response.Messages, LegacyStoreWakuMessage{
Payload: m.Payload,
ContentTopic: m.ContentTopic,
Version: m.Version,
Timestamp: m.Timestamp,
Meta: m.Meta,
})
}
return response
}
func (d *LegacyStoreService) getV1Messages(w http.ResponseWriter, r *http.Request) {
query, options, err := getLegacyStoreParams(r)
if err != nil {
writeLegacyStoreError(w, http.StatusBadRequest, err)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
result, err := d.node.LegacyStore().Query(ctx, *query, options...)
if err != nil {
writeLegacyStoreError(w, http.StatusInternalServerError, err)
return
}
writeErrOrResponse(w, nil, toLegacyStoreResponse(result))
}

View File

@ -1,203 +0,0 @@
openapi: 3.0.3
info:
title: Waku V2 node Store REST API
version: 1.0.0
contact:
name: VAC Team
url: https://forum.vac.dev/
tags:
- name: store
description: Store REST API for WakuV2 node
paths:
/store/v1/messages:
get:
summary: Gets message history
description: >
Retrieves WakuV2 message history. The returned history
can be potentially filtered by optional request parameters.
operationId: getMessageHistory
tags:
- store
parameters:
- name: peerAddr
in: query
schema:
type: string
required: true
description: >
P2P fully qualified peer multiaddress
in the format `(ip4|ip6)/tcp/p2p/$peerId` and URL-encoded.
example: '%2Fip4%2F127.0.0.1%2Ftcp%2F60001%2Fp2p%2F16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN'
- name: pubsubTopic
in: query
schema:
type: string
description: >
The pubsub topic on which a WakuMessage is published.
If left empty, no filtering is applied.
It is also intended for pagination purposes.
It should be a URL-encoded string.
example: 'my%20pubsub%20topic'
- name: contentTopics
in: query
schema: string
description: >
Comma-separated list of content topics. When specified,
only WakuMessages that are linked to any of the given
content topics will be delivered in the get response.
It should be a URL-encoded-comma-separated string.
example: 'my%20first%20content%20topic%2Cmy%20second%20content%20topic%2Cmy%20third%20content%20topic'
- name: startTime
in: query
schema:
type: string
description: >
The inclusive lower bound on the timestamp of
queried WakuMessages. This field holds the
Unix epoch time in nanoseconds as a 64-bits
integer value.
example: '1680590945000000000'
- name: endTime
in: query
schema:
type: string
description: >
The inclusive upper bound on the timestamp of
queried WakuMessages. This field holds the
Unix epoch time in nanoseconds as a 64-bits
integer value.
example: '1680590945000000000'
- name: senderTime
in: query
schema:
type: string
description: >
Cursor field intended for pagination purposes.
Represents the Unix time in nanoseconds at which a message was generated.
It could be empty for retrieving the first page,
and will be returned from the GET response so that
it can be part of the next page request.
example: '1680590947000000000'
- name: storeTime
in: query
schema:
type: string
description: >
Cursor field intended for pagination purposes.
Represents the Unix time in nanoseconds at which a message was stored.
It could be empty for retrieving the first page,
and will be returned from the GET response so that
it can be part of the next page request.
example: '1680590945000000000'
- name: digest
in: query
schema:
type: string
description: >
Cursor field intended for pagination purposes.
URL-base64-encoded string computed as a hash of the
a message content topic plus a message payload.
It could be empty for retrieving the first page,
and will be returned from the GET response so that
it can be part of the next page request.
example: 'Gc4ACThW5t2QQO82huq3WnDv%2FapPPJpD%2FwJfxDxAnR0%3D'
- name: pageSize
in: query
schema:
type: string
description: >
Number of messages to retrieve per page
example: '5'
- name: ascending
in: query
schema:
type: string
description: >
"true" for paging forward, "false" for paging backward
example: "true"
responses:
'200':
description: WakuV2 message history.
content:
application/json:
schema:
$ref: '#/components/schemas/StoreResponse'
'400':
description: Bad request error.
content:
text/plain:
type: string
'412':
description: Precondition failed.
content:
text/plain:
type: string
'500':
description: Internal server error.
content:
text/plain:
type: string
components:
schemas:
StoreResponse:
type: object
properties:
messages:
type: array
items:
$ref: '#/components/schemas/WakuMessage'
cursor:
$ref: '#/components/schemas/HistoryCursor'
error_message:
type: string
required:
- messages
HistoryCursor:
type: object
properties:
pubsub_topic:
type: string
sender_time:
type: string
store_time:
type: string
digest:
type: string
required:
- pubsub_topic
- sender_time
- store_time
- digest
WakuMessage:
type: object
properties:
payload:
type: string
content_topic:
type: string
version:
type: integer
format: int32
timestamp:
type: integer
format: int64
ephemeral:
type: boolean
required:
- payload
- content_topic

View File

@ -1,84 +0,0 @@
openapi: 3.0.3
info:
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team
url: https://forum.vac.dev/
tags:
- name: lightpush
description: Lightpush REST API for WakuV2 node
paths:
/lightpush/v1/message:
post:
summary: Request a message relay from a LightPush service provider
description: Push a message to be relayed on a PubSub topic.
operationId: postMessagesToPubsubTopic
tags:
- lightpush
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PushRequest'
responses:
'200':
description: OK
content:
text/plain:
schema:
type: string
'400':
description: Bad request.
content:
text/plain:
schema:
type: string
'500':
description: Internal server error
content:
text/plain:
schema:
type: string
'503':
description: Service not available
content:
text/plain:
schema:
type: string
components:
schemas:
PubsubTopic:
type: string
ContentTopic:
type: string
WakuMessage:
type: object
properties:
payload:
type: string
format: byte
contentTopic:
$ref: '#/components/schemas/ContentTopic'
version:
type: number
timestamp:
type: number
required:
- payload
- contentTopic
PushRequest:
type: object
properties:
pusbsubTopic:
$ref: '#/components/schemas/PubsubTopic'
message:
$ref: '#/components/schemas/WakuMessage'
required:
- message

View File

@ -1,96 +0,0 @@
package rest
import (
"encoding/json"
"errors"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"go.uber.org/zap"
)
const routeLightPushV1Messages = "/lightpush/v1/message"
type LightpushService struct {
node *node.WakuNode
log *zap.Logger
}
func NewLightpushService(node *node.WakuNode, m *chi.Mux, log *zap.Logger) *LightpushService {
serv := &LightpushService{
node: node,
log: log.Named("lightpush"),
}
m.Post(routeLightPushV1Messages, serv.postMessagev1)
return serv
}
func (msg lightpushRequest) Check() error {
if msg.Message == nil {
return errors.New("waku message is required")
}
return nil
}
type lightpushRequest struct {
PubSubTopic string `json:"pubsubTopic"`
Message *RestWakuMessage `json:"message"`
}
// handled error codes are 200, 400, 500, 503
func (serv *LightpushService) postMessagev1(w http.ResponseWriter, req *http.Request) {
request := &lightpushRequest{}
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(request); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
if err := request.Check(); err != nil {
w.WriteHeader(http.StatusBadRequest)
_, err = w.Write([]byte(err.Error()))
serv.log.Error("writing response", zap.Error(err))
return
}
if serv.node.Lightpush() == nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
message, err := request.Message.ToProto()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, err = w.Write([]byte(err.Error()))
if err != nil {
serv.log.Error("writing response", zap.Error(err))
}
return
}
if err = message.Validate(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
_, err = w.Write([]byte(err.Error()))
if err != nil {
serv.log.Error("writing response", zap.Error(err))
}
return
}
_, err = serv.node.Lightpush().Publish(req.Context(), message, lightpush.WithPubSubTopic(request.PubSubTopic))
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
_, err = w.Write([]byte(err.Error()))
if err != nil {
serv.log.Error("writing response", zap.Error(err))
}
} else {
writeErrOrResponse(w, err, true)
}
}

View File

@ -1,68 +0,0 @@
package rest
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/go-chi/chi/v5"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/tests"
"github.com/waku-org/go-waku/waku/v2/node"
wakupeerstore "github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"github.com/waku-org/go-waku/waku/v2/utils"
)
// node2 connects to node1
func twoLightPushConnectedNodes(t *testing.T, pubSubTopic string) (*node.WakuNode, *node.WakuNode) {
node1 := createNode(t, node.WithLightPush(), node.WithWakuRelay())
node2 := createNode(t, node.WithLightPush(), node.WithWakuRelay())
_, err := node1.Relay().Subscribe(context.Background(), protocol.NewContentFilter(pubSubTopic))
require.NoError(t, err)
_, err = node2.Relay().Subscribe(context.Background(), protocol.NewContentFilter(pubSubTopic))
require.NoError(t, err)
node2.Host().Peerstore().AddAddr(node1.Host().ID(), tests.GetHostAddress(node1.Host()), peerstore.PermanentAddrTTL)
err = node2.Host().Peerstore().AddProtocols(node1.Host().ID(), lightpush.LightPushID_v20beta1)
require.NoError(t, err)
err = node2.Host().Peerstore().(*wakupeerstore.WakuPeerstoreImpl).SetPubSubTopics(node1.Host().ID(), []string{pubSubTopic})
require.NoError(t, err)
return node1, node2
}
func TestLightpushMessagev1(t *testing.T) {
pubSubTopic := "/waku/2/default-waku/proto"
node1, node2 := twoLightPushConnectedNodes(t, pubSubTopic)
defer func() {
node1.Stop()
node2.Stop()
}()
router := chi.NewRouter()
serv := NewLightpushService(node2, router, utils.Logger())
_ = serv
msg := lightpushRequest{
PubSubTopic: pubSubTopic,
Message: &RestWakuMessage{
Payload: []byte{1, 2, 3},
ContentTopic: "abc",
Timestamp: utils.GetUnixEpoch(),
},
}
msgJSONBytes, err := json.Marshal(msg)
require.NoError(t, err)
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, routeLightPushV1Messages, bytes.NewReader(msgJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
require.Equal(t, "true", rr.Body.String())
}

View File

@ -1,49 +0,0 @@
package rest
import (
"errors"
"github.com/waku-org/go-waku/cmd/waku/server"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
)
type RestWakuMessage struct {
Payload server.Base64URLByte `json:"payload"`
ContentTopic string `json:"contentTopic"`
Version *uint32 `json:"version,omitempty"`
Timestamp *int64 `json:"timestamp,omitempty"`
Meta []byte `json:"meta,omitempty"`
Ephemeral *bool `json:"ephemeral"`
}
func (r *RestWakuMessage) FromProto(input *pb.WakuMessage) error {
if err := input.Validate(); err != nil {
return err
}
r.Payload = input.Payload
r.ContentTopic = input.ContentTopic
r.Timestamp = input.Timestamp
r.Version = input.Version
r.Meta = input.Meta
r.Ephemeral = input.Ephemeral
return nil
}
func (r *RestWakuMessage) ToProto() (*pb.WakuMessage, error) {
if r == nil {
return nil, errors.New("wakumessage is missing")
}
msg := &pb.WakuMessage{
Payload: r.Payload,
ContentTopic: r.ContentTopic,
Version: r.Version,
Timestamp: r.Timestamp,
Meta: r.Meta,
Ephemeral: r.Ephemeral,
}
return msg, nil
}

View File

@ -1,9 +1,12 @@
package rest
import (
"context"
"encoding/json"
"errors"
"net/http"
"strings"
"sync"
"github.com/go-chi/chi/v5"
"github.com/waku-org/go-waku/cmd/waku/server"
@ -17,57 +20,98 @@ import (
const routeRelayV1Subscriptions = "/relay/v1/subscriptions"
const routeRelayV1Messages = "/relay/v1/messages/{topic}"
const routeRelayV1AutoSubscriptions = "/relay/v1/auto/subscriptions"
const routeRelayV1AutoMessages = "/relay/v1/auto/messages"
// RelayService represents the REST service for WakuRelay
type RelayService struct {
node *node.WakuNode
node *node.WakuNode
cancel context.CancelFunc
log *zap.Logger
cacheCapacity uint
messages map[string][]*pb.WakuMessage
cacheCapacity int
messagesMutex sync.RWMutex
runner *runnerService
}
// NewRelayService returns an instance of RelayService
func NewRelayService(node *node.WakuNode, m *chi.Mux, cacheCapacity uint, log *zap.Logger) *RelayService {
func NewRelayService(node *node.WakuNode, m *chi.Mux, cacheCapacity int, log *zap.Logger) *RelayService {
s := &RelayService{
node: node,
log: log.Named("relay"),
cacheCapacity: cacheCapacity,
messages: make(map[string][]*pb.WakuMessage),
}
s.runner = newRunnerService(node.Broadcaster(), s.addEnvelope)
m.Post(routeRelayV1Subscriptions, s.postV1Subscriptions)
m.Delete(routeRelayV1Subscriptions, s.deleteV1Subscriptions)
m.Get(routeRelayV1Messages, s.getV1Messages)
m.Post(routeRelayV1Messages, s.postV1Message)
m.Post(routeRelayV1AutoSubscriptions, s.postV1AutoSubscriptions)
m.Delete(routeRelayV1AutoSubscriptions, s.deleteV1AutoSubscriptions)
m.Route(routeRelayV1AutoMessages, func(r chi.Router) {
r.Get("/{contentTopic}", s.getV1AutoMessages)
r.Post("/", s.postV1AutoMessage)
})
return s
}
func (r *RelayService) addEnvelope(envelope *protocol.Envelope) {
r.messagesMutex.Lock()
defer r.messagesMutex.Unlock()
if _, ok := r.messages[envelope.PubsubTopic()]; !ok {
return
}
// Keep a specific max number of messages per topic
if len(r.messages[envelope.PubsubTopic()]) >= r.cacheCapacity {
r.messages[envelope.PubsubTopic()] = r.messages[envelope.PubsubTopic()][1:]
}
r.messages[envelope.PubsubTopic()] = append(r.messages[envelope.PubsubTopic()], envelope.Message())
}
// Start starts the RelayService
func (r *RelayService) Start(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
r.cancel = cancel
r.messagesMutex.Lock()
// Node may already be subscribed to some topics when Relay API handlers are installed. Let's add these
for _, topic := range r.node.Relay().Topics() {
r.log.Info("adding topic handler for existing subscription", zap.String("topic", topic))
r.messages[topic] = []*pb.WakuMessage{}
}
r.messagesMutex.Unlock()
r.runner.Start(ctx)
}
// Stop stops the RelayService
func (r *RelayService) Stop() {
if r.cancel == nil {
return
}
r.cancel()
}
func (r *RelayService) deleteV1Subscriptions(w http.ResponseWriter, req *http.Request) {
var topics []string
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&topics); err != nil {
r.log.Error("decoding request failure", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
r.messagesMutex.Lock()
defer r.messagesMutex.Unlock()
var err error
for _, topic := range topics {
err = r.node.Relay().Unsubscribe(req.Context(), protocol.NewContentFilter(topic))
err = r.node.Relay().Unsubscribe(req.Context(), topic)
if err != nil {
r.log.Error("unsubscribing from topic", zap.String("topic", strings.Replace(strings.Replace(topic, "\n", "", -1), "\r", "", -1)), zap.Error(err))
} else {
delete(r.messages, topic)
}
}
@ -78,221 +122,82 @@ func (r *RelayService) postV1Subscriptions(w http.ResponseWriter, req *http.Requ
var topics []string
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&topics); err != nil {
r.log.Error("decoding request failure", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
var err error
var successCnt int
var sub *relay.Subscription
var topicToSubscribe string
for _, topic := range topics {
if topic == "" {
sub, err = r.node.Relay().Subscribe(req.Context())
topicToSubscribe = relay.DefaultWakuTopic
} else {
sub, err = r.node.Relay().SubscribeToTopic(req.Context(), topic)
topicToSubscribe = topic
}
_, err = r.node.Relay().Subscribe(r.node.Relay().Context(), protocol.NewContentFilter(topicToSubscribe), relay.WithCacheSize(r.cacheCapacity))
if err != nil {
r.log.Error("subscribing to topic", zap.String("topic", strings.Replace(topicToSubscribe, "\n", "", -1)), zap.Error(err))
continue
} else {
sub.Unsubscribe()
r.messagesMutex.Lock()
r.messages[topic] = []*pb.WakuMessage{}
r.messagesMutex.Unlock()
}
successCnt++
}
// on partial subscribe failure
if successCnt > 0 && err != nil {
r.log.Error("partial subscribe failed", zap.Error(err))
// on partial failure
writeResponse(w, err, http.StatusOK)
return
}
writeErrOrResponse(w, err, true)
}
func (r *RelayService) getV1Messages(w http.ResponseWriter, req *http.Request) {
topic := topicFromPath(w, req, "topic", r.log)
topic := chi.URLParam(req, "topic")
if topic == "" {
r.log.Debug("topic is not specified, using default waku topic")
topic = relay.DefaultWakuTopic
w.WriteHeader(http.StatusBadRequest)
return
}
//TODO: Update the API to also take a contentTopic since relay now supports filtering based on contentTopic as well.
sub, err := r.node.Relay().GetSubscriptionWithPubsubTopic(topic, "")
if err != nil {
var err error
r.messagesMutex.Lock()
defer r.messagesMutex.Unlock()
if _, ok := r.messages[topic]; !ok {
w.WriteHeader(http.StatusNotFound)
_, err = w.Write([]byte(err.Error()))
_, err = w.Write([]byte("not subscribed to topic"))
r.log.Error("writing response", zap.Error(err))
return
}
var response []*RestWakuMessage
done := false
for {
if done || len(response) > int(r.cacheCapacity) {
break
}
select {
case envelope, open := <-sub.Ch:
if !open {
r.log.Error("consume channel is closed for subscription", zap.String("pubsubTopic", topic))
w.WriteHeader(http.StatusNotFound)
_, err = w.Write([]byte("consume channel is closed for subscription"))
if err != nil {
r.log.Error("writing response", zap.Error(err))
}
return
}
message := &RestWakuMessage{}
if err := message.FromProto(envelope.Message()); err != nil {
r.log.Error("converting protobuffer msg into rest msg", zap.Error(err))
} else {
response = append(response, message)
}
case <-req.Context().Done():
done = true
default:
done = true
}
}
response := r.messages[topic]
r.messages[topic] = []*pb.WakuMessage{}
writeErrOrResponse(w, nil, response)
}
func (r *RelayService) postV1Message(w http.ResponseWriter, req *http.Request) {
topic := topicFromPath(w, req, "topic", r.log)
topic := chi.URLParam(req, "topic")
if topic == "" {
r.log.Debug("topic is not specified, using default waku topic")
topic = relay.DefaultWakuTopic
}
var restMessage *RestWakuMessage
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&restMessage); err != nil {
r.log.Error("decoding request failure", zap.Error(err))
writeErrResponse(w, r.log, err, http.StatusBadRequest)
return
}
defer req.Body.Close()
message, err := restMessage.ToProto()
if err != nil {
r.log.Error("failed to convert message to proto", zap.Error(err))
writeErrResponse(w, r.log, err, http.StatusBadRequest)
return
}
if err := server.AppendRLNProof(r.node, message); err != nil {
r.log.Error("failed to append RLN proof for the message", zap.Error(err))
writeErrOrResponse(w, err, nil)
return
}
_, err = r.node.Relay().Publish(req.Context(), message, relay.WithPubSubTopic(strings.Replace(topic, "\n", "", -1)))
if err != nil {
r.log.Error("publishing message", zap.Error(err))
if err == pb.ErrMissingPayload || err == pb.ErrMissingContentTopic || err == pb.ErrInvalidMetaLength {
writeErrResponse(w, r.log, err, http.StatusBadRequest)
return
}
}
writeErrOrResponse(w, err, true)
}
func (r *RelayService) deleteV1AutoSubscriptions(w http.ResponseWriter, req *http.Request) {
var cTopics []string
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&cTopics); err != nil {
r.log.Error("decoding request failure", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
err := r.node.Relay().Unsubscribe(req.Context(), protocol.NewContentFilter("", cTopics...))
if err != nil {
r.log.Error("unsubscribing from topics", zap.Strings("contentTopics", cTopics), zap.Error(err))
}
writeErrOrResponse(w, err, true)
}
func (r *RelayService) postV1AutoSubscriptions(w http.ResponseWriter, req *http.Request) {
var cTopics []string
var message *pb.WakuMessage
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&cTopics); err != nil {
r.log.Error("decoding request failure", zap.Error(err))
if err := decoder.Decode(&message); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
var err error
_, err = r.node.Relay().Subscribe(r.node.Relay().Context(), protocol.NewContentFilter("", cTopics...), relay.WithCacheSize(r.cacheCapacity))
if err != nil {
r.log.Error("subscribing to topics", zap.Strings("contentTopics", cTopics), zap.Error(err))
}
r.log.Debug("subscribed to topics", zap.Strings("contentTopics", cTopics))
if err != nil {
r.log.Error("writing response", zap.Error(err))
writeErrResponse(w, r.log, err, http.StatusBadRequest)
} else {
writeErrOrResponse(w, err, true)
if topic == "" {
topic = relay.DefaultWakuTopic
}
}
func (r *RelayService) getV1AutoMessages(w http.ResponseWriter, req *http.Request) {
cTopic := topicFromPath(w, req, "contentTopic", r.log)
sub, err := r.node.Relay().GetSubscription(cTopic)
if err != nil {
r.log.Error("writing response", zap.Error(err))
writeErrResponse(w, r.log, err, http.StatusNotFound)
return
}
var response []*RestWakuMessage
done := false
for {
if done || len(response) > int(r.cacheCapacity) {
break
}
select {
case envelope := <-sub.Ch:
message := &RestWakuMessage{}
if err := message.FromProto(envelope.Message()); err != nil {
r.log.Error("converting protobuffer msg into rest msg", zap.Error(err))
} else {
response = append(response, message)
}
case <-req.Context().Done():
done = true
default:
done = true
}
}
writeErrOrResponse(w, nil, response)
}
func (r *RelayService) postV1AutoMessage(w http.ResponseWriter, req *http.Request) {
var restMessage *RestWakuMessage
decoder := json.NewDecoder(req.Body)
if err := decoder.Decode(&restMessage); err != nil {
r.log.Error("decoding request failure", zap.Error(err))
w.WriteHeader(http.StatusBadRequest)
return
}
defer req.Body.Close()
message, err := restMessage.ToProto()
if err != nil {
writeErrOrResponse(w, err, nil)
if !r.node.Relay().IsSubscribed(topic) {
writeErrOrResponse(w, errors.New("not subscribed to pubsubTopic"), nil)
return
}
@ -301,16 +206,10 @@ func (r *RelayService) postV1AutoMessage(w http.ResponseWriter, req *http.Reques
return
}
_, err = r.node.Relay().Publish(req.Context(), message)
_, err = r.node.Relay().PublishToTopic(req.Context(), message, strings.Replace(topic, "\n", "", -1))
if err != nil {
r.log.Error("publishing message", zap.Error(err))
if err == pb.ErrMissingPayload || err == pb.ErrMissingContentTopic || err == pb.ErrInvalidMetaLength {
writeErrResponse(w, r.log, err, http.StatusBadRequest)
return
}
writeErrResponse(w, r.log, err, http.StatusBadRequest)
} else {
w.WriteHeader(http.StatusOK)
}
writeErrOrResponse(w, err, true)
}

View File

@ -1,6 +1,6 @@
openapi: 3.0.3
info:
title: Waku V2 node Relay REST API
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team
@ -106,103 +106,6 @@ paths:
'5XX':
description: Unexpected error.
/relay/v1/auto/messages/{contentTopic}: # Note the plural in messages
get: # get_waku_v2_relay_v1_auto_messages
summary: Get the latest messages on the polled topic
description: Get a list of messages that were received on a subscribed Content topic after the last time this method was called.
operationId: getMessagesByTopic
tags:
- relay
parameters:
- in: path
name: contentTopic # Note the name is the same as in the path
required: true
schema:
type: string
description: The user ID
responses:
'200':
description: The latest messages on the polled topic.
content:
application/json:
schema:
$ref: '#/components/schemas/RelayGetMessagesResponse'
'4XX':
description: Bad request.
'5XX':
description: Unexpected error.
/relay/v1/auto/messages: # Note the plural in messages
post: # post_waku_v2_relay_v1_auto_message
summary: Publish a message to be relayed
description: Publishes a message to be relayed on a Content topic.
operationId: postMessagesToTopic
tags:
- relay
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RelayPostMessagesRequest'
responses:
'200':
description: OK
'4XX':
description: Bad request.
'5XX':
description: Unexpected error.
/relay/v1/auto/subscriptions:
post: # post_waku_v2_relay_v1_auto_subscriptions
summary: Subscribe a node to an array of topics
description: Subscribe a node to an array of Content topics.
operationId: postSubscriptions
tags:
- relay
requestBody:
content:
application/json:
schema:
type array:
items:
$ref: '#/components/schemas/ContentTopic'
responses:
'200':
description: OK
content:
text/plain:
schema:
type: string
'4XX':
description: Bad request.
'5XX':
description: Unexpected error.
delete: # delete_waku_v2_relay_v1_auto_subscriptions
summary: Unsubscribe a node from an array of topics
description: Unsubscribe a node from an array of Content topics.
operationId: deleteSubscriptions
tags:
- relay
requestBody:
content:
application/json:
schema:
type array:
items:
$ref: '#/components/schemas/ContentTopic'
responses:
'200':
description: OK
content:
text/plain:
schema:
type: string
'4XX':
description: Bad request.
'5XX':
description: Unexpected error.
components:
schemas:
PubSubTopic:
@ -242,4 +145,4 @@ components:
type: array
items:
$ref: '#/components/schemas/PubSubTopic'

View File

@ -7,7 +7,6 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
@ -18,9 +17,7 @@ import (
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/utils"
"google.golang.org/protobuf/proto"
)
func makeRelayService(t *testing.T, mux *chi.Mux) *RelayService {
@ -35,22 +32,20 @@ func makeRelayService(t *testing.T, mux *chi.Mux) *RelayService {
func TestPostV1Message(t *testing.T) {
router := chi.NewRouter()
testTopic := "test"
r := makeRelayService(t, router)
msg := &RestWakuMessage{
_ = makeRelayService(t, router)
msg := &pb.WakuMessage{
Payload: []byte{1, 2, 3},
ContentTopic: "abc",
Version: 0,
Timestamp: utils.GetUnixEpoch(),
}
msgJSONBytes, err := json.Marshal(msg)
require.NoError(t, err)
_, err = r.node.Relay().Subscribe(context.Background(), protocol.NewContentFilter(testTopic))
require.NoError(t, err)
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, "/relay/v1/messages/"+testTopic, bytes.NewReader(msgJSONBytes))
req, _ := http.NewRequest(http.MethodPost, "/relay/v1/messages/test", bytes.NewReader(msgJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
require.Equal(t, "true", rr.Body.String())
@ -59,7 +54,10 @@ func TestPostV1Message(t *testing.T) {
func TestRelaySubscription(t *testing.T) {
router := chi.NewRouter()
r := makeRelayService(t, router)
d := makeRelayService(t, router)
go d.Start(context.Background())
defer d.Stop()
// Wait for node to start
time.Sleep(500 * time.Millisecond)
@ -69,44 +67,50 @@ func TestRelaySubscription(t *testing.T) {
require.NoError(t, err)
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, routeRelayV1Subscriptions, bytes.NewReader(topicsJSONBytes))
req, _ := http.NewRequest(http.MethodPost, "/relay/v1/subscriptions", bytes.NewReader(topicsJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
require.Equal(t, "true", rr.Body.String())
// Test max messages in subscription
now := *utils.GetUnixEpoch()
_, err = r.node.Relay().Publish(context.Background(),
tests.CreateWakuMessage("test", proto.Int64(now+1)), relay.WithPubSubTopic("test"))
require.NoError(t, err)
_, err = r.node.Relay().Publish(context.Background(),
tests.CreateWakuMessage("test", proto.Int64(now+2)), relay.WithPubSubTopic("test"))
require.NoError(t, err)
_, err = r.node.Relay().Publish(context.Background(),
tests.CreateWakuMessage("test", proto.Int64(now+3)), relay.WithPubSubTopic("test"))
require.NoError(t, err)
now := utils.GetUnixEpoch()
d.runner.broadcaster.Submit(protocol.NewEnvelope(tests.CreateWakuMessage("test", now+1), now, "test"))
d.runner.broadcaster.Submit(protocol.NewEnvelope(tests.CreateWakuMessage("test", now+2), now, "test"))
d.runner.broadcaster.Submit(protocol.NewEnvelope(tests.CreateWakuMessage("test", now+3), now, "test"))
// Wait for the messages to be processed
time.Sleep(5 * time.Millisecond)
time.Sleep(500 * time.Millisecond)
require.Len(t, d.messages["test"], 3)
d.runner.broadcaster.Submit(protocol.NewEnvelope(tests.CreateWakuMessage("test", now+4), now+4, "test"))
time.Sleep(500 * time.Millisecond)
// Should only have 3 messages
require.Len(t, d.messages["test"], 3)
// Test deletion
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodDelete, routeRelayV1Subscriptions, bytes.NewReader(topicsJSONBytes))
req, _ = http.NewRequest(http.MethodDelete, "/relay/v1/subscriptions", bytes.NewReader(topicsJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
require.Equal(t, "true", rr.Body.String())
require.Len(t, d.messages["test"], 0)
}
func TestRelayGetV1Messages(t *testing.T) {
router := chi.NewRouter()
router1 := chi.NewRouter()
serviceA := makeRelayService(t, router)
go serviceA.Start(context.Background())
defer serviceA.Stop()
serviceB := makeRelayService(t, router)
go serviceB.Start(context.Background())
defer serviceB.Stop()
serviceB := makeRelayService(t, router1)
hostInfo, err := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", serviceB.node.Host().ID().String()))
hostInfo, err := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", serviceB.node.Host().ID().Pretty()))
require.NoError(t, err)
var addr multiaddr.Multiaddr
@ -125,18 +129,18 @@ func TestRelayGetV1Messages(t *testing.T) {
require.NoError(t, err)
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, routeRelayV1Subscriptions, bytes.NewReader(topicsJSONBytes))
req, _ := http.NewRequest(http.MethodPost, "/relay/v1/subscriptions", bytes.NewReader(topicsJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
// Wait for the subscription to be started
time.Sleep(1 * time.Second)
ephemeral := true
msg := &RestWakuMessage{
msg := &pb.WakuMessage{
Payload: []byte{1, 2, 3},
ContentTopic: "test",
Version: 0,
Timestamp: utils.GetUnixEpoch(),
Ephemeral: &ephemeral,
}
msgJsonBytes, err := json.Marshal(msg)
require.NoError(t, err)
@ -158,146 +162,12 @@ func TestRelayGetV1Messages(t *testing.T) {
err = json.Unmarshal(rr.Body.Bytes(), &messages)
require.NoError(t, err)
require.Len(t, messages, 1)
require.Equal(t, *messages[0].Ephemeral, true)
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodGet, "/relay/v1/messages/test", bytes.NewReader([]byte{}))
router1.ServeHTTP(rr, req)
require.Equal(t, http.StatusNotFound, rr.Code)
}
func TestPostAutoV1Message(t *testing.T) {
router := chi.NewRouter()
_ = makeRelayService(t, router)
msg := &RestWakuMessage{
Payload: []byte{1, 2, 3},
ContentTopic: "/toychat/1/huilong/proto",
Timestamp: utils.GetUnixEpoch(),
}
msgJSONBytes, err := json.Marshal(msg)
require.NoError(t, err)
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, routeRelayV1AutoMessages, bytes.NewReader(msgJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
}
func TestRelayAutoSubUnsub(t *testing.T) {
router := chi.NewRouter()
r := makeRelayService(t, router)
// Wait for node to start
time.Sleep(500 * time.Millisecond)
cTopic1 := "/toychat/1/huilong/proto"
cTopics := []string{cTopic1}
topicsJSONBytes, err := json.Marshal(cTopics)
require.NoError(t, err)
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, routeRelayV1AutoSubscriptions, bytes.NewReader(topicsJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
require.Equal(t, "true", rr.Body.String())
// Test publishing messages after subscription
now := *utils.GetUnixEpoch()
_, err = r.node.Relay().Publish(context.Background(),
tests.CreateWakuMessage(cTopic1, proto.Int64(now+1)))
require.NoError(t, err)
// Wait for the messages to be processed
time.Sleep(5 * time.Millisecond)
// Test deletion
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodDelete, routeRelayV1AutoSubscriptions, bytes.NewReader(topicsJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
require.Equal(t, "true", rr.Body.String())
cTopics = append(cTopics, "test")
topicsJSONBytes, err = json.Marshal(cTopics)
require.NoError(t, err)
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodPost, routeRelayV1AutoSubscriptions, bytes.NewReader(topicsJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusBadRequest, rr.Code)
}
func TestRelayGetV1AutoMessages(t *testing.T) {
router := chi.NewRouter()
router1 := chi.NewRouter()
serviceA := makeRelayService(t, router)
serviceB := makeRelayService(t, router1)
hostInfo, err := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", serviceB.node.Host().ID().String()))
require.NoError(t, err)
var addr multiaddr.Multiaddr
for _, a := range serviceB.node.Host().Addrs() {
addr = a.Encapsulate(hostInfo)
break
}
err = serviceA.node.DialPeerWithMultiAddress(context.Background(), addr)
require.NoError(t, err)
// Wait for the dial to complete
time.Sleep(1 * time.Second)
cTopic1 := "/toychat/1/huilong/proto"
cTopics := []string{cTopic1}
topicsJSONBytes, err := json.Marshal(cTopics)
require.NoError(t, err)
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, routeRelayV1AutoSubscriptions, bytes.NewReader(topicsJSONBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
require.Equal(t, "true", rr.Body.String())
// Wait for the subscription to be started
time.Sleep(1 * time.Second)
msg := &RestWakuMessage{
Payload: []byte{1, 2, 3},
ContentTopic: cTopic1,
Timestamp: utils.GetUnixEpoch(),
}
msgJsonBytes, err := json.Marshal(msg)
require.NoError(t, err)
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodPost, routeRelayV1AutoMessages, bytes.NewReader(msgJsonBytes))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
// Wait for the message to be received
time.Sleep(1 * time.Second)
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", routeRelayV1AutoMessages, url.QueryEscape(cTopic1)), bytes.NewReader([]byte{}))
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
var messages []*pb.WakuMessage
err = json.Unmarshal(rr.Body.Bytes(), &messages)
require.NoError(t, err)
require.Len(t, messages, 1)
rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", routeRelayV1AutoMessages, url.QueryEscape(cTopic1)), bytes.NewReader([]byte{}))
router1.ServeHTTP(rr, req)
require.Equal(t, http.StatusNotFound, rr.Code)
require.Len(t, messages, 0)
}

View File

@ -11,7 +11,7 @@ type Adder func(msg *protocol.Envelope)
type runnerService struct {
broadcaster relay.Broadcaster
sub *relay.Subscription
sub relay.Subscription
cancel context.CancelFunc
adder Adder
}
@ -26,7 +26,7 @@ func newRunnerService(broadcaster relay.Broadcaster, adder Adder) *runnerService
func (r *runnerService) Start(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
r.cancel = cancel
r.sub = r.broadcaster.RegisterForAll(relay.WithBufferSize(relay.DefaultRelaySubscriptionBufferSize))
r.sub = r.broadcaster.RegisterForAll(1024)
for {
select {
case <-ctx.Done():

View File

@ -3,7 +3,7 @@ package rest
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
@ -12,115 +12,123 @@ import (
"github.com/go-chi/chi/v5"
"github.com/multiformats/go-multiaddr"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
storepb "github.com/waku-org/go-waku/waku/v2/protocol/store/pb"
"google.golang.org/protobuf/proto"
"github.com/waku-org/go-waku/waku/v2/protocol/store/pb"
"github.com/waku-org/go-waku/waku/v2/utils"
)
type StoreQueryService struct {
type StoreService struct {
node *node.WakuNode
mux *chi.Mux
}
const routeStoreMessagesV1 = "/store/v3/messages"
type StoreResponse struct {
Messages []StoreWakuMessage `json:"messages"`
Cursor *HistoryCursor `json:"cursor,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
}
func NewStoreQueryService(node *node.WakuNode, m *chi.Mux) *StoreQueryService {
s := &StoreQueryService{
type HistoryCursor struct {
PubsubTopic string `json:"pubsub_topic"`
SenderTime string `json:"sender_time"`
StoreTime string `json:"store_time"`
Digest []byte `json:"digest"`
}
type StoreWakuMessage struct {
Payload []byte `json:"payload"`
ContentTopic string `json:"content_topic"`
Version int32 `json:"version"`
Timestamp int64 `json:"timestamp"`
Meta []byte `json:"meta"`
}
const routeStoreMessagesV1 = "/store/v1/messages"
func NewStoreService(node *node.WakuNode, m *chi.Mux) *StoreService {
s := &StoreService{
node: node,
mux: m,
}
m.Get(routeStoreMessagesV1, s.getV3Messages)
m.Get(routeStoreMessagesV1, s.getV1Messages)
return s
}
func getStoreParams(r *http.Request) (store.Criteria, []store.RequestOption, error) {
var options []store.RequestOption
var err error
func getStoreParams(r *http.Request) (multiaddr.Multiaddr, *store.Query, []store.HistoryRequestOption, error) {
query := &store.Query{}
var options []store.HistoryRequestOption
peerAddrStr := r.URL.Query().Get("peerAddr")
var m multiaddr.Multiaddr
if peerAddrStr != "" {
m, err = multiaddr.NewMultiaddr(peerAddrStr)
if err != nil {
return nil, nil, err
}
options = append(options, store.WithPeerAddr(m))
m, err := multiaddr.NewMultiaddr(peerAddrStr)
if err != nil {
return nil, nil, nil, err
}
includeData := false
includeDataStr := r.URL.Query().Get("includeData")
if includeDataStr != "" {
includeData, err = strconv.ParseBool(includeDataStr)
if err != nil {
return nil, nil, errors.New("invalid value for includeData. Use true|false")
}
peerID, err := utils.GetPeerID(m)
if err != nil {
return nil, nil, nil, err
}
options = append(options, store.IncludeData(includeData))
pubsubTopic := r.URL.Query().Get("pubsubTopic")
options = append(options, store.WithPeer(peerID))
query.Topic = r.URL.Query().Get("pubsubTopic")
contentTopics := r.URL.Query().Get("contentTopics")
var contentTopicsArr []string
if contentTopics != "" {
contentTopicsArr = strings.Split(contentTopics, ",")
}
hashesStr := r.URL.Query().Get("hashes")
var hashes []pb.MessageHash
if hashesStr != "" {
hashesStrArr := strings.Split(hashesStr, ",")
for _, hashStr := range hashesStrArr {
hash, err := base64.URLEncoding.DecodeString(hashStr)
if err != nil {
return nil, nil, err
}
hashes = append(hashes, pb.ToMessageHash(hash))
}
}
isMsgHashCriteria := false
if len(hashes) != 0 {
isMsgHashCriteria = true
if pubsubTopic != "" || len(contentTopics) != 0 {
return nil, nil, errors.New("cant use content filters while specifying message hashes")
}
} else {
if pubsubTopic == "" || len(contentTopicsArr) == 0 {
return nil, nil, errors.New("pubsubTopic and contentTopics are required")
}
query.ContentTopics = strings.Split(contentTopics, ",")
}
startTimeStr := r.URL.Query().Get("startTime")
var startTime *int64
if startTimeStr != "" {
startTimeValue, err := strconv.ParseInt(startTimeStr, 10, 64)
query.StartTime, err = strconv.ParseInt(startTimeStr, 10, 64)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
startTime = &startTimeValue
}
endTimeStr := r.URL.Query().Get("endTime")
var endTime *int64
if endTimeStr != "" {
endTimeValue, err := strconv.ParseInt(endTimeStr, 10, 64)
query.EndTime, err = strconv.ParseInt(endTimeStr, 10, 64)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
endTime = &endTimeValue
}
var cursor []byte
cursorStr := r.URL.Query().Get("cursor")
if cursorStr != "" {
cursor, err = base64.URLEncoding.DecodeString(cursorStr)
if err != nil {
return nil, nil, err
var cursor *pb.Index
senderTimeStr := r.URL.Query().Get("senderTime")
storeTimeStr := r.URL.Query().Get("storeTime")
digestStr := r.URL.Query().Get("digest")
if senderTimeStr != "" || storeTimeStr != "" || digestStr != "" {
cursor = &pb.Index{}
if senderTimeStr != "" {
cursor.SenderTime, err = strconv.ParseInt(senderTimeStr, 10, 64)
if err != nil {
return nil, nil, nil, err
}
}
if storeTimeStr != "" {
cursor.ReceiverTime, err = strconv.ParseInt(storeTimeStr, 10, 64)
if err != nil {
return nil, nil, nil, err
}
}
if digestStr != "" {
cursor.Digest, err = base64.URLEncoding.DecodeString(digestStr)
if err != nil {
return nil, nil, nil, err
}
}
cursor.PubsubTopic = query.Topic
options = append(options, store.WithCursor(cursor))
}
@ -128,49 +136,59 @@ func getStoreParams(r *http.Request) (store.Criteria, []store.RequestOption, err
ascendingStr := r.URL.Query().Get("ascending")
if ascendingStr != "" || pageSizeStr != "" {
ascending := true
pageSize := uint64(legacy_store.DefaultPageSize)
pageSize := uint64(store.MaxPageSize)
if ascendingStr != "" {
ascending, err = strconv.ParseBool(ascendingStr)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
}
if pageSizeStr != "" {
pageSize, err = strconv.ParseUint(pageSizeStr, 10, 64)
if err != nil {
return nil, nil, err
}
if pageSize > legacy_store.MaxPageSize {
pageSize = legacy_store.MaxPageSize
return nil, nil, nil, err
}
}
options = append(options, store.WithPaging(ascending, pageSize))
}
var query store.Criteria
if isMsgHashCriteria {
query = store.MessageHashCriteria{
MessageHashes: hashes,
}
} else {
query = store.FilterCriteria{
ContentFilter: protocol.NewContentFilter(pubsubTopic, contentTopicsArr...),
TimeStart: startTime,
TimeEnd: endTime,
}
}
return query, options, nil
return m, query, options, nil
}
func writeStoreError(w http.ResponseWriter, code int, err error) {
writeResponse(w, &storepb.StoreQueryResponse{StatusCode: proto.Uint32(uint32(code)), StatusDesc: proto.String(err.Error())}, code)
writeResponse(w, StoreResponse{ErrorMessage: err.Error()}, code)
}
func (d *StoreQueryService) getV3Messages(w http.ResponseWriter, r *http.Request) {
query, options, err := getStoreParams(r)
func toStoreResponse(result *store.Result) StoreResponse {
response := StoreResponse{}
cursor := result.Cursor()
if cursor != nil {
response.Cursor = &HistoryCursor{
PubsubTopic: cursor.PubsubTopic,
SenderTime: fmt.Sprintf("%d", cursor.SenderTime),
StoreTime: fmt.Sprintf("%d", cursor.ReceiverTime),
Digest: cursor.Digest,
}
}
for _, m := range result.Messages {
response.Messages = append(response.Messages, StoreWakuMessage{
Payload: m.Payload,
ContentTopic: m.ContentTopic,
Version: int32(m.Version),
Timestamp: m.Timestamp,
Meta: m.Meta,
})
}
return response
}
func (d *StoreService) getV1Messages(w http.ResponseWriter, r *http.Request) {
peerAddr, query, options, err := getStoreParams(r)
if err != nil {
writeStoreError(w, http.StatusBadRequest, err)
return
@ -179,11 +197,17 @@ func (d *StoreQueryService) getV3Messages(w http.ResponseWriter, r *http.Request
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
result, err := d.node.Store().Request(ctx, query, options...)
_, err = d.node.AddPeer(peerAddr, peerstore.Static, d.node.Relay().Topics())
if err != nil {
writeLegacyStoreError(w, http.StatusInternalServerError, err)
writeStoreError(w, http.StatusInternalServerError, err)
return
}
writeErrOrResponse(w, nil, result.Response())
result, err := d.node.Store().Query(ctx, *query, options...)
if err != nil {
writeStoreError(w, http.StatusInternalServerError, err)
return
}
writeErrOrResponse(w, nil, toStoreResponse(result))
}

View File

@ -1,135 +1,203 @@
# /store/v3/messages:
get:
summary: Gets message history
description: >
Retrieves Waku message history. The returned history
can be potentially filtered by optional request parameters.
operationId: getMessageHistory
tags:
- store
parameters:
- name: peerAddr
in: query
schema:
type: string
required: true
description: >
P2P fully qualified peer multiaddress
in the format `(ip4|ip6)/tcp/p2p/$peerId` and URL-encoded.
example: '%2Fip4%2F127.0.0.1%2Ftcp%2F60001%2Fp2p%2F16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN'
openapi: 3.0.3
info:
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team
url: https://forum.vac.dev/
- name: includeData
in: query
schema:
type: string
description: >
Boolean indicating if the query should return messages (data) or hashes only.
A value of 'false' returns hashes only.
A value of 'true' returns hashes AND messages.
Default value is 'false'
example: 'true'
tags:
- name: store
description: Store REST API for WakuV2 node
- name: pubsubTopic
in: query
schema:
type: string
paths:
/store/v1/messages:
get:
summary: Gets message history
description: >
The pubsub topic on which a WakuMessage is published.
If left empty, no filtering is applied.
It is also intended for pagination purposes.
It should be a URL-encoded string.
example: 'my%20pubsub%20topic'
- name: contentTopics
in: query
schema: string
description: >
Comma-separated list of content topics. When specified,
only WakuMessages that are linked to any of the given
content topics will be delivered in the get response.
It should be a URL-encoded-comma-separated string.
example: 'my%20first%20content%20topic%2Cmy%20second%20content%20topic%2Cmy%20third%20content%20topic'
- name: startTime
in: query
schema:
type: string
description: >
The inclusive lower bound on the timestamp of
queried WakuMessages. This field holds the
Unix epoch time in nanoseconds as a 64-bits
integer value.
example: '1680590945000000000'
- name: endTime
in: query
schema:
type: string
description: >
The inclusive upper bound on the timestamp of
queried WakuMessages. This field holds the
Unix epoch time in nanoseconds as a 64-bits
integer value.
example: '1680590945000000000'
- name: hashes
in: query
schema:
type: string
description: >
Comma-separated list of message hashes.
URL-base64-encoded string computed as a hash of messages.
Used to find messages by hash.
example: 'Gc4ACThW5t2QQO82huq3WnDv%2FapPPJpD%2FwJfxDxAnR0%3D'
- name: cursor
in: query
schema:
type: string
description: >
Cursor field intended for pagination purposes.
URL-base64-encoded string computed as a hash of a message.
It could be empty for retrieving the first page,
and will be returned from the GET response so that
it can be part of the next page request.
example: 'Gc4ACThW5t2QQO82huq3WnDv%2FapPPJpD%2FwJfxDxAnR0%3D'
- name: pageSize
in: query
schema:
type: string
description: >
Number of messages to retrieve per page
example: '5'
- name: ascending
in: query
schema:
type: string
description: >
"true" for paging forward, "false" for paging backward.
If not specified or if specified with an invalid value, the default is "true".
example: "true"
responses:
'200':
description: Waku message history.
content:
application/json:
Retrieves WakuV2 message history. The returned history
can be potentially filtered by optional request parameters.
operationId: getMessageHistory
tags:
- store
parameters:
- name: peerAddr
in: query
schema:
$ref: '#/components/schemas/StoreQueryResponse'
'400':
description: Bad request error.
content:
text/plain:
type: string
required: true
description: >
P2P fully qualified peer multiaddress
in the format `(ip4|ip6)/tcp/p2p/$peerId` and URL-encoded.
example: '%2Fip4%2F127.0.0.1%2Ftcp%2F60001%2Fp2p%2F16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN'
- name: pubsubTopic
in: query
schema:
type: string
description: >
The pubsub topic on which a WakuMessage is published.
If left empty, no filtering is applied.
It is also intended for pagination purposes.
It should be a URL-encoded string.
example: 'my%20pubsub%20topic'
- name: contentTopics
in: query
schema: string
description: >
Comma-separated list of content topics. When specified,
only WakuMessages that are linked to any of the given
content topics will be delivered in the get response.
It should be a URL-encoded-comma-separated string.
example: 'my%20first%20content%20topic%2Cmy%20second%20content%20topic%2Cmy%20third%20content%20topic'
- name: startTime
in: query
schema:
type: string
description: >
The inclusive lower bound on the timestamp of
queried WakuMessages. This field holds the
Unix epoch time in nanoseconds as a 64-bits
integer value.
example: '1680590945000000000'
- name: endTime
in: query
schema:
type: string
description: >
The inclusive upper bound on the timestamp of
queried WakuMessages. This field holds the
Unix epoch time in nanoseconds as a 64-bits
integer value.
example: '1680590945000000000'
- name: senderTime
in: query
schema:
type: string
description: >
Cursor field intended for pagination purposes.
Represents the Unix time in nanoseconds at which a message was generated.
It could be empty for retrieving the first page,
and will be returned from the GET response so that
it can be part of the next page request.
example: '1680590947000000000'
- name: storeTime
in: query
schema:
type: string
description: >
Cursor field intended for pagination purposes.
Represents the Unix time in nanoseconds at which a message was stored.
It could be empty for retrieving the first page,
and will be returned from the GET response so that
it can be part of the next page request.
example: '1680590945000000000'
- name: digest
in: query
schema:
type: string
description: >
Cursor field intended for pagination purposes.
URL-base64-encoded string computed as a hash of the
a message content topic plus a message payload.
It could be empty for retrieving the first page,
and will be returned from the GET response so that
it can be part of the next page request.
example: 'Gc4ACThW5t2QQO82huq3WnDv%2FapPPJpD%2FwJfxDxAnR0%3D'
- name: pageSize
in: query
schema:
type: string
description: >
Number of messages to retrieve per page
example: '5'
- name: ascending
in: query
schema:
type: string
description: >
"true" for paging forward, "false" for paging backward
example: "true"
responses:
'200':
description: WakuV2 message history.
content:
application/json:
schema:
$ref: '#/components/schemas/StoreResponse'
'400':
description: Bad request error.
content:
text/plain:
type: string
'412':
description: Precondition failed.
content:
text/plain:
type: string
'500':
description: Internal server error.
content:
text/plain:
type: string
components:
schemas:
StoreResponse:
type: object
properties:
messages:
type: array
items:
$ref: '#/components/schemas/WakuMessage'
cursor:
$ref: '#/components/schemas/HistoryCursor'
error_message:
type: string
'412':
description: Precondition failed.
content:
text/plain:
required:
- messages
HistoryCursor:
type: object
properties:
pubsub_topic:
type: string
'500':
description: Internal server error.
content:
text/plain:
type: string
sender_time:
type: string
store_time:
type: string
digest:
type: string
required:
- pubsub_topic
- sender_time
- store_time
- digest
WakuMessage:
type: object
properties:
payload:
type: string
content_topic:
type: string
version:
type: integer
format: int32
timestamp:
type: integer
format: int64
ephemeral:
type: boolean
required:
- payload
- content_topic

View File

@ -17,7 +17,6 @@ import (
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/utils"
"google.golang.org/protobuf/proto"
)
func TestGetMessages(t *testing.T) {
@ -33,16 +32,16 @@ func TestGetMessages(t *testing.T) {
topic1 := "1"
pubsubTopic1 := "topic1"
now := *utils.GetUnixEpoch()
msg1 := tests.CreateWakuMessage(topic1, proto.Int64(now+1))
msg2 := tests.CreateWakuMessage(topic1, proto.Int64(now+2))
msg3 := tests.CreateWakuMessage(topic1, proto.Int64(now+3))
now := utils.GetUnixEpoch()
msg1 := tests.CreateWakuMessage(topic1, now+1)
msg2 := tests.CreateWakuMessage(topic1, now+2)
msg3 := tests.CreateWakuMessage(topic1, now+3)
node1.Broadcaster().Submit(protocol.NewEnvelope(msg1, *utils.GetUnixEpoch(), pubsubTopic1))
node1.Broadcaster().Submit(protocol.NewEnvelope(msg2, *utils.GetUnixEpoch(), pubsubTopic1))
node1.Broadcaster().Submit(protocol.NewEnvelope(msg3, *utils.GetUnixEpoch(), pubsubTopic1))
node1.Broadcaster().Submit(protocol.NewEnvelope(msg1, utils.GetUnixEpoch(), pubsubTopic1))
node1.Broadcaster().Submit(protocol.NewEnvelope(msg2, utils.GetUnixEpoch(), pubsubTopic1))
node1.Broadcaster().Submit(protocol.NewEnvelope(msg3, utils.GetUnixEpoch(), pubsubTopic1))
n1HostInfo, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", node1.Host().ID().String()))
n1HostInfo, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", node1.Host().ID().Pretty()))
n1Addr := node1.ListenAddresses()[0].Encapsulate(n1HostInfo)
node2, err := node.New()
@ -52,7 +51,7 @@ func TestGetMessages(t *testing.T) {
defer node2.Stop()
router := chi.NewRouter()
_ = NewLegacyStoreService(node2, router)
_ = NewStoreService(node2, router)
// TEST: get cursor
// TEST: get no messages
@ -64,12 +63,12 @@ func TestGetMessages(t *testing.T) {
"pubsubTopic": {pubsubTopic1},
"pageSize": {"2"},
}
path := routeLegacyStoreMessagesV1 + "?" + queryParams.Encode()
path := routeStoreMessagesV1 + "?" + queryParams.Encode()
req, _ := http.NewRequest(http.MethodGet, path, nil)
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
response := LegacyStoreResponse{}
response := StoreResponse{}
err = json.Unmarshal(rr.Body.Bytes(), &response)
require.NoError(t, err)
require.Len(t, response.Messages, 2)
@ -84,12 +83,12 @@ func TestGetMessages(t *testing.T) {
"digest": {base64.URLEncoding.EncodeToString(response.Cursor.Digest)},
"pageSize": {"2"},
}
path = routeLegacyStoreMessagesV1 + "?" + queryParams.Encode()
path = routeStoreMessagesV1 + "?" + queryParams.Encode()
req, _ = http.NewRequest(http.MethodGet, path, nil)
router.ServeHTTP(rr, req)
require.Equal(t, http.StatusOK, rr.Code)
response = LegacyStoreResponse{}
response = StoreResponse{}
err = json.Unmarshal(rr.Body.Bytes(), &response)
require.NoError(t, err)
require.Len(t, response.Messages, 1)

View File

@ -2,24 +2,9 @@ package rest
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
)
// The functions writes error response in plain text format with specified statusCode
func writeErrResponse(w http.ResponseWriter, log *zap.Logger, err error, statusCode int) {
w.WriteHeader(statusCode)
_, err = w.Write([]byte(err.Error()))
if err != nil {
log.Error("error while writing response", zap.Error(err))
}
}
// This function writes error or response in json format with statusCode as 500 in case of error
func writeErrOrResponse(w http.ResponseWriter, err error, value interface{}) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
@ -41,7 +26,6 @@ func writeErrOrResponse(w http.ResponseWriter, err error, value interface{}) {
}
}
// This function writes a response in json format
func writeResponse(w http.ResponseWriter, value interface{}, code int) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
jsonResponse, err := json.Marshal(value)
@ -50,34 +34,11 @@ func writeResponse(w http.ResponseWriter, value interface{}, code int) {
return
}
// w.Write implicitly writes a 200 status code
// and only once we can write 2xx-5xx status code
// so any statusCode apart from 1xx being written to the header, will be ignored.
w.WriteHeader(code)
_, _ = w.Write(jsonResponse)
}
func topicFromPath(w http.ResponseWriter, req *http.Request, field string, logger *zap.Logger) string {
topic := chi.URLParam(req, field)
if topic == "" {
errMissing := fmt.Errorf("missing %s", field)
writeGetMessageErr(w, errMissing, http.StatusBadRequest, logger)
return ""
}
topic, err := url.QueryUnescape(topic)
_, err = w.Write(jsonResponse)
if err != nil {
errInvalid := fmt.Errorf("invalid %s format", field)
writeGetMessageErr(w, errInvalid, http.StatusBadRequest, logger)
return ""
w.WriteHeader(http.StatusInternalServerError)
return
}
return topic
}
func writeGetMessageErr(w http.ResponseWriter, err error, code int, logger *zap.Logger) {
// write status before the body
w.WriteHeader(code)
logger.Error("get message", zap.Error(err))
if _, err := w.Write([]byte(err.Error())); err != nil {
logger.Error("writing response", zap.Error(err))
}
}

View File

@ -13,7 +13,7 @@ import (
func MemoryDB(t *testing.T) *persistence.DBStore {
var db *sql.DB
db, err := sqlite.NewDB(":memory:", utils.Logger())
db, err := sqlite.NewDB(":memory:", false, utils.Logger())
require.NoError(t, err)
dbStore, err := persistence.NewDBStore(prometheus.DefaultRegisterer, utils.Logger(), persistence.WithDB(db), persistence.WithMigrations(sqlite.Migrations))

View File

@ -18,44 +18,26 @@ type WakuRest struct {
log *zap.Logger
relayService *RelayService
filterService *FilterService
relayService *RelayService
}
type RestConfig struct {
Address string
Port uint
EnablePProf bool
EnableAdmin bool
RelayCacheCapacity uint
FilterCacheCapacity uint
}
func NewWakuRest(node *node.WakuNode, config RestConfig, log *zap.Logger) *WakuRest {
func NewWakuRest(node *node.WakuNode, address string, port int, enablePProf bool, relayCacheCapacity int, log *zap.Logger) *WakuRest {
wrpc := new(WakuRest)
wrpc.log = log.Named("rest")
mux := chi.NewRouter()
mux.Use(middleware.Logger)
mux.Use(middleware.NoCache)
mux.Use(func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
})
if config.EnablePProf {
if enablePProf {
mux.Mount("/debug", middleware.Profiler())
}
_ = NewDebugService(node, mux)
_ = NewHealthService(node, mux)
_ = NewStoreQueryService(node, mux)
_ = NewLegacyStoreService(node, mux)
_ = NewLightpushService(node, mux, log)
_ = NewStoreService(node, mux)
listenAddr := fmt.Sprintf("%s:%d", config.Address, config.Port)
listenAddr := fmt.Sprintf("%s:%d", address, port)
server := &http.Server{
Addr: listenAddr,
@ -66,20 +48,11 @@ func NewWakuRest(node *node.WakuNode, config RestConfig, log *zap.Logger) *WakuR
wrpc.server = server
if node.Relay() != nil {
relayService := NewRelayService(node, mux, config.RelayCacheCapacity, log)
wrpc.relayService = relayService
}
if config.EnableAdmin {
_ = NewAdminService(node, mux, wrpc.log)
}
if node.FilterLightnode() != nil {
filterService := NewFilterService(node, mux, int(config.FilterCacheCapacity), log)
relayService := NewRelayService(node, mux, relayCacheCapacity, log)
server.RegisterOnShutdown(func() {
filterService.Stop()
relayService.Stop()
})
wrpc.filterService = filterService
wrpc.relayService = relayService
}
return wrpc
@ -88,8 +61,8 @@ func NewWakuRest(node *node.WakuNode, config RestConfig, log *zap.Logger) *WakuR
func (r *WakuRest) Start(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
if r.node.FilterLightnode() != nil {
go r.filterService.Start(ctx)
if r.node.Relay() != nil {
go r.relayService.Start(ctx)
}
go func() {

View File

@ -13,7 +13,7 @@ func TestWakuRest(t *testing.T) {
n, err := node.New(options)
require.NoError(t, err)
rpc := NewWakuRest(n, RestConfig{Address: "127.0.0.1", Port: 8080, EnablePProf: false, EnableAdmin: false, RelayCacheCapacity: 10}, utils.Logger())
rpc := NewWakuRest(n, "127.0.0.1", 8080, false, 10, utils.Logger())
require.NotNil(t, rpc.server)
require.Equal(t, rpc.server.Addr, "127.0.0.1:8080")
}

View File

@ -0,0 +1,87 @@
package rpc
import (
"net/http"
"github.com/libp2p/go-libp2p/core/protocol"
ma "github.com/multiformats/go-multiaddr"
"go.uber.org/zap"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter"
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
)
type AdminService struct {
node *node.WakuNode
log *zap.Logger
}
type GetPeersArgs struct {
}
type PeersArgs struct {
Peers []string `json:"peers,omitempty"`
}
type PeerReply struct {
Multiaddr string `json:"multiaddr,omitempty"`
Protocol protocol.ID `json:"protocol,omitempty"`
Connected bool `json:"connected,omitempty"`
}
type PeersReply []PeerReply
func (a *AdminService) PostV1Peers(req *http.Request, args *PeersArgs, reply *SuccessReply) error {
for _, peer := range args.Peers {
addr, err := ma.NewMultiaddr(peer)
if err != nil {
a.log.Error("building multiaddr", zap.Error(err))
return err
}
err = a.node.DialPeerWithMultiAddress(req.Context(), addr)
if err != nil {
a.log.Error("dialing peers", zap.Error(err))
return err
}
}
*reply = true
return nil
}
func isWakuProtocol(protocol protocol.ID) bool {
return protocol == legacy_filter.FilterID_v20beta1 ||
protocol == filter.FilterPushID_v20beta1 ||
protocol == filter.FilterSubscribeID_v20beta1 ||
protocol == relay.WakuRelayID_v200 ||
protocol == lightpush.LightPushID_v20beta1 ||
protocol == store.StoreID_v20beta4
}
func (a *AdminService) GetV1Peers(req *http.Request, args *GetPeersArgs, reply *PeersReply) error {
peers, err := a.node.Peers()
if err != nil {
a.log.Error("getting peers", zap.Error(err))
return nil
}
for _, peer := range peers {
for _, addr := range peer.Addrs {
for _, proto := range peer.Protocols {
if !isWakuProtocol(proto) {
continue
}
*reply = append(*reply, PeerReply{
Multiaddr: addr.String(),
Protocol: proto,
Connected: peer.Connected,
})
}
}
}
return nil
}

View File

@ -0,0 +1,74 @@
package rpc
import (
"bytes"
"context"
"crypto/rand"
"fmt"
"net/http"
"testing"
"time"
"github.com/multiformats/go-multiaddr"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/tests"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/timesource"
"github.com/waku-org/go-waku/waku/v2/utils"
)
func makeAdminService(t *testing.T) *AdminService {
options := node.WithWakuRelay()
n, err := node.New(options)
require.NoError(t, err)
err = n.Start(context.Background())
require.NoError(t, err)
return &AdminService{n, utils.Logger()}
}
func TestV1Peers(t *testing.T) {
port, err := tests.FindFreePort(t, "", 5)
require.NoError(t, err)
host, err := tests.MakeHost(context.Background(), port, rand.Reader)
require.NoError(t, err)
relay := relay.NewWakuRelay(nil, 0, timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger())
relay.SetHost(host)
err = relay.Start(context.Background())
require.NoError(t, err)
defer relay.Stop()
var reply PeersReply
request, err := http.NewRequest(http.MethodPost, "url", bytes.NewReader([]byte("")))
require.NoError(t, err)
a := makeAdminService(t)
err = a.GetV1Peers(request, &GetPeersArgs{}, &reply)
require.NoError(t, err)
require.Len(t, reply, 0)
var reply2 SuccessReply
hostInfo, err := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", host.ID().Pretty()))
require.NoError(t, err)
var addr multiaddr.Multiaddr
for _, a := range host.Addrs() {
addr = a.Encapsulate(hostInfo)
break
}
err = a.PostV1Peers(request, &PeersArgs{Peers: []string{addr.String()}}, &reply2)
require.NoError(t, err)
require.True(t, reply2)
time.Sleep(2 * time.Second)
err = a.GetV1Peers(request, &GetPeersArgs{}, &reply)
require.NoError(t, err)
require.Len(t, reply, 1)
}

View File

@ -0,0 +1,195 @@
package rpc
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"github.com/gorilla/rpc/v2"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
// Based on github.com/gorilla/rpc/v2/json which is governed by a BSD-style license
var null = json.RawMessage([]byte("null"))
// An Error is a wrapper for a JSON interface value. It can be used by either
// a service's handler func to write more complex JSON data to an error field
// of a server's response, or by a client to read it.
type Error struct {
Data interface{}
}
func (e *Error) Error() string {
return fmt.Sprintf("%v", e.Data)
}
// ----------------------------------------------------------------------------
// Request and Response
// ----------------------------------------------------------------------------
// serverRequest represents a JSON-RPC request received by the server.
type serverRequest struct {
// A String containing the name of the method to be invoked.
Method string `json:"method"`
// An Array of objects to pass as arguments to the method.
Params *json.RawMessage `json:"params"`
// The request id. This can be of any type. It is used to match the
// response with the request that it is replying to.
ID *json.RawMessage `json:"id"`
}
// serverResponse represents a JSON-RPC response returned by the server.
type serverResponse struct {
// The Object that was returned by the invoked method. This must be null
// in case there was an error invoking the method.
Result interface{} `json:"result"`
// An Error object if there was an error invoking the method. It must be
// null if there was no error.
Error interface{} `json:"error"`
// This must be the same id as the request it is responding to.
ID *json.RawMessage `json:"id"`
}
// ----------------------------------------------------------------------------
// Codec
// ----------------------------------------------------------------------------
// NewCodec returns a new SnakeCaseCodec Codec.
func NewSnakeCaseCodec() *SnakeCaseCodec {
return &SnakeCaseCodec{}
}
// SnakeCaseCodec creates a CodecRequest to process each request.
type SnakeCaseCodec struct {
}
// NewRequest returns a CodecRequest.
func (c *SnakeCaseCodec) NewRequest(r *http.Request) rpc.CodecRequest {
return newCodecRequest(r)
}
// ----------------------------------------------------------------------------
// CodecRequest
// ----------------------------------------------------------------------------
// newCodecRequest returns a new CodecRequest.
func newCodecRequest(r *http.Request) rpc.CodecRequest {
// Decode the request body and check if RPC method is valid.
req := new(serverRequest)
err := json.NewDecoder(r.Body).Decode(req)
r.Body.Close()
return &CodecRequest{request: req, err: err}
}
// CodecRequest decodes and encodes a single request.
type CodecRequest struct {
request *serverRequest
err error
}
// Method returns the RPC method for the current request.
//
// The method uses a dotted notation as in "Service.Method".
func (c *CodecRequest) Method() (string, error) {
if c.err == nil {
return toWakuMethod(c.request.Method), nil
}
return "", c.err
}
// toWakuMethod transform get_waku_v2_debug_v1_info to Debug.GetV1Info
func toWakuMethod(input string) string {
typ := "get"
if strings.HasPrefix(input, "post") {
typ = "post"
} else if strings.HasPrefix(input, "delete") {
typ = "delete"
}
base := typ + "_waku_v2_"
cleanedInput := strings.Replace(input, base, "", 1)
splited := strings.Split(cleanedInput, "_")
c := cases.Title(language.AmericanEnglish)
method := c.String(typ)
for _, val := range splited[1:] {
method = method + c.String(val)
}
return c.String(splited[0]) + "." + method
}
// ReadRequest fills the request object for the RPC method.
func (c *CodecRequest) ReadRequest(args interface{}) error {
if c.err == nil {
if c.request.Params != nil {
// JSON params is array value. RPC params is struct.
// Attempt to unmarshal into array containing the request struct.
params := [1]interface{}{args}
err := json.Unmarshal(*c.request.Params, &params)
if err != nil {
// This failed so we might have received an array of parameters
// instead of a object
argsValueOf := reflect.Indirect(reflect.ValueOf(args))
if argsValueOf.Kind() == reflect.Struct {
var params []interface{}
for i := 0; i < argsValueOf.NumField(); i++ {
params = append(params, argsValueOf.Field(i).Addr().Interface())
}
c.err = json.Unmarshal(*c.request.Params, &params)
} else {
// Unknown field type...
c.err = err
}
}
} else {
c.err = errors.New("rpc: method request ill-formed: missing params field")
}
}
return c.err
}
// WriteResponse encodes the response and writes it to the ResponseWriter.
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
if c.request.ID != nil {
// Id is null for notifications and they don't have a response.
res := &serverResponse{
Result: reply,
Error: &null,
ID: c.request.ID,
}
c.writeServerResponse(w, 200, res)
}
}
func (c *CodecRequest) WriteError(w http.ResponseWriter, _ int, err error) {
res := &serverResponse{
Result: &null,
ID: c.request.ID,
}
if jsonErr, ok := err.(*Error); ok {
res.Error = jsonErr.Data
} else {
res.Error = err.Error()
}
c.writeServerResponse(w, 400, res)
}
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, status int, res *serverResponse) {
b, err := json.Marshal(res)
if err == nil {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_, _ = w.Write(b)
} else {
// Not sure in which case will this happen. But seems harmless.
rpc.WriteError(w, status, err.Error())
}
}

View File

@ -0,0 +1,18 @@
package rpc
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestConvertWakuMethod(t *testing.T) {
res := toWakuMethod("get_waku_v2_debug_v1_info")
require.Equal(t, "Debug.GetV1Info", res)
res = toWakuMethod("post_waku_v2_relay_v1_message")
require.Equal(t, "Relay.PostV1Message", res)
res = toWakuMethod("delete_waku_v2_relay_v1_subscriptions")
require.Equal(t, "Relay.DeleteV1Subscriptions", res)
}

View File

@ -0,0 +1,40 @@
package rpc
import (
"net/http"
"github.com/waku-org/go-waku/waku/v2/node"
)
type DebugService struct {
node *node.WakuNode
}
type InfoArgs struct {
}
type InfoReply struct {
ENRUri string `json:"enrUri,omitempty"`
ListenAddresses []string `json:"listenAddresses,omitempty"`
}
func NewDebugService(node *node.WakuNode) *DebugService {
return &DebugService{
node: node,
}
}
func (d *DebugService) GetV1Info(r *http.Request, args *InfoArgs, reply *InfoReply) error {
reply.ENRUri = d.node.ENR().String()
for _, addr := range d.node.ListenAddresses() {
reply.ListenAddresses = append(reply.ListenAddresses, addr.String())
}
return nil
}
type VersionResponse string
func (d *DebugService) GetV1Version(r *http.Request, args *InfoArgs, reply *VersionResponse) error {
*reply = VersionResponse(node.GetVersionInfo().String())
return nil
}

View File

@ -0,0 +1,31 @@
package rpc
import (
"bytes"
"context"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/waku/v2/node"
)
func TestGetV1Info(t *testing.T) {
var reply InfoReply
request, err := http.NewRequest(http.MethodPost, "url", bytes.NewReader([]byte("")))
require.NoError(t, err)
wakuNode1, err := node.New()
require.NoError(t, err)
defer wakuNode1.Stop()
err = wakuNode1.Start(context.Background())
require.NoError(t, err)
d := &DebugService{
node: wakuNode1,
}
err = d.GetV1Info(request, &InfoArgs{}, &reply)
require.NoError(t, err)
}

View File

@ -0,0 +1,133 @@
package rpc
import (
"fmt"
"net/http"
"sync"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter/pb"
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
"go.uber.org/zap"
)
type FilterService struct {
node *node.WakuNode
log *zap.Logger
messages map[string][]*wpb.WakuMessage
cacheCapacity int
messagesMutex sync.RWMutex
runner *runnerService
}
type FilterContentArgs struct {
Topic string `json:"topic,omitempty"`
ContentFilters []*pb.FilterRequest_ContentFilter `json:"contentFilters,omitempty"`
}
type ContentTopicArgs struct {
ContentTopic string `json:"contentTopic,omitempty"`
}
func NewFilterService(node *node.WakuNode, cacheCapacity int, log *zap.Logger) *FilterService {
s := &FilterService{
node: node,
log: log.Named("filter"),
cacheCapacity: cacheCapacity,
messages: make(map[string][]*wpb.WakuMessage),
}
s.runner = newRunnerService(node.Broadcaster(), s.addEnvelope)
return s
}
func makeContentFilter(args *FilterContentArgs) legacy_filter.ContentFilter {
var contentTopics []string
for _, contentFilter := range args.ContentFilters {
contentTopics = append(contentTopics, contentFilter.ContentTopic)
}
return legacy_filter.ContentFilter{
Topic: args.Topic,
ContentTopics: contentTopics,
}
}
func (f *FilterService) addEnvelope(envelope *protocol.Envelope) {
f.messagesMutex.Lock()
defer f.messagesMutex.Unlock()
contentTopic := envelope.Message().ContentTopic
if _, ok := f.messages[contentTopic]; !ok {
return
}
// Keep a specific max number of messages per topic
if len(f.messages[envelope.PubsubTopic()]) >= f.cacheCapacity {
f.messages[envelope.PubsubTopic()] = f.messages[envelope.PubsubTopic()][1:]
}
f.messages[contentTopic] = append(f.messages[contentTopic], envelope.Message())
}
func (f *FilterService) Start() {
f.runner.Start()
}
func (f *FilterService) Stop() {
f.runner.Stop()
}
func (f *FilterService) PostV1Subscription(req *http.Request, args *FilterContentArgs, reply *SuccessReply) error {
_, _, err := f.node.LegacyFilter().Subscribe(
req.Context(),
makeContentFilter(args),
legacy_filter.WithAutomaticPeerSelection(),
)
if err != nil {
f.log.Error("subscribing to topic", zap.String("topic", args.Topic), zap.Error(err))
return err
}
for _, contentFilter := range args.ContentFilters {
f.messages[contentFilter.ContentTopic] = make([]*wpb.WakuMessage, 0)
}
*reply = true
return nil
}
func (f *FilterService) DeleteV1Subscription(req *http.Request, args *FilterContentArgs, reply *SuccessReply) error {
err := f.node.LegacyFilter().UnsubscribeFilter(
req.Context(),
makeContentFilter(args),
)
if err != nil {
f.log.Error("unsubscribing from topic", zap.String("topic", args.Topic), zap.Error(err))
return err
}
for _, contentFilter := range args.ContentFilters {
delete(f.messages, contentFilter.ContentTopic)
}
*reply = true
return nil
}
func (f *FilterService) GetV1Messages(req *http.Request, args *ContentTopicArgs, reply *MessagesReply) error {
f.messagesMutex.Lock()
defer f.messagesMutex.Unlock()
if _, ok := f.messages[args.ContentTopic]; !ok {
return fmt.Errorf("topic %s not subscribed", args.ContentTopic)
}
for i := range f.messages[args.ContentTopic] {
*reply = append(*reply, ProtoToRPC(f.messages[args.ContentTopic][i]))
}
f.messages[args.ContentTopic] = make([]*wpb.WakuMessage, 0)
return nil
}

View File

@ -0,0 +1,172 @@
package rpc
import (
"context"
"crypto/rand"
"fmt"
"testing"
"time"
"github.com/multiformats/go-multiaddr"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/tests"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_filter/pb"
wpb "github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/timesource"
"github.com/waku-org/go-waku/waku/v2/utils"
)
var testTopic = "test"
func makeFilterService(t *testing.T, isFullNode bool) *FilterService {
var nodeOpts []node.WakuNodeOption
nodeOpts = append(nodeOpts, node.WithLegacyWakuFilter(isFullNode))
if isFullNode {
nodeOpts = append(nodeOpts, node.WithWakuRelay())
}
n, err := node.New(nodeOpts...)
require.NoError(t, err)
err = n.Start(context.Background())
require.NoError(t, err)
if isFullNode {
sub, err := n.Relay().SubscribeToTopic(context.Background(), testTopic)
go func() {
for range sub.Ch {
}
}()
require.NoError(t, err)
}
return NewFilterService(n, 30, utils.Logger())
}
func TestFilterSubscription(t *testing.T) {
port, err := tests.FindFreePort(t, "", 5)
require.NoError(t, err)
host, err := tests.MakeHost(context.Background(), port, rand.Reader)
require.NoError(t, err)
b := relay.NewBroadcaster(10)
require.NoError(t, b.Start(context.Background()))
node := relay.NewWakuRelay(b, 0, timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger())
node.SetHost(host)
err = node.Start(context.Background())
require.NoError(t, err)
_, err = node.SubscribeToTopic(context.Background(), testTopic)
require.NoError(t, err)
b2 := relay.NewBroadcaster(10)
require.NoError(t, b2.Start(context.Background()))
f := legacy_filter.NewWakuFilter(b2, false, timesource.NewDefaultClock(), prometheus.DefaultRegisterer, utils.Logger())
f.SetHost(host)
err = f.Start(context.Background(), relay.NoopSubscription())
require.NoError(t, err)
d := makeFilterService(t, true)
defer d.node.Stop()
hostInfo, err := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", host.ID().Pretty()))
require.NoError(t, err)
var addr multiaddr.Multiaddr
for _, a := range host.Addrs() {
addr = a.Encapsulate(hostInfo)
break
}
_, err = d.node.AddPeer(addr, peerstore.Static, []string{testTopic}, legacy_filter.FilterID_v20beta1)
require.NoError(t, err)
args := &FilterContentArgs{Topic: testTopic, ContentFilters: []*pb.FilterRequest_ContentFilter{{ContentTopic: "ct"}}}
var reply SuccessReply
err = d.PostV1Subscription(
makeRequest(t),
args,
&reply,
)
require.NoError(t, err)
require.True(t, reply)
err = d.DeleteV1Subscription(
makeRequest(t),
args,
&reply,
)
require.NoError(t, err)
require.True(t, reply)
}
func TestFilterGetV1Messages(t *testing.T) {
serviceA := makeFilterService(t, true)
var reply SuccessReply
serviceB := makeFilterService(t, false)
go serviceB.Start()
defer serviceB.Stop()
hostInfo, err := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", serviceB.node.Host().ID().Pretty()))
require.NoError(t, err)
var addr multiaddr.Multiaddr
for _, a := range serviceB.node.Host().Addrs() {
addr = a.Encapsulate(hostInfo)
break
}
err = serviceA.node.DialPeerWithMultiAddress(context.Background(), addr)
require.NoError(t, err)
// Wait for the dial to complete
time.Sleep(1 * time.Second)
args := &FilterContentArgs{Topic: testTopic, ContentFilters: []*pb.FilterRequest_ContentFilter{{ContentTopic: "ct"}}}
err = serviceB.PostV1Subscription(
makeRequest(t),
args,
&reply,
)
require.NoError(t, err)
require.True(t, reply)
// Wait for the subscription to be started
time.Sleep(1 * time.Second)
_, err = serviceA.node.Relay().PublishToTopic(
context.Background(),
&wpb.WakuMessage{ContentTopic: "ct"},
testTopic,
)
require.NoError(t, err)
require.True(t, reply)
// Wait for the message to be received
time.Sleep(1 * time.Second)
var messagesReply1 MessagesReply
err = serviceB.GetV1Messages(
makeRequest(t),
&ContentTopicArgs{"ct"},
&messagesReply1,
)
require.NoError(t, err)
require.Len(t, messagesReply1, 1)
var messagesReply2 MessagesReply
err = serviceB.GetV1Messages(
makeRequest(t),
&ContentTopicArgs{"ct"},
&messagesReply2,
)
require.NoError(t, err)
require.Len(t, messagesReply2, 0)
}

View File

@ -0,0 +1,187 @@
package rpc
import (
"errors"
"fmt"
"net/http"
"sync"
"github.com/waku-org/go-waku/cmd/waku/server"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"go.uber.org/zap"
)
// RelayService represents the JSON RPC service for WakuRelay
type RelayService struct {
node *node.WakuNode
log *zap.Logger
messages map[string][]*pb.WakuMessage
cacheCapacity int
messagesMutex sync.RWMutex
runner *runnerService
}
// RelayMessageArgs represents the requests used for posting messages
type RelayMessageArgs struct {
Topic string `json:"topic,omitempty"`
Message *RPCWakuMessage `json:"message,omitempty"`
}
// TopicsArgs represents the lists of topics to use when subscribing / unsubscribing
type TopicsArgs struct {
Topics []string `json:"topics,omitempty"`
}
// TopicArgs represents a request that contains a single topic
type TopicArgs struct {
Topic string `json:"topic,omitempty"`
}
// NewRelayService returns an instance of RelayService
func NewRelayService(node *node.WakuNode, cacheCapacity int, log *zap.Logger) *RelayService {
s := &RelayService{
node: node,
cacheCapacity: cacheCapacity,
log: log.Named("relay"),
messages: make(map[string][]*pb.WakuMessage),
}
s.runner = newRunnerService(node.Broadcaster(), s.addEnvelope)
return s
}
func (r *RelayService) addEnvelope(envelope *protocol.Envelope) {
r.messagesMutex.Lock()
defer r.messagesMutex.Unlock()
if _, ok := r.messages[envelope.PubsubTopic()]; !ok {
return
}
// Keep a specific max number of messages per topic
if len(r.messages[envelope.PubsubTopic()]) >= r.cacheCapacity {
r.messages[envelope.PubsubTopic()] = r.messages[envelope.PubsubTopic()][1:]
}
r.messages[envelope.PubsubTopic()] = append(r.messages[envelope.PubsubTopic()], envelope.Message())
}
// Start starts the RelayService
func (r *RelayService) Start() {
r.messagesMutex.Lock()
// Node may already be subscribed to some topics when Relay API handlers are installed. Let's add these
for _, topic := range r.node.Relay().Topics() {
r.log.Info("adding topic handler for existing subscription", zap.String("topic", topic))
r.messages[topic] = make([]*pb.WakuMessage, 0)
}
r.messagesMutex.Unlock()
r.runner.Start()
}
// Stop stops the RelayService
func (r *RelayService) Stop() {
r.runner.Stop()
}
// PostV1Message is invoked when the json rpc request uses the post_waku_v2_relay_v1_message method
func (r *RelayService) PostV1Message(req *http.Request, args *RelayMessageArgs, reply *SuccessReply) error {
var err error
topic := relay.DefaultWakuTopic
if args.Topic != "" {
topic = args.Topic
}
if !r.node.Relay().IsSubscribed(topic) {
return errors.New("not subscribed to pubsubTopic")
}
msg := args.Message.toProto()
if err = server.AppendRLNProof(r.node, msg); err != nil {
return err
}
_, err = r.node.Relay().PublishToTopic(req.Context(), msg, topic)
if err != nil {
r.log.Error("publishing message", zap.Error(err))
return err
}
*reply = true
return nil
}
// PostV1Subscription is invoked when the json rpc request uses the post_waku_v2_relay_v1_subscription method
func (r *RelayService) PostV1Subscription(req *http.Request, args *TopicsArgs, reply *SuccessReply) error {
ctx := req.Context()
for _, topic := range args.Topics {
var err error
if topic == "" {
var sub *relay.Subscription
sub, err = r.node.Relay().Subscribe(ctx)
sub.Unsubscribe()
} else {
var sub *relay.Subscription
sub, err = r.node.Relay().SubscribeToTopic(ctx, topic)
if err != nil {
r.log.Error("subscribing to topic", zap.String("topic", topic), zap.Error(err))
return err
}
sub.Unsubscribe()
}
if err != nil {
r.log.Error("subscribing to topic", zap.String("topic", topic), zap.Error(err))
return err
}
r.messagesMutex.Lock()
r.messages[topic] = make([]*pb.WakuMessage, 0)
r.messagesMutex.Unlock()
}
*reply = true
return nil
}
// DeleteV1Subscription is invoked when the json rpc request uses the delete_waku_v2_relay_v1_subscription method
func (r *RelayService) DeleteV1Subscription(req *http.Request, args *TopicsArgs, reply *SuccessReply) error {
ctx := req.Context()
for _, topic := range args.Topics {
err := r.node.Relay().Unsubscribe(ctx, topic)
if err != nil {
r.log.Error("unsubscribing from topic", zap.String("topic", topic), zap.Error(err))
return err
}
delete(r.messages, topic)
}
*reply = true
return nil
}
// GetV1Messages is invoked when the json rpc request uses the get_waku_v2_relay_v1_messages method
func (r *RelayService) GetV1Messages(req *http.Request, args *TopicArgs, reply *MessagesReply) error {
r.messagesMutex.Lock()
defer r.messagesMutex.Unlock()
if _, ok := r.messages[args.Topic]; !ok {
return fmt.Errorf("topic %s not subscribed", args.Topic)
}
for i := range r.messages[args.Topic] {
*reply = append(*reply, ProtoToRPC(r.messages[args.Topic][i]))
}
r.messages[args.Topic] = make([]*pb.WakuMessage, 0)
return nil
}

View File

@ -0,0 +1,142 @@
package rpc
import (
"context"
"fmt"
"testing"
"time"
"github.com/multiformats/go-multiaddr"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/utils"
)
func makeRelayService(t *testing.T) *RelayService {
options := node.WithWakuRelayAndMinPeers(0)
n, err := node.New(options)
require.NoError(t, err)
err = n.Start(context.Background())
require.NoError(t, err)
return NewRelayService(n, 30, utils.Logger())
}
func TestPostV1Message(t *testing.T) {
var reply SuccessReply
d := makeRelayService(t)
msg := &pb.WakuMessage{
Payload: []byte{1, 2, 3},
ContentTopic: "abc",
Version: 0,
Timestamp: utils.GetUnixEpoch(),
}
err := d.PostV1Message(
makeRequest(t),
&RelayMessageArgs{
Message: ProtoToRPC(msg),
},
&reply,
)
require.NoError(t, err)
require.True(t, reply)
}
func TestRelaySubscription(t *testing.T) {
var reply SuccessReply
d := makeRelayService(t)
args := &TopicsArgs{Topics: []string{"test"}}
err := d.PostV1Subscription(
makeRequest(t),
args,
&reply,
)
require.NoError(t, err)
require.True(t, reply)
err = d.DeleteV1Subscription(
makeRequest(t),
args,
&reply,
)
require.NoError(t, err)
require.True(t, reply)
}
func TestRelayGetV1Messages(t *testing.T) {
serviceA := makeRelayService(t)
go serviceA.Start()
defer serviceA.Stop()
var reply SuccessReply
serviceB := makeRelayService(t)
go serviceB.Start()
defer serviceB.Stop()
hostInfo, err := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", serviceB.node.Host().ID().Pretty()))
require.NoError(t, err)
var addr multiaddr.Multiaddr
for _, a := range serviceB.node.Host().Addrs() {
addr = a.Encapsulate(hostInfo)
break
}
err = serviceA.node.DialPeerWithMultiAddress(context.Background(), addr)
require.NoError(t, err)
// Wait for the dial to complete
time.Sleep(1 * time.Second)
args := &TopicsArgs{Topics: []string{"test"}}
err = serviceB.PostV1Subscription(
makeRequest(t),
args,
&reply,
)
require.NoError(t, err)
require.True(t, reply)
// Wait for the subscription to be started
time.Sleep(1 * time.Second)
err = serviceA.PostV1Message(
makeRequest(t),
&RelayMessageArgs{
Topic: "test",
Message: ProtoToRPC(&pb.WakuMessage{
Payload: []byte("test"),
}),
},
&reply,
)
require.NoError(t, err)
require.True(t, reply)
// Wait for the message to be received
time.Sleep(1 * time.Second)
var messagesReply1 MessagesReply
err = serviceB.GetV1Messages(
makeRequest(t),
&TopicArgs{"test"},
&messagesReply1,
)
require.NoError(t, err)
require.Len(t, messagesReply1, 1)
var messagesReply2 MessagesReply
err = serviceB.GetV1Messages(
makeRequest(t),
&TopicArgs{"test"},
&messagesReply2,
)
require.NoError(t, err)
require.Len(t, messagesReply2, 0)
}

View File

@ -0,0 +1,41 @@
package rpc
import (
"encoding/base64"
"strings"
)
type SuccessReply = bool
type Empty struct {
}
type MessagesReply = []*RPCWakuMessage
type Base64URLByte []byte
// UnmarshalText is used by json.Unmarshal to decode both url-safe and standard
// base64 encoded strings with and without padding
func (h *Base64URLByte) UnmarshalText(b []byte) error {
inputValue := ""
if b != nil {
inputValue = string(b)
}
enc := base64.StdEncoding
if strings.ContainsAny(inputValue, "-_") {
enc = base64.URLEncoding
}
if len(inputValue)%4 != 0 {
enc = enc.WithPadding(base64.NoPadding)
}
decodedBytes, err := enc.DecodeString(inputValue)
if err != nil {
return err
}
*h = decodedBytes
return nil
}

View File

@ -0,0 +1,32 @@
package rpc
import (
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
)
type Adder func(msg *protocol.Envelope)
type runnerService struct {
broadcaster relay.Broadcaster
sub relay.Subscription
adder Adder
}
func newRunnerService(broadcaster relay.Broadcaster, adder Adder) *runnerService {
return &runnerService{
broadcaster: broadcaster,
adder: adder,
}
}
func (r *runnerService) Start() {
r.sub = r.broadcaster.RegisterForAll(1024)
for envelope := range r.sub.Ch {
r.adder(envelope)
}
}
func (r *runnerService) Stop() {
r.sub.Unsubscribe()
}

View File

@ -0,0 +1,75 @@
package rpc
import (
"net/http"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
"github.com/waku-org/go-waku/waku/v2/protocol/store/pb"
"go.uber.org/zap"
)
type StoreService struct {
node *node.WakuNode
log *zap.Logger
}
// cursor *pb.Index
// pageSize uint64
// asc bool
type StorePagingOptions struct {
PageSize uint64 `json:"pageSize,omitempty"`
Cursor *pb.Index `json:"cursor,omitempty"`
Forward bool `json:"forward,omitempty"`
}
type StoreMessagesArgs struct {
Topic string `json:"pubsubTopic,omitempty"`
ContentFilters []string `json:"contentFilters,omitempty"`
StartTime int64 `json:"startTime,omitempty"`
EndTime int64 `json:"endTime,omitempty"`
PagingOptions StorePagingOptions `json:"pagingOptions,omitempty"`
}
type StoreMessagesReply struct {
Messages []*RPCWakuMessage `json:"messages,omitempty"`
PagingInfo StorePagingOptions `json:"pagingInfo,omitempty"`
Error string `json:"error,omitempty"`
}
func (s *StoreService) GetV1Messages(req *http.Request, args *StoreMessagesArgs, reply *StoreMessagesReply) error {
options := []store.HistoryRequestOption{
store.WithAutomaticRequestID(),
store.WithAutomaticPeerSelection(),
store.WithPaging(args.PagingOptions.Forward, args.PagingOptions.PageSize),
store.WithCursor(args.PagingOptions.Cursor),
}
res, err := s.node.Store().Query(
req.Context(),
store.Query{
Topic: args.Topic,
ContentTopics: args.ContentFilters,
StartTime: args.StartTime,
EndTime: args.EndTime,
},
options...,
)
if err != nil {
s.log.Error("querying messages", zap.Error(err))
reply.Error = err.Error()
return nil
}
reply.Messages = make([]*RPCWakuMessage, len(res.Messages))
for i := range res.Messages {
reply.Messages[i] = ProtoToRPC(res.Messages[i])
}
reply.PagingInfo = StorePagingOptions{
PageSize: args.PagingOptions.PageSize,
Cursor: res.Cursor(),
Forward: args.PagingOptions.Forward,
}
return nil
}

View File

@ -0,0 +1,33 @@
package rpc
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/utils"
)
func makeStoreService(t *testing.T) *StoreService {
options := node.WithWakuStore()
n, err := node.New(options)
require.NoError(t, err)
err = n.Start(context.Background())
require.NoError(t, err)
return &StoreService{n, utils.Logger()}
}
func TestStoreGetV1Messages(t *testing.T) {
var reply StoreMessagesReply
s := makeStoreService(t)
err := s.GetV1Messages(
makeRequest(t),
&StoreMessagesArgs{},
&reply,
)
require.NoError(t, err)
require.NotEmpty(t, reply.Error)
}

View File

@ -0,0 +1,39 @@
package rpc
import (
"bytes"
"encoding/base64"
"encoding/json"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
)
func makeRequest(t *testing.T) *http.Request {
request, err := http.NewRequest(http.MethodPost, "url", bytes.NewReader([]byte("")))
require.NoError(t, err)
return request
}
func TestBase64Encoding(t *testing.T) {
input := "Hello World"
rpcMsg := ProtoToRPC(&pb.WakuMessage{
Payload: []byte(input),
})
jsonBytes, err := json.Marshal(rpcMsg)
require.NoError(t, err)
m := make(map[string]interface{})
err = json.Unmarshal(jsonBytes, &m)
require.NoError(t, err)
require.Equal(t, base64.StdEncoding.EncodeToString([]byte(input)), m["payload"])
decodedRPCMsg := new(RPCWakuMessage)
err = json.Unmarshal(jsonBytes, decodedRPCMsg)
require.NoError(t, err)
require.Equal(t, input, string(decodedRPCMsg.Payload))
}

View File

@ -0,0 +1,80 @@
package rpc
import (
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
)
type RateLimitProof struct {
Proof Base64URLByte `json:"proof,omitempty"`
MerkleRoot Base64URLByte `json:"merkle_root,omitempty"`
Epoch Base64URLByte `json:"epoch,omitempty"`
ShareX Base64URLByte `json:"share_x,omitempty"`
ShareY Base64URLByte `json:"share_y,omitempty"`
Nullifier Base64URLByte `json:"nullifier,omitempty"`
RlnIdentifier Base64URLByte `json:"rln_identifier,omitempty"`
}
type RPCWakuMessage struct {
Payload Base64URLByte `json:"payload,omitempty"`
ContentTopic string `json:"contentTopic,omitempty"`
Version uint32 `json:"version"`
Timestamp int64 `json:"timestamp,omitempty"`
RateLimitProof *RateLimitProof `json:"rateLimitProof,omitempty"`
Ephemeral bool `json:"ephemeral,omitempty"`
}
func ProtoToRPC(input *pb.WakuMessage) *RPCWakuMessage {
if input == nil {
return nil
}
rpcWakuMsg := &RPCWakuMessage{
Payload: input.Payload,
ContentTopic: input.ContentTopic,
Version: input.Version,
Timestamp: input.Timestamp,
Ephemeral: input.Ephemeral,
}
if input.RateLimitProof != nil {
rpcWakuMsg.RateLimitProof = &RateLimitProof{
Proof: input.RateLimitProof.Proof,
MerkleRoot: input.RateLimitProof.MerkleRoot,
Epoch: input.RateLimitProof.Epoch,
ShareX: input.RateLimitProof.ShareX,
ShareY: input.RateLimitProof.ShareY,
Nullifier: input.RateLimitProof.Nullifier,
RlnIdentifier: input.RateLimitProof.RlnIdentifier,
}
}
return rpcWakuMsg
}
func (r *RPCWakuMessage) toProto() *pb.WakuMessage {
if r == nil {
return nil
}
msg := &pb.WakuMessage{
Payload: r.Payload,
ContentTopic: r.ContentTopic,
Version: r.Version,
Timestamp: r.Timestamp,
Ephemeral: r.Ephemeral,
}
if r.RateLimitProof != nil {
msg.RateLimitProof = &pb.RateLimitProof{
Proof: r.RateLimitProof.Proof,
MerkleRoot: r.RateLimitProof.MerkleRoot,
Epoch: r.RateLimitProof.Epoch,
ShareX: r.RateLimitProof.ShareX,
ShareY: r.RateLimitProof.ShareY,
Nullifier: r.RateLimitProof.Nullifier,
RlnIdentifier: r.RateLimitProof.RlnIdentifier,
}
}
return msg
}

View File

@ -0,0 +1,121 @@
package rpc
import (
"context"
"fmt"
"net/http"
"net/http/pprof"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/rpc/v2"
"github.com/waku-org/go-waku/waku/v2/node"
"go.uber.org/zap"
)
type WakuRPC struct {
node *node.WakuNode
server *http.Server
log *zap.Logger
relayService *RelayService
filterService *FilterService
adminService *AdminService
}
func NewWakuRPC(node *node.WakuNode, address string, port int, enableAdmin bool, enablePProf bool, cacheCapacity int, log *zap.Logger) *WakuRPC {
wrpc := new(WakuRPC)
wrpc.log = log.Named("rpc")
s := rpc.NewServer()
s.RegisterCodec(NewSnakeCaseCodec(), "application/json")
s.RegisterCodec(NewSnakeCaseCodec(), "application/json;charset=UTF-8")
mux := mux.NewRouter()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
s.ServeHTTP(w, r)
wrpc.log.Info("served request", zap.String("path", r.URL.Path), zap.Duration("duration", time.Since(t)))
})
if enablePProf {
mux.PathPrefix("/debug/").Handler(http.DefaultServeMux)
mux.HandleFunc("/debug/pprof/", pprof.Index)
}
debugService := NewDebugService(node)
err := s.RegisterService(debugService, "Debug")
if err != nil {
wrpc.log.Error("registering debug service", zap.Error(err))
}
var relayService *RelayService
if node.Relay() != nil {
relayService = NewRelayService(node, cacheCapacity, log)
err = s.RegisterService(relayService, "Relay")
if err != nil {
wrpc.log.Error("registering relay service", zap.Error(err))
}
}
err = s.RegisterService(&StoreService{node, log}, "Store")
if err != nil {
wrpc.log.Error("registering store service", zap.Error(err))
}
if enableAdmin {
adminService := &AdminService{node, log.Named("admin")}
err = s.RegisterService(adminService, "Admin")
if err != nil {
wrpc.log.Error("registering admin service", zap.Error(err))
}
wrpc.adminService = adminService
}
filterService := NewFilterService(node, cacheCapacity, log)
err = s.RegisterService(filterService, "Filter")
if err != nil {
wrpc.log.Error("registering filter service", zap.Error(err))
}
listenAddr := fmt.Sprintf("%s:%d", address, port)
server := &http.Server{
Addr: listenAddr,
Handler: mux,
}
server.RegisterOnShutdown(func() {
filterService.Stop()
if relayService != nil {
relayService.Stop()
}
})
wrpc.node = node
wrpc.server = server
wrpc.relayService = relayService
wrpc.filterService = filterService
return wrpc
}
func (r *WakuRPC) Start() {
if r.relayService != nil {
go r.relayService.Start()
}
go r.filterService.Start()
go func() {
_ = r.server.ListenAndServe()
}()
r.log.Info("server started", zap.String("addr", r.server.Addr))
}
func (r *WakuRPC) Stop(ctx context.Context) error {
r.log.Info("shutting down server")
return r.server.Shutdown(ctx)
}

View File

@ -0,0 +1,19 @@
package rpc
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/utils"
)
func TestWakuRpc(t *testing.T) {
options := node.WithWakuStore()
n, err := node.New(options)
require.NoError(t, err)
rpc := NewWakuRPC(n, "127.0.0.1", 8080, true, false, 30, utils.Logger())
require.NotNil(t, rpc.server)
require.Equal(t, rpc.server.Addr, "127.0.0.1:8080")
}

View File

@ -1,50 +0,0 @@
package server
import (
"encoding/base64"
"strings"
"github.com/libp2p/go-libp2p/core/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/legacy_store"
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/protocol/store"
)
func IsWakuProtocol(protocol protocol.ID) bool {
return protocol == filter.FilterPushID_v20beta1 ||
protocol == filter.FilterSubscribeID_v20beta1 ||
protocol == relay.WakuRelayID_v200 ||
protocol == lightpush.LightPushID_v20beta1 ||
protocol == legacy_store.StoreID_v20beta4 ||
protocol == store.StoreQueryID_v300
}
type Base64URLByte []byte
// UnmarshalText is used by json.Unmarshal to decode both url-safe and standard
// base64 encoded strings with and without padding
func (h *Base64URLByte) UnmarshalText(b []byte) error {
inputValue := ""
if b != nil {
inputValue = string(b)
}
enc := base64.StdEncoding
if strings.ContainsAny(inputValue, "-_") {
enc = base64.URLEncoding
}
if len(inputValue)%4 != 0 {
enc = enc.WithPadding(base64.NoPadding)
}
decodedBytes, err := enc.DecodeString(inputValue)
if err != nil {
return err
}
*h = decodedBytes
return nil
}

View File

@ -1,33 +0,0 @@
{
pkgs ? import <nixpkgs> { },
self ? ./.,
subPkgs ? "cmd/waku",
ldflags ? [],
output ? null,
commit ? builtins.substring 0 7 (self.rev or "dirty"),
version ? builtins.readFile ./VERSION,
}:
pkgs.buildGo123Module {
name = "go-waku";
src = self;
subPackages = subPkgs;
tags = ["gowaku_no_rln"];
ldflags = [
"-X github.com/waku-org/go-waku/waku/v2/node.GitCommit=${commit}"
"-X github.com/waku-org/go-waku/waku/v2/node.Version=${version}"
] ++ ldflags;
doCheck = false;
# Otherwise library would be just called bin/c.
postInstall = if builtins.isString output then ''
mv $out/bin/* $out/bin/${output}
'' else "";
# FIXME: This needs to be manually changed when updating modules.
vendorHash = "sha256-uz9IVTEd+3UypZQc2CVWCFeLE4xEagn9YT9W2hr0K/o=";
# Fix for 'nix run' trying to execute 'go-waku'.
meta = { mainProgram = "waku"; };
}

View File

@ -1,30 +0,0 @@
# BUILD IMAGE --------------------------------------------------------
FROM golang:1.20 as builder
WORKDIR /app
COPY . .
# Build the final node binary
RUN make -j$(nproc) build
# ACTUAL IMAGE -------------------------------------------------------
FROM debian:12.1-slim
LABEL maintainer="romanzac@status.im"
LABEL source="https://github.com/waku-org/go-waku"
LABEL description="go-waku: Waku V2 node - test"
# color, nocolor, json
ENV GOLOG_LOG_FMT=nocolor
RUN apt update && apt install -y ca-certificates
# go-waku default ports
EXPOSE 9000 30303 60000 60001 8008 8009
COPY --from=builder /app/build/waku /usr/bin/waku
ENTRYPOINT ["/usr/bin/waku"]
# By default just show help if called without arguments
CMD ["--help"]

View File

@ -30,7 +30,7 @@ import (
)
func main() {
discoveryURL := "enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im"
discoveryURL := "enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@test.waku.nodes.status.im"
nodes, err := dnsdisc.RetrieveNodes(context.Background(), discoveryURL)
if err != nil {
panic(err)
@ -40,4 +40,4 @@ func main() {
}
```
`dnsdisc.RetrieveNodes` can also accept a `WithNameserver(nameserver string)` option which can be used to specify the nameserver to use to retrieve the TXT record from the domain name
`dnsdisc.RetrieveNodes` can also accept a `WithNameserver(nameserver string)` option which can be used to specify the nameserver to use to retrieve the TXT record from the domain name

View File

@ -38,7 +38,7 @@ One of these options must be specified when instantiating a node supporting the
```go
...
peerAddr, err := multiaddr.NewMultiaddr("/dns4/node-01.do-ams3.waku.test.status.im/tcp/30303/p2p/16Uiu2HAkykgaECHswi3YKJ5dMLbq2kPVCo89fcyTd38UcQD6ej5W")
peerAddr, err := multiaddr.NewMultiaddr("/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ")
if err != nil {
panic(err)
}

View File

@ -65,17 +65,22 @@ if err != nil {
```
To send a message, it needs to be wrapped into a [`WakuMessage`](https://rfc.vac.dev/spec/14/) protobuffer.
The payload of the message is not limited to strings. Any kind of data that can be serialized
To send a message, it needs to be wrapped into a [`WakuMessage`](https://rfc.vac.dev/spec/14/) protobuffer. The payload of the message is not limited to strings. Any kind of data that can be serialized
into a `[]byte` can be sent as long as it does not exceed the maximum length a message can have (~1MB)
`wakuNode.Lightpush().Publish(ctx, msg, opts...)` is used to publish a message. This function will return a message id on success, or an error if the message could not be published.
The following functions can be used to publish a message:
- `wakuNode.Lightpush().Publish(ctx, msg, opts...)` - to send a message to the default waku pubsub topic
- `wakuNode.Lightpush().PublishToTopic(ctx, msg, topic, opts...)` - to send a message to a custom pubsub topic
If no options are specified, go-waku will automatically choose the peer used to broadcast the message via Lightpush and publish the message to a pubsub topic derived from the content topic of the message. This behaviour can be controlled via options:
Both of these functions will return a message id on success, or an error if the message could not be published.
If no options are specified, go-waku will automatically choose the peer used to broadcast the message via Lightpush. This behaviour can be controlled via options:
### Options
- `lightpush.WithPubSubTopic(topic)` - broadcast the message using a custom pubsub topic
- `lightpush.WithDefaultPubsubTopic()` - broadcast the message to the default pubsub topic
- `lightpush.WithPeer(peerID)` - use an specific peer ID (which should be part of the node peerstore) to broadcast the message with
- `lightpush.WithAutomaticPeerSelection(host)` - automatically select a peer that supports lightpush protocol from the peerstore to broadcast the message with
- `lightpush.WithFastestPeerSelection(ctx)` - automatically select a peer based on its ping reply time

View File

@ -41,19 +41,34 @@ One of these options must be specified when instantiating a node supporting the
## Receiving messages
```go
...
contentFilter := protocol.NewContentFilter(relay.DefaultWakuTopic)
sub, err := wakuNode.Relay().Subscribe(context.Background, contentFilter) ([]*Subscription, error)
sub, err := wakuNode.Relay().Subscribe(context.Background())
if err != nil {
fmt.Println(err)
return
}
for value := range sub[0].C {
for value := range sub.C {
fmt.Println("Received msg:", string(value.Message().Payload))
}
...
```
To receive messages sent via the relay protocol, you need to subscribe specifying a content filter with the function `Subscribe(ctx context.Context, contentFilter waku_proto.ContentFilter, opts ...RelaySubscribeOption) ([]*Subscription, error)`. This functions return a list of `Subscription` struct containing a channel on which messages will be received. To stop receiving messages `WakuRelay`'s `Unsubscribe(ctx context.Context, contentFilter waku_proto.ContentFilter) error` can be executed which will close the channel (without unsubscribing from the pubsub topic) which will make sure the subscription is stopped, and if no other subscriptions exist for underlying pubsub topic, the pubsub is also unsubscribed.
To receive messages sent via the relay protocol, you need to subscribe to a pubsub topic. This can be done via any of these functions:
- `wakuNode.Relay().Subscribe(ctx)` - subscribes to the default waku pubsub topic `/waku/2/default-waku/proto`
- `wakuNode.Relay().SubscribeToTopic(ctx, topic)` - subscribes to a custom pubsub topic
These functions return a `Subscription` struct containing a channel on which messages will be received. To stop receiving messages in this channel `sub.Unsubscribe()` can be executed which will close the channel (without unsubscribing from the pubsub topic)
> Pubsub topics should follow the [recommended usage](https://rfc.vac.dev/spec/23/) structure. For this purpose, the `NewPubsubTopic` helper function was created:
```go
import "github.com/waku-org/go-waku/waku/v2/protocol"
topic := protocol.NewPubsubTopic("the_topic_name", "the_encoding")
/*
fmt.Println(topic.String()) // => `/waku/2/the_topic_name/the_encoding`
*/
```
## Sending messages
@ -80,13 +95,11 @@ if err != nil {
To send a message, it needs to be wrapped into a [`WakuMessage`](https://rfc.vac.dev/spec/14/) protobuffer. The payload of the message is not limited to strings. Any kind of data that can be serialized
into a `[]byte` can be sent as long as it does not exceed the maximum length a message can have (~1MB)
`wakuNode.Relay().Publish(ctx, msg, opts...)` is used to publish a message. This function will return a message id on success, or an error if the message could not be published.
The following functions can be used to publish a message:
- `wakuNode.Relay().Publish(ctx, msg)` - to send a message to the default waku pubsub topic
- `wakuNode.Relay().PublishToTopic(ctx, msg, topic)` - to send a message to a custom pubsub topic
If no options are specified, go-waku will automatically choose the peer used to broadcast the message via Relay and publish the message to a pubsub topic derived from the content topic of the message. This behaviour can be controlled via options:
### Options
- `relay.WithPubSubTopic(topic)` - broadcast the message using a custom pubsub topic
- `relay.WithDefaultPubsubTopic()` - broadcast the message to the default pubsub topic
Both of these functions will return a message id on success, or an error if the message could not be published.
> If `WithWakuRelayAndMinPeers` was used during the instantiation of the wakuNode, it should be possible to verify if there's enough peers for publishing to a topic with `wakuNode.Relay().EnoughPeersToPublish()` and `wakuNode.Relay().EnoughPeersToPublishToTopic(topic)`

View File

@ -28,14 +28,19 @@ if err != nil {
// Handle error ...
}
for !result.IsComplete() {
for _, msg := range result.GetMessages() {
// Do something with the messages
}
err := result.Next(ctx)
for {
hasNext, err := result.Next(ctx)
if err != nil {
// Handle error ...
break
}
if !hasNext { // No more messages available
break
}
for _, msg := range result.GetMessages() {
// Do something with the messages
}
}
```

View File

@ -24,7 +24,7 @@ A node will attempt connection to all discovered nodes.
This can be used, for example, to connect to one of the existing fleets.
Current URLs for the published fleet lists:
- production fleet: `enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im`
- test fleet: `enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im`
- production fleet: `enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@prod.waku.nodes.status.im`
- test fleet: `enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@test.waku.nodes.status.im`
See the [separate tutorial](../../tutorial/dns-disc.md) for a complete guide to DNS discovery.
See the [separate tutorial](../../tutorial/dns-disc.md) for a complete guide to DNS discovery.

View File

@ -17,12 +17,12 @@ or store and serve historical messages itself.
Ensure that `store` is enabled (this is `true` by default) and provide at least one store service node address with the `--storenode` CLI option.
See the following example, using the peer at `/dns4/node-01.ac-cn-hongkong-c.waku.test.status.im/tcp/30303/p2p/16Uiu2HAkzHaTP5JsUwfR9NR8Rj9HC24puS6ocaU8wze4QrXr9iXp` as store service node.
See the following example, using the peer at `/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm` as store service node.
```sh
./build/waku \
--store=true \
--storenode=/dns4/node-01.ac-cn-hongkong-c.waku.test.status.im/tcp/30303/p2p/16Uiu2HAkzHaTP5JsUwfR9NR8Rj9HC24puS6ocaU8wze4QrXr9iXp
--storenode=/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm
```
Your node can now send queries to retrieve historical messages

View File

@ -48,7 +48,7 @@ export SEPOLIA_WS_NODE_ADDRESS=<WS RPC URL to a Sepolia Node>
export RLN_RELAY_CONTRACT_ADDRESS="0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4" # Replace this with any compatible implementation
docker run -i -t -p 60000:60000 -p 9000:9000/udp \
-v /absolute/path/to/your/rlnKeystore.json:/rlnKeystore.json:ro \
wakuorg/go-waku:latest \
statusteam/go-waku:latest \
--dns-discovery=true \
--dns-discovery-url="$WAKU_FLEET" \
--discv5-discovery \

View File

@ -130,7 +130,7 @@ We include an example below.
```
### Connecting to the `waku.sandbox` network
### Connecting to the `wakuv2.prod` network
You can use DNS discovery to bootstrap connection to the existing production network.
Discovery v5 will attempt to extract the ENRs of the discovered nodes as bootstrap entries to the routing table.
@ -138,11 +138,11 @@ Discovery v5 will attempt to extract the ENRs of the discovered nodes as bootstr
```sh
./build/waku \
--dns-discovery=true \
--dns-discovery-url=enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im \
--dns-discovery-url=enrtree://ANTL4SLG2COUILKAPE7EF2BYNL2SHSHVCHLRD5J7ZJLN5R3PRJD2Y@prod.waku.nodes.status.im \
--discv5-discovery=true
```
### Connecting to the `waku.test` network
### Connecting to the `wakuv2.test` network
You can use DNS discovery to bootstrap connection to the existing test network.
Discovery v5 will attempt to extract the ENRs of the discovered nodes as bootstrap entries to the routing table.
@ -150,7 +150,7 @@ Discovery v5 will attempt to extract the ENRs of the discovered nodes as bootstr
```sh
./build/waku \
--dns-discovery=true \
--dns-discovery-url=enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im \
--dns-discovery-url=enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@test.waku.nodes.status.im \
--discv5-discovery=true
```
@ -159,7 +159,7 @@ Discovery v5 will attempt to extract the ENRs of the discovered nodes as bootstr
Often go-waku nodes choose to also store historical messages
from where it can be queried by other peers who may have been temporarily offline.
For example, a typical configuration for such a store service node,
[connecting to the `waku.test`](#connecting-to-the-wakutest-network) fleet on startup,
[connecting to the `wakuv2.test`](#connecting-to-the-wakuv2test-fleet) fleet on startup,
appears below.
```sh
@ -169,7 +169,7 @@ appears below.
--db-path=/mnt/go-waku/data/db1/ \
--store-capacity=150000 \
--dns-discovery=true \
--dns-discovery-url=enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im \
--dns-discovery-url=enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@test.waku.nodes.status.im \
--discv5-discovery=true
```
@ -181,4 +181,4 @@ A running go-waku node can be interacted with using the [Waku v2 JSON RPC API](h
> **Note:** Private and Admin API functionality are disabled by default.
To configure a go-waku node with these enabled,
use the `--rpc-admin:true` and `--rpc-private:true` CLI options.
use the `--rpc-admin:true` and `--rpc-private:true` CLI options.

View File

@ -8,7 +8,7 @@ For a more advanced configuration see our [configuration guides](./how-to/config
[Build the go-waku node](./how-to/build.md)
or download a precompiled binary from our [releases page](https://github.com/waku-org/go-waku/releases).
<!-- Docker images are published to [wakuorg/go-waku](https://hub.docker.com/r/wakuorg/go-waku/tags) on DockerHub. -->
<!-- Docker images are published to [statusteam/go-waku](https://hub.docker.com/r/statusteam/go-waku/tags) on DockerHub. -->
<!-- TODO: more advanced explanation on finding and using docker images -->
## 2. Run

View File

@ -102,10 +102,10 @@ INFO: RLN config:
- Your RLN identity commitment key is: 6c6598126ba10d1b70100893b76d7f8d7343eeb8f5ecfd48371b421c5aa6f012
INFO: attempting DNS discovery with enrtree://ANTL4SLG2COUILKAPE7EF2BYNL2SHSHVCHLRD5J7ZJLN5R3PRJD2Y@prod.waku.nodes.status.im
INFO: Discovered and connecting to [/dns4/node-01.gc-us-central1-a.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAm6fyqE1jB5MonzvoMdU8v76bWV8ZeNpncDamY1MQXfjdB /dns4/node-01.ac-cn-hongkong-c.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmSJvSJphxRdbnigUV5bjRRZFBhTtWFTSyiKaQByCjwmpV /dns4/node-01.do-ams3.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmQSMNExfUYUqfuXWkD5DaNZnMYnigRxFKbk3tcEFQeQeE]
INFO: Connected to 16Uiu2HAm6fyqE1jB5MonzvoMdU8v76bWV8ZeNpncDamY1MQXfjdB
INFO: Connected to 16Uiu2HAmQSMNExfUYUqfuXWkD5DaNZnMYnigRxFKbk3tcEFQeQeE
INFO: Connected to 16Uiu2HAmSJvSJphxRdbnigUV5bjRRZFBhTtWFTSyiKaQByCjwmpV
INFO: Discovered and connecting to [/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA /dns4/node-01.ac-cn-hongkong- c.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD /dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e]
INFO: Connected to 16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA
INFO: Connected to 16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e
INFO: Connected to 16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD
INFO: No store node configured. Choosing one at random...
```
You will also see some historical messages being fetched, again the content may be different on your end:
@ -214,10 +214,10 @@ INFO: RLN config:
- Your rln identity commitment key is: bd093cbf14fb933d53f596c33f98b3df83b7e9f7a1906cf4355fac712077cb28
INFO: attempting DNS discovery with enrtree://ANTL4SLG2COUILKAPE7EF2BYNL2SHSHVCHLRD5J7ZJLN5R3PRJD2Y@prod.waku.nodes.status.im
INFO: Discovered and connecting to [/dns4/node-01.gc-us-central1-a.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAm6fyqE1jB5MonzvoMdU8v76bWV8ZeNpncDamY1MQXfjdB /dns4/node-01.ac-cn-hongkong-c.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmSJvSJphxRdbnigUV5bjRRZFBhTtWFTSyiKaQByCjwmpV /dns4/node-01.do-ams3.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmQSMNExfUYUqfuXWkD5DaNZnMYnigRxFKbk3tcEFQeQeE]
INFO: Connected to 16Uiu2HAm6fyqE1jB5MonzvoMdU8v76bWV8ZeNpncDamY1MQXfjdB
INFO: Connected to 16Uiu2HAmQSMNExfUYUqfuXWkD5DaNZnMYnigRxFKbk3tcEFQeQeE
INFO: Connected to 16Uiu2HAmSJvSJphxRdbnigUV5bjRRZFBhTtWFTSyiKaQByCjwmpV
INFO: Discovered and connecting to [/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA /dns4/node-01.ac-cn-hongkong- c.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD /dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e]
INFO: Connected to 16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA
INFO: Connected to 16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e
INFO: Connected to 16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD
INFO: No store node configured. Choosing one at random...
>> message1
@ -264,13 +264,13 @@ INFO: RLN config:
- Your rln identity commitment key is: bd093cbf14fb933d53f596c33f98b3df83b7e9f7a1906cf4355fac712077cb28
INFO: attempting DNS discovery with enrtree://ANTL4SLG2COUILKAPE7EF2BYNL2SHSHVCHLRD5J7ZJLN5R3PRJD2Y@prod.waku.nodes.status.im
INFO: Discovered and connecting to [/dns4/node-01.gc-us-central1-a.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAm6fyqE1jB5MonzvoMdU8v76bWV8ZeNpncDamY1MQXfjdB /dns4/node-01.ac-cn-hongkong-c.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmSJvSJphxRdbnigUV5bjRRZFBhTtWFTSyiKaQByCjwmpV /dns4/node-01.do-ams3.waku.sandbox.status.im/tcp/8000/wss/p2p/16Uiu2HAmQSMNExfUYUqfuXWkD5DaNZnMYnigRxFKbk3tcEFQeQeE]
INFO: Connected to 16Uiu2HAm6fyqE1jB5MonzvoMdU8v76bWV8ZeNpncDamY1MQXfjdB
INFO: Connected to 16Uiu2HAmQSMNExfUYUqfuXWkD5DaNZnMYnigRxFKbk3tcEFQeQeE
INFO: Connected to 16Uiu2HAmSJvSJphxRdbnigUV5bjRRZFBhTtWFTSyiKaQByCjwmpV
INFO: Discovered and connecting to [/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA /dns4/node-01.ac-cn-hongkong- c.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD /dns4/node-01.do-ams3.wakuv2.prod.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e]
INFO: Connected to 16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA
INFO: Connected to 16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e
INFO: Connected to 16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD
INFO: No store node configured. Choosing one at random...
[Jul 26 13:05 Alice] message1
[Jul 26 13:05 Alice] message2
[Jul 26 13:05 Alice] message4
```
```

View File

@ -59,7 +59,7 @@ class MainActivity : AppCompatActivity() {
lbl.text = (lbl.text.toString() + ">>> Default pubsub topic: " + defaultPubsubTopic() + "\n");
try {
node.connect("/dns4/node-01.gc-us-central1-a.waku.test.status.im/tcp/30303/p2p/16Uiu2HAmDCp8XJ9z1ev18zuv8NHekAsjNyezAvmMfFEJkiharitG")
node.connect("/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS")
lbl.text = (lbl.text.toString() + ">>> Connected to Peer" + "\n")
node.peers().forEach {

View File

@ -22,3 +22,12 @@ fun ContentTopic(applicationName: String, applicationVersion: Long, contentTopic
return Gowaku.contentTopic(applicationName, applicationVersion, contentTopicName, encoding)
}
/**
* Create a pubsub topic string
* @param name
* @param encoding
* @return Pubsub topic string according to RFC 23
*/
fun PubsubTopic(name: String, encoding: String): String {
return Gowaku.pubsubTopic(name, encoding)
}

View File

@ -1,6 +0,0 @@
.PHONY: all build
build:
go build -o build/basic_light_client .
all: build

View File

@ -1,35 +0,0 @@
# Using the `basic_light_client` application
## Background
The `basic_light_client` application is a basic example app that demonstrates how to send/receive messages using Waku Filter and Lightpush.
There are 2 ways of running the example.:
1. To work with the public Waku network in which case it uses the autosharding feature.This is the default way to run this.
2. To work with a custom Waku network which using static sharding. In this case a clusterID has to be specified.
## Preparation
```
make
```
## Basic application usage
To start the `basic_light_client` application run the following from the project directory
```
./build/basic_light_client --maddr=<filterNode>
```
The app will send a "Hello world!" through the lightpush protocol and display it on the terminal as soon as it receives the message.
In order to run it with you own static sharded network, then run it as below
```
./build/basic_light_client --maddr=<filterNode> --cluster-id=<value of cluster-id> --shard=<shard number>
```
e.g: ./build/basic_light_client --maddr="/ip4/0.0.0.0/tcp/30304/p2p/16Uiu2HAmBu5zRFzBGAzzMAuGWhaxN2EwcbW7CzibELQELzisf192" --cluster-id=2 --shard=1 // If you want to run with clusterID 2 and shard as 1
Cluster-id is a unique identifier for your own network and shard number is a segment/shard identifier of your network.
Note that clusterID's 1 & 16 are reserved for the public Waku Network and Status repectively.

View File

@ -1,168 +0,0 @@
module basic2
go 1.23.0
toolchain go1.23.11
replace github.com/waku-org/go-waku => ../..
replace github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.15
replace github.com/libp2p/go-libp2p-pubsub v0.13.1 => github.com/waku-org/go-libp2p-pubsub v0.13.1-gowaku
require (
github.com/ethereum/go-ethereum v1.16.3
github.com/libp2p/go-libp2p v0.39.1
github.com/multiformats/go-multiaddr v0.14.0
github.com/urfave/cli/v2 v2.27.5
github.com/waku-org/go-waku v0.2.3-0.20221109195301-b2a5a68d28ba
go.uber.org/zap v1.27.0
google.golang.org/protobuf v1.36.4
)
require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/avast/retry-go/v4 v4.5.1 // indirect
github.com/beevik/ntp v0.3.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/consensys/gnark-crypto v0.18.0 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
github.com/cruxic/go-hmac-drbg v0.0.0-20170206035330-84c46983886d // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elastic/gosigar v0.14.3 // indirect
github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
github.com/ethereum/go-verkle v0.2.2 // indirect
github.com/flynn/noise v1.1.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/huin/goupnp v1.3.0 // indirect
github.com/ipfs/go-cid v0.5.0 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
github.com/jellydator/ttlcache/v3 v3.3.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/koron/go-ssdp v0.0.5 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-flow-metrics v0.2.0 // indirect
github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
github.com/libp2p/go-libp2p-pubsub v0.13.1 // indirect
github.com/libp2p/go-msgio v0.3.0 // indirect
github.com/libp2p/go-nat v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.2 // indirect
github.com/libp2p/go-reuseport v0.4.0 // indirect
github.com/libp2p/go-yamux/v4 v4.0.2 // indirect
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect
github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-multistream v0.6.0 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v2 v2.2.12 // indirect
github.com/pion/dtls/v3 v3.0.4 // indirect
github.com/pion/ice/v2 v2.3.37 // indirect
github.com/pion/ice/v4 v4.0.6 // indirect
github.com/pion/interceptor v0.1.37 // indirect
github.com/pion/logging v0.2.3 // indirect
github.com/pion/mdns v0.0.12 // indirect
github.com/pion/mdns/v2 v2.0.7 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.11 // indirect
github.com/pion/sctp v1.8.35 // indirect
github.com/pion/sdp/v3 v3.0.10 // indirect
github.com/pion/srtp/v3 v3.0.4 // indirect
github.com/pion/stun v0.6.1 // indirect
github.com/pion/stun/v2 v2.0.0 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v2 v2.2.10 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v2 v2.1.6 // indirect
github.com/pion/turn/v4 v4.0.0 // indirect
github.com/pion/webrtc/v4 v4.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.49.0 // indirect
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect
github.com/raulk/go-watchdog v1.3.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/supranational/blst v0.3.14 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/waku-org/go-discover v0.0.0-20251003191045-8ee308fe7971 // indirect
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20240110193335-a67d1cc760a0 // indirect
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59 // indirect
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b // indirect
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 // indirect
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 // indirect
github.com/wk8/go-ordered-map v1.0.0 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.uber.org/dig v1.18.0 // indirect
go.uber.org/fx v1.23.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.36.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)

View File

@ -1,781 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw=
github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0=
github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI=
github.com/crate-crypto/go-eth-kzg v1.3.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/cruxic/go-hmac-drbg v0.0.0-20170206035330-84c46983886d h1:bE1UyBQ5aE6FjhNY4lbPtMqh7VDldoVkvZMtFEbd+CE=
github.com/cruxic/go-hmac-drbg v0.0.0-20170206035330-84c46983886d/go.mod h1:HAe1wsCrwH2uFnFaCC2vlcyEohnxs8KeShAFqGIHvmM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU=
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U=
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo=
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w=
github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E=
github.com/ethereum/go-ethereum v1.16.3 h1:nDoBSrmsrPbrDIVLTkDQCy1U9KdHN+F2PzvMbDoS42Q=
github.com/ethereum/go-ethereum v1.16.3/go.mod h1:Lrsc6bt9Gm9RyvhfFK53vboCia8kpF9nv+2Ukntnl+8=
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/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg=
github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-migrate/migrate/v4 v4.15.2 h1:vU+M05vs6jWHKDdmE1Ecwj0BznygFc4QsdRe2E/L7kc=
github.com/golang-migrate/migrate/v4 v4.15.2/go.mod h1:f2toGLkYqD3JH+Todi4aZ2ZdbeUNx4sIwiOK96rE9Lw=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg=
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4=
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k=
github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8=
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs=
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk=
github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/koron/go-ssdp v0.0.5 h1:E1iSMxIs4WqxTbIBLtmNBeOOC+1sCIXQeqTWVnpmwhk=
github.com/koron/go-ssdp v0.0.5/go.mod h1:Qm59B7hpKpDqfyRNWRNr00jGwLdXjDyZh6y7rH6VS0w=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
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/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw=
github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc=
github.com/libp2p/go-libp2p v0.39.1 h1:1Ur6rPCf3GR+g8jkrnaQaM0ha2IGespsnNlCqJLLALE=
github.com/libp2p/go-libp2p v0.39.1/go.mod h1:3zicI8Lp7Isun+Afo/JOACUbbJqqR2owK6RQWFsVAbI=
github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94=
github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8=
github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA=
github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg=
github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0=
github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM=
github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=
github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=
github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8=
github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE=
github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s=
github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU=
github.com/libp2p/go-yamux/v4 v4.0.2 h1:nrLh89LN/LEiqcFiqdKDRHjGstN300C1269K/EX0CPU=
github.com/libp2p/go-yamux/v4 v4.0.2/go.mod h1:C808cCRgOs1iBwY4S71T5oxgMxgLmqUw56qh4AeBW2o=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8=
github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc=
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc=
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo=
github.com/multiformats/go-multiaddr v0.14.0 h1:bfrHrJhrRuh/NXH5mCnemjpbGjzRw/b+tJFOD41g2tU=
github.com/multiformats/go-multiaddr v0.14.0/go.mod h1:6EkVAxtznq2yC3QT5CM1UTAwG0GTP3EWAIcjHuzQ+r4=
github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M=
github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc=
github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E=
github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-multistream v0.6.0 h1:ZaHKbsL404720283o4c/IHQXiS6gb8qAN5EIJ4PN5EA=
github.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U=
github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg=
github.com/pion/ice/v2 v2.3.37 h1:ObIdaNDu1rCo7hObhs34YSBcO7fjslJMZV0ux+uZWh0=
github.com/pion/ice/v2 v2.3.37/go.mod h1:mBF7lnigdqgtB+YHkaY/Y6s6tsyRyo4u4rPGRuOjUBQ=
github.com/pion/ice/v4 v4.0.6 h1:jmM9HwI9lfetQV/39uD0nY4y++XZNPhvzIPCb8EwxUM=
github.com/pion/ice/v4 v4.0.6/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI=
github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.11 h1:17xjnY5WO5hgO6SD3/NTIUPvSFw/PbLsIJyz1r1yNIk=
github.com/pion/rtp v1.8.11/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4=
github.com/pion/sctp v1.8.35 h1:qwtKvNK1Wc5tHMIYgTDJhfZk7vATGVHhXbUDfHbYwzA=
github.com/pion/sctp v1.8.35/go.mod h1:EcXP8zCYVTRy3W9xtOF7wJm1L1aXfKRQzaM33SjQlzg=
github.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA=
github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M=
github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ=
github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4=
github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8=
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v2 v2.1.3/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc=
github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY=
github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM=
github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA=
github.com/pion/webrtc/v4 v4.0.8 h1:T1ZmnT9qxIJIt4d8XoiMOBrTClGHDDXNg9e/fh018Qc=
github.com/pion/webrtc/v4 v4.0.8/go.mod h1:HHBeUVBAC+j4ZFnYhovEFStF02Arb1EyD4G7e7HBTJw=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=
github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk=
github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo=
github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/waku-org/go-discover v0.0.0-20251003191045-8ee308fe7971 h1:BNw9UkCVftI0l0QWm5ofpM5HaLfRuv77Fcea4byk6wk=
github.com/waku-org/go-discover v0.0.0-20251003191045-8ee308fe7971/go.mod h1:oVHZeHiB/OJpc0k4r+Iq+5bJXvOV3Mpz1gPQh8Vswps=
github.com/waku-org/go-libp2p-pubsub v0.13.1-gowaku h1:ovXh1m76i0yPWXt95Z9ZRyEk0+WKSuPt5e4IddgwUrY=
github.com/waku-org/go-libp2p-pubsub v0.13.1-gowaku/go.mod h1:MKPU5vMI8RRFyTP0HfdsF9cLmL1nHAeJm44AxJGJx44=
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20240110193335-a67d1cc760a0 h1:R4YYx2QamhBRl/moIxkDCNW+OP7AHbyWLBygDc/xIMo=
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20240110193335-a67d1cc760a0/go.mod h1:EhZP9fee0DYjKH/IOQvoNSy1tSHp2iZadsHGphcAJgY=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59 h1:jisj+OCI6QydLtFq3Pyhu49wl9ytPN7oAHjMfepHDrA=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59/go.mod h1:1PdBdPzyTaKt3VnpAHk3zj+r9dXPFOr3IHZP9nFle6E=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b h1:KgZVhsLkxsj5gb/FfndSCQu6VYwALrCOgYI3poR95yE=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 h1:Sd7QD/1Yo2o2M1MY49F8Zr4KNBPUEK5cz5HoXQVJbrs=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065/go.mod h1:7cSGUoGVIla1IpnChrLbkVjkYgdOcr7rcifEfh4ReR4=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 h1:4HSdWMFMufpRo3ECTX6BrvA+VzKhXZf7mS0rTa5cCWU=
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1/go.mod h1:+LeEYoW5/uBUTVjtBGLEVCUe9mOYAlu5ZPkIxLOSr5Y=
github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8=
github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk=
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
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/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@ -1,271 +0,0 @@
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"net"
"os"
"os/signal"
"syscall"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
cli "github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"github.com/waku-org/go-waku/waku/v2/node"
"github.com/waku-org/go-waku/waku/v2/payload"
wps "github.com/waku-org/go-waku/waku/v2/peerstore"
"github.com/waku-org/go-waku/waku/v2/protocol"
"github.com/waku-org/go-waku/waku/v2/protocol/filter"
"github.com/waku-org/go-waku/waku/v2/protocol/lightpush"
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
"github.com/waku-org/go-waku/waku/v2/utils"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
)
var log = utils.Logger().Named("basic-light-client")
var ClusterID = altsrc.NewUintFlag(&cli.UintFlag{
Name: "cluster-id",
Value: 1,
Usage: "Cluster id that the client is interested in connecting to.",
Destination: &clusterID,
})
var Shard = altsrc.NewUintFlag(&cli.UintFlag{
Name: "shard",
Value: 0,
Usage: "shard that the node is interested in publishing/receiving from.",
Destination: &shard,
})
var StaticNode = altsrc.NewStringFlag(&cli.StringFlag{
Name: "maddr",
Usage: "multiaddress of static node to connect to.",
Destination: &multiaddress,
Required: true,
})
var clusterID, shard uint
var pubsubTopicStr string
var multiaddress string
func main() {
cliFlags := []cli.Flag{
ClusterID,
Shard,
StaticNode,
}
app := &cli.App{
Name: "basic-light-client-example",
Flags: cliFlags,
Action: func(c *cli.Context) error {
err := Execute()
if err != nil {
utils.Logger().Error("failure while executing wakunode", zap.Error(err))
switch e := err.(type) {
case cli.ExitCoder:
return e
case error:
return cli.Exit(err.Error(), 1)
}
}
return nil
},
}
err := app.Run(os.Args)
if err != nil {
panic(err)
}
}
func Execute() error {
var cTopic, err = protocol.NewContentTopic("basic-light-client", "1", "test", "proto")
if err != nil {
return errors.New("invalid contentTopic")
}
contentTopic := cTopic.String()
hostAddr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:0")
key, err := randomHex(32)
if err != nil {
log.Error("Could not generate random key", zap.Error(err))
return err
}
prvKey, err := crypto.HexToECDSA(key)
if err != nil {
log.Error("Could not convert hex into ecdsa key", zap.Error(err))
return err
}
ctx := context.Background()
lightNode, err := node.New(
node.WithPrivateKey(prvKey),
node.WithHostAddress(hostAddr),
node.WithWakuFilterLightNode(),
node.WithClusterID(uint16(clusterID)),
//node.WithLogLevel(zapcore.DebugLevel),
)
if err != nil {
log.Error("Error creating wakunode", zap.Error(err))
return err
}
err = lightNode.Start(ctx)
if err != nil {
log.Error("Error starting wakunode", zap.Error(err))
return err
}
//Populate pubsubTopic if shard is specified. Otherwise it is derived via autosharing algorithm
if shard != 0 {
pubsubTopic := protocol.NewStaticShardingPubsubTopic(uint16(clusterID), uint16(shard))
pubsubTopicStr = pubsubTopic.String()
}
maddr, err := multiaddr.NewMultiaddr(multiaddress)
if err != nil {
log.Info("Error decoding multiaddr ", zap.Error(err))
}
peerID, err := lightNode.AddPeer([]multiaddr.Multiaddr{maddr}, wps.Static,
[]string{pubsubTopicStr}, filter.FilterSubscribeID_v20beta1, lightpush.LightPushID_v20beta1)
if err != nil {
log.Info("Error adding filter peer on light node ", zap.Error(err))
}
useFilterAndLightPush(lightNode, contentTopic, pubsubTopicStr, peerID)
// shut the node down
lightNode.Stop()
return nil
}
func useFilterAndLightPush(lightNode *node.WakuNode, contentTopic string, pubsubTopic string, filterNode peer.ID) {
// Send FilterRequest from light node to full node
cf := protocol.ContentFilter{
PubsubTopic: pubsubTopic,
ContentTopics: protocol.NewContentTopicSet(contentTopic),
}
time.Sleep(2 * time.Second)
log.Info("Subscribing to peer ", zap.String("peerId", filterNode.String()))
theFilter, err := lightNode.FilterLightnode().Subscribe(context.Background(), cf, filter.WithPeer(filterNode))
if err != nil {
panic(err)
}
go func() {
for env := range theFilter[0].C { //Safely picking first subscriptions since only 1 contentTopic is subscribed
log.Info("Light node received msg ", zap.String("message", string(env.Message().Payload)))
}
log.Info("Message channel closed!")
}()
time.Sleep(2 * time.Second)
msg := &pb.WakuMessage{ContentTopic: contentTopic, Version: proto.Uint32(1), Timestamp: proto.Int64(time.Now().Unix()), Payload: []byte("Hello World!")}
hash, err := lightNode.Lightpush().Publish(context.Background(), msg, lightpush.WithPeer(filterNode), lightpush.WithPubSubTopic(pubsubTopic))
if err != nil {
panic(err)
}
log.Info("published msg via lightpush with hash:", zap.Stringer("hash", hash))
log.Info("Done sending msgs.......")
log.Info("Press Ctrl+C to exit safely")
// Wait for a SIGINT or SIGTERM signal
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
fmt.Println("\n\n\nReceived signal, shutting down...")
log.Info("UnSubscribing to peer ", zap.String("filterNode", filterNode.String()))
result, err := lightNode.FilterLightnode().Unsubscribe(context.Background(), cf, filter.WithPeer(filterNode))
if err != nil {
log.Error("failed to unsubscribe due to ", zap.Error(err))
panic(err)
}
for _, err := range result.Errors() {
if err.Err != nil {
log.Error("failed to unsubscribe due to res err", zap.Error(err.Err))
panic(err)
}
}
}
func randomHex(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
func write(ctx context.Context, wakuNode *node.WakuNode, contentTopic string, msgContent string) {
var version uint32 = 0
p := new(payload.Payload)
p.Data = []byte(wakuNode.ID() + ": " + msgContent)
p.Key = &payload.KeyInfo{Kind: payload.None}
payload, err := p.Encode(version)
if err != nil {
log.Error("Error encoding the payload", zap.Error(err))
return
}
msg := &pb.WakuMessage{
Payload: payload,
Version: proto.Uint32(version),
ContentTopic: contentTopic,
Timestamp: utils.GetUnixEpoch(wakuNode.Timesource()),
}
_, err = wakuNode.Relay().Publish(ctx, msg, relay.WithPubSubTopic(pubsubTopicStr))
if err != nil {
log.Error("Error sending a message", zap.Error(err))
}
log.Info("Published msg,", zap.String("data", string(msg.Payload)))
}
func writeLoop(ctx context.Context, wakuNode *node.WakuNode, contentTopic string) {
for {
time.Sleep(2 * time.Second)
write(ctx, wakuNode, contentTopic, "Hello world!")
}
}
func readLoop(ctx context.Context, wakuNode *node.WakuNode, contentTopic string) {
sub, err := wakuNode.Relay().Subscribe(ctx, protocol.NewContentFilter(pubsubTopicStr, contentTopic))
if err != nil {
log.Error("Could not subscribe", zap.Error(err))
return
}
for envelope := range sub[0].Ch {
if envelope.Message().ContentTopic != contentTopic {
continue
}
payload, err := payload.DecodePayload(envelope.Message(), &payload.KeyInfo{Kind: payload.None})
if err != nil {
log.Error("Error decoding payload", zap.Error(err))
continue
}
log.Info("Received msg, ", zap.String("data", string(payload.Data)))
}
}

Some files were not shown because too many files have changed in this diff Show More