Skip to content

Commit

Permalink
[tx]: BIP 125 replace-by-fee (RBF) support:
Browse files Browse the repository at this point in the history
      * create replaceable transactions with 'mmgen-txcreate' and 'mmgen-txdo'
        using the '--rbf' option
      * create replacement transactions from existing MMGen transactions
        using the new, optionally scriptable 'mmgen-txbump' command
      * Bitcoind must be started with the '-walletrbf' option to enable RBF
        functionality

[tx]: Command scriptability:
      * New '--yes' option makes 'txbump' and 'txsign' fully non-interactive
        and 'txcreate' and 'txsend' mostly non-interactive

[tx]: Satoshis-per-byte format:
      * Tx fees, both on the command line and at the interactive prompt, may be
        specified either as absolute BTC amounts or in satoshis-per-byte format
        (an integer followed by the letter 's')

[tx]: Fees
	  * Completely reworked fee-handling code with better fee checking
	  * default tx fee eliminated, max_tx_fee configurable in mmgen.cfg

Bugfixes and usability improvements:
      *	'mmgen-tool listaddresses' now list addresses from multiple seeds
        correctly
      * Improved user interaction with all 'mmgen-tx*' commands
  • Loading branch information
mmgen committed May 17, 2017
1 parent 8556de2 commit 93c9975
Show file tree
Hide file tree
Showing 16 changed files with 553 additions and 151 deletions.
4 changes: 2 additions & 2 deletions data_files/mmgen.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
# A value of 0 disables user entropy, but this is not recommended:
# usr_randchars 30

# Set the default transaction fee in BTC:
# tx_fee 0.0003
# Set the maximum transaction fee in BTC:
# max_tx_fee 0.01

# Set the transaction fee adjustment factor. Auto-calculated fees are
# multiplied by this value:
Expand Down
25 changes: 25 additions & 0 deletions mmgen-txbump
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python

# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.

"""
mmgen-txbump: Increase the fee on a replaceable (RBF) MMGen transaction, and
optionally sign and send it.
"""

from mmgen.main import launch
launch("txbump")
19 changes: 10 additions & 9 deletions mmgen/globalvars.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ def die(ev=0,s=''):
sys.exit(ev)
# Variables - these might be altered at runtime:

version = '0.9.0'
release_date = 'December 2016'
version = '0.9.O+'
release_date = 'May 2017'

proj_name = 'MMGen'
proj_url = 'https://github.com/mmgen/mmgen'
Expand All @@ -51,13 +51,14 @@ def die(ev=0,s=''):
hash_preset = '3'
usr_randchars = 30

tx_fee = BTCAmt('0.0003')
tx_fee_adj = 1.0
tx_confs = 3

max_tx_fee = BTCAmt('0.01')
tx_fee_adj = 1.0
tx_confs = 3
satoshi = BTCAmt('0.00000001') # One bitcoin equals 100,000,000 satoshis
seed_len = 256

http_timeout = 60
max_int = 0xffffffff

# Constants - some of these might be overriden, but they don't change thereafter

Expand Down Expand Up @@ -111,8 +112,8 @@ def die(ev=0,s=''):
)
cfg_file_opts = (
'color','debug','hash_preset','http_timeout','no_license','rpc_host','rpc_port',
'quiet','tx_fee','tx_fee_adj','usr_randchars','testnet','rpc_user','rpc_password',
'bitcoin_data_dir','force_256_color'
'quiet','tx_fee_adj','usr_randchars','testnet','rpc_user','rpc_password',
'bitcoin_data_dir','force_256_color','max_tx_fee'
)
env_opts = (
'MMGEN_BOGUS_WALLET_DATA',
Expand All @@ -132,7 +133,7 @@ def die(ev=0,s=''):

# Global var sets user opt:
global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',
'quiet','tx_confs','tx_fee_adj','tx_fee','key_generator']
'quiet','tx_confs','tx_fee_adj','key_generator']

keyconv_exec = 'keyconv'

Expand Down
133 changes: 133 additions & 0 deletions mmgen/main_txbump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""
mmgen-txbump: Increase the fee on a replaceable (replace-by-fee) MMGen
transaction, and optionally sign and send it
"""

from mmgen.txcreate import *
from mmgen.txsign import *

opts_data = {
'desc': 'Increase the fee on a replaceable (RBF) {g.proj_name} transaction, creating a new transaction, and optionally sign and send the new transaction'.format(g=g),
'usage': '[opts] <{g.proj_name} TX file> [seed source] ...'.format(g=g),
'sets': ( ('yes', True, 'quiet', True), ),
'options': """
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for
brainwallet input
-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
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)
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
is required only for brainwallet and incognito inputs
with non-standard (< {g.seed_len}-bit) seed lengths.
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
-K, --key-generator= m Use method 'm' for public key generation
Options: {kgs}
(default: {kg})
-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
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)
-O, --old-incog-fmt Specify old-format incognito input
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
-P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
-q, --quiet Suppress warnings; overwrite files without prompting
-s, --send Sign and send the transaction (the default if seed
data is provided)
-v, --verbose Produce more verbose output
-y, --yes Answer 'yes' to prompts, suppress non-essential output
-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),
'notes': '\n' + fee_notes + txsign_notes
}

cmd_args = opts.init(opts_data)

c = bitcoin_connection()

tx_file = cmd_args.pop(0)
check_infile(tx_file)

seed_files = get_seed_files(opt,cmd_args) if (cmd_args or opt.send) else None
kal = get_keyaddrlist(opt)
kl = get_keylist(opt)

tx = MMGenBumpTX(filename=tx_file,send=(seed_files or kl or kal))

do_license_msg()

silent = opt.yes and opt.tx_fee != None and opt.output_to_reduce != None

if not silent:
msg(green('ORIGINAL TRANSACTION'))
msg(tx.format_view(terse=True))

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))

msg('Creating new transaction')

op_idx = tx.choose_output()

if not silent:
msg('Minimum fee for new transaction: {} BTC'.format(tx.min_fee))

fee = tx.get_usr_fee_interactive(tx_fee=opt.tx_fee,desc='User-selected')

tx.update_output_amt(op_idx,tx.sum_inputs()-tx.sum_outputs(exclude=op_idx)-fee)

d = tx.get_fee()
assert d == fee and d <= g.max_tx_fee

if not opt.yes:
tx.add_comment() # edits an existing comment
tx.create_raw(c) # creates tx.hex, tx.txid
tx.add_timestamp()
tx.add_blockcount(c)

qmsg('Fee successfully increased')

if not silent:
msg(green('\nREPLACEMENT TRANSACTION:'))
msg_r(tx.format_view(terse=True))

if seed_files or kl or kal:
txsign(opt,c,tx,seed_files,kl,kal)
tx.write_to_file(ask_write=False)
if tx.send(c):
tx.write_to_file(ask_write=False)
else:
tx.write_to_file(ask_write=not opt.yes,ask_write_default_yes=False,ask_overwrite=not opt.yes)
34 changes: 20 additions & 14 deletions mmgen/main_txcreate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,30 @@
opts_data = {
'desc': 'Create a transaction with outputs to specified Bitcoin or {g.proj_name} addresses'.format(g=g),
'usage': '[opts] <addr,amt> ... [change addr] [addr file] ...',
'sets': ( ('yes', True, 'quiet', True), ),
'options': """
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-a, --tx-fee-adj= f Adjust transaction fee by factor 'f' (see below)
-B, --no-blank Don't blank screen before displaying unspent outputs
-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 (default: {g.tx_fee} BTC (but see below))
-i, --info Display unspent outputs and exit
-m, --minconf= n Minimum number of confirmations required to spend outputs (default: 1)
-q, --quiet Suppress warnings; overwrite files without prompting
-v, --verbose Produce more verbose output
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-a, --tx-fee-adj= f Adjust transaction fee by factor 'f' (see below)
-B, --no-blank Don't blank screen before displaying unspent outputs
-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
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
-m, --minconf= n Minimum number of confirmations required to spend
outputs (default: 1)
-q, --quiet Suppress warnings; overwrite files without prompting
-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),
'notes': '\n' + txcreate_notes
'notes': '\n' + txcreate_notes + fee_notes
}

cmd_args = opts.init(opts_data)
do_license_msg()
tx = txcreate(opt,cmd_args,do_info=opt.info)
tx.write_to_file(ask_write_default_yes=False)
tx.write_to_file(ask_write=not opt.yes,ask_overwrite=not opt.yes,ask_write_default_yes=False)
14 changes: 10 additions & 4 deletions mmgen/main_txdo.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
opts_data = {
'desc': 'Create, sign and send an {g.proj_name} transaction'.format(g=g),
'usage': '[opts] <addr,amt> ... [change addr] [addr file] ... [seed source] ...',
'sets': ( ('yes', True, 'quiet', True), ),
'options': """
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
Expand All @@ -37,7 +38,10 @@
-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 (default: {g.tx_fee} BTC (but see below))
-f, --tx-fee= f Transaction fee, as a decimal BTC amount or in
satoshis per byte (an integer followed by 's').
If omitted, bitcoind's 'estimatefee' will be used
to calculate the fee.
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
'f' at offset 'o' (comma-separated)
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
Expand All @@ -59,13 +63,15 @@
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
for password hashing (default: '{g.hash_preset}')
-P, --passwd-file= f Get {pnm} wallet passphrase from file 'f'
-r, --rbf Make transaction BIP 125 (replace-by-fee) replaceable
-q, --quiet Suppress warnings; overwrite files without prompting
-v, --verbose Produce more verbose output
-y, --yes Answer 'yes' to prompts, suppress non-essential output
-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),
'notes': '\n' + txcreate_notes + txsign_notes
'notes': '\n' + txcreate_notes + fee_notes + txsign_notes
}

cmd_args = opts.init(opts_data)
Expand All @@ -81,5 +87,5 @@
txsign(opt,c,tx,seed_files,kl,kal)
tx.write_to_file(ask_write=False)

if tx.send(opt,c):
tx.write_to_file(ask_write=False)
if tx.send(c):
tx.write_to_file(ask_overwrite=False,ask_write=False)
21 changes: 15 additions & 6 deletions mmgen/main_txsend.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
'desc': 'Send a Bitcoin transaction signed by {pnm}-txsign'.format(
pnm=g.proj_name.lower()),
'usage': '[opts] <signed transaction file>',
'sets': ( ('yes', True, 'quiet', True), ),
'options': """
-h, --help Print this help message
--, --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
-y, --yes Answer 'yes' to prompts, suppress non-essential output
"""
}

Expand All @@ -44,12 +46,19 @@
do_license_msg()
tx = MMGenTX(infile)
c = bitcoin_connection()

if not tx.check_signed(c):
die(1,'Transaction has no signature!')
die(1,'Transaction is not signed!')

if tx.btc_txid:
msg('Warning: transaction has already been sent!')

qmsg("Signed transaction file '%s' is valid" % infile)
tx.view_with_prompt('View transaction data?')
if tx.add_comment(): # edits an existing comment, returns true if changed
tx.write_to_file(ask_write_default_yes=True)

if tx.send(opt,c):
tx.write_to_file(ask_write=False)
if not opt.yes:
tx.view_with_prompt('View transaction data?')
if tx.add_comment(): # edits an existing comment, returns true if changed
tx.write_to_file(ask_write_default_yes=True)

if tx.send(c):
tx.write_to_file(ask_overwrite=False,ask_write=False)
16 changes: 11 additions & 5 deletions mmgen/main_txsign.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@
opts_data = {
'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()),
'usage': '[opts] <transaction file>... [seed source]...',
'sets': ( ('yes', True, 'quiet', True), ),
'options': """
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for brainwallet
input
-b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for
brainwallet input
-d, --outdir= d Specify an alternate directory 'd' for output
-D, --tx-id Display transaction ID and exit
-e, --echo-passphrase Print passphrase to screen when typing it
Expand All @@ -57,6 +58,7 @@
-I, --info Display information about the transaction and exit
-t, --terse-info Like '--info', but produce more concise output
-v, --verbose Produce more verbose output
-y, --yes Answer 'yes' to prompts, suppress non-essential output
""".format(
g=g,pnm=pnm,pnl=pnm.lower(),
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
Expand Down Expand Up @@ -98,8 +100,12 @@
if opt.info or opt.terse_info:
tx.view(pause=False,terse=opt.terse_info); continue

tx.view_with_prompt('View data for transaction%s?' % tx_num_str)
if not opt.yes:
tx.view_with_prompt('View data for transaction%s?' % tx_num_str)

txsign(opt,c,tx,seed_files,kl,kal,tx_num_str)
tx.add_comment() # edits an existing comment
tx.write_to_file(ask_write_default_yes=True,add_desc=tx_num_str)

if not opt.yes:
tx.add_comment() # edits an existing comment

tx.write_to_file(ask_write=not opt.yes,ask_write_default_yes=True,add_desc=tx_num_str)
2 changes: 1 addition & 1 deletion mmgen/obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def fmtc(cls,s,width=None,color=False,encl='',trunc_ok=None,
if trunc_ok and len(s) > width: s = s[:width]
if app:
return cls.colorize(a+s+b,color=color) + \
cls.colorize(app.ljust(width-len(a+s+b)),color=appcolor)
cls.colorize(app.ljust(width-len(a+s+b)),color=appcolor)
else:
return cls.colorize((a+s+b).ljust(width),color=color)

Expand Down
Loading

0 comments on commit 93c9975

Please sign in to comment.