diff --git a/endpoints/get_blocks.py b/endpoints/get_blocks.py index 0a7b760..f083d47 100644 --- a/endpoints/get_blocks.py +++ b/endpoints/get_blocks.py @@ -8,7 +8,7 @@ from dbsession import async_session from models.Block import Block -from models.Transaction import TransactionOutput, TransactionInput +from models.Transaction import Transaction, TransactionOutput, TransactionInput from server import app, kaspad_client @@ -70,107 +70,26 @@ async def get_block(response: Response, "hash": blockId, "includeTransactions": True }) - try: - return resp["getBlockResponse"]["block"] - except KeyError: - # not found on kaspad - check database - async with async_session() as s: - requested_block = await s.execute(select(Block) - .where(Block.hash == blockId).limit(1)) - - try: - requested_block = requested_block.first()[0] # type: Block - except TypeError: - raise HTTPException(status_code=404, detail="Block not found") - - if requested_block: - response.headers["X-Data-Source"] = "Database" - - # get transactions information - async with async_session() as s: - transactions = await s.execute("SELECT * FROM transactions WHERE block_hash @> '{" - f"{blockId}" - "}'""") - transactions = transactions.all() - - tx_outputs = await s.execute(select(TransactionOutput) - .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]))) - - tx_inputs = tx_inputs.scalars().all() - - # create tx data - tx_list = [] - for tx in transactions: - tx_list.append({ - "inputs": [ - { - "previousOutpoint": { - "transactionId": tx_inp.previous_outpoint_hash, - "index": tx_inp.previous_outpoint_index - }, - "signatureScript": tx_inp.signature_script, - "sigOpCount": tx_inp.sig_op_count - } - for tx_inp in tx_inputs if tx_inp.transaction_id == tx.transaction_id], - "outputs": [ - { - "amount": tx_out.amount, - "scriptPublicKey": { - "scriptPublicKey": tx_out.script_public_key - }, - "verboseData": { - "scriptPublicKeyType": tx_out.script_public_key_type, - "scriptPublicKeyAddress": tx_out.script_public_key_address - } - } for tx_out in tx_outputs if tx_out.transaction_id == tx.transaction_id], - "subnetworkId": tx.subnetwork_id, - "verboseData": { - "transactionId": tx.transaction_id, - "hash": tx.hash, - "mass": tx.mass, - "blockHash": tx.block_hash, - "blockTime": tx.block_time - } - }) - - return { - "header": { - "version": requested_block.version, - "hashMerkleRoot": requested_block.hash_merkle_root, - "acceptedIdMerkleRoot": requested_block.accepted_id_merkle_root, - "utxoCommitment": requested_block.utxo_commitment, - "timestamp": round(requested_block.timestamp.timestamp() * 1000), - "bits": requested_block.bits, - "nonce": requested_block.nonce, - "daaScore": requested_block.daa_score, - "blueWork": requested_block.blue_score, - "parents": [{"parentHashes": requested_block.parents}], - "blueScore": requested_block.blue_score, - "pruningPoint": requested_block.pruning_point - }, - "transactions": tx_list, - "verboseData": { - "hash": requested_block.hash, - "difficulty": requested_block.difficulty, - "selectedParentHash": requested_block.selected_parent_hash, - "transactionIds": [], - "blueScore": requested_block.blue_score, - "childrenHashes": [], - "mergeSetBluesHashes": requested_block.merge_set_blues_hashes, - "mergeSetRedsHashes": requested_block.merge_set_reds_hashes, - "isChainBlock": requested_block.is_chain_block - } - } - else: - raise HTTPException(status_code=404, detail="Block not found") - + requested_block = None + + if "block" in resp["getBlockResponse"]: + # 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 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']: + requested_block['transactions'] = await get_block_transactions(blockId) + + return requested_block @app.get("/blocks", response_model=BlockResponse, tags=["Kaspa blocks"]) async def get_blocks(lowHash: str = Query(regex="[a-f0-9]{64}"), @@ -188,3 +107,106 @@ 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)) + + try: + requested_block = requested_block.first()[0] # type: Block + except TypeError: + raise HTTPException(status_code=404, detail="Block not found") + + if requested_block: + return { + "header": { + "version": requested_block.version, + "hashMerkleRoot": requested_block.hash_merkle_root, + "acceptedIdMerkleRoot": requested_block.accepted_id_merkle_root, + "utxoCommitment": requested_block.utxo_commitment, + "timestamp": round(requested_block.timestamp.timestamp() * 1000), + "bits": requested_block.bits, + "nonce": requested_block.nonce, + "daaScore": requested_block.daa_score, + "blueWork": requested_block.blue_work, + "parents": [{"parentHashes": requested_block.parents}], + "blueScore": requested_block.blue_score, + "pruningPoint": requested_block.pruning_point + }, + "transactions": None, # This will be filled later + "verboseData": { + "hash": requested_block.hash, + "difficulty": requested_block.difficulty, + "selectedParentHash": requested_block.selected_parent_hash, + "transactionIds": [], + "blueScore": requested_block.blue_score, + "childrenHashes": [], + "mergeSetBluesHashes": requested_block.merge_set_blues_hashes, + "mergeSetRedsHashes": requested_block.merge_set_reds_hashes, + "isChainBlock": requested_block.is_chain_block + } + } + return None + +""" +Get the transactions associated with a block +""" +async def get_block_transactions(blockId): + # create tx data + tx_list = [] + + async with async_session() as s: + transactions = await s.execute(select(Transaction).filter(Transaction.block_hash.contains([blockId]))) + + transactions = transactions.scalars().all() + + tx_outputs = await s.execute(select(TransactionOutput) + .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]))) + + tx_inputs = tx_inputs.scalars().all() + + for tx in transactions: + tx_list.append({ + "inputs": [ + { + "previousOutpoint": { + "transactionId": tx_inp.previous_outpoint_hash, + "index": tx_inp.previous_outpoint_index + }, + "signatureScript": tx_inp.signature_script, + "sigOpCount": tx_inp.sig_op_count + } + for tx_inp in tx_inputs if tx_inp.transaction_id == tx.transaction_id], + "outputs": [ + { + "amount": tx_out.amount, + "scriptPublicKey": { + "scriptPublicKey": tx_out.script_public_key + }, + "verboseData": { + "scriptPublicKeyType": tx_out.script_public_key_type, + "scriptPublicKeyAddress": tx_out.script_public_key_address + } + } for tx_out in tx_outputs if tx_out.transaction_id == tx.transaction_id], + "subnetworkId": tx.subnetwork_id, + "verboseData": { + "transactionId": tx.transaction_id, + "hash": tx.hash, + "mass": tx.mass, + "blockHash": tx.block_hash, + "blockTime": tx.block_time + } + }) + + return tx_list \ No newline at end of file