mirror of https://github.com/waku-org/nwaku.git
Merge pull request #2 from status-im/setup-repo
Setup repo + move code from nimbus / nim-eth
This commit is contained in:
commit
46fb777ccb
|
@ -0,0 +1,36 @@
|
||||||
|
version: '{build}'
|
||||||
|
|
||||||
|
image: Visual Studio 2015
|
||||||
|
|
||||||
|
init: # Scripts called at the very beginning
|
||||||
|
# Enable paths > 260 characters
|
||||||
|
- ps: Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
|
||||||
|
- git config --global core.longpaths true
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- NimBinaries
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
# We always want 32 and 64-bit compilation
|
||||||
|
fast_finish: false
|
||||||
|
|
||||||
|
platform:
|
||||||
|
- x64
|
||||||
|
- x86
|
||||||
|
|
||||||
|
install:
|
||||||
|
# use the newest versions documented here: https://www.appveyor.com/docs/windows-images-software/#mingw-msys-cygwin
|
||||||
|
- IF "%PLATFORM%" == "x86" SET PATH=C:\mingw-w64\i686-6.3.0-posix-dwarf-rt_v5-rev1\mingw32\bin;%PATH%
|
||||||
|
- IF "%PLATFORM%" == "x64" SET PATH=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%PATH%
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
# the 32-bit build is done on a 64-bit image, so we need to override the architecture
|
||||||
|
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% CI_CACHE=NimBinaries update
|
||||||
|
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% fetch-dlls
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% LOG_LEVEL=TRACE
|
||||||
|
- build\wakunode.exe --help
|
||||||
|
- mingw32-make -j2 ARCH_OVERRIDE=%PLATFORM% test
|
||||||
|
|
||||||
|
deploy: off
|
|
@ -1 +1,21 @@
|
||||||
build
|
/nimcache
|
||||||
|
|
||||||
|
# Executables shall be put in an ignored build/ directory
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Nimble packages
|
||||||
|
/vendor/.nimble
|
||||||
|
|
||||||
|
# ntags/ctags output
|
||||||
|
/tags
|
||||||
|
|
||||||
|
# a symlink that can't be added to the repo because of Windows
|
||||||
|
/waku.nims
|
||||||
|
|
||||||
|
# Ignore dynamic, static libs and libtool archive files
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
|
|
@ -1,15 +1,100 @@
|
||||||
[submodule "vendor/nimbus"]
|
|
||||||
path = vendor/nimbus
|
|
||||||
url = git@github.com:status-im/nimbus.git
|
|
||||||
[submodule "vendor/nim-eth"]
|
[submodule "vendor/nim-eth"]
|
||||||
path = vendor/nim-eth
|
path = vendor/nim-eth
|
||||||
url = git@github.com:status-im/nim-eth.git
|
url = https://github.com/status-im/nim-eth.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
[submodule "vendor/nim-secp256k1"]
|
[submodule "vendor/nim-secp256k1"]
|
||||||
path = vendor/nim-secp256k1
|
path = vendor/nim-secp256k1
|
||||||
url = git@github.com:status-im/nim-secp256k1.git
|
url = https://github.com/status-im/nim-secp256k1.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
[submodule "vendor/nim-libp2p"]
|
[submodule "vendor/nim-libp2p"]
|
||||||
path = vendor/nim-libp2p
|
path = vendor/nim-libp2p
|
||||||
url = git@github.com:status-im/nim-libp2p.git
|
url = https://github.com/status-im/nim-libp2p.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
[submodule "vendor/nim-stew"]
|
[submodule "vendor/nim-stew"]
|
||||||
path = vendor/nim-stew
|
path = vendor/nim-stew
|
||||||
url = git@github.com:status-im/nim-stew.git
|
url = https://github.com/status-im/nim-stew.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nimbus-build-system"]
|
||||||
|
path = vendor/nimbus-build-system
|
||||||
|
url = https://github.com/status-im/nimbus-build-system.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-nat-traversal"]
|
||||||
|
path = vendor/nim-nat-traversal
|
||||||
|
url = https://github.com/status-im/nim-nat-traversal.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-libbacktrace"]
|
||||||
|
path = vendor/nim-libbacktrace
|
||||||
|
url = https://github.com/status-im/nim-libbacktrace.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-confutils"]
|
||||||
|
path = vendor/nim-confutils
|
||||||
|
url = https://github.com/status-im/nim-confutils.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-chronicles"]
|
||||||
|
path = vendor/nim-chronicles
|
||||||
|
url = https://github.com/status-im/nim-chronicles.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-faststreams"]
|
||||||
|
path = vendor/nim-faststreams
|
||||||
|
url = https://github.com/status-im/nim-faststreams.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-chronos"]
|
||||||
|
path = vendor/nim-chronos
|
||||||
|
url = https://github.com/status-im/nim-chronos.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-json-serialization"]
|
||||||
|
path = vendor/nim-json-serialization
|
||||||
|
url = https://github.com/status-im/nim-json-serialization.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-serialization"]
|
||||||
|
path = vendor/nim-serialization
|
||||||
|
url = https://github.com/status-im/nim-serialization.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nimcrypto"]
|
||||||
|
path = vendor/nimcrypto
|
||||||
|
url = https://github.com/cheatfate/nimcrypto.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-metrics"]
|
||||||
|
path = vendor/nim-metrics
|
||||||
|
url = https://github.com/status-im/nim-metrics.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-stint"]
|
||||||
|
path = vendor/nim-stint
|
||||||
|
url = https://github.com/status-im/nim-stint.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-json-rpc"]
|
||||||
|
path = vendor/nim-json-rpc
|
||||||
|
url = https://github.com/status-im/nim-json-rpc.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-http-utils"]
|
||||||
|
path = vendor/nim-http-utils
|
||||||
|
url = https://github.com/status-im/nim-http-utils.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/news"]
|
||||||
|
path = vendor/news
|
||||||
|
url = https://github.com/tormund/news.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
[submodule "vendor/nim-bearssl"]
|
||||||
|
path = vendor/nim-bearssl
|
||||||
|
url = https://github.com/status-im/nim-bearssl.git
|
||||||
|
ignore = dirty
|
||||||
|
branch = master
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
language: c
|
||||||
|
|
||||||
|
dist: bionic
|
||||||
|
|
||||||
|
# https://docs.travis-ci.com/user/caching/
|
||||||
|
cache:
|
||||||
|
ccache: true
|
||||||
|
directories:
|
||||||
|
- vendor/nimbus-build-system/vendor/Nim/bin
|
||||||
|
|
||||||
|
git:
|
||||||
|
# when multiple CI builds are queued, the tested commit needs to be in the last X commits cloned with "--depth X"
|
||||||
|
depth: 10
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: linux
|
||||||
|
arch: amd64
|
||||||
|
sudo: required
|
||||||
|
env:
|
||||||
|
- NPROC=2
|
||||||
|
before_install:
|
||||||
|
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib"
|
||||||
|
- sudo apt-get -q update
|
||||||
|
- os: linux
|
||||||
|
arch: arm64
|
||||||
|
sudo: required
|
||||||
|
env:
|
||||||
|
- NPROC=6 # Worth trying more than 2 parallel jobs: https://travis-ci.community/t/no-cache-support-on-arm64/5416/8
|
||||||
|
# (also used to get a different cache key than the amd64 one)
|
||||||
|
before_install:
|
||||||
|
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib"
|
||||||
|
- sudo apt-get -q update
|
||||||
|
- sudo apt-get install -y libpcre3-dev
|
||||||
|
- os: osx
|
||||||
|
before_install:
|
||||||
|
- HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install ccache
|
||||||
|
env:
|
||||||
|
- NPROC=2
|
||||||
|
|
||||||
|
script:
|
||||||
|
- set -e # fail fast
|
||||||
|
# Building Nim-1.0.4 takes up to 10 minutes on Travis - the time limit after which jobs are cancelled for having no output
|
||||||
|
- make -j${NPROC} NIMFLAGS="--parallelBuild:${NPROC}" V=1 update # to allow a newer Nim version to be detected
|
||||||
|
- make -j${NPROC} NIMFLAGS="--parallelBuild:${NPROC}" LOG_LEVEL=TRACE
|
||||||
|
- build/wakunode --help
|
||||||
|
- make -j${NPROC} NIMFLAGS="--parallelBuild:${NPROC}" test
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
beacon_chain is licensed under the Apache License version 2
|
||||||
|
Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2018 Status Research & Development GmbH
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,25 @@
|
||||||
|
beacon_chain is licensed under the MIT License
|
||||||
|
Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
109
Makefile
109
Makefile
|
@ -1,19 +1,102 @@
|
||||||
all: wakunode start_network quicksim
|
# Copyright (c) 2020 Status Research & Development GmbH. Licensed under
|
||||||
|
# either of:
|
||||||
|
# - Apache License, version 2.0
|
||||||
|
# - MIT license
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
|
||||||
start_network: node/v0/start_network.nim
|
SHELL := bash # the shell used internally by Make
|
||||||
nim c --threads:on -o:build/start_network node/v0/start_network.nim
|
|
||||||
|
|
||||||
quicksim: node/v0/quicksim.nim
|
# used inside the included makefiles
|
||||||
nim c --threads:on -o:build/quicksim node/v0/quicksim.nim
|
BUILD_SYSTEM_DIR := vendor/nimbus-build-system
|
||||||
|
|
||||||
wakunode: node/v0/wakunode.nim
|
# we don't want an error here, so we can handle things later, in the ".DEFAULT" target
|
||||||
nim c --threads:on -o:build/wakunode node/v0/wakunode.nim
|
-include $(BUILD_SYSTEM_DIR)/makefiles/variables.mk
|
||||||
|
|
||||||
wakunode2: node/v2/wakunode.nim
|
.PHONY: \
|
||||||
nim c --threads:on -o:build/wakunode2 node/v2/wakunode.nim
|
all \
|
||||||
|
deps \
|
||||||
|
update \
|
||||||
|
wakunode \
|
||||||
|
test \
|
||||||
|
clean \
|
||||||
|
libbacktrace
|
||||||
|
|
||||||
quicksim2: node/v2/quicksim.nim
|
ifeq ($(NIM_PARAMS),)
|
||||||
nim c --threads:on -o:build/quicksim2 node/v2/quicksim.nim
|
# "variables.mk" was not included, so we update the submodules.
|
||||||
|
GIT_SUBMODULE_UPDATE := git submodule update --init --recursive
|
||||||
|
.DEFAULT:
|
||||||
|
+@ echo -e "Git submodules not found. Running '$(GIT_SUBMODULE_UPDATE)'.\n"; \
|
||||||
|
$(GIT_SUBMODULE_UPDATE); \
|
||||||
|
echo
|
||||||
|
# Now that the included *.mk files appeared, and are newer than this file, Make will restart itself:
|
||||||
|
# https://www.gnu.org/software/make/manual/make.html#Remaking-Makefiles
|
||||||
|
#
|
||||||
|
# After restarting, it will execute its original goal, so we don't have to start a child Make here
|
||||||
|
# with "$(MAKE) $(MAKECMDGOALS)". Isn't hidden control flow great?
|
||||||
|
|
||||||
protocol2: protocol/v2/waku_protocol.nim
|
else # "variables.mk" was included. Business as usual until the end of this file.
|
||||||
nim c --threads:on -o:build/protocol2 protocol/v2/waku_protocol.nim
|
|
||||||
|
# default target, because it's the first one that doesn't start with '.'
|
||||||
|
all: | wakunode wakusim
|
||||||
|
|
||||||
|
# must be included after the default target
|
||||||
|
-include $(BUILD_SYSTEM_DIR)/makefiles/targets.mk
|
||||||
|
|
||||||
|
# "-d:release" implies "--stacktrace:off" and it cannot be added to config.nims
|
||||||
|
ifeq ($(USE_LIBBACKTRACE), 0)
|
||||||
|
NIM_PARAMS := $(NIM_PARAMS) -d:debug -d:disable_libbacktrace
|
||||||
|
else
|
||||||
|
NIM_PARAMS := $(NIM_PARAMS) -d:release
|
||||||
|
endif
|
||||||
|
|
||||||
|
deps: | deps-common waku.nims
|
||||||
|
ifneq ($(USE_LIBBACKTRACE), 0)
|
||||||
|
deps: | libbacktrace
|
||||||
|
endif
|
||||||
|
|
||||||
|
#- deletes and recreates "waku.nims" which on Windows is a copy instead of a proper symlink
|
||||||
|
update: | update-common
|
||||||
|
rm -rf waku.nims && \
|
||||||
|
$(MAKE) waku.nims $(HANDLE_OUTPUT)
|
||||||
|
|
||||||
|
# a phony target, because teaching `make` how to do conditional recompilation of Nim projects is too complicated
|
||||||
|
wakunode: | build deps
|
||||||
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
|
$(ENV_SCRIPT) nim wakunode $(NIM_PARAMS) waku.nims
|
||||||
|
|
||||||
|
wakusim: | build deps wakunode
|
||||||
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
|
$(ENV_SCRIPT) nim wakusim $(NIM_PARAMS) waku.nims
|
||||||
|
|
||||||
|
wakunode2: | build deps
|
||||||
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
|
$(ENV_SCRIPT) nim wakunode2 $(NIM_PARAMS) waku.nims
|
||||||
|
|
||||||
|
wakusim2: | build deps wakunode2
|
||||||
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
|
$(ENV_SCRIPT) nim wakusim2 $(NIM_PARAMS) waku.nims
|
||||||
|
|
||||||
|
protocol2:
|
||||||
|
echo -e $(BUILD_MSG) "build/$@" && \
|
||||||
|
$(ENV_SCRIPT) nim protocol2 $(NIM_PARAMS) waku.nims
|
||||||
|
# symlink
|
||||||
|
waku.nims:
|
||||||
|
ln -s waku.nimble $@
|
||||||
|
|
||||||
|
# nim-libbacktrace
|
||||||
|
libbacktrace:
|
||||||
|
+ $(MAKE) -C vendor/nim-libbacktrace --no-print-directory BUILD_CXX_LIB=0
|
||||||
|
|
||||||
|
# builds and runs the test suite
|
||||||
|
test: | build deps
|
||||||
|
$(ENV_SCRIPT) nim test $(NIM_PARAMS) waku.nims
|
||||||
|
|
||||||
|
# usual cleaning
|
||||||
|
clean: | clean-common
|
||||||
|
rm -rf build/{wakunode,quicksim,start_network,all_tests}
|
||||||
|
ifneq ($(USE_LIBBACKTRACE), 0)
|
||||||
|
+ $(MAKE) -C vendor/nim-libbacktrace clean $(HANDLE_OUTPUT)
|
||||||
|
endif
|
||||||
|
|
||||||
|
endif # "variables.mk" was not included
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
if defined(release):
|
||||||
|
switch("nimcache", "nimcache/release/$projectName")
|
||||||
|
else:
|
||||||
|
switch("nimcache", "nimcache/debug/$projectName")
|
||||||
|
|
||||||
|
if defined(windows):
|
||||||
|
# disable timestamps in Windows PE headers - https://wiki.debian.org/ReproducibleBuilds/TimestampsInPEBinaries
|
||||||
|
switch("passL", "-Wl,--no-insert-timestamp")
|
||||||
|
# increase stack size
|
||||||
|
switch("passL", "-Wl,--stack,8388608")
|
||||||
|
# https://github.com/nim-lang/Nim/issues/4057
|
||||||
|
--tlsEmulation:off
|
||||||
|
if defined(i386):
|
||||||
|
# set the IMAGE_FILE_LARGE_ADDRESS_AWARE flag so we can use PAE, if enabled, and access more than 2 GiB of RAM
|
||||||
|
switch("passL", "-Wl,--large-address-aware")
|
||||||
|
|
||||||
|
# The dynamic Chronicles output currently prevents us from using colors on Windows
|
||||||
|
# because these require direct manipulations of the stdout File object.
|
||||||
|
switch("define", "chronicles_colors=off")
|
||||||
|
|
||||||
|
# This helps especially for 32-bit x86, which sans SSE2 and newer instructions
|
||||||
|
# requires quite roundabout code generation for cryptography, and other 64-bit
|
||||||
|
# and larger arithmetic use cases, along with register starvation issues. When
|
||||||
|
# engineering a more portable binary release, this should be tweaked but still
|
||||||
|
# use at least -msse2 or -msse3.
|
||||||
|
if defined(disableMarchNative):
|
||||||
|
switch("passC", "-msse3")
|
||||||
|
else:
|
||||||
|
switch("passC", "-march=native")
|
||||||
|
if defined(windows):
|
||||||
|
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65782
|
||||||
|
# ("-fno-asynchronous-unwind-tables" breaks Nim's exception raising, sometimes)
|
||||||
|
switch("passC", "-mno-avx512vl")
|
||||||
|
|
||||||
|
--threads:on
|
||||||
|
--opt:speed
|
||||||
|
--excessiveStackTrace:on
|
||||||
|
# enable metric collection
|
||||||
|
--define:metrics
|
||||||
|
# for heap-usage-by-instance-type metrics and object base-type strings
|
||||||
|
--define:nimTypeNames
|
||||||
|
|
||||||
|
# the default open files limit is too low on macOS (512), breaking the
|
||||||
|
# "--debugger:native" build. It can be increased with `ulimit -n 1024`.
|
||||||
|
if not defined(macosx):
|
||||||
|
# add debugging symbols and original files and line numbers
|
||||||
|
--debugger:native
|
||||||
|
if not (defined(windows) and defined(i386)) and not defined(disable_libbacktrace):
|
||||||
|
# light-weight stack traces using libbacktrace and libunwind
|
||||||
|
--define:nimStackTraceOverride
|
||||||
|
switch("import", "libbacktrace")
|
||||||
|
|
||||||
|
--define:nimOldCaseObjects # https://github.com/status-im/nim-confutils/issues/9
|
||||||
|
|
||||||
|
# `switch("warning[CaseTransition]", "off")` fails with "Error: invalid command line option: '--warning[CaseTransition]'"
|
||||||
|
switch("warning", "CaseTransition:off")
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# We use ${BASH_SOURCE[0]} instead of $0 to allow sourcing this file
|
||||||
|
# and we fall back to a Zsh-specific special var to also support Zsh.
|
||||||
|
REL_PATH="$(dirname ${BASH_SOURCE[0]:-${(%):-%x}})"
|
||||||
|
ABS_PATH="$(cd ${REL_PATH}; pwd)"
|
||||||
|
source ${ABS_PATH}/vendor/nimbus-build-system/scripts/env.sh
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Package
|
|
||||||
|
|
||||||
version = "0.1.0"
|
|
||||||
author = "Status Research & Development GmbH"
|
|
||||||
description = "Waku, Private P2P Messaging for Resource-Rerestricted Devices"
|
|
||||||
license = "MIT"
|
|
||||||
srcDir = "src"
|
|
||||||
#bin = @["build/waku"]
|
|
||||||
|
|
||||||
# Dependencies
|
|
||||||
requires "nim >= 1.2.0"
|
|
|
@ -1,5 +0,0 @@
|
||||||
# Waku Node
|
|
||||||
|
|
||||||
TODO.
|
|
||||||
|
|
||||||
See README in `v0` folder for instructions on how to run a node.
|
|
|
@ -1,48 +0,0 @@
|
||||||
global:
|
|
||||||
scrape_interval: 1s
|
|
||||||
|
|
||||||
scrape_configs:
|
|
||||||
- job_name: "wakusim"
|
|
||||||
static_configs:
|
|
||||||
- targets: ['127.0.0.1:8010']
|
|
||||||
labels:
|
|
||||||
node: '0'
|
|
||||||
- targets: ['127.0.0.1:8011']
|
|
||||||
labels:
|
|
||||||
node: '1'
|
|
||||||
- targets: ['127.0.0.1:8012']
|
|
||||||
labels:
|
|
||||||
node: '2'
|
|
||||||
- targets: ['127.0.0.1:8013']
|
|
||||||
labels:
|
|
||||||
node: '3'
|
|
||||||
- targets: ['127.0.0.1:8014']
|
|
||||||
labels:
|
|
||||||
node: '4'
|
|
||||||
- targets: ['127.0.0.1:8015']
|
|
||||||
labels:
|
|
||||||
node: '5'
|
|
||||||
- targets: ['127.0.0.1:8016']
|
|
||||||
labels:
|
|
||||||
node: '6'
|
|
||||||
- targets: ['127.0.0.1:8017']
|
|
||||||
labels:
|
|
||||||
node: '7'
|
|
||||||
- targets: ['127.0.0.1:8018']
|
|
||||||
labels:
|
|
||||||
node: '8'
|
|
||||||
- targets: ['127.0.0.1:8019']
|
|
||||||
labels:
|
|
||||||
node: '9'
|
|
||||||
- targets: ['127.0.0.1:8020']
|
|
||||||
labels:
|
|
||||||
node: '10'
|
|
||||||
- targets: ['127.0.0.1:8021']
|
|
||||||
labels:
|
|
||||||
node: '11'
|
|
||||||
- targets: ['127.0.0.1:8008']
|
|
||||||
labels:
|
|
||||||
node: '12'
|
|
||||||
- targets: ['127.0.0.1:8009']
|
|
||||||
labels:
|
|
||||||
node: '13'
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,7 @@
|
||||||
|
import
|
||||||
|
./test_waku_connect,
|
||||||
|
./test_waku_config,
|
||||||
|
./test_waku_bridge,
|
||||||
|
./test_waku_mail,
|
||||||
|
./test_rpc_waku
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import
|
||||||
|
unittest, chronos, strutils,
|
||||||
|
eth/[keys, p2p]
|
||||||
|
|
||||||
|
var nextPort = 30303
|
||||||
|
|
||||||
|
proc localAddress*(port: int): Address =
|
||||||
|
let port = Port(port)
|
||||||
|
result = Address(udpPort: port, tcpPort: port,
|
||||||
|
ip: parseIpAddress("127.0.0.1"))
|
||||||
|
|
||||||
|
proc setupTestNode*(capabilities: varargs[ProtocolInfo, `protocolInfo`]): EthereumNode =
|
||||||
|
let keys1 = KeyPair.random()[]
|
||||||
|
result = newEthereumNode(keys1, localAddress(nextPort), 1, nil,
|
||||||
|
addAllCapabilities = false)
|
||||||
|
nextPort.inc
|
||||||
|
for capability in capabilities:
|
||||||
|
result.addCapability capability
|
||||||
|
|
||||||
|
template asyncTest*(name, body: untyped) =
|
||||||
|
test name:
|
||||||
|
proc scenario {.async.} = body
|
||||||
|
waitFor scenario()
|
||||||
|
|
||||||
|
template procSuite*(name, body: untyped) =
|
||||||
|
proc suitePayload =
|
||||||
|
suite name:
|
||||||
|
body
|
||||||
|
|
||||||
|
suitePayload()
|
||||||
|
|
||||||
|
template sourceDir*: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
|
@ -0,0 +1,237 @@
|
||||||
|
import
|
||||||
|
unittest, strformat, options, stew/byteutils, json_rpc/[rpcserver, rpcclient],
|
||||||
|
eth/common as eth_common, eth/[rlp, keys, p2p],
|
||||||
|
../waku/protocol/v1/waku_protocol,
|
||||||
|
../waku/node/v1/rpc/[hexstrings, rpc_types, waku, key_storage],
|
||||||
|
./test_helpers
|
||||||
|
|
||||||
|
from os import DirSep, ParDir
|
||||||
|
|
||||||
|
## Generate client convenience marshalling wrappers from forward declarations
|
||||||
|
## For testing, ethcallsigs needs to be kept in sync with ../waku/node/v1/rpc/waku
|
||||||
|
const sigPath = &"{sourceDir}{DirSep}{ParDir}{DirSep}waku{DirSep}node{DirSep}v1{DirSep}rpc{DirSep}wakucallsigs.nim"
|
||||||
|
createRpcSigs(RpcSocketClient, sigPath)
|
||||||
|
|
||||||
|
proc setupNode(capabilities: varargs[ProtocolInfo, `protocolInfo`]): EthereumNode =
|
||||||
|
let
|
||||||
|
keypair = KeyPair.random()[]
|
||||||
|
srvAddress = Address(ip: parseIpAddress("0.0.0.0"), tcpPort: Port(30303),
|
||||||
|
udpPort: Port(30303))
|
||||||
|
|
||||||
|
result = newEthereumNode(keypair, srvAddress, 1, nil, "waku test rpc",
|
||||||
|
addAllCapabilities = false)
|
||||||
|
for capability in capabilities:
|
||||||
|
result.addCapability capability
|
||||||
|
|
||||||
|
proc doTests {.async.} =
|
||||||
|
var ethNode = setupNode(Waku)
|
||||||
|
|
||||||
|
# Create Ethereum RPCs
|
||||||
|
let rpcPort = 8545
|
||||||
|
var
|
||||||
|
rpcServer = newRpcSocketServer(["localhost:" & $rpcPort])
|
||||||
|
client = newRpcSocketClient()
|
||||||
|
let keys = newKeyStorage()
|
||||||
|
setupWakuRPC(ethNode, keys, rpcServer)
|
||||||
|
|
||||||
|
# Begin tests
|
||||||
|
rpcServer.start()
|
||||||
|
await client.connect("localhost", Port(rpcPort))
|
||||||
|
|
||||||
|
suite "Waku Remote Procedure Calls":
|
||||||
|
test "waku_version":
|
||||||
|
check await(client.waku_version()) == wakuVersionStr
|
||||||
|
test "waku_info":
|
||||||
|
let info = await client.waku_info()
|
||||||
|
check info.maxMessageSize == defaultMaxMsgSize
|
||||||
|
test "waku_setMaxMessageSize":
|
||||||
|
let testValue = 1024'u64
|
||||||
|
check await(client.waku_setMaxMessageSize(testValue)) == true
|
||||||
|
var info = await client.waku_info()
|
||||||
|
check info.maxMessageSize == testValue
|
||||||
|
expect ValueError:
|
||||||
|
discard await(client.waku_setMaxMessageSize(defaultMaxMsgSize + 1))
|
||||||
|
info = await client.waku_info()
|
||||||
|
check info.maxMessageSize == testValue
|
||||||
|
test "waku_setMinPoW":
|
||||||
|
let testValue = 0.0001
|
||||||
|
check await(client.waku_setMinPoW(testValue)) == true
|
||||||
|
let info = await client.waku_info()
|
||||||
|
check info.minPow == testValue
|
||||||
|
# test "waku_markTrustedPeer":
|
||||||
|
# TODO: need to connect a peer to test
|
||||||
|
test "waku asymKey tests":
|
||||||
|
let keyID = await client.waku_newKeyPair()
|
||||||
|
check:
|
||||||
|
await(client.waku_hasKeyPair(keyID)) == true
|
||||||
|
await(client.waku_deleteKeyPair(keyID)) == true
|
||||||
|
await(client.waku_hasKeyPair(keyID)) == false
|
||||||
|
expect ValueError:
|
||||||
|
discard await(client.waku_deleteKeyPair(keyID))
|
||||||
|
|
||||||
|
let privkey = "0x5dc5381cae54ba3174dc0d46040fe11614d0cc94d41185922585198b4fcef9d3"
|
||||||
|
let pubkey = "0x04e5fd642a0f630bbb1e4cd7df629d7b8b019457a9a74f983c0484a045cebb176def86a54185b50bbba6bbf97779173695e92835d63109c23471e6da382f922fdb"
|
||||||
|
let keyID2 = await client.waku_addPrivateKey(privkey)
|
||||||
|
check:
|
||||||
|
await(client.waku_getPublicKey(keyID2)) == pubkey.toPublicKey
|
||||||
|
await(client.waku_getPrivateKey(keyID2)).toRaw() == privkey.toPrivateKey.toRaw()
|
||||||
|
await(client.waku_hasKeyPair(keyID2)) == true
|
||||||
|
await(client.waku_deleteKeyPair(keyID2)) == true
|
||||||
|
await(client.waku_hasKeyPair(keyID2)) == false
|
||||||
|
expect ValueError:
|
||||||
|
discard await(client.waku_deleteKeyPair(keyID2))
|
||||||
|
test "waku symKey tests":
|
||||||
|
let keyID = await client.waku_newSymKey()
|
||||||
|
check:
|
||||||
|
await(client.waku_hasSymKey(keyID)) == true
|
||||||
|
await(client.waku_deleteSymKey(keyID)) == true
|
||||||
|
await(client.waku_hasSymKey(keyID)) == false
|
||||||
|
expect ValueError:
|
||||||
|
discard await(client.waku_deleteSymKey(keyID))
|
||||||
|
|
||||||
|
let symKey = "0x0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
let keyID2 = await client.waku_addSymKey(symKey)
|
||||||
|
check:
|
||||||
|
await(client.waku_getSymKey(keyID2)) == symKey.toSymKey
|
||||||
|
await(client.waku_hasSymKey(keyID2)) == true
|
||||||
|
await(client.waku_deleteSymKey(keyID2)) == true
|
||||||
|
await(client.waku_hasSymKey(keyID2)) == false
|
||||||
|
expect ValueError:
|
||||||
|
discard await(client.waku_deleteSymKey(keyID2))
|
||||||
|
|
||||||
|
let keyID3 = await client.waku_generateSymKeyFromPassword("password")
|
||||||
|
let keyID4 = await client.waku_generateSymKeyFromPassword("password")
|
||||||
|
let keyID5 = await client.waku_generateSymKeyFromPassword("nimbus!")
|
||||||
|
check:
|
||||||
|
await(client.waku_getSymKey(keyID3)) ==
|
||||||
|
await(client.waku_getSymKey(keyID4))
|
||||||
|
await(client.waku_getSymKey(keyID3)) !=
|
||||||
|
await(client.waku_getSymKey(keyID5))
|
||||||
|
await(client.waku_hasSymKey(keyID3)) == true
|
||||||
|
await(client.waku_deleteSymKey(keyID3)) == true
|
||||||
|
await(client.waku_hasSymKey(keyID3)) == false
|
||||||
|
expect ValueError:
|
||||||
|
discard await(client.waku_deleteSymKey(keyID3))
|
||||||
|
|
||||||
|
# Some defaults for the filter & post tests
|
||||||
|
let
|
||||||
|
ttl = 30'u64
|
||||||
|
topicStr = "0x12345678"
|
||||||
|
payload = "0x45879632"
|
||||||
|
# A very low target and long time so we are sure the test never fails
|
||||||
|
# because of this
|
||||||
|
powTarget = 0.001
|
||||||
|
powTime = 1.0
|
||||||
|
|
||||||
|
test "waku filter create and delete":
|
||||||
|
let
|
||||||
|
topic = topicStr.toTopic()
|
||||||
|
symKeyID = await client.waku_newSymKey()
|
||||||
|
options = WakuFilterOptions(symKeyID: some(symKeyID),
|
||||||
|
topics: some(@[topic]))
|
||||||
|
filterID = await client.waku_newMessageFilter(options)
|
||||||
|
|
||||||
|
check:
|
||||||
|
filterID.string.isValidIdentifier
|
||||||
|
await(client.waku_deleteMessageFilter(filterID)) == true
|
||||||
|
expect ValueError:
|
||||||
|
discard await(client.waku_deleteMessageFilter(filterID))
|
||||||
|
|
||||||
|
test "waku symKey post and filter loop":
|
||||||
|
let
|
||||||
|
topic = topicStr.toTopic()
|
||||||
|
symKeyID = await client.waku_newSymKey()
|
||||||
|
options = WakuFilterOptions(symKeyID: some(symKeyID),
|
||||||
|
topics: some(@[topic]))
|
||||||
|
filterID = await client.waku_newMessageFilter(options)
|
||||||
|
message = WakuPostMessage(symKeyID: some(symKeyID),
|
||||||
|
ttl: ttl,
|
||||||
|
topic: some(topic),
|
||||||
|
payload: payload.HexDataStr,
|
||||||
|
powTime: powTime,
|
||||||
|
powTarget: powTarget)
|
||||||
|
check:
|
||||||
|
await(client.waku_setMinPoW(powTarget)) == true
|
||||||
|
await(client.waku_post(message)) == true
|
||||||
|
|
||||||
|
let messages = await client.waku_getFilterMessages(filterID)
|
||||||
|
check:
|
||||||
|
messages.len == 1
|
||||||
|
messages[0].sig.isNone()
|
||||||
|
messages[0].recipientPublicKey.isNone()
|
||||||
|
messages[0].ttl == ttl
|
||||||
|
messages[0].topic == topic
|
||||||
|
messages[0].payload == hexToSeqByte(payload)
|
||||||
|
messages[0].padding.len > 0
|
||||||
|
messages[0].pow >= powTarget
|
||||||
|
|
||||||
|
await(client.waku_deleteMessageFilter(filterID)) == true
|
||||||
|
|
||||||
|
test "waku asymKey post and filter loop":
|
||||||
|
let
|
||||||
|
topic = topicStr.toTopic()
|
||||||
|
privateKeyID = await client.waku_newKeyPair()
|
||||||
|
options = WakuFilterOptions(privateKeyID: some(privateKeyID))
|
||||||
|
filterID = await client.waku_newMessageFilter(options)
|
||||||
|
pubKey = await client.waku_getPublicKey(privateKeyID)
|
||||||
|
message = WakuPostMessage(pubKey: some(pubKey),
|
||||||
|
ttl: ttl,
|
||||||
|
topic: some(topic),
|
||||||
|
payload: payload.HexDataStr,
|
||||||
|
powTime: powTime,
|
||||||
|
powTarget: powTarget)
|
||||||
|
check:
|
||||||
|
await(client.waku_setMinPoW(powTarget)) == true
|
||||||
|
await(client.waku_post(message)) == true
|
||||||
|
|
||||||
|
let messages = await client.waku_getFilterMessages(filterID)
|
||||||
|
check:
|
||||||
|
messages.len == 1
|
||||||
|
messages[0].sig.isNone()
|
||||||
|
messages[0].recipientPublicKey.get() == pubKey
|
||||||
|
messages[0].ttl == ttl
|
||||||
|
messages[0].topic == topic
|
||||||
|
messages[0].payload == hexToSeqByte(payload)
|
||||||
|
messages[0].padding.len > 0
|
||||||
|
messages[0].pow >= powTarget
|
||||||
|
|
||||||
|
await(client.waku_deleteMessageFilter(filterID)) == true
|
||||||
|
|
||||||
|
test "waku signature in post and filter loop":
|
||||||
|
let
|
||||||
|
topic = topicStr.toTopic()
|
||||||
|
symKeyID = await client.waku_newSymKey()
|
||||||
|
privateKeyID = await client.waku_newKeyPair()
|
||||||
|
pubKey = await client.waku_getPublicKey(privateKeyID)
|
||||||
|
options = WakuFilterOptions(symKeyID: some(symKeyID),
|
||||||
|
topics: some(@[topic]),
|
||||||
|
sig: some(pubKey))
|
||||||
|
filterID = await client.waku_newMessageFilter(options)
|
||||||
|
message = WakuPostMessage(symKeyID: some(symKeyID),
|
||||||
|
sig: some(privateKeyID),
|
||||||
|
ttl: ttl,
|
||||||
|
topic: some(topic),
|
||||||
|
payload: payload.HexDataStr,
|
||||||
|
powTime: powTime,
|
||||||
|
powTarget: powTarget)
|
||||||
|
check:
|
||||||
|
await(client.waku_setMinPoW(powTarget)) == true
|
||||||
|
await(client.waku_post(message)) == true
|
||||||
|
|
||||||
|
let messages = await client.waku_getFilterMessages(filterID)
|
||||||
|
check:
|
||||||
|
messages.len == 1
|
||||||
|
messages[0].sig.get() == pubKey
|
||||||
|
messages[0].recipientPublicKey.isNone()
|
||||||
|
messages[0].ttl == ttl
|
||||||
|
messages[0].topic == topic
|
||||||
|
messages[0].payload == hexToSeqByte(payload)
|
||||||
|
messages[0].padding.len > 0
|
||||||
|
messages[0].pow >= powTarget
|
||||||
|
|
||||||
|
await(client.waku_deleteMessageFilter(filterID)) == true
|
||||||
|
|
||||||
|
rpcServer.stop()
|
||||||
|
rpcServer.close()
|
||||||
|
|
||||||
|
waitFor doTests()
|
|
@ -0,0 +1,95 @@
|
||||||
|
#
|
||||||
|
# Waku
|
||||||
|
# (c) Copyright 2019
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# Licensed under either of
|
||||||
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# MIT license (LICENSE-MIT)
|
||||||
|
|
||||||
|
import
|
||||||
|
sequtils, unittest, tables, chronos, eth/p2p, eth/p2p/peer_pool,
|
||||||
|
eth/p2p/rlpx_protocols/whisper_protocol as whisper,
|
||||||
|
../waku/protocol/v1/waku_protocol as waku,
|
||||||
|
../waku/protocol/v1/waku_bridge,
|
||||||
|
./test_helpers
|
||||||
|
|
||||||
|
let safeTTL = 5'u32
|
||||||
|
let waitInterval = waku.messageInterval + 150.milliseconds
|
||||||
|
|
||||||
|
procSuite "Waku - Whisper bridge tests":
|
||||||
|
# Waku Whisper node has both capabilities, listens to Whisper and Waku and
|
||||||
|
# relays traffic between the two.
|
||||||
|
var
|
||||||
|
nodeWakuWhisper = setupTestNode(Whisper, Waku) # This will be the bridge
|
||||||
|
nodeWhisper = setupTestNode(Whisper)
|
||||||
|
nodeWaku = setupTestNode(Waku)
|
||||||
|
|
||||||
|
nodeWakuWhisper.startListening()
|
||||||
|
let bridgeNode = newNode(nodeWakuWhisper.toENode())
|
||||||
|
nodeWakuWhisper.shareMessageQueue()
|
||||||
|
|
||||||
|
waitFor nodeWhisper.peerPool.connectToNode(bridgeNode)
|
||||||
|
waitFor nodeWaku.peerPool.connectToNode(bridgeNode)
|
||||||
|
|
||||||
|
asyncTest "WakuWhisper and Whisper peers connected":
|
||||||
|
check:
|
||||||
|
nodeWhisper.peerPool.connectedNodes.len() == 1
|
||||||
|
nodeWaku.peerPool.connectedNodes.len() == 1
|
||||||
|
|
||||||
|
asyncTest "Whisper - Waku communcation via bridge":
|
||||||
|
# topic whisper node subscribes to, waku node posts to
|
||||||
|
let topic1 = [byte 0x12, 0, 0, 0]
|
||||||
|
# topic waku node subscribes to, whisper node posts to
|
||||||
|
let topic2 = [byte 0x34, 0, 0, 0]
|
||||||
|
var payloads = [repeat(byte 0, 10), repeat(byte 1, 10)]
|
||||||
|
var futures = [newFuture[int](), newFuture[int]()]
|
||||||
|
|
||||||
|
proc handler1(msg: whisper.ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payloads[0]
|
||||||
|
futures[0].complete(1)
|
||||||
|
proc handler2(msg: waku.ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payloads[1]
|
||||||
|
futures[1].complete(1)
|
||||||
|
|
||||||
|
var filter1 = whisper.subscribeFilter(nodeWhisper,
|
||||||
|
whisper.initFilter(topics = @[topic1]), handler1)
|
||||||
|
var filter2 = waku.subscribeFilter(nodeWaku,
|
||||||
|
waku.initFilter(topics = @[topic2]), handler2)
|
||||||
|
|
||||||
|
check:
|
||||||
|
# Message should also end up in the Whisper node its queue via the bridge
|
||||||
|
waku.postMessage(nodeWaku, ttl = safeTTL + 1, topic = topic1,
|
||||||
|
payload = payloads[0]) == true
|
||||||
|
# Message should also end up in the Waku node its queue via the bridge
|
||||||
|
whisper.postMessage(nodeWhisper, ttl = safeTTL, topic = topic2,
|
||||||
|
payload = payloads[1]) == true
|
||||||
|
nodeWhisper.protocolState(Whisper).queue.items.len == 1
|
||||||
|
nodeWaku.protocolState(Waku).queue.items.len == 1
|
||||||
|
|
||||||
|
# waitInterval*2 as messages have to pass the bridge also (2 hops)
|
||||||
|
await allFutures(futures).withTimeout(waitInterval*2)
|
||||||
|
|
||||||
|
# Relay can receive Whisper & Waku messages
|
||||||
|
nodeWakuWhisper.protocolState(Whisper).queue.items.len == 2
|
||||||
|
nodeWakuWhisper.protocolState(Waku).queue.items.len == 2
|
||||||
|
|
||||||
|
# Whisper node can receive Waku messages (via bridge)
|
||||||
|
nodeWhisper.protocolState(Whisper).queue.items.len == 2
|
||||||
|
# Waku node can receive Whisper messages (via bridge)
|
||||||
|
nodeWaku.protocolState(Waku).queue.items.len == 2
|
||||||
|
|
||||||
|
whisper.unsubscribeFilter(nodeWhisper, filter1) == true
|
||||||
|
waku.unsubscribeFilter(nodeWaku, filter2) == true
|
||||||
|
|
||||||
|
# XXX: This reads a bit weird, but eh
|
||||||
|
waku.resetMessageQueue(nodeWaku)
|
||||||
|
whisper.resetMessageQueue(nodeWhisper)
|
||||||
|
# shared queue so Waku and Whisper should be set to 0
|
||||||
|
waku.resetMessageQueue(nodeWakuWhisper)
|
||||||
|
|
||||||
|
check:
|
||||||
|
nodeWhisper.protocolState(Whisper).queue.items.len == 0
|
||||||
|
nodeWaku.protocolState(Waku).queue.items.len == 0
|
||||||
|
nodeWakuWhisper.protocolState(Whisper).queue.items.len == 0
|
||||||
|
nodeWakuWhisper.protocolState(Waku).queue.items.len == 0
|
|
@ -0,0 +1,64 @@
|
||||||
|
#
|
||||||
|
# Waku
|
||||||
|
# (c) Copyright 2020
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# Licensed under either of
|
||||||
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# MIT license (LICENSE-MIT)
|
||||||
|
|
||||||
|
import
|
||||||
|
sequtils, options, unittest, times,
|
||||||
|
../waku/protocol/v1/waku_protocol
|
||||||
|
|
||||||
|
suite "Waku envelope validation":
|
||||||
|
test "should validate and allow envelope according to config":
|
||||||
|
let ttl = 1'u32
|
||||||
|
let topic = [byte 1, 2, 3, 4]
|
||||||
|
let config = WakuConfig(powRequirement: 0, bloom: some(topic.topicBloom()),
|
||||||
|
isLightNode: false, maxMsgSize: defaultMaxMsgSize)
|
||||||
|
|
||||||
|
let env = Envelope(expiry:epochTime().uint32 + ttl, ttl: ttl, topic: topic,
|
||||||
|
data: repeat(byte 9, 256), nonce: 0)
|
||||||
|
check env.valid()
|
||||||
|
|
||||||
|
let msg = initMessage(env)
|
||||||
|
check msg.allowed(config)
|
||||||
|
|
||||||
|
test "should invalidate envelope due to ttl 0":
|
||||||
|
let ttl = 0'u32
|
||||||
|
let topic = [byte 1, 2, 3, 4]
|
||||||
|
|
||||||
|
let env = Envelope(expiry:epochTime().uint32 + ttl, ttl: ttl, topic: topic,
|
||||||
|
data: repeat(byte 9, 256), nonce: 0)
|
||||||
|
check env.valid() == false
|
||||||
|
|
||||||
|
test "should invalidate envelope due to expired":
|
||||||
|
let ttl = 1'u32
|
||||||
|
let topic = [byte 1, 2, 3, 4]
|
||||||
|
|
||||||
|
let env = Envelope(expiry:epochTime().uint32, ttl: ttl, topic: topic,
|
||||||
|
data: repeat(byte 9, 256), nonce: 0)
|
||||||
|
check env.valid() == false
|
||||||
|
|
||||||
|
test "should invalidate envelope due to in the future":
|
||||||
|
let ttl = 1'u32
|
||||||
|
let topic = [byte 1, 2, 3, 4]
|
||||||
|
|
||||||
|
# there is currently a 2 second tolerance, hence the + 3
|
||||||
|
let env = Envelope(expiry:epochTime().uint32 + ttl + 3, ttl: ttl,
|
||||||
|
topic: topic, data: repeat(byte 9, 256), nonce: 0)
|
||||||
|
check env.valid() == false
|
||||||
|
|
||||||
|
test "should not allow envelope due to bloom filter":
|
||||||
|
let topic = [byte 1, 2, 3, 4]
|
||||||
|
let wrongTopic = [byte 9, 8, 7, 6]
|
||||||
|
let config = WakuConfig(powRequirement: 0,
|
||||||
|
bloom: some(wrongTopic.topicBloom()),
|
||||||
|
isLightNode: false, maxMsgSize: defaultMaxMsgSize)
|
||||||
|
|
||||||
|
let env = Envelope(expiry:100000 , ttl: 30, topic: topic,
|
||||||
|
data: repeat(byte 9, 256), nonce: 0)
|
||||||
|
|
||||||
|
let msg = initMessage(env)
|
||||||
|
check msg.allowed(config) == false
|
|
@ -0,0 +1,557 @@
|
||||||
|
#
|
||||||
|
# Waku
|
||||||
|
# (c) Copyright 2019
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# Licensed under either of
|
||||||
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# MIT license (LICENSE-MIT)
|
||||||
|
|
||||||
|
import
|
||||||
|
sequtils, tables, unittest, chronos, eth/[keys, p2p], eth/p2p/peer_pool,
|
||||||
|
../waku/protocol/v1/waku_protocol,
|
||||||
|
./test_helpers
|
||||||
|
|
||||||
|
const
|
||||||
|
safeTTL = 5'u32
|
||||||
|
waitInterval = messageInterval + 150.milliseconds
|
||||||
|
conditionTimeoutMs = 3000.milliseconds
|
||||||
|
|
||||||
|
proc resetMessageQueues(nodes: varargs[EthereumNode]) =
|
||||||
|
for node in nodes:
|
||||||
|
node.resetMessageQueue()
|
||||||
|
|
||||||
|
# check on a condition until true or return a future containing false
|
||||||
|
# if timeout expires first
|
||||||
|
proc eventually(timeout: Duration, condition: proc(): bool {.gcsafe.}):
|
||||||
|
Future[bool] =
|
||||||
|
let wrappedCondition = proc(): Future[bool] {.async.} =
|
||||||
|
let f = newFuture[bool]()
|
||||||
|
while not condition():
|
||||||
|
await sleepAsync(100.milliseconds)
|
||||||
|
f.complete(true)
|
||||||
|
return await f
|
||||||
|
return withTimeout(wrappedCondition(), timeout)
|
||||||
|
|
||||||
|
suite "Waku connections":
|
||||||
|
asyncTest "Waku connections":
|
||||||
|
var
|
||||||
|
n1 = setupTestNode(Waku)
|
||||||
|
n2 = setupTestNode(Waku)
|
||||||
|
n3 = setupTestNode(Waku)
|
||||||
|
n4 = setupTestNode(Waku)
|
||||||
|
|
||||||
|
var topics: seq[Topic]
|
||||||
|
n1.protocolState(Waku).config.topics = some(topics)
|
||||||
|
n2.protocolState(Waku).config.topics = some(topics)
|
||||||
|
n3.protocolState(Waku).config.topics = none(seq[Topic])
|
||||||
|
n4.protocolState(Waku).config.topics = none(seq[Topic])
|
||||||
|
|
||||||
|
n1.startListening()
|
||||||
|
n3.startListening()
|
||||||
|
|
||||||
|
let
|
||||||
|
p1 = await n2.rlpxConnect(newNode(n1.toENode()))
|
||||||
|
p2 = await n2.rlpxConnect(newNode(n3.toENode()))
|
||||||
|
p3 = await n4.rlpxConnect(newNode(n3.toENode()))
|
||||||
|
check:
|
||||||
|
p1.isNil
|
||||||
|
p2.isNil == false
|
||||||
|
p3.isNil == false
|
||||||
|
|
||||||
|
asyncTest "Filters with encryption and signing":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let encryptKeyPair = KeyPair.random()[]
|
||||||
|
let signKeyPair = KeyPair.random()[]
|
||||||
|
var symKey: SymKey
|
||||||
|
let topic = [byte 0x12, 0, 0, 0]
|
||||||
|
var filters: seq[string] = @[]
|
||||||
|
var payloads = [repeat(byte 1, 10), repeat(byte 2, 10),
|
||||||
|
repeat(byte 3, 10), repeat(byte 4, 10)]
|
||||||
|
var futures = [newFuture[int](), newFuture[int](),
|
||||||
|
newFuture[int](), newFuture[int]()]
|
||||||
|
|
||||||
|
proc handler1(msg: ReceivedMessage) =
|
||||||
|
var count {.global.}: int
|
||||||
|
check msg.decoded.payload == payloads[0] or
|
||||||
|
msg.decoded.payload == payloads[1]
|
||||||
|
count += 1
|
||||||
|
if count == 2: futures[0].complete(1)
|
||||||
|
proc handler2(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payloads[1]
|
||||||
|
futures[1].complete(1)
|
||||||
|
proc handler3(msg: ReceivedMessage) =
|
||||||
|
var count {.global.}: int
|
||||||
|
check msg.decoded.payload == payloads[2] or
|
||||||
|
msg.decoded.payload == payloads[3]
|
||||||
|
count += 1
|
||||||
|
if count == 2: futures[2].complete(1)
|
||||||
|
proc handler4(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payloads[3]
|
||||||
|
futures[3].complete(1)
|
||||||
|
|
||||||
|
# Filters
|
||||||
|
# filter for encrypted asym
|
||||||
|
filters.add(node1.subscribeFilter(initFilter(
|
||||||
|
privateKey = some(encryptKeyPair.seckey), topics = @[topic]), handler1))
|
||||||
|
# filter for encrypted asym + signed
|
||||||
|
filters.add(node1.subscribeFilter(initFilter(some(signKeyPair.pubkey),
|
||||||
|
privateKey = some(encryptKeyPair.seckey), topics = @[topic]), handler2))
|
||||||
|
# filter for encrypted sym
|
||||||
|
filters.add(node1.subscribeFilter(initFilter(symKey = some(symKey),
|
||||||
|
topics = @[topic]), handler3))
|
||||||
|
# filter for encrypted sym + signed
|
||||||
|
filters.add(node1.subscribeFilter(initFilter(some(signKeyPair.pubkey),
|
||||||
|
symKey = some(symKey), topics = @[topic]), handler4))
|
||||||
|
# Messages
|
||||||
|
check:
|
||||||
|
# encrypted asym
|
||||||
|
node2.postMessage(some(encryptKeyPair.pubkey), ttl = safeTTL,
|
||||||
|
topic = topic, payload = payloads[0]) == true
|
||||||
|
# encrypted asym + signed
|
||||||
|
node2.postMessage(some(encryptKeyPair.pubkey),
|
||||||
|
src = some(signKeyPair.seckey), ttl = safeTTL,
|
||||||
|
topic = topic, payload = payloads[1]) == true
|
||||||
|
# encrypted sym
|
||||||
|
node2.postMessage(symKey = some(symKey), ttl = safeTTL, topic = topic,
|
||||||
|
payload = payloads[2]) == true
|
||||||
|
# encrypted sym + signed
|
||||||
|
node2.postMessage(symKey = some(symKey),
|
||||||
|
src = some(signKeyPair.seckey),
|
||||||
|
ttl = safeTTL, topic = topic,
|
||||||
|
payload = payloads[3]) == true
|
||||||
|
|
||||||
|
node2.protocolState(Waku).queue.items.len == 4
|
||||||
|
|
||||||
|
check:
|
||||||
|
await allFutures(futures).withTimeout(waitInterval)
|
||||||
|
node1.protocolState(Waku).queue.items.len == 4
|
||||||
|
|
||||||
|
for filter in filters:
|
||||||
|
check node1.unsubscribeFilter(filter) == true
|
||||||
|
|
||||||
|
asyncTest "Filters with topics":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let topic1 = [byte 0x12, 0, 0, 0]
|
||||||
|
let topic2 = [byte 0x34, 0, 0, 0]
|
||||||
|
var payloads = [repeat(byte 0, 10), repeat(byte 1, 10)]
|
||||||
|
var futures = [newFuture[int](), newFuture[int]()]
|
||||||
|
proc handler1(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payloads[0]
|
||||||
|
futures[0].complete(1)
|
||||||
|
proc handler2(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payloads[1]
|
||||||
|
futures[1].complete(1)
|
||||||
|
|
||||||
|
var filter1 = node1.subscribeFilter(initFilter(topics = @[topic1]), handler1)
|
||||||
|
var filter2 = node1.subscribeFilter(initFilter(topics = @[topic2]), handler2)
|
||||||
|
|
||||||
|
check:
|
||||||
|
node2.postMessage(ttl = safeTTL + 1, topic = topic1,
|
||||||
|
payload = payloads[0]) == true
|
||||||
|
node2.postMessage(ttl = safeTTL, topic = topic2,
|
||||||
|
payload = payloads[1]) == true
|
||||||
|
node2.protocolState(Waku).queue.items.len == 2
|
||||||
|
|
||||||
|
await allFutures(futures).withTimeout(waitInterval)
|
||||||
|
node1.protocolState(Waku).queue.items.len == 2
|
||||||
|
|
||||||
|
node1.unsubscribeFilter(filter1) == true
|
||||||
|
node1.unsubscribeFilter(filter2) == true
|
||||||
|
|
||||||
|
asyncTest "Filters with PoW":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let topic = [byte 0x12, 0, 0, 0]
|
||||||
|
var payload = repeat(byte 0, 10)
|
||||||
|
var futures = [newFuture[int](), newFuture[int]()]
|
||||||
|
proc handler1(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payload
|
||||||
|
futures[0].complete(1)
|
||||||
|
proc handler2(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payload
|
||||||
|
futures[1].complete(1)
|
||||||
|
|
||||||
|
var filter1 = node1.subscribeFilter(
|
||||||
|
initFilter(topics = @[topic], powReq = 0), handler1)
|
||||||
|
var filter2 = node1.subscribeFilter(
|
||||||
|
initFilter(topics = @[topic], powReq = 1_000_000), handler2)
|
||||||
|
|
||||||
|
check:
|
||||||
|
node2.postMessage(ttl = safeTTL, topic = topic, payload = payload) == true
|
||||||
|
|
||||||
|
(await futures[0].withTimeout(waitInterval)) == true
|
||||||
|
(await futures[1].withTimeout(waitInterval)) == false
|
||||||
|
node1.protocolState(Waku).queue.items.len == 1
|
||||||
|
|
||||||
|
node1.unsubscribeFilter(filter1) == true
|
||||||
|
node1.unsubscribeFilter(filter2) == true
|
||||||
|
|
||||||
|
asyncTest "Filters with queues":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let topic = [byte 0, 0, 0, 0]
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
|
||||||
|
var filter = node1.subscribeFilter(initFilter(topics = @[topic]))
|
||||||
|
for i in countdown(10, 1):
|
||||||
|
check node2.postMessage(ttl = safeTTL, topic = topic,
|
||||||
|
payload = payload) == true
|
||||||
|
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
check:
|
||||||
|
node1.getFilterMessages(filter).len() == 10
|
||||||
|
node1.getFilterMessages(filter).len() == 0
|
||||||
|
node1.unsubscribeFilter(filter) == true
|
||||||
|
|
||||||
|
asyncTest "Local filter notify":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let topic = [byte 0, 0, 0, 0]
|
||||||
|
|
||||||
|
var filter = node1.subscribeFilter(initFilter(topics = @[topic]))
|
||||||
|
check:
|
||||||
|
node1.postMessage(ttl = safeTTL, topic = topic,
|
||||||
|
payload = repeat(byte 4, 10)) == true
|
||||||
|
node1.getFilterMessages(filter).len() == 1
|
||||||
|
node1.unsubscribeFilter(filter) == true
|
||||||
|
|
||||||
|
asyncTest "Bloomfilter blocking":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let sendTopic1 = [byte 0x12, 0, 0, 0]
|
||||||
|
let sendTopic2 = [byte 0x34, 0, 0, 0]
|
||||||
|
let filterTopics = @[[byte 0x34, 0, 0, 0],[byte 0x56, 0, 0, 0]]
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
var f: Future[int] = newFuture[int]()
|
||||||
|
proc handler(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payload
|
||||||
|
f.complete(1)
|
||||||
|
var filter = node1.subscribeFilter(
|
||||||
|
initFilter(topics = filterTopics), handler)
|
||||||
|
await node1.setBloomFilter(node1.filtersToBloom())
|
||||||
|
|
||||||
|
check:
|
||||||
|
node2.postMessage(ttl = safeTTL, topic = sendTopic1,
|
||||||
|
payload = payload) == true
|
||||||
|
node2.protocolState(Waku).queue.items.len == 1
|
||||||
|
|
||||||
|
(await f.withTimeout(waitInterval)) == false
|
||||||
|
node1.protocolState(Waku).queue.items.len == 0
|
||||||
|
|
||||||
|
resetMessageQueues(node1, node2)
|
||||||
|
|
||||||
|
f = newFuture[int]()
|
||||||
|
|
||||||
|
check:
|
||||||
|
node2.postMessage(ttl = safeTTL, topic = sendTopic2,
|
||||||
|
payload = payload) == true
|
||||||
|
node2.protocolState(Waku).queue.items.len == 1
|
||||||
|
|
||||||
|
await f.withTimeout(waitInterval)
|
||||||
|
f.read() == 1
|
||||||
|
node1.protocolState(Waku).queue.items.len == 1
|
||||||
|
|
||||||
|
node1.unsubscribeFilter(filter) == true
|
||||||
|
|
||||||
|
await node1.setBloomFilter(fullBloom())
|
||||||
|
|
||||||
|
asyncTest "PoW blocking":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let topic = [byte 0, 0, 0, 0]
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
|
||||||
|
await node1.setPowRequirement(1_000_000)
|
||||||
|
check:
|
||||||
|
node2.postMessage(ttl = safeTTL, topic = topic, payload = payload) == true
|
||||||
|
node2.protocolState(Waku).queue.items.len == 1
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
check:
|
||||||
|
node1.protocolState(Waku).queue.items.len == 0
|
||||||
|
|
||||||
|
resetMessageQueues(node1, node2)
|
||||||
|
|
||||||
|
await node1.setPowRequirement(0.0)
|
||||||
|
check:
|
||||||
|
node2.postMessage(ttl = safeTTL, topic = topic, payload = payload) == true
|
||||||
|
node2.protocolState(Waku).queue.items.len == 1
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
check:
|
||||||
|
node1.protocolState(Waku).queue.items.len == 1
|
||||||
|
|
||||||
|
asyncTest "Queue pruning":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let topic = [byte 0, 0, 0, 0]
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
# We need a minimum TTL of 2 as when set to 1 there is a small chance that
|
||||||
|
# it is already expired after messageInterval due to rounding down of float
|
||||||
|
# to uint32 in postMessage()
|
||||||
|
let lowerTTL = 2'u32 # Lower TTL as we need to wait for messages to expire
|
||||||
|
for i in countdown(10, 1):
|
||||||
|
check node2.postMessage(ttl = lowerTTL, topic = topic, payload = payload)
|
||||||
|
check node2.protocolState(Waku).queue.items.len == 10
|
||||||
|
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
check node1.protocolState(Waku).queue.items.len == 10
|
||||||
|
|
||||||
|
await sleepAsync(milliseconds((lowerTTL+1)*1000))
|
||||||
|
check node1.protocolState(Waku).queue.items.len == 0
|
||||||
|
check node2.protocolState(Waku).queue.items.len == 0
|
||||||
|
|
||||||
|
asyncTest "P2P post":
|
||||||
|
var node1 = setupTestNode(Waku)
|
||||||
|
var node2 = setupTestNode(Waku)
|
||||||
|
node2.startListening()
|
||||||
|
waitFor node1.peerPool.connectToNode(newNode(node2.toENode()))
|
||||||
|
|
||||||
|
let topic = [byte 0, 0, 0, 0]
|
||||||
|
var f: Future[int] = newFuture[int]()
|
||||||
|
proc handler(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == repeat(byte 4, 10)
|
||||||
|
f.complete(1)
|
||||||
|
|
||||||
|
var filter = node1.subscribeFilter(initFilter(topics = @[topic],
|
||||||
|
allowP2P = true), handler)
|
||||||
|
# Need to be sure that node1 is added in the peerpool of node2 as
|
||||||
|
# postMessage with target will iterate over the peers
|
||||||
|
require await eventually(conditionTimeoutMs,
|
||||||
|
proc(): bool = node2.peerPool.len == 1)
|
||||||
|
check:
|
||||||
|
node1.setPeerTrusted(toNodeId(node2.keys.pubkey)) == true
|
||||||
|
node2.postMessage(ttl = 10, topic = topic,
|
||||||
|
payload = repeat(byte 4, 10),
|
||||||
|
targetPeer = some(toNodeId(node1.keys.pubkey))) == true
|
||||||
|
|
||||||
|
await f.withTimeout(waitInterval)
|
||||||
|
f.read() == 1
|
||||||
|
node1.protocolState(Waku).queue.items.len == 0
|
||||||
|
node2.protocolState(Waku).queue.items.len == 0
|
||||||
|
|
||||||
|
node1.unsubscribeFilter(filter) == true
|
||||||
|
|
||||||
|
asyncTest "Light node posting":
|
||||||
|
var ln = setupTestNode(Waku)
|
||||||
|
await ln.setLightNode(true)
|
||||||
|
var fn = setupTestNode(Waku)
|
||||||
|
fn.startListening()
|
||||||
|
await ln.peerPool.connectToNode(newNode(fn.toENode()))
|
||||||
|
|
||||||
|
let topic = [byte 0, 0, 0, 0]
|
||||||
|
|
||||||
|
check:
|
||||||
|
ln.peerPool.connectedNodes.len() == 1
|
||||||
|
# normal post
|
||||||
|
ln.postMessage(ttl = safeTTL, topic = topic,
|
||||||
|
payload = repeat(byte 0, 10)) == true
|
||||||
|
ln.protocolState(Waku).queue.items.len == 1
|
||||||
|
# TODO: add test on message relaying
|
||||||
|
|
||||||
|
asyncTest "Connect two light nodes":
|
||||||
|
var ln1 = setupTestNode(Waku)
|
||||||
|
var ln2 = setupTestNode(Waku)
|
||||||
|
|
||||||
|
await ln1.setLightNode(true)
|
||||||
|
await ln2.setLightNode(true)
|
||||||
|
|
||||||
|
ln2.startListening()
|
||||||
|
let peer = await ln1.rlpxConnect(newNode(ln2.toENode()))
|
||||||
|
check peer.isNil == true
|
||||||
|
|
||||||
|
asyncTest "Waku set-topic-interest":
|
||||||
|
var
|
||||||
|
wakuTopicNode = setupTestNode(Waku)
|
||||||
|
wakuNode = setupTestNode(Waku)
|
||||||
|
|
||||||
|
let
|
||||||
|
topic1 = [byte 0xDA, 0xDA, 0xDA, 0xAA]
|
||||||
|
topic2 = [byte 0xD0, 0xD0, 0xD0, 0x00]
|
||||||
|
wrongTopic = [byte 0x4B, 0x1D, 0x4B, 0x1D]
|
||||||
|
|
||||||
|
# Set one topic so we are not considered a full node
|
||||||
|
wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1])
|
||||||
|
|
||||||
|
wakuNode.startListening()
|
||||||
|
await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||||
|
|
||||||
|
# Update topic interest
|
||||||
|
check:
|
||||||
|
await setTopicInterest(wakuTopicNode, @[topic1, topic2])
|
||||||
|
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
check:
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = topic1, payload = payload)
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = topic2, payload = payload)
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = wrongTopic, payload = payload)
|
||||||
|
wakuNode.protocolState(Waku).queue.items.len == 3
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
check:
|
||||||
|
wakuTopicNode.protocolState(Waku).queue.items.len == 2
|
||||||
|
|
||||||
|
asyncTest "Waku set-minimum-pow":
|
||||||
|
var
|
||||||
|
wakuPowNode = setupTestNode(Waku)
|
||||||
|
wakuNode = setupTestNode(Waku)
|
||||||
|
|
||||||
|
wakuNode.startListening()
|
||||||
|
await wakuPowNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||||
|
|
||||||
|
# Update minimum pow
|
||||||
|
await setPowRequirement(wakuPowNode, 1.0)
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
|
||||||
|
check:
|
||||||
|
wakuNode.peerPool.len == 1
|
||||||
|
|
||||||
|
# check powRequirement is updated
|
||||||
|
for peer in wakuNode.peerPool.peers:
|
||||||
|
check:
|
||||||
|
peer.state(Waku).powRequirement == 1.0
|
||||||
|
|
||||||
|
asyncTest "Waku set-light-node":
|
||||||
|
var
|
||||||
|
wakuLightNode = setupTestNode(Waku)
|
||||||
|
wakuNode = setupTestNode(Waku)
|
||||||
|
|
||||||
|
wakuNode.startListening()
|
||||||
|
await wakuLightNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||||
|
|
||||||
|
# Update minimum pow
|
||||||
|
await setLightNode(wakuLightNode, true)
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
|
||||||
|
check:
|
||||||
|
wakuNode.peerPool.len == 1
|
||||||
|
|
||||||
|
# check lightNode is updated
|
||||||
|
for peer in wakuNode.peerPool.peers:
|
||||||
|
check:
|
||||||
|
peer.state(Waku).isLightNode
|
||||||
|
|
||||||
|
asyncTest "Waku set-bloom-filter":
|
||||||
|
var
|
||||||
|
wakuBloomNode = setupTestNode(Waku)
|
||||||
|
wakuNode = setupTestNode(Waku)
|
||||||
|
bloom = fullBloom()
|
||||||
|
topics = @[[byte 0xDA, 0xDA, 0xDA, 0xAA]]
|
||||||
|
|
||||||
|
# Set topic interest
|
||||||
|
discard await wakuBloomNode.setTopicInterest(topics)
|
||||||
|
|
||||||
|
wakuBloomNode.startListening()
|
||||||
|
await wakuNode.peerPool.connectToNode(newNode(wakuBloomNode.toENode()))
|
||||||
|
|
||||||
|
# Sanity check
|
||||||
|
check:
|
||||||
|
wakuNode.peerPool.len == 1
|
||||||
|
|
||||||
|
# check bloom filter is updated
|
||||||
|
for peer in wakuNode.peerPool.peers:
|
||||||
|
check:
|
||||||
|
peer.state(Waku).bloom == bloom
|
||||||
|
peer.state(Waku).topics == some(topics)
|
||||||
|
|
||||||
|
let hasBloomNodeConnectedCondition = proc(): bool =
|
||||||
|
wakuBloomNode.peerPool.len == 1
|
||||||
|
# wait for the peer to be connected on the other side
|
||||||
|
let hasBloomNodeConnected =
|
||||||
|
await eventually(conditionTimeoutMs, hasBloomNodeConnectedCondition)
|
||||||
|
# check bloom filter is updated
|
||||||
|
check:
|
||||||
|
hasBloomNodeConnected
|
||||||
|
|
||||||
|
# disable one bit in the bloom filter
|
||||||
|
bloom[0] = 0x0
|
||||||
|
|
||||||
|
# and set it
|
||||||
|
await setBloomFilter(wakuBloomNode, bloom)
|
||||||
|
|
||||||
|
let bloomFilterUpdatedCondition = proc(): bool =
|
||||||
|
for peer in wakuNode.peerPool.peers:
|
||||||
|
return peer.state(Waku).bloom == bloom and
|
||||||
|
peer.state(Waku).topics == none(seq[Topic])
|
||||||
|
|
||||||
|
let bloomFilterUpdated =
|
||||||
|
await eventually(conditionTimeoutMs, bloomFilterUpdatedCondition)
|
||||||
|
# check bloom filter is updated
|
||||||
|
check:
|
||||||
|
bloomFilterUpdated
|
||||||
|
|
||||||
|
asyncTest "Waku topic-interest":
|
||||||
|
var
|
||||||
|
wakuTopicNode = setupTestNode(Waku)
|
||||||
|
wakuNode = setupTestNode(Waku)
|
||||||
|
|
||||||
|
let
|
||||||
|
topic1 = [byte 0xDA, 0xDA, 0xDA, 0xAA]
|
||||||
|
topic2 = [byte 0xD0, 0xD0, 0xD0, 0x00]
|
||||||
|
wrongTopic = [byte 0x4B, 0x1D, 0x4B, 0x1D]
|
||||||
|
|
||||||
|
wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1, topic2])
|
||||||
|
|
||||||
|
wakuNode.startListening()
|
||||||
|
await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||||
|
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
check:
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = topic1, payload = payload)
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = topic2, payload = payload)
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = wrongTopic, payload = payload)
|
||||||
|
wakuNode.protocolState(Waku).queue.items.len == 3
|
||||||
|
|
||||||
|
await eventually(conditionTimeoutMs,
|
||||||
|
proc (): bool = wakuTopicNode.protocolState(Waku).queue.items.len == 2)
|
||||||
|
|
||||||
|
asyncTest "Waku topic-interest versus bloom filter":
|
||||||
|
var
|
||||||
|
wakuTopicNode = setupTestNode(Waku)
|
||||||
|
wakuNode = setupTestNode(Waku)
|
||||||
|
|
||||||
|
let
|
||||||
|
topic1 = [byte 0xDA, 0xDA, 0xDA, 0xAA]
|
||||||
|
topic2 = [byte 0xD0, 0xD0, 0xD0, 0x00]
|
||||||
|
bloomTopic = [byte 0x4B, 0x1D, 0x4B, 0x1D]
|
||||||
|
|
||||||
|
# It was checked that the topics don't trigger false positives on the bloom.
|
||||||
|
wakuTopicNode.protocolState(Waku).config.topics = some(@[topic1, topic2])
|
||||||
|
wakuTopicNode.protocolState(Waku).config.bloom = some(toBloom([bloomTopic]))
|
||||||
|
|
||||||
|
wakuNode.startListening()
|
||||||
|
await wakuTopicNode.peerPool.connectToNode(newNode(wakuNode.toENode()))
|
||||||
|
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
check:
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = topic1, payload = payload)
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = topic2, payload = payload)
|
||||||
|
wakuNode.postMessage(ttl = safeTTL, topic = bloomTopic, payload = payload)
|
||||||
|
wakuNode.protocolState(Waku).queue.items.len == 3
|
||||||
|
await sleepAsync(waitInterval)
|
||||||
|
check:
|
||||||
|
wakuTopicNode.protocolState(Waku).queue.items.len == 2
|
|
@ -0,0 +1,116 @@
|
||||||
|
import
|
||||||
|
unittest, chronos, tables, sequtils, times,
|
||||||
|
eth/[p2p, async_utils], eth/p2p/peer_pool,
|
||||||
|
../waku/protocol/v1/[waku_protocol, waku_mail],
|
||||||
|
./test_helpers
|
||||||
|
|
||||||
|
const
|
||||||
|
transmissionTimeout = chronos.milliseconds(100)
|
||||||
|
|
||||||
|
proc waitForConnected(node: EthereumNode) {.async.} =
|
||||||
|
while node.peerPool.connectedNodes.len == 0:
|
||||||
|
await sleepAsync(chronos.milliseconds(1))
|
||||||
|
|
||||||
|
procSuite "Waku Mail Client":
|
||||||
|
var client = setupTestNode(Waku)
|
||||||
|
var simpleServer = setupTestNode(Waku)
|
||||||
|
|
||||||
|
simpleServer.startListening()
|
||||||
|
let simpleServerNode = newNode(simpleServer.toENode())
|
||||||
|
let clientNode = newNode(client.toENode())
|
||||||
|
waitFor client.peerPool.connectToNode(simpleServerNode)
|
||||||
|
require:
|
||||||
|
waitFor simpleServer.waitForConnected().withTimeout(transmissionTimeout)
|
||||||
|
|
||||||
|
asyncTest "Two peers connected":
|
||||||
|
check:
|
||||||
|
client.peerPool.connectedNodes.len() == 1
|
||||||
|
simpleServer.peerPool.connectedNodes.len() == 1
|
||||||
|
|
||||||
|
asyncTest "Mail Request and Request Complete":
|
||||||
|
let
|
||||||
|
topic = [byte 0, 0, 0, 0]
|
||||||
|
bloom = toBloom(@[topic])
|
||||||
|
lower = 0'u32
|
||||||
|
upper = epochTime().uint32
|
||||||
|
limit = 100'u32
|
||||||
|
request = MailRequest(lower: lower, upper: upper, bloom: @bloom,
|
||||||
|
limit: limit)
|
||||||
|
|
||||||
|
var symKey: SymKey
|
||||||
|
check client.setPeerTrusted(simpleServerNode.id)
|
||||||
|
var cursorFut = client.requestMail(simpleServerNode.id, request, symKey, 1)
|
||||||
|
|
||||||
|
# Simple mailserver part
|
||||||
|
let peer = simpleServer.peerPool.connectedNodes[clientNode]
|
||||||
|
var f = peer.nextMsg(Waku.p2pRequest)
|
||||||
|
require await f.withTimeout(transmissionTimeout)
|
||||||
|
let response = f.read()
|
||||||
|
let decoded = decode(response.envelope.data, symKey = some(symKey))
|
||||||
|
require decoded.isSome()
|
||||||
|
|
||||||
|
var rlp = rlpFromBytes(decoded.get().payload)
|
||||||
|
let output = rlp.read(MailRequest)
|
||||||
|
check:
|
||||||
|
output.lower == lower
|
||||||
|
output.upper == upper
|
||||||
|
output.bloom == bloom
|
||||||
|
output.limit == limit
|
||||||
|
|
||||||
|
var dummy: Hash
|
||||||
|
await peer.p2pRequestComplete(dummy, dummy, @[])
|
||||||
|
|
||||||
|
check await cursorFut.withTimeout(transmissionTimeout)
|
||||||
|
|
||||||
|
asyncTest "Mail Send":
|
||||||
|
let topic = [byte 0x12, 0x34, 0x56, 0x78]
|
||||||
|
let payload = repeat(byte 0, 10)
|
||||||
|
var f = newFuture[int]()
|
||||||
|
|
||||||
|
proc handler(msg: ReceivedMessage) =
|
||||||
|
check msg.decoded.payload == payload
|
||||||
|
f.complete(1)
|
||||||
|
|
||||||
|
let filter = subscribeFilter(client,
|
||||||
|
initFilter(topics = @[topic], allowP2P = true), handler)
|
||||||
|
|
||||||
|
check:
|
||||||
|
client.setPeerTrusted(simpleServerNode.id)
|
||||||
|
# ttl 0 to show that ttl should be ignored
|
||||||
|
# TODO: perhaps not the best way to test this, means no PoW calculation
|
||||||
|
# may be done, and not sure if that is OK?
|
||||||
|
simpleServer.postMessage(ttl = 0, topic = topic, payload = payload,
|
||||||
|
targetPeer = some(clientNode.id))
|
||||||
|
|
||||||
|
await f.withTimeout(transmissionTimeout)
|
||||||
|
|
||||||
|
client.unsubscribeFilter(filter)
|
||||||
|
|
||||||
|
asyncTest "Multiple Client Request and Complete":
|
||||||
|
var count = 5
|
||||||
|
proc customHandler(peer: Peer, envelope: Envelope)=
|
||||||
|
var envelopes: seq[Envelope]
|
||||||
|
traceAsyncErrors peer.p2pMessage(envelopes)
|
||||||
|
|
||||||
|
var cursor: seq[byte]
|
||||||
|
count = count - 1
|
||||||
|
if count == 0:
|
||||||
|
cursor = @[]
|
||||||
|
else:
|
||||||
|
cursor = @[byte count]
|
||||||
|
|
||||||
|
var dummy: Hash
|
||||||
|
traceAsyncErrors peer.p2pRequestComplete(dummy, dummy, cursor)
|
||||||
|
|
||||||
|
simpleServer.enableMailServer(customHandler)
|
||||||
|
check client.setPeerTrusted(simpleServerNode.id)
|
||||||
|
var request: MailRequest
|
||||||
|
var symKey: SymKey
|
||||||
|
let cursor =
|
||||||
|
await client.requestMail(simpleServerNode.id, request, symKey, 5)
|
||||||
|
require cursor.isSome()
|
||||||
|
check:
|
||||||
|
cursor.get().len == 0
|
||||||
|
count == 0
|
||||||
|
|
||||||
|
# TODO: Also check for received envelopes.
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 8ea2ee260207c57a4b495931508569e181bb7b93
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 68c6d27304245c948526487b37e10951acf7dbc8
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit af184ae47e20672b68d20e7cacd3b726533548e1
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 357baa52a0ea3da699c8229d97230c7f4340da92
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 6e5d570490989c753d4645ba9173ef9358d302bb
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit caab5c917ffd4a4c583dad9c59d2e1012eee9baa
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 33d70b9f378591e074838d6608a4468940137357
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5c0d0961114bcaaf3da52d5918bf0b85ef0e4ce9
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit cb695d175fd809f6262f1ebc329e7c0b1d4505f5
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit fc63983c73ba541413f12ee70a2b6f025b03efea
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 65c91f1a7b5bf83968d0bebab74d14ddb6e9a432
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 2403c33929c74f2d150f50dc8bc3a598af70661a
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9f085c33a9dc381bcf455225ccfad893b3d0361e
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 9e49b00148884a01d61478ae5d2c69b543b93ceb
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit ff028982d6fc414c17d98d4c5e6d6d3856c0c598
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e8e1f1c2cff5aebf74dff07b1cf119134267922b
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 30d0ceaba02c0b966515f98873a0404786fbf796
|
|
@ -0,0 +1,57 @@
|
||||||
|
mode = ScriptMode.Verbose
|
||||||
|
|
||||||
|
### Package
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "Status Research & Development GmbH"
|
||||||
|
description = "Waku, Private P2P Messaging for Resource-Rerestricted Devices"
|
||||||
|
license = "MIT or Apache License 2.0"
|
||||||
|
srcDir = "src"
|
||||||
|
#bin = @["build/waku"]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
requires "nim >= 1.2.0",
|
||||||
|
"chronicles",
|
||||||
|
"confutils",
|
||||||
|
"chronos",
|
||||||
|
"eth",
|
||||||
|
"json_rpc",
|
||||||
|
"libbacktrace",
|
||||||
|
"nimcrypto",
|
||||||
|
"stew",
|
||||||
|
"stint",
|
||||||
|
"metrics",
|
||||||
|
"libp2p" # For wakunode v2
|
||||||
|
|
||||||
|
### Helper functions
|
||||||
|
proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =
|
||||||
|
if not dirExists "build":
|
||||||
|
mkDir "build"
|
||||||
|
# allow something like "nim nimbus --verbosity:0 --hints:off nimbus.nims"
|
||||||
|
var extra_params = params
|
||||||
|
for i in 2..<paramCount():
|
||||||
|
extra_params &= " " & paramStr(i)
|
||||||
|
exec "nim " & lang & " --out:build/" & name & " " & extra_params & " " & srcDir & name & ".nim"
|
||||||
|
|
||||||
|
proc test(name: string, lang = "c") =
|
||||||
|
buildBinary name, "tests/", "-d:chronicles_log_level=ERROR"
|
||||||
|
exec "build/" & name
|
||||||
|
|
||||||
|
### Tasks
|
||||||
|
task test, "Run tests":
|
||||||
|
test "all_tests"
|
||||||
|
|
||||||
|
task wakunode, "Build Waku cli":
|
||||||
|
buildBinary "wakunode", "waku/node/v1/", "-d:chronicles_log_level=TRACE"
|
||||||
|
|
||||||
|
task wakusim, "Build Waku simulation tools":
|
||||||
|
buildBinary "quicksim", "waku/node/v1/", "-d:chronicles_log_level=INFO"
|
||||||
|
buildBinary "start_network", "waku/node/v1/", "-d:chronicles_log_level=DEBUG"
|
||||||
|
|
||||||
|
task protocol2, "Build the experimental Waku protocol":
|
||||||
|
buildBinary "waku_protocol", "waku/protocol/v2/", "-d:chronicles_log_level=DEBUG"
|
||||||
|
|
||||||
|
task wakunode2, "Build Experimental Waku cli":
|
||||||
|
buildBinary "wakunode", "waku/node/v2/", "-d:chronicles_log_level=TRACE"
|
||||||
|
|
||||||
|
task wakusim2, "Build Experimental Waku simulation tools":
|
||||||
|
buildBinary "quicksim", "waku/node/v2/", "-d:chronicles_log_level=INFO"
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Waku Node
|
||||||
|
|
||||||
|
TODO.
|
||||||
|
|
||||||
|
See README in `v1` folder for instructions on how to run a node.
|
|
@ -1,8 +1,9 @@
|
||||||
# Introduction
|
# Introduction
|
||||||
`wakunode` is a cli application that allows you to run a
|
`wakunode` is a cli application that allows you to run a
|
||||||
[Waku](https://github.com/vacp2p/specs/blob/master/waku.md) enabled node.
|
[Waku](https://specs.vac.dev/waku/waku.html) enabled node.
|
||||||
|
|
||||||
The application and Waku specification are still experimental and fully in flux.
|
The Waku specification is still in draft and thus this implementation will
|
||||||
|
change accordingly.
|
||||||
|
|
||||||
Additionally the original Whisper (EIP-627) protocol can also be enabled as can
|
Additionally the original Whisper (EIP-627) protocol can also be enabled as can
|
||||||
an experimental Whisper - Waku bridging option.
|
an experimental Whisper - Waku bridging option.
|
||||||
|
@ -19,11 +20,15 @@ More information on the installation of these can be found [here](https://github
|
||||||
## Build & Run
|
## Build & Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make # The first `make` invocation will update all Git submodules and prompt you to run `make` again.
|
# The first `make` invocation will update all Git submodules.
|
||||||
# It's only required once per Git clone. You'll run `make update` after each `git pull`, in the future,
|
# You'll run `make update` after each `git pull`, in the future, to keep those submodules up to date.
|
||||||
# to keep those submodules up to date.
|
|
||||||
make wakunode
|
make wakunode
|
||||||
|
|
||||||
|
# See available command line options
|
||||||
./build/wakunode --help
|
./build/wakunode --help
|
||||||
|
|
||||||
|
# Connect the client directly with the Status test fleet
|
||||||
|
./build/wakunode --log-level:debug --discovery:off --fleet:test --log-metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
# Using Metrics
|
# Using Metrics
|
||||||
|
@ -87,9 +92,9 @@ This dashboard can be found at `./waku/metrics/waku-sim-all-nodes-grafana-dashbo
|
||||||
|
|
||||||
# Spec support
|
# Spec support
|
||||||
|
|
||||||
*This section last updated April 7, 2020*
|
*This section last updated April 21, 2020*
|
||||||
|
|
||||||
This client of Waku is spec compliant with [Waku spec v0.4](https://specs.vac.dev/waku/waku.html).
|
This client of Waku is spec compliant with [Waku spec v1.0](https://specs.vac.dev/waku/waku.html).
|
||||||
|
|
||||||
It doesn't yet implement the following recommended features:
|
It doesn't yet implement the following recommended features:
|
||||||
- No support for rate limiting
|
- No support for rate limiting
|
|
@ -1,6 +1,5 @@
|
||||||
import
|
import
|
||||||
confutils/defs, chronicles, chronos,
|
confutils/defs, chronicles, chronos, eth/keys
|
||||||
eth/keys, eth/p2p/rlpx_protocols/waku_protocol
|
|
||||||
|
|
||||||
type
|
type
|
||||||
Fleet* = enum
|
Fleet* = enum
|
|
@ -9,8 +9,8 @@ RUN apt-get -qq update \
|
||||||
ARG GIT_REVISION
|
ARG GIT_REVISION
|
||||||
|
|
||||||
RUN cd /root \
|
RUN cd /root \
|
||||||
&& git clone https://github.com/status-im/nimbus.git \
|
&& git clone https://github.com/status-im/nim-waku.git \
|
||||||
&& cd nimbus \
|
&& cd nim-waku \
|
||||||
&& git reset --hard ${GIT_REVISION} \
|
&& git reset --hard ${GIT_REVISION} \
|
||||||
&& { make &>/dev/null || true; } \
|
&& { make &>/dev/null || true; } \
|
||||||
&& make -j$(nproc) update \
|
&& make -j$(nproc) update \
|
||||||
|
@ -28,7 +28,7 @@ RUN apt-get -qq update \
|
||||||
&& apt-get -qq clean \
|
&& apt-get -qq clean \
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
COPY --from=build /root/nimbus/build/wakunode /usr/bin/wakunode
|
COPY --from=build /root/nim-waku/build/wakunode /usr/bin/wakunode
|
||||||
|
|
||||||
MAINTAINER Kim De Mey <kimdemey@status.im>
|
MAINTAINER Kim De Mey <kimdemey@status.im>
|
||||||
LABEL description="Wakunode: Waku and Whisper client"
|
LABEL description="Wakunode: Waku and Whisper client"
|
|
@ -0,0 +1,24 @@
|
||||||
|
global:
|
||||||
|
scrape_interval: 1s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: "wakusim"
|
||||||
|
static_configs:
|
||||||
|
- targets: ['127.0.0.1:8010']
|
||||||
|
labels:
|
||||||
|
node: '0'
|
||||||
|
- targets: ['127.0.0.1:8011']
|
||||||
|
labels:
|
||||||
|
node: '1'
|
||||||
|
- targets: ['127.0.0.1:8012']
|
||||||
|
labels:
|
||||||
|
node: '2'
|
||||||
|
- targets: ['127.0.0.1:8013']
|
||||||
|
labels:
|
||||||
|
node: '3'
|
||||||
|
- targets: ['127.0.0.1:8008']
|
||||||
|
labels:
|
||||||
|
node: '4'
|
||||||
|
- targets: ['127.0.0.1:8009']
|
||||||
|
labels:
|
||||||
|
node: '5'
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
||||||
import
|
import
|
||||||
os, strformat, chronicles, json_rpc/[rpcclient, rpcserver], nimcrypto/sysrand,
|
os, strformat, chronicles, json_rpc/[rpcclient, rpcserver], nimcrypto/sysrand,
|
||||||
eth/common as eth_common, eth/keys, eth/p2p/rlpx_protocols/waku_protocol,
|
eth/common as eth_common, eth/keys,
|
||||||
../../vendor/nimbus/nimbus/rpc/[hexstrings, rpc_types, waku],
|
../../protocol/v1/waku_protocol, ./rpc/[hexstrings, rpc_types],
|
||||||
options as what # TODO: Huh? Redefinition?
|
options as what # TODO: Huh? Redefinition?
|
||||||
|
|
||||||
from os import DirSep
|
from os import DirSep
|
||||||
|
@ -33,18 +33,18 @@ let
|
||||||
symKey = "0x0000000000000000000000000000000000000000000000000000000000000001"
|
symKey = "0x0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
topics = generateTopics()
|
topics = generateTopics()
|
||||||
symKeyID = waitFor lightNode.waku_addSymKey(symKey)
|
symKeyID = waitFor lightNode.waku_addSymKey(symKey)
|
||||||
options = WhisperFilterOptions(symKeyID: some(symKeyID),
|
options = WakuFilterOptions(symKeyID: some(symKeyID),
|
||||||
topics: some(topics))
|
topics: some(topics))
|
||||||
filterID = waitFor lightNode.waku_newMessageFilter(options)
|
filterID = waitFor lightNode.waku_newMessageFilter(options)
|
||||||
|
|
||||||
symKeyID2 = waitFor lightNode2.waku_addSymKey(symKey)
|
symKeyID2 = waitFor lightNode2.waku_addSymKey(symKey)
|
||||||
options2 = WhisperFilterOptions(symKeyID: some(symKeyID2),
|
options2 = WakuFilterOptions(symKeyID: some(symKeyID2),
|
||||||
topics: some(topics))
|
topics: some(topics))
|
||||||
filterID2 = waitFor lightNode2.waku_newMessageFilter(options2)
|
filterID2 = waitFor lightNode2.waku_newMessageFilter(options2)
|
||||||
|
|
||||||
symkeyID3 = waitFor trafficNode.waku_addSymKey(symKey)
|
symkeyID3 = waitFor trafficNode.waku_addSymKey(symKey)
|
||||||
|
|
||||||
var message = WhisperPostMessage(symKeyID: some(symkeyID3),
|
var message = WakuPostMessage(symKeyID: some(symkeyID3),
|
||||||
ttl: 30,
|
ttl: 30,
|
||||||
topic: some(topics[0]),
|
topic: some(topics[0]),
|
||||||
payload: "0x45879632".HexDataStr,
|
payload: "0x45879632".HexDataStr,
|
|
@ -0,0 +1,225 @@
|
||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
# at your option.
|
||||||
|
# This file may not be copied, modified, or distributed except according to
|
||||||
|
# those terms.
|
||||||
|
|
||||||
|
## This module implements the Ethereum hexadecimal string formats for JSON
|
||||||
|
## See: https://github.com/ethereum/wiki/wiki/JSON-RPC#hex-value-encoding
|
||||||
|
|
||||||
|
#[
|
||||||
|
Note:
|
||||||
|
The following types are converted to hex strings when marshalled to JSON:
|
||||||
|
* Hash256
|
||||||
|
* UInt256
|
||||||
|
* seq[byte]
|
||||||
|
* openArray[seq]
|
||||||
|
* PublicKey
|
||||||
|
* PrivateKey
|
||||||
|
* SymKey
|
||||||
|
* Topic
|
||||||
|
* Bytes
|
||||||
|
]#
|
||||||
|
|
||||||
|
import
|
||||||
|
stint, stew/byteutils, eth/[keys, rlp], eth/common/eth_types,
|
||||||
|
../../../protocol/v1/waku_protocol
|
||||||
|
|
||||||
|
type
|
||||||
|
HexDataStr* = distinct string
|
||||||
|
Identifier* = distinct string # 32 bytes, no 0x prefix!
|
||||||
|
HexStrings = HexDataStr | Identifier
|
||||||
|
|
||||||
|
# Hex validation
|
||||||
|
|
||||||
|
template hasHexHeader(value: string): bool =
|
||||||
|
if value.len >= 2 and value[0] == '0' and value[1] in {'x', 'X'}: true
|
||||||
|
else: false
|
||||||
|
|
||||||
|
template isHexChar(c: char): bool =
|
||||||
|
if c notin {'0'..'9'} and
|
||||||
|
c notin {'a'..'f'} and
|
||||||
|
c notin {'A'..'F'}: false
|
||||||
|
else: true
|
||||||
|
|
||||||
|
func isValidHexQuantity*(value: string): bool =
|
||||||
|
if not value.hasHexHeader:
|
||||||
|
return false
|
||||||
|
# No leading zeros (but allow 0x0)
|
||||||
|
if value.len < 3 or (value.len > 3 and value[2] == '0'): return false
|
||||||
|
for i in 2 ..< value.len:
|
||||||
|
let c = value[i]
|
||||||
|
if not c.isHexChar:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func isValidHexData*(value: string, header = true): bool =
|
||||||
|
if header and not value.hasHexHeader:
|
||||||
|
return false
|
||||||
|
# Must be even number of digits
|
||||||
|
if value.len mod 2 != 0: return false
|
||||||
|
# Leading zeros are allowed
|
||||||
|
for i in 2 ..< value.len:
|
||||||
|
let c = value[i]
|
||||||
|
if not c.isHexChar:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
template isValidHexData(value: string, hexLen: int, header = true): bool =
|
||||||
|
value.len == hexLen and value.isValidHexData(header)
|
||||||
|
|
||||||
|
func isValidIdentifier*(value: string): bool =
|
||||||
|
# 32 bytes for Whisper ID, no 0x prefix
|
||||||
|
result = value.isValidHexData(64, false)
|
||||||
|
|
||||||
|
func isValidPublicKey*(value: string): bool =
|
||||||
|
# 65 bytes for Public Key plus 1 byte for 0x prefix
|
||||||
|
result = value.isValidHexData(132)
|
||||||
|
|
||||||
|
func isValidPrivateKey*(value: string): bool =
|
||||||
|
# 32 bytes for Private Key plus 1 byte for 0x prefix
|
||||||
|
result = value.isValidHexData(66)
|
||||||
|
|
||||||
|
func isValidSymKey*(value: string): bool =
|
||||||
|
# 32 bytes for Private Key plus 1 byte for 0x prefix
|
||||||
|
result = value.isValidHexData(66)
|
||||||
|
|
||||||
|
func isValidHash256*(value: string): bool =
|
||||||
|
# 32 bytes for Hash256 plus 1 byte for 0x prefix
|
||||||
|
result = value.isValidHexData(66)
|
||||||
|
|
||||||
|
func isValidTopic*(value: string): bool =
|
||||||
|
# 4 bytes for Topic plus 1 byte for 0x prefix
|
||||||
|
result = value.isValidHexData(10)
|
||||||
|
|
||||||
|
const
|
||||||
|
SInvalidData = "Invalid hex data format for Ethereum"
|
||||||
|
|
||||||
|
proc validateHexData*(value: string) {.inline.} =
|
||||||
|
if unlikely(not value.isValidHexData):
|
||||||
|
raise newException(ValueError, SInvalidData & ": " & value)
|
||||||
|
|
||||||
|
# Initialisation
|
||||||
|
|
||||||
|
proc hexDataStr*(value: string): HexDataStr {.inline.} =
|
||||||
|
value.validateHexData
|
||||||
|
result = value.HexDataStr
|
||||||
|
|
||||||
|
# Converters for use in RPC
|
||||||
|
|
||||||
|
import json
|
||||||
|
from json_rpc/rpcserver import expect
|
||||||
|
|
||||||
|
proc `%`*(value: HexStrings): JsonNode =
|
||||||
|
result = %(value.string)
|
||||||
|
|
||||||
|
# Overloads to support expected representation of hex data
|
||||||
|
|
||||||
|
proc `%`*(value: Hash256): JsonNode =
|
||||||
|
#result = %("0x" & $value) # More clean but no lowercase :(
|
||||||
|
result = %("0x" & value.data.toHex)
|
||||||
|
|
||||||
|
proc `%`*(value: UInt256): JsonNode =
|
||||||
|
result = %("0x" & value.toString(16))
|
||||||
|
|
||||||
|
proc `%`*(value: PublicKey): JsonNode =
|
||||||
|
result = %("0x04" & $value)
|
||||||
|
|
||||||
|
proc `%`*(value: PrivateKey): JsonNode =
|
||||||
|
result = %("0x" & $value)
|
||||||
|
|
||||||
|
proc `%`*(value: SymKey): JsonNode =
|
||||||
|
result = %("0x" & value.toHex)
|
||||||
|
|
||||||
|
proc `%`*(value: waku_protocol.Topic): JsonNode =
|
||||||
|
result = %("0x" & value.toHex)
|
||||||
|
|
||||||
|
proc `%`*(value: seq[byte]): JsonNode =
|
||||||
|
result = %("0x" & value.toHex)
|
||||||
|
|
||||||
|
# Helpers for the fromJson procs
|
||||||
|
|
||||||
|
proc toPublicKey*(key: string): PublicKey {.inline.} =
|
||||||
|
result = PublicKey.fromHex(key[4 .. ^1]).tryGet()
|
||||||
|
|
||||||
|
proc toPrivateKey*(key: string): PrivateKey {.inline.} =
|
||||||
|
result = PrivateKey.fromHex(key[2 .. ^1]).tryGet()
|
||||||
|
|
||||||
|
proc toSymKey*(key: string): SymKey {.inline.} =
|
||||||
|
hexToByteArray(key[2 .. ^1], result)
|
||||||
|
|
||||||
|
proc toTopic*(topic: string): waku_protocol.Topic {.inline.} =
|
||||||
|
hexToByteArray(topic[2 .. ^1], result)
|
||||||
|
|
||||||
|
# Marshalling from JSON to Nim types that includes format checking
|
||||||
|
|
||||||
|
func invalidMsg(name: string): string = "When marshalling from JSON, parameter \"" & name & "\" is not valid"
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var HexDataStr) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidHexData:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as Ethereum data \"" & hexStr & "\"")
|
||||||
|
result = hexStr.hexDataStr
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var Identifier) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidIdentifier:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a identifier \"" & hexStr & "\"")
|
||||||
|
result = hexStr.Identifier
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var UInt256) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not (hexStr.len <= 66 and hexStr.isValidHexQuantity):
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a UInt256 \"" & hexStr & "\"")
|
||||||
|
result = readUintBE[256](hexToPaddedByteArray[32](hexStr))
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var PublicKey) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidPublicKey:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a public key \"" & hexStr & "\"")
|
||||||
|
result = hexStr.toPublicKey
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var PrivateKey) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidPrivateKey:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a private key \"" & hexStr & "\"")
|
||||||
|
result = hexStr.toPrivateKey
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var SymKey) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidSymKey:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a symmetric key \"" & hexStr & "\"")
|
||||||
|
result = toSymKey(hexStr)
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var waku_protocol.Topic) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidTopic:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a topic \"" & hexStr & "\"")
|
||||||
|
result = toTopic(hexStr)
|
||||||
|
|
||||||
|
# Following procs currently required only for testing, the `createRpcSigs` macro
|
||||||
|
# requires it as it will convert the JSON results back to the original Nim
|
||||||
|
# types, but it needs the `fromJson` calls for those specific Nim types to do so
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var seq[byte]) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidHexData:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a hex data \"" & hexStr & "\"")
|
||||||
|
result = hexToSeqByte(hexStr)
|
||||||
|
|
||||||
|
proc fromJson*(n: JsonNode, argName: string, result: var Hash256) =
|
||||||
|
n.kind.expect(JString, argName)
|
||||||
|
let hexStr = n.getStr()
|
||||||
|
if not hexStr.isValidHash256:
|
||||||
|
raise newException(ValueError, invalidMsg(argName) & " as a Hash256 \"" & hexStr & "\"")
|
||||||
|
hexToByteArray(hexStr, result.data)
|
|
@ -0,0 +1,22 @@
|
||||||
|
#
|
||||||
|
# Nimbus
|
||||||
|
# (c) Copyright 2019
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# Licensed under either of
|
||||||
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# MIT license (LICENSE-MIT)
|
||||||
|
|
||||||
|
import tables, eth/keys, eth/p2p/rlpx_protocols/whisper/whisper_types
|
||||||
|
|
||||||
|
type
|
||||||
|
KeyStorage* = ref object
|
||||||
|
asymKeys*: Table[string, KeyPair]
|
||||||
|
symKeys*: Table[string, SymKey]
|
||||||
|
|
||||||
|
KeyGenerationError* = object of CatchableError
|
||||||
|
|
||||||
|
proc newKeyStorage*(): KeyStorage =
|
||||||
|
new(result)
|
||||||
|
result.asymKeys = initTable[string, KeyPair]()
|
||||||
|
result.symKeys = initTable[string, SymKey]()
|
|
@ -0,0 +1,58 @@
|
||||||
|
import
|
||||||
|
hexstrings, options, eth/[keys, rlp],
|
||||||
|
../../../protocol/v1/waku_protocol
|
||||||
|
|
||||||
|
#[
|
||||||
|
Notes:
|
||||||
|
* Some of the types suppose 'null' when there is no appropriate value.
|
||||||
|
To allow for this, you can use Option[T] or use refs so the JSON transform can convert to `JNull`.
|
||||||
|
* Parameter objects from users must have their data verified so will use EthAddressStr instead of EthAddres, for example
|
||||||
|
* Objects returned to the user can use native Waku types, where hexstrings provides converters to hex strings.
|
||||||
|
This is because returned arrays in JSON is
|
||||||
|
a) not an efficient use of space
|
||||||
|
b) not the format the user expects (for example addresses are expected to be hex strings prefixed by "0x")
|
||||||
|
]#
|
||||||
|
|
||||||
|
type
|
||||||
|
WakuInfo* = object
|
||||||
|
# Returned to user
|
||||||
|
minPow*: float64 # Current minimum PoW requirement.
|
||||||
|
# TODO: may be uint32
|
||||||
|
maxMessageSize*: uint64 # Current message size limit in bytes.
|
||||||
|
memory*: int # Memory size of the floating messages in bytes.
|
||||||
|
messages*: int # Number of floating messages.
|
||||||
|
|
||||||
|
WakuFilterOptions* = object
|
||||||
|
# Parameter from user
|
||||||
|
symKeyID*: Option[Identifier] # ID of symmetric key for message decryption.
|
||||||
|
privateKeyID*: Option[Identifier] # ID of private (asymmetric) key for message decryption.
|
||||||
|
sig*: Option[PublicKey] # (Optional) Public key of the signature.
|
||||||
|
minPow*: Option[float64] # (Optional) Minimal PoW requirement for incoming messages.
|
||||||
|
topics*: Option[seq[waku_protocol.Topic]] # (Optional when asym key): Array of possible topics (or partial topics).
|
||||||
|
allowP2P*: Option[bool] # (Optional) Indicates if this filter allows processing of direct peer-to-peer messages.
|
||||||
|
|
||||||
|
WakuFilterMessage* = object
|
||||||
|
# Returned to user
|
||||||
|
sig*: Option[PublicKey] # Public key who signed this message.
|
||||||
|
recipientPublicKey*: Option[PublicKey] # The recipients public key.
|
||||||
|
ttl*: uint64 # Time-to-live in seconds.
|
||||||
|
timestamp*: uint64 # Unix timestamp of the message generation.
|
||||||
|
topic*: waku_protocol.Topic # 4 Bytes: Message topic.
|
||||||
|
payload*: seq[byte] # Decrypted payload.
|
||||||
|
padding*: seq[byte] # (Optional) Padding (byte array of arbitrary length).
|
||||||
|
pow*: float64 # Proof of work value.
|
||||||
|
hash*: Hash # Hash of the enveloped message.
|
||||||
|
|
||||||
|
WakuPostMessage* = object
|
||||||
|
# Parameter from user
|
||||||
|
symKeyID*: Option[Identifier] # ID of symmetric key for message encryption.
|
||||||
|
pubKey*: Option[PublicKey] # Public key for message encryption.
|
||||||
|
sig*: Option[Identifier] # (Optional) ID of the signing key.
|
||||||
|
ttl*: uint64 # Time-to-live in seconds.
|
||||||
|
topic*: Option[waku_protocol.Topic] # Message topic (mandatory when key is symmetric).
|
||||||
|
payload*: HexDataStr # Payload to be encrypted.
|
||||||
|
padding*: Option[HexDataStr] # (Optional) Padding (byte array of arbitrary length).
|
||||||
|
powTime*: float64 # Maximal time in seconds to be spent on proof of work.
|
||||||
|
powTarget*: float64 # Minimal PoW target required for this message.
|
||||||
|
# TODO: EnodeStr
|
||||||
|
targetPeer*: Option[string] # (Optional) Peer ID (for peer-to-peer message only).
|
|
@ -0,0 +1,364 @@
|
||||||
|
import
|
||||||
|
json_rpc/rpcserver, tables, options, sequtils,
|
||||||
|
eth/[common, rlp, keys, p2p],
|
||||||
|
nimcrypto/[sysrand, hmac, sha2, pbkdf2],
|
||||||
|
rpc_types, hexstrings, key_storage,
|
||||||
|
../../../protocol/v1/waku_protocol
|
||||||
|
|
||||||
|
from stew/byteutils import hexToSeqByte, hexToByteArray
|
||||||
|
|
||||||
|
# Blatant copy of Whisper RPC but for the Waku protocol
|
||||||
|
|
||||||
|
proc setupWakuRPC*(node: EthereumNode, keys: KeyStorage, rpcsrv: RpcServer) =
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_version") do() -> string:
|
||||||
|
## Returns string of the current Waku protocol version.
|
||||||
|
result = wakuVersionStr
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_info") do() -> WakuInfo:
|
||||||
|
## Returns diagnostic information about the Waku node.
|
||||||
|
let config = node.protocolState(Waku).config
|
||||||
|
result = WakuInfo(minPow: config.powRequirement,
|
||||||
|
maxMessageSize: config.maxMsgSize,
|
||||||
|
memory: 0,
|
||||||
|
messages: 0)
|
||||||
|
|
||||||
|
# TODO: uint32 instead of uint64 is OK here, but needs to be added in json_rpc
|
||||||
|
rpcsrv.rpc("waku_setMaxMessageSize") do(size: uint64) -> bool:
|
||||||
|
## Sets the maximal message size allowed by this node.
|
||||||
|
## Incoming and outgoing messages with a larger size will be rejected.
|
||||||
|
## Waku message size can never exceed the limit imposed by the underlying
|
||||||
|
## P2P protocol (10 Mb).
|
||||||
|
##
|
||||||
|
## size: Message size in bytes.
|
||||||
|
##
|
||||||
|
## Returns true on success and an error on failure.
|
||||||
|
result = node.setMaxMessageSize(size.uint32)
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Invalid size")
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_setMinPoW") do(pow: float) -> bool:
|
||||||
|
## Sets the minimal PoW required by this node.
|
||||||
|
##
|
||||||
|
## pow: The new PoW requirement.
|
||||||
|
##
|
||||||
|
## Returns true on success and an error on failure.
|
||||||
|
# Note: `setPowRequirement` does not raise on failures of sending the update
|
||||||
|
# to the peers. Hence in theory this should not causes errors.
|
||||||
|
await node.setPowRequirement(pow)
|
||||||
|
result = true
|
||||||
|
|
||||||
|
# TODO: change string in to ENodeStr with extra checks
|
||||||
|
rpcsrv.rpc("waku_markTrustedPeer") do(enode: string) -> bool:
|
||||||
|
## Marks specific peer trusted, which will allow it to send historic
|
||||||
|
## (expired) messages.
|
||||||
|
## Note: This function is not adding new nodes, the node needs to exists as
|
||||||
|
## a peer.
|
||||||
|
##
|
||||||
|
## enode: Enode of the trusted peer.
|
||||||
|
##
|
||||||
|
## Returns true on success and an error on failure.
|
||||||
|
# TODO: It will now require an enode://pubkey@ip:port uri
|
||||||
|
# could also accept only the pubkey (like geth)?
|
||||||
|
let peerNode = newNode(enode)
|
||||||
|
result = node.setPeerTrusted(peerNode.id)
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Not a peer")
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_newKeyPair") do() -> Identifier:
|
||||||
|
## Generates a new public and private key pair for message decryption and
|
||||||
|
## encryption.
|
||||||
|
##
|
||||||
|
## Returns key identifier on success and an error on failure.
|
||||||
|
result = generateRandomID().Identifier
|
||||||
|
keys.asymKeys.add(result.string, KeyPair.random().tryGet())
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_addPrivateKey") do(key: PrivateKey) -> Identifier:
|
||||||
|
## Stores the key pair, and returns its ID.
|
||||||
|
##
|
||||||
|
## key: Private key as hex bytes.
|
||||||
|
##
|
||||||
|
## Returns key identifier on success and an error on failure.
|
||||||
|
result = generateRandomID().Identifier
|
||||||
|
|
||||||
|
keys.asymKeys.add(result.string, key.toKeyPair().tryGet())
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_deleteKeyPair") do(id: Identifier) -> bool:
|
||||||
|
## Deletes the specifies key if it exists.
|
||||||
|
##
|
||||||
|
## id: Identifier of key pair
|
||||||
|
##
|
||||||
|
## Returns true on success and an error on failure.
|
||||||
|
var unneeded: KeyPair
|
||||||
|
result = keys.asymKeys.take(id.string, unneeded)
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Invalid key id")
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_hasKeyPair") do(id: Identifier) -> bool:
|
||||||
|
## Checks if the Waku node has a private key of a key pair matching the
|
||||||
|
## given ID.
|
||||||
|
##
|
||||||
|
## id: Identifier of key pair
|
||||||
|
##
|
||||||
|
## Returns (true or false) on success and an error on failure.
|
||||||
|
result = keys.asymkeys.hasKey(id.string)
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_getPublicKey") do(id: Identifier) -> PublicKey:
|
||||||
|
## Returns the public key for identity ID.
|
||||||
|
##
|
||||||
|
## id: Identifier of key pair
|
||||||
|
##
|
||||||
|
## Returns public key on success and an error on failure.
|
||||||
|
# Note: key not found exception as error in case not existing
|
||||||
|
result = keys.asymkeys[id.string].pubkey
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_getPrivateKey") do(id: Identifier) -> PrivateKey:
|
||||||
|
## Returns the private key for identity ID.
|
||||||
|
##
|
||||||
|
## id: Identifier of key pair
|
||||||
|
##
|
||||||
|
## Returns private key on success and an error on failure.
|
||||||
|
# Note: key not found exception as error in case not existing
|
||||||
|
result = keys.asymkeys[id.string].seckey
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_newSymKey") do() -> Identifier:
|
||||||
|
## Generates a random symmetric key and stores it under an ID, which is then
|
||||||
|
## returned. Can be used encrypting and decrypting messages where the key is
|
||||||
|
## known to both parties.
|
||||||
|
##
|
||||||
|
## Returns key identifier on success and an error on failure.
|
||||||
|
result = generateRandomID().Identifier
|
||||||
|
var key: SymKey
|
||||||
|
if randomBytes(key) != key.len:
|
||||||
|
raise newException(KeyGenerationError, "Failed generating key")
|
||||||
|
|
||||||
|
keys.symKeys.add(result.string, key)
|
||||||
|
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_addSymKey") do(key: SymKey) -> Identifier:
|
||||||
|
## Stores the key, and returns its ID.
|
||||||
|
##
|
||||||
|
## key: The raw key for symmetric encryption as hex bytes.
|
||||||
|
##
|
||||||
|
## Returns key identifier on success and an error on failure.
|
||||||
|
result = generateRandomID().Identifier
|
||||||
|
|
||||||
|
keys.symKeys.add(result.string, key)
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_generateSymKeyFromPassword") do(password: string) -> Identifier:
|
||||||
|
## Generates the key from password, stores it, and returns its ID.
|
||||||
|
##
|
||||||
|
## password: Password.
|
||||||
|
##
|
||||||
|
## Returns key identifier on success and an error on failure.
|
||||||
|
## Warning: an empty string is used as salt because the shh RPC API does not
|
||||||
|
## allow for passing a salt. A very good password is necessary (calculate
|
||||||
|
## yourself what that means :))
|
||||||
|
var ctx: HMAC[sha256]
|
||||||
|
var symKey: SymKey
|
||||||
|
if pbkdf2(ctx, password, "", 65356, symKey) != sizeof(SymKey):
|
||||||
|
raise newException(KeyGenerationError, "Failed generating key")
|
||||||
|
|
||||||
|
result = generateRandomID().Identifier
|
||||||
|
keys.symKeys.add(result.string, symKey)
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_hasSymKey") do(id: Identifier) -> bool:
|
||||||
|
## Returns true if there is a key associated with the name string.
|
||||||
|
## Otherwise, returns false.
|
||||||
|
##
|
||||||
|
## id: Identifier of key.
|
||||||
|
##
|
||||||
|
## Returns (true or false) on success and an error on failure.
|
||||||
|
result = keys.symkeys.hasKey(id.string)
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_getSymKey") do(id: Identifier) -> SymKey:
|
||||||
|
## Returns the symmetric key associated with the given ID.
|
||||||
|
##
|
||||||
|
## id: Identifier of key.
|
||||||
|
##
|
||||||
|
## Returns Raw key on success and an error on failure.
|
||||||
|
# Note: key not found exception as error in case not existing
|
||||||
|
result = keys.symkeys[id.string]
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_deleteSymKey") do(id: Identifier) -> bool:
|
||||||
|
## Deletes the key associated with the name string if it exists.
|
||||||
|
##
|
||||||
|
## id: Identifier of key.
|
||||||
|
##
|
||||||
|
## Returns (true or false) on success and an error on failure.
|
||||||
|
var unneeded: SymKey
|
||||||
|
result = keys.symKeys.take(id.string, unneeded)
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Invalid key id")
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_subscribe") do(id: string,
|
||||||
|
options: WakuFilterOptions) -> Identifier:
|
||||||
|
## Creates and registers a new subscription to receive notifications for
|
||||||
|
## inbound Waku messages. Returns the ID of the newly created
|
||||||
|
## subscription.
|
||||||
|
##
|
||||||
|
## id: identifier of function call. In case of Waku must contain the
|
||||||
|
## value "messages".
|
||||||
|
## options: WakuFilterOptions
|
||||||
|
##
|
||||||
|
## Returns the subscription ID on success, the error on failure.
|
||||||
|
|
||||||
|
# TODO: implement subscriptions, only for WS & IPC?
|
||||||
|
discard
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_unsubscribe") do(id: Identifier) -> bool:
|
||||||
|
## Cancels and removes an existing subscription.
|
||||||
|
##
|
||||||
|
## id: Subscription identifier
|
||||||
|
##
|
||||||
|
## Returns true on success, the error on failure
|
||||||
|
result = node.unsubscribeFilter(id.string)
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Invalid filter id")
|
||||||
|
|
||||||
|
proc validateOptions[T,U,V](asym: Option[T], sym: Option[U], topic: Option[V]) =
|
||||||
|
if (asym.isSome() and sym.isSome()) or (asym.isNone() and sym.isNone()):
|
||||||
|
raise newException(ValueError,
|
||||||
|
"Either privateKeyID/pubKey or symKeyID must be present")
|
||||||
|
if asym.isNone() and topic.isNone():
|
||||||
|
raise newException(ValueError, "Topic mandatory with symmetric key")
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_newMessageFilter") do(options: WakuFilterOptions) -> Identifier:
|
||||||
|
## Create a new filter within the node. This filter can be used to poll for
|
||||||
|
## new messages that match the set of criteria.
|
||||||
|
##
|
||||||
|
## options: WakuFilterOptions
|
||||||
|
##
|
||||||
|
## Returns filter identifier on success, error on failure
|
||||||
|
|
||||||
|
# Check if either symKeyID or privateKeyID is present, and not both
|
||||||
|
# Check if there are Topics when symmetric key is used
|
||||||
|
validateOptions(options.privateKeyID, options.symKeyID, options.topics)
|
||||||
|
|
||||||
|
var
|
||||||
|
src: Option[PublicKey]
|
||||||
|
privateKey: Option[PrivateKey]
|
||||||
|
symKey: Option[SymKey]
|
||||||
|
topics: seq[waku_protocol.Topic]
|
||||||
|
powReq: float64
|
||||||
|
allowP2P: bool
|
||||||
|
|
||||||
|
src = options.sig
|
||||||
|
|
||||||
|
if options.privateKeyID.isSome():
|
||||||
|
privateKey = some(keys.asymKeys[options.privateKeyID.get().string].seckey)
|
||||||
|
|
||||||
|
if options.symKeyID.isSome():
|
||||||
|
symKey= some(keys.symKeys[options.symKeyID.get().string])
|
||||||
|
|
||||||
|
if options.minPow.isSome():
|
||||||
|
powReq = options.minPow.get()
|
||||||
|
|
||||||
|
if options.topics.isSome():
|
||||||
|
topics = options.topics.get()
|
||||||
|
|
||||||
|
if options.allowP2P.isSome():
|
||||||
|
allowP2P = options.allowP2P.get()
|
||||||
|
|
||||||
|
let filter = initFilter(src, privateKey, symKey, topics, powReq, allowP2P)
|
||||||
|
result = node.subscribeFilter(filter).Identifier
|
||||||
|
|
||||||
|
# TODO: Should we do this here "automatically" or separate it in another
|
||||||
|
# RPC call? Is there a use case for that?
|
||||||
|
# Same could be said about bloomfilter, except that there is a use case
|
||||||
|
# there to have a full node no matter what message filters.
|
||||||
|
# Could also be moved to waku_protocol.nim
|
||||||
|
let config = node.protocolState(Waku).config
|
||||||
|
if config.topics.isSome():
|
||||||
|
try:
|
||||||
|
# TODO: an addTopics call would probably be more useful
|
||||||
|
let result = await node.setTopicInterest(config.topics.get().concat(filter.topics))
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Too many topics")
|
||||||
|
except CatchableError:
|
||||||
|
trace "setTopics error occured"
|
||||||
|
elif config.isLightNode:
|
||||||
|
try:
|
||||||
|
await node.setBloomFilter(node.filtersToBloom())
|
||||||
|
except CatchableError:
|
||||||
|
trace "setBloomFilter error occured"
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_deleteMessageFilter") do(id: Identifier) -> bool:
|
||||||
|
## Uninstall a message filter in the node.
|
||||||
|
##
|
||||||
|
## id: Filter identifier as returned when the filter was created.
|
||||||
|
##
|
||||||
|
## Returns true on success, error on failure.
|
||||||
|
result = node.unsubscribeFilter(id.string)
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Invalid filter id")
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_getFilterMessages") do(id: Identifier) -> seq[WakuFilterMessage]:
|
||||||
|
## Retrieve messages that match the filter criteria and are received between
|
||||||
|
## the last time this function was called and now.
|
||||||
|
##
|
||||||
|
## id: ID of filter that was created with `waku_newMessageFilter`.
|
||||||
|
##
|
||||||
|
## Returns array of messages on success and an error on failure.
|
||||||
|
let messages = node.getFilterMessages(id.string)
|
||||||
|
for msg in messages:
|
||||||
|
result.add WakuFilterMessage(
|
||||||
|
sig: msg.decoded.src,
|
||||||
|
recipientPublicKey: msg.dst,
|
||||||
|
ttl: msg.ttl,
|
||||||
|
topic: msg.topic,
|
||||||
|
timestamp: msg.timestamp,
|
||||||
|
payload: msg.decoded.payload,
|
||||||
|
# Note: waku_protocol padding is an Option as there is the
|
||||||
|
# possibility of 0 padding in case of custom padding.
|
||||||
|
padding: msg.decoded.padding.get(@[]),
|
||||||
|
pow: msg.pow,
|
||||||
|
hash: msg.hash)
|
||||||
|
|
||||||
|
rpcsrv.rpc("waku_post") do(message: WakuPostMessage) -> bool:
|
||||||
|
## Creates a Waku message and injects it into the network for
|
||||||
|
## distribution.
|
||||||
|
##
|
||||||
|
## message: Waku message to post.
|
||||||
|
##
|
||||||
|
## Returns true on success and an error on failure.
|
||||||
|
|
||||||
|
# Check if either symKeyID or pubKey is present, and not both
|
||||||
|
# Check if there is a Topic when symmetric key is used
|
||||||
|
validateOptions(message.pubKey, message.symKeyID, message.topic)
|
||||||
|
|
||||||
|
var
|
||||||
|
sigPrivKey: Option[PrivateKey]
|
||||||
|
symKey: Option[SymKey]
|
||||||
|
topic: waku_protocol.Topic
|
||||||
|
padding: Option[seq[byte]]
|
||||||
|
targetPeer: Option[NodeId]
|
||||||
|
|
||||||
|
if message.sig.isSome():
|
||||||
|
sigPrivKey = some(keys.asymKeys[message.sig.get().string].seckey)
|
||||||
|
|
||||||
|
if message.symKeyID.isSome():
|
||||||
|
symKey = some(keys.symKeys[message.symKeyID.get().string])
|
||||||
|
|
||||||
|
# Note: If no topic it will be defaulted to 0x00000000
|
||||||
|
if message.topic.isSome():
|
||||||
|
topic = message.topic.get()
|
||||||
|
|
||||||
|
if message.padding.isSome():
|
||||||
|
padding = some(hexToSeqByte(message.padding.get().string))
|
||||||
|
|
||||||
|
if message.targetPeer.isSome():
|
||||||
|
targetPeer = some(newNode(message.targetPeer.get()).id)
|
||||||
|
|
||||||
|
result = node.postMessage(message.pubKey,
|
||||||
|
symKey,
|
||||||
|
sigPrivKey,
|
||||||
|
ttl = message.ttl.uint32,
|
||||||
|
topic = topic,
|
||||||
|
payload = hexToSeqByte(message.payload.string),
|
||||||
|
padding = padding,
|
||||||
|
powTime = message.powTime,
|
||||||
|
powTarget = message.powTarget,
|
||||||
|
targetPeer = targetPeer)
|
||||||
|
if not result:
|
||||||
|
raise newException(ValueError, "Message could not be posted")
|
|
@ -1,5 +1,5 @@
|
||||||
proc waku_version(): string
|
proc waku_version(): string
|
||||||
proc waku_info(): WhisperInfo
|
proc waku_info(): WakuInfo
|
||||||
proc waku_setMaxMessageSize(size: uint64): bool
|
proc waku_setMaxMessageSize(size: uint64): bool
|
||||||
proc waku_setMinPoW(pow: float): bool
|
proc waku_setMinPoW(pow: float): bool
|
||||||
proc waku_markTrustedPeer(enode: string): bool
|
proc waku_markTrustedPeer(enode: string): bool
|
||||||
|
@ -18,10 +18,10 @@ proc waku_hasSymKey(id: Identifier): bool
|
||||||
proc waku_getSymKey(id: Identifier): SymKey
|
proc waku_getSymKey(id: Identifier): SymKey
|
||||||
proc waku_deleteSymKey(id: Identifier): bool
|
proc waku_deleteSymKey(id: Identifier): bool
|
||||||
|
|
||||||
proc waku_newMessageFilter(options: WhisperFilterOptions): Identifier
|
proc waku_newMessageFilter(options: WakuFilterOptions): Identifier
|
||||||
proc waku_deleteMessageFilter(id: Identifier): bool
|
proc waku_deleteMessageFilter(id: Identifier): bool
|
||||||
proc waku_getFilterMessages(id: Identifier): seq[WhisperFilterMessage]
|
proc waku_getFilterMessages(id: Identifier): seq[WakuFilterMessage]
|
||||||
proc waku_post(message: WhisperPostMessage): bool
|
proc waku_post(message: WakuPostMessage): bool
|
||||||
|
|
||||||
proc wakusim_generateTraffic(amount: int): bool
|
proc wakusim_generateTraffic(amount: int): bool
|
||||||
proc wakusim_generateRandomTraffic(amount: int): bool
|
proc wakusim_generateRandomTraffic(amount: int): bool
|
|
@ -0,0 +1,32 @@
|
||||||
|
import
|
||||||
|
json_rpc/rpcserver, stew/endians2, nimcrypto/sysrand,
|
||||||
|
eth/[p2p, async_utils],
|
||||||
|
../../../protocol/v1/waku_protocol
|
||||||
|
|
||||||
|
proc generateTraffic(node: EthereumNode, amount = 100) {.async.} =
|
||||||
|
var topicNumber = 0'u32
|
||||||
|
let payload = @[byte 0]
|
||||||
|
for i in 0..<amount:
|
||||||
|
discard waku_protocol.postMessage(node, ttl = 10,
|
||||||
|
topic = toBytesLE(i.uint32), payload = payload)
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
|
||||||
|
proc generateRandomTraffic(node: EthereumNode, amount = 100) {.async.} =
|
||||||
|
var topic: array[4, byte]
|
||||||
|
let payload = @[byte 0]
|
||||||
|
for i in 0..<amount:
|
||||||
|
while randomBytes(topic) != 4:
|
||||||
|
discard
|
||||||
|
discard waku_protocol.postMessage(node, ttl = 10, topic = topic,
|
||||||
|
payload = payload)
|
||||||
|
await sleepAsync(1.milliseconds)
|
||||||
|
|
||||||
|
proc setupWakuSimRPC*(node: EthereumNode, rpcsrv: RpcServer) =
|
||||||
|
|
||||||
|
rpcsrv.rpc("wakusim_generateTraffic") do(amount: int) -> bool:
|
||||||
|
traceAsyncErrors node.generateTraffic(amount)
|
||||||
|
return true
|
||||||
|
|
||||||
|
rpcsrv.rpc("wakusim_generateRandomTraffic") do(amount: int) -> bool:
|
||||||
|
traceAsyncErrors node.generateRandomTraffic(amount)
|
||||||
|
return true
|
|
@ -3,8 +3,9 @@ import
|
||||||
chronicles/topics_registry, # TODO: What? Need this for setLoglevel, weird.
|
chronicles/topics_registry, # TODO: What? Need this for setLoglevel, weird.
|
||||||
eth/[keys, p2p, async_utils], eth/common/utils, eth/net/nat,
|
eth/[keys, p2p, async_utils], eth/common/utils, eth/net/nat,
|
||||||
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
|
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
|
||||||
eth/p2p/rlpx_protocols/[whisper_protocol, waku_protocol, waku_bridge],
|
eth/p2p/rlpx_protocols/whisper_protocol,
|
||||||
../../vendor/nimbus/nimbus/rpc/[waku, wakusim, key_storage]
|
../../protocol/v1/[waku_protocol, waku_bridge],
|
||||||
|
./rpc/[waku, wakusim, key_storage]
|
||||||
|
|
||||||
const clientId = "Nimbus waku node"
|
const clientId = "Nimbus waku node"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import
|
import
|
||||||
confutils/defs, chronicles, chronos,
|
confutils/defs, chronicles, chronos,
|
||||||
eth/keys, eth/p2p/rlpx_protocols/waku_protocol
|
eth/keys
|
||||||
|
|
||||||
type
|
type
|
||||||
Fleet* = enum
|
Fleet* = enum
|
|
@ -2,8 +2,8 @@ import
|
||||||
os, strformat, chronicles, json_rpc/[rpcclient, rpcserver], nimcrypto/sysrand,
|
os, strformat, chronicles, json_rpc/[rpcclient, rpcserver], nimcrypto/sysrand,
|
||||||
eth/common as eth_common, eth/keys,
|
eth/common as eth_common, eth/keys,
|
||||||
# XXX: Replace me
|
# XXX: Replace me
|
||||||
eth/p2p/rlpx_protocols/waku_protocol,
|
../../protocol/v1/waku_protocol,
|
||||||
../../vendor/nimbus/nimbus/rpc/[hexstrings, rpc_types, waku],
|
../v1/rpc/[hexstrings, rpc_types, waku],
|
||||||
rpc/wakurpc,
|
rpc/wakurpc,
|
||||||
options as what # TODO: Huh? Redefinition?
|
options as what # TODO: Huh? Redefinition?
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||||
const sigWakuPath = &"{sourceDir}{DirSep}rpc{DirSep}wakucallsigs.nim"
|
const sigWakuPath = &"{sourceDir}{DirSep}rpc{DirSep}wakucallsigs.nim"
|
||||||
createRpcSigs(RpcHttpClient, sigWakuPath)
|
createRpcSigs(RpcHttpClient, sigWakuPath)
|
||||||
|
|
||||||
# More minimal than v0 quicksim, just RPC client for now
|
# More minimal than v1 quicksim, just RPC client for now
|
||||||
|
|
||||||
let node1 = newRpcHttpClient()
|
let node1 = newRpcHttpClient()
|
||||||
#let node2 = newRpcHttpClient()
|
#let node2 = newRpcHttpClient()
|
|
@ -1,7 +1,7 @@
|
||||||
# NOTE: Taken from v0, only version exists right now
|
# NOTE: Taken from v1, only version exists right now
|
||||||
proc waku_version(): string
|
proc waku_version(): string
|
||||||
|
|
||||||
proc waku_info(): WhisperInfo
|
proc waku_info(): WakuInfo
|
||||||
proc waku_setMaxMessageSize(size: uint64): bool
|
proc waku_setMaxMessageSize(size: uint64): bool
|
||||||
proc waku_setMinPoW(pow: float): bool
|
proc waku_setMinPoW(pow: float): bool
|
||||||
proc waku_markTrustedPeer(enode: string): bool
|
proc waku_markTrustedPeer(enode: string): bool
|
||||||
|
@ -20,10 +20,10 @@ proc waku_hasSymKey(id: Identifier): bool
|
||||||
proc waku_getSymKey(id: Identifier): SymKey
|
proc waku_getSymKey(id: Identifier): SymKey
|
||||||
proc waku_deleteSymKey(id: Identifier): bool
|
proc waku_deleteSymKey(id: Identifier): bool
|
||||||
|
|
||||||
proc waku_newMessageFilter(options: WhisperFilterOptions): Identifier
|
proc waku_newMessageFilter(options: WakuFilterOptions): Identifier
|
||||||
proc waku_deleteMessageFilter(id: Identifier): bool
|
proc waku_deleteMessageFilter(id: Identifier): bool
|
||||||
proc waku_getFilterMessages(id: Identifier): seq[WhisperFilterMessage]
|
proc waku_getFilterMessages(id: Identifier): seq[WakuFilterMessage]
|
||||||
proc waku_post(message: WhisperPostMessage): bool
|
proc waku_post(message: WakuPostMessage): bool
|
||||||
|
|
||||||
proc wakusim_generateTraffic(amount: int): bool
|
proc wakusim_generateTraffic(amount: int): bool
|
||||||
proc wakusim_generateRandomTraffic(amount: int): bool
|
proc wakusim_generateRandomTraffic(amount: int): bool
|
|
@ -2,12 +2,10 @@ import
|
||||||
json_rpc/rpcserver, tables, options,
|
json_rpc/rpcserver, tables, options,
|
||||||
eth/[common, rlp, keys, p2p],
|
eth/[common, rlp, keys, p2p],
|
||||||
#DevP2P impl
|
#DevP2P impl
|
||||||
#eth/p2p/rlpx_protocols/waku_protocol,
|
# ../../../protocol/v1/waku_protocol,
|
||||||
../../../protocol/v2/waku_protocol,
|
../../../protocol/v2/waku_protocol,
|
||||||
nimcrypto/[sysrand, hmac, sha2, pbkdf2],
|
nimcrypto/[sysrand, hmac, sha2, pbkdf2],
|
||||||
../../../vendor/nimbus/nimbus/rpc/rpc_types,
|
../../v1/rpc/[rpc_types, hexstrings, key_storage]
|
||||||
../../../vendor/nimbus/nimbus/rpc/hexstrings,
|
|
||||||
../../../vendor/nimbus/nimbus/rpc/key_storage
|
|
||||||
|
|
||||||
from stew/byteutils import hexToSeqByte, hexToByteArray
|
from stew/byteutils import hexToSeqByte, hexToByteArray
|
||||||
|
|
|
@ -5,7 +5,7 @@ import
|
||||||
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
|
eth/p2p/[discovery, enode, peer_pool, bootnodes, whispernodes],
|
||||||
eth/p2p/rlpx_protocols/[whisper_protocol, waku_protocol, waku_bridge],
|
eth/p2p/rlpx_protocols/[whisper_protocol, waku_protocol, waku_bridge],
|
||||||
# TODO remove me
|
# TODO remove me
|
||||||
../../vendor/nimbus/nimbus/rpc/[wakusim, key_storage],
|
../v1/rpc/[wakusim, key_storage],
|
||||||
../../vendor/nim-libp2p/libp2p/standard_setup,
|
../../vendor/nim-libp2p/libp2p/standard_setup,
|
||||||
../../vendor/nim-libp2p/libp2p/multiaddress,
|
../../vendor/nim-libp2p/libp2p/multiaddress,
|
||||||
../../vendor/nim-libp2p/libp2p/crypto/crypto,
|
../../vendor/nim-libp2p/libp2p/crypto/crypto,
|
|
@ -0,0 +1,17 @@
|
||||||
|
#
|
||||||
|
# Waku - Whisper Bridge
|
||||||
|
# (c) Copyright 2019
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# Licensed under either of
|
||||||
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# MIT license (LICENSE-MIT)
|
||||||
|
#
|
||||||
|
|
||||||
|
import
|
||||||
|
eth/p2p,
|
||||||
|
eth/p2p/rlpx_protocols/whisper_protocol,
|
||||||
|
./waku_protocol
|
||||||
|
|
||||||
|
proc shareMessageQueue*(node: EthereumNode) =
|
||||||
|
node.protocolState(Waku).queue = node.protocolState(Whisper).queue
|
|
@ -0,0 +1,86 @@
|
||||||
|
#
|
||||||
|
# Waku Mail Client & Server
|
||||||
|
# (c) Copyright 2019
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# Licensed under either of
|
||||||
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# MIT license (LICENSE-MIT)
|
||||||
|
#
|
||||||
|
import
|
||||||
|
chronos,
|
||||||
|
eth/[p2p, async_utils],
|
||||||
|
./waku_protocol
|
||||||
|
|
||||||
|
const
|
||||||
|
requestCompleteTimeout = chronos.seconds(5)
|
||||||
|
|
||||||
|
type
|
||||||
|
Cursor = seq[byte]
|
||||||
|
|
||||||
|
MailRequest* = object
|
||||||
|
lower*: uint32 ## Unix timestamp; oldest requested envelope's creation time
|
||||||
|
upper*: uint32 ## Unix timestamp; newest requested envelope's creation time
|
||||||
|
bloom*: seq[byte] ## Bloom filter to apply on the envelopes
|
||||||
|
limit*: uint32 ## Maximum amount of envelopes to return
|
||||||
|
cursor*: Cursor ## Optional cursor
|
||||||
|
|
||||||
|
proc requestMail*(node: EthereumNode, peerId: NodeId, request: MailRequest,
|
||||||
|
symKey: SymKey, requests = 10): Future[Option[Cursor]] {.async.} =
|
||||||
|
## Send p2p mail request and check request complete.
|
||||||
|
## If result is none, and error occured. If result is a none empty cursor,
|
||||||
|
## more envelopes are available.
|
||||||
|
# TODO: Perhaps don't go the recursive route or could use the actual response
|
||||||
|
# proc to implement this (via a handler) and store the necessary data in the
|
||||||
|
# WakuPeer object.
|
||||||
|
# TODO: Several requestMail calls in parallel can create issues with handling
|
||||||
|
# the wrong response to a request. Can additionaly check the requestId but
|
||||||
|
# that would only solve it half. Better to use the requestResponse mechanism.
|
||||||
|
|
||||||
|
# TODO: move this check out of requestMail?
|
||||||
|
let peer = node.getPeer(peerId, Waku)
|
||||||
|
if not peer.isSome():
|
||||||
|
error "Invalid peer"
|
||||||
|
return result
|
||||||
|
elif not peer.get().state(Waku).trusted:
|
||||||
|
return result
|
||||||
|
|
||||||
|
var writer = initRlpWriter()
|
||||||
|
writer.append(request)
|
||||||
|
let payload = writer.finish()
|
||||||
|
let data = encode(Payload(payload: payload, symKey: some(symKey)))
|
||||||
|
if not data.isSome():
|
||||||
|
error "Encoding of payload failed"
|
||||||
|
return result
|
||||||
|
|
||||||
|
# TODO: should this envelope be valid in terms of ttl, PoW, etc.?
|
||||||
|
let env = Envelope(expiry:0, ttl: 0, data: data.get(), nonce: 0)
|
||||||
|
# Send the request
|
||||||
|
traceAsyncErrors peer.get().p2pRequest(env)
|
||||||
|
|
||||||
|
# Wait for the Request Complete packet
|
||||||
|
var f = peer.get().nextMsg(Waku.p2pRequestComplete)
|
||||||
|
if await f.withTimeout(requestCompleteTimeout):
|
||||||
|
let response = f.read()
|
||||||
|
# TODO: I guess the idea is to check requestId (Hash) also?
|
||||||
|
let requests = requests - 1
|
||||||
|
# If there is cursor data, do another request
|
||||||
|
if response.cursor.len > 0 and requests > 0:
|
||||||
|
var newRequest = request
|
||||||
|
newRequest.cursor = response.cursor
|
||||||
|
return await requestMail(node, peerId, newRequest, symKey, requests)
|
||||||
|
else:
|
||||||
|
return some(response.cursor)
|
||||||
|
else:
|
||||||
|
error "p2pRequestComplete timeout"
|
||||||
|
return result
|
||||||
|
|
||||||
|
proc p2pRequestHandler(peer: Peer, envelope: Envelope) =
|
||||||
|
# Mail server p2p request implementation
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc enableMailServer*(node: EthereumNode, customHandler: P2PRequestHandler) =
|
||||||
|
node.protocolState(Waku).p2pRequestHandler = customHandler
|
||||||
|
|
||||||
|
proc enableMailServer*(node: EthereumNode) =
|
||||||
|
node.protocolState(Waku).p2pRequestHandler = p2pRequestHandler
|
|
@ -0,0 +1,649 @@
|
||||||
|
#
|
||||||
|
# Waku
|
||||||
|
# (c) Copyright 2018-2019
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# Licensed under either of
|
||||||
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# MIT license (LICENSE-MIT)
|
||||||
|
#
|
||||||
|
|
||||||
|
## Waku
|
||||||
|
## *******
|
||||||
|
##
|
||||||
|
## Waku is a fork of Whisper.
|
||||||
|
##
|
||||||
|
## Waku is a gossip protocol that synchronizes a set of messages across nodes
|
||||||
|
## with attention given to sender and recipient anonymitiy. Messages are
|
||||||
|
## categorized by a topic and stay alive in the network based on a time-to-live
|
||||||
|
## measured in seconds. Spam prevention is based on proof-of-work, where large
|
||||||
|
## or long-lived messages must spend more work.
|
||||||
|
##
|
||||||
|
## Implementation should be according to Waku specification defined here:
|
||||||
|
## https://github.com/vacp2p/specs/blob/master/waku/waku.md
|
||||||
|
##
|
||||||
|
## Example usage
|
||||||
|
## ----------
|
||||||
|
## First an `EthereumNode` needs to be created, either with all capabilities set
|
||||||
|
## or with specifically the Waku capability set.
|
||||||
|
## The latter can be done like this:
|
||||||
|
##
|
||||||
|
## .. code-block::nim
|
||||||
|
## var node = newEthereumNode(keypair, address, netId, nil,
|
||||||
|
## addAllCapabilities = false)
|
||||||
|
## node.addCapability Waku
|
||||||
|
##
|
||||||
|
## Now calls such as ``postMessage`` and ``subscribeFilter`` can be done.
|
||||||
|
## However, they only make real sense after ``connectToNetwork`` was started. As
|
||||||
|
## else there will be no peers to send and receive messages from.
|
||||||
|
|
||||||
|
import
|
||||||
|
options, tables, times, chronos, chronicles, metrics,
|
||||||
|
eth/[keys, async_utils, p2p], eth/p2p/rlpx_protocols/whisper/whisper_types,
|
||||||
|
eth/trie/trie_defs
|
||||||
|
|
||||||
|
export
|
||||||
|
whisper_types
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "waku"
|
||||||
|
|
||||||
|
declarePublicCounter dropped_low_pow_envelopes,
|
||||||
|
"Dropped envelopes because of too low PoW"
|
||||||
|
declarePublicCounter dropped_too_large_envelopes,
|
||||||
|
"Dropped envelopes because larger than maximum allowed size"
|
||||||
|
declarePublicCounter dropped_bloom_filter_mismatch_envelopes,
|
||||||
|
"Dropped envelopes because not matching with bloom filter"
|
||||||
|
declarePublicCounter dropped_topic_mismatch_envelopes,
|
||||||
|
"Dropped envelopes because of not matching topics"
|
||||||
|
declarePublicCounter dropped_duplicate_envelopes,
|
||||||
|
"Dropped duplicate envelopes"
|
||||||
|
|
||||||
|
const
|
||||||
|
defaultQueueCapacity = 2048
|
||||||
|
wakuVersion* = 1 ## Waku version.
|
||||||
|
wakuVersionStr* = $wakuVersion ## Waku version.
|
||||||
|
defaultMinPow* = 0.2'f64 ## The default minimum PoW requirement for this node.
|
||||||
|
defaultMaxMsgSize* = 1024'u32 * 1024'u32 ## The current default and max
|
||||||
|
## message size. This can never be larger than the maximum RLPx message size.
|
||||||
|
messageInterval* = chronos.milliseconds(300) ## Interval at which messages are
|
||||||
|
## send to peers, in ms.
|
||||||
|
pruneInterval* = chronos.milliseconds(1000) ## Interval at which message
|
||||||
|
## queue is pruned, in ms.
|
||||||
|
topicInterestMax = 10000
|
||||||
|
|
||||||
|
type
|
||||||
|
WakuConfig* = object
|
||||||
|
powRequirement*: float64
|
||||||
|
bloom*: Option[Bloom]
|
||||||
|
isLightNode*: bool
|
||||||
|
maxMsgSize*: uint32
|
||||||
|
confirmationsEnabled*: bool
|
||||||
|
rateLimits*: Option[RateLimits]
|
||||||
|
topics*: Option[seq[Topic]]
|
||||||
|
|
||||||
|
WakuPeer = ref object
|
||||||
|
initialized: bool # when successfully completed the handshake
|
||||||
|
powRequirement*: float64
|
||||||
|
bloom*: Bloom
|
||||||
|
isLightNode*: bool
|
||||||
|
trusted*: bool
|
||||||
|
topics*: Option[seq[Topic]]
|
||||||
|
received: HashSet[Hash]
|
||||||
|
|
||||||
|
P2PRequestHandler* = proc(peer: Peer, envelope: Envelope) {.gcsafe.}
|
||||||
|
|
||||||
|
WakuNetwork = ref object
|
||||||
|
queue*: ref Queue
|
||||||
|
filters*: Filters
|
||||||
|
config*: WakuConfig
|
||||||
|
p2pRequestHandler*: P2PRequestHandler
|
||||||
|
|
||||||
|
RateLimits* = object
|
||||||
|
# TODO: uint or specifically uint32?
|
||||||
|
limitIp*: uint
|
||||||
|
limitPeerId*: uint
|
||||||
|
limitTopic*: uint
|
||||||
|
|
||||||
|
StatusOptions* = object
|
||||||
|
powRequirement*: Option[(float64)]
|
||||||
|
bloomFilter*: Option[Bloom]
|
||||||
|
lightNode*: Option[bool]
|
||||||
|
confirmationsEnabled*: Option[bool]
|
||||||
|
rateLimits*: Option[RateLimits]
|
||||||
|
topicInterest*: Option[seq[Topic]]
|
||||||
|
|
||||||
|
KeyKind* = enum
|
||||||
|
powRequirementKey,
|
||||||
|
bloomFilterKey,
|
||||||
|
lightNodeKey,
|
||||||
|
confirmationsEnabledKey,
|
||||||
|
rateLimitsKey,
|
||||||
|
topicInterestKey
|
||||||
|
|
||||||
|
template countSomeFields*(x: StatusOptions): int =
|
||||||
|
var count = 0
|
||||||
|
for f in fields(x):
|
||||||
|
if f.isSome():
|
||||||
|
inc count
|
||||||
|
count
|
||||||
|
|
||||||
|
proc append*(rlpWriter: var RlpWriter, value: StatusOptions) =
|
||||||
|
var list = initRlpList(countSomeFields(value))
|
||||||
|
if value.powRequirement.isSome():
|
||||||
|
list.append((powRequirementKey, cast[uint64](value.powRequirement.get())))
|
||||||
|
if value.bloomFilter.isSome():
|
||||||
|
list.append((bloomFilterKey, @(value.bloomFilter.get())))
|
||||||
|
if value.lightNode.isSome():
|
||||||
|
list.append((lightNodeKey, value.lightNode.get()))
|
||||||
|
if value.confirmationsEnabled.isSome():
|
||||||
|
list.append((confirmationsEnabledKey, value.confirmationsEnabled.get()))
|
||||||
|
if value.rateLimits.isSome():
|
||||||
|
list.append((rateLimitsKey, value.rateLimits.get()))
|
||||||
|
if value.topicInterest.isSome():
|
||||||
|
list.append((topicInterestKey, value.topicInterest.get()))
|
||||||
|
|
||||||
|
let bytes = list.finish()
|
||||||
|
|
||||||
|
rlpWriter.append(rlpFromBytes(bytes))
|
||||||
|
|
||||||
|
proc read*(rlp: var Rlp, T: typedesc[StatusOptions]): T =
|
||||||
|
if not rlp.isList():
|
||||||
|
raise newException(RlpTypeMismatch,
|
||||||
|
"List expected, but the source RLP is not a list.")
|
||||||
|
|
||||||
|
let sz = rlp.listLen()
|
||||||
|
# We already know that we are working with a list
|
||||||
|
doAssert rlp.enterList()
|
||||||
|
for i in 0 ..< sz:
|
||||||
|
rlp.tryEnterList()
|
||||||
|
|
||||||
|
var k: KeyKind
|
||||||
|
try:
|
||||||
|
k = rlp.read(KeyKind)
|
||||||
|
except RlpTypeMismatch:
|
||||||
|
# skip unknown keys and their value
|
||||||
|
rlp.skipElem()
|
||||||
|
rlp.skipElem()
|
||||||
|
continue
|
||||||
|
|
||||||
|
case k
|
||||||
|
of powRequirementKey:
|
||||||
|
let pow = rlp.read(uint64)
|
||||||
|
result.powRequirement = some(cast[float64](pow))
|
||||||
|
of bloomFilterKey:
|
||||||
|
let bloom = rlp.read(seq[byte])
|
||||||
|
if bloom.len != bloomSize:
|
||||||
|
raise newException(UselessPeerError, "Bloomfilter size mismatch")
|
||||||
|
var bloomFilter: Bloom
|
||||||
|
bloomFilter.bytesCopy(bloom)
|
||||||
|
result.bloomFilter = some(bloomFilter)
|
||||||
|
of lightNodeKey:
|
||||||
|
result.lightNode = some(rlp.read(bool))
|
||||||
|
of confirmationsEnabledKey:
|
||||||
|
result.confirmationsEnabled = some(rlp.read(bool))
|
||||||
|
of rateLimitsKey:
|
||||||
|
result.rateLimits = some(rlp.read(RateLimits))
|
||||||
|
of topicInterestKey:
|
||||||
|
result.topicInterest = some(rlp.read(seq[Topic]))
|
||||||
|
|
||||||
|
proc allowed*(msg: Message, config: WakuConfig): bool =
|
||||||
|
# Check max msg size, already happens in RLPx but there is a specific waku
|
||||||
|
# max msg size which should always be < RLPx max msg size
|
||||||
|
if msg.size > config.maxMsgSize:
|
||||||
|
dropped_too_large_envelopes.inc()
|
||||||
|
warn "Message size too large", size = msg.size
|
||||||
|
return false
|
||||||
|
|
||||||
|
if msg.pow < config.powRequirement:
|
||||||
|
dropped_low_pow_envelopes.inc()
|
||||||
|
warn "Message PoW too low", pow = msg.pow, minPow = config.powRequirement
|
||||||
|
return false
|
||||||
|
|
||||||
|
if config.topics.isSome():
|
||||||
|
if msg.env.topic notin config.topics.get():
|
||||||
|
dropped_topic_mismatch_envelopes.inc()
|
||||||
|
warn "Message topic does not match Waku topic list"
|
||||||
|
return false
|
||||||
|
else:
|
||||||
|
if config.bloom.isSome() and not bloomFilterMatch(config.bloom.get(), msg.bloom):
|
||||||
|
dropped_bloom_filter_mismatch_envelopes.inc()
|
||||||
|
warn "Message does not match node bloom filter"
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc run(peer: Peer) {.gcsafe, async.}
|
||||||
|
proc run(node: EthereumNode, network: WakuNetwork) {.gcsafe, async.}
|
||||||
|
|
||||||
|
proc initProtocolState*(network: WakuNetwork, node: EthereumNode) {.gcsafe.} =
|
||||||
|
new(network.queue)
|
||||||
|
network.queue[] = initQueue(defaultQueueCapacity)
|
||||||
|
network.filters = initTable[string, Filter]()
|
||||||
|
network.config.bloom = some(fullBloom())
|
||||||
|
network.config.powRequirement = defaultMinPow
|
||||||
|
network.config.isLightNode = false
|
||||||
|
# RateLimits and confirmations are not yet implemented so we set confirmations
|
||||||
|
# to false and we don't pass RateLimits at all.
|
||||||
|
network.config.confirmationsEnabled = false
|
||||||
|
network.config.rateLimits = none(RateLimits)
|
||||||
|
network.config.maxMsgSize = defaultMaxMsgSize
|
||||||
|
network.config.topics = none(seq[Topic])
|
||||||
|
asyncCheck node.run(network)
|
||||||
|
|
||||||
|
p2pProtocol Waku(version = wakuVersion,
|
||||||
|
rlpxName = "waku",
|
||||||
|
peerState = WakuPeer,
|
||||||
|
networkState = WakuNetwork):
|
||||||
|
|
||||||
|
onPeerConnected do (peer: Peer):
|
||||||
|
trace "onPeerConnected Waku"
|
||||||
|
let
|
||||||
|
wakuNet = peer.networkState
|
||||||
|
wakuPeer = peer.state
|
||||||
|
|
||||||
|
let options = StatusOptions(
|
||||||
|
powRequirement: some(wakuNet.config.powRequirement),
|
||||||
|
bloomFilter: wakuNet.config.bloom,
|
||||||
|
lightNode: some(wakuNet.config.isLightNode),
|
||||||
|
confirmationsEnabled: some(wakuNet.config.confirmationsEnabled),
|
||||||
|
rateLimits: wakuNet.config.rateLimits,
|
||||||
|
topicInterest: wakuNet.config.topics)
|
||||||
|
|
||||||
|
let m = await peer.status(options,
|
||||||
|
timeout = chronos.milliseconds(5000))
|
||||||
|
|
||||||
|
wakuPeer.powRequirement = m.options.powRequirement.get(defaultMinPow)
|
||||||
|
wakuPeer.bloom = m.options.bloomFilter.get(fullBloom())
|
||||||
|
|
||||||
|
wakuPeer.isLightNode = m.options.lightNode.get(false)
|
||||||
|
if wakuPeer.isLightNode and wakuNet.config.isLightNode:
|
||||||
|
# No sense in connecting two light nodes so we disconnect
|
||||||
|
raise newException(UselessPeerError, "Two light nodes connected")
|
||||||
|
|
||||||
|
wakuPeer.topics = m.options.topicInterest
|
||||||
|
if wakuPeer.topics.isSome():
|
||||||
|
if wakuPeer.topics.get().len > topicInterestMax:
|
||||||
|
raise newException(UselessPeerError, "Topic-interest is too large")
|
||||||
|
if wakuNet.config.topics.isSome():
|
||||||
|
raise newException(UselessPeerError,
|
||||||
|
"Two Waku nodes with topic-interest connected")
|
||||||
|
|
||||||
|
wakuPeer.received.init()
|
||||||
|
wakuPeer.trusted = false
|
||||||
|
wakuPeer.initialized = true
|
||||||
|
|
||||||
|
# No timer based queue processing for a light node.
|
||||||
|
if not wakuNet.config.isLightNode:
|
||||||
|
traceAsyncErrors peer.run()
|
||||||
|
|
||||||
|
debug "Waku peer initialized", peer
|
||||||
|
|
||||||
|
handshake:
|
||||||
|
proc status(peer: Peer, options: StatusOptions)
|
||||||
|
|
||||||
|
proc messages(peer: Peer, envelopes: openarray[Envelope]) =
|
||||||
|
if not peer.state.initialized:
|
||||||
|
warn "Handshake not completed yet, discarding messages"
|
||||||
|
return
|
||||||
|
|
||||||
|
for envelope in envelopes:
|
||||||
|
# check if expired or in future, or ttl not 0
|
||||||
|
if not envelope.valid():
|
||||||
|
warn "Expired or future timed envelope", peer
|
||||||
|
# disconnect from peers sending bad envelopes
|
||||||
|
# await peer.disconnect(SubprotocolReason)
|
||||||
|
continue
|
||||||
|
|
||||||
|
let msg = initMessage(envelope)
|
||||||
|
if not msg.allowed(peer.networkState.config):
|
||||||
|
# disconnect from peers sending bad envelopes
|
||||||
|
# await peer.disconnect(SubprotocolReason)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# This peer send this message thus should not receive it again.
|
||||||
|
# If this peer has the message in the `received` set already, this means
|
||||||
|
# it was either already received here from this peer or send to this peer.
|
||||||
|
# Either way it will be in our queue already (and the peer should know
|
||||||
|
# this) and this peer is sending duplicates.
|
||||||
|
# Note: geth does not check if a peer has send a message to them before
|
||||||
|
# broadcasting this message. This too is seen here as a duplicate message
|
||||||
|
# (see above comment). If we want to seperate these cases (e.g. when peer
|
||||||
|
# rating), then we have to add a "peer.state.send" HashSet.
|
||||||
|
# Note: it could also be a race between the arrival of a message send by
|
||||||
|
# this node to a peer and that same message arriving from that peer (after
|
||||||
|
# it was received from another peer) here.
|
||||||
|
if peer.state.received.containsOrIncl(msg.hash):
|
||||||
|
dropped_duplicate_envelopes.inc()
|
||||||
|
trace "Peer sending duplicate messages", peer, hash = $msg.hash
|
||||||
|
# await peer.disconnect(SubprotocolReason)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# This can still be a duplicate message, but from another peer than
|
||||||
|
# the peer who send the message.
|
||||||
|
if peer.networkState.queue[].add(msg):
|
||||||
|
# notify filters of this message
|
||||||
|
peer.networkState.filters.notify(msg)
|
||||||
|
|
||||||
|
nextID 22
|
||||||
|
|
||||||
|
proc statusOptions(peer: Peer, options: StatusOptions) =
|
||||||
|
if not peer.state.initialized:
|
||||||
|
warn "Handshake not completed yet, discarding statusOptions"
|
||||||
|
return
|
||||||
|
|
||||||
|
if options.topicInterest.isSome():
|
||||||
|
peer.state.topics = options.topicInterest
|
||||||
|
elif options.bloomFilter.isSome():
|
||||||
|
peer.state.bloom = options.bloomFilter.get()
|
||||||
|
peer.state.topics = none(seq[Topic])
|
||||||
|
|
||||||
|
if options.powRequirement.isSome():
|
||||||
|
peer.state.powRequirement = options.powRequirement.get()
|
||||||
|
|
||||||
|
if options.lightNode.isSome():
|
||||||
|
peer.state.isLightNode = options.lightNode.get()
|
||||||
|
|
||||||
|
nextID 126
|
||||||
|
|
||||||
|
proc p2pRequest(peer: Peer, envelope: Envelope) =
|
||||||
|
if not peer.networkState.p2pRequestHandler.isNil():
|
||||||
|
peer.networkState.p2pRequestHandler(peer, envelope)
|
||||||
|
|
||||||
|
proc p2pMessage(peer: Peer, envelopes: openarray[Envelope]) =
|
||||||
|
if peer.state.trusted:
|
||||||
|
# when trusted we can bypass any checks on envelope
|
||||||
|
for envelope in envelopes:
|
||||||
|
let msg = Message(env: envelope, isP2P: true)
|
||||||
|
peer.networkState.filters.notify(msg)
|
||||||
|
|
||||||
|
# Following message IDs are not part of EIP-627, but are added and used by
|
||||||
|
# the Status application, we ignore them for now.
|
||||||
|
nextID 11
|
||||||
|
proc batchAcknowledged(peer: Peer) = discard
|
||||||
|
proc messageResponse(peer: Peer) = discard
|
||||||
|
|
||||||
|
nextID 123
|
||||||
|
requestResponse:
|
||||||
|
proc p2pSyncRequest(peer: Peer) = discard
|
||||||
|
proc p2pSyncResponse(peer: Peer) = discard
|
||||||
|
|
||||||
|
|
||||||
|
proc p2pRequestComplete(peer: Peer, requestId: Hash, lastEnvelopeHash: Hash,
|
||||||
|
cursor: seq[byte]) = discard
|
||||||
|
# TODO:
|
||||||
|
# In the current specification the parameters are not wrapped in a regular
|
||||||
|
# envelope as is done for the P2P Request packet. If we could alter this in
|
||||||
|
# the spec it would be a cleaner separation between Waku and Mail server /
|
||||||
|
# client.
|
||||||
|
# Also, if a requestResponse block is used, a reqestId will automatically
|
||||||
|
# be added by the protocol DSL.
|
||||||
|
# However the requestResponse block in combination with p2pRequest cannot be
|
||||||
|
# used due to the unfortunate fact that the packet IDs are not consecutive,
|
||||||
|
# and nextID is not recognized in between these. The nextID behaviour could
|
||||||
|
# be fixed, however it would be cleaner if the specification could be
|
||||||
|
# changed to have these IDs to be consecutive.
|
||||||
|
|
||||||
|
# 'Runner' calls ---------------------------------------------------------------
|
||||||
|
|
||||||
|
proc processQueue(peer: Peer) =
|
||||||
|
# Send to peer all valid and previously not send envelopes in the queue.
|
||||||
|
var
|
||||||
|
envelopes: seq[Envelope] = @[]
|
||||||
|
wakuPeer = peer.state(Waku)
|
||||||
|
wakuNet = peer.networkState(Waku)
|
||||||
|
|
||||||
|
for message in wakuNet.queue.items:
|
||||||
|
if wakuPeer.received.contains(message.hash):
|
||||||
|
# trace "message was already send to peer", hash = $message.hash, peer
|
||||||
|
continue
|
||||||
|
|
||||||
|
if message.pow < wakuPeer.powRequirement:
|
||||||
|
trace "Message PoW too low for peer", pow = message.pow,
|
||||||
|
powReq = wakuPeer.powRequirement
|
||||||
|
continue
|
||||||
|
|
||||||
|
if wakuPeer.topics.isSome():
|
||||||
|
if message.env.topic notin wakuPeer.topics.get():
|
||||||
|
trace "Message does not match topics list"
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if not bloomFilterMatch(wakuPeer.bloom, message.bloom):
|
||||||
|
trace "Message does not match peer bloom filter"
|
||||||
|
continue
|
||||||
|
|
||||||
|
trace "Adding envelope"
|
||||||
|
envelopes.add(message.env)
|
||||||
|
wakuPeer.received.incl(message.hash)
|
||||||
|
|
||||||
|
if envelopes.len() > 0:
|
||||||
|
trace "Sending envelopes", amount=envelopes.len
|
||||||
|
# Ignore failure of sending messages, this could occur when the connection
|
||||||
|
# gets dropped
|
||||||
|
traceAsyncErrors peer.messages(envelopes)
|
||||||
|
|
||||||
|
proc run(peer: Peer) {.async.} =
|
||||||
|
while peer.connectionState notin {Disconnecting, Disconnected}:
|
||||||
|
peer.processQueue()
|
||||||
|
await sleepAsync(messageInterval)
|
||||||
|
|
||||||
|
proc pruneReceived(node: EthereumNode) {.raises: [].} =
|
||||||
|
if node.peerPool != nil: # XXX: a bit dirty to need to check for this here ...
|
||||||
|
var wakuNet = node.protocolState(Waku)
|
||||||
|
|
||||||
|
for peer in node.protocolPeers(Waku):
|
||||||
|
if not peer.initialized:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# NOTE: Perhaps alter the queue prune call to keep track of a HashSet
|
||||||
|
# of pruned messages (as these should be smaller), and diff this with
|
||||||
|
# the received sets.
|
||||||
|
peer.received = intersection(peer.received, wakuNet.queue.itemHashes)
|
||||||
|
|
||||||
|
proc run(node: EthereumNode, network: WakuNetwork) {.async.} =
|
||||||
|
while true:
|
||||||
|
# prune message queue every second
|
||||||
|
# TTL unit is in seconds, so this should be sufficient?
|
||||||
|
network.queue[].prune()
|
||||||
|
# pruning the received sets is not necessary for correct workings
|
||||||
|
# but simply from keeping the sets growing indefinitely
|
||||||
|
node.pruneReceived()
|
||||||
|
await sleepAsync(pruneInterval)
|
||||||
|
|
||||||
|
# Private EthereumNode calls ---------------------------------------------------
|
||||||
|
|
||||||
|
proc sendP2PMessage(node: EthereumNode, peerId: NodeId,
|
||||||
|
envelopes: openarray[Envelope]): bool =
|
||||||
|
for peer in node.peers(Waku):
|
||||||
|
if peer.remote.id == peerId:
|
||||||
|
asyncCheck peer.p2pMessage(envelopes)
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc queueMessage(node: EthereumNode, msg: Message): bool =
|
||||||
|
|
||||||
|
var wakuNet = node.protocolState(Waku)
|
||||||
|
# We have to do the same checks here as in the messages proc not to leak
|
||||||
|
# any information that the message originates from this node.
|
||||||
|
if not msg.allowed(wakuNet.config):
|
||||||
|
return false
|
||||||
|
|
||||||
|
trace "Adding message to queue", hash = $msg.hash
|
||||||
|
if wakuNet.queue[].add(msg):
|
||||||
|
# Also notify our own filters of the message we are sending,
|
||||||
|
# e.g. msg from local Dapp to Dapp
|
||||||
|
wakuNet.filters.notify(msg)
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
# Public EthereumNode calls ----------------------------------------------------
|
||||||
|
|
||||||
|
proc postMessage*(node: EthereumNode, pubKey = none[PublicKey](),
|
||||||
|
symKey = none[SymKey](), src = none[PrivateKey](),
|
||||||
|
ttl: uint32, topic: Topic, payload: seq[byte],
|
||||||
|
padding = none[seq[byte]](), powTime = 1'f,
|
||||||
|
powTarget = defaultMinPow,
|
||||||
|
targetPeer = none[NodeId]()): bool =
|
||||||
|
## Post a message on the message queue which will be processed at the
|
||||||
|
## next `messageInterval`.
|
||||||
|
##
|
||||||
|
## NOTE: This call allows a post without encryption. If encryption is
|
||||||
|
## mandatory it should be enforced a layer up
|
||||||
|
let payload = encode(Payload(payload: payload, src: src, dst: pubKey,
|
||||||
|
symKey: symKey, padding: padding))
|
||||||
|
if payload.isSome():
|
||||||
|
var env = Envelope(expiry:epochTime().uint32 + ttl,
|
||||||
|
ttl: ttl, topic: topic, data: payload.get(), nonce: 0)
|
||||||
|
|
||||||
|
# Allow lightnode to post only direct p2p messages
|
||||||
|
if targetPeer.isSome():
|
||||||
|
return node.sendP2PMessage(targetPeer.get(), [env])
|
||||||
|
else:
|
||||||
|
# non direct p2p message can not have ttl of 0
|
||||||
|
if env.ttl == 0:
|
||||||
|
return false
|
||||||
|
var msg = initMessage(env, powCalc = false)
|
||||||
|
# XXX: make this non blocking or not?
|
||||||
|
# In its current blocking state, it could be noticed by a peer that no
|
||||||
|
# messages are send for a while, and thus that mining PoW is done, and
|
||||||
|
# that next messages contains a message originated from this peer
|
||||||
|
# zah: It would be hard to execute this in a background thread at the
|
||||||
|
# moment. We'll need a way to send custom "tasks" to the async message
|
||||||
|
# loop (e.g. AD2 support for AsyncChannels).
|
||||||
|
if not msg.sealEnvelope(powTime, powTarget):
|
||||||
|
return false
|
||||||
|
|
||||||
|
# need to check expiry after mining PoW
|
||||||
|
if not msg.env.valid():
|
||||||
|
return false
|
||||||
|
|
||||||
|
result = node.queueMessage(msg)
|
||||||
|
|
||||||
|
# Allows light nodes to post via untrusted messages packet.
|
||||||
|
# Queue gets processed immediatly as the node sends only its own messages,
|
||||||
|
# so the privacy ship has already sailed anyhow.
|
||||||
|
# TODO:
|
||||||
|
# - Could be still a concern in terms of efficiency, if multiple messages
|
||||||
|
# need to be send.
|
||||||
|
# - For Waku Mode, the checks in processQueue are rather useless as the
|
||||||
|
# idea is to connect only to 1 node? Also refactor in that case.
|
||||||
|
if node.protocolState(Waku).config.isLightNode:
|
||||||
|
for peer in node.peers(Waku):
|
||||||
|
peer.processQueue()
|
||||||
|
else:
|
||||||
|
error "Encoding of payload failed"
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc subscribeFilter*(node: EthereumNode, filter: Filter,
|
||||||
|
handler:FilterMsgHandler = nil): string =
|
||||||
|
## Initiate a filter for incoming/outgoing messages. Messages can be
|
||||||
|
## retrieved with the `getFilterMessages` call or with a provided
|
||||||
|
## `FilterMsgHandler`.
|
||||||
|
##
|
||||||
|
## NOTE: This call allows for a filter without decryption. If encryption is
|
||||||
|
## mandatory it should be enforced a layer up.
|
||||||
|
return node.protocolState(Waku).filters.subscribeFilter(filter, handler)
|
||||||
|
|
||||||
|
proc unsubscribeFilter*(node: EthereumNode, filterId: string): bool =
|
||||||
|
## Remove a previously subscribed filter.
|
||||||
|
var filter: Filter
|
||||||
|
return node.protocolState(Waku).filters.take(filterId, filter)
|
||||||
|
|
||||||
|
proc getFilterMessages*(node: EthereumNode, filterId: string): seq[ReceivedMessage] =
|
||||||
|
## Get all the messages currently in the filter queue. This will reset the
|
||||||
|
## filter message queue.
|
||||||
|
return node.protocolState(Waku).filters.getFilterMessages(filterId)
|
||||||
|
|
||||||
|
proc filtersToBloom*(node: EthereumNode): Bloom =
|
||||||
|
## Returns the bloom filter of all topics of all subscribed filters.
|
||||||
|
return node.protocolState(Waku).filters.toBloom()
|
||||||
|
|
||||||
|
proc setPowRequirement*(node: EthereumNode, powReq: float64) {.async.} =
|
||||||
|
## Sets the PoW requirement for this node, will also send
|
||||||
|
## this new PoW requirement to all connected peers.
|
||||||
|
##
|
||||||
|
## Failures when sending messages to peers will not be reported.
|
||||||
|
# NOTE: do we need a tolerance of old PoW for some time?
|
||||||
|
node.protocolState(Waku).config.powRequirement = powReq
|
||||||
|
var futures: seq[Future[void]] = @[]
|
||||||
|
let list = StatusOptions(powRequirement: some(powReq))
|
||||||
|
for peer in node.peers(Waku):
|
||||||
|
futures.add(peer.statusOptions(list))
|
||||||
|
|
||||||
|
# Exceptions from sendMsg will not be raised
|
||||||
|
await allFutures(futures)
|
||||||
|
|
||||||
|
proc setBloomFilter*(node: EthereumNode, bloom: Bloom) {.async.} =
|
||||||
|
## Sets the bloom filter for this node, will also send
|
||||||
|
## this new bloom filter to all connected peers.
|
||||||
|
##
|
||||||
|
## Failures when sending messages to peers will not be reported.
|
||||||
|
# NOTE: do we need a tolerance of old bloom filter for some time?
|
||||||
|
node.protocolState(Waku).config.bloom = some(bloom)
|
||||||
|
# reset topics
|
||||||
|
node.protocolState(Waku).config.topics = none(seq[Topic])
|
||||||
|
|
||||||
|
var futures: seq[Future[void]] = @[]
|
||||||
|
let list = StatusOptions(bloomFilter: some(bloom))
|
||||||
|
for peer in node.peers(Waku):
|
||||||
|
futures.add(peer.statusOptions(list))
|
||||||
|
|
||||||
|
# Exceptions from sendMsg will not be raised
|
||||||
|
await allFutures(futures)
|
||||||
|
|
||||||
|
proc setTopicInterest*(node: EthereumNode, topics: seq[Topic]):
|
||||||
|
Future[bool] {.async.} =
|
||||||
|
if topics.len > topicInterestMax:
|
||||||
|
return false
|
||||||
|
|
||||||
|
node.protocolState(Waku).config.topics = some(topics)
|
||||||
|
|
||||||
|
var futures: seq[Future[void]] = @[]
|
||||||
|
let list = StatusOptions(topicInterest: some(topics))
|
||||||
|
for peer in node.peers(Waku):
|
||||||
|
futures.add(peer.statusOptions(list))
|
||||||
|
|
||||||
|
# Exceptions from sendMsg will not be raised
|
||||||
|
await allFutures(futures)
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc setMaxMessageSize*(node: EthereumNode, size: uint32): bool =
|
||||||
|
## Set the maximum allowed message size.
|
||||||
|
## Can not be set higher than ``defaultMaxMsgSize``.
|
||||||
|
if size > defaultMaxMsgSize:
|
||||||
|
warn "size > defaultMaxMsgSize"
|
||||||
|
return false
|
||||||
|
node.protocolState(Waku).config.maxMsgSize = size
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc setPeerTrusted*(node: EthereumNode, peerId: NodeId): bool =
|
||||||
|
## Set a connected peer as trusted.
|
||||||
|
for peer in node.peers(Waku):
|
||||||
|
if peer.remote.id == peerId:
|
||||||
|
peer.state(Waku).trusted = true
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc setLightNode*(node: EthereumNode, isLightNode: bool) {.async.} =
|
||||||
|
## Set this node as a Waku light node.
|
||||||
|
node.protocolState(Waku).config.isLightNode = isLightNode
|
||||||
|
# TODO: Add starting/stopping of `processQueue` loop depending on value of isLightNode.
|
||||||
|
var futures: seq[Future[void]] = @[]
|
||||||
|
let list = StatusOptions(lightNode: some(isLightNode))
|
||||||
|
for peer in node.peers(Waku):
|
||||||
|
futures.add(peer.statusOptions(list))
|
||||||
|
|
||||||
|
# Exceptions from sendMsg will not be raised
|
||||||
|
await allFutures(futures)
|
||||||
|
|
||||||
|
proc configureWaku*(node: EthereumNode, config: WakuConfig) =
|
||||||
|
## Apply a Waku configuration.
|
||||||
|
##
|
||||||
|
## NOTE: Should be run before connection is made with peers as some
|
||||||
|
## of the settings are only communicated at peer handshake.
|
||||||
|
node.protocolState(Waku).config = config
|
||||||
|
|
||||||
|
proc resetMessageQueue*(node: EthereumNode) =
|
||||||
|
## Full reset of the message queue.
|
||||||
|
##
|
||||||
|
## NOTE: Not something that should be run in normal circumstances.
|
||||||
|
node.protocolState(Waku).queue[] = initQueue(defaultQueueCapacity)
|
Loading…
Reference in New Issue