Skip to content

Commit

Permalink
🔊 Add some logs to catch informations when error occurred && cleaning
Browse files Browse the repository at this point in the history
  • Loading branch information
Quentin Burg committed Dec 12, 2023
1 parent 31785c0 commit c1bde8c
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 44 deletions.
3 changes: 2 additions & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
jupyter
pytest
black
black
pylance
10 changes: 8 additions & 2 deletions src/config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from dotenv import load_dotenv
import os
import subprocess
import logging


load_dotenv()
load_dotenv(override=True)

TEZOS_RPC = os.getenv("TEZOS_RPC")
SECRET_KEY_CMD = os.getenv("SECRET_KEY_CMD")
LEVEL = os.getenv("LEVEL", logging.INFO)

if SECRET_KEY_CMD is not None:
command = SECRET_KEY_CMD.split()
Expand All @@ -18,3 +19,8 @@

assert TEZOS_RPC is not None, "Please specify a TEZOS_RPC"
assert SECRET_KEY is not None and len(SECRET_KEY) > 0, "Could not read secret key"


# -- LOGGING --

logging.basicConfig(level=LEVEL, format="%(levelname)s: %(message)s")
3 changes: 1 addition & 2 deletions src/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from pydantic import UUID4
from sqlalchemy.orm import Session

from src.utils import ContractNotFound, CreditNotFound
from src.utils import EntrypointNotFound, UserNotFound
from .utils import ContractNotFound, CreditNotFound, EntrypointNotFound, UserNotFound
from . import models, schemas
from sqlalchemy.exc import NoResultFound

Expand Down
5 changes: 3 additions & 2 deletions src/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

from src.utils import ConfigurationError
from .utils import ConfigurationError
from .config import logging


def config(filename="sql/database.ini", section="postgresql"):
Expand All @@ -30,7 +31,7 @@ def config(filename="sql/database.ini", section="postgresql"):
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
except Exception as e:
print(e)
logging.error(f"Error occurred on database configuration : {e}")
raise ConfigurationError("Cannot connect to database.")


Expand Down
10 changes: 10 additions & 0 deletions src/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
class User(Base):
__tablename__ = "users"

def __repr__(self):
return "User(id='{}', name='{}', address='{}')".format(
self.id, self.name, self.address
)

id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
address = Column(String, unique=True)
Expand Down Expand Up @@ -66,6 +71,11 @@ def __repr__(self):
class Credit(Base):
__tablename__ = "credits"

def __repr__(self):
return "Credit(id='{}', amount='{}', owner_id='{}')".format(
self.id, self.amount, self.owner_id
)

id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
amount = Column(Integer, default=0)
owner_id = Column(UUID(as_uuid=True), ForeignKey("users.id"))
Expand Down
33 changes: 18 additions & 15 deletions src/routes.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from src import database
from fastapi import APIRouter, HTTPException, status, Depends
from typing import List
import src.crud as crud
import src.schemas as schemas
import uuid
import asyncio

from sqlalchemy.orm import Session
from . import tezos
from . import tezos, crud, schemas, database
from pytezos.rpc.errors import MichelsonError
from pytezos.crypto.encoding import is_address
from .utils import ContractNotFound, CreditNotFound, EntrypointNotFound, UserNotFound
from .utils import (
ContractNotFound,
CreditNotFound,
EntrypointNotFound,
UserNotFound,
OperationNotFound,
)
from .config import logging


router = APIRouter()
Expand Down Expand Up @@ -74,7 +76,7 @@ async def update_credits(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Credit not found.",
)
except tezos.OperationNotFound:
except OperationNotFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Could not find the operation.",
Expand Down Expand Up @@ -255,23 +257,24 @@ async def post_operation(
except ContractNotFound:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Target {contract_address} is not allowed",
detail=f"{contract_address} is not found",
)

entrypoint_name = operation["parameters"]["entrypoint"]
print(contract_address, entrypoint_name)

try:
crud.get_entrypoint(db, str(contract.address), entrypoint_name)
except EntrypointNotFound:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Entrypoint {entrypoint_name} is not allowed",
detail=f"Entrypoint {entrypoint_name} is not found",
)

try:
# Simulate the operation alone without sending it
# TODO: log the result
op = tezos.simulate_transaction(call_data.operations)
logging.debug(f"Result of operation simulation : {op}")
op_estimated_fees = [(int(x["fee"]), x["destination"]) for x in op.contents]
estimated_fees = tezos.group_fees(op_estimated_fees)
if not tezos.check_credits(db, estimated_fees):
Expand All @@ -281,14 +284,14 @@ async def post_operation(
result = await tezos.tezos_manager.queue_operation(call_data.sender_address, op)
except MichelsonError as e:
print("Received failing operation, discarding")
print(e)
logging.error(f"Invalid operation {e}")
raise HTTPException(
# FIXME? Is this the best one?
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Operation is invalid",
)
except Exception as e:
print(e)
logging.error(f"Unknown error on /operation : {e}")
raise HTTPException(
# FIXME? Is this the best one?
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
Expand All @@ -311,7 +314,7 @@ async def signed_operation(
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid signature."
)
address = tezos.public_key_hash(call_data.sender_key)
call_data = schemas.UnsignedCall(
call_data_unsigned = schemas.UnsignedCall(
sender_address=address, operations=call_data.operations
)
return await post_operation(call_data, db)
return await post_operation(call_data_unsigned, db)
41 changes: 21 additions & 20 deletions src/tezos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
import asyncio
from typing import Union

from src import database

# from .pytezos import ptz, pytezos
from . import crud, schemas
from . import crud, schemas, config, database
from .utils import OperationNotFound
from pytezos.rpc.errors import MichelsonError
from pytezos.michelson.types import MichelsonType
from pytezos.michelson.types.base import MichelsonType
import pytezos

from . import config

log = config.logging

# Config stuff for pytezos
assert (
Expand All @@ -19,15 +19,14 @@

admin_key = pytezos.pytezos.key.from_encoded_key(config.SECRET_KEY)
ptz = pytezos.pytezos.using(config.TEZOS_RPC, admin_key)
print(f"INFO: API address is {ptz.key.public_key_hash()}")
log.info(f"API address is {ptz.key.public_key_hash()}")
constants = ptz.shell.block.context.constants()


class OperationNotFound(Exception):
pass


async def find_transaction(tx_hash):
"""Finds the transaction from its hash.
This function searches the last 10 blocks
"""
block_time = int(constants["minimal_block_delay"])
nb_try = 0
while nb_try < 4:
Expand Down Expand Up @@ -74,7 +73,7 @@ def check_credits(db, estimated_fees):
for address, total_fee in estimated_fees.items():
credits = crud.get_credits_from_contract_address(db, address)
if total_fee > credits.amount:
print(
log.warning(
f"Unsufficient credits {credits.amount} for contract"
+ f"{address}; total fees are {total_fee}."
)
Expand All @@ -85,10 +84,6 @@ def check_credits(db, estimated_fees):
async def confirm_deposit(tx_hash, payer, amount: Union[int, str]):
receiver = ptz.key.public_key_hash()
op_result = await find_transaction(tx_hash)
print(op_result["contents"])
print("payer " + str(payer))
print("receiver " + str(receiver))
print("amount " + str(amount))
return any(
op
for op in op_result["contents"]
Expand All @@ -100,6 +95,9 @@ async def confirm_deposit(tx_hash, payer, amount: Union[int, str]):


async def confirm_withdraw(tx_hash, db, user_id, withdraw):
"""Ensure withdraw transaction is successful to update credits user. \n
Can raise an OperationNotFound exception if transaction is not found.
"""
await find_transaction(tx_hash)
credit_update = schemas.CreditUpdate(
id=withdraw.id, amount=-withdraw.amount, owner_id=user_id, operation_hash=""
Expand Down Expand Up @@ -142,6 +140,7 @@ def check_signature(pair_data, signature, public_key, pair_type=None):
# .verify raises an exception when the verification fails
return public_key.verify(message=packed_pair, signature=signature)
except ValueError:
log.error(f"Signature {signature} for {public_key} is not valid.")
return False


Expand All @@ -167,7 +166,6 @@ def __init__(self, ptz):
# Receive an operation from sender and add it to the waiting queue;
# blocks until there is a result in self.results
async def queue_operation(self, sender, operation):
print(operation)
self.results[sender] = "waiting"
self.ops_queue[sender] = operation
while self.results[sender] == "waiting":
Expand All @@ -176,6 +174,7 @@ async def queue_operation(self, sender, operation):
await asyncio.sleep(1)

if self.results[sender] == "waiting":
log.error(f"Still waiting for transaction from {sender}... Abort")
raise Exception()

return {
Expand Down Expand Up @@ -209,7 +208,7 @@ async def main_loop(self):
# TODO catch errors

n_ops = len(self.ops_queue)
print(f"found {n_ops} operations to send")
log.debug(f"found {n_ops} operations to send")
acceptable_operations = OrderedDict()
for sender in self.ops_queue:
op = self.ops_queue[sender]
Expand All @@ -223,8 +222,9 @@ async def main_loop(self):
self.results[sender] = "failing"

n_ops = len(acceptable_operations)
print(f"found {n_ops} valid operations to send")
log.debug(f"found {n_ops} valid operations to send")
if n_ops > 0:
log.info(f"{n_ops} operations to process and send")
# Post all the correct operations together and get the
# result from the RPC to know what the real fees were
posted_tx = ptz.bulk(*acceptable_operations.values()).send()
Expand All @@ -233,9 +233,10 @@ async def main_loop(self):
self.results[k] = {"transaction": posted_tx}
asyncio.create_task(self.update_fees(posted_tx))
self.ops_queue = dict()
print("Tezos loop executed")
except Exception:
log.debug("Tezos loop executed")
except Exception as e:
# FIXME: Should we raise an Exception here ?
log.error(f"Error occurred on main loop : {e}")
pass


Expand Down
6 changes: 4 additions & 2 deletions src/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# -- EXCEPTIONS --


class UserNotFound(Exception):
pass

Expand All @@ -19,3 +17,7 @@ class CreditNotFound(Exception):

class ConfigurationError(Exception):
pass


class OperationNotFound(Exception):
pass

0 comments on commit c1bde8c

Please sign in to comment.