mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-20 07:34:24 +01:00
rpc: new signpsbt + sendpsbt rpcs
Changelog-Added: JSON-RPC: new call `signpsbt` which will add the wallet's signatures to a provided psbt Changelog-Added: JSON-RPC: new call `sendpsbt` which will finalize and send a signed PSBT
This commit is contained in:
committed by
Christian Decker
parent
fd8a716695
commit
9830c94778
@@ -1129,6 +1129,24 @@ class LightningRpc(UnixDomainSocketRpc):
|
|||||||
}
|
}
|
||||||
return self.call("unreserveinputs", payload)
|
return self.call("unreserveinputs", payload)
|
||||||
|
|
||||||
|
def signpsbt(self, psbt):
|
||||||
|
"""
|
||||||
|
Add internal wallet's signatures to PSBT
|
||||||
|
"""
|
||||||
|
payload = {
|
||||||
|
"psbt": psbt,
|
||||||
|
}
|
||||||
|
return self.call("signpsbt", payload)
|
||||||
|
|
||||||
|
def sendpsbt(self, psbt):
|
||||||
|
"""
|
||||||
|
Finalize extract and broadcast a PSBT
|
||||||
|
"""
|
||||||
|
payload = {
|
||||||
|
"psbt": psbt,
|
||||||
|
}
|
||||||
|
return self.call("sendpsbt", payload)
|
||||||
|
|
||||||
def signmessage(self, message):
|
def signmessage(self, message):
|
||||||
"""
|
"""
|
||||||
Sign a message with this node's secret key.
|
Sign a message with this node's secret key.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from bitcoin.rpc import JSONRPCError
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from fixtures import * # noqa: F401,F403
|
from fixtures import * # noqa: F401,F403
|
||||||
from fixtures import TEST_NETWORK
|
from fixtures import TEST_NETWORK
|
||||||
@@ -518,7 +519,7 @@ def test_reserveinputs(node_factory, bitcoind, chainparams):
|
|||||||
unreserve_psbt = bitcoind.rpc.createpsbt(unreserve_utxos, [])
|
unreserve_psbt = bitcoind.rpc.createpsbt(unreserve_utxos, [])
|
||||||
|
|
||||||
unreserved = l1.rpc.unreserveinputs(unreserve_psbt)
|
unreserved = l1.rpc.unreserveinputs(unreserve_psbt)
|
||||||
assert unreserved['all_unreserved']
|
assert all([x['unreserved'] for x in unreserved['outputs']])
|
||||||
outputs = l1.rpc.listfunds()['outputs']
|
outputs = l1.rpc.listfunds()['outputs']
|
||||||
assert len([x for x in outputs if not x['reserved']]) == len(unreserved['outputs'])
|
assert len([x for x in outputs if not x['reserved']]) == len(unreserved['outputs'])
|
||||||
for i in range(len(unreserved['outputs'])):
|
for i in range(len(unreserved['outputs'])):
|
||||||
@@ -531,7 +532,7 @@ def test_reserveinputs(node_factory, bitcoind, chainparams):
|
|||||||
unreserve_utxos.append({'txid': 'b' * 64, 'vout': 0, 'sequence': 0})
|
unreserve_utxos.append({'txid': 'b' * 64, 'vout': 0, 'sequence': 0})
|
||||||
unreserve_psbt = bitcoind.rpc.createpsbt(unreserve_utxos, [])
|
unreserve_psbt = bitcoind.rpc.createpsbt(unreserve_utxos, [])
|
||||||
unreserved = l1.rpc.unreserveinputs(unreserve_psbt)
|
unreserved = l1.rpc.unreserveinputs(unreserve_psbt)
|
||||||
assert not unreserved['all_unreserved']
|
assert not any([x['unreserved'] for x in unreserved['outputs']])
|
||||||
for un in unreserved['outputs']:
|
for un in unreserved['outputs']:
|
||||||
assert not un['unreserved']
|
assert not un['unreserved']
|
||||||
assert len([x for x in l1.rpc.listfunds()['outputs'] if not x['reserved']]) == 3
|
assert len([x for x in l1.rpc.listfunds()['outputs'] if not x['reserved']]) == 3
|
||||||
@@ -561,6 +562,122 @@ def test_reserveinputs(node_factory, bitcoind, chainparams):
|
|||||||
assert len(l1.rpc.listfunds()['outputs']) == 12
|
assert len(l1.rpc.listfunds()['outputs']) == 12
|
||||||
|
|
||||||
|
|
||||||
|
def test_sign_and_send_psbt(node_factory, bitcoind, chainparams):
|
||||||
|
"""
|
||||||
|
Tests for the sign + send psbt RPCs
|
||||||
|
"""
|
||||||
|
amount = 1000000
|
||||||
|
total_outs = 12
|
||||||
|
l1 = node_factory.get_node(feerates=(7500, 7500, 7500, 7500))
|
||||||
|
l2 = node_factory.get_node()
|
||||||
|
addr = chainparams['example_addr']
|
||||||
|
|
||||||
|
# Add a medley of funds to withdraw later, bech32 + p2sh-p2wpkh
|
||||||
|
for i in range(total_outs // 2):
|
||||||
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'],
|
||||||
|
amount / 10**8)
|
||||||
|
bitcoind.rpc.sendtoaddress(l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'],
|
||||||
|
amount / 10**8)
|
||||||
|
bitcoind.generate_block(1)
|
||||||
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == total_outs)
|
||||||
|
|
||||||
|
# Make a PSBT out of our inputs
|
||||||
|
reserved = l1.rpc.reserveinputs(outputs=[{addr: Millisatoshi(3 * amount * 1000)}])
|
||||||
|
assert len([x for x in l1.rpc.listfunds()['outputs'] if x['reserved']]) == 4
|
||||||
|
psbt = bitcoind.rpc.decodepsbt(reserved['psbt'])
|
||||||
|
saved_input = psbt['tx']['vin'][0]
|
||||||
|
|
||||||
|
# Go ahead and unreserve the UTXOs, we'll use a smaller
|
||||||
|
# set of them to create a second PSBT that we'll attempt to sign
|
||||||
|
# and broadcast (to disastrous results)
|
||||||
|
l1.rpc.unreserveinputs(reserved['psbt'])
|
||||||
|
|
||||||
|
# Re-reserve one of the utxos we just unreserved
|
||||||
|
utxos = []
|
||||||
|
utxos.append(saved_input['txid'] + ":" + str(saved_input['vout']))
|
||||||
|
second_reservation = l1.rpc.reserveinputs([{addr: Millisatoshi(amount * 0.5 * 1000)}], feerate='253perkw', utxos=utxos)
|
||||||
|
|
||||||
|
# We require the utxos be reserved before signing them
|
||||||
|
with pytest.raises(RpcError, match=r"Aborting PSBT signing. UTXO .* is not reserved"):
|
||||||
|
l1.rpc.signpsbt(reserved['psbt'])['signed_psbt']
|
||||||
|
|
||||||
|
# Now we unreserve the singleton, so we can reserve it again
|
||||||
|
l1.rpc.unreserveinputs(second_reservation['psbt'])
|
||||||
|
|
||||||
|
# We re-reserve the first set...
|
||||||
|
utxos = []
|
||||||
|
for vin in psbt['tx']['vin']:
|
||||||
|
utxos.append(vin['txid'] + ':' + str(vin['vout']))
|
||||||
|
reserved = l1.rpc.reserveinputs(outputs=[{addr: Millisatoshi(3 * amount * 1000)}], utxos=utxos)
|
||||||
|
# Sign + send the PSBT we've created
|
||||||
|
signed_psbt = l1.rpc.signpsbt(reserved['psbt'])['signed_psbt']
|
||||||
|
broadcast_tx = l1.rpc.sendpsbt(signed_psbt)
|
||||||
|
|
||||||
|
# Check that it was broadcast successfully
|
||||||
|
l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format(broadcast_tx['tx']))
|
||||||
|
bitcoind.generate_block(1)
|
||||||
|
|
||||||
|
# We expect a change output to be added to the wallet
|
||||||
|
expected_outs = total_outs - 4 + 1
|
||||||
|
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == expected_outs)
|
||||||
|
|
||||||
|
# Let's try *sending* a PSBT that can't be finalized (it's unsigned)
|
||||||
|
with pytest.raises(RpcError, match=r"PSBT not finalizeable"):
|
||||||
|
l1.rpc.sendpsbt(second_reservation['psbt'])
|
||||||
|
|
||||||
|
# Now we try signing a PSBT with an output that's already been spent
|
||||||
|
with pytest.raises(RpcError, match=r"Aborting PSBT signing. UTXO {} is not reserved".format(utxos[0])):
|
||||||
|
l1.rpc.signpsbt(second_reservation['psbt'])
|
||||||
|
|
||||||
|
# Queue up another node, to make some PSBTs for us
|
||||||
|
for i in range(total_outs // 2):
|
||||||
|
bitcoind.rpc.sendtoaddress(l2.rpc.newaddr()['bech32'],
|
||||||
|
amount / 10**8)
|
||||||
|
bitcoind.rpc.sendtoaddress(l2.rpc.newaddr('p2sh-segwit')['p2sh-segwit'],
|
||||||
|
amount / 10**8)
|
||||||
|
# Create a PSBT using L2
|
||||||
|
bitcoind.generate_block(1)
|
||||||
|
wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == total_outs)
|
||||||
|
l2_reserved = l2.rpc.reserveinputs(outputs=[{addr: Millisatoshi(3 * amount * 1000)}])
|
||||||
|
|
||||||
|
# Try to get L1 to sign it
|
||||||
|
with pytest.raises(RpcError, match=r"No wallet inputs to sign"):
|
||||||
|
l1.rpc.signpsbt(l2_reserved['psbt'])
|
||||||
|
|
||||||
|
# Add some of our own PSBT inputs to it
|
||||||
|
l1_reserved = l1.rpc.reserveinputs(outputs=[{addr: Millisatoshi(3 * amount * 1000)}])
|
||||||
|
joint_psbt = bitcoind.rpc.joinpsbts([l1_reserved['psbt'], l2_reserved['psbt']])
|
||||||
|
|
||||||
|
half_signed_psbt = l1.rpc.signpsbt(joint_psbt)['signed_psbt']
|
||||||
|
totally_signed = l2.rpc.signpsbt(half_signed_psbt)['signed_psbt']
|
||||||
|
|
||||||
|
broadcast_tx = l1.rpc.sendpsbt(totally_signed)
|
||||||
|
l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format(broadcast_tx['tx']))
|
||||||
|
|
||||||
|
# Send a PSBT that's not ours
|
||||||
|
l2_reserved = l2.rpc.reserveinputs(outputs=[{addr: Millisatoshi(3 * amount * 1000)}])
|
||||||
|
l2_signed_psbt = l2.rpc.signpsbt(l2_reserved['psbt'])['signed_psbt']
|
||||||
|
l1.rpc.sendpsbt(l2_signed_psbt)
|
||||||
|
|
||||||
|
# Re-try sending the same tx?
|
||||||
|
bitcoind.generate_block(1)
|
||||||
|
sync_blockheight(bitcoind, [l1])
|
||||||
|
# Expect an error here
|
||||||
|
with pytest.raises(JSONRPCError, match=r"Transaction already in block chain"):
|
||||||
|
bitcoind.rpc.sendrawtransaction(broadcast_tx['tx'])
|
||||||
|
|
||||||
|
# Try an empty PSBT
|
||||||
|
with pytest.raises(RpcError, match=r"should be a PSBT, not"):
|
||||||
|
l1.rpc.signpsbt('')
|
||||||
|
with pytest.raises(RpcError, match=r"should be a PSBT, not"):
|
||||||
|
l1.rpc.sendpsbt('')
|
||||||
|
|
||||||
|
# Try a modified (invalid) PSBT string
|
||||||
|
modded_psbt = l2_reserved['psbt'][:-3] + 'A' + l2_reserved['psbt'][-3:]
|
||||||
|
with pytest.raises(RpcError, match=r"should be a PSBT, not"):
|
||||||
|
l1.rpc.signpsbt(modded_psbt)
|
||||||
|
|
||||||
|
|
||||||
def test_txsend(node_factory, bitcoind, chainparams):
|
def test_txsend(node_factory, bitcoind, chainparams):
|
||||||
amount = 1000000
|
amount = 1000000
|
||||||
l1 = node_factory.get_node(random_hsm=True)
|
l1 = node_factory.get_node(random_hsm=True)
|
||||||
|
|||||||
@@ -34,6 +34,28 @@
|
|||||||
#include <wally_bip32.h>
|
#include <wally_bip32.h>
|
||||||
#include <wire/wire_sync.h>
|
#include <wire/wire_sync.h>
|
||||||
|
|
||||||
|
struct tx_broadcast {
|
||||||
|
struct command *cmd;
|
||||||
|
const struct utxo **utxos;
|
||||||
|
const struct wally_tx *wtx;
|
||||||
|
struct amount_sat *expected_change;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct tx_broadcast *unreleased_tx_to_broadcast(const tal_t *ctx,
|
||||||
|
struct command *cmd,
|
||||||
|
struct unreleased_tx *utx)
|
||||||
|
{
|
||||||
|
struct tx_broadcast *txb = tal(ctx, struct tx_broadcast);
|
||||||
|
struct amount_sat *change = tal(txb, struct amount_sat);
|
||||||
|
|
||||||
|
txb->cmd = cmd;
|
||||||
|
txb->utxos = utx->wtx->utxos;
|
||||||
|
txb->wtx = utx->tx->wtx;
|
||||||
|
*change = utx->wtx->change;
|
||||||
|
txb->expected_change = change;
|
||||||
|
return txb;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wallet_withdrawal_broadcast - The tx has been broadcast (or it failed)
|
* wallet_withdrawal_broadcast - The tx has been broadcast (or it failed)
|
||||||
*
|
*
|
||||||
@@ -45,29 +67,34 @@
|
|||||||
*/
|
*/
|
||||||
static void wallet_withdrawal_broadcast(struct bitcoind *bitcoind UNUSED,
|
static void wallet_withdrawal_broadcast(struct bitcoind *bitcoind UNUSED,
|
||||||
bool success, const char *msg,
|
bool success, const char *msg,
|
||||||
struct unreleased_tx *utx)
|
struct tx_broadcast *txb)
|
||||||
{
|
{
|
||||||
struct command *cmd = utx->wtx->cmd;
|
struct command *cmd = txb->cmd;
|
||||||
struct lightningd *ld = cmd->ld;
|
struct lightningd *ld = cmd->ld;
|
||||||
struct amount_sat change = AMOUNT_SAT(0);
|
|
||||||
|
|
||||||
/* FIXME: This won't be necessary once we use ccan/json_out! */
|
/* FIXME: This won't be necessary once we use ccan/json_out! */
|
||||||
/* Massage output into shape so it doesn't kill the JSON serialization */
|
/* Massage output into shape so it doesn't kill the JSON serialization */
|
||||||
char *output = tal_strjoin(cmd, tal_strsplit(cmd, msg, "\n", STR_NO_EMPTY), " ", STR_NO_TRAIL);
|
char *output = tal_strjoin(cmd, tal_strsplit(cmd, msg, "\n", STR_NO_EMPTY), " ", STR_NO_TRAIL);
|
||||||
if (success) {
|
if (success) {
|
||||||
|
struct bitcoin_txid txid;
|
||||||
|
struct amount_sat change = AMOUNT_SAT(0);
|
||||||
|
|
||||||
/* Mark used outputs as spent */
|
/* Mark used outputs as spent */
|
||||||
wallet_confirm_utxos(ld->wallet, utx->wtx->utxos);
|
wallet_confirm_utxos(ld->wallet, txb->utxos);
|
||||||
|
|
||||||
/* Extract the change output and add it to the DB */
|
/* Extract the change output and add it to the DB */
|
||||||
wallet_extract_owned_outputs(ld->wallet, utx->tx->wtx, NULL, &change);
|
wallet_extract_owned_outputs(ld->wallet, txb->wtx, NULL, &change);
|
||||||
|
|
||||||
/* Note normally, change_satoshi == withdraw->wtx->change, but
|
/* Note normally, change_satoshi == withdraw->wtx->change, but
|
||||||
* not if we're actually making a payment to ourselves! */
|
* not if we're actually making a payment to ourselves! */
|
||||||
assert(amount_sat_greater_eq(change, utx->wtx->change));
|
if (txb->expected_change)
|
||||||
|
assert(amount_sat_greater_eq(change, *txb->expected_change));
|
||||||
|
|
||||||
struct json_stream *response = json_stream_success(cmd);
|
struct json_stream *response = json_stream_success(cmd);
|
||||||
json_add_tx(response, "tx", utx->tx);
|
wally_txid(txb->wtx, &txid);
|
||||||
json_add_txid(response, "txid", &utx->txid);
|
json_add_hex_talarr(response, "tx",
|
||||||
|
linearize_wtx(tmpctx, txb->wtx));
|
||||||
|
json_add_txid(response, "txid", &txid);
|
||||||
was_pending(command_success(cmd, response));
|
was_pending(command_success(cmd, response));
|
||||||
} else {
|
} else {
|
||||||
was_pending(command_fail(cmd, LIGHTNINGD,
|
was_pending(command_fail(cmd, LIGHTNINGD,
|
||||||
@@ -127,7 +154,8 @@ static struct command_result *broadcast_and_wait(struct command *cmd,
|
|||||||
/* Now broadcast the transaction */
|
/* Now broadcast the transaction */
|
||||||
bitcoind_sendrawtx(cmd->ld->topology->bitcoind,
|
bitcoind_sendrawtx(cmd->ld->topology->bitcoind,
|
||||||
tal_hex(tmpctx, linearize_tx(tmpctx, utx->tx)),
|
tal_hex(tmpctx, linearize_tx(tmpctx, utx->tx)),
|
||||||
wallet_withdrawal_broadcast, utx);
|
wallet_withdrawal_broadcast,
|
||||||
|
unreleased_tx_to_broadcast(cmd, cmd, utx));
|
||||||
|
|
||||||
return command_still_pending(cmd);
|
return command_still_pending(cmd);
|
||||||
}
|
}
|
||||||
@@ -1218,7 +1246,6 @@ static struct command_result *json_unreserveinputs(struct command *cmd,
|
|||||||
{
|
{
|
||||||
struct json_stream *response;
|
struct json_stream *response;
|
||||||
struct wally_psbt *psbt;
|
struct wally_psbt *psbt;
|
||||||
bool all_unreserved;
|
|
||||||
|
|
||||||
/* for each input in the psbt, attempt to 'unreserve' it */
|
/* for each input in the psbt, attempt to 'unreserve' it */
|
||||||
if (!param(cmd, buffer, params,
|
if (!param(cmd, buffer, params,
|
||||||
@@ -1227,7 +1254,6 @@ static struct command_result *json_unreserveinputs(struct command *cmd,
|
|||||||
return command_param_failed();
|
return command_param_failed();
|
||||||
|
|
||||||
response = json_stream_success(cmd);
|
response = json_stream_success(cmd);
|
||||||
all_unreserved = psbt->tx->num_inputs != 0;
|
|
||||||
json_array_start(response, "outputs");
|
json_array_start(response, "outputs");
|
||||||
for (size_t i = 0; i < psbt->tx->num_inputs; i++) {
|
for (size_t i = 0; i < psbt->tx->num_inputs; i++) {
|
||||||
struct wally_tx_input *in;
|
struct wally_tx_input *in;
|
||||||
@@ -1243,11 +1269,9 @@ static struct command_result *json_unreserveinputs(struct command *cmd,
|
|||||||
json_add_u64(response, "vout", in->index);
|
json_add_u64(response, "vout", in->index);
|
||||||
json_add_bool(response, "unreserved", unreserved);
|
json_add_bool(response, "unreserved", unreserved);
|
||||||
json_object_end(response);
|
json_object_end(response);
|
||||||
all_unreserved &= unreserved;
|
|
||||||
}
|
}
|
||||||
json_array_end(response);
|
json_array_end(response);
|
||||||
|
|
||||||
json_add_bool(response, "all_unreserved", all_unreserved);
|
|
||||||
return command_success(cmd, response);
|
return command_success(cmd, response);
|
||||||
}
|
}
|
||||||
static const struct json_command unreserveinputs_command = {
|
static const struct json_command unreserveinputs_command = {
|
||||||
@@ -1258,3 +1282,145 @@ static const struct json_command unreserveinputs_command = {
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
AUTODATA(json_command, &unreserveinputs_command);
|
AUTODATA(json_command, &unreserveinputs_command);
|
||||||
|
|
||||||
|
static struct command_result *match_psbt_inputs_to_utxos(struct command *cmd,
|
||||||
|
struct wally_psbt *psbt,
|
||||||
|
struct utxo ***utxos)
|
||||||
|
{
|
||||||
|
*utxos = tal_arr(cmd, struct utxo *, 0);
|
||||||
|
for (size_t i = 0; i < psbt->tx->num_inputs; i++) {
|
||||||
|
struct utxo *utxo;
|
||||||
|
struct bitcoin_txid txid;
|
||||||
|
|
||||||
|
wally_tx_input_get_txid(&psbt->tx->inputs[i], &txid);
|
||||||
|
utxo = wallet_utxo_get(*utxos, cmd->ld->wallet,
|
||||||
|
&txid, psbt->tx->inputs[i].index);
|
||||||
|
if (!utxo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Oops we haven't reserved this utxo yet.
|
||||||
|
* Let's just go ahead and reserve it now. */
|
||||||
|
if (utxo->status != output_state_reserved)
|
||||||
|
return command_fail(cmd, LIGHTNINGD,
|
||||||
|
"Aborting PSBT signing. UTXO %s:%u is not reserved",
|
||||||
|
type_to_string(tmpctx, struct bitcoin_txid,
|
||||||
|
&utxo->txid),
|
||||||
|
utxo->outnum);
|
||||||
|
tal_arr_expand(utxos, utxo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct command_result *json_signpsbt(struct command *cmd,
|
||||||
|
const char *buffer,
|
||||||
|
const jsmntok_t *obj UNNEEDED,
|
||||||
|
const jsmntok_t *params)
|
||||||
|
{
|
||||||
|
struct command_result *res;
|
||||||
|
struct json_stream *response;
|
||||||
|
struct wally_psbt *psbt, *signed_psbt;
|
||||||
|
struct utxo **utxos;
|
||||||
|
|
||||||
|
if (!param(cmd, buffer, params,
|
||||||
|
p_req("psbt", param_psbt, &psbt),
|
||||||
|
NULL))
|
||||||
|
return command_param_failed();
|
||||||
|
|
||||||
|
/* We have to find/locate the utxos that are ours on this PSBT,
|
||||||
|
* so that the HSM knows how/what to sign for (it's possible some of
|
||||||
|
* our utxos require more complicated data to sign for e.g.
|
||||||
|
* closeinfo outputs */
|
||||||
|
res = match_psbt_inputs_to_utxos(cmd, psbt, &utxos);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (tal_count(utxos) == 0)
|
||||||
|
return command_fail(cmd, LIGHTNINGD,
|
||||||
|
"No wallet inputs to sign");
|
||||||
|
|
||||||
|
/* FIXME: hsm will sign almost anything, but it should really
|
||||||
|
* fail cleanly (not abort!) and let us report the error here. */
|
||||||
|
u8 *msg = towire_hsm_sign_withdrawal(cmd,
|
||||||
|
cast_const2(const struct utxo **, utxos),
|
||||||
|
psbt);
|
||||||
|
|
||||||
|
if (!wire_sync_write(cmd->ld->hsm_fd, take(msg)))
|
||||||
|
fatal("Could not write sign_withdrawal to HSM: %s",
|
||||||
|
strerror(errno));
|
||||||
|
|
||||||
|
msg = wire_sync_read(cmd, cmd->ld->hsm_fd);
|
||||||
|
|
||||||
|
if (!fromwire_hsm_sign_withdrawal_reply(cmd, msg, &signed_psbt))
|
||||||
|
fatal("HSM gave bad sign_withdrawal_reply %s",
|
||||||
|
tal_hex(tmpctx, msg));
|
||||||
|
|
||||||
|
response = json_stream_success(cmd);
|
||||||
|
json_add_psbt(response, "signed_psbt", signed_psbt);
|
||||||
|
return command_success(cmd, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct json_command signpsbt_command = {
|
||||||
|
"signpsbt",
|
||||||
|
"bitcoin",
|
||||||
|
json_signpsbt,
|
||||||
|
"Sign this wallet's inputs on a provided PSBT.",
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
AUTODATA(json_command, &signpsbt_command);
|
||||||
|
|
||||||
|
static struct command_result *json_sendpsbt(struct command *cmd,
|
||||||
|
const char *buffer,
|
||||||
|
const jsmntok_t *obj UNNEEDED,
|
||||||
|
const jsmntok_t *params)
|
||||||
|
{
|
||||||
|
struct command_result *res;
|
||||||
|
struct wally_psbt *psbt;
|
||||||
|
struct wally_tx *w_tx;
|
||||||
|
struct tx_broadcast *txb;
|
||||||
|
struct utxo **utxos;
|
||||||
|
|
||||||
|
if (!param(cmd, buffer, params,
|
||||||
|
p_req("psbt", param_psbt, &psbt),
|
||||||
|
NULL))
|
||||||
|
return command_param_failed();
|
||||||
|
|
||||||
|
w_tx = psbt_finalize(psbt, true);
|
||||||
|
if (!w_tx)
|
||||||
|
return command_fail(cmd, LIGHTNINGD,
|
||||||
|
"PSBT not finalizeable %s",
|
||||||
|
type_to_string(tmpctx, struct wally_psbt,
|
||||||
|
psbt));
|
||||||
|
|
||||||
|
/* We have to find/locate the utxos that are ours on this PSBT,
|
||||||
|
* so that we know who to mark as used.
|
||||||
|
*/
|
||||||
|
res = match_psbt_inputs_to_utxos(cmd, psbt, &utxos);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
txb = tal(cmd, struct tx_broadcast);
|
||||||
|
txb->utxos = cast_const2(const struct utxo **,
|
||||||
|
tal_steal(txb, utxos));
|
||||||
|
txb->wtx = tal_steal(txb, w_tx);
|
||||||
|
txb->cmd = cmd;
|
||||||
|
txb->expected_change = NULL;
|
||||||
|
|
||||||
|
/* Now broadcast the transaction */
|
||||||
|
bitcoind_sendrawtx(cmd->ld->topology->bitcoind,
|
||||||
|
tal_hex(tmpctx, linearize_wtx(tmpctx, w_tx)),
|
||||||
|
wallet_withdrawal_broadcast, txb);
|
||||||
|
|
||||||
|
return command_still_pending(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct json_command sendpsbt_command = {
|
||||||
|
"sendpsbt",
|
||||||
|
"bitcoin",
|
||||||
|
json_sendpsbt,
|
||||||
|
"Finalize, extract and send a PSBT.",
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
AUTODATA(json_command, &sendpsbt_command);
|
||||||
|
|||||||
Reference in New Issue
Block a user