mirror of
https://github.com/fusion44/blitz_api.git
synced 2026-08-13 11:52:45 +02:00
feat: get raw transaction data from bitcoin core
This commit is contained in:
parent
2053f0ea3b
commit
d9ea422e91
4 changed files with 117 additions and 4 deletions
|
|
@ -1,6 +1,6 @@
|
|||
from argparse import ArgumentError
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from fastapi import Query
|
||||
from pydantic.main import BaseModel
|
||||
|
|
@ -64,6 +64,55 @@ class BtcLocalAddress(BaseModel):
|
|||
)
|
||||
|
||||
|
||||
class RawTransaction(BaseModel):
|
||||
in_active_chain: Union[None, bool] = Query(
|
||||
None,
|
||||
description='Whether specified block is in the active chain or not (only present with explicit "blockhash" argument)',
|
||||
)
|
||||
txid: str = Query(..., description="The transaction id (same as provided)")
|
||||
hash: str = Query(
|
||||
...,
|
||||
description="The transaction hash (differs from txid for witness transactions)",
|
||||
)
|
||||
size: int = Query(..., description="The serialized transaction size")
|
||||
vsize: int = Query(
|
||||
...,
|
||||
description="The virtual transaction size (differs from size for witness transactions)",
|
||||
)
|
||||
weight: int = Query(
|
||||
..., description="The transaction's weight (between vsize*4 - 3 and vsize*4)"
|
||||
)
|
||||
version: int = Query(..., description="The version")
|
||||
locktime: int = Query(..., description="The lock time")
|
||||
vin: List[dict] = Query(..., description="The transaction's inputs")
|
||||
vout: List[dict] = Query(..., description="The transaction's outputs")
|
||||
blockhash: str = Query(..., description="The block hash")
|
||||
confirmations: int = Query(..., description="The number of confirmations")
|
||||
blocktime: int = Query(
|
||||
..., description="The block time in seconds since epoch (Jan 1 1970 GMT)"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_rpc(cls, tx):
|
||||
return cls(
|
||||
in_active_chain=tx["in_active_chain"]
|
||||
if "in_active_chain" in tx.keys()
|
||||
else None,
|
||||
txid=tx["txid"] if "txid" in tx.keys() else "",
|
||||
hash=tx["hash"] if "hash" in tx.keys() else "",
|
||||
size=tx["size"] if "size" in tx.keys() else 0,
|
||||
vsize=tx["vsize"] if "vsize" in tx.keys() else 0,
|
||||
weight=tx["weight"] if "weight" in tx.keys() else 0,
|
||||
version=tx["version"] if "version" in tx.keys() else 0,
|
||||
locktime=tx["locktime"] if "locktime" in tx.keys() else 0,
|
||||
vin=tx["vin"] if "vin" in tx.keys() else [],
|
||||
vout=tx["vout"] if "vout" in tx.keys() else [],
|
||||
blockhash=tx["blockhash"] if "blockhash" in tx.keys() else "",
|
||||
confirmations=tx["confirmations"] if "confirmations" in tx.keys() else 0,
|
||||
blocktime=tx["blocktime"] if "blocktime" in tx.keys() else 0,
|
||||
)
|
||||
|
||||
|
||||
# getnetworkinfo
|
||||
class NetworkInfo(BaseModel):
|
||||
version: int = Query(..., description="The bitcoin core server version")
|
||||
|
|
|
|||
|
|
@ -3,12 +3,19 @@ from fastapi.params import Depends, Query
|
|||
|
||||
from app.auth.auth_bearer import JWTBearer
|
||||
from app.bitcoind.docs import blocks_sub_doc, estimate_fee_mode_desc
|
||||
from app.bitcoind.models import BlockchainInfo, BtcInfo, FeeEstimationMode, NetworkInfo
|
||||
from app.bitcoind.models import (
|
||||
BlockchainInfo,
|
||||
BtcInfo,
|
||||
FeeEstimationMode,
|
||||
NetworkInfo,
|
||||
RawTransaction,
|
||||
)
|
||||
from app.bitcoind.service import (
|
||||
estimate_fee,
|
||||
get_blockchain_info,
|
||||
get_btc_info,
|
||||
get_network_info,
|
||||
get_raw_transaction,
|
||||
handle_block_sub,
|
||||
)
|
||||
from app.bitcoind.utils import bitcoin_rpc
|
||||
|
|
@ -106,6 +113,27 @@ async def getnetworkinfo():
|
|||
return info
|
||||
|
||||
|
||||
@router.get(
|
||||
"/get-raw-transaction",
|
||||
name=f"{_PREFIX}.get-raw-transaction",
|
||||
summary="Get information about a raw transaction",
|
||||
description="See documentation on [bitcoincore.org](https://bitcoincore.org/en/doc/22.0.0/rpc/rawtransactions/getrawtransaction/)",
|
||||
response_description="A JSON String with relevant information.",
|
||||
dependencies=[Depends(JWTBearer())],
|
||||
response_model=RawTransaction,
|
||||
responses={
|
||||
400: {"description": "Invalid transaction id"},
|
||||
404: {"description": "No such mempool or blockchain transaction."},
|
||||
},
|
||||
)
|
||||
async def get_raw_transaction_path(
|
||||
txid: str = Query(
|
||||
..., min_length=64, max_length=64, description="The transaction id"
|
||||
)
|
||||
):
|
||||
return await get_raw_transaction(txid)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/block-sub",
|
||||
name=f"{_PREFIX}.block-sub",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from app.bitcoind.models import (
|
|||
BtcInfo,
|
||||
FeeEstimationMode,
|
||||
NetworkInfo,
|
||||
RawTransaction,
|
||||
)
|
||||
from app.bitcoind.utils import bitcoin_config, bitcoin_rpc_async
|
||||
|
||||
|
|
@ -91,6 +92,21 @@ async def get_network_info() -> NetworkInfo:
|
|||
return NetworkInfo.from_rpc(result["result"])
|
||||
|
||||
|
||||
async def get_raw_transaction(txid: str) -> RawTransaction:
|
||||
result = await bitcoin_rpc_async("getrawtransaction", [txid, 1])
|
||||
|
||||
if result["error"] == None:
|
||||
return RawTransaction.from_rpc(result["result"])
|
||||
|
||||
if "No such mempool or blockchain transaction." in result["error"]:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail=result["error"])
|
||||
|
||||
if "must be of length 64" in result["error"]:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail=result["error"])
|
||||
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=result["error"])
|
||||
|
||||
|
||||
async def get_btc_info() -> BtcInfo:
|
||||
binfo = await get_blockchain_info()
|
||||
ninfo = await get_network_info()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import itertools
|
||||
import json
|
||||
from types import coroutine
|
||||
|
||||
|
|
@ -61,17 +62,22 @@ def bitcoin_rpc(method: str, params: list = []) -> requests.Response:
|
|||
return requests.post(bitcoin_config.rpc_url, auth=auth, headers=headers, data=data)
|
||||
|
||||
|
||||
# https://github.com/python/cpython/blob/3.10/Lib/asyncio/tasks.py#L31
|
||||
_generate_rpc_id = itertools.count(1).__next__
|
||||
|
||||
|
||||
async def bitcoin_rpc_async(method: str, params: list = []) -> coroutine:
|
||||
auth = aiohttp.BasicAuth(bitcoin_config.username, bitcoin_config.pw)
|
||||
headers = {"Content-type": "text/plain"}
|
||||
headers = {"Content-type": "text/json"}
|
||||
data = (
|
||||
'{"jsonrpc": "2.0", "method": "'
|
||||
+ method
|
||||
+ '", "id":"0", "params":'
|
||||
+ f'", "id":{_generate_rpc_id()}, "params":'
|
||||
+ json.dumps(params)
|
||||
+ "}"
|
||||
)
|
||||
|
||||
# TODO: Refactor this to use Exceptions
|
||||
async with aiohttp.ClientSession(auth=auth, headers=headers) as session:
|
||||
async with session.post(bitcoin_config.rpc_url, data=data) as resp:
|
||||
if resp.status == status.HTTP_200_OK:
|
||||
|
|
@ -87,6 +93,20 @@ async def bitcoin_rpc_async(method: str, params: list = []) -> coroutine:
|
|||
"status": status.HTTP_403_FORBIDDEN,
|
||||
}
|
||||
else:
|
||||
e = await resp.json()
|
||||
m = e["error"]["message"]
|
||||
if e["error"]:
|
||||
if "No such mempool or blockchain transaction." in m:
|
||||
return {
|
||||
"error": "No such mempool or blockchain transaction.",
|
||||
"status": status.HTTP_404_NOT_FOUND,
|
||||
}
|
||||
if "parameter 1 must be of length 64" in m:
|
||||
return {
|
||||
"error": m,
|
||||
"status": status.HTTP_400_BAD_REQUEST,
|
||||
}
|
||||
|
||||
return {
|
||||
"error": f"Unknown answer from Bitcoin Core. Reason: {resp.reason}",
|
||||
"status": resp.status,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue