diff --git a/Makefile b/Makefile index 34eff046..951eb3bd 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ clean: test: tox +itest: + ./itests/run_itest.sh + coverage: tox -e coverage @@ -18,4 +21,4 @@ mypy: run: tox -e run -.PHONY: all clean test coverage mypy run +.PHONY: all clean test itest coverage mypy run diff --git a/docker/btcd/Dockerfile b/docker/btcd/Dockerfile new file mode 100644 index 00000000..13998e48 --- /dev/null +++ b/docker/btcd/Dockerfile @@ -0,0 +1,56 @@ +FROM golang:1.12-alpine as builder + +MAINTAINER Olaoluwa Osuntokun + +# Install build dependencies such as git and glide. +RUN apk add --no-cache git gcc musl-dev + +WORKDIR $GOPATH/src/github.com/btcsuite/btcd + +# Grab and install the latest version of of btcd and all related dependencies. +RUN git clone https://github.com/btcsuite/btcd.git --branch v0.20.0-beta . \ + && GO111MODULE=on go install -v . ./cmd/... + +# Start a new image +FROM alpine as final + +# Expose mainnet ports (server, rpc) +EXPOSE 8333 8334 + +# Expose testnet ports (server, rpc) +EXPOSE 18333 18334 + +# Expose simnet ports (server, rpc) +EXPOSE 18555 18556 + +# Expose segnet ports (server, rpc) +EXPOSE 28901 28902 + +# Copy the compiled binaries from the builder image. +COPY --from=builder /go/bin/addblock /bin/ +COPY --from=builder /go/bin/btcctl /bin/ +COPY --from=builder /go/bin/btcd /bin/ +COPY --from=builder /go/bin/findcheckpoint /bin/ +COPY --from=builder /go/bin/gencerts /bin/ + +COPY "docker/btcd/start-btcctl.sh" . +COPY "docker/btcd/start-btcd.sh" . + +RUN apk add --no-cache \ + bash \ + ca-certificates \ +&& mkdir "/rpc" "/root/.btcd" "/root/.btcctl" \ +&& touch "/root/.btcd/btcd.conf" \ +&& chmod +x start-btcctl.sh \ +&& chmod +x start-btcd.sh \ +# Manually generate certificate and add all domains, it is needed to connect +# "btcctl" and "lnd" to "btcd" over docker links. +&& "/bin/gencerts" --host="*" --directory="/rpc" --force + +# Create a volume to house pregenerated RPC credentials. This will be +# shared with any lnd, btcctl containers so they can securely query btcd's RPC +# server. +# You should NOT do this before certificate generation! +# Otherwise manually generated certificate will be overridden with shared +# mounted volume! For more info read dockerfile "VOLUME" documentation. +VOLUME ["/rpc"] diff --git a/docker/btcd/start-btcctl.sh b/docker/btcd/start-btcctl.sh new file mode 100644 index 00000000..7ff1aefb --- /dev/null +++ b/docker/btcd/start-btcctl.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +# exit from script if error was raised. +set -e + +# error function is used within a bash function in order to send the error +# message directly to the stderr output and exit. +error() { + echo "$1" > /dev/stderr + exit 0 +} + +# return is used within bash function in order to return the value. +return() { + echo "$1" +} + +# set_default function gives the ability to move the setting of default +# env variable from docker file to the script thereby giving the ability to the +# user override it durin container start. +set_default() { + # docker initialized env variables with blank string and we can't just + # use -z flag as usually. + BLANK_STRING='""' + + VARIABLE="$1" + DEFAULT="$2" + + if [[ -z "$VARIABLE" || "$VARIABLE" == "$BLANK_STRING" ]]; then + + if [ -z "$DEFAULT" ]; then + error "You should specify default variable" + else + VARIABLE="$DEFAULT" + fi + fi + + return "$VARIABLE" +} + +# Set default variables if needed. +RPCUSER=$(set_default "$RPCUSER" "devuser") +RPCPASS=$(set_default "$RPCPASS" "devpass") +NETWORK=$(set_default "$NETWORK" "simnet") + +PARAMS="" +if [ "$NETWORK" != "mainnet" ]; then + PARAMS=$(echo --$NETWORK) +fi + +PARAMS=$(echo $PARAMS \ + "--rpccert=/rpc/rpc.cert" \ + "--rpcuser=$RPCUSER" \ + "--rpcpass=$RPCPASS" \ + "--rpcserver=rpcserver" \ +) + +PARAMS="$PARAMS $@" +exec btcctl $PARAMS diff --git a/docker/btcd/start-btcd.sh b/docker/btcd/start-btcd.sh new file mode 100644 index 00000000..24fd9d75 --- /dev/null +++ b/docker/btcd/start-btcd.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# exit from script if error was raised. +set -e + +# error function is used within a bash function in order to send the error +# message directly to the stderr output and exit. +error() { + echo "$1" > /dev/stderr + exit 0 +} + +# return is used within bash function in order to return the value. +return() { + echo "$1" +} + +# set_default function gives the ability to move the setting of default +# env variable from docker file to the script thereby giving the ability to the +# user override it durin container start. +set_default() { + # docker initialized env variables with blank string and we can't just + # use -z flag as usually. + BLANK_STRING='""' + + VARIABLE="$1" + DEFAULT="$2" + + if [[ -z "$VARIABLE" || "$VARIABLE" == "$BLANK_STRING" ]]; then + + if [ -z "$DEFAULT" ]; then + error "You should specify default variable" + else + VARIABLE="$DEFAULT" + fi + fi + + return "$VARIABLE" +} + +# Set default variables if needed. +RPCUSER=$(set_default "$RPCUSER" "devuser") +RPCPASS=$(set_default "$RPCPASS" "devpass") +DEBUG=$(set_default "$DEBUG" "info") +NETWORK=$(set_default "$NETWORK" "simnet") + +PARAMS="" +if [ "$NETWORK" != "mainnet" ]; then + PARAMS=$(echo --$NETWORK) +fi + +PARAMS=$(echo $PARAMS \ + "--debuglevel=$DEBUG" \ + "--rpcuser=$RPCUSER" \ + "--rpcpass=$RPCPASS" \ + "--datadir=/data" \ + "--logdir=/data" \ + "--rpccert=/rpc/rpc.cert" \ + "--rpckey=/rpc/rpc.key" \ + "--rpclisten=0.0.0.0" \ + "--txindex" +) + +# Set the mining flag only if address is non empty. +if [[ -n "$MINING_ADDRESS" ]]; then + PARAMS="$PARAMS --miningaddr=$MINING_ADDRESS" +fi + +# Add user parameters to command. +PARAMS="$PARAMS $@" + +# Print command and start bitcoin node. +echo "Command: btcd $PARAMS" +exec btcd $PARAMS diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..b9c5b480 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,108 @@ +version: '3' +services: + + btcd: + image: btcd + container_name: btcd + build: + context: ../ + dockerfile: docker/btcd/Dockerfile + volumes: + - shared:/rpc + - bitcoin:/data + environment: + - RPCUSER + - RPCPASS + - NETWORK + - DEBUG + - MINING_ADDRESS + ports: + - 8334:8334 + - 18334:18334 + - 18556:18556 + - 28902:28902 + entrypoint: ["./start-btcd.sh"] + + btcctl: + image: btcd + container_name: btcctl + build: + context: ../ + dockerfile: docker/btcd/Dockerfile + volumes: + - shared:/rpc + - bitcoin:/data + environment: + - RPCUSER + - RPCPASS + - NETWORK + - DEBUG + - MINING_ADDRESS + links: + - "btcd:rpcserver" + entrypoint: ["./start-btcctl.sh"] + + lnd: + image: lnd + container_name: lnd + build: + context: ../ + dockerfile: docker/lnd/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + - RPC_LISTEN="0.0.0.0:10009" + volumes: + - shared:/rpc + - lnd_dir:/root/.lnd + links: + - "btcd:blockchain" + entrypoint: ["./start-lnd.sh"] + + sqk: + image: sqk + container_name: sqk + build: + context: ../ + dockerfile: docker/sqk/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + - HEADLESS + volumes: + - shared:/rpc + - lnd_dir:/root/.lnd + links: + - "btcd:blockchain" + - "lnd:lnd" + sysctls: + - net.ipv6.conf.all.disable_ipv6=0 + entrypoint: ["./start-sqk.sh"] + + frontend: + container_name: frontend + build: + context: ../ + dockerfile: docker/frontend/Dockerfile + ports: + - 8080:80 + +volumes: + # btcctl and lnd containers. + shared: + driver: local + + # bitcoin volume is needed for maintaining blockchain persistence + # during btcd container recreation. + bitcoin: + driver: local + + # lnd_dir volume is needed for sharing tht tls certificate + lnd_dir: + driver: local diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile new file mode 100644 index 00000000..7b421196 --- /dev/null +++ b/docker/frontend/Dockerfile @@ -0,0 +1,11 @@ +FROM mhart/alpine-node:latest AS builder +WORKDIR /app +COPY ./frontend/squeakclient-frontend . +RUN npm install +RUN yarn run build + +FROM mhart/alpine-node +RUN yarn global add serve +WORKDIR /app +COPY --from=builder /app/build . +CMD ["serve", "-p", "80", "-s", "."] diff --git a/docker/lnd/Dockerfile b/docker/lnd/Dockerfile new file mode 100644 index 00000000..8cce0878 --- /dev/null +++ b/docker/lnd/Dockerfile @@ -0,0 +1,40 @@ +FROM golang:1.12-alpine as builder + +MAINTAINER Olaoluwa Osuntokun + +RUN apk update && \ + apk upgrade && \ + apk add git + +# Copy in the local repository to build from. +RUN git clone https://github.com/lightningnetwork/lnd.git --branch v0.9.0-beta + +# Force Go to use the cgo based DNS resolver. This is required to ensure DNS +# queries required to connect to linked containers succeed. +ENV GODEBUG netdns=cgo + +# Install dependencies and install/build lnd. +RUN apk add --no-cache --update alpine-sdk \ + git \ + make \ +&& cd lnd \ +&& make \ +&& make install + +# Start a new, final image to reduce size. +FROM alpine as final + +# Expose lnd ports (server, rpc). +EXPOSE 9735 10009 + +# Copy the binaries and entrypoint from the builder image. +COPY --from=builder /go/bin/lncli /bin/ +COPY --from=builder /go/bin/lnd /bin/ + +# Add bash. +RUN apk add --no-cache \ + bash + +# Copy the entrypoint script. +COPY "docker/lnd/start-lnd.sh" . +RUN chmod +x start-lnd.sh diff --git a/docker/lnd/start-lnd.sh b/docker/lnd/start-lnd.sh new file mode 100644 index 00000000..fd7cfbe0 --- /dev/null +++ b/docker/lnd/start-lnd.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# exit from script if error was raised. +set -e + +# error function is used within a bash function in order to send the error +# message directly to the stderr output and exit. +error() { + echo "$1" > /dev/stderr + exit 0 +} + +# return is used within bash function in order to return the value. +return() { + echo "$1" +} + +# set_default function gives the ability to move the setting of default +# env variable from docker file to the script thereby giving the ability to the +# user override it durin container start. +set_default() { + # docker initialized env variables with blank string and we can't just + # use -z flag as usually. + BLANK_STRING='""' + + VARIABLE="$1" + DEFAULT="$2" + + if [[ -z "$VARIABLE" || "$VARIABLE" == "$BLANK_STRING" ]]; then + + if [ -z "$DEFAULT" ]; then + error "You should specify default variable" + else + VARIABLE="$DEFAULT" + fi + fi + + return "$VARIABLE" +} + +# Set default variables if needed. +RPCUSER=$(set_default "$RPCUSER" "devuser") +RPCPASS=$(set_default "$RPCPASS" "devpass") +DEBUG=$(set_default "$DEBUG" "debug") +NETWORK=$(set_default "$NETWORK" "simnet") +CHAIN=$(set_default "$CHAIN" "bitcoin") +BACKEND="btcd" + +exec lnd \ + --noseedbackup \ + --logdir="/data" \ + "--$CHAIN.active" \ + "--$CHAIN.$NETWORK" \ + "--$CHAIN.node"="btcd" \ + "--$BACKEND.rpccert"="/rpc/rpc.cert" \ + "--$BACKEND.rpchost"="blockchain" \ + "--$BACKEND.rpcuser"="$RPCUSER" \ + "--$BACKEND.rpcpass"="$RPCPASS" \ + --rpclisten=0.0.0.0:10009 \ + --debuglevel="$DEBUG" \ + --tlsextradomain=lnd \ + "$@" diff --git a/docker/sqkclient/Dockerfile b/docker/sqkclient/Dockerfile new file mode 100644 index 00000000..5b803f00 --- /dev/null +++ b/docker/sqkclient/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:18.04 + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y \ + python3-pip \ + curl \ + git + +COPY requirements.txt / + +RUN pip3 install --upgrade pip +RUN pip3 install -r requirements.txt + +# Install the gRPC files for lnd. +RUN git clone https://github.com/googleapis/googleapis.git +RUN curl -o rpc.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/rpc.proto + +COPY . /app +RUN cp -r googleapis /app +RUN cp rpc.proto /app/squeaknode/common + +WORKDIR /app + +RUN python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. squeaknode/common/rpc.proto +RUN python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. squeaknode/client/rpc/route_guide.proto +RUN python3 setup.py install + +# Copy the entrypoint script. +COPY "docker/sqkclient/start-sqkclient.sh" . +RUN chmod +x start-sqkclient.sh diff --git a/docker/sqkclient/start-sqkclient.sh b/docker/sqkclient/start-sqkclient.sh new file mode 100755 index 00000000..360ee3dc --- /dev/null +++ b/docker/sqkclient/start-sqkclient.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# exit from script if error was raised. +set -e + +# error function is used within a bash function in order to send the error +# message directly to the stderr output and exit. +error() { + echo "$1" > /dev/stderr + exit 0 +} + +# return is used within bash function in order to return the value. +return() { + echo "$1" +} + +# set_default function gives the ability to move the setting of default +# env variable from docker file to the script thereby giving the ability to the +# user override it durin container start. +set_default() { + # docker initialized env variables with blank string and we can't just + # use -z flag as usually. + BLANK_STRING='""' + + VARIABLE="$1" + DEFAULT="$2" + + if [[ -z "$VARIABLE" || "$VARIABLE" == "$BLANK_STRING" ]]; then + + if [ -z "$DEFAULT" ]; then + error "You should specify default variable" + else + VARIABLE="$DEFAULT" + fi + fi + + return "$VARIABLE" +} + +# Set default variables if needed. +RPCUSER=$(set_default "$RPCUSER" "devuser") +RPCPASS=$(set_default "$RPCPASS" "devpass") +DEBUG=$(set_default "$DEBUG" "debug") +NETWORK=$(set_default "$NETWORK" "simnet") +CHAIN=$(set_default "$CHAIN" "bitcoin") +BACKEND="btcd" + +# This is a hack that is needed because python-bitcoinlib does not +# currently support simnet network. +BTCD_RPC_PORT="18332" +if [[ "$NETWORK" == "mainnet" ]]; then + BTCD_RPC_PORT="8334" +elif [[ "$NETWORK" == "testnet" ]]; then + BTCD_RPC_PORT="18334" +elif [[ "$NETWORK" == "regtest" ]]; then + BTCD_RPC_PORT="18445" +elif [[ "$NETWORK" == "simnet" ]]; then + BTCD_RPC_PORT="18556" +fi + + +# Add btcd's RPC TLS certificate to system Certificate Authority list. exec runsqueak \ +cp /rpc/rpc.cert /usr/share/ca-certificates/btcd.crt +echo btcd.crt >> /etc/ca-certificates.conf +update-ca-certificates + +# To make python requests use the system ca-certificates bundle, it +# needs to be told to use it over its own embedded bundle +export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt + +exec runsqueaknodeclient \ + "--network"="$NETWORK" \ + "--rpcuser"="$RPCUSER" \ + "--rpcpass"="$RPCPASS" \ + "--$BACKEND.rpchost"="blockchain" \ + "--$BACKEND.rpcport"="$BTCD_RPC_PORT" \ + "--$BACKEND.rpcuser"="$RPCUSER" \ + "--$BACKEND.rpcpass"="$RPCPASS" \ + "--lnd.rpchost"="lnd" \ + --log-level="$DEBUG" \ diff --git a/docker/test/Dockerfile b/docker/test/Dockerfile new file mode 100644 index 00000000..5898765d --- /dev/null +++ b/docker/test/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:18.04 + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y \ + python3-pip \ + curl \ + git + +COPY requirements.txt / + +RUN pip3 install --upgrade pip +RUN pip3 install -r requirements.txt + +RUN git clone https://github.com/googleapis/googleapis.git + +Run mkdir /app +COPY ./squeaknode/client/rpc/route_guide.proto /app +RUN cp -r googleapis /app + +WORKDIR /app + +RUN python3 -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. route_guide.proto + +# Copy the entrypoint script. +COPY "itests/test.sh" . +COPY "itests/test.py" . +RUN chmod +x test.sh + +CMD ["bash", "test.sh"] diff --git a/itests/docker-compose.yml b/itests/docker-compose.yml new file mode 100644 index 00000000..3eae8ec0 --- /dev/null +++ b/itests/docker-compose.yml @@ -0,0 +1,200 @@ +version: '3' +services: + + btcd: + image: btcd + container_name: btcd + build: + context: ../ + dockerfile: docker/btcd/Dockerfile + volumes: + - shared_test:/rpc + - bitcoin_test:/data + environment: + - RPCUSER + - RPCPASS + - NETWORK + - DEBUG + - MINING_ADDRESS + ports: + - 8334:8334 + - 18334:18334 + - 18556:18556 + - 28902:28902 + entrypoint: ["./start-btcd.sh"] + + btcctl: + image: btcd + container_name: btcctl + build: + context: ../ + dockerfile: docker/btcd/Dockerfile + volumes: + - shared_test:/rpc + - bitcoin_test:/data + environment: + - RPCUSER + - RPCPASS + - NETWORK + - DEBUG + - MINING_ADDRESS + links: + - "btcd:rpcserver" + entrypoint: ["./start-btcctl.sh"] + + lnd_alice: + image: lnd + container_name: lnd_alice + build: + context: ../ + dockerfile: docker/lnd/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + volumes: + - shared_test:/rpc + - lnd_dir_alice:/root/.lnd + links: + - "btcd:blockchain" + entrypoint: ["./start-lnd.sh"] + + lnd_bob: + image: lnd + container_name: lnd_bob + build: + context: ../ + dockerfile: docker/lnd/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + volumes: + - shared_test:/rpc + - lnd_dir_bob:/root/.lnd + links: + - "btcd:blockchain" + entrypoint: ["./start-lnd.sh"] + + lnd_carol: + image: lnd + container_name: lnd_carol + build: + context: ../ + dockerfile: docker/lnd/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + volumes: + - shared_test:/rpc + - lnd_dir_carol:/root/.lnd + links: + - "btcd:blockchain" + entrypoint: ["./start-lnd.sh"] + + sqkclient_alice: + image: sqkclient + container_name: sqkclient_alice + build: + context: ../ + dockerfile: docker/sqkclient/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + - HEADLESS + volumes: + - shared_test:/rpc + - lnd_dir_alice:/root/.lnd + links: + - "btcd:blockchain" + - "lnd_alice:lnd" + sysctls: + - net.ipv6.conf.all.disable_ipv6=0 + entrypoint: ["./start-sqkclient.sh"] + + sqkclient_bob: + image: sqkclient + container_name: sqkclient_bob + build: + context: ../ + dockerfile: docker/sqkclient/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + - HEADLESS + volumes: + - shared_test:/rpc + - lnd_dir_bob:/root/.lnd + links: + - "btcd:blockchain" + - "lnd_bob:lnd" + sysctls: + - net.ipv6.conf.all.disable_ipv6=0 + entrypoint: ["./start-sqkclient.sh"] + + sqkclient_carol: + image: sqkclient + container_name: sqkclient_carol + build: + context: ../ + dockerfile: docker/sqkclient/Dockerfile + environment: + - RPCUSER + - RPCPASS + - NETWORK + - CHAIN + - DEBUG + - HEADLESS + volumes: + - shared_test:/rpc + - lnd_dir_carol:/root/.lnd + links: + - "btcd:blockchain" + - "lnd_carol:lnd" + sysctls: + - net.ipv6.conf.all.disable_ipv6=0 + entrypoint: ["./start-sqkclient.sh"] + + test: + image: test + container_name: test + build: + context: ../ + dockerfile: docker/test/Dockerfile + links: + - "btcd:blockchain" + - "sqkclient_alice:sqkclient_alice" + - "sqkclient_bob:sqkclient_bob" + - "sqkclient_carol:sqkclient_carol" + command: tail -f /dev/null + +volumes: + # btcctl and lnd containers. + shared_test: + driver: local + + # bitcoin_test volume is needed for maintaining blockchain persistence + # during btcd container recreation. + bitcoin_test: + driver: local + + # lnd_dir volume is needed for sharing tht tls certificate + lnd_dir_alice: + driver: local + lnd_dir_bob: + driver: local + lnd_dir_carol: + driver: local diff --git a/itests/run_itest.sh b/itests/run_itest.sh new file mode 100755 index 00000000..3c5de7b2 --- /dev/null +++ b/itests/run_itest.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +cd itests +docker-compose down --volumes +docker-compose build +docker-compose up -d + +# Initialize the blockchain with miner rewards going to alice. +sleep 10 +alice_address=$(docker exec -it lnd_alice lncli --network=simnet newaddress np2wkh | jq .address -r) +MINING_ADDRESS=$alice_address docker-compose up -d btcd +echo "Mining 400 blocks to address: $alice_address ..." +docker-compose run btcctl generate 400 +echo "Finished mining blocks." +sleep 10 + +echo "Running test.sh...." +docker-compose run test ./test.sh diff --git a/itests/test.py b/itests/test.py new file mode 100644 index 00000000..b45d200d --- /dev/null +++ b/itests/test.py @@ -0,0 +1,129 @@ +# Copyright 2015 gRPC authors. +# +# 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. +"""The Python implementation of the gRPC route guide client.""" +from __future__ import print_function + +import logging +import random +import time + +import grpc +import route_guide_pb2 +import route_guide_pb2_grpc + + +def make_route_note(message, latitude, longitude): + return route_guide_pb2.RouteNote( + message=message, + location=route_guide_pb2.Point(latitude=latitude, longitude=longitude)) + + +def guide_get_one_feature(stub, point): + feature = stub.GetFeature(point) + if not feature.location: + print("Server returned incomplete feature") + return + + if feature.name: + print("Feature called %s at %s" % (feature.name, feature.location)) + else: + print("Found no feature at %s" % feature.location) + + +def guide_get_feature(stub): + guide_get_one_feature( + stub, + route_guide_pb2.Point( + latitude=409146138, + longitude=-746188906, + ), + ) + guide_get_one_feature( + stub, + route_guide_pb2.Point( + latitude=0, + longitude=0, + ), + ) + + +def guide_list_features(stub): + rectangle = route_guide_pb2.Rectangle( + lo=route_guide_pb2.Point(latitude=400000000, longitude=-750000000), + hi=route_guide_pb2.Point(latitude=420000000, longitude=-730000000)) + print("Looking for features between 40, -75 and 42, -73") + + features = stub.ListFeatures(rectangle) + + for feature in features: + print("Feature called %s at %s" % (feature.name, feature.location)) + + +def generate_route(feature_list): + for _ in range(0, 10): + random_feature = feature_list[random.randint(0, len(feature_list) - 1)] + print("Visiting point %s" % random_feature.location) + yield random_feature.location + + +def generate_messages(): + messages = [ + make_route_note("First message", 0, 0), + make_route_note("Second message", 0, 1), + make_route_note("Third message", 1, 0), + make_route_note("Fourth message", 0, 0), + make_route_note("Fifth message", 1, 0), + ] + for msg in messages: + print("Sending %s at %s" % (msg.message, msg.location)) + yield msg + + +def guide_route_chat(stub): + responses = stub.RouteChat(generate_messages()) + for response in responses: + print("Received message %s at %s" % (response.message, + response.location)) + + +def run(): + # NOTE(gRPC Python Team): .close() is possible on a channel and should be + # used in circumstances in which the with statement does not fit the needs + # of the code. + with grpc.insecure_channel('sqkclient_alice:50051') as alice_channel, \ + grpc.insecure_channel('sqkclient_bob:50051') as bob_channel, \ + grpc.insecure_channel('sqkclient_carol:50051') as carol_channel: + + # Make the stubs + alice_stub = route_guide_pb2_grpc.RouteGuideStub(alice_channel) + bob_stub = route_guide_pb2_grpc.RouteGuideStub(bob_channel) + carol_stub = route_guide_pb2_grpc.RouteGuideStub(carol_channel) + + print("-------------- GetFeature --------------") + guide_get_feature(alice_stub) + print("-------------- ListFeatures --------------") + guide_list_features(alice_stub) + print("-------------- RouteChat --------------") + guide_route_chat(alice_stub) + + print("-------------- WalletBalance --------------") + balance = alice_stub.WalletBalance(route_guide_pb2.WalletBalanceRequest()) + print("Balance: %s" % balance) + print("Balance confirmed %s %s" % (balance.total_balance, balance.total_balance)) + assert balance.total_balance == 1505000000000 + + +if __name__ == '__main__': + logging.basicConfig() + run() diff --git a/itests/test.sh b/itests/test.sh new file mode 100644 index 00000000..27759385 --- /dev/null +++ b/itests/test.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python3 test.py diff --git a/requirements.txt b/requirements.txt index 73e9f73e..6e52dbd3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,7 @@ flask==1.1.1 squeaklib +argparse +googleapis-common-protos +grpcio +grpcio-tools +requests diff --git a/setup.py b/setup.py index 70e0fdb5..94dbf3ec 100644 --- a/setup.py +++ b/setup.py @@ -18,4 +18,9 @@ setup( zip_safe=False, install_requires=["flask"], extras_require={"test": ["pytest", "coverage"]}, + entry_points={ + 'console_scripts': [ + 'runsqueaknodeclient = squeaknode.client.main:main', + ], + }, ) diff --git a/squeaknode/client/clientsqueaknode.py b/squeaknode/client/clientsqueaknode.py new file mode 100644 index 00000000..0bd7f997 --- /dev/null +++ b/squeaknode/client/clientsqueaknode.py @@ -0,0 +1,67 @@ +import logging + +from squeaknode.common.blockchain_client import BlockchainClient +from squeaknode.common.lightning_client import LightningClient +from squeaknode.common.squeak_maker import SqueakMaker + + +logger = logging.getLogger(__name__) + + +class SqueakNodeClient(object): + """Network node that handles client commands. + """ + + def __init__(self, blockchain_client: BlockchainClient, lightning_client: LightningClient) -> None: + self.blockchain_client = blockchain_client + self.lightning_client = lightning_client + + @property + def address(self): + return (self.peer_server.ip, self.peer_server.port) + + @property + def signing_key(self): + pass + + def get_address(self): + pass + + def generate_signing_key(self): + pass + + def make_squeak(self, content): + key = self.get_signing_key() + if key is None: + logger.error('Missing signing key.') + raise MissingSigningKeyError() + else: + squeak_maker = SqueakMaker(key, self.blockchain) + squeak = squeak_maker.make_squeak(content) + logger.info('Made squeak: {}'.format(squeak)) + self.add_squeak(squeak) + return squeak + + def add_squeak(self, squeak): + self.squeaks_access.add_squeak(squeak) + + def listen_squeaks_changed(self, callback): + self.squeaks_access.listen_squeaks_changed(callback) + + def add_follow(self, follow): + pass + + def listen_follows_changed(self, callback): + pass + + def get_wallet_balance(self): + return self.lightning_client.get_wallet_balance() + + +class ClientNodeError(Exception): + pass + + +class MissingSigningKeyError(ClientNodeError): + def __str__(self): + return 'Missing signing key.' diff --git a/squeaknode/client/main.py b/squeaknode/client/main.py new file mode 100644 index 00000000..518f626a --- /dev/null +++ b/squeaknode/client/main.py @@ -0,0 +1,167 @@ +import argparse +import logging +import threading +import time + +from squeak.params import SelectParams + +from squeaknode.common.blockchain_client import BlockchainClient +from squeaknode.common.lightning_client import LightningClient +from squeaknode.common.btcd_blockchain_client import BTCDBlockchainClient +from squeaknode.common.lnd_lightning_client import LNDLightningClient +from squeaknode.client.rpc.route_guide_server import RouteGuideServicer +from squeaknode.client.clientsqueaknode import SqueakNodeClient + + + +def load_blockchain_client(rpc_host, rpc_port, rpc_user, rpc_pass) -> BlockchainClient: + return BTCDBlockchainClient( + host=rpc_host, + port=rpc_port, + rpc_user=rpc_user, + rpc_password=rpc_pass, + ) + + +def load_lightning_client(rpc_host, rpc_port, network) -> LightningClient: + return LNDLightningClient( + host=rpc_host, + port=rpc_port, + network=network, + ) + + +def _start_node(blockchain_client, lightning_client): + node = SqueakNodeClient(blockchain_client, lightning_client) + return node + + +def _start_route_guide_rpc_server(node): + server = RouteGuideServicer(node) + thread = threading.Thread( + target=server.serve, + args=(), + ) + thread.daemon = True + thread.start() + return server, thread + + +def parse_args(): + parser = argparse.ArgumentParser( + description="squeaknode runs a node using squeak protocol. ", + ) + parser.add_argument( + '--network', + dest='network', + type=str, + default='mainnet', + choices=['mainnet', 'testnet', 'regtest', 'simnet'], + help='The bitcoin network to use', + ) + parser.add_argument( + '--rpcport', + dest='rpcport', + type=int, + default=None, + help='RPC server port number', + ) + parser.add_argument( + '--rpcuser', + dest='rpcuser', + type=str, + default='', + help='RPC username', + ) + parser.add_argument( + '--rpcpass', + dest='rpcpass', + type=str, + default='', + help='RPC password', + ) + parser.add_argument( + '--btcd.rpchost', + dest='btcd_rpchost', + type=str, + default='localhost', + help='Blockchain (bitcoin) backend hostname', + ) + parser.add_argument( + '--btcd.rpcport', + dest='btcd_rpcport', + type=int, + default=18332, + help='Blockchain (bitcoin) backend port', + ) + parser.add_argument( + '--btcd.rpcuser', + dest='btcd_rpcuser', + type=str, + default='', + help='Blockchain (bitcoin) backend username', + ) + parser.add_argument( + '--btcd.rpcpass', + dest='btcd_rpcpass', + type=str, + default='', + help='Blockchain (bitcoin) backend password', + ) + parser.add_argument( + '--lnd.rpchost', + dest='lnd_rpchost', + type=str, + default='localhost', + help='Lightning network backend hostname', + ) + parser.add_argument( + '--lnd.rpcport', + dest='lnd_rpcport', + type=int, + default=10009, + help='Lightning network backend port', + ) + parser.add_argument( + '--log-level', + dest='log_level', + type=str, + default='info', + help='Logging level', + ) + return parser.parse_args() + + +def main(): + logging.basicConfig(level=logging.ERROR) + args = parse_args() + level = args.log_level.upper() + print("level: " + level, flush=True) + logging.getLogger().setLevel(level) + + print('network:', args.network, flush=True) + SelectParams(args.network) + + blockchain_client = load_blockchain_client( + args.btcd_rpchost, + args.btcd_rpcport, + args.btcd_rpcuser, + args.btcd_rpcpass, + ) + lightning_client = load_lightning_client( + args.lnd_rpchost, + args.lnd_rpcport, + args.network, + ) + + node = _start_node(blockchain_client, lightning_client) + + # start rpc server + route_guide_server, route_guide_server_thread = _start_route_guide_rpc_server(node) + + while True: + time.sleep(10) + + +if __name__ == '__main__': + main() diff --git a/squeaknode/client/rpc/__init__.py b/squeaknode/client/rpc/__init__.py new file mode 100644 index 00000000..b794fd40 --- /dev/null +++ b/squeaknode/client/rpc/__init__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/squeaknode/client/rpc/route_guide.proto b/squeaknode/client/rpc/route_guide.proto new file mode 100644 index 00000000..a66113e9 --- /dev/null +++ b/squeaknode/client/rpc/route_guide.proto @@ -0,0 +1,253 @@ +// Copyright 2015 gRPC authors. +// +// 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. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.routeguide"; +option java_outer_classname = "RouteGuideProto"; +option objc_class_prefix = "RTG"; + +package routeguide; + +// Interface exported by the server. +service RouteGuide { + // A simple RPC. + // + // Obtains the feature at a given position. + // + // A feature with an empty name is returned if there's no feature at the given + // position. + rpc GetFeature(Point) returns (Feature) {} + + // A server-to-client streaming RPC. + // + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + rpc ListFeatures(Rectangle) returns (stream Feature) {} + + // A client-to-server streaming RPC. + // + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + rpc RecordRoute(stream Point) returns (RouteSummary) {} + + // A Bidirectional streaming RPC. + // + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} + + /** sqk: `walletbalance` + WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + confirmed unspent outputs and all unconfirmed unspent outputs under control + of the wallet. + */ + rpc WalletBalance (WalletBalanceRequest) returns (WalletBalanceResponse) {} + + /** sqk: `addpeer` + */ + rpc ConnectHost (ConnectHostRequest) returns (ConnectHostResponse) {} + + /** sqk: `disconnectpeer` + */ + rpc DisconnectPeer (DisconnectPeerRequest) returns (DisconnectPeerResponse) {} + + /** sqk: `listpeers` + ListPeers returns a verbose listing of all currently active peers. + */ + rpc ListPeers (ListPeersRequest) returns (ListPeersResponse) {} + + /** sqk: `makesqueak` + */ + rpc MakeSqueak (MakeSqueakRequest) returns (MakeSqueakResponse) {} + + /** sqk: `generatesigningKey` + */ + rpc GenerateSigningKey (GenerateSigningKeyRequest) returns (GenerateSigningKeyResponse) {} +} + +// Points are represented as latitude-longitude pairs in the E7 representation +// (degrees multiplied by 10**7 and rounded to the nearest integer). +// Latitudes should be in the range +/- 90 degrees and longitude should be in +// the range +/- 180 degrees (inclusive). +message Point { + int32 latitude = 1; + int32 longitude = 2; +} + +// A latitude-longitude rectangle, represented as two diagonally opposite +// points "lo" and "hi". +message Rectangle { + // One corner of the rectangle. + Point lo = 1; + + // The other corner of the rectangle. + Point hi = 2; +} + +// A feature names something at a given point. +// +// If a feature could not be named, the name is empty. +message Feature { + // The name of the feature. + string name = 1; + + // The point where the feature is detected. + Point location = 2; +} + +// A RouteNote is a message sent while at a given point. +message RouteNote { + // The location from which the message is sent. + Point location = 1; + + // The message to be sent. + string message = 2; +} + +// A RouteSummary is received in response to a RecordRoute rpc. +// +// It contains the number of individual points received, the number of +// detected features, and the total distance covered as the cumulative sum of +// the distance between each point. +message RouteSummary { + // The number of points received. + int32 point_count = 1; + + // The number of known features passed while traversing the route. + int32 feature_count = 2; + + // The distance covered in metres. + int32 distance = 3; + + // The duration of the traversal in seconds. + int32 elapsed_time = 4; +} + +message WalletBalanceRequest { +} + +message WalletBalanceResponse { + /// The balance of the wallet + int64 total_balance = 1; + + /// The confirmed balance of a wallet(with >= 1 confirmations) + int64 confirmed_balance = 2; + + /// The unconfirmed balance of a wallet(with 0 confirmations) + int64 unconfirmed_balance = 3; +} + +// message GetPeersRequest { +// } + +// message GetPeersResponse { +// /// The balance of the wallet +// int64 total_balance = 1 [json_name = "total_balance"]; + +// /// The confirmed balance of a wallet(with >= 1 confirmations) +// int64 confirmed_balance = 2 [json_name = "confirmed_balance"]; + +// /// The unconfirmed balance of a wallet(with 0 confirmations) +// int64 unconfirmed_balance = 3 [json_name = "unconfirmed_balance"]; +// } + +message ConnectHostRequest { + // The address of the peer to add. + string host = 1; +} + +message ConnectHostResponse { +} + +message DisconnectPeerRequest { + Addr addr = 1; +} + +message DisconnectPeerResponse { +} + +message ListPeersRequest { +} + +message ListPeersResponse { + /// The list of currently connected peers + repeated Peer peers = 1; +} + +message MakeSqueakRequest { + /// Content of the squeak to be made. + string content = 1; +} + +message MakeSqueakResponse { + /// The squeak. + Squeak squeak = 1; +} + +message GenerateSigningKeyRequest {} + +message GenerateSigningKeyResponse { + /// Address of the signing key. + string address = 1; +} + +message Addr { + string host = 1; + + uint64 port = 2; +} + +message Peer { + /// Address + Addr addr = 1; + + /// Bytes of data transmitted to this peer + uint64 bytes_sent = 2; + + /// Bytes of data transmitted from this peer + uint64 bytes_recv = 3; + + /// Satoshis sent to this peer + int64 sat_sent = 4; + + /// Satoshis received from this peer + int64 sat_recv = 5; + + /// A channel is inbound if the counterparty initiated the connection + bool inbound = 6; + + /// Ping time to this peer + int64 ping_time = 7; +} + +message Squeak { + /// Hash of the squeak. + bytes hash = 1; + + /// Address of the creator. + string address = 2; + + /// Content of the squeak. + string content = 3; + + /// Block height of the squeak. + uint64 block_height = 4; + + /// Timestamp of the squeak. + uint64 timestamp = 5; +} diff --git a/squeaknode/client/rpc/route_guide_db.json b/squeaknode/client/rpc/route_guide_db.json new file mode 100644 index 00000000..9d6a980a --- /dev/null +++ b/squeaknode/client/rpc/route_guide_db.json @@ -0,0 +1,601 @@ +[{ + "location": { + "latitude": 407838351, + "longitude": -746143763 + }, + "name": "Patriots Path, Mendham, NJ 07945, USA" +}, { + "location": { + "latitude": 408122808, + "longitude": -743999179 + }, + "name": "101 New Jersey 10, Whippany, NJ 07981, USA" +}, { + "location": { + "latitude": 413628156, + "longitude": -749015468 + }, + "name": "U.S. 6, Shohola, PA 18458, USA" +}, { + "location": { + "latitude": 419999544, + "longitude": -740371136 + }, + "name": "5 Conners Road, Kingston, NY 12401, USA" +}, { + "location": { + "latitude": 414008389, + "longitude": -743951297 + }, + "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA" +}, { + "location": { + "latitude": 419611318, + "longitude": -746524769 + }, + "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA" +}, { + "location": { + "latitude": 406109563, + "longitude": -742186778 + }, + "name": "4001 Tremley Point Road, Linden, NJ 07036, USA" +}, { + "location": { + "latitude": 416802456, + "longitude": -742370183 + }, + "name": "352 South Mountain Road, Wallkill, NY 12589, USA" +}, { + "location": { + "latitude": 412950425, + "longitude": -741077389 + }, + "name": "Bailey Turn Road, Harriman, NY 10926, USA" +}, { + "location": { + "latitude": 412144655, + "longitude": -743949739 + }, + "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA" +}, { + "location": { + "latitude": 415736605, + "longitude": -742847522 + }, + "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA" +}, { + "location": { + "latitude": 413843930, + "longitude": -740501726 + }, + "name": "162 Merrill Road, Highland Mills, NY 10930, USA" +}, { + "location": { + "latitude": 410873075, + "longitude": -744459023 + }, + "name": "Clinton Road, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 412346009, + "longitude": -744026814 + }, + "name": "16 Old Brook Lane, Warwick, NY 10990, USA" +}, { + "location": { + "latitude": 402948455, + "longitude": -747903913 + }, + "name": "3 Drake Lane, Pennington, NJ 08534, USA" +}, { + "location": { + "latitude": 406337092, + "longitude": -740122226 + }, + "name": "6324 8th Avenue, Brooklyn, NY 11220, USA" +}, { + "location": { + "latitude": 406421967, + "longitude": -747727624 + }, + "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA" +}, { + "location": { + "latitude": 416318082, + "longitude": -749677716 + }, + "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA" +}, { + "location": { + "latitude": 415301720, + "longitude": -748416257 + }, + "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA" +}, { + "location": { + "latitude": 402647019, + "longitude": -747071791 + }, + "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA" +}, { + "location": { + "latitude": 412567807, + "longitude": -741058078 + }, + "name": "New York State Reference Route 987E, Southfields, NY 10975, USA" +}, { + "location": { + "latitude": 416855156, + "longitude": -744420597 + }, + "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA" +}, { + "location": { + "latitude": 404663628, + "longitude": -744820157 + }, + "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA" +}, { + "location": { + "latitude": 407113723, + "longitude": -749746483 + }, + "name": "" +}, { + "location": { + "latitude": 402133926, + "longitude": -743613249 + }, + "name": "" +}, { + "location": { + "latitude": 400273442, + "longitude": -741220915 + }, + "name": "" +}, { + "location": { + "latitude": 411236786, + "longitude": -744070769 + }, + "name": "" +}, { + "location": { + "latitude": 411633782, + "longitude": -746784970 + }, + "name": "211-225 Plains Road, Augusta, NJ 07822, USA" +}, { + "location": { + "latitude": 415830701, + "longitude": -742952812 + }, + "name": "" +}, { + "location": { + "latitude": 413447164, + "longitude": -748712898 + }, + "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA" +}, { + "location": { + "latitude": 405047245, + "longitude": -749800722 + }, + "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA" +}, { + "location": { + "latitude": 418858923, + "longitude": -746156790 + }, + "name": "" +}, { + "location": { + "latitude": 417951888, + "longitude": -748484944 + }, + "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA" +}, { + "location": { + "latitude": 407033786, + "longitude": -743977337 + }, + "name": "26 East 3rd Street, New Providence, NJ 07974, USA" +}, { + "location": { + "latitude": 417548014, + "longitude": -740075041 + }, + "name": "" +}, { + "location": { + "latitude": 410395868, + "longitude": -744972325 + }, + "name": "" +}, { + "location": { + "latitude": 404615353, + "longitude": -745129803 + }, + "name": "" +}, { + "location": { + "latitude": 406589790, + "longitude": -743560121 + }, + "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA" +}, { + "location": { + "latitude": 414653148, + "longitude": -740477477 + }, + "name": "18 Lannis Avenue, New Windsor, NY 12553, USA" +}, { + "location": { + "latitude": 405957808, + "longitude": -743255336 + }, + "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA" +}, { + "location": { + "latitude": 411733589, + "longitude": -741648093 + }, + "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA" +}, { + "location": { + "latitude": 412676291, + "longitude": -742606606 + }, + "name": "1270 Lakes Road, Monroe, NY 10950, USA" +}, { + "location": { + "latitude": 409224445, + "longitude": -748286738 + }, + "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA" +}, { + "location": { + "latitude": 406523420, + "longitude": -742135517 + }, + "name": "652 Garden Street, Elizabeth, NJ 07202, USA" +}, { + "location": { + "latitude": 401827388, + "longitude": -740294537 + }, + "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA" +}, { + "location": { + "latitude": 410564152, + "longitude": -743685054 + }, + "name": "13-17 Stanley Street, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 408472324, + "longitude": -740726046 + }, + "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA" +}, { + "location": { + "latitude": 412452168, + "longitude": -740214052 + }, + "name": "5 White Oak Lane, Stony Point, NY 10980, USA" +}, { + "location": { + "latitude": 409146138, + "longitude": -746188906 + }, + "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" +}, { + "location": { + "latitude": 404701380, + "longitude": -744781745 + }, + "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 409642566, + "longitude": -746017679 + }, + "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA" +}, { + "location": { + "latitude": 408031728, + "longitude": -748645385 + }, + "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA" +}, { + "location": { + "latitude": 413700272, + "longitude": -742135189 + }, + "name": "367 Prospect Road, Chester, NY 10918, USA" +}, { + "location": { + "latitude": 404310607, + "longitude": -740282632 + }, + "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA" +}, { + "location": { + "latitude": 409319800, + "longitude": -746201391 + }, + "name": "11 Ward Street, Mount Arlington, NJ 07856, USA" +}, { + "location": { + "latitude": 406685311, + "longitude": -742108603 + }, + "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA" +}, { + "location": { + "latitude": 419018117, + "longitude": -749142781 + }, + "name": "43 Dreher Road, Roscoe, NY 12776, USA" +}, { + "location": { + "latitude": 412856162, + "longitude": -745148837 + }, + "name": "Swan Street, Pine Island, NY 10969, USA" +}, { + "location": { + "latitude": 416560744, + "longitude": -746721964 + }, + "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA" +}, { + "location": { + "latitude": 405314270, + "longitude": -749836354 + }, + "name": "" +}, { + "location": { + "latitude": 414219548, + "longitude": -743327440 + }, + "name": "" +}, { + "location": { + "latitude": 415534177, + "longitude": -742900616 + }, + "name": "565 Winding Hills Road, Montgomery, NY 12549, USA" +}, { + "location": { + "latitude": 406898530, + "longitude": -749127080 + }, + "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA" +}, { + "location": { + "latitude": 407586880, + "longitude": -741670168 + }, + "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA" +}, { + "location": { + "latitude": 400106455, + "longitude": -742870190 + }, + "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA" +}, { + "location": { + "latitude": 400066188, + "longitude": -746793294 + }, + "name": "" +}, { + "location": { + "latitude": 418803880, + "longitude": -744102673 + }, + "name": "40 Mountain Road, Napanoch, NY 12458, USA" +}, { + "location": { + "latitude": 414204288, + "longitude": -747895140 + }, + "name": "" +}, { + "location": { + "latitude": 414777405, + "longitude": -740615601 + }, + "name": "" +}, { + "location": { + "latitude": 415464475, + "longitude": -747175374 + }, + "name": "48 North Road, Forestburgh, NY 12777, USA" +}, { + "location": { + "latitude": 404062378, + "longitude": -746376177 + }, + "name": "" +}, { + "location": { + "latitude": 405688272, + "longitude": -749285130 + }, + "name": "" +}, { + "location": { + "latitude": 400342070, + "longitude": -748788996 + }, + "name": "" +}, { + "location": { + "latitude": 401809022, + "longitude": -744157964 + }, + "name": "" +}, { + "location": { + "latitude": 404226644, + "longitude": -740517141 + }, + "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA" +}, { + "location": { + "latitude": 410322033, + "longitude": -747871659 + }, + "name": "" +}, { + "location": { + "latitude": 407100674, + "longitude": -747742727 + }, + "name": "" +}, { + "location": { + "latitude": 418811433, + "longitude": -741718005 + }, + "name": "213 Bush Road, Stone Ridge, NY 12484, USA" +}, { + "location": { + "latitude": 415034302, + "longitude": -743850945 + }, + "name": "" +}, { + "location": { + "latitude": 411349992, + "longitude": -743694161 + }, + "name": "" +}, { + "location": { + "latitude": 404839914, + "longitude": -744759616 + }, + "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 414638017, + "longitude": -745957854 + }, + "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA" +}, { + "location": { + "latitude": 412127800, + "longitude": -740173578 + }, + "name": "" +}, { + "location": { + "latitude": 401263460, + "longitude": -747964303 + }, + "name": "" +}, { + "location": { + "latitude": 412843391, + "longitude": -749086026 + }, + "name": "" +}, { + "location": { + "latitude": 418512773, + "longitude": -743067823 + }, + "name": "" +}, { + "location": { + "latitude": 404318328, + "longitude": -740835638 + }, + "name": "42-102 Main Street, Belford, NJ 07718, USA" +}, { + "location": { + "latitude": 419020746, + "longitude": -741172328 + }, + "name": "" +}, { + "location": { + "latitude": 404080723, + "longitude": -746119569 + }, + "name": "" +}, { + "location": { + "latitude": 401012643, + "longitude": -744035134 + }, + "name": "" +}, { + "location": { + "latitude": 404306372, + "longitude": -741079661 + }, + "name": "" +}, { + "location": { + "latitude": 403966326, + "longitude": -748519297 + }, + "name": "" +}, { + "location": { + "latitude": 405002031, + "longitude": -748407866 + }, + "name": "" +}, { + "location": { + "latitude": 409532885, + "longitude": -742200683 + }, + "name": "" +}, { + "location": { + "latitude": 416851321, + "longitude": -742674555 + }, + "name": "" +}, { + "location": { + "latitude": 406411633, + "longitude": -741722051 + }, + "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA" +}, { + "location": { + "latitude": 413069058, + "longitude": -744597778 + }, + "name": "261 Van Sickle Road, Goshen, NY 10924, USA" +}, { + "location": { + "latitude": 418465462, + "longitude": -746859398 + }, + "name": "" +}, { + "location": { + "latitude": 411733222, + "longitude": -744228360 + }, + "name": "" +}, { + "location": { + "latitude": 410248224, + "longitude": -747127767 + }, + "name": "3 Hasta Way, Newton, NJ 07860, USA" +}] diff --git a/squeaknode/client/rpc/route_guide_resources.py b/squeaknode/client/rpc/route_guide_resources.py new file mode 100644 index 00000000..ebab0d10 --- /dev/null +++ b/squeaknode/client/rpc/route_guide_resources.py @@ -0,0 +1,36 @@ +# Copyright 2015 gRPC authors. +# +# 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. +"""Common resources used in the gRPC route guide example.""" +import json + +from squeaknode.client.rpc import route_guide_pb2 + + +def read_route_guide_database(): + """Reads the route guide database. + + Returns: + The full contents of the route guide database as a sequence of + route_guide_pb2.Features. + """ + feature_list = [] + with open("squeaknode/client/rpc/route_guide_db.json") as route_guide_db_file: + for item in json.load(route_guide_db_file): + feature = route_guide_pb2.Feature( + name=item["name"], + location=route_guide_pb2.Point( + latitude=item["location"]["latitude"], + longitude=item["location"]["longitude"])) + feature_list.append(feature) + return feature_list diff --git a/squeaknode/client/rpc/route_guide_server.py b/squeaknode/client/rpc/route_guide_server.py new file mode 100644 index 00000000..9599e029 --- /dev/null +++ b/squeaknode/client/rpc/route_guide_server.py @@ -0,0 +1,174 @@ +# Copyright 2015 gRPC authors. +# +# 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. +"""The Python implementation of the gRPC route guide server.""" +import math +import time +from concurrent import futures + +import grpc + +from squeaknode.client.rpc import route_guide_pb2 +from squeaknode.client.rpc import route_guide_pb2_grpc +from squeaknode.client.rpc import route_guide_resources + + +def get_feature(feature_db, point): + """Returns Feature at given location or None.""" + for feature in feature_db: + if feature.location == point: + return feature + return None + + +def get_distance(start, end): + """Distance between two points.""" + coord_factor = 10000000.0 + lat_1 = start.latitude / coord_factor + lat_2 = end.latitude / coord_factor + lon_1 = start.longitude / coord_factor + lon_2 = end.longitude / coord_factor + lat_rad_1 = math.radians(lat_1) + lat_rad_2 = math.radians(lat_2) + delta_lat_rad = math.radians(lat_2 - lat_1) + delta_lon_rad = math.radians(lon_2 - lon_1) + + # Formula is based on http://mathforum.org/library/drmath/view/51879.html + a = (pow(math.sin(delta_lat_rad / 2), 2) + + (math.cos(lat_rad_1) * math.cos(lat_rad_2) * pow( + math.sin(delta_lon_rad / 2), 2))) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + R = 6371000 + # metres + return R * c + + +class RouteGuideServicer(route_guide_pb2_grpc.RouteGuideServicer): + """Provides methods that implement functionality of route guide server.""" + + def __init__(self, node): + self.db = route_guide_resources.read_route_guide_database() + self.node = node + + def GetFeature(self, request, context): + feature = get_feature(self.db, request) + if feature is None: + return route_guide_pb2.Feature(name="", location=request) + else: + return feature + + def ListFeatures(self, request, context): + left = min(request.lo.longitude, request.hi.longitude) + right = max(request.lo.longitude, request.hi.longitude) + top = max(request.lo.latitude, request.hi.latitude) + bottom = min(request.lo.latitude, request.hi.latitude) + for feature in self.db: + if (feature.location.longitude >= left and + feature.location.longitude <= right and + feature.location.latitude >= bottom and + feature.location.latitude <= top): + yield feature + + def RecordRoute(self, request_iterator, context): + point_count = 0 + feature_count = 0 + distance = 0.0 + prev_point = None + + start_time = time.time() + for point in request_iterator: + point_count += 1 + if get_feature(self.db, point): + feature_count += 1 + if prev_point: + distance += get_distance(prev_point, point) + prev_point = point + + elapsed_time = time.time() - start_time + return route_guide_pb2.RouteSummary( + point_count=point_count, + feature_count=feature_count, + distance=int(distance), + elapsed_time=int(elapsed_time)) + + def RouteChat(self, request_iterator, context): + prev_notes = [] + for new_note in request_iterator: + for prev_note in prev_notes: + if prev_note.location == new_note.location: + yield prev_note + prev_notes.append(new_note) + + def WalletBalance(self, request, context): + response = self.node.get_wallet_balance() + return route_guide_pb2.WalletBalanceResponse( + total_balance=response.total_balance, + confirmed_balance=response.confirmed_balance, + unconfirmed_balance=response.unconfirmed_balance, + ) + + def ConnectHost(self, request, context): + host = request.host + self.node.connect_host(host) + return route_guide_pb2.ConnectHostResponse() + + def DisconnectPeer(self, request, context): + addr = request.addr + host = addr.host + port = addr.port + address = (host, port) + self.node.disconnect_peer(address) + return route_guide_pb2.DisconnectPeerResponse() + + def ListPeers(self, request, context): + peers = self.node.get_peers() + peer_msgs = [ + route_guide_pb2.Peer( + addr=route_guide_pb2.Addr( + host=peer.address[0], + port=peer.address[1], + ), + ) + for peer in peers + ] + return route_guide_pb2.ListPeersResponse( + peers=peer_msgs, + ) + + def MakeSqueak(self, request, context): + content = request.content + squeak = self.node.make_squeak(content) + squeak_msg = route_guide_pb2.Squeak( + hash=squeak.GetHash(), + address=str(squeak.GetAddress()), + content=squeak.GetDecryptedContentStr(), + block_height=squeak.nBlockHeight, + timestamp=squeak.nTime, + ) + return route_guide_pb2.MakeSqueakResponse( + squeak=squeak_msg, + ) + + def GenerateSigningKey(self, request, context): + address = self.node.generate_signing_key() + return route_guide_pb2.GenerateSigningKeyResponse( + address=str(address), + ) + + def serve(self): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + route_guide_pb2_grpc.add_RouteGuideServicer_to_server( + self, server) + server.add_insecure_port('0.0.0.0:50051') + server.start() + server.wait_for_termination() diff --git a/squeaknode/common/config.ini b/squeaknode/common/config.ini new file mode 100644 index 00000000..9fd9342f --- /dev/null +++ b/squeaknode/common/config.ini @@ -0,0 +1,15 @@ +[DEFAULT] +network = + +[lnd] +rpc_host = +rpc_port = + +[btcd] +rpc_host = +rpc_port = +rpc_user= +rpc_pass = + +[squeakserver] +host = diff --git a/squeaknode/common/lnd_lightning_client.py b/squeaknode/common/lnd_lightning_client.py index 0a2da237..e1504ff4 100644 --- a/squeaknode/common/lnd_lightning_client.py +++ b/squeaknode/common/lnd_lightning_client.py @@ -4,8 +4,8 @@ import os import grpc -import squeakclient.rpc_pb2 as ln -import squeakclient.rpc_pb2_grpc as lnrpc +import squeaknode.common.rpc_pb2 as ln +import squeaknode.common.rpc_pb2_grpc as lnrpc from squeaknode.common.lightning_client import LightningClient