generated from electron-react-boilerplate/electron-react-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from Jwyman328/feature/privacy
Feature/privacy
- Loading branch information
Showing
56 changed files
with
7,441 additions
and
177 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,4 @@ chardet | |
charset-normalizer==2.1.0 | ||
hwi===3.0.0 | ||
cryptography==42.0.8 | ||
bitcoinlib==0.6.15 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
from src.api.fees import get_fees | ||
from src.api.electrum import electrum_request, parse_electrum_url, ElectrumMethod |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
from enum import Enum | ||
from typing import List, Optional, Literal | ||
from dataclasses import dataclass | ||
from bitcoinlib.transactions import Transaction | ||
import structlog | ||
import json | ||
import socket | ||
import asyncio | ||
|
||
LOGGER = structlog.get_logger() | ||
|
||
|
||
def parse_electrum_url(electrum_url: str) -> tuple[Optional[str], Optional[str]]: | ||
try: | ||
url, port = electrum_url.split(":") | ||
if url == "" or port == "": | ||
return None, None | ||
return url, port | ||
except ValueError: | ||
return None, None | ||
|
||
|
||
class ElectrumMethod(Enum): | ||
GET_TRANSACTIONS = "blockchain.transaction.get" | ||
|
||
|
||
@dataclass | ||
class GetTransactionsRequestParams: | ||
txid: str | ||
verbose: bool | ||
|
||
def create_params_list(self) -> List[str | bool]: | ||
return [self.txid, self.verbose] | ||
|
||
|
||
GetTransactionsResponse = Transaction | ||
|
||
ALL_UTXOS_REQUEST_PARAMS = GetTransactionsRequestParams | ||
ElectrumRawResponses = dict[str, str] | ||
ElectrumDataResponses = GetTransactionsResponse | ||
|
||
|
||
@dataclass | ||
class ElectrumResponse: | ||
status: Literal["success", "error"] | ||
data: Optional[ElectrumDataResponses] | ||
|
||
|
||
async def electrum_request( | ||
url: str, | ||
port: int, | ||
electrum_method: ElectrumMethod, | ||
params: Optional[ALL_UTXOS_REQUEST_PARAMS], | ||
request_id: Optional[int] = 1, | ||
) -> ElectrumResponse: | ||
writer = None | ||
try: | ||
# Use asyncio to manage the socket connection | ||
reader, writer = await asyncio.open_connection(url, port) | ||
|
||
request = json.dumps( | ||
{ | ||
"jsonrpc": "2.0", | ||
"id": request_id, | ||
"method": electrum_method.value, | ||
"params": params.create_params_list() if params else [], | ||
} | ||
) | ||
|
||
LOGGER.info(f"Sending electrum request: {request}") | ||
writer.write((request + "\n").encode("utf-8")) | ||
await writer.drain() | ||
|
||
# Read the response asynchronously | ||
response = b"" | ||
while True: | ||
part = await reader.read(4096) | ||
response += part | ||
if len(part) < 4096: | ||
break | ||
|
||
# Decode and parse the JSON response | ||
raw_response_data = json.loads(response.decode("utf-8").strip()) | ||
|
||
# Assuming handle_raw_electrum_response is an existing function | ||
response_data = handle_raw_electrum_response( | ||
electrum_method, raw_response_data) | ||
return ElectrumResponse(status="success", data=response_data) | ||
except socket.error as e: | ||
LOGGER.error(f"Socket error: {e}") | ||
return ElectrumResponse(status="error", data=None) | ||
except json.JSONDecodeError as e: | ||
LOGGER.error(f"JSON decode error: {e}") | ||
return ElectrumResponse(status="error", data=None) | ||
except Exception as e: | ||
LOGGER.error(f"An error occurred: {e}") | ||
return ElectrumResponse(status="error", data=None) | ||
finally: | ||
# Close the writer/reader | ||
if writer: | ||
writer.close() | ||
await writer.wait_closed() | ||
|
||
|
||
def handle_raw_electrum_response( | ||
electrum_method: ElectrumMethod, raw_response: dict | ||
) -> ElectrumDataResponses: | ||
if electrum_method == ElectrumMethod.GET_TRANSACTIONS: | ||
transaction = Transaction.parse(raw_response["result"], strict=True) | ||
return transaction |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,16 @@ | ||
from dependency_injector import containers, providers | ||
|
||
from src.services.privacy_metrics.privacy_metrics import PrivacyMetricsService | ||
|
||
|
||
class ServiceContainer(containers.DeclarativeContainer): | ||
from src.services import WalletService, FeeService, HardwareWalletService | ||
|
||
wiring_config = containers.WiringConfiguration(packages=["..controllers", "..services"]) | ||
wiring_config = containers.WiringConfiguration( | ||
packages=["..controllers", "..services"] | ||
) | ||
|
||
wallet_service = providers.Factory(WalletService) | ||
hardware_wallet_service = providers.Singleton(HardwareWalletService) | ||
fee_service = providers.Factory(FeeService) | ||
privacy_metrics_service = providers.Factory(PrivacyMetricsService) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,11 @@ | ||
from .balance import balance_page | ||
from .utxos import utxo_page | ||
from .transactions import transactions_page | ||
from .fees import fees_api | ||
from .wallet import wallet_api | ||
|
||
from .health_check import health_check_api | ||
|
||
from .hardware_wallets import hardware_wallet_api | ||
|
||
from .privacy_metrics import privacy_metrics_api |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
from flask import Blueprint | ||
import json | ||
|
||
from src.my_types.controller_types.privacy_metrics_dtos import ( | ||
AnalyzeTxPrivacyRequestDto, | ||
) | ||
from src.services import PrivacyMetricsService | ||
from dependency_injector.wiring import inject, Provide | ||
from src.containers.service_container import ServiceContainer | ||
from flask import request | ||
import structlog | ||
|
||
from src.my_types import ( | ||
GetAllPrivacyMetricsResponseDto, | ||
PrivacyMetricDto, | ||
AnalyzeTxPrivacyResponseDto, | ||
) | ||
from src.my_types.controller_types.generic_response_types import SimpleErrorResponse | ||
|
||
privacy_metrics_api = Blueprint( | ||
"privacy_metrics", __name__, url_prefix="/privacy-metrics" | ||
) | ||
|
||
LOGGER = structlog.get_logger() | ||
|
||
|
||
@privacy_metrics_api.route("/") | ||
@inject | ||
def get_privacy_metrics( | ||
privacy_service: PrivacyMetricsService = Provide[ | ||
ServiceContainer.privacy_metrics_service | ||
], | ||
): | ||
""" | ||
Get all privacy metrics. | ||
""" | ||
try: | ||
all_metrics = privacy_service.get_all_privacy_metrics() | ||
|
||
return GetAllPrivacyMetricsResponseDto.model_validate( | ||
dict( | ||
metrics=[ | ||
PrivacyMetricDto( | ||
name=privacy_metric.name, | ||
display_name=privacy_metric.display_name, | ||
description=privacy_metric.description, | ||
) | ||
for privacy_metric in all_metrics | ||
] | ||
) | ||
).model_dump() | ||
|
||
except Exception as e: | ||
LOGGER.error("error getting privacy metrics", error=e) | ||
return SimpleErrorResponse(message="error getting privacy metrics").model_dump() | ||
|
||
|
||
@privacy_metrics_api.route("/", methods=["POST"]) | ||
@inject | ||
def anaylze_tx_privacy( | ||
privacy_service: PrivacyMetricsService = Provide[ | ||
ServiceContainer.privacy_metrics_service | ||
], | ||
): | ||
""" | ||
Analyze a selected transaction based on an array of selected privacy metrics. | ||
""" | ||
try: | ||
request_data = AnalyzeTxPrivacyRequestDto.model_validate( | ||
json.loads(request.data) | ||
) | ||
results = privacy_service.analyze_tx_privacy( | ||
request_data.txid, request_data.privacy_metrics | ||
) | ||
|
||
return AnalyzeTxPrivacyResponseDto.model_validate( | ||
dict(results=results) | ||
).model_dump() | ||
|
||
except Exception as e: | ||
LOGGER.error("error analzying transaction privacy metrics", error=e) | ||
return SimpleErrorResponse( | ||
message="error analzying transaction privacy metrics" | ||
).model_dump() |
Oops, something went wrong.