Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature add regtest #10

Merged
merged 2 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bitvmx_protocol_library/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ class BitcoinNetwork(Enum):
MUTINYNET = "mutinynet"
TESTNET = "testnet"
MAINNET = "mainnet"
REGTEST = "regtest"
165 changes: 165 additions & 0 deletions blockchain_query_services/services/bitcoin_rpc_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import json
from typing import Tuple

import requests
from bitcoinutils.constants import SATOSHIS_PER_BITCOIN
from bitcoinutils.transactions import Transaction
from dependency_injector import containers, providers

from blockchain_query_services.entities.transaction_info_service.transaction_info_bo import (
TransactionInfoBO,
)
from blockchain_query_services.entities.transaction_info_service.transaction_input_bo import (
TransactionInputBO,
)
from blockchain_query_services.entities.transaction_info_service.transaction_output_bo import (
TransactionOutputBO,
)


class BitcoinRPCClient:
def __init__(self, rpc_user, rpc_password, rpc_host="host.docker.internal", rpc_port=8443):
"""
Initialize Bitcoin RPC client for regtest

:param rpc_user: RPC username
:param rpc_password: RPC password
:param rpc_host: RPC host (default localhost)
:param rpc_port: RPC port for regtest (default 18443)
"""
self.rpc_url = f"http://{rpc_host}:{rpc_port}/"
self.auth = (rpc_user, rpc_password)
self.address = None
try:
self._call_rpc("getwalletinfo", [])
except Exception as e:
if "No wallet is loaded" in e.args[0]:
self._call_rpc("createwallet", ["testwallet"])
else:
raise e

def _call_rpc(self, method, params=None):
"""
Make an RPC call to the Bitcoin node

:param method: RPC method name
:param params: List of parameters
:return: RPC response
"""
payload = {"jsonrpc": "1.0", "id": "curltest", "method": method, "params": params or []}

response = requests.post(
self.rpc_url,
auth=self.auth,
data=json.dumps(payload),
headers={"Content-Type": "text/plain"},
)

if response.status_code != 200:
raise Exception(f"RPC Error: {response.text}")

return response.json()["result"]

def get_new_address(self):
"""
Generate a new Bitcoin address

:return: New Bitcoin address
"""
if self.address is None:
new_address = self._call_rpc("getnewaddress")
self.address = new_address
return new_address
else:
return self.address

def generate_blocks(self, num_blocks):
"""
Mine specified number of blocks to generate funds

:param num_blocks: Number of blocks to mine
:return: List of block hashes
"""
return self._call_rpc("generatetoaddress", [num_blocks, self.get_new_address()])

def check_transaction_confirmed(self, tx_id: str):
try:
self._call_rpc("gettxoutproof", [[tx_id]])
return True
except Exception as e:
if "Transaction not yet in block" in e.args[0]:
return False
raise e

def get_tx_info(self, tx_id: str) -> TransactionInfoBO:
tx_info_hex = self._call_rpc("getrawtransaction", [tx_id])
decoded_transaction = self._call_rpc("decoderawtransaction", [tx_info_hex])
inputs = list(
map(
lambda x: TransactionInputBO(
tx_id=x["txid"],
index=x["vout"],
witness=x["txinwitness"],
),
decoded_transaction["vin"],
)
)
outputs = list(
map(
lambda x: TransactionOutputBO(
index=x["n"],
value=int(x["value"] * SATOSHIS_PER_BITCOIN),
address=x["scriptPubKey"]["address"],
),
decoded_transaction["vout"],
)
)
return TransactionInfoBO(
confirmed=self.check_transaction_confirmed(tx_id=tx_id),
tx_id=tx_id,
inputs=inputs,
outputs=outputs,
)

def send_raw_transaction(self, raw_tx: str):
self._call_rpc("sendrawtransaction", [raw_tx])

def get_utxo_index(self, amount: float, tx_id: str) -> int:
raw_transaction = self._call_rpc("getrawtransaction", [tx_id])
tx = Transaction.from_raw(rawtxhex=raw_transaction)
amount_of_satoshis = amount * SATOSHIS_PER_BITCOIN
for i in range(len(tx.outputs)):
if tx.outputs[i].amount == amount_of_satoshis:
return i
raise Exception("Amount not found")

def send_to_address(self, address: str, amount: float) -> Tuple[str, int]:
"""
Send funds to a specific address

:param address: Destination Bitcoin address
:param amount: Amount in BTC to send
:return: Transaction ID, index of tx sending the funds
"""
tx_id = None
while tx_id is None:
try:
tx_id = self._call_rpc("sendtoaddress", [address, amount])
except Exception as e:
if "Insufficient funds" not in e.args[0]:
raise e
else:
self.generate_blocks(num_blocks=1)
index = self.get_utxo_index(amount=amount, tx_id=tx_id)
return tx_id, index


class BitcoinRPCClients(containers.DeclarativeContainer):

regtest = providers.Factory(
BitcoinRPCClient,
rpc_user="myuser",
rpc_password="SomeDecentp4ssw0rd",
rpc_host="host.docker.internal",
rpc_port=8443,
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,21 @@
from blockchain_query_services.services.mutinynet_api.broadcast_transaction_service import (
BroadcastTransactionService as BroadcastTransactionServiceMutinynet,
)
from blockchain_query_services.services.mutinynet_api.faucet_service import (
FaucetService as FaucetServiceMutinynet,
)
from blockchain_query_services.services.mutinynet_api.transaction_info_service import (
TransactionInfoService as TransactionInfoServiceMutinynet,
)
from blockchain_query_services.services.regtest_api.broadcast_transaction_service import (
BroadcastTransactionService as BroadcastTransactionServiceRegtest,
)
from blockchain_query_services.services.regtest_api.faucet_service import (
FaucetService as FaucetServiceRegtest,
)
from blockchain_query_services.services.regtest_api.transaction_info_service import (
TransactionInfoService as TransactionInfoServiceRegtest,
)
from blockchain_query_services.services.testnet_api.broadcast_transaction_service import (
BroadcastTransactionService as BroadcastTransactionServiceTestnet,
)
Expand All @@ -27,20 +39,32 @@ class BroadcastTransactionServices(containers.DeclarativeContainer):
mutinynet = providers.Singleton(BroadcastTransactionServiceMutinynet)
testnet = providers.Singleton(BroadcastTransactionServiceTestnet)
mainnet = providers.Singleton(BroadcastTransactionServiceMainnet)
regtest = providers.Singleton(BroadcastTransactionServiceRegtest)


class TransactionInfoServices(containers.DeclarativeContainer):
mutinynet = providers.Singleton(TransactionInfoServiceMutinynet)
testnet = providers.Singleton(TransactionInfoServiceTestnet)
mainnet = providers.Singleton(TransactionInfoServiceMainnet)
regtest = providers.Singleton(TransactionInfoServiceRegtest)


class FaucetServices(containers.DeclarativeContainer):
mutinynet = providers.Singleton(FaucetServiceMutinynet)
regtest = providers.Singleton(FaucetServiceRegtest)


if common_protocol_properties.network == BitcoinNetwork.MUTINYNET:
broadcast_transaction_service = BroadcastTransactionServices.mutinynet()
transaction_info_service = TransactionInfoServices.mutinynet()
faucet_service = FaucetServices.mutinynet()
elif common_protocol_properties.network == BitcoinNetwork.TESTNET:
broadcast_transaction_service = BroadcastTransactionServices.testnet()
transaction_info_service = TransactionInfoServices.testnet()
elif common_protocol_properties.network == BitcoinNetwork.MAINNET:
broadcast_transaction_service = BroadcastTransactionServices.mainnet()
transaction_info_service = TransactionInfoServices.mainnet()
elif common_protocol_properties.network == BitcoinNetwork.REGTEST:
broadcast_transaction_service = BroadcastTransactionServices.regtest()
transaction_info_service = TransactionInfoServices.regtest()
faucet_service = FaucetServices.regtest()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

from blockchain_query_services.services.bitcoin_rpc_services import BitcoinRPCClients


class BroadcastTransactionService:

def __init__(self):
self.bitcoin_rpc_client = BitcoinRPCClients.regtest()

def __call__(self, transaction: str):
self.bitcoin_rpc_client.send_raw_transaction(raw_tx=transaction)
self.bitcoin_rpc_client.generate_blocks(num_blocks=1)
32 changes: 32 additions & 0 deletions blockchain_query_services/services/regtest_api/faucet_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Optional

from bitcoinutils.constants import SATOSHIS_PER_BITCOIN

from blockchain_query_services.services.bitcoin_rpc_services import BitcoinRPCClients
from blockchain_query_services.services.interfaces.faucet_service import FaucetServiceInterface


class FaucetService(FaucetServiceInterface):
def __init__(self):
self.bitcoin_rpc_client = BitcoinRPCClients.regtest()

def __call__(
self,
amount: Optional[int] = 1000000,
destination_address: Optional[str] = None,
):
if destination_address is None:
raise Exception("For regtest the faucet's destination address is mandatory")

# Destination address (replace with actual address)
destination_address = destination_address

# Amount to send
send_amount = amount / float(SATOSHIS_PER_BITCOIN)

try:
# Send funds
return self.bitcoin_rpc_client.send_to_address(destination_address, send_amount)
except Exception as e:
print(f"Error sending funds: {e}")
raise e
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from blockchain_query_services.entities.transaction_info_service.transaction_info_bo import (
TransactionInfoBO,
)
from blockchain_query_services.services.bitcoin_rpc_services import BitcoinRPCClients


class TransactionInfoService:

def __init__(self):
self.bitcoin_rpc_client = BitcoinRPCClients.regtest()

def __call__(self, tx_id: str) -> TransactionInfoBO:
return self.bitcoin_rpc_client.get_tx_info(tx_id=tx_id)
18 changes: 18 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ services:
networks:
- bitvmx-net

bitcoin-regtest-node:
build:
context: .
target: kylemanna/bitcoind
image: kylemanna/bitcoind
ports:
- "8443:8443"
networks:
- bitvmx-net
volumes:
- type: bind
source: regtestbitcoin.conf
target: /bitcoin/.bitcoin/bitcoin.conf
# volumes:
# - type: bind
# source: ./bitcoin_data
# target: /root/.bitcoin

format:
build:
context: .
Expand Down
5 changes: 4 additions & 1 deletion prover_app/api/v1/fund/crud/v1/view_controllers/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ def __init__(self, faucet_service: FaucetServiceInterface):
self.faucet_service = faucet_service

async def __call__(self, fund_post_view_input: FundPostV1Input) -> FundPostV1Output:
if common_protocol_properties.network == BitcoinNetwork.MUTINYNET:
if (
not common_protocol_properties.network == BitcoinNetwork.MUTINYNET
and not common_protocol_properties.network == BitcoinNetwork.REGTEST
):
raise HTTPException(
status_code=404,
detail="Endpoint not available for network "
Expand Down
5 changes: 4 additions & 1 deletion prover_app/api/v1/setup/fund/v1/view_controllers/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ def __init__(

async def __call__(self, setup_post_view_input: SetupFundPostV1Input) -> SetupFundPostV1Output:
# sha_256_bitcoin_script = BitcoinScript.from_int_list(script_list=pybitvmbinding.sha_256_script(int(64 / 2)))
if not self.common_protocol_properties.network == BitcoinNetwork.MUTINYNET:
if (
not self.common_protocol_properties.network == BitcoinNetwork.MUTINYNET
and not self.common_protocol_properties.network == BitcoinNetwork.REGTEST
):
raise HTTPException(
status_code=404,
detail="Endpoint not available for network "
Expand Down
6 changes: 4 additions & 2 deletions prover_app/dependency_injection/api/v1/fund.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from dependency_injector import containers, providers

from blockchain_query_services.services.mutinynet_api.faucet_service import FaucetService
from blockchain_query_services.services.blockchain_query_services_dependency_injection import (
faucet_service,
)
from prover_app.api.v1.fund.crud.v1.view_controllers.post import FundPostViewControllerV1


class FundPostViewControllers(containers.DeclarativeContainer):
v1 = providers.Singleton(
FundPostViewControllerV1,
faucet_service=FaucetService(),
faucet_service=faucet_service,
)
4 changes: 2 additions & 2 deletions prover_app/dependency_injection/domain/create_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
)
from blockchain_query_services.services.blockchain_query_services_dependency_injection import (
broadcast_transaction_service,
faucet_service,
transaction_info_service,
)
from blockchain_query_services.services.mutinynet_api.faucet_service import FaucetService
from prover_app.dependency_injection.persistences.bitvmx_protocol_prover_dto_persistences import (
BitVMXProtocolProverDTOPersistences,
)
Expand All @@ -38,7 +38,7 @@ class CreateSetupControllers(containers.DeclarativeContainer):
broadcast_transaction_service=broadcast_transaction_service,
transaction_info_service=transaction_info_service,
transaction_generator_from_public_keys_service=TransactionGeneratorFromPublicKeysService(),
faucet_service=FaucetService(),
faucet_service=faucet_service,
bitvmx_bitcoin_scripts_generator_service=BitVMXBitcoinScriptsGeneratorService(),
generate_prover_public_keys_service_class=GenerateProverPublicKeysService,
verify_verifier_signatures_service_class=VerifyVerifierSignaturesService,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from dependency_injector import containers, providers

from bitvmx_protocol_library.config import common_protocol_properties
from blockchain_query_services.services.mutinynet_api.faucet_service import FaucetService
from blockchain_query_services.services.blockchain_query_services_dependency_injection import (
faucet_service,
)
from prover_app.config import protocol_properties
from prover_app.domain.controllers.v1.setup.create_setup_with_funding_controller import (
CreateSetupWithFundingController,
Expand All @@ -11,7 +13,7 @@
class CreateSetupWithFundingControllers(containers.DeclarativeContainer):
bitvmx_protocol = providers.Singleton(
CreateSetupWithFundingController,
faucet_service=FaucetService(),
faucet_service=faucet_service,
common_protocol_properties=common_protocol_properties,
protocol_properties=protocol_properties,
)
Loading
Loading