Skip to content

Commit

Permalink
Resolve inputs' previous outpoint directly (#21)
Browse files Browse the repository at this point in the history
* resolve previous outpoint txs

* light and full mode

* fix when outpoint not in DB

* description for query param

* resolved review comments
  • Loading branch information
lAmeR1 authored Mar 11, 2023
1 parent 91a5cce commit 6109dd1
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 28 deletions.
35 changes: 27 additions & 8 deletions endpoints/get_address_transactions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
from enum import Enum
from typing import List

from fastapi import Path, Query
Expand All @@ -12,6 +13,11 @@

from models.TxAddrMapping import TxAddrMapping

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 " \
"adds it into each TxInput."


class TransactionsReceivedAndSpent(BaseModel):
tx_received: str
tx_spent: str | None
Expand All @@ -21,10 +27,17 @@ class TransactionsReceivedAndSpent(BaseModel):
class TransactionForAddressResponse(BaseModel):
transactions: List[TransactionsReceivedAndSpent]


class TransactionCount(BaseModel):
total: int


class PreviousOutpointLookupMode(str, Enum):
no = "no"
light = "light"
full = "full"


@app.get("/addresses/{kaspaAddress}/transactions",
response_model=TransactionForAddressResponse,
response_model_exclude_unset=True,
Expand Down Expand Up @@ -69,6 +82,7 @@ async def get_transactions_for_address(
"transactions": tx_list
}


@app.get("/addresses/{kaspaAddress}/full-transactions",
response_model=List[TxModel],
response_model_exclude_unset=True,
Expand All @@ -88,7 +102,9 @@ async def get_full_transactions_for_address(
ge=0,
default=0),
fields: str = "",
):
resolve_previous_outpoints: PreviousOutpointLookupMode =
Query(default="no",
description=DESC_RESOLVE_PARAM)):
"""
Get all transactions for a given address from database.
And then get their related full transaction data
Expand All @@ -98,21 +114,24 @@ async def get_full_transactions_for_address(
# Doing it this way as opposed to adding it directly in the IN clause
# so I can re-use the same result in tx_list, TxInput and TxOutput
tx_within_limit_offset = await s.execute(select(TxAddrMapping.transaction_id)
.filter(TxAddrMapping.address == kaspaAddress)
.limit(limit)
.offset(offset)
.order_by(TxAddrMapping.block_time.desc())
)
.filter(TxAddrMapping.address == kaspaAddress)
.limit(limit)
.offset(offset)
.order_by(TxAddrMapping.block_time.desc())
)

tx_ids_in_page = [x[0] for x in tx_within_limit_offset.all()]

return await search_for_transactions(TxSearch(transactionIds=tx_ids_in_page), fields)
return await search_for_transactions(TxSearch(transactionIds=tx_ids_in_page),
fields,
resolve_previous_outpoints)


@app.get("/addresses/{kaspaAddress}/transactions-count",
response_model=TransactionCount,
tags=["Kaspa addresses"])
async def get_transaction_count_for_address(
kaspaAddress: str = Path(
kaspaAddress: str = Path(
description="Kaspa address as string e.g. "
"kaspa:pzhh76qc82wzduvsrd9xh4zde9qhp0xc8rl7qu2mvl2e42uvdqt75zrcgpm00",
regex="^kaspa\:[a-z0-9]{61,63}$")
Expand Down
120 changes: 100 additions & 20 deletions endpoints/get_transactions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# encoding: utf-8

from enum import Enum
from typing import List

from fastapi import Path, HTTPException
from fastapi import Path, HTTPException, Query
from pydantic import BaseModel, parse_obj_as
from sqlalchemy import Integer, cast
from sqlalchemy.future import select

from dbsession import async_session
Expand All @@ -11,29 +14,36 @@
from models.Transaction import Transaction, TransactionOutput, TransactionInput
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 " \
"adds it into each TxInput."

class TxInput(BaseModel):

class TxOutput(BaseModel):
id: int
transaction_id: str
index: int
previous_outpoint_hash: str
previous_outpoint_index: str
signature_script: str
sig_op_count: str
amount: int
script_public_key: str
script_public_key_address: str
script_public_key_type: str
accepting_block_hash: str | None

class Config:
orm_mode = True


class TxOutput(BaseModel):
class TxInput(BaseModel):
id: int
transaction_id: str
index: int
amount: int
script_public_key: str
script_public_key_address: str
script_public_key_type: str
accepting_block_hash: str | None
previous_outpoint_hash: str
previous_outpoint_index: str
previous_outpoint_resolved: TxOutput | None
previous_outpoint_address: str | None
previous_outpoint_amount: int | None
signature_script: str
sig_op_count: str

class Config:
orm_mode = True
Expand All @@ -60,13 +70,22 @@ class TxSearch(BaseModel):
transactionIds: List[str]


class PreviousOutpointLookupMode(str, Enum):
no = "no"
light = "light"
full = "full"


@app.get("/transactions/{transactionId}",
response_model=TxModel,
tags=["Kaspa transactions"],
response_model_exclude_unset=True)
async def get_transaction(transactionId: str = Path(regex="[a-f0-9]{64}"),
inputs: bool = True,
outputs: bool = True):
outputs: bool = True,
resolve_previous_outpoints: PreviousOutpointLookupMode =
Query(default=PreviousOutpointLookupMode.no,
description=DESC_RESOLVE_PARAM)):
"""
Get block information for a given block id
"""
Expand All @@ -87,9 +106,37 @@ async def get_transaction(transactionId: str = Path(regex="[a-f0-9]{64}"),
tx_outputs = tx_outputs.scalars().all()

if inputs:
tx_inputs = await s.execute(select(TransactionInput) \
.filter(TransactionInput.transaction_id == transactionId))
tx_inputs = tx_inputs.scalars().all()
if resolve_previous_outpoints in ["light", "full"]:
tx_inputs = await s.execute(select(TransactionInput, TransactionOutput)
.outerjoin(TransactionOutput,
(TransactionOutput.transaction_id == TransactionInput.previous_outpoint_hash) &
(TransactionOutput.index == cast(TransactionInput.previous_outpoint_index, Integer)))
.filter(TransactionInput.transaction_id == transactionId))

tx_inputs = tx_inputs.all()

if resolve_previous_outpoints in ["light", "full"]:
for tx_in, tx_prev_outputs in tx_inputs:
# it is possible, that the old tx is not in database. Leave fields empty
if not tx_prev_outputs:
tx_in.previous_outpoint_amount = None
tx_in.previous_outpoint_address = None
if resolve_previous_outpoints == "full":
tx_in.previous_outpoint_resolved = None
continue

tx_in.previous_outpoint_amount = tx_prev_outputs.amount
tx_in.previous_outpoint_address = tx_prev_outputs.script_public_key_address
if resolve_previous_outpoints == "full":
tx_in.previous_outpoint_resolved = tx_prev_outputs

# remove unneeded list
tx_inputs = [x[0] for x in tx_inputs]

else:
tx_inputs = await s.execute(select(TransactionInput) \
.filter(TransactionInput.transaction_id == transactionId))
tx_inputs = tx_inputs.scalars().all()

if tx:
return {
Expand All @@ -114,7 +161,10 @@ async def get_transaction(transactionId: str = Path(regex="[a-f0-9]{64}"),
tags=["Kaspa transactions"],
response_model_exclude_unset=True)
async def search_for_transactions(txSearch: TxSearch,
fields: str = ""):
fields: str = "",
resolve_previous_outpoints: PreviousOutpointLookupMode =
Query(default=PreviousOutpointLookupMode.no,
description=DESC_RESOLVE_PARAM)):
"""
Get block information for a given block id
"""
Expand All @@ -129,9 +179,39 @@ async def search_for_transactions(txSearch: TxSearch,
tx_list = tx_list.all()

if not fields or "inputs" in fields:
tx_inputs = await s.execute(select(TransactionInput) \
.filter(TransactionInput.transaction_id.in_(txSearch.transactionIds)))
tx_inputs = tx_inputs.scalars().all()
# join TxOutputs if needed
if resolve_previous_outpoints in ["light", "full"]:
tx_inputs = await s.execute(select(TransactionInput, TransactionOutput)
.outerjoin(TransactionOutput,
(TransactionOutput.transaction_id == TransactionInput.previous_outpoint_hash) &
(TransactionOutput.index == cast(TransactionInput.previous_outpoint_index, Integer)))
.filter(TransactionInput.transaction_id.in_(txSearch.transactionIds)))

# without joining previous_tx_outputs
else:
tx_inputs = await s.execute(select(TransactionInput)
.filter(TransactionInput.transaction_id.in_(txSearch.transactionIds)))
tx_inputs = tx_inputs.all()

if resolve_previous_outpoints in ["light", "full"]:
for tx_in, tx_prev_outputs in tx_inputs:

# it is possible, that the old tx is not in database. Leave fields empty
if not tx_prev_outputs:
tx_in.previous_outpoint_amount = None
tx_in.previous_outpoint_address = None
if resolve_previous_outpoints == "full":
tx_in.previous_outpoint_resolved = None
continue

tx_in.previous_outpoint_amount = tx_prev_outputs.amount
tx_in.previous_outpoint_address = tx_prev_outputs.script_public_key_address
if resolve_previous_outpoints == "full":
tx_in.previous_outpoint_resolved = tx_prev_outputs

# remove unneeded list
tx_inputs = [x[0] for x in tx_inputs]

else:
tx_inputs = None

Expand Down

0 comments on commit 6109dd1

Please sign in to comment.