From 73769a9492e7be12858aa956492fe183869ca80c Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Fri, 18 Jun 2021 20:10:11 +0200 Subject: [PATCH 1/5] move wallet creation logic to wallet class --- .../specter/managers/wallet_manager.py | 102 +---------- src/cryptoadvance/specter/wallet.py | 165 +++++++++++++++++- 2 files changed, 172 insertions(+), 95 deletions(-) diff --git a/src/cryptoadvance/specter/managers/wallet_manager.py b/src/cryptoadvance/specter/managers/wallet_manager.py index 7849f712b4..504be38e82 100644 --- a/src/cryptoadvance/specter/managers/wallet_manager.py +++ b/src/cryptoadvance/specter/managers/wallet_manager.py @@ -34,27 +34,6 @@ logger = logging.getLogger(__name__) -purposes = OrderedDict( - { - None: "General", - "wpkh": "Single (Segwit)", - "sh-wpkh": "Single (Nested)", - "pkh": "Single (Legacy)", - "wsh": "Multisig (Segwit)", - "sh-wsh": "Multisig (Nested)", - "sh": "Multisig (Legacy)", - } -) - -addrtypes = { - "pkh": "legacy", - "sh-wpkh": "p2sh-segwit", - "wpkh": "bech32", - "sh": "legacy", - "sh-wsh": "p2sh-segwit", - "wsh": "bech32", -} - class WalletManager: # chain is required to manage wallets when bitcoind is not running @@ -322,82 +301,19 @@ def create_wallet(self, name, sigs_required, key_type, keys, devices): wallet_alias = alias("%s %d" % (name, i)) i += 1 - arr = key_type.split("-") - descs = [key.metadata["combined"] for key in keys] - recv_descs = ["%s/0/*" % desc for desc in descs] - change_descs = ["%s/1/*" % desc for desc in descs] - if len(keys) > 1: - recv_descriptor = "sortedmulti({},{})".format( - sigs_required, ",".join(recv_descs) - ) - change_descriptor = "sortedmulti({},{})".format( - sigs_required, ",".join(change_descs) - ) - else: - recv_descriptor = recv_descs[0] - change_descriptor = change_descs[0] - for el in arr[::-1]: - recv_descriptor = "%s(%s)" % (el, recv_descriptor) - change_descriptor = "%s(%s)" % (el, change_descriptor) - - if is_liquid(self.chain): - blinding_key = None - if len(devices) == 1: - blinding_key = devices[0].blinding_key - if not blinding_key: - desc = Descriptor.from_string(recv_descriptor.split("#")[0]) - # For now we use sha256(b"blinding_key", xor(chaincodes)) as a blinding key - # where chaincodes are corresponding to xpub of the first receiving address - xor = bytearray(32) - desc_keys = desc.derive(0).keys - for k in desc_keys: - if k.is_extended: - chaincode = k.key.chain_code - for i in range(32): - xor[i] = xor[i] ^ chaincode[i] - secret = hashlib.sha256(b"blinding_key" + bytes(xor)).digest() - blinding_key = ec.PrivateKey(secret).wif() - if blinding_key: - recv_descriptor = f"blinded(slip77({blinding_key}),{recv_descriptor})" - change_descriptor = ( - f"blinded(slip77({blinding_key}),{change_descriptor})" - ) - - recv_descriptor = AddChecksum(recv_descriptor) - change_descriptor = AddChecksum(change_descriptor) - - # v20.99 is pre-v21 Elements Core for descriptors - if self.bitcoin_core_version_raw >= 209900: - # Use descriptor wallet - self.rpc.createwallet( - os.path.join(self.rpc_path, wallet_alias), True, True, "", False, True - ) - else: - self.rpc.createwallet(os.path.join(self.rpc_path, wallet_alias), True) - - w = self.WalletClass( + w = self.WalletClass.create( + self.rpc, + self.rpc_path, + self.working_folder, + self.device_manager, + self, name, wallet_alias, - "{} of {} {}".format(sigs_required, len(keys), purposes[key_type]) - if len(keys) > 1 - else purposes[key_type], - addrtypes[key_type], - "", - -1, - "", - -1, - 0, - 0, - recv_descriptor, - change_descriptor, + sigs_required, + key_type, keys, devices, - sigs_required, - {}, - [], - os.path.join(self.working_folder, "%s.json" % wallet_alias), - self.device_manager, - self, + self.bitcoin_core_version_raw, ) # save wallet file to disk if w and self.working_folder is not None: diff --git a/src/cryptoadvance/specter/wallet.py b/src/cryptoadvance/specter/wallet.py index d1b7a11aad..70cf847cc2 100644 --- a/src/cryptoadvance/specter/wallet.py +++ b/src/cryptoadvance/specter/wallet.py @@ -1,10 +1,11 @@ import copy, hashlib, json, logging, os, re import time +from collections import OrderedDict from .device import Device from .key import Key from .util.merkleblock import is_valid_merkle_proof -from .helpers import der_to_bytes, get_address_from_dict -from embit import base58, bip32 +from .helpers import der_to_bytes, get_address_from_dict, is_liquid +from embit import base58, bip32, ec from .util.descriptor import Descriptor, sort_descriptor, AddChecksum from embit.liquid.descriptor import LDescriptor from embit.descriptor.checksum import add_checksum @@ -26,6 +27,27 @@ logger = logging.getLogger(__name__) LISTTRANSACTIONS_BATCH_SIZE = 1000 +purposes = OrderedDict( + { + None: "General", + "wpkh": "Single (Segwit)", + "sh-wpkh": "Single (Nested)", + "pkh": "Single (Legacy)", + "wsh": "Multisig (Segwit)", + "sh-wsh": "Multisig (Nested)", + "sh": "Multisig (Legacy)", + } +) + +addrtypes = { + "pkh": "legacy", + "sh-wpkh": "p2sh-segwit", + "wpkh": "bech32", + "sh": "legacy", + "sh-wsh": "p2sh-segwit", + "wsh": "bech32", +} + class Wallet: # if the wallet is old we import 300 addresses @@ -113,6 +135,145 @@ def __init__( if old_format_detected or self.last_block != last_block: self.save_to_file() + @classmethod + def create( + cls, + rpc, + rpc_path, + working_folder, + device_manager, + wallet_manager, + name, + alias, + sigs_required, + key_type, + keys, + devices, + core_version=None, + ): + """Creates a wallet. If core_version is not specified - get it from rpc""" + # get xpubs in a form [fgp/der]xpub from all keys + xpubs = [key.metadata["combined"] for key in keys] + recv_keys = ["%s/0/*" % xpub for xpub in xpubs] + change_keys = ["%s/1/*" % xpub for xpub in xpubs] + is_multisig = len(keys) > 1 + # we start by constructing an argument for descriptor wrappers + if is_multisig: + recv_descriptor = "sortedmulti({},{})".format( + sigs_required, ",".join(recv_keys) + ) + change_descriptor = "sortedmulti({},{})".format( + sigs_required, ",".join(change_keys) + ) + else: + recv_descriptor = recv_keys[0] + change_descriptor = change_keys[0] + # now we iterate over script-type in reverse order + # to get sh(wpkh(xpub)) from sh-wpkh and xpub + arr = key_type.split("-") + for el in arr[::-1]: + recv_descriptor = "%s(%s)" % (el, recv_descriptor) + change_descriptor = "%s(%s)" % (el, change_descriptor) + + if is_liquid(wallet_manager.chain): + blinding_key = None + if len(devices) == 1: + blinding_key = devices[0].blinding_key + if not blinding_key: + desc = LDescriptor.from_string(recv_descriptor) + # For now we use sha256(b"blinding_key", xor(chaincodes)) as a blinding key + # where chaincodes are corresponding to xpub of the first receiving address. + # It's not a standard but we use that until musig(blinding_xpubs) is implemented + xor = bytearray(32) + desc_keys = desc.derive(0).keys + for k in desc_keys: + if k.is_extended: + chaincode = k.key.chain_code + for i in range(32): + xor[i] = xor[i] ^ chaincode[i] + secret = hashlib.sha256(b"blinding_key" + bytes(xor)).digest() + blinding_key = ec.PrivateKey(secret).wif() + if blinding_key: + recv_descriptor = f"blinded(slip77({blinding_key}),{recv_descriptor})" + change_descriptor = ( + f"blinded(slip77({blinding_key}),{change_descriptor})" + ) + + recv_descriptor = AddChecksum(recv_descriptor) + change_descriptor = AddChecksum(change_descriptor) + assert recv_descriptor != change_descriptor + + use_descriptors = core_version >= 209900 + # v20.99 is pre-v21 Elements Core for descriptors + if use_descriptors: + # Use descriptor wallet + rpc.createwallet(os.path.join(rpc_path, alias), True, True, "", False, True) + else: + rpc.createwallet(os.path.join(rpc_path, alias), True) + + wallet_rpc = rpc.wallet(os.path.join(rpc_path, alias)) + # import descriptors + args = [ + { + "desc": desc, + "internal": change, + "timestamp": "now", + "watchonly": True, + } + for (change, desc) in [(False, recv_descriptor), (True, change_descriptor)] + ] + for arg in args: + if use_descriptors: + arg["active"] = True + else: + arg["keypool"] = True + arg["range"] = [0, cls.GAP_LIMIT] + + assert args[0] != args[1] + + # Descriptor wallets were introduced in v0.21.0, but upgraded nodes may + # still have legacy wallets. Use getwalletinfo to check the wallet type. + # The "keypool" for descriptor wallets is automatically refilled + if not is_multisig: + if use_descriptors: + res = wallet_rpc.importdescriptors(args) + else: + res = wallet_rpc.importmulti(args, {"rescan": False}) + # bip67 requires sorted public keys for multisig addresses + else: + if use_descriptors: + res = wallet_rpc.importdescriptors(args) + else: + # try if sortedmulti is supported + res = wallet_rpc.importmulti(args, {"rescan": False}) + + assert all([r["success"] for r in res]) + + return cls( + name, + alias, + "{} of {} {}".format(sigs_required, len(keys), purposes[key_type]) + if len(keys) > 1 + else purposes[key_type], + addrtypes[key_type], + "", + -1, + "", + -1, + 0, + 0, + recv_descriptor, + change_descriptor, + keys, + devices, + sigs_required, + {}, + [], + os.path.join(working_folder, "%s.json" % alias), + device_manager, + wallet_manager, + ) + def fetch_labels(self): """Load addresses and labels to self._addresses""" recv = [ From 0fb06a59bdfdd18a96ece4d9a9c7f4012aa6565d Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sun, 20 Jun 2021 07:30:26 +0200 Subject: [PATCH 2/5] separate liquid stuff to LWallet.create method, drop v19 support --- src/cryptoadvance/specter/liquid/wallet.py | 139 ++++++++++++++++++ .../specter/managers/wallet_manager.py | 2 +- src/cryptoadvance/specter/wallet.py | 89 ++--------- 3 files changed, 153 insertions(+), 77 deletions(-) diff --git a/src/cryptoadvance/specter/liquid/wallet.py b/src/cryptoadvance/specter/liquid/wallet.py index e0a1bbbf78..60b638cafa 100644 --- a/src/cryptoadvance/specter/liquid/wallet.py +++ b/src/cryptoadvance/specter/liquid/wallet.py @@ -7,6 +7,145 @@ class LWallet(Wallet): MIN_FEE_RATE = 0.1 + @classmethod + def create( + cls, + rpc, + rpc_path, + working_folder, + device_manager, + wallet_manager, + name, + alias, + sigs_required, + key_type, + keys, + devices, + core_version=None, + ): + """Creates a wallet. If core_version is not specified - get it from rpc""" + # get xpubs in a form [fgp/der]xpub from all keys + xpubs = [key.metadata["combined"] for key in keys] + recv_keys = ["%s/0/*" % xpub for xpub in xpubs] + change_keys = ["%s/1/*" % xpub for xpub in xpubs] + is_multisig = len(keys) > 1 + # we start by constructing an argument for descriptor wrappers + if is_multisig: + recv_descriptor = "sortedmulti({},{})".format( + sigs_required, ",".join(recv_keys) + ) + change_descriptor = "sortedmulti({},{})".format( + sigs_required, ",".join(change_keys) + ) + else: + recv_descriptor = recv_keys[0] + change_descriptor = change_keys[0] + # now we iterate over script-type in reverse order + # to get sh(wpkh(xpub)) from sh-wpkh and xpub + arr = key_type.split("-") + for el in arr[::-1]: + recv_descriptor = "%s(%s)" % (el, recv_descriptor) + change_descriptor = "%s(%s)" % (el, change_descriptor) + + # get blinding key for the wallet + blinding_key = None + if len(devices) == 1: + blinding_key = devices[0].blinding_key + # if we don't have slip77 key for a device or it is multisig + # we use chaincodes to generate slip77 key. + if not blinding_key: + desc = LDescriptor.from_string(recv_descriptor) + # For now we use sha256(b"blinding_key", xor(chaincodes)) as a blinding key + # where chaincodes are corresponding to xpub of the first receiving address. + # It's not a standard but we use that until musig(blinding_xpubs) is implemented. + # Chaincodes of the first address are not used anywhere else so they can be used + # as a source for the blinding keys. They are also independent of the xpub's origin. + xor = bytearray(32) + desc_keys = desc.derive(0).keys + for k in desc_keys: + if k.is_extended: + chaincode = k.key.chain_code + for i in range(32): + xor[i] = xor[i] ^ chaincode[i] + secret = hashlib.sha256(b"blinding_key" + bytes(xor)).digest() + blinding_key = ec.PrivateKey(secret).wif() + if blinding_key: + recv_descriptor = f"blinded(slip77({blinding_key}),{recv_descriptor})" + change_descriptor = ( + f"blinded(slip77({blinding_key}),{change_descriptor})" + ) + + recv_descriptor = AddChecksum(recv_descriptor) + change_descriptor = AddChecksum(change_descriptor) + assert recv_descriptor != change_descriptor + + # get Core version if we don't know it + if core_version is None: + core_version = rpc.getnetworkinfo().get("version", 0) + + use_descriptors = core_version >= 209900 + # v20.99 is pre-v21 Elements Core for descriptors + if use_descriptors: + # Use descriptor wallet + rpc.createwallet(os.path.join(rpc_path, alias), True, True, "", False, True) + else: + rpc.createwallet(os.path.join(rpc_path, alias), True) + + wallet_rpc = rpc.wallet(os.path.join(rpc_path, alias)) + # import descriptors + args = [ + { + "desc": desc, + "internal": change, + "timestamp": "now", + "watchonly": True, + } + for (change, desc) in [(False, recv_descriptor), (True, change_descriptor)] + ] + for arg in args: + if use_descriptors: + arg["active"] = True + else: + arg["keypool"] = True + arg["range"] = [0, cls.GAP_LIMIT] + + assert args[0] != args[1] + + # Descriptor wallets were introduced in v0.21.0, but upgraded nodes may + # still have legacy wallets. Use getwalletinfo to check the wallet type. + # The "keypool" for descriptor wallets is automatically refilled + if use_descriptors: + res = wallet_rpc.importdescriptors(args) + else: + res = wallet_rpc.importmulti(args, {"rescan": False}) + + assert all([r["success"] for r in res]) + + return cls( + name, + alias, + "{} of {} {}".format(sigs_required, len(keys), purposes[key_type]) + if len(keys) > 1 + else purposes[key_type], + addrtypes[key_type], + "", + -1, + "", + -1, + 0, + 0, + recv_descriptor, + change_descriptor, + keys, + devices, + sigs_required, + {}, + [], + os.path.join(working_folder, "%s.json" % alias), + device_manager, + wallet_manager, + ) + def fetch_transactions(self): return diff --git a/src/cryptoadvance/specter/managers/wallet_manager.py b/src/cryptoadvance/specter/managers/wallet_manager.py index 504be38e82..e152cd9bcc 100644 --- a/src/cryptoadvance/specter/managers/wallet_manager.py +++ b/src/cryptoadvance/specter/managers/wallet_manager.py @@ -14,7 +14,7 @@ from ..rpc import RpcError, get_default_datadir from ..specter_error import SpecterError from ..util.descriptor import AddChecksum -from ..wallet import Wallet +from ..wallet import Wallet, purposes from ..liquid.wallet import LWallet from embit import ec diff --git a/src/cryptoadvance/specter/wallet.py b/src/cryptoadvance/specter/wallet.py index 70cf847cc2..6277cd7cc8 100644 --- a/src/cryptoadvance/specter/wallet.py +++ b/src/cryptoadvance/specter/wallet.py @@ -175,36 +175,15 @@ def create( recv_descriptor = "%s(%s)" % (el, recv_descriptor) change_descriptor = "%s(%s)" % (el, change_descriptor) - if is_liquid(wallet_manager.chain): - blinding_key = None - if len(devices) == 1: - blinding_key = devices[0].blinding_key - if not blinding_key: - desc = LDescriptor.from_string(recv_descriptor) - # For now we use sha256(b"blinding_key", xor(chaincodes)) as a blinding key - # where chaincodes are corresponding to xpub of the first receiving address. - # It's not a standard but we use that until musig(blinding_xpubs) is implemented - xor = bytearray(32) - desc_keys = desc.derive(0).keys - for k in desc_keys: - if k.is_extended: - chaincode = k.key.chain_code - for i in range(32): - xor[i] = xor[i] ^ chaincode[i] - secret = hashlib.sha256(b"blinding_key" + bytes(xor)).digest() - blinding_key = ec.PrivateKey(secret).wif() - if blinding_key: - recv_descriptor = f"blinded(slip77({blinding_key}),{recv_descriptor})" - change_descriptor = ( - f"blinded(slip77({blinding_key}),{change_descriptor})" - ) - recv_descriptor = AddChecksum(recv_descriptor) change_descriptor = AddChecksum(change_descriptor) assert recv_descriptor != change_descriptor - use_descriptors = core_version >= 209900 - # v20.99 is pre-v21 Elements Core for descriptors + # get Core version if we don't know it + if core_version is None: + core_version = rpc.getnetworkinfo().get("version", 0) + + use_descriptors = core_version >= 210000 if use_descriptors: # Use descriptor wallet rpc.createwallet(os.path.join(rpc_path, alias), True, True, "", False, True) @@ -234,18 +213,10 @@ def create( # Descriptor wallets were introduced in v0.21.0, but upgraded nodes may # still have legacy wallets. Use getwalletinfo to check the wallet type. # The "keypool" for descriptor wallets is automatically refilled - if not is_multisig: - if use_descriptors: - res = wallet_rpc.importdescriptors(args) - else: - res = wallet_rpc.importmulti(args, {"rescan": False}) - # bip67 requires sorted public keys for multisig addresses + if use_descriptors: + res = wallet_rpc.importdescriptors(args) else: - if use_descriptors: - res = wallet_rpc.importdescriptors(args) - else: - # try if sortedmulti is supported - res = wallet_rpc.importmulti(args, {"rescan": False}) + res = wallet_rpc.importmulti(args, {"rescan": False}) assert all([r["success"] for r in res]) @@ -1147,7 +1118,7 @@ def get_address(self, index, change=False, check_keypool=True): self.keypoolrefill(pool, index + self.GAP_LIMIT, change=change) desc = self.change_descriptor if change else self.recv_descriptor return ( - LDescriptor.from_string(desc.split("#")[0]) + LDescriptor.from_string(desc) .derive(index) .address(get_network(self.manager.chain)) ) @@ -1329,48 +1300,14 @@ def keypoolrefill(self, start, end=None, change=False): # Descriptor wallets were introduced in v0.21.0, but upgraded nodes may # still have legacy wallets. Use getwalletinfo to check the wallet type. # The "keypool" for descriptor wallets is automatically refilled - if (not self.use_descriptors) or start == 0: - if not self.is_multisig: - if self.use_descriptors: - r = self.rpc.importdescriptors(args) - else: - r = self.rpc.importmulti(args, {"rescan": False}) - # bip67 requires sorted public keys for multisig addresses - else: - if self.use_descriptors: - self.rpc.importdescriptors(args) - else: - # try if sortedmulti is supported - r = self.rpc.importmulti(args, {"rescan": False}) - # doesn't raise, but instead returns "success": False - if not r[0]["success"]: - # first import normal multi - # remove checksum - desc = desc.split("#")[0] - # switch to multi - desc = desc.replace("sortedmulti", "multi") - # add checksum - desc = AddChecksum(desc) - # update descriptor - args[0]["desc"] = desc - r = self.rpc.importmulti(args, {"rescan": False}) - # make a batch of single addresses to import - arg = args[0] - # remove range key - arg.pop("range") - batch = [] - for i in range(start, end): - sorted_desc = sort_descriptor(desc, index=i) - # create fresh object - obj = {} - obj.update(arg) - obj.update({"desc": sorted_desc}) - batch.append(obj) - r = self.rpc.importmulti(batch, {"rescan": False}) + if not self.use_descriptors: + r = self.rpc.importmulti(args, {"rescan": False}) + if change: self.change_keypool = end else: self.keypool = end + self.save_to_file() return end From 74fd3e4c1339b9ed2b07e8da04d38404ae398e86 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sun, 20 Jun 2021 07:37:01 +0200 Subject: [PATCH 3/5] black --- src/cryptoadvance/specter/liquid/wallet.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cryptoadvance/specter/liquid/wallet.py b/src/cryptoadvance/specter/liquid/wallet.py index 60b638cafa..35050fb42b 100644 --- a/src/cryptoadvance/specter/liquid/wallet.py +++ b/src/cryptoadvance/specter/liquid/wallet.py @@ -71,9 +71,7 @@ def create( blinding_key = ec.PrivateKey(secret).wif() if blinding_key: recv_descriptor = f"blinded(slip77({blinding_key}),{recv_descriptor})" - change_descriptor = ( - f"blinded(slip77({blinding_key}),{change_descriptor})" - ) + change_descriptor = f"blinded(slip77({blinding_key}),{change_descriptor})" recv_descriptor = AddChecksum(recv_descriptor) change_descriptor = AddChecksum(change_descriptor) From 0122c37eec3e0b087dec50a11713e4e3a2df8fff Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sun, 20 Jun 2021 07:38:35 +0200 Subject: [PATCH 4/5] remove residual is_liquid from wallet.py --- src/cryptoadvance/specter/liquid/wallet.py | 1 + src/cryptoadvance/specter/wallet.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cryptoadvance/specter/liquid/wallet.py b/src/cryptoadvance/specter/liquid/wallet.py index 35050fb42b..5ac52b9e2e 100644 --- a/src/cryptoadvance/specter/liquid/wallet.py +++ b/src/cryptoadvance/specter/liquid/wallet.py @@ -1,5 +1,6 @@ from ..wallet import * from ..addresslist import Address +from embit import ec from embit.liquid.pset import PSET from embit.liquid.transaction import LTransaction diff --git a/src/cryptoadvance/specter/wallet.py b/src/cryptoadvance/specter/wallet.py index 6277cd7cc8..b1713da995 100644 --- a/src/cryptoadvance/specter/wallet.py +++ b/src/cryptoadvance/specter/wallet.py @@ -4,8 +4,8 @@ from .device import Device from .key import Key from .util.merkleblock import is_valid_merkle_proof -from .helpers import der_to_bytes, get_address_from_dict, is_liquid -from embit import base58, bip32, ec +from .helpers import der_to_bytes, get_address_from_dict +from embit import base58, bip32 from .util.descriptor import Descriptor, sort_descriptor, AddChecksum from embit.liquid.descriptor import LDescriptor from embit.descriptor.checksum import add_checksum From 754c6733e9cbbcd79fd44a23176fa78f07f52b26 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Sun, 20 Jun 2021 09:50:22 +0200 Subject: [PATCH 5/5] deprecate Core v19 and below --- src/cryptoadvance/specter/node.py | 2 +- .../specter/templates/includes/sidebar/sidebar.jinja | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cryptoadvance/specter/node.py b/src/cryptoadvance/specter/node.py index 83b520beff..2c3257abc8 100644 --- a/src/cryptoadvance/specter/node.py +++ b/src/cryptoadvance/specter/node.py @@ -318,7 +318,7 @@ def test_rpc(self): r["code"] = 0 try: r["tests"]["recent_version"] = ( - int(rpc.getnetworkinfo()["version"]) >= 170000 + int(rpc.getnetworkinfo()["version"]) >= 200000 ) if not r["tests"]["recent_version"]: r["err"] = "Core Node might be too old" diff --git a/src/cryptoadvance/specter/templates/includes/sidebar/sidebar.jinja b/src/cryptoadvance/specter/templates/includes/sidebar/sidebar.jinja index 2b4f7ea8de..0e83ef7ef5 100644 --- a/src/cryptoadvance/specter/templates/includes/sidebar/sidebar.jinja +++ b/src/cryptoadvance/specter/templates/includes/sidebar/sidebar.jinja @@ -42,8 +42,8 @@ {% if specter.info["initialblockdownload"] %}


Bitcoin Core is still syncing...
(data might be outdated)

{% endif %} - {% if specter.network_info.version < 190000 %} -


Bitcoin Core version is outdated.
Some features might not work...
(minimum required: v19.0.0).

+ {% if specter.network_info.version < 200000 %} +


Bitcoin Core version is outdated.
Some features might not work...
(minimum required: v20.0.0).

{% endif %}