From 436c11ed9ef39bdd9befd9807a373eee9568c557 Mon Sep 17 00:00:00 2001 From: lAmeR1 <42315864+lAmeR1@users.noreply.github.com> Date: Wed, 3 May 2023 20:09:14 +0200 Subject: [PATCH] Lite version (#26) * allow to run REST server without postgresql database * removed default SQL_URI --- dbsession.py | 2 +- docker/Dockerfile | 1 - endpoints/__init__.py | 16 +++++++++++++ endpoints/get_address_transactions.py | 9 +++++--- endpoints/get_blocks.py | 33 ++++++++++++++++++--------- endpoints/get_circulating_supply.py | 3 ++- endpoints/get_hashrate.py | 2 ++ endpoints/get_marketcap.py | 6 ++--- endpoints/get_transactions.py | 4 +++- 9 files changed, 54 insertions(+), 22 deletions(-) diff --git a/dbsession.py b/dbsession.py index d02c655..5cf4955 100644 --- a/dbsession.py +++ b/dbsession.py @@ -7,7 +7,7 @@ _logger = logging.getLogger(__name__) -engine = create_async_engine(os.getenv("SQL_URI"), pool_pre_ping=True, echo=False) +engine = create_async_engine(os.getenv("SQL_URI", "postgresql+asyncpg://127.0.0.1:5432"), pool_pre_ping=True, echo=False) Base = declarative_base() session_maker = sessionmaker(engine) diff --git a/docker/Dockerfile b/docker/Dockerfile index 625f89e..81f4115 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,6 @@ ARG REPO_DIR EXPOSE 8000 ENV KASPAD_HOST_1=n.seeder1.kaspad.net:16110 -ENV SQL_URI=postgresql+asyncpg://postgres:password@postgresql:5432/postgres ARG version ENV VERSION=$version diff --git a/endpoints/__init__.py b/endpoints/__init__.py index c13ceb9..d5a424d 100644 --- a/endpoints/__init__.py +++ b/endpoints/__init__.py @@ -1,4 +1,9 @@ # encoding: utf-8 +import os +from functools import wraps + +from fastapi import HTTPException + def filter_fields(response_dict, fields): if fields: @@ -7,3 +12,14 @@ def filter_fields(response_dict, fields): } else: return response_dict + + +def sql_db_only(func): + @wraps(func) + async def wrapper(*args, **kwargs): + if not os.getenv("SQL_URI"): + raise HTTPException(status_code=503, detail="Endpoint not available. " + "This endpoint needs a configured SQL database.") + return await func(*args, **kwargs) + + return wrapper diff --git a/endpoints/get_address_transactions.py b/endpoints/get_address_transactions.py index a0e16de..a38a9f0 100644 --- a/endpoints/get_address_transactions.py +++ b/endpoints/get_address_transactions.py @@ -6,12 +6,12 @@ from pydantic import BaseModel from sqlalchemy import text, func from sqlalchemy.future import select -from endpoints.get_transactions import search_for_transactions, TxSearch, TxModel from dbsession import async_session -from server import app - +from endpoints import sql_db_only +from endpoints.get_transactions import search_for_transactions, TxSearch, TxModel from models.TxAddrMapping import TxAddrMapping +from server import app DESC_RESOLVE_PARAM = "Use this parameter if you want to fetch the TransactionInput previous outpoint details." \ " Light fetches only the address and amount. Full fetches the whole TransactionOutput and " \ @@ -43,6 +43,7 @@ class PreviousOutpointLookupMode(str, Enum): response_model_exclude_unset=True, tags=["Kaspa addresses"], deprecated=True) +@sql_db_only async def get_transactions_for_address( kaspaAddress: str = Path( description="Kaspa address as string e.g. " @@ -87,6 +88,7 @@ async def get_transactions_for_address( response_model=List[TxModel], response_model_exclude_unset=True, tags=["Kaspa addresses"]) +@sql_db_only async def get_full_transactions_for_address( kaspaAddress: str = Path( description="Kaspa address as string e.g. " @@ -130,6 +132,7 @@ async def get_full_transactions_for_address( @app.get("/addresses/{kaspaAddress}/transactions-count", response_model=TransactionCount, tags=["Kaspa addresses"]) +@sql_db_only async def get_transaction_count_for_address( kaspaAddress: str = Path( description="Kaspa address as string e.g. " diff --git a/endpoints/get_blocks.py b/endpoints/get_blocks.py index f083d47..158b355 100644 --- a/endpoints/get_blocks.py +++ b/endpoints/get_blocks.py @@ -1,4 +1,5 @@ # encoding: utf-8 +import os from typing import List from fastapi import Query, Path, HTTPException @@ -11,6 +12,8 @@ from models.Transaction import Transaction, TransactionOutput, TransactionInput from server import app, kaspad_client +IS_SQL_DB_CONFIGURED = os.getenv("SQL_URI") is not None + class VerboseDataModel(BaseModel): hash: str = "18c7afdf8f447ca06adb8b4946dc45f5feb1188c7d177da6094dfbc760eca699" @@ -76,14 +79,15 @@ async def get_block(response: Response, # We found the block in kaspad. Just use it requested_block = resp["getBlockResponse"]["block"] else: - # Didn't find the block in kaspad. Try getting it from the DB - response.headers["X-Data-Source"] = "Database" - requested_block = await get_block_from_db(blockId) - + if IS_SQL_DB_CONFIGURED: + # Didn't find the block in kaspad. Try getting it from the DB + response.headers["X-Data-Source"] = "Database" + requested_block = await get_block_from_db(blockId) + if not requested_block: # Still did not get the block raise HTTPException(status_code=404, detail="Block not found") - + # We found the block, now we guarantee it contains the transactions # It's possible that the block from kaspad does not contain transactions if 'transactions' not in requested_block or not requested_block['transactions']: @@ -91,6 +95,7 @@ async def get_block(response: Response, return requested_block + @app.get("/blocks", response_model=BlockResponse, tags=["Kaspa blocks"]) async def get_blocks(lowHash: str = Query(regex="[a-f0-9]{64}"), includeBlocks: bool = False, @@ -108,13 +113,16 @@ async def get_blocks(lowHash: str = Query(regex="[a-f0-9]{64}"), return resp["getBlocksResponse"] + """ Get the block from the database """ + + async def get_block_from_db(blockId): async with async_session() as s: requested_block = await s.execute(select(Block) - .where(Block.hash == blockId).limit(1)) + .where(Block.hash == blockId).limit(1)) try: requested_block = requested_block.first()[0] # type: Block @@ -137,7 +145,7 @@ async def get_block_from_db(blockId): "blueScore": requested_block.blue_score, "pruningPoint": requested_block.pruning_point }, - "transactions": None, # This will be filled later + "transactions": None, # This will be filled later "verboseData": { "hash": requested_block.hash, "difficulty": requested_block.difficulty, @@ -152,9 +160,12 @@ async def get_block_from_db(blockId): } return None + """ Get the transactions associated with a block """ + + async def get_block_transactions(blockId): # create tx data tx_list = [] @@ -165,14 +176,14 @@ async def get_block_transactions(blockId): transactions = transactions.scalars().all() tx_outputs = await s.execute(select(TransactionOutput) - .where(TransactionOutput.transaction_id + .where(TransactionOutput.transaction_id .in_([tx.transaction_id for tx in transactions]))) tx_outputs = tx_outputs.scalars().all() tx_inputs = await s.execute(select(TransactionInput) .where(TransactionInput.transaction_id - .in_([tx.transaction_id for tx in transactions]))) + .in_([tx.transaction_id for tx in transactions]))) tx_inputs = tx_inputs.scalars().all() @@ -208,5 +219,5 @@ async def get_block_transactions(blockId): "blockTime": tx.block_time } }) - - return tx_list \ No newline at end of file + + return tx_list diff --git a/endpoints/get_circulating_supply.py b/endpoints/get_circulating_supply.py index eff9127..ce8a0a4 100644 --- a/endpoints/get_circulating_supply.py +++ b/endpoints/get_circulating_supply.py @@ -2,6 +2,7 @@ from pydantic import BaseModel +from endpoints import sql_db_only from server import app, kaspad_client from fastapi.responses import PlainTextResponse @@ -39,7 +40,7 @@ async def get_circulating_coins(in_billion : bool = False): @app.get("/info/coinsupply/total", tags=["Kaspa network info"], response_class=PlainTextResponse) -async def get_circulating_coins(): +async def get_total_coins(): """ Get total amount of $KAS token as numerical value """ diff --git a/endpoints/get_hashrate.py b/endpoints/get_hashrate.py index 146d93a..6f9bc4b 100644 --- a/endpoints/get_hashrate.py +++ b/endpoints/get_hashrate.py @@ -5,6 +5,7 @@ from sqlalchemy import select from dbsession import async_session +from endpoints import sql_db_only from helper import KeyValueStore from models.Block import Block from server import app, kaspad_client @@ -49,6 +50,7 @@ async def get_hashrate(stringOnly: bool = False): @app.get("/info/hashrate/max", response_model=MaxHashrateResponse, tags=["Kaspa network info"]) +@sql_db_only async def get_max_hashrate(): """ Returns the current hashrate for Kaspa network in TH/s. diff --git a/endpoints/get_marketcap.py b/endpoints/get_marketcap.py index c107579..4dad4d4 100644 --- a/endpoints/get_marketcap.py +++ b/endpoints/get_marketcap.py @@ -1,6 +1,5 @@ # encoding: utf-8 -import requests from pydantic import BaseModel from helper import get_kas_price @@ -26,7 +25,6 @@ async def get_marketcap(stringOnly: bool = False): } else: if mcap < 1000000000: - return f"{round(mcap / 1000000,1)}M" + return f"{round(mcap / 1000000, 1)}M" else: - return f"{round(mcap / 1000000000,1)}B" - + return f"{round(mcap / 1000000000, 1)}B" diff --git a/endpoints/get_transactions.py b/endpoints/get_transactions.py index 605f43a..a2f8bb6 100644 --- a/endpoints/get_transactions.py +++ b/endpoints/get_transactions.py @@ -9,7 +9,7 @@ from sqlalchemy.future import select from dbsession import async_session -from endpoints import filter_fields +from endpoints import filter_fields, sql_db_only from models.Block import Block from models.Transaction import Transaction, TransactionOutput, TransactionInput from server import app @@ -80,6 +80,7 @@ class PreviousOutpointLookupMode(str, Enum): response_model=TxModel, tags=["Kaspa transactions"], response_model_exclude_unset=True) +@sql_db_only async def get_transaction(transactionId: str = Path(regex="[a-f0-9]{64}"), inputs: bool = True, outputs: bool = True, @@ -160,6 +161,7 @@ async def get_transaction(transactionId: str = Path(regex="[a-f0-9]{64}"), response_model=List[TxModel], tags=["Kaspa transactions"], response_model_exclude_unset=True) +@sql_db_only async def search_for_transactions(txSearch: TxSearch, fields: str = "", resolve_previous_outpoints: PreviousOutpointLookupMode =