diff --git a/alembic/versions/321b2954fdf2_add_coinbase_to_transaction.py b/alembic/versions/321b2954fdf2_add_coinbase_to_transaction.py new file mode 100644 index 0000000..9782ef9 --- /dev/null +++ b/alembic/versions/321b2954fdf2_add_coinbase_to_transaction.py @@ -0,0 +1,30 @@ +"""Add coinbase to transaction + +Revision ID: 321b2954fdf2 +Revises: 94324ba323d4 +Create Date: 2025-10-18 18:32:39.295802 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '321b2954fdf2' +down_revision: Union[str, None] = '94324ba323d4' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('service_transactions', sa.Column('coinbase', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('service_transactions', 'coinbase') + # ### end Alembic commands ### diff --git a/alembic/versions/c138389e735b_add_block_index_to_transaction.py b/alembic/versions/c138389e735b_add_block_index_to_transaction.py new file mode 100644 index 0000000..9fd26f2 --- /dev/null +++ b/alembic/versions/c138389e735b_add_block_index_to_transaction.py @@ -0,0 +1,30 @@ +"""Add block_index to transaction + +Revision ID: c138389e735b +Revises: 321b2954fdf2 +Create Date: 2025-10-18 18:39:32.549559 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'c138389e735b' +down_revision: Union[str, None] = '321b2954fdf2' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('service_transactions', sa.Column('block_index', sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('service_transactions', 'block_index') + # ### end Alembic commands ### diff --git a/app/models/transaction.py b/app/models/transaction.py index 504f27e..cb6a3ed 100644 --- a/app/models/transaction.py +++ b/app/models/transaction.py @@ -29,3 +29,6 @@ class Transaction(Base): version: Mapped[int] amount: Mapped[dict[str, float]] = mapped_column(JSONB, default={}) + + coinbase: Mapped[bool] = mapped_column(nullable=True) + block_index: Mapped[int] = mapped_column(nullable=True) diff --git a/app/parser.py b/app/parser.py index b4153c7..4e70144 100644 --- a/app/parser.py +++ b/app/parser.py @@ -161,6 +161,7 @@ async def parse_transactions(txids: list[str]): for transaction_result in transactions_result: transaction_data = transaction_result["result"] assert transaction_data + index = txids.index(transaction_data["txid"]) addresses = list( set( @@ -180,6 +181,8 @@ async def parse_transactions(txids: list[str]): "locktime": transaction_data["locktime"], "version": transaction_data["version"], "timestamp": timestamp, + "index": index, + "coinbase": index == 0, "size": transaction_data["size"], "txid": transaction_data["txid"], } diff --git a/app/schemas.py b/app/schemas.py index f003568..6151132 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -88,6 +88,7 @@ class TransactionResponse(CustomModel): amount: dict[str, Satoshi] outputs: list[OutputFullResponse] inputs: list[InputFullResponse] + coinbase: bool confirmations: int fee: Satoshi diff --git a/app/sync/chain.py b/app/sync/chain.py index 0ed22de..3d46964 100644 --- a/app/sync/chain.py +++ b/app/sync/chain.py @@ -72,6 +72,8 @@ async def process_block(session: AsyncSession, data: dict[str, Any]): "size": transaction_data["size"], "txid": transaction_data["txid"], "currencies": transaction_currencies[transaction_data["txid"]], + "coinbase": transaction_data["coinbase"], + "block_index": transaction_data["index"], "height": block.height, "amount": { currency: float(amount) diff --git a/scripts/add_coinbase_to_txs.py b/scripts/add_coinbase_to_txs.py new file mode 100644 index 0000000..0a29d90 --- /dev/null +++ b/scripts/add_coinbase_to_txs.py @@ -0,0 +1,96 @@ +import asyncio +import json +import pathlib +import typing +import sys + +sys.path.append(str(pathlib.Path(__file__).parent.parent)) + +from sqlalchemy import func, select, update + +from app.database import sessionmanager +from app.settings import get_settings +from app.parser import make_request +from app.models import Transaction + +settings: typing.Any = get_settings() + + +async def main(): + sessionmanager.init(settings.database.endpoint) + + async with sessionmanager.session() as session: + + total = ( + await session.scalar( + select(func.count(Transaction.id)).filter( + (Transaction.coinbase == None) | (Transaction.block_index == None) + ) + ) + or 0 + ) + limit = 100 + + query = ( + select(Transaction.id, Transaction.txid) + .filter((Transaction.coinbase == None) | (Transaction.block_index == None)) + .limit(limit) + ) + + processed = 0 + + while txs := (await session.execute(query)).all(): + updates: list[dict[str, typing.Any]] = [] + + for dbid, txid in txs: + transaction_result = await make_request( + settings.blockchain.endpoint, + [ + { + "id": str(dbid), + "method": "getrawtransaction", + "params": [txid, True], + } + ], + ) + tx = transaction_result[0]["result"] + + block_response = await make_request( + settings.blockchain.endpoint, + [ + { + "id": tx["blockhash"], + "method": "getblock", + "params": [tx["blockhash"]], + } + ], + ) + block = block_response[0]["result"] + + block_index = block["tx"].index(txid) + updates.append( + { + "id": dbid, + "block_index": block_index, + "coinbase": block_index == 0, + } + ) + + await session.execute( + update(Transaction), + updates, + execution_options={"synchronize_session": False}, + ) + await session.commit() + processed += limit + print( + f"Progress: {processed}/{total} ({(processed/total)*100:.2f})", + end="\r", + flush=True, + ) + + print("\nDone") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/helpers.py b/tests/helpers.py index a3e0a34..559e19b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -16,6 +16,7 @@ async def create_transaction( amount: dict[str, float] | None = None, blockhash: str | None = None, addresses: list[str] | None = None, + coinbase: bool = False, ) -> Transaction: if currencies is None: currencies = ["MBC"] @@ -36,6 +37,7 @@ async def create_transaction( locktime=locktime, version=version, amount=amount, + coinbase=coinbase, addresses=addresses or [secrets.token_hex(32), secrets.token_hex(32)], )