diff --git a/bitcoin/psbt.c b/bitcoin/psbt.c index ad9d2d05fc30..71f267e1da23 100644 --- a/bitcoin/psbt.c +++ b/bitcoin/psbt.c @@ -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); } @@ -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) diff --git a/bitcoin/psbt.h b/bitcoin/psbt.h index 1fc8482d604b..312dcf22e957 100644 --- a/bitcoin/psbt.h +++ b/bitcoin/psbt.h @@ -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 diff --git a/bitcoin/tx.c b/bitcoin/tx.c index 6c069d7212fe..714a3684f5e6 100644 --- a/bitcoin/tx.c +++ b/bitcoin/tx.c @@ -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, diff --git a/bitcoin/tx.h b/bitcoin/tx.h index 2db9e9506e01..c2e1bc299ec8 100644 --- a/bitcoin/tx.h +++ b/bitcoin/tx.h @@ -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 */ diff --git a/plugins/multifundchannel.c b/plugins/multifundchannel.c index 16e9fd83854f..d1f78a0390ad 100644 --- a/plugins/multifundchannel.c +++ b/plugins/multifundchannel.c @@ -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, @@ -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, @@ -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); diff --git a/plugins/txprepare.c b/plugins/txprepare.c index 7ce05cb48afd..bd579c8274fc 100644 --- a/plugins/txprepare.c +++ b/plugins/txprepare.c @@ -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); diff --git a/tests/test_plugin.py b/tests/test_plugin.py index f98b5dfdbe2e..4fb5ee7ea860 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -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'}, diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 8e285cfc8474..e9eedbc64ebc 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -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 @@ -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() @@ -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, @@ -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 @@ -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 diff --git a/wallet/reservation.c b/wallet/reservation.c index 867e3735adc7..1b87e6814e68 100644 --- a/wallet/reservation.c +++ b/wallet/reservation.c @@ -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;