Adds flake8 and fixes style issues

This commit is contained in:
Sergi Delgado Segura
2020-03-31 11:57:25 +02:00
parent 03c8ad8c87
commit b56123055d
21 changed files with 103 additions and 401 deletions

View File

@@ -8,6 +8,15 @@ We use [black](https://github.com/psf/black) as our base code formatter with a l
```bash ```bash
black --line-length=120 {source_file_or_directory} black --line-length=120 {source_file_or_directory}
``` ```
In additon, we use [flake8](https://flake8.pycqa.org/en/latest/) to detect style issues with the code:
```bash
flake8 --max-line-length=120 {source_file_or_directory}
```
Not all outputs from flake8 are mandatory. For instance, splitting **bullet points in docstrings (E501)** will cause issues when generating the docuementation, so we will leave that longer than the line lenght limit . Another example are **whitespaces before colons in inline fors (E203)**. `black` places them in that way, so we'll leave them like that.
On top of that, there are a few rules to also have in mind. On top of that, there are a few rules to also have in mind.
### Code Spacing ### Code Spacing

View File

@@ -3,7 +3,6 @@ import sys
import time import time
import json import json
import requests import requests
import binascii
from sys import argv from sys import argv
from uuid import uuid4 from uuid import uuid4
from coincurve import PublicKey from coincurve import PublicKey

View File

@@ -10,12 +10,12 @@ class Appointment:
The :class:`Appointment` contains the information regarding an appointment between a client and the Watchtower. The :class:`Appointment` contains the information regarding an appointment between a client and the Watchtower.
Args: Args:
locator (:mod:`str`): A 16-byte hex-encoded value used by the tower to detect channel breaches. It serves as a trigger locator (:mod:`str`): A 16-byte hex-encoded value used by the tower to detect channel breaches. It serves as a
for the tower to decrypt and broadcast the penalty transaction. trigger for the tower to decrypt and broadcast the penalty transaction.
start_time (:mod:`int`): The block height where the tower is hired to start watching for breaches. start_time (:mod:`int`): The block height where the tower is hired to start watching for breaches.
end_time (:mod:`int`): The block height where the tower will stop watching for breaches. end_time (:mod:`int`): The block height where the tower will stop watching for breaches.
to_self_delay (:mod:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``htlc`` that this appointment is to_self_delay (:mod:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``htlc`` that this appointment
covering. is covering.
encrypted_blob (:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): An ``EncryptedBlob`` object encrypted_blob (:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): An ``EncryptedBlob`` object
containing an encrypted penalty transaction. The tower will decrypt it and broadcast the penalty transaction containing an encrypted penalty transaction. The tower will decrypt it and broadcast the penalty transaction
upon seeing a breach on the blockchain. upon seeing a breach on the blockchain.

View File

@@ -159,8 +159,8 @@ class Cryptographer:
``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``. ``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``.
Args: Args:
encrypted_blob(:mod:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): an ``EncryptedBlob`` potentially encrypted_blob(:mod:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): an ``EncryptedBlob``
containing a penalty transaction. potentially containing a penalty transaction.
secret (:mod:`str`): a value to used to derive the decryption key. Should be the dispute txid. secret (:mod:`str`): a value to used to derive the decryption key. Should be the dispute txid.
Returns: Returns:

View File

@@ -1,4 +1,6 @@
pytest pytest
black black
flake8
responses responses
bitcoind_mock===0.0.4 bitcoind_mock===0.0.4

View File

@@ -1,5 +1,4 @@
import os import os
from teos.utils.auth_proxy import AuthServiceProxy
HOST = "localhost" HOST = "localhost"
PORT = 9814 PORT = 9814

View File

@@ -1,6 +1,6 @@
import os import os
import logging import logging
from math import ceil, floor from math import ceil
from flask import Flask, request, abort, jsonify from flask import Flask, request, abort, jsonify
import teos.errors as errors import teos.errors as errors

View File

@@ -94,8 +94,9 @@ class Builder:
@staticmethod @staticmethod
def update_states(watcher, missed_blocks_watcher, missed_blocks_responder): def update_states(watcher, missed_blocks_watcher, missed_blocks_responder):
""" """
Updates the states of both the :mod:`Watcher <teos.watcher.Watcher>` and the :mod:`Responder <teos.responder.Responder>`. Updates the states of both the :mod:`Watcher <teos.watcher.Watcher>` and the
If both have pending blocks to process they need to be updates at the same time, block by block. :mod:`Responder <teos.responder.Responder>`. If both have pending blocks to process they need to be updates at
the same time, block by block.
If only one instance has to be updated, ``populate_block_queue`` should be used. If only one instance has to be updated, ``populate_block_queue`` should be used.

View File

@@ -1,7 +1,7 @@
from teos import LOG_PREFIX from teos import LOG_PREFIX
from teos.rpc_errors import *
from common.logger import Logger from common.logger import Logger
from teos.tools import bitcoin_cli from teos.tools import bitcoin_cli
import teos.rpc_errors as rpc_errors
from teos.utils.auth_proxy import JSONRPCException from teos.utils.auth_proxy import JSONRPCException
from teos.errors import UNKNOWN_JSON_RPC_EXCEPTION, RPC_TX_REORGED_AFTER_BROADCAST from teos.errors import UNKNOWN_JSON_RPC_EXCEPTION, RPC_TX_REORGED_AFTER_BROADCAST
@@ -81,17 +81,17 @@ class Carrier:
except JSONRPCException as e: except JSONRPCException as e:
errno = e.error.get("code") errno = e.error.get("code")
# Since we're pushing a raw transaction to the network we can face several rejections # Since we're pushing a raw transaction to the network we can face several rejections
if errno == RPC_VERIFY_REJECTED: if errno == rpc_errors.RPC_VERIFY_REJECTED:
# DISCUSS: 37-transaction-rejection # DISCUSS: 37-transaction-rejection
receipt = Receipt(delivered=False, reason=RPC_VERIFY_REJECTED) receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_REJECTED)
logger.error("Transaction couldn't be broadcast", error=e.error) logger.error("Transaction couldn't be broadcast", error=e.error)
elif errno == RPC_VERIFY_ERROR: elif errno == rpc_errors.RPC_VERIFY_ERROR:
# DISCUSS: 37-transaction-rejection # DISCUSS: 37-transaction-rejection
receipt = Receipt(delivered=False, reason=RPC_VERIFY_ERROR) receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_ERROR)
logger.error("Transaction couldn't be broadcast", error=e.error) logger.error("Transaction couldn't be broadcast", error=e.error)
elif errno == RPC_VERIFY_ALREADY_IN_CHAIN: elif errno == rpc_errors.RPC_VERIFY_ALREADY_IN_CHAIN:
logger.info("Transaction is already in the blockchain. Getting confirmation count", txid=txid) logger.info("Transaction is already in the blockchain. Getting confirmation count", txid=txid)
# If the transaction is already in the chain, we get the number of confirmations and watch the tracker # If the transaction is already in the chain, we get the number of confirmations and watch the tracker
@@ -100,7 +100,9 @@ class Carrier:
if tx_info is not None: if tx_info is not None:
confirmations = int(tx_info.get("confirmations")) confirmations = int(tx_info.get("confirmations"))
receipt = Receipt(delivered=True, confirmations=confirmations, reason=RPC_VERIFY_ALREADY_IN_CHAIN) receipt = Receipt(
delivered=True, confirmations=confirmations, reason=rpc_errors.RPC_VERIFY_ALREADY_IN_CHAIN
)
else: else:
# There's a really unlikely edge case where a transaction can be reorged between receiving the # There's a really unlikely edge case where a transaction can be reorged between receiving the
@@ -108,12 +110,12 @@ class Carrier:
# mempool, which again is really unlikely. # mempool, which again is really unlikely.
receipt = Receipt(delivered=False, reason=RPC_TX_REORGED_AFTER_BROADCAST) receipt = Receipt(delivered=False, reason=RPC_TX_REORGED_AFTER_BROADCAST)
elif errno == RPC_DESERIALIZATION_ERROR: elif errno == rpc_errors.RPC_DESERIALIZATION_ERROR:
# Adding this here just for completeness. We should never end up here. The Carrier only sends txs # Adding this here just for completeness. We should never end up here. The Carrier only sends txs
# handed by the Responder, who receives them from the Watcher, who checks that the tx can be properly # handed by the Responder, who receives them from the Watcher, who checks that the tx can be properly
# deserialized # deserialized
logger.info("Transaction cannot be deserialized".format(txid)) logger.info("Transaction cannot be deserialized".format(txid))
receipt = Receipt(delivered=False, reason=RPC_DESERIALIZATION_ERROR) receipt = Receipt(delivered=False, reason=rpc_errors.RPC_DESERIALIZATION_ERROR)
else: else:
# If something else happens (unlikely but possible) log it so we can treat it in future releases # If something else happens (unlikely but possible) log it so we can treat it in future releases
@@ -145,7 +147,7 @@ class Carrier:
# While it's quite unlikely, the transaction that was already in the blockchain could have been # While it's quite unlikely, the transaction that was already in the blockchain could have been
# reorged while we were querying bitcoind to get the confirmation count. In such a case we just # reorged while we were querying bitcoind to get the confirmation count. In such a case we just
# restart the tracker # restart the tracker
if e.error.get("code") == RPC_INVALID_ADDRESS_OR_KEY: if e.error.get("code") == rpc_errors.RPC_INVALID_ADDRESS_OR_KEY:
logger.info("Transaction not found in mempool nor blockchain", txid=txid) logger.info("Transaction not found in mempool nor blockchain", txid=txid)
else: else:

View File

@@ -123,8 +123,9 @@ class Cleaner:
""" """
Deletes a completed appointment from memory (:obj:`Watcher <teos.watcher.Watcher>`) and disk. Deletes a completed appointment from memory (:obj:`Watcher <teos.watcher.Watcher>`) and disk.
Currently, an appointment is only completed if it cannot make it to the (:obj:`Responder <teos.responder.Responder>`), Currently, an appointment is only completed if it cannot make it to the
otherwise, it will be flagged as triggered and removed once the tracker is completed. (:obj:`Responder <teos.responder.Responder>`), otherwise, it will be flagged as triggered and removed once the
tracker is completed.
Args: Args:
completed_appointments (:obj:`list`): a list of appointments to be deleted. completed_appointments (:obj:`list`): a list of appointments to be deleted.

View File

@@ -172,6 +172,7 @@ class DBManager:
def load_watcher_appointments(self, include_triggered=False): def load_watcher_appointments(self, include_triggered=False):
""" """
Loads all the appointments from the database (all entries with the ``WATCHER_PREFIX`` prefix). Loads all the appointments from the database (all entries with the ``WATCHER_PREFIX`` prefix).
Args: Args:
include_triggered (:obj:`bool`): Whether to include the appointments flagged as triggered or not. ``False`` include_triggered (:obj:`bool`): Whether to include the appointments flagged as triggered or not. ``False``
by default. by default.
@@ -289,7 +290,7 @@ class DBManager:
current_locator_map = self.load_locator_map(locator) current_locator_map = self.load_locator_map(locator)
if set(locator_map).issubset(current_locator_map) and len(locator_map) is not 0: if set(locator_map).issubset(current_locator_map) and len(locator_map) != 0:
key = (LOCATOR_MAP_PREFIX + locator).encode("utf-8") key = (LOCATOR_MAP_PREFIX + locator).encode("utf-8")
self.db.put(key, json.dumps(locator_map).encode("utf-8")) self.db.put(key, json.dumps(locator_map).encode("utf-8"))

View File

@@ -3,7 +3,8 @@ def show_usage():
"USAGE: " "USAGE: "
"\n\tpython teosd.py [global options]" "\n\tpython teosd.py [global options]"
"\n\nGLOBAL OPTIONS:" "\n\nGLOBAL OPTIONS:"
"\n\t--btcnetwork \t\tNetwork bitcoind is connected to. Either mainnet, testnet or regtest. Defaults to 'mainnet' (modifiable in conf file)." "\n\t--btcnetwork \t\tNetwork bitcoind is connected to. Either mainnet, testnet or regtest. Defaults to "
"'mainnet' (modifiable in conf file)."
"\n\t--btcrpcuser \t\tbitcoind rpcuser. Defaults to 'user' (modifiable in conf file)." "\n\t--btcrpcuser \t\tbitcoind rpcuser. Defaults to 'user' (modifiable in conf file)."
"\n\t--btcrpcpassword \tbitcoind rpcpassword. Defaults to 'passwd' (modifiable in conf file)." "\n\t--btcrpcpassword \tbitcoind rpcpassword. Defaults to 'passwd' (modifiable in conf file)."
"\n\t--btcrpcconnect \tbitcoind rpcconnect. Defaults to 'localhost' (modifiable in conf file)." "\n\t--btcrpcconnect \tbitcoind rpcconnect. Defaults to 'localhost' (modifiable in conf file)."

View File

@@ -303,7 +303,7 @@ class Responder:
# Clear the receipts issued in this block # Clear the receipts issued in this block
self.carrier.issued_receipts = {} self.carrier.issued_receipts = {}
if len(self.trackers) is 0: if len(self.trackers) != 0:
logger.info("No more pending trackers") logger.info("No more pending trackers")
# Register the last processed block for the responder # Register the last processed block for the responder

View File

@@ -3,16 +3,16 @@
# General application defined errors # General application defined errors
RPC_MISC_ERROR = -1 # std::exception thrown in command handling RPC_MISC_ERROR = -1 # std::exception thrown in command handling
RPC_TYPE_ERROR = -3 # Unexpected type was passed as parameter RPC_TYPE_ERROR = -3 # Unexpected type was passed as parameter
RPC_INVALID_ADDRESS_OR_KEY = -5 # Invalid address or key RPC_INVALID_ADDRESS_OR_KEY = -5 # Invalid address or key
RPC_OUT_OF_MEMORY = -7 # Ran out of memory during operation RPC_OUT_OF_MEMORY = -7 # Ran out of memory during operation
RPC_INVALID_PARAMETER = -8 # Invalid missing or duplicate parameter RPC_INVALID_PARAMETER = -8 # Invalid missing or duplicate parameter
RPC_DATABASE_ERROR = -20 # Database error RPC_DATABASE_ERROR = -20 # Database error
RPC_DESERIALIZATION_ERROR = -22 # Error parsing or validating structure in raw format RPC_DESERIALIZATION_ERROR = -22 # Error parsing or validating structure in raw format
RPC_VERIFY_ERROR = -25 # General error during transaction or block submission RPC_VERIFY_ERROR = -25 # General error during transaction or block submission
RPC_VERIFY_REJECTED = -26 # Transaction or block was rejected by network rules RPC_VERIFY_REJECTED = -26 # Transaction or block was rejected by network rules
RPC_VERIFY_ALREADY_IN_CHAIN = -27 # Transaction already in chain RPC_VERIFY_ALREADY_IN_CHAIN = -27 # Transaction already in chain
RPC_IN_WARMUP = -28 # Client still warming up RPC_IN_WARMUP = -28 # Client still warming up
RPC_METHOD_DEPRECATED = -32 # RPC method is deprecated RPC_METHOD_DEPRECATED = -32 # RPC method is deprecated
# Aliases for backward compatibility # Aliases for backward compatibility
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR
@@ -20,25 +20,23 @@ RPC_TRANSACTION_REJECTED = RPC_VERIFY_REJECTED
RPC_TRANSACTION_ALREADY_IN_CHAIN = RPC_VERIFY_ALREADY_IN_CHAIN RPC_TRANSACTION_ALREADY_IN_CHAIN = RPC_VERIFY_ALREADY_IN_CHAIN
# P2P client errors # P2P client errors
RPC_CLIENT_NOT_CONNECTED = -9 # Bitcoin is not connected RPC_CLIENT_NOT_CONNECTED = -9 # Bitcoin is not connected
RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10 # Still downloading initial blocks RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10 # Still downloading initial blocks
RPC_CLIENT_NODE_ALREADY_ADDED = -23 # Node is already added RPC_CLIENT_NODE_ALREADY_ADDED = -23 # Node is already added
RPC_CLIENT_NODE_NOT_ADDED = -24 # Node has not been added before RPC_CLIENT_NODE_NOT_ADDED = -24 # Node has not been added before
RPC_CLIENT_NODE_NOT_CONNECTED = -29 # Node to disconnect not found in connected nodes RPC_CLIENT_NODE_NOT_CONNECTED = -29 # Node to disconnect not found in connected nodes
RPC_CLIENT_INVALID_IP_OR_SUBNET = -30 # Invalid IP/Subnet RPC_CLIENT_INVALID_IP_OR_SUBNET = -30 # Invalid IP/Subnet
RPC_CLIENT_P2P_DISABLED = -31 # No valid connection manager instance found RPC_CLIENT_P2P_DISABLED = -31 # No valid connection manager instance found
# Wallet errors # Wallet errors
RPC_WALLET_ERROR = -4 # Unspecified problem with wallet (key not found etc.) RPC_WALLET_ERROR = -4 # Unspecified problem with wallet (key not found etc.)
RPC_WALLET_INSUFFICIENT_FUNDS = -6 # Not enough funds in wallet or account RPC_WALLET_INSUFFICIENT_FUNDS = -6 # Not enough funds in wallet or account
RPC_WALLET_INVALID_LABEL_NAME = -11 # Invalid label name RPC_WALLET_INVALID_LABEL_NAME = -11 # Invalid label name
RPC_WALLET_KEYPOOL_RAN_OUT = -12 # Keypool ran out call keypoolrefill first RPC_WALLET_KEYPOOL_RAN_OUT = -12 # Keypool ran out call keypoolrefill first
RPC_WALLET_UNLOCK_NEEDED = -13 # Enter the wallet passphrase with walletpassphrase first RPC_WALLET_UNLOCK_NEEDED = -13 # Enter the wallet passphrase with walletpassphrase first
RPC_WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect RPC_WALLET_PASSPHRASE_INCORRECT = -14 # The wallet passphrase entered was incorrect
RPC_WALLET_WRONG_ENC_STATE = ( RPC_WALLET_WRONG_ENC_STATE = -15 # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.)
-15 RPC_WALLET_ENCRYPTION_FAILED = -16 # Failed to encrypt the wallet
) # Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) RPC_WALLET_ALREADY_UNLOCKED = -17 # Wallet is already unlocked
RPC_WALLET_ENCRYPTION_FAILED = -16 # Failed to encrypt the wallet RPC_WALLET_NOT_FOUND = -18 # Invalid wallet specified
RPC_WALLET_ALREADY_UNLOCKED = -17 # Wallet is already unlocked RPC_WALLET_NOT_SPECIFIED = -19 # No wallet specified (error when there are multiple wallets loaded)
RPC_WALLET_NOT_FOUND = -18 # Invalid wallet specified
RPC_WALLET_NOT_SPECIFIED = -19 # No wallet specified (error when there are multiple wallets loaded)

View File

@@ -21,8 +21,8 @@ class Watcher:
The :class:`Watcher` keeps track of the accepted appointments in ``appointments`` and, for new received block, The :class:`Watcher` keeps track of the accepted appointments in ``appointments`` and, for new received block,
checks if any breach has happened by comparing the txids with the appointment locators. If a breach is seen, the checks if any breach has happened by comparing the txids with the appointment locators. If a breach is seen, the
:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>` of the corresponding appointment is decrypted and the data :obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>` of the corresponding appointment is decrypted and the
is passed to the :obj:`Responder <teos.responder.Responder>`. data is passed to the :obj:`Responder <teos.responder.Responder>`.
If an appointment reaches its end with no breach, the data is simply deleted. If an appointment reaches its end with no breach, the data is simply deleted.
@@ -225,7 +225,7 @@ class Watcher:
appointments_to_delete, self.appointments, self.locator_uuid_map, self.db_manager appointments_to_delete, self.appointments, self.locator_uuid_map, self.db_manager
) )
if len(self.appointments) is 0: if len(self.appointments) != 0:
logger.info("No more pending appointments") logger.info("No more pending appointments")
# Register the last processed block for the watcher # Register the last processed block for the watcher

View File

@@ -102,12 +102,12 @@ def test_setup_logging():
f_log_suffix = "_file_log" f_log_suffix = "_file_log"
c_log_suffix = "_console_log" c_log_suffix = "_console_log"
assert len(logging.getLogger(prefix + f_log_suffix).handlers) is 0 assert len(logging.getLogger(prefix + f_log_suffix).handlers) == 0
assert len(logging.getLogger(prefix + c_log_suffix).handlers) is 0 assert len(logging.getLogger(prefix + c_log_suffix).handlers) == 0
setup_logging(log_file, prefix) setup_logging(log_file, prefix)
assert len(logging.getLogger(prefix + f_log_suffix).handlers) is 1 assert len(logging.getLogger(prefix + f_log_suffix).handlers) == 1
assert len(logging.getLogger(prefix + c_log_suffix).handlers) is 1 assert len(logging.getLogger(prefix + c_log_suffix).handlers) == 1
os.remove(log_file) os.remove(log_file)

View File

@@ -1,302 +0,0 @@
import json
import binascii
from time import sleep
from riemann.tx import Tx
from cli import teos_cli, DATA_DIR, DEFAULT_CONF, CONF_FILE_NAME
import common.cryptographer
from common.blob import Blob
from common.logger import Logger
from common.tools import compute_locator
from common.appointment import Appointment
from common.cryptographer import Cryptographer
from teos.utils.auth_proxy import JSONRPCException
from test.teos.e2e.conftest import (
END_TIME_DELTA,
build_appointment_data,
get_random_value_hex,
create_penalty_tx,
run_teosd,
get_config,
)
cli_config = get_config(DATA_DIR, CONF_FILE_NAME, DEFAULT_CONF)
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix="")
teos_base_endpoint = "http://{}:{}".format(cli_config.get("TEOS_SERVER"), cli_config.get("TEOS_PORT"))
teos_add_appointment_endpoint = "{}/add_appointment".format(teos_base_endpoint)
teos_get_appointment_endpoint = "{}/get_appointment".format(teos_base_endpoint)
# Run teosd
teosd_process = run_teosd()
def broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, addr):
# Broadcast the commitment transaction and mine a block
bitcoin_cli.sendrawtransaction(commitment_tx)
bitcoin_cli.generatetoaddress(1, addr)
def get_appointment_info(locator):
# Check that the justice has been triggered (the appointment has moved from Watcher to Responder)
sleep(1) # Let's add a bit of delay so the state can be updated
return teos_cli.get_appointment(locator, teos_base_endpoint)
def test_appointment_life_cycle(bitcoin_cli, create_txs):
commitment_tx, penalty_tx = create_txs
commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")
appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx)
locator = compute_locator(commitment_tx_id)
assert teos_cli.add_appointment([json.dumps(appointment_data)], teos_base_endpoint, cli_config) is True
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "being_watched"
new_addr = bitcoin_cli.getnewaddress()
broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "dispute_responded"
# It can be also checked by ensuring that the penalty transaction made it to the network
penalty_tx_id = bitcoin_cli.decoderawtransaction(penalty_tx).get("txid")
try:
bitcoin_cli.getrawtransaction(penalty_tx_id)
assert True
except JSONRPCException:
# If the transaction if not found.
assert False
# Now let's mine some blocks so the appointment reaches its end.
# Since we are running all the nodes remotely data may take more time than normal, and some confirmations may be
# missed, so we generate more than enough confirmations and add some delays.
for _ in range(int(1.5 * END_TIME_DELTA)):
sleep(1)
bitcoin_cli.generatetoaddress(1, new_addr)
appointment_info = get_appointment_info(locator)
assert appointment_info[0].get("status") == "not_found"
def test_appointment_malformed_penalty(bitcoin_cli, create_txs):
# Lets start by creating two valid transaction
commitment_tx, penalty_tx = create_txs
# Now we can modify the penalty so it is invalid when broadcast
mod_penalty_tx = Tx.from_hex(penalty_tx)
tx_in = mod_penalty_tx.tx_ins[0].copy(redeem_script=b"")
mod_penalty_tx = mod_penalty_tx.copy(tx_ins=[tx_in])
commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")
appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, mod_penalty_tx.hex())
locator = compute_locator(commitment_tx_id)
assert teos_cli.add_appointment([json.dumps(appointment_data)], teos_base_endpoint, cli_config) is True
# Broadcast the commitment transaction and mine a block
new_addr = bitcoin_cli.getnewaddress()
broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)
# The appointment should have been removed since the penalty_tx was malformed.
sleep(1)
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "not_found"
def test_appointment_wrong_key(bitcoin_cli, create_txs):
# This tests an appointment encrypted with a key that has not been derived from the same source as the locator.
# Therefore the tower won't be able to decrypt the blob once the appointment is triggered.
commitment_tx, penalty_tx = create_txs
# The appointment data is built using a random 32-byte value.
appointment_data = build_appointment_data(bitcoin_cli, get_random_value_hex(32), penalty_tx)
# We can't use teos_cli.add_appointment here since it computes the locator internally, so let's do it manually.
# We will encrypt the blob using the random value and derive the locator from the commitment tx.
appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid"))
appointment_data["encrypted_blob"] = Cryptographer.encrypt(Blob(penalty_tx), get_random_value_hex(32))
appointment = Appointment.from_dict(appointment_data)
teos_pk, cli_sk, cli_pk_der = teos_cli.load_keys(
cli_config.get("TEOS_PUBLIC_KEY"), cli_config.get("CLI_PRIVATE_KEY"), cli_config.get("CLI_PUBLIC_KEY")
)
hex_pk_der = binascii.hexlify(cli_pk_der)
signature = Cryptographer.sign(appointment.serialize(), cli_sk)
data = {"appointment": appointment.to_dict(), "signature": signature, "public_key": hex_pk_der.decode("utf-8")}
# Send appointment to the server.
response = teos_cli.post_appointment(data, teos_base_endpoint)
response_json = teos_cli.process_post_appointment_response(response)
# Check that the server has accepted the appointment
signature = response_json.get("signature")
assert signature is not None
rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
assert Cryptographer.verify_rpk(teos_pk, rpk) is True
assert response_json.get("locator") == appointment.locator
# Trigger the appointment
new_addr = bitcoin_cli.getnewaddress()
broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)
# The appointment should have been removed since the decryption failed.
sleep(1)
appointment_info = get_appointment_info(appointment.locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "not_found"
def test_two_identical_appointments(bitcoin_cli, create_txs):
# Tests sending two identical appointments to the tower.
# At the moment there are no checks for identical appointments, so both will be accepted, decrypted and kept until
# the end.
# TODO: 34-exact-duplicate-appointment
# This tests sending an appointment with two valid transaction with the same locator.
commitment_tx, penalty_tx = create_txs
commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")
appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx)
locator = compute_locator(commitment_tx_id)
# Send the appointment twice
assert teos_cli.add_appointment([json.dumps(appointment_data)], teos_base_endpoint, cli_config) is True
assert teos_cli.add_appointment([json.dumps(appointment_data)], teos_base_endpoint, cli_config) is True
# Broadcast the commitment transaction and mine a block
new_addr = bitcoin_cli.getnewaddress()
broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)
# The first appointment should have made it to the Responder, and the second one should have been dropped for
# double-spending
sleep(1)
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 2
for info in appointment_info:
assert info.get("status") == "dispute_responded"
assert info.get("penalty_rawtx") == penalty_tx
def test_two_appointment_same_locator_different_penalty(bitcoin_cli, create_txs):
# This tests sending an appointment with two valid transaction with the same locator.
commitment_tx, penalty_tx1 = create_txs
commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")
# We need to create a second penalty spending from the same commitment
decoded_commitment_tx = bitcoin_cli.decoderawtransaction(commitment_tx)
new_addr = bitcoin_cli.getnewaddress()
penalty_tx2 = create_penalty_tx(bitcoin_cli, decoded_commitment_tx, new_addr)
appointment1_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx1)
appointment2_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx2)
locator = compute_locator(commitment_tx_id)
assert teos_cli.add_appointment([json.dumps(appointment1_data)], teos_base_endpoint, cli_config) is True
assert teos_cli.add_appointment([json.dumps(appointment2_data)], teos_base_endpoint, cli_config) is True
# Broadcast the commitment transaction and mine a block
new_addr = bitcoin_cli.getnewaddress()
broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)
# The first appointment should have made it to the Responder, and the second one should have been dropped for
# double-spending
sleep(1)
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "dispute_responded"
assert appointment_info[0].get("penalty_rawtx") == penalty_tx1
def test_appointment_shutdown_teos_trigger_back_online(create_txs, bitcoin_cli):
global teosd_process
teos_pid = teosd_process.pid
commitment_tx, penalty_tx = create_txs
commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")
appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx)
locator = compute_locator(commitment_tx_id)
assert teos_cli.add_appointment([json.dumps(appointment_data)], teos_base_endpoint, cli_config) is True
# Restart teos
teosd_process.terminate()
teosd_process = run_teosd()
assert teos_pid != teosd_process.pid
# Check that the appointment is still in the Watcher
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "being_watched"
# Trigger appointment after restart
new_addr = bitcoin_cli.getnewaddress()
broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)
# The appointment should have been moved to the Responder
sleep(1)
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "dispute_responded"
def test_appointment_shutdown_teos_trigger_while_offline(create_txs, bitcoin_cli):
global teosd_process
teos_pid = teosd_process.pid
commitment_tx, penalty_tx = create_txs
commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")
appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx)
locator = compute_locator(commitment_tx_id)
assert teos_cli.add_appointment([json.dumps(appointment_data)], teos_base_endpoint, cli_config) is True
# Check that the appointment is still in the Watcher
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "being_watched"
# Shutdown and trigger
teosd_process.terminate()
new_addr = bitcoin_cli.getnewaddress()
broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)
# Restart
teosd_process = run_teosd()
assert teos_pid != teosd_process.pid
# The appointment should have been moved to the Responder
sleep(1)
appointment_info = get_appointment_info(locator)
assert appointment_info is not None
assert len(appointment_info) == 1
assert appointment_info[0].get("status") == "dispute_responded"
teosd_process.terminate()

View File

@@ -9,16 +9,9 @@ from teos.watcher import Watcher
from teos.inspector import Inspector from teos.inspector import Inspector
from teos.db_manager import DBManager from teos.db_manager import DBManager
from teos.gatekeeper import Gatekeeper from teos.gatekeeper import Gatekeeper
from teos.chain_monitor import ChainMonitor
from teos.responder import Responder, TransactionTracker from teos.responder import Responder, TransactionTracker
from test.teos.unit.conftest import ( from test.teos.unit.conftest import get_random_value_hex, generate_dummy_appointment, generate_keypair, get_config
get_random_value_hex,
generate_dummy_appointment,
generate_keypair,
get_config,
bitcoind_feed_params,
)
from common.cryptographer import Cryptographer, hash_160 from common.cryptographer import Cryptographer, hash_160
from common.constants import ( from common.constants import (

View File

@@ -1,6 +1,4 @@
import pytest from test.teos.unit.conftest import get_random_value_hex, generate_block, generate_blocks, fork
from test.teos.unit.conftest import get_random_value_hex, generate_block, generate_blocks, fork, bitcoind_connect_params
hex_tx = ( hex_tx = (

View File

@@ -5,7 +5,7 @@ from threading import Thread, Event, Condition
from teos.chain_monitor import ChainMonitor from teos.chain_monitor import ChainMonitor
from test.teos.unit.conftest import get_random_value_hex, generate_block, bitcoind_connect_params, bitcoind_feed_params from test.teos.unit.conftest import get_random_value_hex, generate_block, bitcoind_feed_params
def test_init(run_bitcoind, block_processor): def test_init(run_bitcoind, block_processor):
@@ -64,8 +64,8 @@ def test_update_state(block_processor):
def test_monitor_chain_polling(db_manager, block_processor): def test_monitor_chain_polling(db_manager, block_processor):
# Try polling with the Watcher # Try polling with the Watcher
wq = Queue() watcher_queue = Queue()
chain_monitor = ChainMonitor(Queue(), Queue(), block_processor, bitcoind_feed_params) chain_monitor = ChainMonitor(watcher_queue, Queue(), block_processor, bitcoind_feed_params)
chain_monitor.best_tip = block_processor.get_best_block_hash() chain_monitor.best_tip = block_processor.get_best_block_hash()
chain_monitor.polling_delta = 0.1 chain_monitor.polling_delta = 0.1

View File

@@ -1,7 +1,7 @@
import pytest import pytest
from binascii import unhexlify from binascii import unhexlify
from teos.errors import * import teos.errors as errors
from teos import LOG_PREFIX from teos import LOG_PREFIX
from teos.block_processor import BlockProcessor from teos.block_processor import BlockProcessor
from teos.inspector import Inspector, InspectionFailed from teos.inspector import Inspector, InspectionFailed
@@ -11,7 +11,7 @@ from common.logger import Logger
from common.appointment import Appointment from common.appointment import Appointment
from common.constants import LOCATOR_LEN_BYTES, LOCATOR_LEN_HEX from common.constants import LOCATOR_LEN_BYTES, LOCATOR_LEN_HEX
from test.teos.unit.conftest import get_random_value_hex, generate_keypair, bitcoind_connect_params, get_config from test.teos.unit.conftest import get_random_value_hex, bitcoind_connect_params, get_config
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX) common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
@@ -53,7 +53,7 @@ def test_check_locator():
inspector.check_locator(locator) inspector.check_locator(locator)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_SIZE assert e.erno == errors.APPOINTMENT_WRONG_FIELD_SIZE
raise e raise e
# Wrong size (too small) # Wrong size (too small)
@@ -63,7 +63,7 @@ def test_check_locator():
inspector.check_locator(locator) inspector.check_locator(locator)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_SIZE assert e.erno == errors.APPOINTMENT_WRONG_FIELD_SIZE
raise e raise e
# Empty # Empty
@@ -73,7 +73,7 @@ def test_check_locator():
inspector.check_locator(locator) inspector.check_locator(locator)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_EMPTY_FIELD assert e.erno == errors.APPOINTMENT_EMPTY_FIELD
raise e raise e
# Wrong type (several types tested, it should do for anything that is not a string) # Wrong type (several types tested, it should do for anything that is not a string)
@@ -85,7 +85,7 @@ def test_check_locator():
inspector.check_locator(locator) inspector.check_locator(locator)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_TYPE assert e.erno == errors.APPOINTMENT_WRONG_FIELD_TYPE
raise e raise e
# Wrong format (no hex) # Wrong format (no hex)
@@ -96,7 +96,7 @@ def test_check_locator():
inspector.check_locator(locator) inspector.check_locator(locator)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_FORMAT assert e.erno == errors.APPOINTMENT_WRONG_FIELD_FORMAT
raise e raise e
@@ -116,7 +116,7 @@ def test_check_start_time():
inspector.check_start_time(start_time, current_time) inspector.check_start_time(start_time, current_time)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_FIELD_TOO_SMALL assert e.erno == errors.APPOINTMENT_FIELD_TOO_SMALL
raise e raise e
# Empty field # Empty field
@@ -126,7 +126,7 @@ def test_check_start_time():
inspector.check_start_time(start_time, current_time) inspector.check_start_time(start_time, current_time)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_EMPTY_FIELD assert e.erno == errors.APPOINTMENT_EMPTY_FIELD
raise e raise e
# Wrong data type # Wrong data type
@@ -137,7 +137,7 @@ def test_check_start_time():
inspector.check_start_time(start_time, current_time) inspector.check_start_time(start_time, current_time)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_TYPE assert e.erno == errors.APPOINTMENT_WRONG_FIELD_TYPE
raise e raise e
@@ -158,7 +158,7 @@ def test_check_end_time():
inspector.check_end_time(end_time, start_time, current_time) inspector.check_end_time(end_time, start_time, current_time)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_FIELD_TOO_SMALL assert e.erno == errors.APPOINTMENT_FIELD_TOO_SMALL
raise e raise e
# End time too small (either same height as current block or in the past) # End time too small (either same height as current block or in the past)
@@ -170,7 +170,7 @@ def test_check_end_time():
inspector.check_end_time(end_time, start_time, current_time) inspector.check_end_time(end_time, start_time, current_time)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_FIELD_TOO_SMALL assert e.erno == errors.APPOINTMENT_FIELD_TOO_SMALL
raise e raise e
# Empty field # Empty field
@@ -180,7 +180,7 @@ def test_check_end_time():
inspector.check_end_time(end_time, start_time, current_time) inspector.check_end_time(end_time, start_time, current_time)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_EMPTY_FIELD assert e.erno == errors.APPOINTMENT_EMPTY_FIELD
raise e raise e
# Wrong data type # Wrong data type
@@ -191,7 +191,7 @@ def test_check_end_time():
inspector.check_end_time(end_time, start_time, current_time) inspector.check_end_time(end_time, start_time, current_time)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_TYPE assert e.erno == errors.APPOINTMENT_WRONG_FIELD_TYPE
raise e raise e
@@ -209,7 +209,7 @@ def test_check_to_self_delay():
inspector.check_to_self_delay(to_self_delay) inspector.check_to_self_delay(to_self_delay)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_FIELD_TOO_SMALL assert e.erno == errors.APPOINTMENT_FIELD_TOO_SMALL
raise e raise e
# Empty field # Empty field
@@ -219,7 +219,7 @@ def test_check_to_self_delay():
inspector.check_to_self_delay(to_self_delay) inspector.check_to_self_delay(to_self_delay)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_EMPTY_FIELD assert e.erno == errors.APPOINTMENT_EMPTY_FIELD
raise e raise e
# Wrong data type # Wrong data type
@@ -230,7 +230,7 @@ def test_check_to_self_delay():
inspector.check_to_self_delay(to_self_delay) inspector.check_to_self_delay(to_self_delay)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_TYPE assert e.erno == errors.APPOINTMENT_WRONG_FIELD_TYPE
raise e raise e
@@ -251,7 +251,7 @@ def test_check_blob():
inspector.check_blob(encrypted_blob) inspector.check_blob(encrypted_blob)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_TYPE assert e.erno == errors.APPOINTMENT_WRONG_FIELD_TYPE
raise e raise e
# Empty field # Empty field
@@ -261,7 +261,7 @@ def test_check_blob():
inspector.check_blob(encrypted_blob) inspector.check_blob(encrypted_blob)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_EMPTY_FIELD assert e.erno == errors.APPOINTMENT_EMPTY_FIELD
raise e raise e
# Wrong format (no hex) # Wrong format (no hex)
@@ -272,7 +272,7 @@ def test_check_blob():
inspector.check_blob(encrypted_blob) inspector.check_blob(encrypted_blob)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_WRONG_FIELD_FORMAT assert e.erno == errors.APPOINTMENT_WRONG_FIELD_FORMAT
raise e raise e
@@ -313,7 +313,7 @@ def test_inspect_wrong(run_bitcoind):
inspector.inspect(data) inspector.inspect(data)
except InspectionFailed as e: except InspectionFailed as e:
print(data) print(data)
assert e.erno == APPOINTMENT_WRONG_FIELD assert e.erno == errors.APPOINTMENT_WRONG_FIELD
raise e raise e
# None data # None data
@@ -321,5 +321,5 @@ def test_inspect_wrong(run_bitcoind):
try: try:
inspector.inspect(None) inspector.inspect(None)
except InspectionFailed as e: except InspectionFailed as e:
assert e.erno == APPOINTMENT_EMPTY_FIELD assert e.erno == errors.APPOINTMENT_EMPTY_FIELD
raise e raise e