Compare commits

...

44 commits

Author SHA1 Message Date
dependabot[bot]
6af6126c39
Bump decode-uri-component from 0.2.0 to 0.2.2 in /frontend (#2245)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-07 17:19:58 -08:00
dependabot[bot]
7fdb9d996c
Bump loader-utils from 2.0.2 to 2.0.3 in /frontend (#2242)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.3.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.3/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.3)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-08 13:06:50 -08:00
Jonathan Zernik
bfc746ce0d
Run dev mode with polar (#2239)
* Run in dev mode with polar

* Revert change to sleep in pay offer method
2022-08-26 14:20:25 -07:00
dependabot[bot]
90c0b93a90
Bump terser from 5.12.1 to 5.14.2 in /frontend (#2237)
Bumps [terser](https://github.com/terser/terser) from 5.12.1 to 5.14.2.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-20 19:13:55 -07:00
dependabot[bot]
0181eafe74
Bump moment from 2.29.2 to 2.29.4 in /frontend (#2235)
Bumps [moment](https://github.com/moment/moment) from 2.29.2 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.2...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-13 15:03:57 -07:00
Jonathan Zernik
7c67145f61 Release version 0.3.5 2022-07-10 19:40:51 -07:00
Jonathan Zernik
a92ad92042
Change text of tooltip for copy external address (#2234)
* Change text of tooltip for copy external address

* Update frontend build
2022-07-10 19:31:48 -07:00
Jonathan Zernik
a024057f1b Release version 0.3.4 2022-06-17 19:05:44 -07:00
Jonathan Zernik
93da9c0c97
Fix handling of p2p server port config (#2232) 2022-06-17 19:04:31 -07:00
Jonathan Zernik
e805751bdc
Fix mypy for alembic env script (#2231) 2022-06-11 13:52:19 -07:00
Jonathan Zernik
2cabb8197a
Use only named volumes in docker-compose (#2221)
* Use only named volumes in docker-compose

* Try fixing itest with new docker compose

* Update development doc
2022-06-09 17:58:18 -07:00
Jonathan Zernik
e66377a3c9 Release version 0.3.3 2022-05-25 16:15:20 -07:00
Jonathan Zernik
6ff3f3c4c9
Start settle index from zero on app startup (#2220) 2022-05-25 16:00:44 -07:00
Jonathan Zernik
aef32d9417
Remove zqm hash block config (#2219) 2022-05-24 22:06:23 -07:00
Jonathan Zernik
2d55668af0 Release version 0.3.2 2022-05-24 21:46:59 -07:00
Jonathan Zernik
afd3af091a
Use default peer port config when port not specified (#2218) 2022-05-24 21:41:56 -07:00
Jonathan Zernik
a8ebf8e9e9
Show external address as host:port string (#2216)
* Show external address in peers page as host:port string

* Add peer with host:port string

* Update frontend build
2022-05-24 17:57:33 -07:00
Jonathan Zernik
cb0598cbe5 Release version 0.3.1 2022-05-16 18:04:49 -07:00
Jonathan Zernik
163837a7bc
Use real port for peer get offer request (#2213)
* Use real port number for peer in saved sent offer

* Fix get remote client port
2022-05-11 22:30:52 -07:00
Jonathan Zernik
0c0ce30006
Fix macaroon path in readme example config (#2211) 2022-05-11 18:47:23 -07:00
yzernik
9e1ec67381
Release version 0.3.0 2022-05-08 14:57:44 -07:00
Jonathan Zernik
be90a06973
Remove ascii art (#2206) 2022-05-08 13:32:14 -07:00
Jonathan Zernik
ed360ff3d1
Add clightning backend support (#2205)
* Add c-lightning support

* Got subscribe paid invoices working with c-lightning

* Remove log lines

* Use a single config section for all lightning configs

* Fix itest config

* Update configuration doc with new confis
2022-05-08 12:41:44 -07:00
Jonathan Zernik
80155f1fb7
Fix exception handling in squeak_core payment subscription (#2204) 2022-05-06 18:31:11 -07:00
yzernik
a2eec82e28
Release version 0.2.23 2022-05-03 21:44:14 -07:00
Jonathan Zernik
348353cd43
Fix ascii art print (#2198) 2022-05-03 21:28:09 -07:00
Jonathan Zernik
1dd94e58cf
Print ascii art on start (#2197) 2022-05-03 21:11:13 -07:00
Jonathan Zernik
5231850d13
Implement rename peer (#2196)
* Implement rename peer

* Update frontend build
2022-05-03 20:54:52 -07:00
Jonathan Zernik
c09ab3f209
Simplify serialization of peer squeak download (#2195) 2022-05-03 15:39:13 -07:00
Jonathan Zernik
ad779182d7
Update readme file with create config.ini (#2191) 2022-05-02 16:45:13 -07:00
Jonathan Zernik
28acf6f4fc
Use url_for redirect to login on unauthorized route (#2190) 2022-05-01 22:59:14 -07:00
Jonathan Zernik
098daa08bf
Simplify dockerfile (#2188) 2022-05-01 20:04:02 -07:00
yzernik
43f959e9c4
Release version 0.2.22 2022-04-29 22:04:14 -07:00
dependabot[bot]
566c86d00e
Bump async from 2.6.3 to 2.6.4 in /frontend (#2185)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 20:29:48 -07:00
dependabot[bot]
27d2c92acf
Bump ejs from 3.1.6 to 3.1.7 in /frontend (#2184)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.7)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 20:28:11 -07:00
Jonathan Zernik
bbad5476c8
Add copy to clipboard external address (#2183)
* Include copy to clipboard in external address dialog

* Fix tooltip

* Update frontend build
2022-04-29 19:51:54 -07:00
Jonathan Zernik
cef1c24de5
Change text of external address dialog (#2182)
* Change text of external address dialog modal.

* Change text of external address dialog modal and update frontend build.
2022-04-29 18:08:19 -07:00
Jonathan Zernik
99e4e523dd
Dim profile image in squeak card and squeak on hover (#2181)
* Dim profile image in squeak card and squeak on hover

* Dim profile image in profile card on hover

* Dim profile image in profile page

* Dim profile image in twitter account card on hover

* Update frontend build
2022-04-29 17:47:19 -07:00
yzernik
8bca8c7961
Release version 0.2.21 2022-04-27 03:14:01 -07:00
Jonathan Zernik
cfe9cecbe5
Add download offers button (#2177)
* Got re-download offers button working in buy modal

* Add loader when downloading offers in buy modal

* Update frontend build
2022-04-27 02:44:59 -07:00
Jonathan Zernik
2cd39d05bf
Wait for first successful download single squeak (#2176)
* Got download single squeak working with return after successful download

* Remove comments and log lines

* Remove log lines
2022-04-27 01:58:28 -07:00
Jonathan Zernik
a41ca5a9c5
Update readme to include word signed for squeak struct (#2175) 2022-04-26 19:39:38 -07:00
Jonathan Zernik
2cdb2f8de4
Fix docstring for payreq (#2174) 2022-04-26 19:24:50 -07:00
Jonathan Zernik
b5b3b9f1f3
Rename entrypoint script (#2171) 2022-04-25 16:32:31 -07:00
84 changed files with 6621 additions and 26291 deletions

View file

@ -18,9 +18,7 @@ RUN pip install --upgrade pip
WORKDIR /app
# Copy the source code.
COPY squeaknode ./squeaknode
COPY proto ./proto
COPY LICENSE MANIFEST.in README.md requirements.txt setup.cfg setup.py ./
COPY . .
RUN pip install .[postgres]
@ -42,7 +40,7 @@ EXPOSE 12994
ENV PATH="/opt/venv/bin:$PATH"
# Copy the entrypoint script.
COPY "start-squeaknode.sh" .
RUN chmod +x start-squeaknode.sh
COPY "entrypoint.sh" .
RUN chmod +x entrypoint.sh
CMD ["./start-squeaknode.sh"]
CMD ["./entrypoint.sh"]

View file

@ -11,10 +11,10 @@ A peer-to-peer status feed with posts unlocked by Lightning.
Squeaknode allows you to create, view, buy, and sell squeaks.
A squeak is an immutable structure that:
A squeak is an immutable, signed structure that:
* contains up to 280 utf-8 characters of text, encrypted (can only be decrypted with access to the decryption key obtained by lightning payment)
* contains the height and block hash of the latest bitcoin block
* contains the public key of the author, and is signed by the author
* contains the public key of the author
* contains the public key of the recipient, if the text is end-to-end encrypted
* contains the hash of another squeak, if the squeak is a reply
* can be used to derive a unique hash
@ -33,16 +33,16 @@ The protocol is defined [here](https://github.com/yzernik/squeak/blob/master/doc
* a Tor SOCKS5 proxy (you can open Tor Browser and run it in the background)
### Step 1. Create the configuration
> Update the **config.ini** file and fill in the relevant sections to connect to your Bitcoin node and LND node:
> Create a **config.ini** file and fill in the relevant sections to connect to your Bitcoin node and LND node:
```
[node]
network=mainnet
[lnd]
host=localhost
tls_cert_path=~/.lnd/tls.cert
macaroon_path=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
[lightning]
backend=lnd
lnd_tls_cert_path=~/.lnd/tls.cert
lnd_macaroon_path=~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon
[bitcoin]
rpc_host=localhost
@ -60,6 +60,14 @@ username=devuser
password=devpass
```
If you want to use [c-lightning](https://github.com/ElementsProject/lightning) as the Lightning Network node backend instead of [LND](https://github.com/lightningnetwork/lnd), replace the `lightning` section with the following:
```
[lightning]
backend=clightning
clightning_rpc_file=~/.lightning/lightning-rpc
```
Add any other [configs](docs/configuration.md) that you need.
### Step 2. Install squeaknode:

20
clightning-config.ini Normal file
View file

@ -0,0 +1,20 @@
[node]
network=testnet
[lightning]
backend=clightning
[bitcoin]
rpc_host=localhost
rpc_port=18332
rpc_user=devuser
rpc_pass=devpass
[tor]
proxy_ip=localhost
proxy_port=9150
[webadmin]
enabled=true
username=devuser
password=devpass

29
config-other.ini Normal file
View file

@ -0,0 +1,29 @@
[node]
network=testnet
sqk_dir_path=/home/yzernik/.sqk-other
[lightning]
external_host=localhost
lnd_rpc_host=localhost
lnd_rpc_port=10003
lnd_tls_cert_path=/home/yzernik/.polar/networks/1/volumes/lnd/alice/tls.cert
lnd_macaroon_path=/home/yzernik/.polar/networks/1/volumes/lnd/alice/data/chain/bitcoin/regtest/admin.macaroon
[bitcoin]
rpc_host=localhost
rpc_port=18445
rpc_user=polaruser
rpc_pass=polarpass
[tor]
proxy_ip=localhost
proxy_port=9150
[webadmin]
enabled=true
username=devuser
password=devpass
port=12995
[server]
port=18558

View file

@ -1,17 +1,18 @@
[node]
network=testnet
[lnd]
host=localhost
tls_cert_path=~/.lnd/tls.cert
macaroon_path=~/.lnd/data/chain/bitcoin/testnet/admin.macaroon
[lightning]
external_host=localhost
lnd_rpc_host=localhost
lnd_rpc_port=10004
lnd_tls_cert_path=/home/yzernik/.polar/networks/1/volumes/lnd/bob/tls.cert
lnd_macaroon_path=/home/yzernik/.polar/networks/1/volumes/lnd/bob/data/chain/bitcoin/regtest/admin.macaroon
[bitcoin]
rpc_host=localhost
rpc_port=18332
rpc_user=devuser
rpc_pass=devpass
zeromq_hashblock_port=28334
rpc_port=18445
rpc_user=polaruser
rpc_pass=polarpass
[tor]
proxy_ip=localhost

View file

@ -1,4 +1,4 @@
FROM golang:1.12-alpine as builder
FROM golang:1.18-alpine as builder
MAINTAINER Olaoluwa Osuntokun <laolu@lightning.network>

View file

@ -0,0 +1,25 @@
[node]
network=testnet
[lightning]
backend=clightning
external_host=clightning_server
external_port=19735
[clightning]
enabled=true
[bitcoin]
rpc_host=bitcoin-core
rpc_port=18332
rpc_user=devuser
rpc_pass=devpass
[tor]
proxy_ip=tor-node
proxy_port=9050
[webadmin]
enabled=true
username=devuser
password=devpass

View file

@ -1,17 +1,17 @@
[node]
network=testnet
[lnd]
host=lnd
tls_cert_path=~/.lnd/tls.cert
macaroon_path=~/.lnd/data/chain/bitcoin/testnet/admin.macaroon
[lightning]
lnd_rpc_host=lnd
external_host=lnd
lnd_tls_cert_path=~/.lnd/tls.cert
lnd_macaroon_path=~/.lnd/data/chain/bitcoin/testnet/admin.macaroon
[bitcoin]
rpc_host=bitcoin-core
rpc_port=18332
rpc_user=devuser
rpc_pass=devpass
zeromq_hashblock_port=28334
[tor]
proxy_ip=tor-node

View file

@ -19,6 +19,16 @@ services:
environment:
- NETWORK=mainnet
clightning_client:
command:
- --bitcoin-rpcconnect=bitcoin-core
- --bitcoin-rpcuser=devuser
- --bitcoin-rpcpassword=devpass
- --network=mainnet
- --plugin-dir=/usr/libexec/c-lightning/plugins
- --alias=myitestnode
- --log-level=debug
# squeaknode:
# environment:
# - NETWORK=mainnet

View file

@ -15,6 +15,32 @@ services:
-zmqpubhashblock=tcp://0.0.0.0:28334
-zmqpubhashtx=tcp://0.0.0.0:28334
clightning_client:
command:
- --bitcoin-rpcconnect=bitcoin-core
- --bitcoin-rpcuser=devuser
- --bitcoin-rpcpassword=devpass
- --network=testnet
- --plugin-dir=/usr/libexec/c-lightning/plugins
- --alias=myclientningclient
- --log-level=debug
- --rpc-file=/root/.lightning/lightning-rpc
- --rpc-file-mode=0777
- --proxy=tor-node:9050
clightning_server:
command:
- --bitcoin-rpcconnect=bitcoin-core
- --bitcoin-rpcuser=devuser
- --bitcoin-rpcpassword=devpass
- --network=testnet
- --plugin-dir=/usr/libexec/c-lightning/plugins
- --alias=myclientningserver
- --log-level=debug
- --rpc-file=/root/.lightning/lightning-rpc
- --rpc-file-mode=0777
- --proxy=tor-node:9050
# squeaknode:
# environment:
# - NETWORK=testnet

View file

@ -5,7 +5,7 @@ services:
image: ruimarinho/bitcoin-core:0.21.1
container_name: bitcoin-core
volumes:
- ~/.bitcoin:/home/bitcoin/.bitcoin
- bitcoin:/home/bitcoin/.bitcoin
ports:
- 8332:8332
- 18332:18332
@ -25,7 +25,7 @@ services:
depends_on:
- "tor-node"
volumes:
- ~/.lnd:/root/.lnd
- lnd_client_dir:/root/.lnd
- tor-dir:/var/lib/tor
ports:
- 10009:10009
@ -57,6 +57,45 @@ services:
- net.ipv6.conf.all.disable_ipv6=0
entrypoint: ["./start-lnd.sh"]
clightning_client:
image: elementsproject/lightningd:v0.11.0.1
container_name: clightning_client
expose:
- "9735"
- "19735"
- "9835"
- "10009"
ports:
- 10010:10009
- 9835:9835
volumes:
- clightning_client_dir:/root/.lightning
- tor-dir:/var/lib/tor
links:
- "bitcoin-core:bitcoin-core"
depends_on:
- "tor-node"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
clightning_server:
image: elementsproject/lightningd:v0.11.0.1
container_name: clightning_server
expose:
- "9735"
- "19735"
- "9835"
- "10009"
volumes:
- clightning_server_dir:/root/.lightning
- tor-dir:/var/lib/tor
links:
- "bitcoin-core:bitcoin-core"
depends_on:
- "tor-node"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
tor-node:
image: osminogin/tor-simple
container_name: tor-node
@ -87,12 +126,44 @@ services:
- "lnd_server:lnd"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
entrypoint: ["./start-squeaknode.sh"]
entrypoint: ["./entrypoint.sh"]
squeaknode_clightning:
image: squeaknode
container_name: squeaknode_clightning
build:
context: ../
dockerfile: Dockerfile
depends_on:
- "lnd_server"
volumes:
- clightning_server_dir:/root/.lightning
- squeaknode_clightning_dir:/root/.sqk
- ./config-clightning.ini:/config.ini
ports:
- 18558:18555
- 12996:12994
links:
- "bitcoin-core:bitcoin-core"
- "lnd_server:lnd"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
entrypoint: ["./entrypoint.sh"]
volumes:
bitcoin:
driver: local
lnd_client_dir:
driver: local
lnd_server_dir:
driver: local
clightning_client_dir:
driver: local
clightning_server_dir:
driver: local
squeaknode_dir:
driver: local
squeaknode_clightning_dir:
driver: local
tor-dir:
driver: local

View file

@ -26,13 +26,14 @@ bitcoin.rpc_user | string | | yes | "" | SQUEAKNODE_BITCOIN_RPC_USER | The usern
bitcoin.rpc_pass | string | | yes | "" | SQUEAKNODE_BITCOIN_RPC_PASS | The password to use for authentication on the bitcoin node.
bitcoin.rpc_use_ssl | boolean | [true, false] | yes | false | SQUEAKNODE_BITCOIN_USE_SSL | Use SSL for the connection to the bitcoin node.
bitcoin.rpc_ssl_cert | str | | yes | "" | SQUEAKNODE_BITCOIN_SSL_CERT | The path to the SSL cert to use for connection to the bitcoin node, if one is used.
bitcoin.zeromq_hashblock_port | int | | yes | 28334 | SQUEAKNODE_BITCOIN_ZEROMQ_HASHBLOCK_PORT | The port to use to subscribe with zeromq to new block hashes on the bitcoin node.
lnd.host | string | | yes | "localhost" | SQUEAKNODE_LND_HOST | The host of the LND node to connect.
lnd.external_host | string | | yes | "" | SQUEAKNODE_LND_EXTERNAL_HOST | The host of the LND node to share with other peers.
lnd.port | int | | yes | | SQUEAKNODE_LND_PORT | The port of the LND node to use for LND peer connections.
lnd.rpc_port | int | | yes | | SQUEAKNODE_LND_RPC_PORT | The port of the LND node to use for RPC connections.
lnd.tls_cert_path | string | | yes | "" | SQUEAKNODE_LND_TLS_CERT_PATH | The path to the TLS certificate to use for LND connection.
lnd.macaroon_path | string | | yes | "" | SQUEAKNODE_LND_MACAROON_PATH | The path to the macaroon to use for LND connection.
lightning.backend | string | | yes | "lnd" | SQUEAKNODE_LIGHTNING_BACKEND | The Lightning Network node implementation to use.
lightning.external_host | string | | yes | "" | SQUEAKNODE_LIGHTNING_EXTERNAL_HOST | The host of the lightning node to share with other peers.
lightning.external_port | int | | yes | | SQUEAKNODE_LIGHTNING_EXTERNAL_PORT | The port of the lightning node to share with other peers.
lightning.lnd_rpc_host | string | | yes | "localhost" | SQUEAKNODE_LIGHTNING_LND_RPC_HOST | The host of the LND node to connect.
lightning.lnd_rpc_port | int | | yes | | SQUEAKNODE_LIGHTNING_LND_RPC_PORT | The port of the LND node to use for RPC connections.
lightning.lnd_tls_cert_path | string | | yes | "" | SQUEAKNODE_LIGHTNING_LND_TLS_CERT_PATH | The path to the TLS certificate to use for LND connection.
lightning.lnd_macaroon_path | string | | yes | "" | SQUEAKNODE_LIGHTNING_LND_MACAROON_PATH | The path to the macaroon to use for LND connection.
lightning.clightning_rpc_file | string | | yes | "" | SQUEAKNODE_LIGHTNING_CLIGHTNING_RPC_FILE | The path to the file to use for c-lightning RPC.
tor.proxy_ip | string | | yes | "" | SQUEAKNODE_TOR_PROXY_IP | The ip address or host of the SOCKS5 Tor proxy, if one is used.
tor.proxy_port | int | | yes | 0 | SQUEAKNODE_TOR_PROXY_PORT | The port of the SOCKS5 Tor proxy, is one is used.
db.connection_string | string | | yes | "" | SQUEAKNODE_DB_CONNECTION_STRING | The connection string to use to connect to a SQL database. If none is specified, a sqlite database will be used on the local file system.

View file

@ -32,9 +32,9 @@ You can also run your own squeaknode on your host machine.
pip install .
```
- Make sure that `~/.lnd` directory (created by docker container) has read permissions. You may need to change the permissions:
- Copy the `~/.lnd` directory from the docker container to your host machine.
```
sudo chmod -R 755 ~/.lnd/
docker cp lnd_client:/root/.lnd/ ~/.lnd
```
- Run squeaknode with authentication disabled:

19393
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -12,15 +12,17 @@
"google-protobuf": "^3.12.4",
"informed": "^4.6.1",
"jwt-decode": "^2.2.0",
"moment": "^2.29.2",
"moment": "^2.29.4",
"react": "^16.13.1",
"react-contenteditable": "^3.3.4",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^16.13.1",
"react-redux": "^7.2.1",
"react-responsive": "^8.1.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^5.0.0",
"react-select": "^5.2.2",
"react-tooltip": "^4.2.21",
"sass": "^1.49.7",
"serve": "^13.0.2",
"socket.io-client": "^2.3.0"

View file

@ -168,3 +168,10 @@ export const ICON_SEND = (props) => {
export const ICON_QUESTION = (props) => {
return <svg style={props.styles} viewBox="0 0 16 16"><g><path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z"/></g></svg>
}
export const ICON_CLIPBOARD = (props) => {
return <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard" viewBox="0 0 16 16">
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</svg>
}

View file

@ -163,6 +163,10 @@ import {
GetSentPaymentsForPeerReply,
GetReceivedPaymentsForPeerRequest,
GetReceivedPaymentsForPeerReply,
DownloadSqueakSecretKeyRequest,
DownloadSqueakSecretKeyReply,
RenamePeerRequest,
RenamePeerReply,
} from '../proto/squeak_admin_pb';
import axios from 'axios'
@ -716,8 +720,20 @@ export const getPrivateKey = (id) => {
});
}
export const getPeer = (network, host, port) => {
export const getPeer = (peerId) => {
console.log('Calling getPeer');
const request = new GetPeerRequest();
request.setPeerId(peerId);
const deser = GetPeerReply.deserializeBinary;
return baseRequest({
url: '/getpeer',
req: request,
deser: deser,
});
}
export const getPeerByAddress = (network, host, port) => {
console.log('Calling getPeerByAddress');
const request = new GetPeerByAddressRequest();
const peerAddress = new PeerAddress();
peerAddress.setNetwork(network);
@ -732,6 +748,19 @@ export const getPeer = (network, host, port) => {
});
}
export const renamePeer = (peerId, peerName) => {
console.log('Calling renamePeer');
const request = new RenamePeerRequest();
request.setPeerId(peerId);
request.setPeerName(peerName);
const deser = RenamePeerReply.deserializeBinary;
return baseRequest({
url: '/renamepeer',
req: request,
deser: deser,
});
}
export const getConnectedPeers = () => {
console.log('Calling getConnectedPeers');
const request = new GetConnectedPeersRequest();
@ -923,6 +952,18 @@ export const downloadSqueak = (squeakHash) => {
});
}
export const downloadSqueakSecretKey = (squeakHash) => {
console.log('Calling downloadSqueakSecretKey');
const request = new DownloadSqueakSecretKeyRequest();
request.setSqueakHash(squeakHash);
const deser = DownloadSqueakSecretKeyReply.deserializeBinary;
return baseRequest({
url: '/downloadsqueaksecretkey',
req: request,
deser: deser,
});
}
export const getTwitterAccounts = () => {
console.log('Calling getTwitterAccounts');
const request = new GetTwitterAccountsRequest();

View file

@ -22,6 +22,7 @@ import {
setDeletePeer,
setPeerAutoconnectEnabled,
setPeerAutoconnectDisabled,
setRenamePeer,
} from '../../features/peers/peersSlice'
import {
fetchPaymentSummaryForPeer,
@ -121,13 +122,10 @@ const Peer = (props) => {
const editPeer = ({values}) => {
console.log('Calling editPeer with name:', values.name);
// TODO: call rename action.
alert('Rename peer not yet implemented!');
// dispatch(setRenameProfile({
// peerId: user.getPeerId(),
// profileName: editName,
// }));
// TODO: chain action to update profile squeaks with the new name.
dispatch(setRenamePeer({
peerId: peer.getPeerId(),
peerName: values.name,
}));
setSaved(true)
toggleEditModal()
}

View file

@ -1,11 +1,13 @@
import React, { useEffect, useState, useContext } from 'react'
import './style.scss'
import { withRouter, Link } from 'react-router-dom'
import { ICON_SEARCH, ICON_ARROWBACK, ICON_CLOSE } from '../../Icons'
import { ICON_SEARCH, ICON_ARROWBACK, ICON_CLOSE, ICON_CLIPBOARD } from '../../Icons'
import { getProfileImageSrcString } from '../../squeakimages/images';
import Loader from '../Loader'
import SqueakCard from '../SqueakCard'
import PeerCard from '../../features/peers/PeerCard'
import { CopyToClipboard } from 'react-copy-to-clipboard';
import ReactTooltip from "react-tooltip";
import { Form, Input, Select, Checkbox, Relevant, Debug, TextArea, Option } from 'informed';
@ -59,17 +61,26 @@ const Peers = (props) => {
return 'IPV4';
};
const getExternalAddressStr = () => {
if (externalAddress) {
return `${externalAddress.getHost()}:${externalAddress.getPort()}`;
} else {
return '';
}
};
function removeHttp(url) {
return url.replace(/^https?:\/\//, '');
}
const savePeer = ({values}) => {
const network = getNetwork(values.useTor);
const strippedHost = removeHttp(values.host);
const strippedAddress = removeHttp(values.address);
const url = new URL(`http://${strippedAddress}`);
dispatch(setSavePeer({
name: values.name,
host: strippedHost,
port: values.port,
host: url.hostname,
port: url.port,
network: network,
}));
toggleSavePeerModal();
@ -90,8 +101,7 @@ const Peers = (props) => {
</div>
<div className="edit-input-wrap">
<Input class="informed-input" name="name" label="Peer Name (not required)" />
<Input class="informed-input" name="host" label="Host" />
<Input class="informed-input" name="port" type="number" label="Port" />
<Input class="informed-input" name="address" label="Address (host:port)" />
<Checkbox class="informed-input" name="useTor" label="Connect With Tor: " />
</div>
@ -112,12 +122,27 @@ const Peers = (props) => {
const ShowExternalAddressForm = () => (
<Form className="Squeak-input-side">
<div className="edit-input-wrap">
<p>Other squeaknodes can connect to your node using this address to exchange squeaks and offers.</p>
<p>Other Squeaknode instances can connect to your node using this address to download squeaks and offers.</p>
</div>
<div className="edit-input-wrap">
<Input class="informed-input" name="host" label="Host" initialValue={externalAddress && externalAddress.getHost()} readOnly />
<Input class="informed-input" name="port" label="Port" initialValue={externalAddress && externalAddress.getPort()} readOnly/>
<div class="float-container">
<div class="float-child">
<div className="edit-input-wrap">
<Input class="informed-input" name="external-address" label="External Address" initialValue={externalAddress && `${getExternalAddressStr()}`} readOnly />
</div>
</div>
<div class="float-child">
<CopyToClipboard
text={externalAddress && `${getExternalAddressStr()}`}
>
<a data-tip="Copy address">
<button fullWidth={false}>
<ICON_CLIPBOARD />
</button>
</a>
</CopyToClipboard>
</div>
</div>
<ReactTooltip effect="solid" />
</Form>
);

View file

@ -69,3 +69,16 @@
}
}
}
.float-container {
display: flex; /* ADDED */
align-items: center; /* ADDED */
width: 100%;
}
.float-child {
position: relative;
float: left;
width: 730px;
}

View file

@ -107,6 +107,16 @@
width: 100%;
height: 100%;
object-fit: cover;
-webkit-transition: all 1s ease;
-moz-transition: all 1s ease;
-o-transition: all 1s ease;
-ms-transition: all 1s ease;
transition: all 1s ease;
}
img:hover {
-webkit-filter: brightness(70%);
filter: brightness(70%);
}
}

View file

@ -111,8 +111,28 @@
.search-userPic-wrapper{
flex-basis: 49px;
margin-right: 10px;
display: flex;
flex-direction: column;
}
.search-userPic{
img{
object-fit: cover;
border-radius: 50%;
min-width: 49px;
-webkit-transition: all 1s ease;
-moz-transition: all 1s ease;
-o-transition: all 1s ease;
-ms-transition: all 1s ease;
transition: all 1s ease;
}
}
.search-userPic:hover{
img{
object-fit: cover;
-webkit-filter: brightness(70%);
filter: brightness(70%);
}
}

View file

@ -171,7 +171,9 @@ import { ICON_REPLY, ICON_RETWEET,
<Link onClick={(e)=>{
e.stopPropagation();
}} to={`/app/profile/${props.squeak.getAuthorPubkey()}`}>
<img alt="" style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={author ? `${getProfileImageSrcString(author)}` : null}/>
<div className="card-userPic">
<img alt="" width="100%" height="49px" src={author ? `${getProfileImageSrcString(author)}` : null}/>
</div>
</Link>
{props.hasReply? <div className="squeak-reply-thread"></div> : null}
</div>

View file

@ -15,8 +15,27 @@
margin-right: 10px;
display: flex;
flex-direction: column;
}
.card-userPic{
img{
object-fit: cover;
border-radius: 50%;
min-width: 49px;
-webkit-transition: all 1s ease;
-moz-transition: all 1s ease;
-o-transition: all 1s ease;
-ms-transition: all 1s ease;
transition: all 1s ease;
}
}
.card-userPic:hover{
img{
-webkit-filter: brightness(70%);
filter: brightness(70%);
}
}

View file

@ -6,12 +6,14 @@ import {
} from '@reduxjs/toolkit'
import {
getPeer,
getPeerByAddress,
getConnectedPeers,
connectPeer,
disconnectPeer,
getSavedPeers,
savePeer,
deletePeer,
renamePeer,
enableAutoconnectPeer,
disableAutoconnectPeer,
} from '../../api/client'
@ -37,7 +39,7 @@ export const fetchPeer = createAsyncThunk(
let network = values.network;
let host = values.host;
let port = values.port;
const response = await getPeer(
const response = await getPeerByAddress(
network,
host,
port,
@ -153,6 +155,22 @@ export const setPeerAutoconnectDisabled = createAsyncThunk(
}
)
export const setRenamePeer = createAsyncThunk(
'profile/setRenamePeer',
async (values) => {
console.log('Renaming peer');
console.log(values.peerId);
console.log(values.peerName);
let peerId = values.peerId;
let peerName = values.peerName;
await renamePeer(peerId, peerName);
console.log('Getting renamed peer');
const response = await getPeer(peerId);
console.log(response);
return response.getSqueakPeer();
}
)
const peersSlice = createSlice({
name: 'peers',
@ -262,6 +280,14 @@ const peersSlice = createSlice({
state.currentPeer = newSavedPeer;
state.savedPeers = newSavedPeers;
})
.addCase(setRenamePeer.pending, (state, action) => {
state.currentPeerStatus = 'loading'
})
.addCase(setRenamePeer.fulfilled, (state, action) => {
const newPeer = action.payload;
state.currentPeer = newPeer;
state.currentPeerStatus = 'idle';
})
},
})

View file

@ -34,8 +34,10 @@ const ProfileCard = (props) => {
return <Link onClick={(e)=>e.stopPropagation()} to={`/app/profile/${profile.getPubkey()}`} key={profile.getPubkey()} className="search-result-wapper">
<div className="search-userPic-wrapper">
<div className="search-userPic">
<img style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={`${getProfileImageSrcString(profile)}`}/>
</div>
</div>
<div className="search-user-details">
<div className="search-user-warp">
<div className="search-user-info">

View file

@ -8,7 +8,7 @@ import { Link } from 'react-router-dom'
import { getProfileImageSrcString } from '../../squeakimages/images';
import Loader from '../../components/Loader'
import { Form, Input, Checkbox, Relevant, Debug, TextArea, Option } from 'informed';
import { Form, Input, Checkbox, Relevant, Debug, TextArea, Option, useFormApi } from 'informed';
import Select from 'react-select'
@ -21,6 +21,8 @@ import {
selectBuySqueakStatus,
selectSqueakOffers,
fetchSqueakOffers,
selectDownloadSqueakOffersStatus,
setDownloadSqueakOffers,
} from '../squeaks/squeaksSlice'
import {
fetchPaymentSummary,
@ -43,6 +45,7 @@ const BuySqueak = (props) => {
const signingProfiles = useSelector(selectSigningProfiles);
const buySqueakStatus = useSelector(selectBuySqueakStatus);
const squeakOffers = useSelector(selectSqueakOffers);
const downloadSqueakOffersStatus = useSelector(selectDownloadSqueakOffersStatus);
const dispatch = useDispatch();
const [offer, setOffer] = useState(null);
@ -86,12 +89,45 @@ const BuySqueak = (props) => {
});
}
const submitDownloadOffers = ({ values }) => {
console.log(values);
console.log('downloadOffers');
dispatch(setDownloadSqueakOffers(squeakHash));
}
const SubmitDownloadOffersButton = () => {
const formApi = useFormApi();
return <button
type="submit"
className={'squeak-btn-side squeak-btn-active'}
onClick={formApi.submitForm}>
Re-download Offers
</button>
};
const DownloadOffersForm = () => (
<Form onSubmit={submitDownloadOffers} className="Squeak-input-side">
<div className="inner-input-links">
<div className="input-links-side">
</div>
<div className="squeak-btn-holder">
{downloadSqueakOffersStatus === 'idle' ?
<SubmitDownloadOffersButton /> :
<Loader />
}
</div>
</div>
</Form>
);
return (
<>
<div className="Squeak-input-side">
<div className="edit-input-wrap">
<DownloadOffersForm />
{squeakOffers.length} offers
<div className="inner-input-box">
<Select options={optionsFromOffers(squeakOffers)} onChange={handleChangeOffer} />

View file

@ -218,7 +218,9 @@ import { ICON_ARROWBACK, ICON_HEART, ICON_REPLY, ICON_RETWEET, ICON_HEARTFULL,
<div className="squeak-header-content">
<div className="squeak-user-pic">
<Link to={`/app/profile/${squeak.getAuthorPubkey()}`}>
<img alt="" style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={author ? `${getProfileImageSrcString(author)}` : null}/>
<div className="card-userPic">
<img alt="" width="100%" height="49px" src={author ? `${getProfileImageSrcString(author)}` : null}/>
</div>
</Link>
</div>
<div className="squeak-user-wrap">

View file

@ -20,6 +20,7 @@ import {
getSqueakOffers,
buySqueak,
downloadSqueak,
downloadSqueakSecretKey,
} from '../../api/client'
const initialState = {
@ -36,10 +37,10 @@ const initialState = {
profileSqueaksStatus: 'idle',
profileSqueaks: [],
makeSqueakStatus: 'idle',
squeakOffersStatus: 'idle',
squeakOffers: [],
buySqueakStatus: 'idle',
downloadSqueakStatus: 'idle',
downloadSqueakOffersStatus: 'idle',
}
// Thunk functions
@ -215,6 +216,16 @@ export const setDownloadSqueak = createAsyncThunk(
}
)
export const setDownloadSqueakOffers = createAsyncThunk(
'squeaks/setDownloadSqueakOffers',
async (squeakHash) => {
console.log('Downloading squeak offers');
await downloadSqueakSecretKey(squeakHash);
const response = await getSqueakOffers(squeakHash);
return response.getOffersList();
}
)
const updateCurrentSqueak = (state, newSqueak) => {
const currentSqueak = state.currentSqueak;
if (currentSqueak && currentSqueak.getSqueakHash() === newSqueak.getSqueakHash()) {
@ -409,13 +420,11 @@ const squeaksSlice = createSlice({
})
.addCase(fetchSqueakOffers.pending, (state, action) => {
state.squeakOffers = [];
state.squeakOffersStatus = 'loading'
})
.addCase(fetchSqueakOffers.fulfilled, (state, action) => {
console.log(action);
const squeakOffers = action.payload;
state.squeakOffers = squeakOffers;
state.squeakOffersStatus = 'idle';
})
.addCase(setBuySqueak.pending, (state, action) => {
state.buySqueakStatus = 'loading'
@ -447,6 +456,18 @@ const squeaksSlice = createSlice({
// TODO: check if current squeak is the one that got downloaded.
state.currentSqueak = newSqueak;
})
.addCase(setDownloadSqueakOffers.pending, (state, action) => {
state.downloadSqueakOffersStatus = 'loading'
})
.addCase(setDownloadSqueakOffers.rejected, (state, action) => {
state.downloadSqueakOffersStatus = 'idle'
})
.addCase(setDownloadSqueakOffers.fulfilled, (state, action) => {
console.log(action);
state.downloadSqueakOffersStatus = 'idle';
const squeakOffers = action.payload;
state.squeakOffers = squeakOffers;
})
},
})
@ -504,8 +525,8 @@ export const selectMakeSqueakStatus = state => state.squeaks.makeSqueakStatus
export const selectSqueakOffers = state => state.squeaks.squeakOffers
export const selectSqueakOffersStatus = state => state.squeaks.squeakOffersStatus
export const selectBuySqueakStatus = state => state.squeaks.buySqueakStatus
export const selectDownloadSqueakStatus = state => state.squeaks.downloadSqueakStatus
export const selectDownloadSqueakOffersStatus = state => state.squeaks.downloadSqueakOffersStatus

View file

@ -61,7 +61,9 @@ const TwitterAccountCard = (props) => {
return <div onClick={(e)=>e.stopPropagation()} key={profile.getPubkey()} className="search-result-wapper">
<Link to={`/app/profile/${profile.getPubkey()}`} className="search-userPic-wrapper">
<img style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={`${getProfileImageSrcString(profile)}`}/>
<div className="search-userPic">
<img style={{borderRadius:'50%', minWidth:'49px'}} width="100%" height="49px" src={`${getProfileImageSrcString(profile)}`}/>
</div>
</Link>
<div className="search-user-details">
<div className="search-user-warp">

File diff suppressed because it is too large Load diff

View file

@ -3,10 +3,10 @@ network=simnet
price_msat=1000000
max_squeaks_per_public_key_per_block=50
[lnd]
host=lnd
tls_cert_path=/root/.lnd/tls.cert
macaroon_path=/root/.lnd/data/chain/bitcoin/simnet/admin.macaroon
[lightning]
lnd_rpc_host=lnd
lnd_tls_cert_path=/root/.lnd/tls.cert
lnd_macaroon_path=/root/.lnd/data/chain/bitcoin/simnet/admin.macaroon
[bitcoin]
rpc_host=btcd

View file

@ -125,7 +125,7 @@ services:
- "squeaknode_db:squeaknode_db"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
entrypoint: ["./start-squeaknode.sh"]
entrypoint: ["./entrypoint.sh"]
squeaknode_other:
image: squeaknode
@ -145,7 +145,7 @@ services:
- "squeaknode_other_db:squeaknode_other_db"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
entrypoint: ["./start-squeaknode.sh"]
entrypoint: ["./entrypoint.sh"]
test:
image: test

View file

@ -3,8 +3,8 @@
while true; do
client_address=$(docker exec -it test_lnd_client lncli --network=simnet newaddress np2wkh | jq .address -r)
MINING_ADDRESS=$client_address docker-compose up -d btcd
MINING_ADDRESS=$client_address docker compose up -d btcd
echo "Mining 1 blocks to address: $client_address ..."
docker-compose run btcctl generate 400
docker compose run btcctl generate 400
sleep 10
done

View file

@ -6,16 +6,16 @@ trap "kill 0" EXIT
function mine_blocks {
while true; do
printf "Mining 1 block to address: $MINING_ADDRESS ..."
docker-compose run btcctl generate 1
docker compose run btcctl generate 1
sleep 1
done
}
cd itests
docker-compose down --volumes --remove-orphans
docker-compose build
docker-compose up -d
docker compose down --volumes --remove-orphans
docker compose build
docker compose up -d
# Initialize the blockchain with miner rewards going to the test client.
@ -29,9 +29,9 @@ do
sleep 2
done
MINING_ADDRESS=$client_address docker-compose up -d btcd
export MINING_ADDRESS="$client_address docker compose up -d btcd"
echo "Mining 400 blocks to address: $client_address ..."
docker-compose run btcctl generate 400
docker compose run btcctl generate 400
echo "Finished mining blocks."
sleep 30
@ -40,7 +40,7 @@ mine_blocks &
echo "Background mining task is in background..."
echo "Running test.sh...."
docker-compose run test ./test.sh
docker compose run test ./test.sh
echo "Shutting down itest..."
# docker-compose down
# docker compose down

View file

@ -38,7 +38,9 @@ from tests.util import create_signing_profile
from tests.util import delete_profile
from tests.util import delete_squeak
from tests.util import download_squeak
from tests.util import download_squeak_secret_key
from tests.util import free_price
from tests.util import get_balance
from tests.util import get_external_address
from tests.util import get_hash
from tests.util import get_network
@ -606,6 +608,18 @@ def test_delete_peer(admin_stub, peer_id):
def test_send_coins(admin_stub, other_admin_stub):
new_address_response = admin_stub.LndNewAddress(ln.NewAddressRequest())
get_balance_response = get_balance(
other_admin_stub,
)
print("Get balance: {}".format(get_balance_response))
print("--------------------")
print("--------------------")
print("--------------------")
print("--------------------")
print("--------------------")
print("--------------------")
print("--------------------")
print("--------------------", flush=True)
send_coins_response = send_coins(
other_admin_stub, new_address_response.address, 55555555
)
@ -631,6 +645,8 @@ def test_buy_squeak(
):
# Download squeak
download_squeak(other_admin_stub, saved_squeak_hash)
# Download secret key
download_squeak_secret_key(other_admin_stub, saved_squeak_hash)
# Get the sent offers from the seller node
get_sent_offers_response = admin_stub.GetSentOffers(
@ -781,6 +797,8 @@ def test_download_free_squeak(
with free_price(admin_stub):
# Download squeak
download_result = download_squeak(other_admin_stub, saved_squeak_hash)
# Download secret key
download_squeak_secret_key(other_admin_stub, saved_squeak_hash)
print('download_result:')
print(download_result)
assert download_result.number_downloaded == 1
@ -821,6 +839,8 @@ def test_download_single_squeak(
# Download squeak
download_result = download_squeak(other_admin_stub, saved_squeak_hash)
# Download secret key
download_squeak_secret_key(other_admin_stub, saved_squeak_hash)
assert download_result.number_downloaded == 1
assert download_result.number_requested == 1

View file

@ -221,6 +221,15 @@ def download_squeak(node_stub, squeak_hash):
return download_squeak_response.download_result
def download_squeak_secret_key(node_stub, squeak_hash):
download_squeak_secret_key_response = node_stub.DownloadSqueakSecretKey(
squeak_admin_pb2.DownloadSqueakSecretKeyRequest(
squeak_hash=squeak_hash,
),
)
return download_squeak_secret_key_response.download_result
def download_squeaks(node_stub, pubkeys_in_hex, min_block, max_block, reply_to):
download_squeaks_response = node_stub.DownloadSqueaks(
squeak_admin_pb2.DownloadSqueaksRequest(
@ -428,6 +437,13 @@ def disconnect_peer(node_stub, pubkey):
)
def get_balance(node_stub):
get_balance_request = lnd_pb2.WalletBalanceRequest()
return node_stub.LndWalletBalance(
get_balance_request,
)
def send_coins(node_stub, addr, amount):
send_coins_request = lnd_pb2.SendCoinsRequest(
addr=addr,

View file

@ -208,6 +208,10 @@ service SqueakAdmin {
*/
rpc DownloadSqueak (DownloadSqueakRequest) returns (DownloadSqueakReply) {}
/** sqkadmin: `downloadsqueaksecretkey`
*/
rpc DownloadSqueakSecretKey (DownloadSqueakSecretKeyRequest) returns (DownloadSqueakSecretKeyReply) {}
/** sqkadmin: `downloadoffers`
*/
rpc DownloadOffers (DownloadOffersRequest) returns (DownloadOffersReply) {}
@ -1104,6 +1108,16 @@ message DownloadSqueakReply {
DownloadResult download_result = 1;
}
message DownloadSqueakSecretKeyRequest {
/// The squeak hash.
string squeak_hash = 1;
}
message DownloadSqueakSecretKeyReply {
// The download result
DownloadResult download_result = 1;
}
message DownloadOffersRequest {
/// The squeak hash.
string squeak_hash = 1;

View file

@ -1,4 +1,5 @@
alembic==1.7.1
docutils==0.17.1
Flask==2.0.1
flask-cors==3.0.10
flask-login==0.5.0
@ -7,6 +8,7 @@ googleapis-common-protos==1.53.0
importlib_resources==1.4.0
mypy-protobuf==2.9
protobuf==3.17.3
pyln-client==0.10.1
PySocks==1.7.1
python-bitcoinlib==0.11.0
pyzmq==22.3.0

View file

@ -20,4 +20,4 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__version__ = '0.2.20'
__version__ = '0.3.5'

View file

@ -667,6 +667,19 @@ class SqueakAdminServerHandler(object):
download_result=download_result_msg,
)
def handle_download_squeak_secret_key(self, request):
squeak_hash_str = request.squeak_hash
squeak_hash = bytes.fromhex(squeak_hash_str)
logger.info(
"Handle download squeak secret key for hash: {}".format(squeak_hash_str))
download_result = self.squeak_controller.download_single_squeak_secret_key(
squeak_hash)
logger.info("Download result: {}".format(download_result))
download_result_msg = download_result_to_message(download_result)
return squeak_admin_pb2.DownloadSqueakReply(
download_result=download_result_msg,
)
def handle_download_offers(self, request):
squeak_hash_str = request.squeak_hash
squeak_hash = bytes.fromhex(squeak_hash_str)

View file

@ -200,6 +200,9 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer):
def DownloadSqueak(self, request, context):
return self.handler.handle_download_squeak(request)
def DownloadSqueakSecretKey(self, request, context):
return self.handler.handle_download_squeak_secret_key(request)
def DownloadOffers(self, request, context):
return self.handler.handle_download_offers(request)

View file

@ -71,7 +71,7 @@ def create_app(handler, username, password):
@login.unauthorized_handler
def unauthorized_callback():
return redirect("/login")
return redirect(url_for("login"))
def protobuf_serialized(request_message):
def decorator(func):
@ -403,6 +403,12 @@ def create_app(handler, username, password):
def downloadsqueak(msg):
return handler.handle_download_squeak(msg)
@app.route("/downloadsqueaksecretkey", methods=["POST"])
@login_required
@protobuf_serialized(squeak_admin_pb2.DownloadSqueakSecretKeyRequest())
def downloadsqueaksecretkey(msg):
return handler.handle_download_squeak_secret_key(msg)
@app.route("/downloadoffers", methods=["POST"])
@login_required
@protobuf_serialized(squeak_admin_pb2.DownloadOffersRequest())

View file

@ -1,26 +1,26 @@
{
"files": {
"main.css": "/static/css/main.206389cd.css",
"main.js": "/static/js/main.de2a65b4.js",
"main.css": "/static/css/main.fe6c5463.css",
"main.js": "/static/js/main.f8361353.js",
"static/css/306.7f7715a0.chunk.css": "/static/css/306.7f7715a0.chunk.css",
"static/js/306.90b2a66c.chunk.js": "/static/js/306.90b2a66c.chunk.js",
"static/css/900.b66ac5fb.chunk.css": "/static/css/900.b66ac5fb.chunk.css",
"static/css/900.5027b0a1.chunk.css": "/static/css/900.5027b0a1.chunk.css",
"static/js/900.1b104345.chunk.js": "/static/js/900.1b104345.chunk.js",
"static/css/841.693c3c31.chunk.css": "/static/css/841.693c3c31.chunk.css",
"static/js/841.9f23f674.chunk.js": "/static/js/841.9f23f674.chunk.js",
"static/js/841.91f0aab2.chunk.js": "/static/js/841.91f0aab2.chunk.js",
"static/media/icon.svg": "/static/media/icon.982210ec9275956e4d3cc77fa90167fd.svg",
"index.html": "/index.html",
"main.206389cd.css.map": "/static/css/main.206389cd.css.map",
"main.de2a65b4.js.map": "/static/js/main.de2a65b4.js.map",
"main.fe6c5463.css.map": "/static/css/main.fe6c5463.css.map",
"main.f8361353.js.map": "/static/js/main.f8361353.js.map",
"306.7f7715a0.chunk.css.map": "/static/css/306.7f7715a0.chunk.css.map",
"306.90b2a66c.chunk.js.map": "/static/js/306.90b2a66c.chunk.js.map",
"900.b66ac5fb.chunk.css.map": "/static/css/900.b66ac5fb.chunk.css.map",
"900.5027b0a1.chunk.css.map": "/static/css/900.5027b0a1.chunk.css.map",
"900.1b104345.chunk.js.map": "/static/js/900.1b104345.chunk.js.map",
"841.693c3c31.chunk.css.map": "/static/css/841.693c3c31.chunk.css.map",
"841.9f23f674.chunk.js.map": "/static/js/841.9f23f674.chunk.js.map"
"841.91f0aab2.chunk.js.map": "/static/js/841.91f0aab2.chunk.js.map"
},
"entrypoints": [
"static/css/main.206389cd.css",
"static/js/main.de2a65b4.js"
"static/css/main.fe6c5463.css",
"static/js/main.f8361353.js"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#1da1f2"/><meta nam="description" content="Squeaknode!"/><meta name="og:title" content="Squeaknode"/><meta name="og:description" content="check out my the Squeaknode I made"/><link href="https://fonts.googleapis.com/css2?family=Assistant&display=swap" rel="stylesheet"><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><script defer="defer" src="/static/js/main.de2a65b4.js"></script><link href="/static/css/main.206389cd.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#1da1f2"/><meta nam="description" content="Squeaknode!"/><meta name="og:title" content="Squeaknode"/><meta name="og:description" content="check out my the Squeaknode I made"/><link href="https://fonts.googleapis.com/css2?family=Assistant&display=swap" rel="stylesheet"><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Squeaknode</title><script defer="defer" src="/static/js/main.f8361353.js"></script><link href="/static/css/main.fe6c5463.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

View file

@ -0,0 +1,2 @@
.profile-wrapper{border-right:1px solid #e6ecf0;display:flex;flex-direction:column;max-width:600px;min-height:2000px;width:100%}.profile-header-wrapper{align-items:center;background-color:#fff;border-bottom:1px solid #e6ecf0;border-left:1px solid #e6ecf0;cursor:pointer;display:flex;height:53px;margin:0 auto;max-width:1000px;min-height:53px;padding-left:15px;padding-right:15px;position:-webkit-sticky;position:sticky;top:0;width:100%;z-index:8}.profile-header-back{align-items:flex-start;justify-content:center;min-height:30px;min-width:55px}.header-back-wrapper{align-items:center;border:1px solid transparent;border-radius:9999px;display:flex;height:39px;justify-content:center;margin-left:-5px;transition:.2s ease-in-out;width:39px;will-change:background-color}.header-back-wrapper svg{fill:#1da1f2;height:1.5em}.header-back-wrapper:hover{background-color:rgba(29,161,242,.1)}.profile-header-content{display:flex;flex-direction:column}.profile-header-name{font-size:19px;font-weight:800}.profile-header-squeaks{color:#657786;font-size:14px;line-height:19.6875px}.profile-banner-wrapper{height:200px;max-width:600px;position:relative}.profile-banner-wrapper img{height:100%;object-fit:cover;width:100%}.profile-details-wrapper{padding:10px 15px 15px}.profile-options{align-items:center;display:flex;justify-content:flex-end;position:relative}.profile-image-wrapper{border:4px solid #fff;border-radius:50%;bottom:0;height:134px;left:0;min-width:49px;position:absolute;width:134px;z-index:5}.profile-image-wrapper img{border-radius:50%;height:100%;object-fit:cover;transition:all 1s ease;width:100%}.profile-image-wrapper img:hover{-webkit-filter:brightness(70%);filter:brightness(70%)}.profile-edit-button{align-items:center;border:1px solid #1da1f2;border-radius:9999px;cursor:pointer;display:flex;justify-content:center;margin-left:7px;min-height:39px;min-width:98.8px;padding-left:1em;padding-right:1em;transition:.2s ease-in-out}.profile-edit-button span{color:#1da1f2;font-weight:800;text-align:center;width:100%}.profile-edit-button:hover{background-color:rgba(29,161,242,.1)}.unfollow-switch{background-color:#1da1f2}.unfollow-switch span{color:#fff!important}.unfollow-switch:hover{background-color:#ca2055!important;border:1px solid transparent}.unfollow-switch:hover span{color:#fff}.unfollow-switch:hover span span{display:none}.unfollow-switch:hover span:before{content:"Unfollow"}.profile-details-box{margin-top:40px}.profile-name{font-size:19px;font-weight:800}.profile-username{color:#657786;font-size:15px}.profile-bio{margin-bottom:10px;margin-top:10px}.profile-info-box{display:flex;margin-top:10px}.profile-info-box svg{fill:#657786;height:18.75px;margin-right:5px}.profile-date,.profile-location{color:#657786;margin-right:10px}.profile-social-box{display:flex;margin-top:7px}.profile-social-box div{cursor:pointer;display:flex}.profile-social-box div:hover{text-decoration:underline}.follow-num{cursor:pointer;font-weight:700;margin-right:3px}.follow-text{color:#657786;cursor:pointer;margin-right:20px}.profile-nav-menu{align-items:center;border-bottom:1px solid #e6ecf0;display:flex;justify-content:space-around}.profile-nav-item{border-bottom:2px solid transparent;color:#657786;cursor:pointer;font-weight:700;overflow:hidden;padding:15px;text-align:center;transition:.2s;white-space:nowrap;width:100%;will-change:background-color}.profile-nav-item:hover{background-color:rgba(29,161,242,.1);color:#1da1f2}.activeTab{border-bottom:2px solid #1da1f2;color:#1da1f2}.new-msg{align-items:center;border-radius:999px;cursor:pointer;display:flex;justify-content:center;padding:10px;transition:.2s ease-in-out}.new-msg:hover{background-color:rgba(29,161,242,.1)}.new-msg:hover svg{fill:#1da1f2}.new-msg svg{fill:#000;align-items:center;display:flex;height:25px;transition:.2s ease-in-out;width:25px}
/*# sourceMappingURL=900.5027b0a1.chunk.css.map*/

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
.profile-wrapper{border-right:1px solid #e6ecf0;display:flex;flex-direction:column;max-width:600px;min-height:2000px;width:100%}.profile-header-wrapper{align-items:center;background-color:#fff;border-bottom:1px solid #e6ecf0;border-left:1px solid #e6ecf0;cursor:pointer;display:flex;height:53px;margin:0 auto;max-width:1000px;min-height:53px;padding-left:15px;padding-right:15px;position:-webkit-sticky;position:sticky;top:0;width:100%;z-index:8}.profile-header-back{align-items:flex-start;justify-content:center;min-height:30px;min-width:55px}.header-back-wrapper{align-items:center;border:1px solid transparent;border-radius:9999px;display:flex;height:39px;justify-content:center;margin-left:-5px;transition:.2s ease-in-out;width:39px;will-change:background-color}.header-back-wrapper svg{fill:#1da1f2;height:1.5em}.header-back-wrapper:hover{background-color:rgba(29,161,242,.1)}.profile-header-content{display:flex;flex-direction:column}.profile-header-name{font-size:19px;font-weight:800}.profile-header-squeaks{color:#657786;font-size:14px;line-height:19.6875px}.profile-banner-wrapper{height:200px;max-width:600px;position:relative}.profile-banner-wrapper img{height:100%;object-fit:cover;width:100%}.profile-details-wrapper{padding:10px 15px 15px}.profile-options{align-items:center;display:flex;justify-content:flex-end;position:relative}.profile-image-wrapper{border:4px solid #fff;border-radius:50%;bottom:0;height:134px;left:0;min-width:49px;position:absolute;width:134px;z-index:5}.profile-image-wrapper img{border-radius:50%;height:100%;object-fit:cover;width:100%}.profile-edit-button{align-items:center;border:1px solid #1da1f2;border-radius:9999px;cursor:pointer;display:flex;justify-content:center;margin-left:7px;min-height:39px;min-width:98.8px;padding-left:1em;padding-right:1em;transition:.2s ease-in-out}.profile-edit-button span{color:#1da1f2;font-weight:800;text-align:center;width:100%}.profile-edit-button:hover{background-color:rgba(29,161,242,.1)}.unfollow-switch{background-color:#1da1f2}.unfollow-switch span{color:#fff!important}.unfollow-switch:hover{background-color:#ca2055!important;border:1px solid transparent}.unfollow-switch:hover span{color:#fff}.unfollow-switch:hover span span{display:none}.unfollow-switch:hover span:before{content:"Unfollow"}.profile-details-box{margin-top:40px}.profile-name{font-size:19px;font-weight:800}.profile-username{color:#657786;font-size:15px}.profile-bio{margin-bottom:10px;margin-top:10px}.profile-info-box{display:flex;margin-top:10px}.profile-info-box svg{fill:#657786;height:18.75px;margin-right:5px}.profile-date,.profile-location{color:#657786;margin-right:10px}.profile-social-box{display:flex;margin-top:7px}.profile-social-box div{cursor:pointer;display:flex}.profile-social-box div:hover{text-decoration:underline}.follow-num{cursor:pointer;font-weight:700;margin-right:3px}.follow-text{color:#657786;cursor:pointer;margin-right:20px}.profile-nav-menu{align-items:center;border-bottom:1px solid #e6ecf0;display:flex;justify-content:space-around}.profile-nav-item{border-bottom:2px solid transparent;color:#657786;cursor:pointer;font-weight:700;overflow:hidden;padding:15px;text-align:center;transition:.2s;white-space:nowrap;width:100%;will-change:background-color}.profile-nav-item:hover{background-color:rgba(29,161,242,.1);color:#1da1f2}.activeTab{border-bottom:2px solid #1da1f2;color:#1da1f2}.new-msg{align-items:center;border-radius:999px;cursor:pointer;display:flex;justify-content:center;padding:10px;transition:.2s ease-in-out}.new-msg:hover{background-color:rgba(29,161,242,.1)}.new-msg:hover svg{fill:#1da1f2}.new-msg svg{fill:#000;align-items:center;display:flex;height:25px;transition:.2s ease-in-out;width:25px}
/*# sourceMappingURL=900.b66ac5fb.chunk.css.map*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -20,6 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import logging
from concurrent.futures import as_completed
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import wait
from typing import Optional
@ -77,10 +78,30 @@ class NetworkController:
)
for downloader in downloaders
]
# wait for all tasks to complete
# wait for at least one task to complete successfully.
wait(futures)
def download_single_squeak(self, squeak_hash: bytes) -> None:
peers = self.squeak_store.get_autoconnect_peers()
downloaders = [
self.get_downloader(peer)
for peer in peers
]
executor = ThreadPoolExecutor(50)
# submit tasks and collect futures
futures = [
executor.submit(
downloader.download_single_squeak,
squeak_hash
)
for downloader in downloaders]
for future in as_completed(futures):
err = future.exception()
if err is None:
break
executor.shutdown(wait=False)
def download_single_squeak_secret_key(self, squeak_hash: bytes) -> None:
peers = self.squeak_store.get_autoconnect_peers()
downloaders = [
self.get_downloader(peer)
@ -90,7 +111,7 @@ class NetworkController:
# submit tasks and collect futures
futures = [
executor.submit(
downloader.download_single_squeak,
downloader.download_single_squeak_secret_key,
squeak_hash
)
for downloader in downloaders]

View file

@ -24,6 +24,7 @@ from typing import List
from typing import Optional
import requests
from squeak.core import CBaseSqueak
from squeak.core import CResqueak
from squeak.core import CSqueak
from squeak.core.keys import SqueakPublicKey
@ -88,7 +89,7 @@ class PeerClient:
for squeak_hash_str in squeak_hashes_str
]
def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]:
def get_squeak(self, squeak_hash: bytes) -> Optional[CBaseSqueak]:
squeak_hash_str = squeak_hash.hex()
url = f"{self.base_url}/squeak/{squeak_hash_str}"
r = requests.get(
@ -98,19 +99,16 @@ class PeerClient:
)
if r.status_code != requests.codes.ok:
return None
logger.info(r)
squeak_json = r.json()
# squeak_bytes = r.content
squeak_type = squeak_json['squeak_type']
squeak_bytes_hex = squeak_json['squeak_bytes']
squeak_bytes = bytes.fromhex(squeak_bytes_hex)
logger.info('Client downloaded: {}'.format(squeak_json))
if squeak_type == 'squeak':
squeak_bytes = r.content
try:
return CSqueak.deserialize(squeak_bytes)
elif squeak_type == 'resqueak':
except Exception:
pass
try:
return CResqueak.deserialize(squeak_bytes)
else:
return None
except Exception:
pass
return None
def get_secret_key(self, squeak_hash: bytes) -> Optional[bytes]:
squeak_hash_str = squeak_hash.hex()

View file

@ -64,20 +64,31 @@ class PeerDownloader(ABC):
pubkeys,
)
for squeak_hash in squeak_hashes:
self.download_squeak(
squeak_hash,
min_block,
max_block,
pubkeys,
)
try:
self.download_squeak(
squeak_hash,
min_block,
max_block,
pubkeys,
)
except Exception:
pass
for squeak_hash in squeak_hashes:
self.download_secret_key(squeak_hash)
try:
self.download_secret_key(squeak_hash)
except Exception:
pass
def download_single_squeak(
self,
squeak_hash: bytes,
) -> None:
self.download_squeak(squeak_hash)
def download_single_squeak_secret_key(
self,
squeak_hash: bytes,
) -> None:
self.download_secret_key(squeak_hash)
def download_squeak(
@ -89,22 +100,22 @@ class PeerDownloader(ABC):
) -> None:
# Download the squeak if not already owned.
if self.squeak_store.get_squeak(squeak_hash):
return
raise Exception('Squeak already saved.')
# Download the squeak if not already owned.
squeak = self.client.get_squeak(squeak_hash)
# Check if the squeak is valid.
if not squeak:
return
raise Exception('Squeak not found.')
if get_hash(squeak) != squeak_hash:
return
raise Exception('Squeak has wrong hash.')
if min_block and squeak.nBlockHeight < min_block:
return
raise Exception('Squeak has block height below minimum.')
if max_block and squeak.nBlockHeight > max_block:
return
raise Exception('Squeak has block height above minimum.')
if pubkeys and squeak.GetPubKey() not in pubkeys:
return
raise Exception('Squeak has wronge pubkey.')
# Save the squeak.
self.squeak_store.save_squeak(squeak)
@ -114,10 +125,10 @@ class PeerDownloader(ABC):
# Check if squeak is already owned.
if not squeak:
return
raise Exception('Squeak is not already saved.')
if self.squeak_store.get_squeak_secret_key(squeak_hash):
return
raise Exception('Squeak secret key is already saved.')
# Download the secret key if not already owned.
secret_key = self.client.get_secret_key(squeak_hash)
@ -127,7 +138,7 @@ class PeerDownloader(ABC):
for received_offer in self.squeak_store.get_received_offers(squeak_hash):
if received_offer.peer_address == self.peer.address:
return
raise Exception('Received offer from this peer already saved.')
# Download the offer if secret key not already owned.
offer = self.client.get_offer(squeak_hash)

View file

@ -40,7 +40,7 @@ DEFAULT_LOG_LEVEL = "INFO"
DEFAULT_MAX_SQUEAKS = 10000
DEFAULT_MAX_SQUEAKS_PER_PUBLIC_KEY_PER_BLOCK = 100
DEFAULT_SERVER_RPC_HOST = "0.0.0.0"
DEFAULT_SERVER_RPC_PORT = None
DEFAULT_SERVER_RPC_PORT = 8555
DEFAULT_EXTERNAL_PORT = 8555
DEFAULT_ADMIN_RPC_HOST = "0.0.0.0"
DEFAULT_ADMIN_RPC_PORT = 8994
@ -48,17 +48,20 @@ DEFAULT_WEBADMIN_HOST = "0.0.0.0"
DEFAULT_WEBADMIN_PORT = 12994
DEFAULT_BITCOIN_RPC_HOST = "localhost"
DEFAULT_BITCOIN_RPC_PORT = 18334
DEFAULT_BITCOIN_ZEROMQ_HASHBLOCK_PORT = 28334
BITCOIN_RPC_PORT = {
"mainnet": 8332,
"testnet": 18332,
"simnet": 18556,
}
DEFAULT_LIGHTNING_BACKEND = "lnd"
DEFAULT_LND_PORT = 9735
DEFAULT_LND_RPC_PORT = 10009
DEFAULT_CLIGHTNING_DIR = ".lightning"
DEFAULT_CLIGHTNING_RPC_FILE = str(
Path.home() / DEFAULT_CLIGHTNING_DIR / 'lightning-rpc')
DEFAULT_SQK_DIR = ".sqk"
DEFAULT_SQK_DIR_PATH = str(Path.home() / DEFAULT_SQK_DIR)
DEFAULT_LND_HOST = "localhost"
DEFAULT_LND_RPC_HOST = "localhost"
DEFAULT_INTEREST_BLOCK_INTERVAL = 2016
DEFAULT_SENT_OFFER_RETENTION_S = 86400
DEFAULT_RECEIVED_OFFER_RETENTION_S = 86400
@ -78,18 +81,19 @@ class BitcoinConfig(Config):
rpc_pass = key(cast=str, required=False, default="")
rpc_use_ssl = key(cast=bool, required=False, default=False)
rpc_ssl_cert = key(cast=str, required=False, default="")
zeromq_hashblock_port = key(cast=int, required=False,
default=DEFAULT_BITCOIN_ZEROMQ_HASHBLOCK_PORT)
@section('lnd')
class LndConfig(Config):
host = key(cast=str, required=False, default=DEFAULT_LND_HOST)
@section('lightning')
class LightningConfig(Config):
backend = key(cast=str, required=False, default=DEFAULT_LIGHTNING_BACKEND)
external_host = key(cast=str, required=False, default="")
port = key(cast=int, required=False, default=DEFAULT_LND_PORT)
rpc_port = key(cast=int, required=False, default=DEFAULT_LND_RPC_PORT)
tls_cert_path = key(cast=str, required=False, default="")
macaroon_path = key(cast=str, required=False, default="")
external_port = key(cast=int, required=False, default=DEFAULT_LND_PORT)
lnd_rpc_host = key(cast=str, required=False, default=DEFAULT_LND_RPC_HOST)
lnd_rpc_port = key(cast=int, required=False, default=DEFAULT_LND_RPC_PORT)
lnd_tls_cert_path = key(cast=str, required=False, default="")
lnd_macaroon_path = key(cast=str, required=False, default="")
clightning_rpc_file = key(cast=str, required=False,
default=DEFAULT_CLIGHTNING_RPC_FILE)
@section('tor')
@ -102,7 +106,7 @@ class TorConfig(Config):
class ServerConfig(Config):
enabled = key(cast=bool, required=False, default=True)
host = key(cast=str, required=False, default=DEFAULT_SERVER_RPC_HOST)
port = key(cast=int, required=False, default=DEFAULT_SERVER_RPC_PORT)
port = key(cast=int, required=False, default=0)
external_address = key(cast=str, required=False, default="")
external_port = key(cast=int, required=False,
default=DEFAULT_EXTERNAL_PORT)
@ -172,7 +176,7 @@ class TwitterConfig(Config):
class SqueaknodeConfig(Config):
bitcoin = group_key(BitcoinConfig)
lnd = group_key(LndConfig)
lightning = group_key(LightningConfig)
tor = group_key(TorConfig)
server = group_key(ServerConfig)
rpc = group_key(RpcConfig)

View file

@ -26,8 +26,13 @@ from squeaknode.core.squeak_peer import SqueakPeer
def create_saved_peer(
peer_name: str,
peer_address: PeerAddress,
default_peer_port: int,
) -> SqueakPeer:
validate_saved_peer_name(peer_name)
if peer_address.port == 0:
peer_address = peer_address._replace(
port=default_peer_port,
)
return SqueakPeer(
peer_id=None,
peer_name=peer_name,

View file

@ -24,13 +24,11 @@ from typing import Callable
from typing import Optional
from typing import Tuple
import grpc
from bitcoin.core import CBlockHeader
from squeak.core import CBaseSqueak
from squeak.core import CSqueak
from squeaknode.bitcoin.bitcoin_client import BitcoinClient
from squeaknode.core.exception import InvoiceSubscriptionError
from squeaknode.core.lightning_address import LightningAddressHostPort
from squeaknode.core.offer import Offer
from squeaknode.core.peer_address import PeerAddress
@ -415,25 +413,20 @@ class SqueakCore:
def get_payment_stream():
# Yield the received payments.
try:
for invoice in invoice_stream.result_stream:
if invoice.settled:
payment_hash = invoice.r_hash
settle_index = invoice.settle_index
sent_offer = get_sent_offer_fn(payment_hash)
if sent_offer is not None:
yield ReceivedPayment(
received_payment_id=None,
created_time_ms=None,
squeak_hash=sent_offer.squeak_hash,
payment_hash=sent_offer.payment_hash,
price_msat=sent_offer.price_msat,
settle_index=settle_index,
peer_address=sent_offer.peer_address,
)
except grpc.RpcError as e:
if e.code() != grpc.StatusCode.CANCELLED:
raise InvoiceSubscriptionError()
for invoice in invoice_stream.result_stream:
payment_hash = invoice.r_hash
settle_index = invoice.settle_index
sent_offer = get_sent_offer_fn(payment_hash)
if sent_offer is not None:
yield ReceivedPayment(
received_payment_id=None,
created_time_ms=None,
squeak_hash=sent_offer.squeak_hash,
payment_hash=sent_offer.payment_hash,
price_msat=sent_offer.price_msat,
settle_index=settle_index,
peer_address=sent_offer.peer_address,
)
return ReceivedPaymentsStream(
cancel_fn=cancel_subscription,

View file

@ -29,7 +29,7 @@ from squeaknode.db.models import Models
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
config = context.config # type: ignore
# Interpret the config file for Python logging.
# This line sets up loggers basically.

View file

@ -0,0 +1,140 @@
# MIT License
#
# Copyright (c) 2020 Jonathan Zernik
#
# 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.
import logging
import time
import uuid
from pyln.client import LightningRpc
from squeaknode.core.exception import InvoiceSubscriptionError
from squeaknode.lightning.info import Info
from squeaknode.lightning.invoice import Invoice
from squeaknode.lightning.invoice_stream import InvoiceStream
from squeaknode.lightning.lightning_client import LightningClient
from squeaknode.lightning.pay_req import PayReq
from squeaknode.lightning.payment import Payment
logger = logging.getLogger(__name__)
class CLightningClient(LightningClient):
"""Access a c-lightning instance using the Python API."""
def __init__(
self,
rpc_path: str,
) -> None:
self.rpc_path = rpc_path
def init(self):
# Create an instance of the LightningRpc object using the Core Lightning daemon on your computer.
logger.info('initializing clightning client: {}'.format(self.rpc_path))
self.lrpc = LightningRpc(self.rpc_path)
def pay_invoice(self, payment_request: str) -> Payment:
payment = self.lrpc.pay(payment_request)
if payment['status'] == 'complete':
return Payment(
payment_preimage=bytes.fromhex(payment['payment_preimage']),
payment_error='',
)
else:
return Payment(
payment_preimage=b'',
payment_error='Payment failed.',
)
def get_info(self) -> Info:
info = self.lrpc.getinfo()
pubkey = info['id']
binding = info.get('binding')
uris = []
if binding:
for b in binding:
address = b['address']
if ':' not in address: # TODO: Change type of uri to LightningAddress.
port = b['port']
uri = f"{pubkey}@{address}:{port}"
uris.append(uri)
return Info(
uris=uris,
)
def decode_pay_req(self, payment_request: str) -> PayReq:
pay_req = self.lrpc.decodepay(payment_request)
return PayReq(
payment_hash=bytes.fromhex(pay_req['payment_hash']),
payment_point=b'', # TODO: Use real payment point.
num_msat=pay_req['amount_msat'].millisatoshis,
destination=pay_req['payee'],
timestamp=int(pay_req['created_at']),
expiry=int(pay_req['expiry']),
)
def create_invoice(self, preimage: bytes, amount_msat: int) -> Invoice:
created_invoice = self.lrpc.invoice(
amount_msat,
label=str(uuid.uuid4()),
description="Squeaknode invoice",
preimage=preimage.hex(),
)
creation_time = int(time.time())
expiry = int(created_invoice['expires_at']) - creation_time
return Invoice(
r_hash=bytes.fromhex(created_invoice['payment_hash']),
payment_request=created_invoice['bolt11'],
value_msat=amount_msat,
settled=False,
settle_index=0,
creation_date=creation_time,
expiry=expiry,
)
def subscribe_invoices(self, settle_index: int) -> InvoiceStream:
def cancel_fn():
return None
def get_invoice_stream():
try:
pay_index = settle_index
while True:
payment = self.lrpc.waitanyinvoice(lastpay_index=pay_index)
if payment['status'] == 'paid':
pay_index = payment.get('pay_index') or 0
yield Invoice(
r_hash=bytes.fromhex(payment['payment_hash']),
payment_request=payment.get('bolt11') or '',
value_msat=payment.get('value_msat'),
settled=True,
settle_index=pay_index,
creation_date=0, # This value is ignored.
expiry=payment['expires_at'],
)
except Exception:
raise InvoiceSubscriptionError()
return InvoiceStream(
cancel=cancel_fn,
result_stream=get_invoice_stream(),
)

View file

@ -34,6 +34,13 @@ logger = logging.getLogger(__name__)
class LightningClient(ABC):
@abstractmethod
def init(self):
"""Initialize the lightning client.
Returns:
None
"""
@abstractmethod
def get_info(self) -> Info:
"""Get info about the lightning node.

View file

@ -27,6 +27,7 @@ import grpc
from proto import lnd_pb2
from proto import lnd_pb2_grpc
from squeaknode.core.exception import InvoiceSubscriptionError
from squeaknode.lightning.info import Info
from squeaknode.lightning.invoice import Invoice
from squeaknode.lightning.invoice_stream import InvoiceStream
@ -165,7 +166,25 @@ class LNDLightningClient(LightningClient):
subscribe_result = self.stub.SubscribeInvoices(
subscribe_invoices_request,
)
def get_invoice_stream():
try:
for invoice in subscribe_result:
if invoice.settled:
yield Invoice(
r_hash=invoice.r_hash,
payment_request=invoice.payment_request,
value_msat=invoice.value_msat,
settled=invoice.settled,
settle_index=invoice.settle_index,
creation_date=invoice.creation_date,
expiry=invoice.expiry,
)
except grpc.RpcError as e:
if e.code() != grpc.StatusCode.CANCELLED:
raise InvoiceSubscriptionError()
return InvoiceStream(
cancel=subscribe_result.cancel,
result_stream=iter(subscribe_result),
result_stream=get_invoice_stream(),
)

View file

@ -23,7 +23,7 @@ from typing import NamedTuple
class PayReq(NamedTuple):
"""Represents info about the lightning node."""
"""Represents a payment request."""
payment_hash: bytes
payment_point: bytes
num_msat: int

View file

@ -91,7 +91,9 @@ class PaymentProcessorTask:
def process_subscribed_invoices(self):
while not self.stopped.is_set():
try:
latest_settle_index = self.get_latest_settle_index()
# Use zero as starting settle index, so that no invoices are skipped.
# latest_settle_index = self.get_latest_settle_index()
latest_settle_index = 0
logger.info("Starting payment subscription with settle index: {}".format(
latest_settle_index,
))

View file

@ -105,10 +105,10 @@ class SqueakController:
peer_address: PeerAddress,
) -> Optional[Offer]:
lnd_external_address: Optional[LightningAddressHostPort] = None
if self.config.lnd.external_host:
if self.config.lightning.external_host:
lnd_external_address = LightningAddressHostPort(
host=self.config.lnd.external_host,
port=self.config.lnd.port,
host=self.config.lightning.external_host,
port=self.config.lightning.external_port,
)
price_msat = self.get_sell_price_msat()
if price_msat == 0:
@ -187,7 +187,11 @@ class SqueakController:
return self.squeak_store.get_squeak_profile_private_key(profile_id)
def create_peer(self, peer_name: str, peer_address: PeerAddress):
return self.squeak_store.create_peer(peer_name, peer_address)
return self.squeak_store.create_peer(
peer_name,
peer_address,
self.config.server.port or 0,
)
def get_peer(self, peer_id: int) -> Optional[SqueakPeer]:
return self.squeak_store.get_peer(peer_id)
@ -338,6 +342,10 @@ class SqueakController:
self.network_controller.download_single_squeak(squeak_hash)
return DownloadResult(1, 1, 0, 9999)
def download_single_squeak_secret_key(self, squeak_hash: bytes) -> DownloadResult:
self.network_controller.download_single_squeak_secret_key(squeak_hash)
return DownloadResult(1, 1, 0, 9999)
def get_timeline_squeak_entries(
self,
limit: int,

View file

@ -34,6 +34,7 @@ from squeaknode.core.squeak_core import SqueakCore
from squeaknode.db.db_engine import get_connection_string
from squeaknode.db.db_engine import get_engine
from squeaknode.db.squeak_db import SqueakDb
from squeaknode.lightning.clightning_lightning_client import CLightningClient
from squeaknode.lightning.lnd_lightning_client import LNDLightningClient
from squeaknode.node.node_settings import NodeSettings
from squeaknode.node.payment_processor import PaymentProcessor
@ -48,6 +49,7 @@ from squeaknode.server.app import SqueakPeerWebServer
from squeaknode.server.squeak_peer_server_handler import SqueakPeerServerHandler
from squeaknode.twitter.twitter_forwarder import TwitterForwarder
logger = logging.getLogger(__name__)
@ -118,12 +120,21 @@ class SqueakNode:
self.node_settings = NodeSettings(self.squeak_db)
def create_lightning_client(self):
self.lightning_client = LNDLightningClient(
self.config.lnd.host,
self.config.lnd.rpc_port,
self.config.lnd.tls_cert_path,
self.config.lnd.macaroon_path,
)
if self.config.lightning.backend == 'lnd':
self.lightning_client = LNDLightningClient(
self.config.lightning.lnd_rpc_host,
self.config.lightning.lnd_rpc_port,
self.config.lightning.lnd_tls_cert_path,
self.config.lightning.lnd_macaroon_path,
)
elif self.config.lightning.backend == 'clightning':
self.lightning_client = CLightningClient(
self.config.lightning.clightning_rpc_file,
)
else:
raise Exception('Invalid lightning backend: {}'.format(
self.config.lightning.backend,
))
def create_bitcoin_client(self):
self.bitcoin_client = BitcoinCoreClient(
@ -217,7 +228,7 @@ class SqueakNode:
def create_peer_web_server(self):
self.peer_web_server = SqueakPeerWebServer(
self.config.server.host,
squeak.params.params.DEFAULT_PORT,
self.config.server.port or squeak.params.params.DEFAULT_PORT,
self.peer_handler,
)

View file

@ -389,10 +389,11 @@ class SqueakStore:
))
return get_profile_private_key(profile)
def create_peer(self, peer_name: str, peer_address: PeerAddress):
def create_peer(self, peer_name: str, peer_address: PeerAddress, default_peer_port):
squeak_peer = create_saved_peer(
peer_name,
peer_address,
default_peer_port,
)
return self.squeak_db.insert_peer(squeak_peer)

View file

@ -59,13 +59,10 @@ def create_app(handler):
@app.route('/squeak/<hash>')
def squeak(hash):
try:
squeak_type, squeak_bytes = handler.handle_get_squeak_bytes(hash)
squeak_bytes = handler.handle_get_squeak_bytes(hash)
except NotFoundError:
return "Not found", 404
return jsonify({
'squeak_type': squeak_type,
'squeak_bytes': squeak_bytes.hex(),
})
return squeak_bytes
@app.route('/secretkey/<hash>')
def secret_key(hash):
@ -80,8 +77,9 @@ def create_app(handler):
@app.route('/offer/<hash>')
def offer(hash):
client_host = request.remote_addr
client_port = request.environ.get('REMOTE_PORT') or 0
try:
offer = handler.handle_get_offer(hash, client_host)
offer = handler.handle_get_offer(hash, client_host, client_port)
except NotFoundError:
return "Not found", 404
return jsonify({

View file

@ -22,7 +22,6 @@
import logging
from typing import List
from typing import Optional
from typing import Tuple
from squeak.core.keys import SqueakPublicKey
@ -55,17 +54,16 @@ class SqueakPeerServerHandler(object):
self.node_settings = node_settings
self.config = config
def handle_get_squeak_bytes(self, squeak_hash_str) -> Tuple[str, bytes]:
def handle_get_squeak_bytes(self, squeak_hash_str) -> bytes:
"""Return a tuple with the squeak type and the squeak bytes.
Returns: (str, bytes)
Returns: bytes
"""
squeak_hash = bytes.fromhex(squeak_hash_str)
squeak = self.squeak_controller.get_squeak(squeak_hash)
if not squeak:
raise NotFoundError()
squeak_type = 'resqueak' if squeak.is_resqueak else 'squeak'
return squeak_type, squeak.serialize()
return squeak.serialize()
def handle_get_secret_key(self, squeak_hash_str) -> bytes:
squeak_hash = bytes.fromhex(squeak_hash_str)
@ -77,12 +75,12 @@ class SqueakPeerServerHandler(object):
raise NotFoundError()
return secret_key
def handle_get_offer(self, squeak_hash_str, client_host) -> Offer:
def handle_get_offer(self, squeak_hash_str, client_host, client_port) -> Offer:
squeak_hash = bytes.fromhex(squeak_hash_str)
client_addr = PeerAddress(
network=Network.IPV4,
host=client_host,
port=0,
port=client_port,
)
offer = self.squeak_controller.get_packaged_offer(
squeak_hash,

View file

@ -31,6 +31,11 @@ def peer_name():
yield "fake_peer_name"
@pytest.fixture
def default_peer_port():
yield 9999
@pytest.fixture
def peer_address():
yield PeerAddress(
@ -58,24 +63,37 @@ def peer_address_with_no_port():
)
def test_create_saved_peer(peer_name, peer_address):
def test_create_saved_peer(peer_name, peer_address, default_peer_port):
peer = create_saved_peer(
peer_name,
peer_address,
default_peer_port,
)
assert peer.peer_name == peer_name
assert peer.address == peer_address
def test_create_saved_peer_empty_name(peer_address):
create_saved_peer("", peer_address)
def test_create_saved_peer_empty_name(peer_address, default_peer_port):
create_saved_peer("", peer_address, default_peer_port)
def test_create_saved_peer_use_tor(peer_name, peer_address_with_tor):
def test_create_saved_peer_no_port(peer_name, peer_address_with_no_port, default_peer_port):
peer = create_saved_peer(
peer_name,
peer_address_with_no_port,
default_peer_port,
)
assert peer.peer_name == peer_name
assert peer.address.port == default_peer_port
def test_create_saved_peer_use_tor(peer_name, peer_address_with_tor, default_peer_port):
peer = create_saved_peer(
peer_name,
peer_address_with_tor,
default_peer_port,
)
assert peer.peer_name == peer_name

View file

@ -131,9 +131,10 @@ def failed_payment(payment_request):
@pytest.fixture
def invoice_stream(settled_invoice, unsettled_invoice):
# Length of invoices list is 1.
invoices = [
settled_invoice,
unsettled_invoice,
# unsettled_invoice,
]
yield InvoiceStream(
cancel=lambda: None,
@ -188,6 +189,9 @@ class MockLightningClient(LightningClient):
self.payment = payment
self.invoice_stream = invoice_stream
def init(self):
pass
def get_info(self):
return self.info

View file

@ -109,7 +109,7 @@ def rpc_invoice(preimage, payment_hash, payment_request, price_msat, creation_da
r_hash=payment_hash,
payment_request=payment_request,
value_msat=price_msat,
settled=False,
settled=True,
settle_index=0,
creation_date=creation_date,
expiry=expiry,
@ -122,7 +122,7 @@ def invoice(payment_hash, payment_request, price_msat, creation_date, expiry):
r_hash=payment_hash,
payment_request=payment_request,
value_msat=price_msat,
settled=False,
settled=True,
settle_index=0,
creation_date=creation_date,
expiry=expiry,

View file

@ -167,4 +167,5 @@ def test_create_peer(squeak_store, squeak_controller, peer_address):
squeak_store.create_peer.assert_called_with(
"fake_peer_name",
peer_address,
0,
)

View file

@ -121,6 +121,7 @@ def gen_squeak_peer(peer_name):
return create_saved_peer(
peer_name,
peer_address,
0,
)