Skip to content

Commit 7d0c6b4

Browse files
authored
Merge pull request #2 from MicroBitcoinOrg/feat/tx-coinbase
Add coinbase to transactions
2 parents acdcc6b + 7373df8 commit 7d0c6b4

File tree

8 files changed

+167
-0
lines changed

8 files changed

+167
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Add coinbase to transaction
2+
3+
Revision ID: 321b2954fdf2
4+
Revises: 94324ba323d4
5+
Create Date: 2025-10-18 18:32:39.295802
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '321b2954fdf2'
16+
down_revision: Union[str, None] = '94324ba323d4'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.add_column('service_transactions', sa.Column('coinbase', sa.Boolean(), nullable=True))
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade() -> None:
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_column('service_transactions', 'coinbase')
30+
# ### end Alembic commands ###
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Add block_index to transaction
2+
3+
Revision ID: c138389e735b
4+
Revises: 321b2954fdf2
5+
Create Date: 2025-10-18 18:39:32.549559
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = 'c138389e735b'
16+
down_revision: Union[str, None] = '321b2954fdf2'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.add_column('service_transactions', sa.Column('block_index', sa.Integer(), nullable=True))
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade() -> None:
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_column('service_transactions', 'block_index')
30+
# ### end Alembic commands ###

app/models/transaction.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ class Transaction(Base):
2929
version: Mapped[int]
3030

3131
amount: Mapped[dict[str, float]] = mapped_column(JSONB, default={})
32+
33+
coinbase: Mapped[bool] = mapped_column(nullable=True)
34+
block_index: Mapped[int] = mapped_column(nullable=True)

app/parser.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ async def parse_transactions(txids: list[str]):
161161
for transaction_result in transactions_result:
162162
transaction_data = transaction_result["result"]
163163
assert transaction_data
164+
index = txids.index(transaction_data["txid"])
164165

165166
addresses = list(
166167
set(
@@ -180,6 +181,8 @@ async def parse_transactions(txids: list[str]):
180181
"locktime": transaction_data["locktime"],
181182
"version": transaction_data["version"],
182183
"timestamp": timestamp,
184+
"index": index,
185+
"coinbase": index == 0,
183186
"size": transaction_data["size"],
184187
"txid": transaction_data["txid"],
185188
}

app/schemas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class TransactionResponse(CustomModel):
8888
amount: dict[str, Satoshi]
8989
outputs: list[OutputFullResponse]
9090
inputs: list[InputFullResponse]
91+
coinbase: bool
9192
confirmations: int
9293
fee: Satoshi
9394

app/sync/chain.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ async def process_block(session: AsyncSession, data: dict[str, Any]):
7272
"size": transaction_data["size"],
7373
"txid": transaction_data["txid"],
7474
"currencies": transaction_currencies[transaction_data["txid"]],
75+
"coinbase": transaction_data["coinbase"],
76+
"block_index": transaction_data["index"],
7577
"height": block.height,
7678
"amount": {
7779
currency: float(amount)

scripts/add_coinbase_to_txs.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import asyncio
2+
import json
3+
import pathlib
4+
import typing
5+
import sys
6+
7+
sys.path.append(str(pathlib.Path(__file__).parent.parent))
8+
9+
from sqlalchemy import func, select, update
10+
11+
from app.database import sessionmanager
12+
from app.settings import get_settings
13+
from app.parser import make_request
14+
from app.models import Transaction
15+
16+
settings: typing.Any = get_settings()
17+
18+
19+
async def main():
20+
sessionmanager.init(settings.database.endpoint)
21+
22+
async with sessionmanager.session() as session:
23+
24+
total = (
25+
await session.scalar(
26+
select(func.count(Transaction.id)).filter(
27+
(Transaction.coinbase == None) | (Transaction.block_index == None)
28+
)
29+
)
30+
or 0
31+
)
32+
limit = 100
33+
34+
query = (
35+
select(Transaction.id, Transaction.txid)
36+
.filter((Transaction.coinbase == None) | (Transaction.block_index == None))
37+
.limit(limit)
38+
)
39+
40+
processed = 0
41+
42+
while txs := (await session.execute(query)).all():
43+
updates: list[dict[str, typing.Any]] = []
44+
45+
for dbid, txid in txs:
46+
transaction_result = await make_request(
47+
settings.blockchain.endpoint,
48+
[
49+
{
50+
"id": str(dbid),
51+
"method": "getrawtransaction",
52+
"params": [txid, True],
53+
}
54+
],
55+
)
56+
tx = transaction_result[0]["result"]
57+
58+
block_response = await make_request(
59+
settings.blockchain.endpoint,
60+
[
61+
{
62+
"id": tx["blockhash"],
63+
"method": "getblock",
64+
"params": [tx["blockhash"]],
65+
}
66+
],
67+
)
68+
block = block_response[0]["result"]
69+
70+
block_index = block["tx"].index(txid)
71+
updates.append(
72+
{
73+
"id": dbid,
74+
"block_index": block_index,
75+
"coinbase": block_index == 0,
76+
}
77+
)
78+
79+
await session.execute(
80+
update(Transaction),
81+
updates,
82+
execution_options={"synchronize_session": False},
83+
)
84+
await session.commit()
85+
processed += limit
86+
print(
87+
f"Progress: {processed}/{total} ({(processed/total)*100:.2f})",
88+
end="\r",
89+
flush=True,
90+
)
91+
92+
print("\nDone")
93+
94+
95+
if __name__ == "__main__":
96+
asyncio.run(main())

tests/helpers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ async def create_transaction(
1616
amount: dict[str, float] | None = None,
1717
blockhash: str | None = None,
1818
addresses: list[str] | None = None,
19+
coinbase: bool = False,
1920
) -> Transaction:
2021
if currencies is None:
2122
currencies = ["MBC"]
@@ -36,6 +37,7 @@ async def create_transaction(
3637
locktime=locktime,
3738
version=version,
3839
amount=amount,
40+
coinbase=coinbase,
3941
addresses=addresses or [secrets.token_hex(32), secrets.token_hex(32)],
4042
)
4143

0 commit comments

Comments
 (0)