diff --git a/onchaind/onchain.c b/onchaind/onchain.c index 618d05947..1e5b1e172 100644 --- a/onchaind/onchain.c +++ b/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 + * ` 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: + * + * + */ + 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, diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index 7f5743528..5536340cf 100644 --- a/tests/test_lightningd.py +++ b/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') + 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']) + 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_log('FIXME: handle_htlc_onchain_fulfill') - l2.has_failed() - - bitcoind.rpc.generate(1) + # 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') -# l2.daemon.wait_for_log('Resolved OUR_UNILATERAL/OUR_HTLC by our proposal OUR_HTLC_TIMEOUT_TO_US') + l1.daemon.wait_for_log('sendrawtx exit 0') - # FIXME: This doesn't work :( - # FIXME: sendpay command should time out! + # l2 sees l1 fulfill tx. + bitcoind.rpc.generate(1) + + 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()