mirror of
https://github.com/RoboSats/robosats.git
synced 2025-07-22 15:13:14 +00:00
Add tests for onchain address, pgp sign verification. Improve Dockerfile
This commit is contained in:

committed by
Reckless_Satoshi

parent
79a3df66a2
commit
3e0d451e97
1
.github/workflows/integration-tests.yml
vendored
1
.github/workflows/integration-tests.yml
vendored
@ -32,7 +32,6 @@ jobs:
|
|||||||
- name: Patch Dockerfile and .env-sample
|
- name: Patch Dockerfile and .env-sample
|
||||||
run: |
|
run: |
|
||||||
sed -i "1s/FROM python:.*/FROM python:${{ matrix.python-tag }}/" Dockerfile
|
sed -i "1s/FROM python:.*/FROM python:${{ matrix.python-tag }}/" Dockerfile
|
||||||
sed -i '/RUN pip install --no-cache-dir -r requirements.txt/a COPY requirements_dev.txt .\nRUN pip install --no-cache-dir -r requirements_dev.txt' Dockerfile
|
|
||||||
sed -i "s/^LNVENDOR=.*/LNVENDOR='${{ matrix.ln-vendor }}'/" .env-sample
|
sed -i "s/^LNVENDOR=.*/LNVENDOR='${{ matrix.ln-vendor }}'/" .env-sample
|
||||||
|
|
||||||
- uses: satackey/action-docker-layer-caching@v0.0.11
|
- uses: satackey/action-docker-layer-caching@v0.0.11
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
FROM python:3.11.6-slim-bookworm
|
FROM python:3.11.6-slim-bookworm
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG DEVELOPMENT=False
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/robosats
|
RUN mkdir -p /usr/src/robosats
|
||||||
WORKDIR /usr/src/robosats
|
WORKDIR /usr/src/robosats
|
||||||
@ -17,6 +18,11 @@ RUN python -m pip install --upgrade pip
|
|||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY requirements_dev.txt ./
|
||||||
|
RUN if [ "$DEVELOPMENT" = "true" ]; then \
|
||||||
|
pip install --no-cache-dir -r requirements_dev.txt; \
|
||||||
|
fi
|
||||||
|
|
||||||
# copy current dir's content to container's WORKDIR root i.e. all the contents of the robosats app
|
# copy current dir's content to container's WORKDIR root i.e. all the contents of the robosats app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
@ -79,6 +79,14 @@ class CLNNode:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Cannot get CLN node id: {e}")
|
print(f"Cannot get CLN node id: {e}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def newaddress(cls):
|
||||||
|
"""Only used on tests to fund the regtest node"""
|
||||||
|
nodestub = node_pb2_grpc.NodeStub(cls.node_channel)
|
||||||
|
request = node_pb2.NewaddrRequest()
|
||||||
|
response = nodestub.NewAddr(request)
|
||||||
|
return response.bech32
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def decode_payreq(cls, invoice):
|
def decode_payreq(cls, invoice):
|
||||||
"""Decodes a lightning payment request (invoice)"""
|
"""Decodes a lightning payment request (invoice)"""
|
||||||
|
@ -105,10 +105,11 @@ class LNDNode:
|
|||||||
lightningstub = lightning_pb2_grpc.LightningStub(cls.channel)
|
lightningstub = lightning_pb2_grpc.LightningStub(cls.channel)
|
||||||
request = lightning_pb2.GetInfoRequest()
|
request = lightning_pb2.GetInfoRequest()
|
||||||
response = lightningstub.GetInfo(request)
|
response = lightningstub.GetInfo(request)
|
||||||
log("lightning_pb2_grpc.GetInfo", request, response)
|
|
||||||
|
|
||||||
if response.testnet:
|
if response.testnet:
|
||||||
dummy_address = "tb1qehyqhruxwl2p5pt52k6nxj4v8wwc3f3pg7377x"
|
dummy_address = "tb1qehyqhruxwl2p5pt52k6nxj4v8wwc3f3pg7377x"
|
||||||
|
elif response.chains[0].network == "regtest":
|
||||||
|
dummy_address = "bcrt1q3w8xja7knmycsglnxg2xzjq8uv9u7jdwau25nl"
|
||||||
else:
|
else:
|
||||||
dummy_address = "bc1qgxwaqe4m9mypd7ltww53yv3lyxhcfnhzzvy5j3"
|
dummy_address = "bc1qgxwaqe4m9mypd7ltww53yv3lyxhcfnhzzvy5j3"
|
||||||
# We assume segwit. Use hardcoded address as shortcut so there is no need of user inputs yet.
|
# We assume segwit. Use hardcoded address as shortcut so there is no need of user inputs yet.
|
||||||
|
@ -168,8 +168,8 @@ class TestUtils(TestCase):
|
|||||||
|
|
||||||
def test_validate_pgp_keys(self):
|
def test_validate_pgp_keys(self):
|
||||||
# Example test client generated GPG keys
|
# Example test client generated GPG keys
|
||||||
client_pub_key = r"-----BEGIN PGP PUBLIC KEY BLOCK-----\\xjMEZTWJ1xYJKwYBBAHaRw8BAQdAsfdKb90BurKniu+pBPBDHCkzg08S51W0\mUR0SKqLmdjNTFJvYm9TYXRzIElEIDU1MmRkMWE2NjFhN2FjYTRhNDFmODg5\MTBmZjM0YWMzYjFhYzgwYmI3Nzk0ZWQ5ZmQ1NWQ4Yjc2Yjk3YWFkOTfCjAQQ\FgoAPgWCZTWJ1wQLCQcICZA3N7au4gi/zgMVCAoEFgACAQIZAQKbAwIeARYh\BO5iBLnj0J/E6sntEDc3tq7iCL/OAADkVwEA/tBt9FPqrxLHOPFtyUypppr0\/t6vrl3RrLzCLqqE1nUA/0fmhir2F88KcsxmCJwADo/FglwXGFkjrV4sP6Fj\YBEBzjgEZTWJ1xIKKwYBBAGXVQEFAQEHQCyUIe3sQTaYa/IFNKGNmXz/+hrH\ukcot4TOvi2bD9p8AwEIB8J4BBgWCAAqBYJlNYnXCZA3N7au4gi/zgKbDBYh\BO5iBLnj0J/E6sntEDc3tq7iCL/OAACaFAD7BG3E7TkUoWKtJe5OPzTwX+bM\Xy7hbPSQw0zM9Re8KP0BAIeTG8d280dTK63h/seQAKeMj0zf7AYXr0CscvS7\f38D\=h03E\-----END PGP PUBLIC KEY BLOCK-----"
|
client_pub_key = r"-----BEGIN PGP PUBLIC KEY BLOCK-----\\mDMEZVO9bxYJKwYBBAHaRw8BAQdAVyePBQK63FB2r5ZpIqO998WaqZjmro+LFNH+\sw2raQC0TFJvYm9TYXRzIElEIGVkN2QzYjJiMmU1ODlhYjI2NzIwNjA1ZTc0MTRh\YjRmYmNhMjFjYjRiMzFlNWI0ZTYyYTZmYTUxYzI0YTllYWKIjAQQFgoAPgWCZVO9\bwQLCQcICZAuNFtLSY2XJAMVCAoEFgACAQIZAQKbAwIeARYhBDIhViOFpzWovPuw\vC40W0tJjZckAACTeAEA+AdXmA8p6I+FFqXaFVRh5JRa5ZoO4xhGb+QY00kgZisB\AJee8XdW6FHBj2J3b4M9AYqufdpvuj+lLmaVAshN9U4MuDgEZVO9bxIKKwYBBAGX\VQEFAQEHQORkbvSesg9oJeCRKigTNdQ5tkgmVGXfdz/+vwBIl3E3AwEIB4h4BBgW\CAAqBYJlU71vCZAuNFtLSY2XJAKbDBYhBDIhViOFpzWovPuwvC40W0tJjZckAABZ\1AD/RIJM/WNb28pYqtq4XmeOaqLCrbQs2ua8mXpGBZSl8E0BALWSlbHICYTNy9L6\KV0a5pXbxcXpzejcjpJmVwzuWz8P\=32+r\-----END PGP PUBLIC KEY BLOCK-----"
|
||||||
client_enc_priv_key = r"-----BEGIN PGP PRIVATE KEY BLOCK-----\\xYYEZTWJ1xYJKwYBBAHaRw8BAQdAsfdKb90BurKniu+pBPBDHCkzg08S51W0\mUR0SKqLmdj+CQMICrS3TNCA/LHgxckC+iTUMxkqQJ9GpXWCDacx1rBQCztu\PDgUHNvWdcvW1wWVxU/aJaQLqBTtRVYkJTz332jrKvsSl/LnrfwmUfKgN4nG\Oc1MUm9ib1NhdHMgSUQgNTUyZGQxYTY2MWE3YWNhNGE0MWY4ODkxMGZmMzRh\YzNiMWFjODBiYjc3OTRlZDlmZDU1ZDhiNzZiOTdhYWQ5N8KMBBAWCgA+BYJl\NYnXBAsJBwgJkDc3tq7iCL/OAxUICgQWAAIBAhkBApsDAh4BFiEE7mIEuePQ\n8Tqye0QNze2ruIIv84AAORXAQD+0G30U+qvEsc48W3JTKmmmvT+3q+uXdGs\vMIuqoTWdQD/R+aGKvYXzwpyzGYInAAOj8WCXBcYWSOtXiw/oWNgEQHHiwRl\NYnXEgorBgEEAZdVAQUBAQdALJQh7exBNphr8gU0oY2ZfP/6Gse6Ryi3hM6+\LZsP2nwDAQgH/gkDCPPoYWyzm4mT4N/TDBF11GVq0xSEEcubFqjArFKyibRy\TDnB8+o8BlkRuGClcfRyKkR5/Rp1v5B0n1BuMsc8nY4Yg4BJv4KhsPfXRp4m\31zCeAQYFggAKgWCZTWJ1wmQNze2ruIIv84CmwwWIQTuYgS549CfxOrJ7RA3\N7au4gi/zgAAmhQA+wRtxO05FKFirSXuTj808F/mzF8u4Wz0kMNMzPUXvCj9\AQCHkxvHdvNHUyut4f7HkACnjI9M3+wGF69ArHL0u39/Aw==\=1hCT\-----END PGP PRIVATE KEY BLOCK-----"
|
client_enc_priv_key = r"-----BEGIN PGP PRIVATE KEY BLOCK-----\\xYYEZVO9bxYJKwYBBAHaRw8BAQdAVyePBQK63FB2r5ZpIqO998WaqZjmro+L\FNH+sw2raQD+CQMIHkZZZnDa6d/gHioGTKf6JevirkCBWwz8tFLGFs5DFwjD\tI4ew9CJd09AUxfMq2WvTilhMNrdw2nmqtmAoaIyIo43azVT1VQoxSDnWxFv\Tc1MUm9ib1NhdHMgSUQgZWQ3ZDNiMmIyZTU4OWFiMjY3MjA2MDVlNzQxNGFi\NGZiY2EyMWNiNGIzMWU1YjRlNjJhNmZhNTFjMjRhOWVhYsKMBBAWCgA+BYJl\U71vBAsJBwgJkC40W0tJjZckAxUICgQWAAIBAhkBApsDAh4BFiEEMiFWI4Wn\Nai8+7C8LjRbS0mNlyQAAJN4AQD4B1eYDynoj4UWpdoVVGHklFrlmg7jGEZv\5BjTSSBmKwEAl57xd1boUcGPYndvgz0Biq592m+6P6UuZpUCyE31TgzHiwRl\U71vEgorBgEEAZdVAQUBAQdA5GRu9J6yD2gl4JEqKBM11Dm2SCZUZd93P/6/\AEiXcTcDAQgH/gkDCGSRul0JyboW4JZSQVlHNVlx2mrfE1gRTh2R5hJWU9Kg\aw2gET8OwWDYU4F8wKTo/s7BGn+HN4jrZeLw1k/etKUKLzuPC06KUXhj3rMF\Ti3CeAQYFggAKgWCZVO9bwmQLjRbS0mNlyQCmwwWIQQyIVYjhac1qLz7sLwu\NFtLSY2XJAAAWdQA/0SCTP1jW9vKWKrauF5njmqiwq20LNrmvJl6RgWUpfBN\AQC1kpWxyAmEzcvS+ildGuaV28XF6c3o3I6SZlcM7ls/Dw==\=YAfZ\-----END PGP PRIVATE KEY BLOCK-----"
|
||||||
|
|
||||||
# Example valid formatted GPG keys
|
# Example valid formatted GPG keys
|
||||||
with open("tests/robots/1/pub_key", "r") as file:
|
with open("tests/robots/1/pub_key", "r") as file:
|
||||||
|
@ -126,6 +126,9 @@ class BalanceLog(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Balance at {self.time.strftime('%d/%m/%Y %H:%M:%S')}"
|
return f"Balance at {self.time.strftime('%d/%m/%Y %H:%M:%S')}"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
get_latest_by = "time"
|
||||||
|
|
||||||
|
|
||||||
class Dispute(models.Model):
|
class Dispute(models.Model):
|
||||||
pass
|
pass
|
||||||
|
@ -21,7 +21,10 @@ services:
|
|||||||
network_mode: service:tor
|
network_mode: service:tor
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
build: .
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
DEVELOPMENT: True
|
||||||
image: backend-image
|
image: backend-image
|
||||||
container_name: django-dev
|
container_name: django-dev
|
||||||
restart: always
|
restart: always
|
||||||
@ -30,7 +33,7 @@ services:
|
|||||||
- lnd
|
- lnd
|
||||||
- redis
|
- redis
|
||||||
environment:
|
environment:
|
||||||
DEVELOPMENT: 1
|
DEVELOPMENT: True
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/robosats
|
- .:/usr/src/robosats
|
||||||
- ./node/lnd:/lnd
|
- ./node/lnd:/lnd
|
||||||
|
@ -86,7 +86,7 @@ services:
|
|||||||
- cln:/root/.lightning
|
- cln:/root/.lightning
|
||||||
- ./docker/cln/plugins/cln-grpc-hold:/root/.lightning/plugins/cln-grpc-hold
|
- ./docker/cln/plugins/cln-grpc-hold:/root/.lightning/plugins/cln-grpc-hold
|
||||||
- bitcoin:/root/.bitcoin
|
- bitcoin:/root/.bitcoin
|
||||||
command: --regtest --wumbo --bitcoin-rpcuser=test --bitcoin-rpcpassword=test --rest-host=0.0.0.0 --bind-addr=127.0.0.1:9737 --max-concurrent-htlcs=483 --grpc-port=9999 --grpc-hold-port=9998 --important-plugin=/root/.lightning/plugins/cln-grpc-hold --database-upgrade=true
|
command: --regtest --wumbo --bitcoin-rpcuser=test --bitcoin-rpcpassword=test --rest-host=0.0.0.0 --rest-port=3010 --bind-addr=127.0.0.1:9737 --max-concurrent-htlcs=483 --grpc-port=9999 --grpc-hold-port=9998 --important-plugin=/root/.lightning/plugins/cln-grpc-hold --database-upgrade=true
|
||||||
depends_on:
|
depends_on:
|
||||||
- bitcoind
|
- bitcoind
|
||||||
network_mode: service:bitcoind
|
network_mode: service:bitcoind
|
||||||
@ -132,7 +132,10 @@ services:
|
|||||||
network_mode: service:bitcoind
|
network_mode: service:bitcoind
|
||||||
|
|
||||||
coordinator:
|
coordinator:
|
||||||
build: .
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
DEVELOPMENT: True
|
||||||
image: robosats-image
|
image: robosats-image
|
||||||
container_name: coordinator
|
container_name: coordinator
|
||||||
restart: always
|
restart: always
|
||||||
@ -142,6 +145,9 @@ services:
|
|||||||
USE_TOR: False
|
USE_TOR: False
|
||||||
MACAROON_PATH: 'data/chain/bitcoin/regtest/admin.macaroon'
|
MACAROON_PATH: 'data/chain/bitcoin/regtest/admin.macaroon'
|
||||||
CLN_DIR: '/cln/regtest/'
|
CLN_DIR: '/cln/regtest/'
|
||||||
|
BITCOIND_RPCURL: 'http://127.0.0.1:18443'
|
||||||
|
BITCOIND_RPCUSER: 'test'
|
||||||
|
BITCOIND_RPCPASSWORD: 'test'
|
||||||
env_file:
|
env_file:
|
||||||
- ${ROBOSATS_ENVS_FILE}
|
- ${ROBOSATS_ENVS_FILE}
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -3,10 +3,12 @@ import sys
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from decouple import config
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
from requests.exceptions import ReadTimeout
|
from requests.exceptions import ReadTimeout
|
||||||
|
|
||||||
wait_step = 0.2
|
LNVENDOR = config("LNVENDOR", cast=str, default="LND")
|
||||||
|
WAIT_STEP = 0.2
|
||||||
|
|
||||||
|
|
||||||
def get_node(name="robot"):
|
def get_node(name="robot"):
|
||||||
@ -59,8 +61,8 @@ def wait_for_lnd_node_sync(node_name):
|
|||||||
f"\rWaiting for {node_name} node chain sync {round(waited,1)}s"
|
f"\rWaiting for {node_name} node chain sync {round(waited,1)}s"
|
||||||
)
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
waited += wait_step
|
waited += WAIT_STEP
|
||||||
time.sleep(wait_step)
|
time.sleep(WAIT_STEP)
|
||||||
|
|
||||||
|
|
||||||
def LND_has_active_channels(node_name):
|
def LND_has_active_channels(node_name):
|
||||||
@ -97,8 +99,8 @@ def wait_for_active_channels(lnvendor, node_name="coordinator"):
|
|||||||
)
|
)
|
||||||
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
waited += wait_step
|
waited += WAIT_STEP
|
||||||
time.sleep(wait_step)
|
time.sleep(WAIT_STEP)
|
||||||
|
|
||||||
|
|
||||||
def wait_for_cln_node_sync():
|
def wait_for_cln_node_sync():
|
||||||
@ -112,8 +114,8 @@ def wait_for_cln_node_sync():
|
|||||||
f"\rWaiting for coordinator CLN node sync {round(waited,1)}s"
|
f"\rWaiting for coordinator CLN node sync {round(waited,1)}s"
|
||||||
)
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
waited += wait_step
|
waited += WAIT_STEP
|
||||||
time.sleep(wait_step)
|
time.sleep(WAIT_STEP)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -131,8 +133,66 @@ def wait_for_cln_active_channels():
|
|||||||
f"\rWaiting for coordinator CLN node channels to be active {round(waited,1)}s"
|
f"\rWaiting for coordinator CLN node channels to be active {round(waited,1)}s"
|
||||||
)
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
waited += wait_step
|
waited += WAIT_STEP
|
||||||
time.sleep(wait_step)
|
time.sleep(WAIT_STEP)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_nodes_sync():
|
||||||
|
wait_for_lnd_node_sync("robot")
|
||||||
|
if LNVENDOR == "LND":
|
||||||
|
wait_for_lnd_node_sync("coordinator")
|
||||||
|
elif LNVENDOR == "CLN":
|
||||||
|
wait_for_cln_node_sync()
|
||||||
|
|
||||||
|
|
||||||
|
def wait_channels():
|
||||||
|
wait_for_active_channels(LNVENDOR, "coordinator")
|
||||||
|
wait_for_active_channels("LND", "robot")
|
||||||
|
|
||||||
|
|
||||||
|
def set_up_regtest_network():
|
||||||
|
if channel_is_active():
|
||||||
|
print("Regtest network was already ready. Skipping initalization.")
|
||||||
|
return
|
||||||
|
# Fund two LN nodes in regtest and open channels
|
||||||
|
# Coordinator is either LND or CLN. Robot user is always LND.
|
||||||
|
if LNVENDOR == "LND":
|
||||||
|
coordinator_node_id = get_lnd_node_id("coordinator")
|
||||||
|
coordinator_port = 9735
|
||||||
|
elif LNVENDOR == "CLN":
|
||||||
|
coordinator_node_id = get_cln_node_id()
|
||||||
|
coordinator_port = 9737
|
||||||
|
|
||||||
|
print("Coordinator Node ID: ", coordinator_node_id)
|
||||||
|
|
||||||
|
# Fund both robot and coordinator nodes
|
||||||
|
robot_funding_address = create_address("robot")
|
||||||
|
coordinator_funding_address = create_address("coordinator")
|
||||||
|
generate_blocks(coordinator_funding_address, 1)
|
||||||
|
generate_blocks(robot_funding_address, 101)
|
||||||
|
wait_nodes_sync()
|
||||||
|
|
||||||
|
# Open channel between Robot user and coordinator
|
||||||
|
print(f"\nOpening channel from Robot user node to coordinator {LNVENDOR} node")
|
||||||
|
connect_to_node("robot", coordinator_node_id, f"localhost:{coordinator_port}")
|
||||||
|
open_channel("robot", coordinator_node_id, 100_000_000, 50_000_000)
|
||||||
|
|
||||||
|
# Generate 10 blocks so the channel becomes active and wait for sync
|
||||||
|
generate_blocks(robot_funding_address, 10)
|
||||||
|
|
||||||
|
# Wait a tiny bit so payments can be done in the new channel
|
||||||
|
wait_nodes_sync()
|
||||||
|
wait_channels()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def channel_is_active():
|
||||||
|
robot_channel_active = LND_has_active_channels("robot")
|
||||||
|
if LNVENDOR == "LND":
|
||||||
|
coordinator_channel_active = LND_has_active_channels("coordinator")
|
||||||
|
elif LNVENDOR == "CLN":
|
||||||
|
coordinator_channel_active = CLN_has_active_channels()
|
||||||
|
return robot_channel_active and coordinator_channel_active
|
||||||
|
|
||||||
|
|
||||||
def connect_to_node(node_name, node_id, ip_port):
|
def connect_to_node(node_name, node_id, ip_port):
|
||||||
@ -151,7 +211,7 @@ def connect_to_node(node_name, node_id, ip_port):
|
|||||||
if "already connected to peer" in response.json()["message"]:
|
if "already connected to peer" in response.json()["message"]:
|
||||||
return response.json()
|
return response.json()
|
||||||
print(f"Could not peer coordinator node: {response.json()}")
|
print(f"Could not peer coordinator node: {response.json()}")
|
||||||
time.sleep(wait_step)
|
time.sleep(WAIT_STEP)
|
||||||
|
|
||||||
|
|
||||||
def open_channel(node_name, node_id, local_funding_amount, push_sat):
|
def open_channel(node_name, node_id, local_funding_amount, push_sat):
|
||||||
@ -169,7 +229,7 @@ def open_channel(node_name, node_id, local_funding_amount, push_sat):
|
|||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
def create_address(node_name):
|
def create_address_LND(node_name):
|
||||||
node = get_node(node_name)
|
node = get_node(node_name)
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f'http://localhost:{node["port"]}/v1/newaddress', headers=node["headers"]
|
f'http://localhost:{node["port"]}/v1/newaddress', headers=node["headers"]
|
||||||
@ -177,6 +237,19 @@ def create_address(node_name):
|
|||||||
return response.json()["address"]
|
return response.json()["address"]
|
||||||
|
|
||||||
|
|
||||||
|
def create_address_CLN():
|
||||||
|
from api.lightning.cln import CLNNode
|
||||||
|
|
||||||
|
return CLNNode.newaddress()
|
||||||
|
|
||||||
|
|
||||||
|
def create_address(node_name):
|
||||||
|
if node_name == "coordinator" and LNVENDOR == "CLN":
|
||||||
|
return create_address_CLN()
|
||||||
|
else:
|
||||||
|
return create_address_LND(node_name)
|
||||||
|
|
||||||
|
|
||||||
def generate_blocks(address, num_blocks):
|
def generate_blocks(address, num_blocks):
|
||||||
print(f"Mining {num_blocks} blocks")
|
print(f"Mining {num_blocks} blocks")
|
||||||
data = {
|
data = {
|
||||||
@ -199,7 +272,7 @@ def pay_invoice(node_name, invoice):
|
|||||||
f'http://localhost:{node["port"]}/v1/channels/transactions',
|
f'http://localhost:{node["port"]}/v1/channels/transactions',
|
||||||
json=data,
|
json=data,
|
||||||
headers=node["headers"],
|
headers=node["headers"],
|
||||||
timeout=1,
|
timeout=0.3, # 0.15s is enough for LND to LND hodl ACCEPT.
|
||||||
)
|
)
|
||||||
except ReadTimeout:
|
except ReadTimeout:
|
||||||
# Request to pay hodl invoice has timed out: that's good!
|
# Request to pay hodl invoice has timed out: that's good!
|
||||||
|
22
tests/pgp_utils.py
Normal file
22
tests/pgp_utils.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import gnupg
|
||||||
|
|
||||||
|
|
||||||
|
def sign_message(message, private_key_path, passphrase_path):
|
||||||
|
gpg = gnupg.GPG()
|
||||||
|
|
||||||
|
with open(private_key_path, "r") as f:
|
||||||
|
private_key = f.read()
|
||||||
|
|
||||||
|
with open(passphrase_path, "r") as f:
|
||||||
|
passphrase = f.read()
|
||||||
|
|
||||||
|
gpg.import_keys(private_key, passphrase=passphrase)
|
||||||
|
|
||||||
|
# keyid=import_result.fingerprints[0]
|
||||||
|
signed_message = gpg.sign(
|
||||||
|
message, passphrase=passphrase, extra_args=["--digest-algo", "SHA512"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# [print(name, getattr(signed_message, name)) for name in dir(signed_message) if not callable(getattr(signed_message, name))]
|
||||||
|
|
||||||
|
return signed_message.data.decode(encoding="UTF-8", errors="strict")
|
@ -1 +1 @@
|
|||||||
qz*fp3CzNfK0Y2MWx;<Ke}2&S}ymduQyhjoJtIZE
|
Y#>]mLP@:Ka2/t_;no*:0GeGd}j2rSQ{}1qwZCED
|
@ -1,18 +1,18 @@
|
|||||||
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
xYYEZTWJ1xYJKwYBBAHaRw8BAQdAsfdKb90BurKniu+pBPBDHCkzg08S51W0
|
xYYEZVO9bxYJKwYBBAHaRw8BAQdAVyePBQK63FB2r5ZpIqO998WaqZjmro+L
|
||||||
mUR0SKqLmdj+CQMICrS3TNCA/LHgxckC+iTUMxkqQJ9GpXWCDacx1rBQCztu
|
FNH+sw2raQD+CQMIHkZZZnDa6d/gHioGTKf6JevirkCBWwz8tFLGFs5DFwjD
|
||||||
PDgUHNvWdcvW1wWVxU/aJaQLqBTtRVYkJTz332jrKvsSl/LnrfwmUfKgN4nG
|
tI4ew9CJd09AUxfMq2WvTilhMNrdw2nmqtmAoaIyIo43azVT1VQoxSDnWxFv
|
||||||
Oc1MUm9ib1NhdHMgSUQgNTUyZGQxYTY2MWE3YWNhNGE0MWY4ODkxMGZmMzRh
|
Tc1MUm9ib1NhdHMgSUQgZWQ3ZDNiMmIyZTU4OWFiMjY3MjA2MDVlNzQxNGFi
|
||||||
YzNiMWFjODBiYjc3OTRlZDlmZDU1ZDhiNzZiOTdhYWQ5N8KMBBAWCgA+BYJl
|
NGZiY2EyMWNiNGIzMWU1YjRlNjJhNmZhNTFjMjRhOWVhYsKMBBAWCgA+BYJl
|
||||||
NYnXBAsJBwgJkDc3tq7iCL/OAxUICgQWAAIBAhkBApsDAh4BFiEE7mIEuePQ
|
U71vBAsJBwgJkC40W0tJjZckAxUICgQWAAIBAhkBApsDAh4BFiEEMiFWI4Wn
|
||||||
n8Tqye0QNze2ruIIv84AAORXAQD+0G30U+qvEsc48W3JTKmmmvT+3q+uXdGs
|
Nai8+7C8LjRbS0mNlyQAAJN4AQD4B1eYDynoj4UWpdoVVGHklFrlmg7jGEZv
|
||||||
vMIuqoTWdQD/R+aGKvYXzwpyzGYInAAOj8WCXBcYWSOtXiw/oWNgEQHHiwRl
|
5BjTSSBmKwEAl57xd1boUcGPYndvgz0Biq592m+6P6UuZpUCyE31TgzHiwRl
|
||||||
NYnXEgorBgEEAZdVAQUBAQdALJQh7exBNphr8gU0oY2ZfP/6Gse6Ryi3hM6+
|
U71vEgorBgEEAZdVAQUBAQdA5GRu9J6yD2gl4JEqKBM11Dm2SCZUZd93P/6/
|
||||||
LZsP2nwDAQgH/gkDCPPoYWyzm4mT4N/TDBF11GVq0xSEEcubFqjArFKyibRy
|
AEiXcTcDAQgH/gkDCGSRul0JyboW4JZSQVlHNVlx2mrfE1gRTh2R5hJWU9Kg
|
||||||
TDnB8+o8BlkRuGClcfRyKkR5/Rp1v5B0n1BuMsc8nY4Yg4BJv4KhsPfXRp4m
|
aw2gET8OwWDYU4F8wKTo/s7BGn+HN4jrZeLw1k/etKUKLzuPC06KUXhj3rMF
|
||||||
31zCeAQYFggAKgWCZTWJ1wmQNze2ruIIv84CmwwWIQTuYgS549CfxOrJ7RA3
|
Ti3CeAQYFggAKgWCZVO9bwmQLjRbS0mNlyQCmwwWIQQyIVYjhac1qLz7sLwu
|
||||||
N7au4gi/zgAAmhQA+wRtxO05FKFirSXuTj808F/mzF8u4Wz0kMNMzPUXvCj9
|
NFtLSY2XJAAAWdQA/0SCTP1jW9vKWKrauF5njmqiwq20LNrmvJl6RgWUpfBN
|
||||||
AQCHkxvHdvNHUyut4f7HkACnjI9M3+wGF69ArHL0u39/Aw==
|
AQC1kpWxyAmEzcvS+ildGuaV28XF6c3o3I6SZlcM7ls/Dw==
|
||||||
=1hCT
|
=YAfZ
|
||||||
-----END PGP PRIVATE KEY BLOCK-----
|
-----END PGP PRIVATE KEY BLOCK-----
|
@ -1 +1 @@
|
|||||||
MyopicRacket333
|
UptightPub730
|
@ -1,14 +1,14 @@
|
|||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
mDMEZTWJ1xYJKwYBBAHaRw8BAQdAsfdKb90BurKniu+pBPBDHCkzg08S51W0mUR0
|
mDMEZVO9bxYJKwYBBAHaRw8BAQdAVyePBQK63FB2r5ZpIqO998WaqZjmro+LFNH+
|
||||||
SKqLmdi0TFJvYm9TYXRzIElEIDU1MmRkMWE2NjFhN2FjYTRhNDFmODg5MTBmZjM0
|
sw2raQC0TFJvYm9TYXRzIElEIGVkN2QzYjJiMmU1ODlhYjI2NzIwNjA1ZTc0MTRh
|
||||||
YWMzYjFhYzgwYmI3Nzk0ZWQ5ZmQ1NWQ4Yjc2Yjk3YWFkOTeIjAQQFgoAPgWCZTWJ
|
YjRmYmNhMjFjYjRiMzFlNWI0ZTYyYTZmYTUxYzI0YTllYWKIjAQQFgoAPgWCZVO9
|
||||||
1wQLCQcICZA3N7au4gi/zgMVCAoEFgACAQIZAQKbAwIeARYhBO5iBLnj0J/E6snt
|
bwQLCQcICZAuNFtLSY2XJAMVCAoEFgACAQIZAQKbAwIeARYhBDIhViOFpzWovPuw
|
||||||
EDc3tq7iCL/OAADkVwEA/tBt9FPqrxLHOPFtyUypppr0/t6vrl3RrLzCLqqE1nUA
|
vC40W0tJjZckAACTeAEA+AdXmA8p6I+FFqXaFVRh5JRa5ZoO4xhGb+QY00kgZisB
|
||||||
/0fmhir2F88KcsxmCJwADo/FglwXGFkjrV4sP6FjYBEBuDgEZTWJ1xIKKwYBBAGX
|
AJee8XdW6FHBj2J3b4M9AYqufdpvuj+lLmaVAshN9U4MuDgEZVO9bxIKKwYBBAGX
|
||||||
VQEFAQEHQCyUIe3sQTaYa/IFNKGNmXz/+hrHukcot4TOvi2bD9p8AwEIB4h4BBgW
|
VQEFAQEHQORkbvSesg9oJeCRKigTNdQ5tkgmVGXfdz/+vwBIl3E3AwEIB4h4BBgW
|
||||||
CAAqBYJlNYnXCZA3N7au4gi/zgKbDBYhBO5iBLnj0J/E6sntEDc3tq7iCL/OAACa
|
CAAqBYJlU71vCZAuNFtLSY2XJAKbDBYhBDIhViOFpzWovPuwvC40W0tJjZckAABZ
|
||||||
FAD7BG3E7TkUoWKtJe5OPzTwX+bMXy7hbPSQw0zM9Re8KP0BAIeTG8d280dTK63h
|
1AD/RIJM/WNb28pYqtq4XmeOaqLCrbQs2ua8mXpGBZSl8E0BALWSlbHICYTNy9L6
|
||||||
/seQAKeMj0zf7AYXr0CscvS7f38D
|
KV0a5pXbxcXpzejcjpJmVwzuWz8P
|
||||||
=+xY8
|
=32+r
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
-----BEGIN PGP SIGNED MESSAGE-----
|
-----BEGIN PGP SIGNED MESSAGE-----
|
||||||
Hash: SHA512
|
Hash: SHA512
|
||||||
|
|
||||||
test
|
bcrt1qrrvml8tr4lkwlqpg9g394tye6s5950qf9tj9e9
|
||||||
-----BEGIN PGP SIGNATURE-----
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
wnUEARYKACcFgmU22/EJkDc3tq7iCL/OFiEE7mIEuePQn8Tqye0QNze2ruII
|
iHUEARYIAB0WIQQyIVYjhac1qLz7sLwuNFtLSY2XJAUCZVUUTQAKCRAuNFtLSY2X
|
||||||
v84AAJDMAP9JXQJNRYUiPaSroIfmfJccPQeaVuHTnl0fJqLToL6GbAD/Rt7c
|
JA4zAP9PW71ZvQglGnexa9LYryVbnI0w3WnWXYaOmowy/aMM5wD/a2xZNk95DiDq
|
||||||
Y67Co6RJi70vytMorPKWmiX6C/mrnKL0auQC8gQ=
|
s8PnKT41yS+QIBrn7+iZ2DqlCjKdNgc=
|
||||||
=1ouc
|
=NOcM
|
||||||
-----END PGP SIGNATURE-----
|
-----END PGP SIGNATURE-----
|
||||||
|
@ -1 +1 @@
|
|||||||
gUNa4xT98AA2AQWj4hsdCWFixOmvReu5If3R
|
C2etfi7nPeUD7rCcwAOy4XoLvEAxbTRGSK6H
|
@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
@ -10,25 +9,17 @@ from django.urls import reverse
|
|||||||
from api.management.commands.follow_invoices import Command as FollowInvoices
|
from api.management.commands.follow_invoices import Command as FollowInvoices
|
||||||
from api.models import Currency, Order
|
from api.models import Currency, Order
|
||||||
from api.tasks import cache_market
|
from api.tasks import cache_market
|
||||||
|
from control.models import BalanceLog
|
||||||
from control.tasks import compute_node_balance
|
from control.tasks import compute_node_balance
|
||||||
from tests.node_utils import (
|
from tests.node_utils import (
|
||||||
CLN_has_active_channels,
|
add_invoice,
|
||||||
LND_has_active_channels,
|
|
||||||
connect_to_node,
|
|
||||||
create_address,
|
create_address,
|
||||||
generate_blocks,
|
|
||||||
get_cln_node_id,
|
|
||||||
get_lnd_node_id,
|
|
||||||
open_channel,
|
|
||||||
pay_invoice,
|
pay_invoice,
|
||||||
wait_for_active_channels,
|
set_up_regtest_network,
|
||||||
wait_for_cln_node_sync,
|
|
||||||
wait_for_lnd_node_sync,
|
|
||||||
)
|
)
|
||||||
|
from tests.pgp_utils import sign_message
|
||||||
from tests.test_api import BaseAPITestCase
|
from tests.test_api import BaseAPITestCase
|
||||||
|
|
||||||
LNVENDOR = config("LNVENDOR", cast=str, default="LND")
|
|
||||||
|
|
||||||
|
|
||||||
def read_file(file_path):
|
def read_file(file_path):
|
||||||
"""
|
"""
|
||||||
@ -58,25 +49,6 @@ class TradeTest(BaseAPITestCase):
|
|||||||
"longitude": 135.503,
|
"longitude": 135.503,
|
||||||
}
|
}
|
||||||
|
|
||||||
def wait_nodes_sync():
|
|
||||||
wait_for_lnd_node_sync("robot")
|
|
||||||
if LNVENDOR == "LND":
|
|
||||||
wait_for_lnd_node_sync("coordinator")
|
|
||||||
elif LNVENDOR == "CLN":
|
|
||||||
wait_for_cln_node_sync()
|
|
||||||
|
|
||||||
def wait_channels():
|
|
||||||
wait_for_active_channels("LND", "robot")
|
|
||||||
wait_for_active_channels(LNVENDOR, "coordinator")
|
|
||||||
|
|
||||||
def channel_is_active():
|
|
||||||
robot_channel_active = LND_has_active_channels("robot")
|
|
||||||
if LNVENDOR == "LND":
|
|
||||||
coordinator_channel_active = LND_has_active_channels("coordinator")
|
|
||||||
elif LNVENDOR == "CLN":
|
|
||||||
coordinator_channel_active = CLN_has_active_channels()
|
|
||||||
return robot_channel_active and coordinator_channel_active
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
"""
|
"""
|
||||||
@ -88,40 +60,8 @@ class TradeTest(BaseAPITestCase):
|
|||||||
# Fetch currency prices from external APIs
|
# Fetch currency prices from external APIs
|
||||||
cache_market()
|
cache_market()
|
||||||
|
|
||||||
# Skip node setup and channel creation if both nodes have an active channel already
|
# Initialize bitcoin core, mine some blocks, connect nodes, open channel
|
||||||
if cls.channel_is_active():
|
set_up_regtest_network()
|
||||||
print("Regtest network was already ready. Skipping initalization.")
|
|
||||||
# Take the first node balances snapshot
|
|
||||||
compute_node_balance()
|
|
||||||
return
|
|
||||||
|
|
||||||
# Fund two LN nodes in regtest and open channels
|
|
||||||
# Coordinator is either LND or CLN. Robot user is always LND.
|
|
||||||
if LNVENDOR == "LND":
|
|
||||||
coordinator_node_id = get_lnd_node_id("coordinator")
|
|
||||||
coordinator_port = 9735
|
|
||||||
elif LNVENDOR == "CLN":
|
|
||||||
coordinator_node_id = get_cln_node_id()
|
|
||||||
coordinator_port = 9737
|
|
||||||
|
|
||||||
print("Coordinator Node ID: ", coordinator_node_id)
|
|
||||||
|
|
||||||
funding_address = create_address("robot")
|
|
||||||
generate_blocks(funding_address, 101)
|
|
||||||
cls.wait_nodes_sync()
|
|
||||||
|
|
||||||
# Open channel between Robot user and coordinator
|
|
||||||
print(f"\nOpening channel from Robot user node to coordinator {LNVENDOR} node")
|
|
||||||
connect_to_node("robot", coordinator_node_id, f"localhost:{coordinator_port}")
|
|
||||||
open_channel("robot", coordinator_node_id, 100_000_000, 50_000_000)
|
|
||||||
|
|
||||||
# Generate 10 blocks so the channel becomes active and wait for sync
|
|
||||||
generate_blocks(funding_address, 10)
|
|
||||||
|
|
||||||
# Wait a tiny bit so payments can be done in the new channel
|
|
||||||
cls.wait_nodes_sync()
|
|
||||||
cls.wait_channels()
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# Take the first node balances snapshot
|
# Take the first node balances snapshot
|
||||||
compute_node_balance()
|
compute_node_balance()
|
||||||
@ -155,6 +95,25 @@ class TradeTest(BaseAPITestCase):
|
|||||||
usd.timestamp, datetime, "External price timestamp is not a datetime"
|
usd.timestamp, datetime, "External price timestamp is not a datetime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_initial_balance_log(self):
|
||||||
|
"""
|
||||||
|
Test if the initial node BalanceLog is correct.
|
||||||
|
One channel should exist with 0.5BTC in local.
|
||||||
|
No onchain balance should exist.
|
||||||
|
"""
|
||||||
|
balance_log = BalanceLog.objects.latest()
|
||||||
|
|
||||||
|
self.assertIsInstance(balance_log.time, datetime)
|
||||||
|
self.assertTrue(balance_log.total > 0)
|
||||||
|
self.assertTrue(balance_log.ln_local > 0)
|
||||||
|
self.assertEqual(balance_log.ln_local_unsettled, 0)
|
||||||
|
self.assertTrue(balance_log.ln_remote > 0)
|
||||||
|
self.assertEqual(balance_log.ln_remote_unsettled, 0)
|
||||||
|
self.assertTrue(balance_log.onchain_total > 0)
|
||||||
|
self.assertTrue(balance_log.onchain_confirmed > 0)
|
||||||
|
self.assertEqual(balance_log.onchain_unconfirmed, 0)
|
||||||
|
self.assertTrue(balance_log.onchain_fraction > 0)
|
||||||
|
|
||||||
def get_robot_auth(self, robot_index, first_encounter=False):
|
def get_robot_auth(self, robot_index, first_encounter=False):
|
||||||
"""
|
"""
|
||||||
Create an AUTH header that embeds token, pub_key, and enc_priv_key into a single string
|
Create an AUTH header that embeds token, pub_key, and enc_priv_key into a single string
|
||||||
@ -365,12 +324,8 @@ class TradeTest(BaseAPITestCase):
|
|||||||
self.assertResponse(response)
|
self.assertResponse(response)
|
||||||
|
|
||||||
self.assertEqual(data["id"], order_made_data["id"])
|
self.assertEqual(data["id"], order_made_data["id"])
|
||||||
self.assertTrue(
|
self.assertIsInstance(datetime.fromisoformat(data["created_at"]), datetime)
|
||||||
isinstance(datetime.fromisoformat(data["created_at"]), datetime)
|
self.assertIsInstance(datetime.fromisoformat(data["expires_at"]), datetime)
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
isinstance(datetime.fromisoformat(data["expires_at"]), datetime)
|
|
||||||
)
|
|
||||||
self.assertTrue(data["is_maker"])
|
self.assertTrue(data["is_maker"])
|
||||||
self.assertTrue(data["is_participant"])
|
self.assertTrue(data["is_participant"])
|
||||||
self.assertTrue(data["is_buyer"])
|
self.assertTrue(data["is_buyer"])
|
||||||
@ -382,11 +337,11 @@ class TradeTest(BaseAPITestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
data["ur_nick"], read_file(f"tests/robots/{robot_index}/nickname")
|
data["ur_nick"], read_file(f"tests/robots/{robot_index}/nickname")
|
||||||
)
|
)
|
||||||
self.assertTrue(isinstance(data["satoshis_now"], int))
|
self.assertIsInstance(data["satoshis_now"], int)
|
||||||
self.assertFalse(data["maker_locked"])
|
self.assertFalse(data["maker_locked"])
|
||||||
self.assertFalse(data["taker_locked"])
|
self.assertFalse(data["taker_locked"])
|
||||||
self.assertFalse(data["escrow_locked"])
|
self.assertFalse(data["escrow_locked"])
|
||||||
self.assertTrue(isinstance(data["bond_satoshis"], int))
|
self.assertIsInstance(data["bond_satoshis"], int)
|
||||||
|
|
||||||
# Cancel order to avoid leaving pending HTLCs after a successful test
|
# Cancel order to avoid leaving pending HTLCs after a successful test
|
||||||
self.cancel_order(data["id"])
|
self.cancel_order(data["id"])
|
||||||
@ -441,8 +396,8 @@ class TradeTest(BaseAPITestCase):
|
|||||||
public_data = json.loads(public_response.content.decode())
|
public_data = json.loads(public_response.content.decode())
|
||||||
|
|
||||||
self.assertFalse(public_data["is_participant"])
|
self.assertFalse(public_data["is_participant"])
|
||||||
self.assertTrue(isinstance(public_data["price_now"], float))
|
self.assertIsInstance(public_data["price_now"], float)
|
||||||
self.assertTrue(isinstance(data["satoshis_now"], int))
|
self.assertIsInstance(data["satoshis_now"], int)
|
||||||
|
|
||||||
# Cancel order to avoid leaving pending HTLCs after a successful test
|
# Cancel order to avoid leaving pending HTLCs after a successful test
|
||||||
self.cancel_order(data["id"])
|
self.cancel_order(data["id"])
|
||||||
@ -533,6 +488,7 @@ class TradeTest(BaseAPITestCase):
|
|||||||
taker_index = 2
|
taker_index = 2
|
||||||
maker_form = self.maker_form_buy_with_range
|
maker_form = self.maker_form_buy_with_range
|
||||||
|
|
||||||
|
# Taker GET
|
||||||
response = self.make_and_lock_contract(maker_form, 80, maker_index, taker_index)
|
response = self.make_and_lock_contract(maker_form, 80, maker_index, taker_index)
|
||||||
data = json.loads(response.content.decode())
|
data = json.loads(response.content.decode())
|
||||||
|
|
||||||
@ -547,6 +503,26 @@ class TradeTest(BaseAPITestCase):
|
|||||||
self.assertTrue(data["taker_locked"])
|
self.assertTrue(data["taker_locked"])
|
||||||
self.assertFalse(data["escrow_locked"])
|
self.assertFalse(data["escrow_locked"])
|
||||||
|
|
||||||
|
# Maker GET
|
||||||
|
response = self.get_order(data["id"], maker_index)
|
||||||
|
data = json.loads(response.content.decode())
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertResponse(response)
|
||||||
|
|
||||||
|
self.assertEqual(data["status_message"], Order.Status(Order.Status.WF2).label)
|
||||||
|
self.assertTrue(data["swap_allowed"])
|
||||||
|
self.assertIsInstance(data["suggested_mining_fee_rate"], int)
|
||||||
|
self.assertIsInstance(data["swap_fee_rate"], float)
|
||||||
|
self.assertTrue(data["suggested_mining_fee_rate"] > 0)
|
||||||
|
self.assertTrue(data["swap_fee_rate"] > 0)
|
||||||
|
self.assertEqual(data["maker_status"], "Active")
|
||||||
|
self.assertEqual(data["taker_status"], "Active")
|
||||||
|
self.assertTrue(data["is_participant"])
|
||||||
|
self.assertTrue(data["maker_locked"])
|
||||||
|
self.assertTrue(data["taker_locked"])
|
||||||
|
self.assertFalse(data["escrow_locked"])
|
||||||
|
|
||||||
# Cancel order to avoid leaving pending HTLCs after a successful test
|
# Cancel order to avoid leaving pending HTLCs after a successful test
|
||||||
self.cancel_order(data["id"])
|
self.cancel_order(data["id"])
|
||||||
|
|
||||||
@ -561,8 +537,6 @@ class TradeTest(BaseAPITestCase):
|
|||||||
|
|
||||||
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
|
# Maker's first order fetch. Should trigger maker bond hold invoice generation.
|
||||||
response = self.get_order(locked_taker_response_data["id"], taker_index)
|
response = self.get_order(locked_taker_response_data["id"], taker_index)
|
||||||
print("HEREEEEEEEEEEEEEEEEEEEEEEREEEEEEEEEEEEEEEE")
|
|
||||||
print(response.json())
|
|
||||||
invoice = response.json()["escrow_invoice"]
|
invoice = response.json()["escrow_invoice"]
|
||||||
|
|
||||||
# Lock the invoice from the robot's node
|
# Lock the invoice from the robot's node
|
||||||
@ -597,3 +571,111 @@ class TradeTest(BaseAPITestCase):
|
|||||||
|
|
||||||
# Cancel order to avoid leaving pending HTLCs after a successful test
|
# Cancel order to avoid leaving pending HTLCs after a successful test
|
||||||
self.cancel_order(data["id"], 2)
|
self.cancel_order(data["id"], 2)
|
||||||
|
|
||||||
|
def submit_payout_address(self, order_id, robot_index=1):
|
||||||
|
path = reverse("order")
|
||||||
|
params = f"?order_id={order_id}"
|
||||||
|
headers = self.get_robot_auth(robot_index)
|
||||||
|
|
||||||
|
payout_address = create_address("robot")
|
||||||
|
signed_payout_address = sign_message(
|
||||||
|
payout_address,
|
||||||
|
passphrase_path=f"tests/robots/{robot_index}/token",
|
||||||
|
private_key_path=f"tests/robots/{robot_index}/enc_priv_key",
|
||||||
|
)
|
||||||
|
body = {"action": "update_address", "address": signed_payout_address}
|
||||||
|
|
||||||
|
response = self.client.post(path + params, body, **headers)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def trade_to_submitted_address(
|
||||||
|
self, maker_form, take_amount=80, maker_index=1, taker_index=2
|
||||||
|
):
|
||||||
|
response_escrow_locked = self.trade_to_locked_escrow(
|
||||||
|
maker_form, take_amount, maker_index, taker_index
|
||||||
|
)
|
||||||
|
response = self.submit_payout_address(
|
||||||
|
response_escrow_locked.json()["id"], maker_index
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def test_trade_to_submitted_address(self):
|
||||||
|
"""
|
||||||
|
Tests a trade from order creation until escrow locked, before
|
||||||
|
invoice/address is submitted by buyer.
|
||||||
|
"""
|
||||||
|
maker_index = 1
|
||||||
|
taker_index = 2
|
||||||
|
maker_form = self.maker_form_buy_with_range
|
||||||
|
|
||||||
|
response = self.trade_to_submitted_address(
|
||||||
|
maker_form, 80, maker_index, taker_index
|
||||||
|
)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertResponse(response)
|
||||||
|
|
||||||
|
self.assertEqual(data["status_message"], Order.Status(Order.Status.CHA).label)
|
||||||
|
|
||||||
|
self.assertFalse(data["is_fiat_sent"])
|
||||||
|
|
||||||
|
# Cancel order to avoid leaving pending HTLCs after a successful test
|
||||||
|
self.cancel_order(data["id"])
|
||||||
|
|
||||||
|
def submit_payout_invoice(self, order_id, num_satoshis, robot_index=1):
|
||||||
|
path = reverse("order")
|
||||||
|
params = f"?order_id={order_id}"
|
||||||
|
headers = self.get_robot_auth(robot_index)
|
||||||
|
|
||||||
|
payout_invoice = add_invoice("robot", num_satoshis)
|
||||||
|
signed_payout_invoice = sign_message(
|
||||||
|
payout_invoice,
|
||||||
|
passphrase_path=f"tests/robots/{robot_index}/token",
|
||||||
|
private_key_path=f"tests/robots/{robot_index}/enc_priv_key",
|
||||||
|
)
|
||||||
|
body = {"action": "update_invoice", "invoice": signed_payout_invoice}
|
||||||
|
|
||||||
|
response = self.client.post(path + params, body, **headers)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def trade_to_submitted_invoice(
|
||||||
|
self, maker_form, take_amount=80, maker_index=1, taker_index=2
|
||||||
|
):
|
||||||
|
response_escrow_locked = self.trade_to_locked_escrow(
|
||||||
|
maker_form, take_amount, maker_index, taker_index
|
||||||
|
)
|
||||||
|
|
||||||
|
response_get = self.get_order(response_escrow_locked.json()["id"], maker_index)
|
||||||
|
|
||||||
|
response = self.submit_payout_invoice(
|
||||||
|
response_escrow_locked.json()["id"],
|
||||||
|
response_get.json()["trade_satoshis"],
|
||||||
|
maker_index,
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def test_trade_to_submitted_invoice(self):
|
||||||
|
"""
|
||||||
|
Tests a trade from order creation until escrow locked, before
|
||||||
|
invoice/address is submitted by buyer.
|
||||||
|
"""
|
||||||
|
maker_index = 1
|
||||||
|
taker_index = 2
|
||||||
|
maker_form = self.maker_form_buy_with_range
|
||||||
|
|
||||||
|
response = self.trade_to_submitted_invoice(
|
||||||
|
maker_form, 80, maker_index, taker_index
|
||||||
|
)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertResponse(response)
|
||||||
|
|
||||||
|
self.assertEqual(data["status_message"], Order.Status(Order.Status.CHA).label)
|
||||||
|
self.assertFalse(data["is_fiat_sent"])
|
||||||
|
|
||||||
|
# Cancel order to avoid leaving pending HTLCs after a successful test
|
||||||
|
self.cancel_order(data["id"])
|
||||||
|
Reference in New Issue
Block a user