electrs mit ip adressiert
This commit is contained in:
parent
92d13e2973
commit
a3978376af
4 changed files with 102 additions and 31 deletions
|
|
@ -13,3 +13,8 @@ services:
|
|||
volumes:
|
||||
- .:/app
|
||||
restart: always
|
||||
|
||||
|
||||
networks:
|
||||
bitcoin_bitcoin-net:
|
||||
external: true # ← angepasst
|
||||
|
|
|
|||
106
main.py
106
main.py
|
|
@ -1,29 +1,78 @@
|
|||
import os
|
||||
import socket
|
||||
import json
|
||||
from hashlib import sha256 as hashlib_sha256
|
||||
import base58
|
||||
from flask import Flask, jsonify, request
|
||||
from bitcoinlib.services.bitcoind import BitcoindClient
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Lade die Variablen aus der .env-Datei in die Umgebung
|
||||
load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# --- ZUGANGSDATEN AUS UMGEBUNGSVARIABLEN LADEN ---
|
||||
# os.getenv sucht nach dem Namen und liest den Wert aus
|
||||
RPC_USER = os.getenv('RPC_USER')
|
||||
RPC_PASS = os.getenv('RPC_PASS')
|
||||
# Da das Skript IN der Windows-VM läuft, ist der Node lokal erreichbar:
|
||||
IP_ADRESSE = '192.168.178.225'
|
||||
IP_ADRESSE = '192.168.178.225' # ← Docker-Servicename statt IP
|
||||
ELECTRS_HOST = '192.168.178.225' # ← Docker-Servicename statt IP
|
||||
ELECTRS_PORT = 50001
|
||||
|
||||
# Sicherheits-Check: Falls die .env fehlt oder leer ist, bricht das Skript ab
|
||||
if not RPC_USER or not RPC_PASS:
|
||||
raise ValueError("Fehler: RPC_USER oder RPC_PASS wurden nicht in der Umgebung gefunden!")
|
||||
|
||||
rpc_url = f'http://{RPC_USER}:{RPC_PASS}@{IP_ADRESSE}:8332'
|
||||
rpc_url = f'http://{RPC_USER}:{RPC_PASS}@192.168.178.225:8332'
|
||||
|
||||
def get_node():
|
||||
return BitcoindClient(network='bitcoin', base_url=rpc_url).proxy
|
||||
|
||||
# ==========================================
|
||||
# ELECTRS HILFSFUNKTIONEN
|
||||
# ==========================================
|
||||
def address_to_script(address: str) -> str:
|
||||
"""Konvertiert Bitcoin-Adresse → Output-Script (hex)"""
|
||||
if address.startswith("bc1"):
|
||||
# P2WPKH / P2WSH (bech32)
|
||||
# Manuell dekodieren ohne externe bech32-Library
|
||||
import bech32
|
||||
witver, witprog = bech32.decode("bc", address)
|
||||
if witprog is None:
|
||||
raise ValueError(f"Ungültige bech32-Adresse: {address}")
|
||||
if len(witprog) == 20:
|
||||
return "0014" + bytes(witprog).hex() # P2WPKH
|
||||
return "0020" + bytes(witprog).hex() # P2WSH
|
||||
|
||||
decoded = base58.b58decode_check(address)
|
||||
prefix = decoded[0]
|
||||
payload = decoded[1:].hex()
|
||||
|
||||
if prefix == 0x00: # 1... → P2PKH
|
||||
return f"76a914{payload}88ac"
|
||||
elif prefix == 0x05: # 3... → P2SH
|
||||
return f"a914{payload}87"
|
||||
|
||||
raise ValueError(f"Unbekanntes Adressformat: {address}")
|
||||
|
||||
def address_to_electrum_hash(address: str) -> str:
|
||||
"""Berechnet den Electrum script_hash für eine Adresse"""
|
||||
script = bytes.fromhex(address_to_script(address))
|
||||
return hashlib_sha256(script).digest()[::-1].hex()
|
||||
|
||||
def electrs_query(method: str, params: list):
|
||||
"""Sendet eine Anfrage an den lokalen Electrs-Server"""
|
||||
req = json.dumps({"id": 1, "method": method, "params": params}) + "\n"
|
||||
with socket.create_connection((ELECTRS_HOST, ELECTRS_PORT), timeout=10) as s:
|
||||
s.sendall(req.encode())
|
||||
data = b""
|
||||
while True:
|
||||
chunk = s.recv(4096)
|
||||
data += chunk
|
||||
if b"\n" in chunk:
|
||||
break
|
||||
result = json.loads(data.decode())
|
||||
if "error" in result and result["error"]:
|
||||
raise Exception(f"Electrs Fehler: {result['error']}")
|
||||
return result["result"]
|
||||
|
||||
# ==========================================
|
||||
# ENDPUNKT 1: Aktuelle Blockhöhe abfragen
|
||||
# ==========================================
|
||||
|
|
@ -66,40 +115,56 @@ def get_tx(txid):
|
|||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
# ==========================================
|
||||
# ENDPUNKT 4: Guthaben einer beliebigen Adresse
|
||||
# ENDPUNKT 4: Guthaben & UTXOs via Electrs
|
||||
# ==========================================
|
||||
@app.route('/api/guthaben/<adresse>', methods=['GET'])
|
||||
def get_guthaben(adresse):
|
||||
try:
|
||||
node = get_node()
|
||||
scan_result = node.scantxoutset("start", [f"addr({adresse})"])
|
||||
script_hash = address_to_electrum_hash(adresse)
|
||||
|
||||
# Balance (confirmed + unconfirmed)
|
||||
balance = electrs_query("blockchain.scripthash.get_balance", [script_hash])
|
||||
|
||||
# UTXOs (nur unspent outputs)
|
||||
utxos = electrs_query("blockchain.scripthash.listunspent", [script_hash])
|
||||
|
||||
confirmed = balance["confirmed"]
|
||||
unconfirmed = balance["unconfirmed"]
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'adresse': adresse,
|
||||
'guthaben_btc': scan_result.get('total_amount', 0),
|
||||
'anzahl_utxos': len(scan_result.get('unspents', [])),
|
||||
# NEU: Wir schicken die komplette Liste der Münzen mit!
|
||||
'utxos': scan_result.get('unspents', [])
|
||||
'guthaben_btc': confirmed / 1e8,
|
||||
'unconfirmed_btc': unconfirmed / 1e8,
|
||||
'guthaben_sat': confirmed,
|
||||
'unconfirmed_sat': unconfirmed,
|
||||
'anzahl_utxos': len(utxos),
|
||||
'utxos': [
|
||||
{
|
||||
'tx_hash': u['tx_hash'],
|
||||
'tx_pos': u['tx_pos'],
|
||||
'value': u['value'], # Satoshi
|
||||
'height': u['height']
|
||||
}
|
||||
for u in utxos
|
||||
]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
# ==========================================
|
||||
# NEU: ENDPUNKT 5: Transaktion senden (POST)
|
||||
# ENDPUNKT 5: Transaktion senden (POST)
|
||||
# ==========================================
|
||||
@app.route('/api/sendtx', methods=['POST'])
|
||||
def send_tx():
|
||||
try:
|
||||
# Wir erwarten ein JSON-Body mit dem Inhalt: {"hex": "01000000..."}
|
||||
daten = request.get_json()
|
||||
|
||||
if not daten or 'hex' not in daten:
|
||||
return jsonify({'status': 'error', 'message': 'Kein gueltiger Hex-String uebergeben. Sende ein JSON mit dem Key "hex".'}), 400
|
||||
return jsonify({'status': 'error', 'message': 'Kein gueltiger Hex-String uebergeben.'}), 400
|
||||
|
||||
raw_tx_hex = daten['hex']
|
||||
node = get_node()
|
||||
|
||||
# Sende die Transaktion an den Node, der sie ins Netzwerk broadcastet
|
||||
txid = node.sendrawtransaction(raw_tx_hex)
|
||||
|
||||
return jsonify({
|
||||
|
|
@ -115,5 +180,4 @@ def send_tx():
|
|||
# ==========================================
|
||||
if __name__ == '__main__':
|
||||
print("🚀 API Server startet...")
|
||||
# host='0.0.0.0' bedeutet, dass die API von anderen Geräten im Heimnetzwerk erreichbar ist!
|
||||
app.run(host='0.0.0.0', port=5001)
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@ Flask==3.0.2
|
|||
python-dotenv==1.0.1
|
||||
bitcoinlib==0.6.15
|
||||
requests==2.31.0
|
||||
base58
|
||||
bech32
|
||||
Loading…
Reference in a new issue