Browse Source

Adds test case for fee bumping a tx using PSBT

Additionally, modify docstring of estimate_tx_size
master
Adam Gibson 4 years ago
parent
commit
635f3f10bc
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 10
      jmbitcoin/jmbitcoin/secp256k1_transaction.py
  2. 94
      jmclient/test/test_tx_creation.py

10
jmbitcoin/jmbitcoin/secp256k1_transaction.py

@ -110,10 +110,12 @@ def estimate_tx_size(ins, outs, txtype='p2pkh', outtype=None):
'''Estimate transaction size.
The txtype field as detailed below is used to distinguish
the type, but there is at least one source of meaningful roughness:
we assume the output types are the same as the input (to be fair,
outputs only contribute a little to the overall total). This combined
with a few bytes variation in signature sizes means we will expect,
say, 10% inaccuracy here.
we assume that the scriptPubKey type of all the outputs are the same as
the input, unless `outtype` is specified, in which case *one* of
the outputs is assumed to be that other type, with all of the other
outputs being of the same type as before.
This, combined with a few bytes variation in signature sizes means
we will sometimes see small inaccuracies in this estimate.
Assuming p2pkh:
out: 8+1+3+20+2=34, in: 32+4+1+1+~72+1+33+4=148,

94
jmclient/test/test_tx_creation.py

@ -11,7 +11,7 @@ from commontest import make_wallets, make_sign_and_push, ensure_bip65_activated
import jmbitcoin as bitcoin
import pytest
from jmbase import get_log
from jmclient import load_test_config, jm_single
from jmclient import load_test_config, jm_single, direct_send, estimate_tx_fee, compute_tx_locktime
log = get_log()
#just a random selection of pubkeys for receiving multisigs;
@ -86,6 +86,7 @@ def test_absurd_fees(setup_tx_creation):
ins_full = wallet_service.select_utxos(0, amount)
with pytest.raises(ValueError) as e_info:
txid = make_sign_and_push(ins_full, wallet_service, amount, estimate_fee=True)
jm_single().bc_interface.absurd_fees = False
def test_create_sighash_txs(setup_tx_creation):
#non-standard hash codes:
@ -137,6 +138,97 @@ def test_spend_p2wpkh(setup_tx_creation):
txid = jm_single().bc_interface.pushtx(tx.serialize())
assert txid
def test_spend_then_rbf(setup_tx_creation):
""" Test plan: first, create a normal spend with
rbf enabled in direct_send, then broadcast but
do not mine a block. Then create a re-spend of
the same utxos with a higher fee and check
that broadcast succeeds.
"""
# First phase: broadcast with RBF enabled.
#
# set a baseline feerate:
old_feerate = jm_single().config.get("POLICY", "tx_fees")
jm_single().config.set("POLICY", "tx_fees", "20000")
# set up a single wallet with some coins:
wallet_service = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet']
wallet_service.sync_wallet(fast=True)
# ensure selection of two utxos, doesn't really matter
# but a more general case than only one:
amount = 350000000
# destination doesn't matter; this is easiest:
destn = wallet_service.get_internal_addr(1)
# While `direct_send` usually encapsulates utxo selection
# for user, here we need to know what was chosen, hence
# we return the transaction object, not directly broadcast.
tx1 = direct_send(wallet_service, amount, 0,
destn, answeryes=True,
return_transaction=True,
optin_rbf=True)
assert tx1
# record the utxos for reuse:
assert isinstance(tx1, bitcoin.CTransaction)
utxos_objs = (x.prevout for x in tx1.vin)
utxos = [(x.hash[::-1], x.n) for x in utxos_objs]
# in order to sign on those utxos, we need their script and value.
scrs = {}
vals = {}
for u, details in wallet_service.get_utxos_by_mixdepth()[0].items():
if u in utxos:
scrs[u] = details["script"]
vals[u] = details["value"]
assert len(scrs.keys()) == 2
assert len(vals.keys()) == 2
# This will go to mempool but not get mined because
# we don't call `tick_forward_chain`.
push_succeed = jm_single().bc_interface.pushtx(tx1.serialize())
if push_succeed:
# mimics real operations with transaction monitor:
wallet_service.process_new_tx(tx1)
else:
assert False
# Second phase: bump fee.
#
# we set a larger fee rate.
jm_single().config.set("POLICY", "tx_fees", "30000")
# just a different destination to avoid confusion:
destn2 = wallet_service.get_internal_addr(2)
# We reuse *both* utxos so total fees are comparable
# (modulo tiny 1 byte differences in signatures).
# Ordinary wallet operations would remove the first-spent utxos,
# so for now we build a PSBT using the code from #921 to select
# the same utxos (it could be done other ways).
# Then we broadcast the PSBT and check it is allowed
# before constructing the outputs, we need a good fee estimate,
# using the bumped feerate:
fee = estimate_tx_fee(2, 2, wallet_service.get_txtype())
# reset the feerate:
total_input_val = sum(vals.values())
jm_single().config.set("POLICY", "tx_fees", old_feerate)
outs = [{"address": destn2, "value": 1000000},
{"address": wallet_service.get_internal_addr(0),
"value": total_input_val - 1000000 - fee}]
tx2 = bitcoin.mktx(utxos, outs, version=2,
locktime=compute_tx_locktime())
spent_outs = []
for u in utxos:
spent_outs.append(bitcoin.CTxOut(nValue=vals[u],
scriptPubKey=scrs[u]))
psbt_unsigned = wallet_service.create_psbt_from_tx(tx2,
spent_outs=spent_outs)
signresultandpsbt, err = wallet_service.sign_psbt(
psbt_unsigned.serialize(), with_sign_result=True)
assert not err
signresult, psbt_signed = signresultandpsbt
tx2_signed = psbt_signed.extract_transaction()
# the following assertion is sufficient, because
# tx broadcast would fail if the replacement were
# not allowed by Core:
assert jm_single().bc_interface.pushtx(tx2_signed.serialize())
def test_spend_freeze_script(setup_tx_creation):
ensure_bip65_activated()

Loading…
Cancel
Save