Skip to content

Commit

Permalink
Bugfix: autodetect from bitcoin.conf file with network set (#2037)
Browse files Browse the repository at this point in the history
* autodetect from bitcoin.conf file with network set

* added a test

* forgot testfile

* make black happy

Co-authored-by: Kim Neunert <k9ert@gmx.de>
  • Loading branch information
Kexkey and k9ert authored Jan 11, 2023
1 parent cb85062 commit 2a03ec1
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 27 deletions.
75 changes: 49 additions & 26 deletions src/cryptoadvance/specter/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _get_rpcconfig(datadir=get_default_datadir()):
"""
config = {
"bitcoin.conf": {"default": {}, "main": {}, "test": {}, "regtest": {}},
"cookies": [],
"cookies": {},
}
if not os.path.isdir(datadir): # we don't know where to search for files
return config
Expand Down Expand Up @@ -89,46 +89,71 @@ def _get_rpcconfig(datadir=get_default_datadir()):
for chain in folders:
fname = os.path.join(datadir, folders[chain], ".cookie")
if os.path.exists(fname):
logger.debug(f"Found a cookie file for chain {chain}")
try:
with open(fname, "r") as f:
content = f.read()
user, password = content.split(":")
obj = {"user": user, "password": password, "port": RPC_PORTS[chain]}
config["cookies"].append(obj)
config["cookies"][chain] = obj
except Exception as e:
handle_exception(e)
print("Can't open %s file" % fname)
return config


def _detect_rpc_confs_via_datadir(config=None, datadir=get_default_datadir()):
"""returns the bitcoin.conf configuration for the network
specified in bitcoin.conf with testnet=1, regtest=1, etc. as
well as the network's auth cookie information.
"""

confs = []
conf = {}
networks = []
selected_network = "main"

if config is None:
config = _get_rpcconfig(datadir=datadir)
confs = []
default = {}
for network in config["bitcoin.conf"]:

if "default" in config["bitcoin.conf"]:
default = config["bitcoin.conf"]["default"]
networks.append("default")
if "regtest" in default and default["regtest"] == "1":
selected_network = "regtest"
elif "testnet" in default and default["testnet"] == "1":
selected_network = "test"
elif "signet" in default and default["signet"] == "1":
selected_network = "signet"

logger.debug(f"Bitcoin network set to {selected_network}")

# Network specific options take precedence over default ones,
# as per https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md#network-specific-options
networks.append(selected_network)

for network in networks:
if "rpcuser" in config["bitcoin.conf"][network]:
default["user"] = config["bitcoin.conf"][network]["rpcuser"]
conf["user"] = config["bitcoin.conf"][network]["rpcuser"]
if "rpcpassword" in config["bitcoin.conf"][network]:
default["password"] = config["bitcoin.conf"][network]["rpcpassword"]
conf["password"] = config["bitcoin.conf"][network]["rpcpassword"]
if "rpcconnect" in config["bitcoin.conf"][network]:
default["host"] = config["bitcoin.conf"][network]["rpcconnect"]
conf["host"] = config["bitcoin.conf"][network]["rpcconnect"]
if "rpcport" in config["bitcoin.conf"][network]:
default["port"] = int(config["bitcoin.conf"][network]["rpcport"])
if "user" in default and "password" in default:
if (
"port" not in config["bitcoin.conf"]["default"]
): # only one rpc makes sense in this case
if network == "default":
continue
default["port"] = RPC_PORTS[network]
confs.append(default.copy())
# try cookies now
for cookie in config["cookies"]:
conf["port"] = int(config["bitcoin.conf"][network]["rpcport"])
if conf:
confs.append(conf)

# Check for cookies as auth fallback, rpcpassword in bitcoin.conf takes precedence
# as per https://github.com/bitcoin/bitcoin/blob/master/doc/init.md#configuration
# Only take the selected network cookie info
if "cookies" in config and selected_network in config["cookies"]:
cookie = config["cookies"][selected_network]
o = {}
o.update(default)
o.update(conf)
o.update(cookie)
confs.append(o)

return confs


Expand Down Expand Up @@ -178,12 +203,7 @@ def autodetect_rpc_confs(
available_conf_arr = []
if len(conf_arr) > 0:
for conf in conf_arr:
rpc = BitcoinRPC(
**conf, proxy_url="socks5h://localhost:9050", only_tor=False
)
if port is not None:
if int(rpc.port) != port:
continue
rpc = BitcoinRPC(**conf, proxy_url=proxy_url, only_tor=only_tor)
try:
rpc.getmininginfo()
available_conf_arr.append(conf)
Expand All @@ -195,6 +215,9 @@ def autodetect_rpc_confs(
pass
except RpcError:
pass
except BrokenCoreConnectionException:
# If conf's auth doesn't work, let's try cookie's auth if found
pass
# have to make a list of acceptable exception unfortunately
# please enlarge if you find new ones
return available_conf_arr
Expand Down
7 changes: 7 additions & 0 deletions tests/misc_testdata/bitcoin.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
regtest=1
rpcconnect=bitcoin
main.rpcport=8332
test.rpcport=18332
regtest.rpcport=18443
rpcuser=bitcoin
rpcpassword=CHANGEME
50 changes: 49 additions & 1 deletion tests/test_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
import pytest
import requests
from requests import Response
from cryptoadvance.specter.rpc import BitcoinRPC, RpcError
from cryptoadvance.specter.rpc import (
BitcoinRPC,
RpcError,
_detect_rpc_confs_via_datadir,
_get_rpcconfig,
)
from cryptoadvance.specter.specter_error import SpecterError

# To investigate the Bitcoin API, here are some great resources:
Expand All @@ -24,6 +29,49 @@ def __init__(self, status_code, json, headers):
self.encoding = None


def test_get_rpcconfig(empty_data_folder):
c = _get_rpcconfig(empty_data_folder)
assert c["bitcoin.conf"]["default"] == {}
assert c["bitcoin.conf"]["main"] == {}
assert c["bitcoin.conf"]["regtest"] == {}
assert c["bitcoin.conf"]["test"] == {}
c = _get_rpcconfig("./tests/misc_testdata")
# Looks like this:
# regtest=1
# rpcconnect=bitcoin
# main.rpcport=8332
# test.rpcport=18332
# regtest.rpcport=18443
# rpcuser=bitcoin
# rpcpassword=CHANGEME
assert c["bitcoin.conf"] == {
"default": {
"regtest": "1",
"rpcconnect": "bitcoin",
"rpcpassword": "CHANGEME",
"rpcuser": "bitcoin",
},
"main": {"rpcport": "8332"},
"regtest": {"rpcport": "18443"},
"test": {"rpcport": "18332"},
}


def test_detect_rpc_confs_via_datadir(empty_data_folder):
c = _detect_rpc_confs_via_datadir(datadir="./tests/misc_testdata")
# Looks like this:
# regtest=1
# rpcconnect=bitcoin
# main.rpcport=8332
# test.rpcport=18332
# regtest.rpcport=18443
# rpcuser=bitcoin
# rpcpassword=CHANGEME
assert c == [
{"host": "bitcoin", "password": "CHANGEME", "port": 18443, "user": "bitcoin"}
]


def test_RpcError_response(caplog):
caplog.set_level(logging.DEBUG)
# Creating an RpcError with a Response object
Expand Down

0 comments on commit 2a03ec1

Please sign in to comment.