10
10
11
11
# TODO
12
12
13
- - [ ] add wallet name
14
13
- [ ] add version birthday to new config
15
14
- [ ] allow manual coin selection when sending
16
- - [ ] address labeling
17
15
- [ ] implement scrolling in the curses balance panel
18
16
- [ ] implement --json, --csv
19
17
- [ ] implement command-on-monitor
@@ -59,6 +57,9 @@ from typing import IO, Optional as Op
59
57
from configparser import ConfigParser
60
58
61
59
60
+ __VERSION__ = "0.3.0"
61
+
62
+
62
63
# --- command line parsing ------------------------------------------------------------
63
64
# -------------------------------------------------------------------------------------
64
65
@@ -332,6 +333,10 @@ class JSONRPCError(Exception):
332
333
)
333
334
self .error = rpc_error
334
335
336
+ @property
337
+ def code (self ) -> int :
338
+ return int (self .error ["code" ])
339
+
335
340
336
341
class BitcoinRPC (object ):
337
342
"""Base JSON-RPC proxy class. Contains only private methods; do not use
@@ -1004,65 +1009,7 @@ def run_setup(config) -> t.Tuple[t.Any, t.Any]:
1004
1009
done (f"wrote config to { config .loaded_from } " )
1005
1010
p ()
1006
1011
1007
- section ("wallet setup in Core" )
1008
- rpc_wallet_create (rpc , wallet )
1009
- done (f"created wallet { yellow (wallet .name )} in Core as watch-only" )
1010
-
1011
- rpcw = config .rpc (wallet )
1012
- rpcw .importmulti (* wallet .importmulti_args ())
1013
- done ("imported descriptors 0/* and 1/* (change)" )
1014
-
1015
- scan_result = {} # type: ignore
1016
- scan_thread = threading .Thread (
1017
- target = _run_scantxoutset ,
1018
- args = (config .rpc (wallet ), wallet .scantxoutset_args (), scan_result ),
1019
- )
1020
- scan_thread .start ()
1021
-
1022
- p ()
1023
- section ("scanning the chain for balance and history" )
1024
- while scan_thread .is_alive ():
1025
- spin ("scanning the UTXO set for your balance (few minutes) " )
1026
- time .sleep (0.2 )
1027
-
1028
- p ()
1029
- done ("scan of UTXO set complete!" )
1030
-
1031
- # TODO this will fail if we timed out
1032
- unspents = scan_result ["result" ]["unspents" ]
1033
- bal = sum ([i ["amount" ] for i in unspents ])
1034
- bal_str = yellow (bold (f"{ bal } BTC" ))
1035
- bal_count = yellow (bold (f"{ len (unspents )} UTXOs" ))
1036
- blank (f"found an existing balance of { yellow (bal_str )} across { yellow (bal_count )} " )
1037
-
1038
- if unspents :
1039
- rescan_begin_height = min ([i ["height" ] for i in unspents ])
1040
- p ()
1041
- blank (
1042
- f"beginning chain rescan from height { bold (str (rescan_begin_height ))} "
1043
- f"(minutes to hours)"
1044
- )
1045
- blank (" this allows us to find transactions associated with your coins\n " )
1046
- rescan_thread = threading .Thread (
1047
- target = _run_rescan ,
1048
- args = (config .rpc (wallet ), rescan_begin_height ),
1049
- daemon = True ,
1050
- )
1051
- rescan_thread .start ()
1052
-
1053
- time .sleep (2 )
1054
-
1055
- scan_info = rpcw .getwalletinfo ()["scanning" ]
1056
- while scan_info :
1057
- spin (f"scan progress: { scan_info ['progress' ] * 100 :.2f} % " )
1058
- time .sleep (0.5 )
1059
- scan_info = rpcw .getwalletinfo ()["scanning" ]
1060
-
1061
- name = yellow (wallet .name )
1062
- p ()
1063
- done (f"scan complete. wallet { name } ready to use." )
1064
- info (f"Hint: check out your UTXOs with `coldcore -w { wallet .name } balance`" )
1065
-
1012
+ rpcw = init_wallet_in_core (config , rpc , wallet )
1066
1013
p ()
1067
1014
1068
1015
got = inp ("do you want to perform some test transactions? [Y/n] " ).lower ()
@@ -1889,8 +1836,6 @@ def start_ui(config, wallet_configs, action=None):
1889
1836
# --- main/CLI ------------------------------------------------------------------------
1890
1837
# -------------------------------------------------------------------------------------
1891
1838
1892
- __VERSION__ = "0.2.0-beta"
1893
-
1894
1839
root_logger = logging .getLogger ()
1895
1840
logger = logging .getLogger ("main" )
1896
1841
@@ -2124,7 +2069,97 @@ def newaddr(num: int = 1, clip: ClipArg = False): # type: ignore
2124
2069
@cli .cmd
2125
2070
def ui ():
2126
2071
config , walls = _get_config (require_wallets = False )
2127
- start_ui (config , walls )
2072
+ try :
2073
+ start_ui (config , walls )
2074
+ except JSONRPCError as e :
2075
+ if e .code == - 18 :
2076
+ print ()
2077
+ print ("Wallet not found - are you using a new instance of bitcoind?" )
2078
+ print ("If so, reinitialize with `coldcore reinit-wallet`" )
2079
+ sys .exit (1 )
2080
+ else :
2081
+ raise
2082
+
2083
+
2084
+ @cli .cmd
2085
+ def reinit_wallet ():
2086
+ """
2087
+ Reinitialize the wallet on an instance of bitcoind which hasn't seen it before.
2088
+ """
2089
+ config , walls = _get_config (require_wallets = False )
2090
+ assert walls
2091
+ rpc = discover_rpc (config )
2092
+ assert rpc
2093
+ init_wallet_in_core (config , rpc , walls [0 ])
2094
+
2095
+
2096
+ def init_wallet_in_core (config , rpc , wallet ) -> BitcoinRPC :
2097
+ """
2098
+ Initialize a wallet in core useing createwallet and scan for its balance and
2099
+ history.
2100
+ """
2101
+ F .section ("wallet setup in Core" )
2102
+ rpc_wallet_create (rpc , wallet )
2103
+ F .done (f"created wallet { yellow (wallet .name )} in Core as watch-only" )
2104
+
2105
+ rpcw = config .rpc (wallet )
2106
+ rpcw .importmulti (* wallet .importmulti_args ())
2107
+ F .done ("imported descriptors 0/* and 1/* (change)" )
2108
+
2109
+ scan_result = {} # type: ignore
2110
+ scan_thread = threading .Thread (
2111
+ target = _run_scantxoutset ,
2112
+ args = (config .rpc (wallet ), wallet .scantxoutset_args (), scan_result ),
2113
+ )
2114
+ scan_thread .start ()
2115
+
2116
+ print ()
2117
+ F .section ("scanning the chain for balance and history" )
2118
+ while scan_thread .is_alive ():
2119
+ F .spin ("scanning the UTXO set for your balance (few minutes) " )
2120
+ time .sleep (0.2 )
2121
+
2122
+ print ()
2123
+ F .done ("scan of UTXO set complete!" )
2124
+
2125
+ # TODO this will fail if we timed out
2126
+ unspents = scan_result ["result" ]["unspents" ]
2127
+ bal = sum ([i ["amount" ] for i in unspents ])
2128
+ bal_str = yellow (bold (f"{ bal } BTC" ))
2129
+ bal_count = yellow (bold (f"{ len (unspents )} UTXOs" ))
2130
+ F .blank (
2131
+ f"found an existing balance of { yellow (bal_str )} across { yellow (bal_count )} "
2132
+ )
2133
+
2134
+ if unspents :
2135
+ rescan_begin_height = min ([i ["height" ] for i in unspents ])
2136
+ print ()
2137
+ F .blank (
2138
+ f"beginning chain rescan from height { bold (str (rescan_begin_height ))} "
2139
+ f"(minutes to hours)"
2140
+ )
2141
+ F .blank (" this allows us to find transactions associated with your coins\n " )
2142
+ rescan_thread = threading .Thread (
2143
+ target = _run_rescan ,
2144
+ args = (config .rpc (wallet ), rescan_begin_height ),
2145
+ daemon = True ,
2146
+ )
2147
+ rescan_thread .start ()
2148
+
2149
+ time .sleep (2 )
2150
+
2151
+ scan_info = rpcw .getwalletinfo ()["scanning" ]
2152
+ while scan_info :
2153
+ F .spin (f"scan progress: { scan_info ['progress' ] * 100 :.2f} % " )
2154
+ time .sleep (0.5 )
2155
+ scan_info = rpcw .getwalletinfo ()["scanning" ]
2156
+
2157
+ name = yellow (wallet .name )
2158
+ print ()
2159
+ F .done (f"scan complete. wallet { name } ready to use." )
2160
+ F .info (f"Hint: check out your UTXOs with `coldcore -w { wallet .name } balance`" )
2161
+
2162
+ return rpcw
2128
2163
2129
2164
2130
2165
@cli .main
@@ -2601,7 +2636,8 @@ def discover_rpc(
2601
2636
def _is_already_loaded_err (e : JSONRPCError ) -> bool :
2602
2637
msg = str (e ).lower ()
2603
2638
return (
2604
- ("already loaded" in msg )
2639
+ e .code == - 4
2640
+ or ("already loaded" in msg )
2605
2641
or ("duplicate -wallet filename" in msg )
2606
2642
or ("database already exists" in msg )
2607
2643
)
@@ -2661,6 +2697,16 @@ def _get_rpc_inner(
2661
2697
)
2662
2698
2663
2699
2700
+ def get_node_version (rpc : BitcoinRPC ) -> t .Tuple [int , int , int ]:
2701
+ """E.g. (22, 99, 0)."""
2702
+ netinfo = rpc .getnetworkinfo ()
2703
+ ver = re .match (r"/Satoshi:(\d+)\.(\d+)\.(\d+)/" , netinfo ["subversion" ])
2704
+ assert ver
2705
+ groups = [int (i ) for i in ver .groups ()]
2706
+ assert len (groups ) == 3
2707
+ return (groups [0 ], groups [1 ], groups [2 ])
2708
+
2709
+
2664
2710
# --- Wallet/transaction utilities --------------------------------------------
2665
2711
# -----------------------------------------------------------------------------
2666
2712
@@ -2682,8 +2728,24 @@ def _sh(*args, **kwargs) -> subprocess.CompletedProcess:
2682
2728
2683
2729
2684
2730
def rpc_wallet_create (rpc : BitcoinRPC , wall : Wallet ):
2731
+ node_ver = get_node_version (rpc )
2685
2732
try :
2686
- rpc .createwallet (wall .bitcoind_name , True )
2733
+ if node_ver < (22 , 99 , 0 ):
2734
+ rpc .createwallet (
2735
+ wall .bitcoind_name ,
2736
+ True , # disable_private_keys
2737
+ )
2738
+ else :
2739
+ # In 22.99+ (i.e. the 23.0 release), createwallet creates descriptor wallets
2740
+ # by default, which are incompatible with importmulti.
2741
+ rpc .createwallet (
2742
+ wall .bitcoind_name ,
2743
+ True , # disable_private_keys
2744
+ True , # blank
2745
+ None , # passphrase
2746
+ False , # avoid_reuse
2747
+ False , # descriptors
2748
+ )
2687
2749
except JSONRPCError as e :
2688
2750
if not _is_already_loaded_err (e ):
2689
2751
# Wallet already exists; ok.
0 commit comments