Browse Source

onchaind: extract payment_preimage from onchain HTLC redemption.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ppa-0.6.1
Rusty Russell 7 years ago
parent
commit
cbe72b658e
  1. 73
      onchaind/onchain.c
  2. 90
      tests/test_lightningd.py

73
onchaind/onchain.c

@ -497,9 +497,78 @@ static void unwatch_tx(const struct bitcoin_tx *tx)
}
static void handle_htlc_onchain_fulfill(struct tracked_output *out,
const struct bitcoin_tx *tx)
const struct bitcoin_tx *tx)
{
status_failed(STATUS_FAIL_INTERNAL_ERROR, "FIXME: %s", __func__);
const u8 *witness_preimage;
struct preimage preimage;
struct sha256 sha;
struct ripemd160 ripemd;
/* Our HTLC, they filled (must be a HTLC-success tx). */
if (out->tx_type == THEIR_UNILATERAL) {
/* BOLT #3:
*
* ## HTLC-Timeout and HTLC-Success Transactions
*
* ... `txin[0]` witness stack: `0 <remotesig> <localsig>
* <payment_preimage>` for HTLC-Success
*/
if (tal_count(tx->input[0].witness) != 5) /* +1 for wscript */
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"%s/%s spent with weird witness %zu",
tx_type_name(out->tx_type),
output_type_name(out->output_type),
tal_count(tx->input[0].witness));
witness_preimage = tx->input[0].witness[3];
} else if (out->tx_type == OUR_UNILATERAL) {
/* BOLT #3:
*
* The remote node can redeem the HTLC with the witness:
*
* <remotesig> <payment_preimage>
*/
if (tal_count(tx->input[0].witness) != 3) /* +1 for wscript */
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"%s/%s spent with weird witness %zu",
tx_type_name(out->tx_type),
output_type_name(out->output_type),
tal_count(tx->input[0].witness));
witness_preimage = tx->input[0].witness[1];
} else
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"onchain_fulfill for %s/%s?",
tx_type_name(out->tx_type),
output_type_name(out->output_type));
if (tal_len(witness_preimage) != sizeof(preimage))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"%s/%s spent with bad witness length %zu",
tx_type_name(out->tx_type),
output_type_name(out->output_type),
tal_len(witness_preimage));
memcpy(&preimage, witness_preimage, sizeof(preimage));
sha256(&sha, &preimage, sizeof(preimage));
ripemd160(&ripemd, &sha, sizeof(sha));
if (!structeq(&ripemd, &out->htlc->ripemd))
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"%s/%s spent with bad preimage %s (ripemd not %s)",
tx_type_name(out->tx_type),
output_type_name(out->output_type),
type_to_string(trc, struct preimage, &preimage),
type_to_string(trc, struct ripemd160,
&out->htlc->ripemd));
/* Tell master we found a preimage. */
status_trace("%s/%s gave us preimage %s",
tx_type_name(out->tx_type),
output_type_name(out->output_type),
type_to_string(trc, struct preimage, &preimage));
wire_sync_write(REQ_FD,
take(towire_onchain_extracted_preimage(NULL,
&preimage)));
}
static void resolve_htlc_tx(struct tracked_output ***outs,

90
tests/test_lightningd.py

@ -7,6 +7,7 @@ from lightning import LightningRpc
import copy
import json
import logging
import queue
import os
import random
import re
@ -472,8 +473,14 @@ class LightningDTests(BaseLightningDTests):
route = l1.rpc.getroute(l3.info['id'], 10**8, 1)["route"]
assert len(route) == 2
q = queue.Queue()
def try_pay():
l1.rpc.sendpay(to_json(route), rhash, async=False)
try:
l1.rpc.sendpay(to_json(route), rhash, async=False)
q.put(None)
except Exception as err:
q.put(err)
t = threading.Thread(target=try_pay)
t.daemon = True
@ -490,11 +497,16 @@ class LightningDTests(BaseLightningDTests):
l2.daemon.wait_for_log('Propose handling OUR_UNILATERAL/THEIR_HTLC by OUR_HTLC_SUCCESS_TX .* in 0 blocks')
l2.daemon.wait_for_log('sendrawtx exit 0')
# Payment should succeed.
l1.bitcoin.rpc.generate(1)
# FIXME: l1 should get preimage off the chain!
l1.daemon.wait_for_log('FIXME: handle_htlc_onchain_fulfill')
l1.has_failed()
l1.daemon.wait_for_log('THEIR_UNILATERAL/OUR_HTLC gave us preimage')
err = q.get(timeout = 10)
if err:
print("Got err from sendpay thread")
raise err
t.join(timeout=1)
assert not t.isAlive()
# After 4 more blocks, l2 can spend to-us.
l1.bitcoin.rpc.generate(4)
l2.daemon.wait_for_log('Broadcasting OUR_DELAYED_RETURN_TO_WALLET .* to resolve OUR_UNILATERAL/DELAYED_OUTPUT_TO_US')
@ -509,8 +521,6 @@ class LightningDTests(BaseLightningDTests):
l1.bitcoin.rpc.generate(100)
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
# FIXME: kill thread?
def test_permfail_new_commit(self):
# Test case where we have two possible commits: it will use new one.
disconnects = ['-WIRE_REVOKE_AND_ACK', 'permfail']
@ -532,8 +542,6 @@ class LightningDTests(BaseLightningDTests):
l2.daemon.wait_for_log('Propose handling OUR_UNILATERAL/THEIR_HTLC by THEIR_HTLC_TIMEOUT_TO_THEM \\(IGNORING\\) in 5 blocks')
l1.daemon.wait_for_log('Propose handling THEIR_UNILATERAL/OUR_HTLC by OUR_HTLC_TIMEOUT_TO_US (.*) in 5 blocks')
# FIXME: Implement FULFILL!
# OK, time out HTLC.
bitcoind.rpc.generate(5)
l1.daemon.wait_for_log('sendrawtx exit 0')
@ -541,8 +549,6 @@ class LightningDTests(BaseLightningDTests):
l1.daemon.wait_for_log('Resolved THEIR_UNILATERAL/OUR_HTLC by our proposal OUR_HTLC_TIMEOUT_TO_US')
l2.daemon.wait_for_log('Ignoring output.*: OUR_UNILATERAL/THEIR_HTLC')
# FIXME: This doesn't work :(
# FIXME: sendpay command should time out!
t.cancel()
# Now, 100 blocks it should be done.
@ -570,28 +576,25 @@ class LightningDTests(BaseLightningDTests):
l2.daemon.wait_for_log('-> ONCHAIND_OUR_UNILATERAL')
l2.daemon.wait_for_log('Propose handling OUR_UNILATERAL/THEIR_HTLC by THEIR_HTLC_TIMEOUT_TO_THEM \\(IGNORING\\) in 5 blocks')
l1.daemon.wait_for_log('Propose handling THEIR_UNILATERAL/OUR_HTLC by OUR_HTLC_TIMEOUT_TO_US (.*) in 5 blocks')
# OK, time out HTLC.
bitcoind.rpc.generate(5)
l1.daemon.wait_for_log('FIXME: handle_htlc_onchain_fulfill')
l1.has_failed()
# l1.daemon.wait_for_log('sendrawtx exit 0')
bitcoind.rpc.generate(1)
# l1.daemon.wait_for_log('Resolved THEIR_UNILATERAL/OUR_HTLC by our proposal OUR_HTLC_TIMEOUT_TO_US')
# l2 then gets preimage, uses it instead of ignoring
l2.daemon.wait_for_log('Propose handling OUR_UNILATERAL/THEIR_HTLC by OUR_HTLC_SUCCESS_TX .* in 0 blocks')
l2.daemon.wait_for_log('sendrawtx exit 0')
bitcoind.rpc.generate(1)
# OK, l1 sees l2 fulfill htlc.
l1.daemon.wait_for_log('THEIR_UNILATERAL/OUR_HTLC gave us preimage')
l2.daemon.wait_for_log('Propose handling OUR_HTLC_SUCCESS_TX/DELAYED_OUTPUT_TO_US by OUR_DELAYED_RETURN_TO_WALLET .* in 6 blocks')
bitcoind.rpc.generate(6)
l2.daemon.wait_for_log('sendrawtx exit 0')
# FIXME: This doesn't work :(
# FIXME: sendpay command should time out!
l2.daemon.wait_for_log('sendrawtx exit 0')
t.cancel()
# Now, 100 blocks it should be done.
bitcoind.rpc.generate(100)
# l1.daemon.wait_for_log('onchaind complete, forgetting peer')
bitcoind.rpc.generate(94)
l1.daemon.wait_for_log('onchaind complete, forgetting peer')
assert not l2.daemon.is_in_log('onchaind complete, forgetting peer')
bitcoind.rpc.generate(6)
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
def test_permfail_htlc_out(self):
@ -612,27 +615,36 @@ class LightningDTests(BaseLightningDTests):
l1.daemon.wait_for_log('Their unilateral tx, old commit point')
l1.daemon.wait_for_log('-> ONCHAIND_THEIR_UNILATERAL')
l2.daemon.wait_for_log('-> ONCHAIND_OUR_UNILATERAL')
l2.daemon.wait_for_log('Propose handling OUR_UNILATERAL/OUR_HTLC by OUR_HTLC_TIMEOUT_TO_US \\(.*\\) in 5 blocks')
l1.daemon.wait_for_log('Propose handling THEIR_UNILATERAL/THEIR_HTLC by THEIR_HTLC_TIMEOUT_TO_THEM \\(IGNORING\\) in 5 blocks')
# OK, time out HTLC.
bitcoind.rpc.generate(5)
l2.daemon.wait_for_logs(['Propose handling OUR_UNILATERAL/OUR_HTLC by OUR_HTLC_TIMEOUT_TO_US \\(.*\\) in 5 blocks',
'Propose handling OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by OUR_DELAYED_RETURN_TO_WALLET .* in 6 blocks'])
l2.daemon.wait_for_log('FIXME: handle_htlc_onchain_fulfill')
l2.has_failed()
l1.daemon.wait_for_log('Propose handling THEIR_UNILATERAL/THEIR_HTLC by THEIR_HTLC_TIMEOUT_TO_THEM \\(IGNORING\\) in 5 blocks')
# l1 then gets preimage, uses it instead of ignoring
l1.daemon.wait_for_log('Propose handling THEIR_UNILATERAL/THEIR_HTLC by THEIR_HTLC_FULFILL_TO_US .* in 0 blocks')
l1.daemon.wait_for_log('sendrawtx exit 0')
# l2 sees l1 fulfill tx.
bitcoind.rpc.generate(1)
l1.daemon.wait_for_log('Propose handling THEIR_UNILATERAL/THEIR_HTLC by THEIR_HTLC_FULFILL_TO_US .* in 0 blocks')
# l2.daemon.wait_for_log('Resolved OUR_UNILATERAL/OUR_HTLC by our proposal OUR_HTLC_TIMEOUT_TO_US')
# FIXME: This doesn't work :(
# FIXME: sendpay command should time out!
l2.daemon.wait_for_log('OUR_UNILATERAL/OUR_HTLC gave us preimage')
t.cancel()
# Now, 100 blocks it should be done.
bitcoind.rpc.generate(100)
# l2 can send OUR_DELAYED_RETURN_TO_WALLET after 5 more blocks.
bitcoind.rpc.generate(5)
l2.daemon.wait_for_log('Broadcasting OUR_DELAYED_RETURN_TO_WALLET .* to resolve OUR_UNILATERAL/DELAYED_OUTPUT_TO_US')
l2.daemon.wait_for_log('sendrawtx exit 0')
# Now, 100 blocks they should be done.
bitcoind.rpc.generate(93)
assert not l1.daemon.is_in_log('onchaind complete, forgetting peer')
assert not l2.daemon.is_in_log('onchaind complete, forgetting peer')
bitcoind.rpc.generate(1)
l1.daemon.wait_for_log('onchaind complete, forgetting peer')
# l2.daemon.wait_for_log('onchaind complete, forgetting peer')
assert not l2.daemon.is_in_log('onchaind complete, forgetting peer')
bitcoind.rpc.generate(5)
assert not l2.daemon.is_in_log('onchaind complete, forgetting peer')
bitcoind.rpc.generate(1)
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
def test_gossip_jsonrpc(self):
l1,l2 = self.connect()

Loading…
Cancel
Save