From 2873897f6816a179371065d7c3dd527f9b2b37ad Mon Sep 17 00:00:00 2001 From: philemon Date: Fri, 4 Aug 2017 14:13:03 +0300 Subject: [PATCH] Replace --aug1hf with --coin=bch, display 'BCH' for BCH amounts * Deprecate the --aug1hf option * Use --coin=bch option to select BCH chain; works with all MMGen commands * All BCH amounts now correctly display with 'BCH' instead of 'BTC' * use non-standard RPC ports (8442,18442) for BCH mainnet/testnet to allow dual-node operation. Use 'mmlive-node-start' and '-stop' scripts (home.mmgen/bin/mmlive-node-{start,stop} in the MMGenLive repo) to start daemons with correct RPC ports; the --coin option automatically configures the correct ports for all MMGen scripts. * mmgen-txsend: new --status option checks confirmation/mempool status of TXs --- mmgen/globalvars.py | 10 ++-- mmgen/main_txbump.py | 14 +++--- mmgen/main_txcreate.py | 4 +- mmgen/main_txdo.py | 13 ++++-- mmgen/main_txsend.py | 9 +++- mmgen/main_txsign.py | 10 +++- mmgen/obj.py | 6 ++- mmgen/opts.py | 17 +++++-- mmgen/rpc.py | 7 +-- mmgen/tool.py | 10 ++-- mmgen/tw.py | 18 ++++---- mmgen/tx.py | 101 ++++++++++++++++++++++------------------- mmgen/txcreate.py | 34 +++++++------- mmgen/txsign.py | 16 +++---- mmgen/util.py | 45 ++++++++++++------ 15 files changed, 186 insertions(+), 128 deletions(-) diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index d34f2e6e..359853b1 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -49,6 +49,10 @@ class g(object): Cdates = '2013-2017' keywords = 'Bitcoin, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin' + coin = 'BTC' + coins = 'BTC','BCH' + ports = { 'BTC': (8332,18332), 'BCH': (8442,18442) } + user_entropy = '' hash_preset = '3' usr_randchars = 30 @@ -106,19 +110,19 @@ class g(object): # User opt sets global var: common_opts = ( 'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password', - 'bitcoin_data_dir','force_256_color','regtest' + 'bitcoin_data_dir','force_256_color','regtest','coin' ) required_opts = ( 'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout', 'show_hash_presets','label','keep_passphrase','keep_hash_preset','yes', - 'brain_params','b16','usr_randchars' + 'brain_params','b16','usr_randchars','coin' ) incompatible_opts = ( ('quiet','verbose'), ('label','keep_label'), ('tx_id','info'), ('tx_id','terse_info'), - ('aug1hf','rbf'), + ('aug1hf','rbf'), # TODO: remove in 0.9.4 ('batch','rescan') ) cfg_file_opts = ( diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index 6c652345..7e69a071 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -36,7 +36,7 @@ opts_data = { -c, --comment-file= f Source the transaction's comment from file 'f' -d, --outdir= d Specify an alternate directory 'd' for output -e, --echo-passphrase Print passphrase to screen when typing it --f, --tx-fee= f Transaction fee, as a decimal BTC amount or in +-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or in satoshis per byte (an integer followed by 's') -H, --hidden-incog-input-params=f,o Read hidden incognito data from file 'f' at offset 'o' (comma-separated) @@ -51,7 +51,7 @@ opts_data = { -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key- address file (output of '{pnl}-keygen'). Permits online signing without an {pnm} seed source. The - key-address file is also used to verify {pnm}-to-BTC + key-address file is also used to verify {pnm}-to-{cu} mappings, so the user should record its checksum. -o, --output-to-reduce=o Deduct the fee from output 'o' (an integer, or 'c' for the transaction's change output, if present) @@ -67,11 +67,13 @@ opts_data = { -z, --show-hash-presets Show information on available hash presets """.format(g=g,pnm=pnm,pnl=pnm.lower(), kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]), - kg=g.key_generator), + kg=g.key_generator, + cu=g.coin + ), 'notes': '\n' + fee_notes + txsign_notes } -cmd_args = opts.init(opts_data,add_opts=['aug1hf']) +cmd_args = opts.init(opts_data) c = bitcoin_connection() @@ -96,14 +98,14 @@ tx.set_min_fee() if not [o.amt for o in tx.outputs if o.amt >= tx.min_fee]: die(1,'Transaction cannot be bumped.' + - '\nAll outputs have less than the minimum fee ({} BTC)'.format(tx.min_fee)) + '\nAll outputs have less than the minimum fee ({} {})'.format(tx.min_fee,g.coin)) msg('Creating new transaction') op_idx = tx.choose_output() if not silent: - msg('Minimum fee for new transaction: {} BTC'.format(tx.min_fee)) + msg('Minimum fee for new transaction: {} {}'.format(tx.min_fee,g.coin)) fee = tx.get_usr_fee_interactive(tx_fee=opt.tx_fee,desc='User-selected') diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index 80dd73e6..46b136e9 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -35,7 +35,7 @@ opts_data = { -c, --comment-file=f Source the transaction's comment from file 'f' -C, --tx-confs= c Desired number of confirmations (default: {g.tx_confs}) -d, --outdir= d Specify an alternate directory 'd' for output --f, --tx-fee= f Transaction fee, as a decimal BTC amount or in satoshis +-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or in satoshis per byte (an integer followed by 's'). If omitted, fee will be calculated using bitcoind's 'estimatefee' call -i, --info Display unspent outputs and exit @@ -45,7 +45,7 @@ opts_data = { -r, --rbf Make transaction BIP 125 replaceable (replace-by-fee) -v, --verbose Produce more verbose output -y, --yes Answer 'yes' to prompts, suppress non-essential output -""".format(g=g), +""".format(g=g,cu=g.coin), 'notes': '\n' + txcreate_notes + fee_notes } diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index 6864958d..5bce88e9 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -39,7 +39,7 @@ opts_data = { -C, --tx-confs= c Desired number of confirmations (default: {g.tx_confs}) -d, --outdir= d Specify an alternate directory 'd' for output -e, --echo-passphrase Print passphrase to screen when typing it --f, --tx-fee= f Transaction fee, as a decimal BTC amount or in +-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or in satoshis per byte (an integer followed by 's'). If omitted, bitcoind's 'estimatefee' will be used to calculate the fee. @@ -58,7 +58,7 @@ opts_data = { -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key- address file (output of '{pnl}-keygen'). Permits online signing without an {pnm} seed source. The - key-address file is also used to verify {pnm}-to-BTC + key-address file is also used to verify {pnm}-to-{cu} mappings, so the user should record its checksum. -O, --old-incog-fmt Specify old-format incognito input -p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p' @@ -71,11 +71,18 @@ opts_data = { -z, --show-hash-presets Show information on available hash presets """.format(g=g,pnm=pnm,pnl=pnm.lower(), kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]), - kg=g.key_generator), + kg=g.key_generator, + cu=g.coin + ), 'notes': '\n' + txcreate_notes + fee_notes + txsign_notes } cmd_args = opts.init(opts_data) + +if opt.aug1hf: # TODO: remove in 0.9.4 + msg(yellow('The --aug1hf option is deprecated. Please use --coin=bch instead')) + g.coin = 'BCH' + seed_files = get_seed_files(opt,cmd_args) c = bitcoin_connection() do_license_msg() diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index 726fd907..e36b06e3 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -33,6 +33,7 @@ opts_data = { --, --longhelp Print help message for long options (common options) -d, --outdir= d Specify an alternate directory 'd' for output -q, --quiet Suppress warnings; overwrite files without prompting +-s, --status Get status of a sent transaction -y, --yes Answer 'yes' to prompts, suppress non-essential output """ } @@ -43,7 +44,8 @@ if len(cmd_args) == 1: infile = cmd_args[0]; check_infile(infile) else: opts.usage() -do_license_msg() +if not opt.status: do_license_msg() + c = bitcoin_connection() tx = MMGenTX(infile) # sig check performed here qmsg("Signed transaction file '%s' is valid" % infile) @@ -51,6 +53,11 @@ qmsg("Signed transaction file '%s' is valid" % infile) if not tx.marked_signed(c): die(1,'Transaction is not signed!') +if opt.status: + if tx.btc_txid: qmsg('{} txid: {}'.format(g.coin,tx.btc_txid.hl())) + tx.get_status(c,status=True) + sys.exit(0) + if not opt.yes: tx.view_with_prompt('View transaction data?') if tx.add_comment(): # edits an existing comment, returns true if changed diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index c418d308..e3c3a6b0 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -52,7 +52,7 @@ opts_data = { -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key- address file (output of '{pnl}-keygen'). Permits online signing without an {pnm} seed source. The - key-address file is also used to verify {pnm}-to-BTC + key-address file is also used to verify {pnm}-to-{cu} mappings, so the user should record its checksum. -P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f' -q, --quiet Suppress warnings; overwrite files without prompting @@ -63,12 +63,18 @@ opts_data = { """.format( g=g,pnm=pnm,pnl=pnm.lower(), kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]), - kg=g.key_generator), + kg=g.key_generator, + cu=g.coin + ), 'notes': '\n' + txsign_notes } infiles = opts.init(opts_data,add_opts=['b16']) +if opt.aug1hf: # TODO: remove in 0.9.4 + msg(yellow('The --aug1hf option is deprecated. Please use --coin=bch instead')) + g.coin = 'BCH' + if not infiles: opts.usage() for i in infiles: check_infile(i) diff --git a/mmgen/obj.py b/mmgen/obj.py index 92a95690..22462970 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -296,9 +296,11 @@ class BTCAmt(Decimal,Hilite,InitErrors): m = "'%s': value cannot be converted to decimal" % num else: if me.normalize().as_tuple()[-1] < -cls.max_prec: - m = "'%s': too many decimal places in BTC amount" % num + from mmgen.globalvars import g + m = "'{}': too many decimal places in {} amount".format(num,g.coin) elif me > cls.max_amt: - m = "'%s': BTC amount too large (>%s)" % (num,cls.max_amt) + from mmgen.globalvars import g + m = "'{}': {} amount too large (>{})".format(num,g.coin,cls.max_amt) # elif me.as_tuple()[0]: # m = "'%s': BTC amount cannot be negative" % num else: diff --git a/mmgen/opts.py b/mmgen/opts.py index 61bfdf3a..24e5348e 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -47,6 +47,7 @@ def _show_hash_presets(): # most, but not all, of these set the corresponding global var common_opts_data = """ +--, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all} --, --color=0|1 Disable or enable color output --, --force-256-color Force 256-color output when color is enabled --, --bitcoin-data-dir=d Specify Bitcoin data directory location 'd' @@ -60,7 +61,11 @@ common_opts_data = """ --, --testnet=0|1 Disable or enable testnet --, --skip-cfg-file Skip reading the configuration file --, --version Print version information and exit -""".format(pnm=g.proj_name) +""".format( + pnm=g.proj_name, + cu_dfl=g.coin, + cu_all=' '.join(g.coins), + ) def opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args): d = ( @@ -102,6 +107,8 @@ def opt_postproc_initializations(): if g.platform == 'win': start_mscolor() + g.coin = g.coin.upper() # allow user to use lowercase + def set_data_dir_root(): g.data_dir_root = os.path.normpath(os.path.expanduser(opt.data_dir)) if opt.data_dir else \ os.path.join(g.home_dir,'.'+g.proj_name.lower()) @@ -273,10 +280,10 @@ def check_opts(usr_opts): # Returns false if any check fails from mmgen.tx import MMGenTX ret = MMGenTX().convert_fee_spec(val,224,on_fail='return') if ret == False: - msg("'{}': invalid {} (not a BTC amount or satoshis-per-byte specification)".format( - val,desc)) + msg("'{}': invalid {} (not a {} amount or satoshis-per-byte specification)".format( + val,desc,g.coin.upper())) elif ret != None and ret > g.max_tx_fee: - msg("'{}': invalid {} (greater than max_tx_fee ({} BTC))".format(val,desc,g.max_tx_fee)) + msg("'{}': invalid {} (> max_tx_fee ({} {}))".format(val,desc,g.max_tx_fee,g.coin.upper())) else: return True return False @@ -386,6 +393,8 @@ def check_opts(usr_opts): # Returns false if any check fails elif key == 'key_generator': if not opt_compares(val,'<=',len(g.key_generators),desc): return False if not opt_compares(val,'>',0,desc): return False + elif key == 'coin': + if not opt_is_in_list(val.upper(),g.coins,'coin'): return False else: if g.debug: Msg("check_opts(): No test for opt '%s'" % key) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 6220c264..c30e4a5d 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -28,11 +28,7 @@ from mmgen.obj import BTCAmt class BitcoinRPCConnection(object): - def __init__( - self, - host=g.rpc_host,port=(8332,18332)[g.testnet], - user=None,passwd=None,auth_cookie=None, - ): + def __init__(self,host=None,port=None,user=None,passwd=None,auth_cookie=None): dmsg('=== BitcoinRPCConnection.__init__() debug ===') dmsg(' host [{}] port [{}] user [{}] passwd [{}] auth_cookie [{}]\n'.format( @@ -154,6 +150,7 @@ class BitcoinRPCConnection(object): 'getblockcount', 'getblockhash', 'getmempoolentry', + 'getnettotals', 'getnetworkinfo', 'getpeerinfo', 'getrawmempool', diff --git a/mmgen/tool.py b/mmgen/tool.py index e4096307..e3f0fa79 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -485,8 +485,8 @@ def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa if usr_addr_list and (label.mmid not in usr_addr_list): continue if label.mmid in addrs: if addrs[label.mmid]['addr'] != d['address']: - die(2,'duplicate BTC address ({}) for this MMGen address! ({})'.format( - (d['address'], addrs[label.mmid]['addr']))) + die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format( + g.coin,d['address'],addrs[label.mmid]['addr'])) else: addrs[label.mmid] = { 'amt':BTCAmt('0'), 'lbl':label, 'addr':BTCAddr(d['address']) } addrs[label.mmid]['amt'] += d['amount'] @@ -501,7 +501,7 @@ def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa assert len(accts) == len(acct_addrs), 'listaccounts() and getaddressesbyaccount() not of same length' for a in acct_addrs: if len(a) != 1: - die(2,"'{}': more than one BTC address in account!".format(a)) + die(2,"'{}': more than one {} address in account!".format(a,g.coin)) for label,addr in zip(accts,[b[0] for b in acct_addrs]): if usr_addr_list and (label.mmid not in usr_addr_list): continue if label.mmid not in addrs: @@ -543,7 +543,7 @@ def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa cmt=addrs[mmid]['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'), amt=addrs[mmid]['amt'].fmt('3.0',color=True))) - out.append('\nTOTAL: %s BTC' % total.hl(color=True)) + out.append('\nTOTAL: {} {}'.format(total.hl(color=True),g.coin)) o = '\n'.join(out) return do_pager(o) if pager else Msg(o) @@ -567,7 +567,7 @@ def Getbalance(minconf=1): Msg(fs.format('Wallet', *[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)])) for key in sorted(accts.keys()): - Msg(fs.format(key+':', *[a.fmt(color=True,suf=' BTC') for a in accts[key]])) + Msg(fs.format(key+':', *[a.fmt(color=True,suf=' '+g.coin) for a in accts[key]])) if 'SPENDABLE' in accts: Msg(red('Warning: this wallet contains PRIVATE KEYS for the SPENDABLE balance!')) diff --git a/mmgen/tw.py b/mmgen/tw.py index d14275bb..00407e25 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -157,13 +157,13 @@ watch-only wallet using '{}-addrimport' and then re-run this program. if self.sort_key == k and getattr(a,k) == getattr(b,k): b.skip = (k,'addr')[k=='twmmid'] - hdr_fmt = 'UNSPENT OUTPUTS (sort order: %s) Total BTC: %s' - out = [hdr_fmt % (' '.join(self.sort_info()), self.total.hl())] + hdr_fmt = 'UNSPENT OUTPUTS (sort order: {}) Total {}: {}' + out = [hdr_fmt.format(' '.join(self.sort_info()),g.coin,self.total.hl())] if g.chain in ('testnet','regtest'): out += [green('Chain: {}'.format(g.chain.upper()))] af = BTCAddr.fmtc('Address',width=addr_w+1) cf = ('Conf.','Age(d)')[self.show_days] - out += [fs % ('Num','TX id'.ljust(tx_w - 5) + ' Vout','',af,'Amt(BTC) ',cf)] + out += [fs % ('Num','TX id'.ljust(tx_w - 5) + ' Vout','',af,'Amt({}) '.format(g.coin),cf)] for n,i in enumerate(unsp): addr_dots = '|' + '.'*33 @@ -195,7 +195,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program. fs = ' %-4s %-67s %s %s %s %-8s %-6s %s' out = [fs % ('Num','Tx ID,Vout','Address'.ljust(34),'MMGen ID'.ljust(15), - 'Amount(BTC)','Conf.','Age(d)', 'Label')] + 'Amount({})'.format(g.coin),'Conf.','Age(d)', 'Label')] max_lbl_len = max([len(i.label) for i in self.unspent if i.label] or [1]) for n,i in enumerate(self.unspent): @@ -209,17 +209,18 @@ watch-only wallet using '{}-addrimport' and then re-run this program. TwComment.fmtc('',color=color,nullrepl='-',width=max_lbl_len)) out.append(s.rstrip()) - fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n' + fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal {}: {}\n' self.fmt_print = fs.format( make_timestr(), ' '.join(self.sort_info(include_group=False)), '\n'.join(out), + g.coin, self.total.hl(color=color)) return self.fmt_print def display_total(self): - fs = '\nTotal unspent: %s BTC (%s outputs)' - msg(fs % (self.total.hl(),len(self.unspent))) + fs = '\nTotal unspent: {} {} ({} outputs)' + msg(fs.format(self.total.hl(),g.coin,len(self.unspent))) def get_idx_and_label_from_user(self): msg('') @@ -246,7 +247,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program. return n,s def view_and_sort(self,tx): - txos = 'Total to spend, excluding fees: {} BTC\n\n'.format(tx.sum_outputs().hl()) if tx.outputs else '' + fs = 'Total to spend, excluding fees: {} {}\n\n' + txos = fs.format(tx.sum_outputs().hl(),g.coin) if tx.outputs else '' prompt = """ {}Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen diff --git a/mmgen/tx.py b/mmgen/tx.py index 4998f77c..de83d2c0 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -319,7 +319,7 @@ class MMGenTX(MMGenObject): def get_relay_fee(self): assert self.estimate_size() kb_fee = BTCAmt(bitcoin_connection().getnetworkinfo()['relayfee']) - vmsg('Relay fee: {} BTC/kB'.format(kb_fee)) + vmsg('Relay fee: {} {}/kB'.format(kb_fee,g.coin)) return kb_fee * self.estimate_size() / 1024 def convert_fee_spec(self,tx_fee,tx_size,on_fail='throw'): @@ -339,16 +339,20 @@ class MMGenTX(MMGenObject): def get_usr_fee(self,tx_fee,desc='Missing description'): btc_fee = self.convert_fee_spec(tx_fee,self.estimate_size(),on_fail='return') if btc_fee == None: - msg("'{}': cannot convert satoshis-per-byte to BTC because transaction size is unknown".format(tx_fee)) - assert False # because we shouldn't be calling this if tx size is unknown + # we shouldn't be calling this if tx size is unknown + m = "'{}': cannot convert satoshis-per-byte to {} because transaction size is unknown" + assert False, m.format(tx_fee,g.coin) elif btc_fee == False: - msg("'{}': invalid TX fee (not a BTC amount or satoshis-per-byte specification)".format(tx_fee)) + m = "'{}': invalid TX fee (not a {} amount or satoshis-per-byte specification)" + msg(m.format(tx_fee,g.coin)) return False elif btc_fee > g.max_tx_fee: - msg('{} BTC: {} fee too large (maximum fee: {} BTC)'.format(btc_fee,desc,g.max_tx_fee)) + m = '{} {c}: {} fee too large (maximum fee: {} {c})' + msg(m.format(btc_fee,desc,g.max_tx_fee,c=g.coin)) return False elif btc_fee < self.get_relay_fee(): - msg('{} BTC: {} fee too small (below relay fee of {} BTC)'.format(str(btc_fee),desc,str(self.get_relay_fee()))) + m = '{} {c}: {} fee too small (below relay fee of {} {c})' + msg(m.format(str(btc_fee),desc,str(self.get_relay_fee()),c=g.coin)) return False else: return btc_fee @@ -360,8 +364,8 @@ class MMGenTX(MMGenObject): btc_fee = self.get_usr_fee(tx_fee,desc) if btc_fee: m = ('',' (after {}x adjustment)'.format(opt.tx_fee_adj))[opt.tx_fee_adj != 1] - p = '{} TX fee{}: {} BTC ({} satoshis per byte)'.format(desc,m, - btc_fee.hl(),pink(str(self.btc2spb(btc_fee)))) + p = '{} TX fee{}: {} {} ({} satoshis per byte)'.format(desc,m, + btc_fee.hl(),g.coin,pink(str(self.btc2spb(btc_fee)))) if opt.yes or keypress_confirm(p+'. OK?',default_yes=True): if opt.yes: msg(p) return btc_fee @@ -444,8 +448,8 @@ class MMGenTX(MMGenObject): self.die_if_incorrect_chain() - if opt.aug1hf and self.has_segwit_inputs(): - die(2,yellow("'--aug1hf' option is incompatible with Segwit transaction inputs!")) + if g.coin == 'BCH' and self.has_segwit_inputs(): + die(2,yellow("Segwit inputs cannot be spent on BCH chain!")) if not keys: msg('No keys. Cannot sign!') @@ -468,15 +472,15 @@ class MMGenTX(MMGenObject): from mmgen.bitcoin import hash256 msg_r('Signing transaction{}...'.format(tx_num_str)) - ht = ('ALL','ALL|FORKID')[bool(opt.aug1hf)] # sighashtype defaults to 'ALL' + ht = ('ALL','ALL|FORKID')[g.coin=='BCH'] # sighashtype defaults to 'ALL' ret = c.signrawtransaction(self.hex,sig_data,keys.values(),ht,on_fail='return') from mmgen.rpc import rpc_error,rpc_errmsg if rpc_error(ret): errmsg = rpc_errmsg(ret) if 'Invalid sighash param' in errmsg: - m = 'This chain does not support the Aug. 1 2017 UAHF.' - m += "\nRe-run 'mmgen-txsign' or 'mmgen-txdo' without the --aug1hf option." + m = 'This is not the BCH chain.' + m += "\nRe-run the script without the --aug1hf or --coin=bch option." else: m = errmsg msg(yellow(m)) @@ -549,6 +553,17 @@ class MMGenTX(MMGenObject): def is_in_utxos(self,c): return 'txid' in c.getrawtransaction(self.btc_txid,True,on_fail='silent') + def get_status(self,c,status=False): + if self.is_in_mempool(c): + msg(('Warning: transaction is in mempool!','Transaction is in mempool')[status]) + elif self.is_in_wallet(c): + die(1,'Transaction has been confirmed{}'.format('' if status else '!')) + elif self.is_in_utxos(c): + die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!')) + ret = self.is_replaced(c) # 1: replacement in mempool, 2: replacement confirmed + if ret: + die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!') + def send(self,c,prompt_user=True): self.die_if_incorrect_chain() @@ -562,16 +577,7 @@ class MMGenTX(MMGenObject): if self.get_fee() > g.max_tx_fee: die(2,'Transaction fee ({}) greater than max_tx_fee ({})!'.format(self.get_fee(),g.max_tx_fee)) - if self.is_in_mempool(c): - msg('Warning: transaction is in mempool!') - elif self.is_in_wallet(c): - die(1,'Transaction has been confirmed!') - elif self.is_in_utxos(c): - die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!')) - - ret = self.is_replaced(c) # 1: replacement in mempool, 2: replacement confirmed - if ret: - die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!') + self.get_status(c) if prompt_user: m1 = ("Once this transaction is sent, there's no taking it back!",'')[bool(opt.quiet)] @@ -592,10 +598,10 @@ class MMGenTX(MMGenObject): errmsg = rpc_errmsg(ret) if 'Signature must use SIGHASH_FORKID' in errmsg: m = 'The Aug. 1 2017 UAHF has activated on this chain.' - m += "\nRe-run 'mmgen-txsign' or 'mmgen-txdo' with the --aug1hf option." + m += "\nRe-run the script with the --coin=bch option." elif 'Illegal use of SIGHASH_FORKID' in errmsg: m = 'The Aug. 1 2017 UAHF is not yet active on this chain.' - m += "\nRe-run 'mmgen-txsign' or 'mmgen-txdo' without the --aug1hf option." + m += "\nRe-run the script without the --aug1hf or --coin=bch option." else: m = errmsg msg(yellow(m)) @@ -667,74 +673,77 @@ class MMGenTX(MMGenObject): blockcount = None hdr_fs = ( - 'TRANSACTION DATA\n\nHeader: [ID:{}] [{} BTC] [{} UTC] [RBF:{}] [Signed:{}]\n', - 'Transaction {} {} BTC ({} UTC) RBF={} Signed={}\n' + 'TRANSACTION DATA\n\n[ID:{}] [{} {}] [{} UTC] [RBF:{}] [Signed:{}]\n', + 'Transaction {} {} {} ({} UTC) RBF={} Signed={}\n' )[bool(terse)] - out = hdr_fs.format(self.txid.hl(),self.send_amt.hl(),self.timestamp, + out = hdr_fs.format(self.txid.hl(),self.send_amt.hl(),g.coin,self.timestamp, self.is_rbf(color=True),self.marked_signed(color=True)) enl = ('\n','')[bool(terse)] if self.chain in ('testnet','regtest'): out += green('Chain: {}\n'.format(self.chain.upper())) - if self.btc_txid: out += 'Bitcoin TxID: {}\n'.format(self.btc_txid.hl()) + if self.btc_txid: out += '{} TxID: {}\n'.format(g.coin,self.btc_txid.hl()) out += enl if self.label: out += 'Comment: %s\n%s' % (self.label.hl(),enl) out += 'Inputs:\n' + enl - nonmm_str = '(non-{pnm} address){s} '.format(pnm=g.proj_name,s=('',' ')[terse]) + nonmm_str = '(non-{pnm} address)'.format(pnm=g.proj_name) + max_mmwid = max(max(len(i.mmid) for i in self.inputs if i.mmid)+len('()'),len(nonmm_str)) for n,e in enumerate(sorted(self.inputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)): if blockcount: confs = e.confs + blockcount - self.blockcount days = int(confs * g.mins_per_block / (60*24)) - mmid_fmt = e.mmid.fmt(width=len(nonmm_str),encl='()',color=True) if e.mmid \ - else MMGenID.hlc(nonmm_str) + mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True) if e.mmid else MMGenID.hlc(nonmm_str) if terse: - out += '%3s: %s %s %s BTC' % (n+1, e.addr.fmt(color=True),mmid_fmt, e.amt.hl()) + out += '{:3} {} {} {} {}'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin) else: for d in ( (n+1, 'tx,vout:', '%s,%s' % (e.txid, e.vout)), ('', 'address:', e.addr.fmt(color=True) + ' ' + mmid_fmt), ('', 'comment:', e.label.hl() if e.label else ''), - ('', 'amount:', '%s BTC' % e.amt.hl()), + ('', 'amount:', '{} {}'.format(e.amt.hl(),g.coin)), ('', 'confirmations:', '%s (around %s days)' % (confs,days) if blockcount else '') ): if d[2]: out += ('%3s %-8s %s\n' % d) out += '\n' out += 'Outputs:\n' + enl + max_mmwid = max((len(o.mmid),len(o.mmid)+len(' (chg)'))[bool(o.is_chg)] for o in self.outputs if o.mmid) + max_mmwid = max(max_mmwid+len('()'),len(nonmm_str)) for n,e in enumerate(sorted(self.outputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)): if e.mmid: app=('',' (chg)')[bool(e.is_chg and terse)] - mmid_fmt = e.mmid.fmt(width=len(nonmm_str),encl='()',color=True, + mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True, app=app,appcolor='green') else: mmid_fmt = MMGenID.hlc(nonmm_str) if terse: - out += '%3s: %s %s %s BTC' % (n+1, e.addr.fmt(color=True),mmid_fmt, e.amt.hl()) + out += '{:3} {} {} {} {}'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin) else: for d in ( (n+1, 'address:', e.addr.fmt(color=True) + ' ' + mmid_fmt), ('', 'comment:', e.label.hl() if e.label else ''), - ('', 'amount:', '%s BTC' % e.amt.hl()), + ('', 'amount:', '{} {}'.format(e.amt.hl(),g.coin)), ('', 'change:', green('True') if e.is_chg else '') ): if d[2]: out += ('%3s %-8s %s\n' % d) out += '\n' fs = ( - 'Total input: %s BTC\nTotal output: %s BTC\nTX fee: %s BTC (%s satoshis per byte)\n', - 'In %s BTC - Out %s BTC - Fee %s BTC (%s satoshis/byte)\n' + 'Total input: {} {c}\nTotal output: {} {c}\nTX fee: {} {c} ({} satoshis per byte)\n', + 'In {} {c} - Out {} {c} - Fee {} {c} ({} satoshis/byte)\n' )[bool(terse)] total_in = self.sum_inputs() total_out = self.sum_outputs() - out += fs % ( + out += fs.format( total_in.hl(), total_out.hl(), (total_in-total_out).hl(), pink(str(self.btc2spb(total_in-total_out))), + c=g.coin ) if opt.verbose: ts = len(self.hex)/2 if self.hex else 'unknown' @@ -851,10 +860,10 @@ class MMGenBumpTX(MMGenTX): else: o_amt = self.outputs[idx].amt cs = ('',' (change output)')[chg_idx == idx] - p = 'Fee will be deducted from output {}{} ({} BTC)'.format(idx+1,cs,o_amt) + p = 'Fee will be deducted from output {}{} ({} {})'.format(idx+1,cs,o_amt,g.coin) if o_amt < self.min_fee: - msg('Minimum fee ({} BTC) is greater than output amount ({} BTC)'.format( - self.min_fee,o_amt)) + msg('Minimum fee ({} {c}) is greater than output amount ({} {c})'.format( + self.min_fee,o_amt,c=g.coin)) elif opt.yes or keypress_confirm(p+'. OK?',default_yes=True): if opt.yes: msg(p) self.bump_output_idx = idx @@ -866,11 +875,11 @@ class MMGenBumpTX(MMGenTX): def get_usr_fee(self,tx_fee,desc): ret = super(type(self),self).get_usr_fee(tx_fee,desc) if ret < self.min_fee: - msg('{} BTC: {} fee too small. Minimum fee: {} BTC ({} satoshis per byte)'.format( - ret,desc,self.min_fee,self.btc2spb(self.min_fee))) + msg('{} {c}: {} fee too small. Minimum fee: {} {c} ({} satoshis per byte)'.format( + ret,desc,self.min_fee,self.btc2spb(self.min_fee,c=g.coin))) return False output_amt = self.outputs[self.bump_output_idx].amt if ret >= output_amt: - msg('{} BTC: {} fee too large. Maximum fee: <{} BTC'.format(ret,desc,output_amt)) + msg('{} {c}: {} fee too large. Maximum fee: <{} {c}'.format(ret,desc,output_amt,c=g.coin)) return False return ret diff --git a/mmgen/txcreate.py b/mmgen/txcreate.py index 6687ba2d..ec924472 100755 --- a/mmgen/txcreate.py +++ b/mmgen/txcreate.py @@ -51,10 +51,10 @@ one address with no amount on the command line. fee_notes = """ FEE SPECIFICATION: Transaction fees, both on the command line and at the -interactive prompt, may be specified as either absolute BTC amounts, using a -plain decimal number, or as satoshis per byte, using an integer followed by +interactive prompt, may be specified as either absolute {} amounts, using +a plain decimal number, or as satoshis per byte, using an integer followed by the letter 's'. -""" +""".format(g.coin) wmsg = { 'addr_in_addrfile_only': """ @@ -77,19 +77,15 @@ NOTE: This transaction includes non-{pnm} inputs, which makes the signing process more complicated. When signing the transaction, keys for non-{pnm} inputs must be supplied to '{pnl}-txsign' in a file with the '--keys-from-file' option. -Selected non-{pnm} inputs: %s +Selected non-{pnm} inputs: {{}} """.strip().format(pnm=pnm,pnl=pnm.lower()), 'not_enough_btc': """ -Selected outputs insufficient to fund this transaction (%s BTC needed) -""".strip(), - 'throwaway_change': """ -ERROR: This transaction produces change (%s BTC); however, no change address -was specified. -""".strip(), +Selected outputs insufficient to fund this transaction ({{}} {} needed) +""".strip().format(g.coin), 'no_change_output': """ ERROR: No change address specified. If you wish to create a transaction with -only one output, specify a single output address with no BTC amount -""".strip(), +only one output, specify a single output address with no {} amount +""".strip().format(g.coin), } def select_unspent(unspent,prompt): @@ -139,7 +135,7 @@ def get_fee_from_estimate_or_user(tx,estimate_fail_msg_shown=[]): else: start_fee = BTCAmt(ret) * opt.tx_fee_adj * tx.estimate_size() / 1024 if opt.verbose: - msg('{} fee ({} confs): {} BTC/kB'.format(desc,opt.tx_confs,ret)) + msg('{} fee ({} confs): {} {}/kB'.format(desc,opt.tx_confs,ret,g.coin)) msg('TX size (estimated): {}'.format(tx.estimate_size())) return tx.get_usr_fee_interactive(start_fee,desc=desc) @@ -195,12 +191,12 @@ def get_inputs_from_user(tw,tx,caller): t_inputs = sum(s.amt for s in sel_unspent) if t_inputs < tx.send_amt: - msg(wmsg['not_enough_btc'] % (tx.send_amt - t_inputs)) + msg(wmsg['not_enough_btc'].format(tx.send_amt-t_inputs)) continue non_mmaddrs = [i for i in sel_unspent if i.twmmid.type == 'non-mmgen'] if non_mmaddrs and caller != 'txdo': - msg(wmsg['non_mmgen_inputs'] % ', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs])))) + msg(wmsg['non_mmgen_inputs'].format(', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs]))))) if not keypress_confirm('Accept?'): continue @@ -209,12 +205,12 @@ def get_inputs_from_user(tw,tx,caller): change_amt = tx.sum_inputs() - tx.send_amt - get_fee_from_estimate_or_user(tx) if change_amt >= 0: - p = 'Transaction produces %s BTC in change' % change_amt.hl() + p = 'Transaction produces {} {} in change'.format(change_amt.hl(),g.coin) if opt.yes or keypress_confirm(p+'. OK?',default_yes=True): if opt.yes: msg(p) return change_amt else: - msg(wmsg['not_enough_btc'] % abs(change_amt)) + msg(wmsg['not_enough_btc'].format(abs(change_amt))) def txcreate(cmd_args,do_info=False,caller='txcreate'): @@ -234,7 +230,9 @@ def txcreate(cmd_args,do_info=False,caller='txcreate'): tx.send_amt = tx.sum_outputs() - msg('Total amount to spend: %s' % ('Unknown','%s BTC'%tx.send_amt.hl())[bool(tx.send_amt)]) + msg('Total amount to spend: {}'.format( + ('Unknown','{} {}'.format(tx.send_amt.hl(),g.coin))[bool(tx.send_amt)] + )) change_amt = get_inputs_from_user(tw,tx,caller) diff --git a/mmgen/txsign.py b/mmgen/txsign.py index 9166dd3c..9f7abeab 100755 --- a/mmgen/txsign.py +++ b/mmgen/txsign.py @@ -57,12 +57,12 @@ column below: wmsg = { 'mapping_error': """ -{pnm} -> BTC address mappings differ! -%-23s %s -> %s -%-23s %s -> %s -""".strip().format(pnm=pnm), +{pnm} -> {c} address mappings differ! +{{:<23}} {{}} -> {{}} +{{:<23}} {{}} -> {{}} +""".strip().format(pnm=pnm,c=g.coin), 'missing_keys_error': """ -ERROR: a key file must be supplied for the following non-{pnm} address%s:\n %s +ERROR: a key file must be supplied for the following non-{pnm} address{{}}:\n {{}} """.format(pnm=pnm).strip() } @@ -106,7 +106,7 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None): if not need_keys: return [] desc,m1 = ('key-address file','From key-address file:') if keyaddr_list else \ ('seed(s)','Generated from seed:') - qmsg('Checking {} -> BTC address mappings for {} (from {})'.format(pnm,src,desc)) + qmsg('Checking {} -> {} address mappings for {} (from {})'.format(pnm,g.coin,src,desc)) d = keyaddr_list.flat_list() if keyaddr_list else \ generate_keys_for_mmgen_addrs([e.mmid for e in need_keys],infiles,saved_seeds) new_keys = [] @@ -118,7 +118,7 @@ def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None): if src == 'inputs': new_keys.append((f.addr,f.wif)) else: - die(3,wmsg['mapping_error'] % (m1,f.mmid,f.addr,'tx file:',e.mmid,e.addr)) + die(3,wmsg['mapping_error'].format(m1,f.mmid,f.addr,'tx file:',e.mmid,e.addr)) if new_keys: vmsg('Added %s wif key%s from %s' % (len(new_keys),suf(new_keys,'s'),desc)) return new_keys @@ -165,7 +165,7 @@ def txsign(opt,c,tx,seed_files,kl,kal,tx_num_str=''): tmp = KeyAddrList(addrlist=non_mm_addrs,do_chksum=False) tmp.add_wifs(kl) m = tmp.list_missing('wif') - if m: die(2,wmsg['missing_keys_error'] % (suf(m,'es'),'\n '.join(m))) + if m: die(2,wmsg['missing_keys_error'].format(suf(m,'es'),'\n '.join(m))) keys += tmp.get_addr_wif_pairs() if opt.mmgen_keys_from_file: diff --git a/mmgen/util.py b/mmgen/util.py index b6922eef..7167eba5 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -58,7 +58,8 @@ def Die(ev=0,s=''): sys.exit(ev) def rdie(ev=0,s=''): die(ev,red(s)) -def ydie(ev=0,s=''): die(ev,yellow(s)) +def wdie(ev=0,s=''): die(ev,yellow(s)) +def hi(): sys.stdout.write(yellow('hi')) def pformat(d): import pprint @@ -773,20 +774,16 @@ def get_bitcoind_auth_cookie(): return '' def bitcoin_connection(): - cfg = get_bitcoind_cfg_options(('rpcuser','rpcpassword')) - import mmgen.rpc - c = mmgen.rpc.BitcoinRPCConnection( - g.rpc_host or 'localhost', - g.rpc_port or (8332,18332)[g.testnet], - g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override bitcoind's - g.rpc_password or cfg['rpcpassword'], - auth_cookie=get_bitcoind_auth_cookie()) - # do an RPC call so we exit immediately if we can't connect - if not g.bitcoind_version: - g.bitcoind_version = int(c.getnetworkinfo()['version']) - g.chain = c.getblockchaininfo()['chain'] - if g.chain != 'regtest': g.chain += 'net' - assert g.chain in g.chains + + def check_coin_mismatch(c): + fb = '00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148' + err = [] + if int(c.getblockchaininfo()['blocks']) <= 478558 or c.getblockhash(478559) == fb: + if g.coin == 'BCH': err = 'BCH','BTC' + elif g.coin == 'BTC': err = 'BTC','BCH' + if err: wdie(2,"'{}' requested, but this is the {} chain!".format(*err)) + + def check_chain_mismatch(): err = None if g.regtest and g.chain != 'regtest': err = '--regtest option' @@ -797,4 +794,22 @@ def bitcoin_connection(): err = 'mainnet' if err: die(1,'{} selected but chain is {}'.format(err,g.chain)) + + cfg = get_bitcoind_cfg_options(('rpcuser','rpcpassword')) + import mmgen.rpc + c = mmgen.rpc.BitcoinRPCConnection( + g.rpc_host or 'localhost', + g.rpc_port or g.ports[g.coin][g.testnet], + g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override bitcoind's + g.rpc_password or cfg['rpcpassword'], + auth_cookie=get_bitcoind_auth_cookie()) + + if not g.bitcoind_version: # First call + g.bitcoind_version = int(c.getnetworkinfo()['version']) + g.chain = c.getblockchaininfo()['chain'] + if g.chain != 'regtest': + g.chain += 'net' + assert g.chain in g.chains + if g.chain == 'mainnet': + check_coin_mismatch(c) return c