Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elements PSBT / test fixup #4033

Merged
merged 10 commits into from
Sep 10, 2020
79 changes: 73 additions & 6 deletions bitcoin/psbt.c
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,29 @@ void psbt_input_set_wit_utxo(struct wally_psbt *psbt, size_t in,

assert(in < psbt->num_inputs);
assert(tal_bytelen(scriptPubkey) > 0);
wally_err = wally_tx_output_init(amt.satoshis, /* Raw: type conv */
scriptPubkey,
tal_bytelen(scriptPubkey),
&tx_out);
if (is_elements(chainparams)) {
u8 value[9];
wally_err =
wally_tx_confidential_value_from_satoshi(amt.satoshis, /* Raw: wally API */
value,
sizeof(value));
assert(wally_err == WALLY_OK);
wally_err =
wally_tx_elements_output_init(scriptPubkey,
tal_bytelen(scriptPubkey),
chainparams->fee_asset_tag,
ELEMENTS_ASSET_LEN,
value, sizeof(value),
NULL, 0, NULL, 0,
NULL, 0, &tx_out);

} else
wally_err = wally_tx_output_init(amt.satoshis, /* Raw: type conv */
scriptPubkey,
tal_bytelen(scriptPubkey),
&tx_out);
assert(wally_err == WALLY_OK);
wally_err = wally_psbt_input_set_witness_utxo(&psbt->inputs[in],
&tx_out);
wally_err = wally_psbt_input_set_witness_utxo(&psbt->inputs[in], &tx_out);
assert(wally_err == WALLY_OK);
}

Expand Down Expand Up @@ -292,6 +308,57 @@ void psbt_elements_input_set_asset(struct wally_psbt *psbt, size_t in,
abort();
}

void psbt_elements_normalize_fees(struct wally_psbt *psbt)
{
struct amount_asset asset;
size_t fee_output_idx = psbt->num_outputs;

if (!is_elements(chainparams))
return;

/* Elements requires that every input value is accounted for,
* including the fees */
struct amount_sat total_in = AMOUNT_SAT(0), val;
for (size_t i = 0; i < psbt->num_inputs; i++) {
val = psbt_input_get_amount(psbt, i);
if (!amount_sat_add(&total_in, total_in, val))
return;
}
for (size_t i = 0; i < psbt->num_outputs; i++) {
asset = wally_tx_output_get_amount(&psbt->tx->outputs[i]);
if (elements_wtx_output_is_fee(psbt->tx, i)) {
if (fee_output_idx == psbt->num_outputs) {
fee_output_idx = i;
continue;
}
/* We already have at least one fee output,
* remove this one */
psbt_rm_output(psbt, i--);
continue;
}
if (!amount_asset_is_main(&asset))
continue;

if (!amount_sat_sub(&total_in, total_in,
amount_asset_to_sat(&asset)))
return;
}

if (amount_sat_eq(total_in, AMOUNT_SAT(0)))
return;

/* We need to add a fee output */
if (fee_output_idx == psbt->num_outputs) {
psbt_append_output(psbt, NULL, total_in);
} else {
u64 sats = total_in.satoshis; /* Raw: wally API */
struct wally_tx_output *out = &psbt->tx->outputs[fee_output_idx];
if (wally_tx_confidential_value_from_satoshi(
sats, out->value, out->value_len) != WALLY_OK)
return;
}
}

bool psbt_has_input(struct wally_psbt *psbt,
struct bitcoin_txid *txid,
u32 outnum)
Expand Down
8 changes: 8 additions & 0 deletions bitcoin/psbt.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ bool psbt_is_finalized(const struct wally_psbt *psbt);
void psbt_txid(const struct wally_psbt *psbt, struct bitcoin_txid *txid,
struct wally_tx **wtx);

/* psbt_elements_normalize_fees - Figure out the fee output for a PSET
*
* Adds a fee output if not present, or updates it to include the diff
* between inputs - outputs. Unlike bitcoin, elements requires every
* satoshi to be accounted for in an output.
*/
void psbt_elements_normalize_fees(struct wally_psbt *psbt);

struct wally_tx *psbt_finalize(struct wally_psbt *psbt, bool finalize_in_place);

/* psbt_make_key - Create a new, proprietary c-lightning key
Expand Down
11 changes: 8 additions & 3 deletions bitcoin/tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,16 @@ int bitcoin_tx_add_multi_outputs(struct bitcoin_tx *tx,
return tx->wtx->num_outputs;
}

bool elements_tx_output_is_fee(const struct bitcoin_tx *tx, int outnum)
bool elements_wtx_output_is_fee(const struct wally_tx *tx, int outnum)
{
assert(outnum < tx->wtx->num_outputs);
assert(outnum < tx->num_outputs);
return chainparams->is_elements &&
tx->wtx->outputs[outnum].script_len == 0;
tx->outputs[outnum].script_len == 0;
}

bool elements_tx_output_is_fee(const struct bitcoin_tx *tx, int outnum)
{
return elements_wtx_output_is_fee(tx->wtx, outnum);
}

struct amount_sat bitcoin_tx_compute_fee_w_inputs(const struct bitcoin_tx *tx,
Expand Down
5 changes: 5 additions & 0 deletions bitcoin/tx.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ bool bitcoin_tx_check(const struct bitcoin_tx *tx);
*/
void bitcoin_tx_finalize(struct bitcoin_tx *tx);

/**
* Returns true if the given outnum is a fee output
*/
bool elements_wtx_output_is_fee(const struct wally_tx *tx, int outnum);

/**
* Returns true if the given outnum is a fee output
*/
Expand Down
35 changes: 7 additions & 28 deletions plugins/multifundchannel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1362,9 +1362,10 @@ perform_funding_tx_finalize(struct multifundchannel_command *mfc)
/* Funding outpoint. */
struct multifundchannel_destination *dest;
dest = deck[outnum];
(void) psbt_append_output(mfc->psbt,
(void) psbt_insert_output(mfc->psbt,
dest->funding_script,
dest->amount);
dest->amount,
outnum);
dest->outnum = outnum;
tal_append_fmt(&content, "%s: %s",
type_to_string(tmpctx, struct node_id,
Expand All @@ -1375,9 +1376,10 @@ perform_funding_tx_finalize(struct multifundchannel_command *mfc)
} else {
/* Change output. */
assert(mfc->change_needed);
(void) psbt_append_output(mfc->psbt,
(void) psbt_insert_output(mfc->psbt,
mfc->change_scriptpubkey,
mfc->change_amount);
mfc->change_amount,
outnum);
tal_append_fmt(&content, "change: %s",
type_to_string(tmpctx,
struct amount_sat,
Expand All @@ -1386,30 +1388,7 @@ perform_funding_tx_finalize(struct multifundchannel_command *mfc)
}

/* Elements requires a fee output. */
if (chainparams->is_elements) {
struct amount_sat inputs = AMOUNT_SAT(0);
struct amount_sat outputs = AMOUNT_SAT(0);
struct amount_sat fees;
for (size_t i = 0; i < mfc->psbt->num_inputs; ++i)
if (!amount_sat_add(&inputs,
inputs,
psbt_input_get_amount(mfc->psbt,
i)))
plugin_err(mfc->cmd->plugin,
"Overflow while adding inputs");
for (size_t i = 0; i < mfc->psbt->num_outputs; ++i)
if (!amount_sat_add(&outputs,
outputs,
psbt_output_get_amount(mfc->psbt,
i)))
plugin_err(mfc->cmd->plugin,
"Overflow while adding outputs");
if (!amount_sat_sub(&fees, inputs, outputs))
fees = AMOUNT_SAT(0);
/* If there is any fee at all, add the fee output. */
if (!amount_sat_eq(fees, AMOUNT_SAT(0)))
(void) psbt_append_output(mfc->psbt, NULL, fees);
}
psbt_elements_normalize_fees(mfc->psbt);

/* Generate the TXID. */
mfc->txid = tal(mfc, struct bitcoin_txid);
Expand Down
4 changes: 4 additions & 0 deletions plugins/txprepare.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ static struct command_result *finish_txprepare(struct command *cmd,
psbt_add_output(txp->psbt, out, i);
}

/* If this is elements, we should normalize
* the PSBT fee output */
psbt_elements_normalize_fees(txp->psbt);

utx = tal(NULL, struct unreleased_tx);
utx->psbt = tal_steal(utx, txp->psbt);
psbt_txid(txp->psbt, &utx->txid, &utx->tx);
Expand Down
6 changes: 4 additions & 2 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1488,8 +1488,10 @@ def test_coin_movement_notices(node_factory, bitcoind, chainparams):
l2_wallet_mvts = [
{'type': 'chain_mvt', 'credit': 2000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 991900000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
[
{'type': 'chain_mvt', 'credit': 0, 'debit': 991900000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
],
{'type': 'chain_mvt', 'credit': 0, 'debit': 8100000, 'tag': 'chain_fees'},
{'type': 'chain_mvt', 'credit': 991900000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 100001000, 'debit': 0, 'tag': 'deposit'},
Expand Down
19 changes: 13 additions & 6 deletions tests/test_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ def test_txprepare(node_factory, bitcoind, chainparams):
assert len(decode['vout']) == 3 if chainparams['feeoutput'] else 2
# Change output pos is random.
for vout in decode['vout']:
if vout['scriptPubKey']['type'] == 'fee':
continue
if vout['scriptPubKey']['addresses'] == [addr]:
changeout = vout

Expand Down Expand Up @@ -532,7 +534,7 @@ def test_fundpsbt(node_factory, bitcoind, chainparams):
l1.rpc.fundpsbt(amount // 2, feerate, 0)


def test_utxopsbt(node_factory, bitcoind):
def test_utxopsbt(node_factory, bitcoind, chainparams):
amount = 1000000
l1 = node_factory.get_node()

Expand All @@ -548,7 +550,8 @@ def test_utxopsbt(node_factory, bitcoind):
bitcoind.generate_block(1)
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == len(outputs))

feerate = '7500perkw'
fee_val = 7500
feerate = '{}perkw'.format(fee_val)

# Explicitly spend the first output above.
funding = l1.rpc.utxopsbt(amount // 2, feerate, 0,
Expand All @@ -566,13 +569,19 @@ def test_utxopsbt(node_factory, bitcoind):
assert 'reservations' not in funding

# This should add 99 to the weight, but otherwise be identical except for locktime.
funding2 = l1.rpc.utxopsbt(amount // 2, feerate, 99,
start_weight = 99
funding2 = l1.rpc.utxopsbt(amount // 2, feerate, start_weight,
['{}:{}'.format(outputs[0][0], outputs[0][1])],
reserve=False, locktime=bitcoind.rpc.getblockcount() + 1)
psbt2 = bitcoind.rpc.decodepsbt(funding2['psbt'])
assert psbt2['tx']['locktime'] == bitcoind.rpc.getblockcount() + 1
assert psbt2['tx']['vin'] == psbt['tx']['vin']
assert psbt2['tx']['vout'] == psbt['tx']['vout']
if chainparams['elements']:
# elements includes the fee as an output
addl_fee = Millisatoshi(fee_val * start_weight // 1000 * 1000)
assert psbt2['tx']['vout'][0]['value'] == psbt['tx']['vout'][0]['value'] + addl_fee.to_btc()
else:
assert psbt2['tx']['vout'] == psbt['tx']['vout']
assert funding2['excess_msat'] < funding['excess_msat']
assert funding2['feerate_per_kw'] == 7500
assert funding2['estimated_final_weight'] == funding['estimated_final_weight'] + 99
Expand Down Expand Up @@ -618,8 +627,6 @@ def test_utxopsbt(node_factory, bitcoind):
reservedok=True)


@unittest.skipIf(TEST_NETWORK == 'liquid-regtest',
"See ElementsProject/lightning#3998")
def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
"""
Tests for the sign + send psbt RPCs
Expand Down
10 changes: 10 additions & 0 deletions wallet/reservation.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,16 @@ static struct wally_psbt *psbt_using_utxos(const tal_t *ctx,
NULL, redeemscript);

psbt_input_set_wit_utxo(psbt, i, scriptPubkey, utxos[i]->amount);
if (is_elements(chainparams)) {
struct amount_asset asset;
/* FIXME: persist asset tags */
asset = amount_sat_to_asset(&utxos[i]->amount,
chainparams->fee_asset_tag);
/* FIXME: persist nonces */
psbt_elements_input_set_asset(psbt,
psbt->num_inputs - 1,
&asset);
}
}

return psbt;
Expand Down