Ejemplo n.º 1
0
    def unvault_vaults(self, vaults, destinations, feerate):
        """
        Unvault these {vaults}, advertizing a Spend tx spending to these {destinations}
        (mapping of addresses to amounts)
        """
        man = self.man(0)
        deposits = []
        deriv_indexes = []
        for v in vaults:
            deposits.append(f"{v['txid']}:{v['vout']}")
            deriv_indexes.append(v["derivation_index"])
        man.wait_for_active_vaults(deposits)

        spend_tx = man.rpc.getspendtx(deposits, destinations,
                                      feerate)["spend_tx"]
        for man in self.mans():
            spend_tx = man.man_keychain.sign_spend_psbt(
                spend_tx, deriv_indexes)
            man.rpc.updatespendtx(spend_tx)

        spend_psbt = serializations.PSBT()
        spend_psbt.deserialize(spend_tx)
        spend_psbt.tx.calc_sha256()
        man.rpc.setspendtx(spend_psbt.tx.hash)

        self.bitcoind.generate_block(1, wait_for_mempool=len(deposits))
        for w in self.participants():
            wait_for(lambda: len(
                w.rpc.listvaults(["unvaulted"], deposits)["vaults"]) == len(
                    deposits))
Ejemplo n.º 2
0
def test_sigfetcher_secured_vaults(revault_network, bitcoind):
    """
    Test that unvault sigs are retrieved and stored for secured vault even if the
    daemon user did not start activating it or is not a stakeholder.
    """
    rn = revault_network
    rn.deploy(
        3,
        1,
        csv=1,
        with_cosigs=False,
        with_watchtowers=False,
    )
    vault = rn.fund(1)
    rn.secure_vault(vault)
    stks = rn.stks()

    outpoint = f"{vault['txid']}:{vault['vout']}"
    unvault_psbt = stks[0].rpc.getunvaulttx(outpoint)["unvault_tx"]
    unvault_psbt = stks[0].stk_keychain.sign_unvault_psbt(
        unvault_psbt, vault["derivation_index"])
    stks[0].rpc.unvaulttx(outpoint, unvault_psbt)

    for stk in rn.participants():
        stk.wait_for_logs([
            "Syncing Unvault signature",
        ])
        wait_for(lambda: stk.rpc.listpresignedtransactions([outpoint])[
            "presigned_transactions"][0]["unvault"]["psbt"] == unvault_psbt)
Ejemplo n.º 3
0
 def wait_for_secured_vaults(self, outpoints):
     """
     Polls listvaults until we acknowledge the 'secured' :tm: vaults at {outpoints}
     """
     assert isinstance(outpoints, list)
     wait_for(
         lambda: len(self.rpc.listvaults(["secured"], outpoints)["vaults"]
                     ) == len(outpoints))
Ejemplo n.º 4
0
 def wait_for_active_vaults(self, outpoints):
     """
     Polls listvaults until we acknowledge the active vaults at {outpoints}
     """
     assert isinstance(outpoints, list)
     wait_for(
         lambda: len(self.rpc.listvaults(["active"], outpoints)["vaults"]
                     ) == len(outpoints))
Ejemplo n.º 5
0
 def wait_for_deposits(self, outpoints):
     """
     Polls listvaults until we acknowledge the confirmed vaults at {outpoints}
     """
     assert isinstance(outpoints, list)
     wait_for(
         lambda: len(self.rpc.listvaults(["funded"], outpoints)["vaults"]
                     ) == len(outpoints))
Ejemplo n.º 6
0
    def cancel_vault(self, vault):
        deposit = f"{vault['txid']}:{vault['vout']}"

        for w in self.participants():
            wait_for(lambda: len(
                w.rpc.listvaults(["unvaulting", "unvaulted", "spending"],
                                 [deposit])["vaults"]) == 1)

        self.stk(0).rpc.revault(deposit)
        self.bitcoind.generate_block(1, wait_for_mempool=1)
        for w in self.participants():
            wait_for(lambda: len(
                w.rpc.listvaults(["canceled"], [deposit])["vaults"]) == 1)
Ejemplo n.º 7
0
def test_revocation_sig_sharing(revault_network):
    revault_network.deploy(4, 2, n_stkmanagers=1)
    stks = revault_network.stks()
    mans = revault_network.mans()

    vault = revault_network.fund(10)
    deposit = f"{vault['txid']}:{vault['vout']}"
    child_index = vault["derivation_index"]

    # We can just get everyone to sign it out of band and a single one handing
    # it to the sync server.
    stks[0].wait_for_deposits([deposit])
    psbts = stks[0].rpc.getrevocationtxs(deposit)
    cancel_psbt = psbts["cancel_tx"]
    emer_psbt = psbts["emergency_tx"]
    unemer_psbt = psbts["emergency_unvault_tx"]
    for stk in stks:
        cancel_psbt = stk.stk_keychain.sign_revocation_psbt(
            cancel_psbt, child_index)
        emer_psbt = stk.stk_keychain.sign_revocation_psbt(
            emer_psbt, child_index)
        unemer_psbt = stk.stk_keychain.sign_revocation_psbt(
            unemer_psbt, child_index)
    stks[0].rpc.revocationtxs(deposit, cancel_psbt, emer_psbt, unemer_psbt)
    assert stks[0].rpc.listvaults()["vaults"][0]["status"] == "secured"
    # Note that we can't pass it twice
    with pytest.raises(RpcError, match="Invalid vault status"):
        stks[0].rpc.revocationtxs(deposit, cancel_psbt, emer_psbt, unemer_psbt)
    # They must all have fetched the signatures, even the managers!
    for stk in stks + mans:
        wait_for(lambda: len(
            stk.rpc.listvaults(["secured"], [deposit])["vaults"]) > 0)

    vault = revault_network.fund(20)
    deposit = f"{vault['txid']}:{vault['vout']}"
    child_index = vault["derivation_index"]

    # Or everyone can sign on their end and push to the sync server
    for stk in stks:
        stk.wait_for_deposits([deposit])
        psbts = stk.rpc.getrevocationtxs(deposit)
        cancel_psbt = stk.stk_keychain.sign_revocation_psbt(
            psbts["cancel_tx"], child_index)
        emer_psbt = stk.stk_keychain.sign_revocation_psbt(
            psbts["emergency_tx"], child_index)
        unemer_psbt = stk.stk_keychain.sign_revocation_psbt(
            psbts["emergency_unvault_tx"], child_index)
        stk.rpc.revocationtxs(deposit, cancel_psbt, emer_psbt, unemer_psbt)
    for stk in stks + mans:
        wait_for(lambda: len(
            stk.rpc.listvaults(["secured"], [deposit])["vaults"]) > 0)
Ejemplo n.º 8
0
 def unvault_vaults(self, vaults, destinations, feerate, priority=False):
     """
     Unvault these {vaults}, advertizing a Spend tx spending to these {destinations}
     (mapping of addresses to amounts)
     """
     spend_psbt = self.broadcast_unvaults(vaults, destinations, feerate, priority)
     deposits = [f"{v['txid']}:{v['vout']}" for v in vaults]
     self.bitcoind.generate_block(1, wait_for_mempool=len(deposits))
     for w in self.participants():
         wait_for(
             lambda: len(w.rpc.listvaults(["unvaulted"], deposits)["vaults"])
             == len(deposits)
         )
     return spend_psbt
Ejemplo n.º 9
0
    def spend_vaults(self, vaults, destinations, feerate):
        """
        Spend these {vaults} to these {destinations} (mapping of addresses to amounts).

        :return: the list of spent deposits along with the Spend PSBT.
        """
        deposits, spend_psbt = self.spend_vaults_unconfirmed(
            vaults, destinations, feerate)

        self.bitcoind.generate_block(1, wait_for_mempool=[spend_psbt.tx.hash])
        wait_for(lambda: len(
            self.man(0).rpc.listvaults(["spent"], deposits)["vaults"]) == len(
                deposits))

        return deposits, spend_psbt.tx.hash
Ejemplo n.º 10
0
def test_sigfetcher_coordinator_dead(revault_network, bitcoind):
    rn = revault_network
    rn.deploy(
        2,
        1,
        csv=1,
        with_cosigs=False,
        with_watchtowers=False,
    )
    vault = revault_network.fund(1)

    # We kill the coordinator
    for d in rn.daemons:
        if d not in rn.participants():
            d.stop()

    # Now we secure the vault
    deposit = f"{vault['txid']}:{vault['vout']}"
    for stk in rn.stks():
        stk.wait_for_deposits([deposit])
        psbts = stk.rpc.getrevocationtxs(deposit)
        cancel_psbt = stk.stk_keychain.sign_revocation_psbt(
            psbts["cancel_tx"], vault["derivation_index"])
        emer_psbt = stk.stk_keychain.sign_revocation_psbt(
            psbts["emergency_tx"], vault["derivation_index"])
        unemer_psbt = stk.stk_keychain.sign_revocation_psbt(
            psbts["emergency_unvault_tx"], vault["derivation_index"])

        # Revaultd complains because the coordinator is dead
        with pytest.raises(RpcError, match="Connection refused"):
            stk.rpc.revocationtxs(deposit, cancel_psbt, emer_psbt, unemer_psbt)

        # The sigfetcher tries to fetch the signatures, but fails
        stk.wait_for_log("Error while fetching signatures")
        wait_for(lambda: len(stk.rpc.listvaults(["securing"])["vaults"]) == 1)

    # Now we start the coordinator again, and the vault will be secured :)
    for d in rn.daemons:
        if d not in rn.participants():
            d.start()

    for stk in rn.stks():
        stk.wait_for_logs([
            "Syncing Cancel signature",
            "Syncing Emergency signature",
            "Syncing Unvault Emergency signature",
        ])
        stk.wait_for_secured_vaults([deposit])
Ejemplo n.º 11
0
def test_reorged_spend(revault_network, bitcoind):
    CSV = 12
    revault_network.deploy(4, 2, csv=CSV, with_watchtowers=False)
    vaults = revault_network.fundmany([32, 3])

    # Spend the vaults, record the spend time
    revault_network.activate_fresh_vaults(vaults)
    deposits, _ = revault_network.spend_vaults_anyhow(vaults)
    initial_moved_at = revault_network.stk(0).rpc.listvaults(
        ["spent"])["vaults"][0]["moved_at"]

    # Initial sanity checks..
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == bitcoind.rpc.
                 getblockcount())
        assert len(w.rpc.listvaults(["spent"],
                                    deposits)["vaults"]) == len(deposits)
        for vault in w.rpc.listvaults(["spent"], deposits)["vaults"]:
            for field in timestamps_from_status("spent"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("spent", present=False):
                assert vault[field] is None, field

    # If we are 'spent' and the Spend gets unconfirmed, it'll get marked for
    # re-broadcast
    blockheight = bitcoind.rpc.getblockcount()
    bitcoind.simple_reorg(blockheight, shift=-1)
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Vault {deposits[0]}'s Spend transaction got unconfirmed",
            f"Vault {deposits[1]}'s Spend transaction got unconfirmed",
            "Rescan of all vaults in db done.",
        ])

    # All good if we re-confirm it
    bitcoind.generate_block(1, wait_for_mempool=1)
    for w in revault_network.participants():
        wait_for(lambda: len(w.rpc.listvaults(["spent"], deposits)["vaults"])
                 == len(deposits))
        for vault in w.rpc.listvaults(["spent"], deposits)["vaults"]:
            for field in timestamps_from_status("spent"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("spent", present=False):
                assert vault[field] is None, field
            # It's in a new block, it shouldn't have the same timestamp!
            assert vault["moved_at"] != initial_moved_at
Ejemplo n.º 12
0
def test_getinfo(revaultd_manager, bitcoind):
    res = revaultd_manager.rpc.call("getinfo")
    assert res["network"] == "regtest"
    assert res["sync"] == 1.0
    assert res["version"] == "0.3.1"
    assert res["vaults"] == 0
    # revaultd_manager always deploys with N = 2, M = 3, threshold = M
    assert res["managers_threshold"] == 3
    # test descriptors: RPC call & which Revaultd's were configured
    assert res["descriptors"]["cpfp"] == revaultd_manager.cpfp_desc
    assert res["descriptors"]["deposit"] == revaultd_manager.deposit_desc
    assert res["descriptors"]["unvault"] == revaultd_manager.unvault_desc

    wait_for(lambda: revaultd_manager.rpc.call("getinfo")["blockheight"] > 0)
    height = revaultd_manager.rpc.call("getinfo")["blockheight"]
    bitcoind.generate_block(1)
    wait_for(lambda: revaultd_manager.rpc.call("getinfo")["blockheight"] == height + 1)
Ejemplo n.º 13
0
def test_revaulted_spend(revault_network, bitcoind, executor):
    """
    Revault an ongoing Spend transaction carried out by the managers, under misc
    circumstances.
    """
    CSV = 12
    revault_network.deploy(2, 2, n_stkmanagers=1, csv=CSV)
    mans = revault_network.mans()
    stks = revault_network.stks()

    # Simple case. Managers Spend a single vault.
    vault = revault_network.fund(0.05)
    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)

    revault_network.spend_vaults_anyhow_unconfirmed([vault])
    revault_network.cancel_vault(vault)

    # Managers spend two vaults, both are canceled.
    vaults = [revault_network.fund(0.05), revault_network.fund(0.1)]
    for v in vaults:
        revault_network.secure_vault(v)
        revault_network.activate_vault(v)

    revault_network.unvault_vaults_anyhow(vaults)
    for vault in vaults:
        revault_network.cancel_vault(vault)

    # Managers spend three vaults, only a single one is canceled. And both of them were
    # created in the same deposit transaction.
    vaults = revault_network.fundmany([0.2, 0.08])
    vaults.append(revault_network.fund(0.03))
    for v in vaults:
        revault_network.secure_vault(v)
        revault_network.activate_vault(v)
    revault_network.unvault_vaults_anyhow(vaults)
    revault_network.cancel_vault(vaults[0])

    # vaults[0] is canceled, therefore the Spend transaction is now invalid. The vaults
    # should be marked as unvaulted since they are not being spent anymore.
    deposits = [f"{v['txid']}:{v['vout']}" for v in vaults[1:]]
    for w in mans + stks:
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                        ) == len(deposits))
Ejemplo n.º 14
0
    def spend_vaults(self, vaults, destinations, feerate):
        """
        Spend these {vaults} to these {destinations} (mapping of addresses to amounts).
        Make sure to call this only with revault deployment with a low (<500) CSV, or you'll encounter
        an ugly timeout from bitcoinlib.

        :return: the list of spent deposits along with the Spend PSBT.
        """
        deposits, spend_psbt = self.spend_vaults_unconfirmed(
            vaults, destinations, feerate
        )

        self.bitcoind.generate_block(1, wait_for_mempool=[spend_psbt.tx.hash])
        wait_for(
            lambda: len(self.man(0).rpc.listvaults(["spent"], deposits)["vaults"])
            == len(deposits)
        )

        return deposits, spend_psbt.tx.hash
Ejemplo n.º 15
0
def test_sigfetcher(revault_network, bitcoind, executor):
    rn = revault_network
    rn.deploy(7, 3, n_stkmanagers=2)
    # First of all, activate a vault
    vault = revault_network.fund(0.05)
    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)

    # Stopping revaultd, deleting the database
    for w in rn.participants():
        w.stop()
        datadir_db = os.path.join(w.datadir_with_network, "revaultd.sqlite3")
        os.remove(datadir_db)

    # Starting revaultd again
    for w in rn.participants():
        # Manually starting it so that we can check that
        # the db is being created again
        TailableProc.start(w)
        w.wait_for_logs([
            "No database at .*, creating a new one",
            "revaultd started on network regtest",
            "bitcoind now synced",
            "JSONRPC server started",
            "Signature fetcher thread started",
        ])

    # They should all get back to the 'active' state, pulling sigs from the coordinator
    for w in rn.participants():
        w.wait_for_log("Got a new unconfirmed deposit")
        wait_for(lambda: len(w.rpc.listvaults(["funded"], [])) == 1)
    for w in rn.stks():
        w.wait_for_logs([
            "Syncing Unvault Emergency signature",
            "Syncing Emergency signature",
            "Syncing Cancel signature",
            "Syncing Unvault signature",
        ])
    for w in rn.man_wallets:
        w.wait_for_logs([
            "Syncing Cancel signature",
            "Syncing Unvault signature",
        ])
Ejemplo n.º 16
0
    def spend_vaults_unconfirmed(self, vaults, destinations, feerate, priority=False):
        """
        Spend these {vaults} to these {destinations} (mapping of addresses to amounts), not
        confirming the Spend transaction.
        Make sure to call this only with revault deployment with a low (<500) CSV, or you'll encounter
        an ugly timeout from bitcoinlib.

        :return: the list of spent deposits along with the Spend PSBT.
        """
        assert len(vaults) > 0
        man = self.man(0)
        deposits = []
        deriv_indexes = []
        for v in vaults:
            deposits.append(f"{v['txid']}:{v['vout']}")
            deriv_indexes.append(v["derivation_index"])

        for man in self.mans():
            man.wait_for_active_vaults(deposits)

        spend_tx = man.rpc.getspendtx(deposits, destinations, feerate)["spend_tx"]
        for man in self.mans():
            spend_tx = man.man_keychain.sign_spend_psbt(spend_tx, deriv_indexes)
            man.rpc.updatespendtx(spend_tx)

        spend_psbt = serializations.PSBT()
        spend_psbt.deserialize(spend_tx)
        spend_psbt.tx.calc_sha256()
        man.rpc.setspendtx(spend_psbt.tx.hash, priority)

        self.bitcoind.generate_block(1, wait_for_mempool=len(deposits))
        self.bitcoind.generate_block(self.csv)
        man.wait_for_log(
            f"Succesfully broadcasted Spend tx '{spend_psbt.tx.hash}'",
        )
        for w in self.participants():
            wait_for(
                lambda: len(w.rpc.listvaults(["spending"], deposits)["vaults"])
                == len(deposits)
            )

        return deposits, spend_psbt
Ejemplo n.º 17
0
def test_coordinator_broadcast(revault_network, bitcoind, executor):
    """
    Test that the coordinator broadcasts spend transactions when they become valid
    """
    CSV = 12
    revault_network.deploy(2, 2, n_stkmanagers=1, csv=CSV)

    vault = revault_network.fund(0.05)
    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    revault_network.unvault_vaults_anyhow([vault])

    revault_network.stop_wallets()

    bitcoind.generate_block(CSV - 1)
    bitcoind.generate_block(1, wait_for_mempool=1)

    revault_network.start_wallets()

    for w in revault_network.participants():
        wait_for(lambda: len(w.rpc.listvaults(["spent"])["vaults"]) == 1, )
Ejemplo n.º 18
0
def test_getrevocationtxs(revault_network, bitcoind):
    rn = revault_network
    rn.deploy(4, 2)
    stks = rn.stks()
    stk = stks[0]
    addr = stk.rpc.call("getdepositaddress")["address"]
    txid = bitcoind.rpc.sendtoaddress(addr, 0.22222)
    stk.wait_for_logs(
        ["Got a new unconfirmed deposit", "Incremented deposit derivation index"]
    )
    vault = stk.rpc.listvaults()["vaults"][0]
    deposit = f"{vault['txid']}:{vault['vout']}"

    # If we are not a stakeholder, it'll fail
    with pytest.raises(RpcError, match="This is a stakeholder command"):
        rn.man(0).rpc.getrevocationtxs(deposit)

    # If the vault isn't confirmed, it'll fail
    for n in stks:
        wait_for(lambda: len(n.rpc.listvaults([], [deposit])["vaults"]) == 1)
        with pytest.raises(RpcError, match="Invalid vault status"):
            n.rpc.getrevocationtxs(deposit)

    # Now, get it confirmed. They all derived the same transactions
    bitcoind.generate_block(6, txid)
    wait_for(lambda: stk.rpc.listvaults()["vaults"][0]["status"] == "funded")
    txs = stk.rpc.getrevocationtxs(deposit)
    assert len(txs.keys()) == 3
    remaining_stks = stks[1:]
    for n in remaining_stks:
        wait_for(lambda: n.rpc.listvaults()["vaults"][0]["status"] == "funded")
        assert txs == n.rpc.getrevocationtxs(deposit)
Ejemplo n.º 19
0
    def spend_vaults_unconfirmed(self, vaults, destinations, feerate):
        """
        Spend these {vaults} to these {destinations} (mapping of addresses to amounts), not
        confirming the Spend transaction.

        :return: the list of spent deposits along with the Spend PSBT.
        """
        man = self.man(0)
        deposits = []
        deriv_indexes = []
        for v in vaults:
            deposits.append(f"{v['txid']}:{v['vout']}")
            deriv_indexes.append(v["derivation_index"])

        for man in self.mans():
            man.wait_for_active_vaults(deposits)

        spend_tx = man.rpc.getspendtx(deposits, destinations,
                                      feerate)["spend_tx"]
        for man in self.mans():
            spend_tx = man.man_keychain.sign_spend_psbt(
                spend_tx, deriv_indexes)
            man.rpc.updatespendtx(spend_tx)

        spend_psbt = serializations.PSBT()
        spend_psbt.deserialize(spend_tx)
        spend_psbt.tx.calc_sha256()
        man.rpc.setspendtx(spend_psbt.tx.hash)

        self.bitcoind.generate_block(1, wait_for_mempool=len(deposits))
        self.bitcoind.generate_block(self.csv)
        man.wait_for_log(
            f"Succesfully broadcasted Spend tx '{spend_psbt.tx.hash}'", )
        wait_for(lambda: len(
            self.man(0).rpc.listvaults(["spending"], deposits)["vaults"]) ==
                 len(deposits))

        return deposits, spend_psbt
Ejemplo n.º 20
0
def test_largewallets(revaultd_stakeholder, bitcoind):
    """Test a wallet with 1000 deposits and 10 dust deposits"""
    amount = 0.01
    dust_amount = 0.00012345
    bitcoind.generate_block(10)

    for i in range(10):
        txids = []
        for i in range(100):
            addr = revaultd_stakeholder.rpc.call(
                "getdepositaddress")["address"]
            txids.append(bitcoind.rpc.sendtoaddress(addr, amount))

        addr = revaultd_stakeholder.rpc.call("getdepositaddress")["address"]
        txids.append(bitcoind.rpc.sendtoaddress(addr, dust_amount))

        bitcoind.generate_block(6, wait_for_mempool=txids)

    wait_for(lambda: revaultd_stakeholder.rpc.getinfo()["vaults"] == 10 * 100)
    assert len(revaultd_stakeholder.rpc.listvaults()["vaults"]) == 10 * 100
    # We previously experienced crashes when calling listpresignedtransactions
    # with a large number of vaults
    revaultd_stakeholder.rpc.listpresignedtransactions()
Ejemplo n.º 21
0
def test_raw_broadcast_cancel(revault_network, bitcoind):
    """
    Test broadcasting a dozen of pair of Unvault and Cancel for vaults with
    different derivation indexes.
    """
    revault_network.deploy(3, 2, n_stkmanagers=2)
    stks = revault_network.stks()
    mans = revault_network.mans()

    for i in range(10):
        vault = revault_network.fund(10)
        assert (vault["derivation_index"] == i
                ), "Derivation index isn't increasing one by one?"

        deposit = f"{vault['txid']}:{vault['vout']}"
        revault_network.secure_vault(vault)
        revault_network.activate_vault(vault)

        unvault_tx = stks[0].rpc.listpresignedtransactions(
            [deposit])["presigned_transactions"][0]["unvault"]["hex"]
        txid = bitcoind.rpc.sendrawtransaction(unvault_tx)
        bitcoind.generate_block(1, wait_for_mempool=txid)

        for w in stks + mans:
            wait_for(
                lambda: len(w.rpc.listvaults(["unvaulted"], [deposit])) == 1)

        cancel_tx = stks[0].rpc.listpresignedtransactions(
            [deposit])["presigned_transactions"][0]["cancel"]["hex"]
        logging.debug(f"{cancel_tx}")
        txid = bitcoind.rpc.sendrawtransaction(cancel_tx)
        bitcoind.generate_block(1, wait_for_mempool=txid)

        for w in stks + mans:
            wait_for(
                lambda: len(w.rpc.listvaults(["canceled"], [deposit])) == 1)
Ejemplo n.º 22
0
    def generate_block(self, numblocks=1, wait_for_mempool=0):
        if wait_for_mempool:
            if isinstance(wait_for_mempool, str):
                wait_for_mempool = [wait_for_mempool]
            if isinstance(wait_for_mempool, list):
                wait_for(lambda: all(txid in self.rpc.getrawmempool()
                                     for txid in wait_for_mempool))
            else:
                wait_for(
                    lambda: len(self.rpc.getrawmempool()) >= wait_for_mempool)

        old_blockcount = self.rpc.getblockcount()
        addr = self.rpc.getnewaddress()
        self.rpc.generatetoaddress(numblocks, addr)
        wait_for(
            lambda: self.rpc.getblockcount() == old_blockcount + numblocks)
Ejemplo n.º 23
0
def test_not_announceable_spend(revault_network, bitcoind, executor):
    CSV = 4
    revault_network.deploy(5, 7, csv=CSV)
    man = revault_network.man(0)

    vaults = []
    deposits = []
    deriv_indexes = []
    amounts = [(i + 1) / 100 for i in range(20)]
    total_amount = sum(amounts) * COIN
    vaults = revault_network.fundmany(amounts)
    deposits = [f"{v['txid']}:{v['vout']}" for v in vaults]
    deriv_indexes = [v["derivation_index"] for v in vaults]
    revault_network.activate_fresh_vaults(vaults)

    feerate = 1
    n_outputs = 588
    fees = revault_network.compute_spendtx_fees(feerate, len(deposits),
                                                n_outputs)
    output_value = int((total_amount - fees) // n_outputs)
    destinations = {
        bitcoind.rpc.getnewaddress(): output_value
        for _ in range(n_outputs)
    }

    # Hey, this spend is huge!
    with pytest.raises(
            RpcError,
            match="Spend transaction is too large, try spending less outpoints'"
    ):
        man.rpc.getspendtx(deposits, destinations, feerate)

    # One less spent outpoint is ok though
    deposits.pop()
    deriv_indexes.pop()
    amounts.pop()
    total_amount = sum(amounts) * COIN
    fees = revault_network.compute_spendtx_fees(feerate, len(deposits),
                                                n_outputs)
    output_value = int((total_amount - fees) // n_outputs)
    for addr in destinations:
        destinations[addr] = output_value
    spend_tx = man.rpc.getspendtx(deposits, destinations, feerate)["spend_tx"]
    for man in revault_network.mans():
        spend_tx = man.man_keychain.sign_spend_psbt(spend_tx, deriv_indexes)
        man.rpc.updatespendtx(spend_tx)
    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    spend_txid = spend_psbt.tx.hash
    man.rpc.setspendtx(spend_txid)

    wait_for(
        lambda: len(man.rpc.listvaults(["unvaulting"], deposits)["vaults"]
                    ) == len(deposits))
    # We need a single confirmation to consider the Unvault transaction confirmed
    bitcoind.generate_block(1, wait_for_mempool=len(deposits))
    wait_for(
        lambda: len(man.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                    ) == len(deposits))

    # We'll broadcast the Spend transaction as soon as it's valid
    bitcoind.generate_block(CSV - 1)
    man.wait_for_log(f"Succesfully broadcasted Spend tx '{spend_txid}'")
    wait_for(lambda: len(man.rpc.listvaults(["spending"], deposits)["vaults"])
             == len(deposits))

    # And will mark it as spent after a single confirmation of the Spend tx
    bitcoind.generate_block(1, wait_for_mempool=[spend_psbt.tx.hash])
    wait_for(lambda: len(man.rpc.listvaults(["spent"], deposits)["vaults"]) ==
             len(deposits))
    for vault in man.rpc.listvaults(["spent"], deposits)["vaults"]:
        assert vault["moved_at"] is not None
Ejemplo n.º 24
0
def test_wt_policy(directory, revault_network, bitcoind):
    """Test we "can't" breach the policies defined by the watchtowers"""
    rn = revault_network
    CSV = 3
    rn.deploy(2, 1, csv=CSV)
    vaults = sorted(rn.fundmany([1, 2, 2, 4, 2, 2]), key=lambda v: v["amount"])
    rn.activate_fresh_vaults(vaults)

    # By default the watchtowers are configured with a plugin enforcing no
    # spending policy.
    rn.spend_vaults_anyhow([vaults[0]])

    # If we have a single watchtower preventing any unvault, we won't be able
    # to spend.
    revault_all_path = os.path.join(WT_PLUGINS_DIR, "revault_all.py")
    rn.stks()[0].watchtower.add_plugins([{"path": revault_all_path, "config": {}}])
    rn.broadcast_unvaults_anyhow(vaults[1:3])
    bitcoind.generate_block(1, 2)
    rn.stks()[0].watchtower.wait_for_log("Broadcasted Cancel transaction")
    for stk in rn.stks():
        deposits = [f"{v['txid']}:{v['vout']}" for v in vaults[1:3]]
        wait_for(
            lambda: len(stk.rpc.listvaults(["canceling"], deposits)["vaults"]) == 2
        )
    bitcoind.generate_block(1)
    rn.stks()[0].watchtower.remove_plugins([revault_all_path])

    # Test a policy limiting the amount we can unvault per day
    max_per_day_path = os.path.join(WT_PLUGINS_DIR, "max_value_per_day.py")
    datadir = os.path.join(directory, "max_per_day_plugin_datadir")
    plugin = {
        "path": max_per_day_path,
        "config": {"max_value": 5 * COIN, "data_dir": datadir},
    }
    rn.stks()[1].watchtower.add_plugins([plugin])
    # The first one will go through (4 < 5)
    v = vaults[-1]
    assert v["amount"] == 4 * COIN
    deposits, spend_psbt = rn.spend_vaults_anyhow_unconfirmed([v])
    for stk in rn.stks():
        stk.watchtower.wait_for_log(
            f"Got a confirmed Unvault UTXO for vault at '{v['txid']}:{v['vout']}'"
        )
    bitcoind.generate_block(1, wait_for_mempool=[spend_psbt.tx.hash])
    wait_for(
        lambda: len(rn.man(0).rpc.listvaults(["spent"], deposits)["vaults"])
        == len(deposits)
    )
    # The second one won't (4 + 2 > 5)
    v = vaults[-2]
    assert v["amount"] == 2 * COIN
    assert len(bitcoind.rpc.getrawmempool()) == 0
    rn.broadcast_unvaults_anyhow([v])
    bitcoind.generate_block(1, 1)
    rn.stks()[1].watchtower.wait_for_log("Broadcasted Cancel transaction")
    for stk in rn.stks():
        deposit = f"{v['txid']}:{v['vout']}"
        wait_for(
            lambda: len(stk.rpc.listvaults(["canceling"], [deposit])["vaults"]) == 1
        )
    bitcoind.generate_block(1)
    # But it will if we wait till the next day, it'll go through
    bitcoind.generate_block(144)
    v = vaults[-3]
    assert v["amount"] == 2 * COIN
    rn.spend_vaults_anyhow([v])
Ejemplo n.º 25
0
def test_reorged_unvault(revault_network, bitcoind):
    """Test various scenarii with reorgs around the Unvault transaction of a vault."""
    CSV = 12
    revault_network.deploy(4, 2, csv=CSV, with_watchtowers=False)
    man = revault_network.man(0)
    vaults = revault_network.fundmany([32, 3])
    deposits = []
    amounts = []
    for v in vaults:
        revault_network.secure_vault(v)
        revault_network.activate_vault(v)
        deposits.append(f"{v['txid']}:{v['vout']}")
        amounts.append(v["amount"])

    addr = bitcoind.rpc.getnewaddress()
    amount = sum(amounts)
    feerate = 1
    fee = revault_network.compute_spendtx_fees(feerate, len(vaults), 1)
    destinations = {addr: amount - fee}
    revault_network.unvault_vaults(vaults, destinations, feerate)
    bitcoind.generate_block(1)

    unvault_tx_a = man.rpc.listonchaintransactions(
        [deposits[0]])["onchain_transactions"][0]["unvault"]
    unvault_tx_b = man.rpc.listonchaintransactions(
        [deposits[1]])["onchain_transactions"][0]["unvault"]

    # Initial sanity checks..
    assert unvault_tx_a["blockheight"] == unvault_tx_b["blockheight"]
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == bitcoind.rpc.
                 getblockcount())
        assert len(w.rpc.listvaults(["unvaulted"],
                                    deposits)["vaults"]) == len(deposits)
        for vault in w.rpc.listvaults(["unvaulted"], deposits)["vaults"]:
            assert vault["moved_at"] is None
            for field in timestamps_from_status("unvaulted"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("unvaulted", present=False):
                assert vault[field] is None, field

    # First, if we reorg but not up to the Unvault tx height, nothing will happen.
    bitcoind.simple_reorg(unvault_tx_a["blockheight"] + 1)
    height = bitcoind.rpc.getblockcount()
    new_tip = f"{height}.*{bitcoind.rpc.getblockhash(height)}"
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"{deposits[0]}.* First Stage transaction is still confirmed .*'{unvault_tx_a['blockheight']}'",
            f"{deposits[1]}.* First Stage transaction is still confirmed .*'{unvault_tx_b['blockheight']}'",
            "Rescan .*done",
            f"New tip.* {new_tip}",
        ])
        assert len(w.rpc.listvaults(["unvaulted"],
                                    deposits)["vaults"]) == len(deposits)
        for vault in w.rpc.listvaults(["unvaulted"], deposits)["vaults"]:
            assert vault["moved_at"] is None
            for field in timestamps_from_status("unvaulted"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("unvaulted", present=False):
                assert vault[field] is None, field

    # Now, if the Unvault tx moves we'll rewind up to the ancestor, rescan the chain
    # and get back to the 'unvaulted' state.
    bitcoind.simple_reorg(unvault_tx_a["blockheight"], shift=1)
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Vault {deposits[0]}'s Unvault transaction .* got unconfirmed",
            f"Vault {deposits[1]}'s Unvault transaction .* got unconfirmed",
            "Rescan of all vaults in db done.",
        ])
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == bitcoind.rpc.
                 getblockcount())
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                        ) == len(deposits))
        for vault in w.rpc.listvaults(["unvaulted"], deposits)["vaults"]:
            assert vault["moved_at"] is None
            for field in timestamps_from_status("unvaulted"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("unvaulted", present=False):
                assert vault[field] is None, field

    # If it's not confirmed anymore, we'll detect it and mark the vault as unvaulting
    unvault_tx_a = man.rpc.listonchaintransactions(
        [deposits[0]])["onchain_transactions"][0]["unvault"]
    bitcoind.simple_reorg(unvault_tx_a["blockheight"], shift=-1)
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Vault {deposits[0]}'s Unvault transaction .* got unconfirmed",
            f"Vault {deposits[1]}'s Unvault transaction .* got unconfirmed",
            "Rescan of all vaults in db done.",
        ])
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == bitcoind.rpc.
                 getblockcount())
        assert len(w.rpc.listvaults(["unvaulting"],
                                    deposits)["vaults"]) == len(deposits)
        for vault in w.rpc.listvaults(["unvaulting"], deposits)["vaults"]:
            assert vault["moved_at"] is None
            for field in timestamps_from_status("unvaulting"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("unvaulting", present=False):
                assert vault[field] is None, field

    # Now if we are spending
    # unvault_vault() above actually registered the Spend transaction, so we can activate
    # it by generating enough block for it to be mature.
    # NOTE: this exercises the logic of "jump from unvaulting to spending state"
    assert len(bitcoind.rpc.getrawmempool()) == len(vaults)
    bitcoind.generate_block(1, wait_for_mempool=len(vaults))
    bitcoind.generate_block(CSV - 1)
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == bitcoind.rpc.
                 getblockcount())
        wait_for(
            lambda: len(w.rpc.listvaults(["spending"], deposits)["vaults"]
                        ) == len(deposits))
        for vault in w.rpc.listvaults(["spending"], deposits)["vaults"]:
            assert vault["moved_at"] is None
            for field in timestamps_from_status("spending"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("spending", present=False):
                assert vault[field] is None, field

    # If we are 'spending' and the Unvault gets unconfirmed, we'll rewind, get back to
    # unvaulting, and mark the Spend for re-broadcast
    unvault_tx_a = man.rpc.listonchaintransactions(
        [deposits[0]])["onchain_transactions"][0]["unvault"]
    bitcoind.simple_reorg(unvault_tx_a["blockheight"], shift=-1)
    height = bitcoind.rpc.getblockcount()
    new_tip = f"{height}.*{bitcoind.rpc.getblockhash(height)}"
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Vault {deposits[0]}'s Unvault transaction .* got unconfirmed",
            f"Vault {deposits[1]}'s Unvault transaction .* got unconfirmed",
            "Rescan of all vaults in db done.",
            f"New tip.* {new_tip}",
        ])
    for w in revault_network.participants():
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulting"], deposits)["vaults"]
                        ) == len(deposits))
        for vault in w.rpc.listvaults(["unvaulting"], deposits)["vaults"]:
            assert vault["moved_at"] is None
            for field in timestamps_from_status("unvaulting"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("unvaulting", present=False):
                assert vault[field] is None, field

    # Get to re-broadcast the spend
    bitcoind.generate_block(1, wait_for_mempool=len(vaults))
    bitcoind.generate_block(CSV - 1)
    for w in revault_network.participants():
        wait_for(
            lambda: len(w.rpc.listvaults(["spending"], deposits)["vaults"]
                        ) == len(deposits))
        for vault in w.rpc.listvaults(["spending"], deposits)["vaults"]:
            assert vault["moved_at"] is None
            for field in timestamps_from_status("spending"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("spending", present=False):
                assert vault[field] is None, field

    # And confirm it
    bitcoind.generate_block(1, wait_for_mempool=1)
    for w in revault_network.participants():
        wait_for(lambda: len(w.rpc.listvaults(["spent"], deposits)["vaults"])
                 == len(deposits))
        for vault in w.rpc.listvaults(["spent"], deposits)["vaults"]:
            for field in timestamps_from_status("spent"):
                assert vault[field] is not None, field
            for field in timestamps_from_status("spent", present=False):
                assert vault[field] is None, field
Ejemplo n.º 26
0
def test_reorged_cancel(revault_network, bitcoind):
    revault_network.deploy(4, 2, csv=12, with_watchtowers=False)
    stks = revault_network.stks()
    mans = revault_network.mans()
    vault = revault_network.fund(32)
    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    deposit = f"{vault['txid']}:{vault['vout']}"
    amount = vault["amount"]

    addr = bitcoind.rpc.getnewaddress()
    feerate = 1
    fee = revault_network.compute_spendtx_fees(feerate, 1, 1)
    destinations = {addr: amount - fee}
    revault_network.unvault_vaults([vault], destinations, feerate)
    unvault_tx = mans[0].rpc.listonchaintransactions(
        [deposit])["onchain_transactions"][0]["unvault"]

    # Now let's cancel the spending
    revault_network.cancel_vault(vault)
    cancel_tx = mans[0].rpc.listonchaintransactions(
        [deposit])["onchain_transactions"][0]["cancel"]
    initial_moved_at = revault_network.stk(
        0).rpc.listvaults()["vaults"][0]["moved_at"]

    # Reorging, but not unconfirming the cancel
    bitcoind.simple_reorg(cancel_tx["blockheight"])
    for w in stks + mans:
        w.wait_for_logs([
            "Detected reorg",
            f"Vault {deposit}'s Cancel transaction got unconfirmed",
            "Rescan of all vaults in db done.",
        ])
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == bitcoind.rpc.
                 getblockcount())

    # Let's unconfirm the cancel and check that the vault is now in 'canceling' state
    bitcoind.simple_reorg(cancel_tx["blockheight"], shift=-1)
    for w in stks + mans:
        w.wait_for_logs([
            "Detected reorg",
            f"Vault {deposit}'s Cancel transaction got unconfirmed",
            "Rescan of all vaults in db done.",
        ])
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == bitcoind.rpc.
                 getblockcount())
    for w in stks + mans:
        wait_for(lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
                 == "canceling")
        vault = w.rpc.listvaults([], [deposit])["vaults"][0]
        assert vault["moved_at"] is None
        for field in timestamps_from_status("canceling"):
            assert vault[field] is not None, field
        for field in timestamps_from_status("canceling", present=False):
            assert vault[field] is None, field

    # Confirming the cancel again
    bitcoind.generate_block(1, wait_for_mempool=1)
    for w in stks + mans:
        w.wait_for_log("Cancel tx .* was confirmed at height .*")
        wait_for(lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
                 == "canceled")
        for field in timestamps_from_status("canceled"):
            vault = w.rpc.listvaults([], [deposit])["vaults"][0]
            assert vault[field] is not None, field
        for field in timestamps_from_status("canceled", present=False):
            assert vault[field] is None, field
        # It's in a new block, it shouldn't have the same timestamp!
        assert vault["moved_at"] != initial_moved_at

    # Let's unconfirm the unvault
    bitcoind.simple_reorg(unvault_tx["blockheight"], shift=-1)
    for w in stks + mans:
        w.wait_for_log(
            f"Vault {deposit}'s Unvault transaction .* got unconfirmed")

    # Here we go canceling everything again
    bitcoind.generate_block(1, wait_for_mempool=2)
    for w in stks + mans:
        wait_for(lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
                 == "canceled")
        for field in timestamps_from_status("canceled"):
            assert [field] is not None, field
        for field in timestamps_from_status("canceled", present=False):
            assert vault[field] is None, field
Ejemplo n.º 27
0
def test_spend_threshold(revault_network, bitcoind, executor):
    CSV = 20
    managers_threshold = 2
    revault_network.deploy(4,
                           3,
                           csv=CSV,
                           managers_threshold=managers_threshold)
    man = revault_network.man(0)

    # Get some more funds
    bitcoind.generate_block(1)

    vaults = []
    deposits = []
    deriv_indexes = []
    total_amount = 0
    for i in range(5):
        amount = random.randint(5, 5000) / 100
        vaults.append(revault_network.fund(amount))
        deposits.append(f"{vaults[i]['txid']}:{vaults[i]['vout']}")
        deriv_indexes.append(vaults[i]["derivation_index"])
        total_amount += vaults[i]["amount"]
    revault_network.activate_fresh_vaults(vaults)

    feerate = 1
    n_outputs = 3
    fees = revault_network.compute_spendtx_fees(feerate, len(deposits),
                                                n_outputs)
    destinations = {
        bitcoind.rpc.getnewaddress(): (total_amount - fees) // n_outputs
        for _ in range(n_outputs)
    }
    spend_tx = man.rpc.getspendtx(deposits, destinations, feerate)["spend_tx"]

    # Trying to broadcast when managers_threshold - 1 managers signed
    for man in revault_network.mans()[:managers_threshold - 1]:
        spend_tx = man.man_keychain.sign_spend_psbt(spend_tx, deriv_indexes)
    man.rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()

    # Revaultd didn't like it
    with pytest.raises(
            RpcError,
            match=
            f"Not enough signatures, needed: {managers_threshold}, current: {managers_threshold - 1}'",
    ):
        man.rpc.setspendtx(spend_psbt.tx.hash)

    # Killing the daemon and restart shouldn't cause any issue
    for m in revault_network.mans():
        m.stop()
        m.start()

    # Alright, I'll make the last manager sign...
    man = revault_network.mans()[managers_threshold]
    spend_tx = man.man_keychain.sign_spend_psbt(spend_tx, deriv_indexes)
    man.rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()

    # All good now?
    man.rpc.setspendtx(spend_psbt.tx.hash)

    for m in revault_network.mans():
        wait_for(
            lambda: len(m.rpc.listvaults(["unvaulting"], deposits)["vaults"]
                        ) == len(deposits))

    # Killing the daemon and restart it while unvaulting shouldn't cause
    # any issue
    for m in revault_network.mans():
        m.stop()
        m.start()

    # We need a single confirmation to consider the Unvault transaction confirmed
    bitcoind.generate_block(1, wait_for_mempool=len(deposits))
    for m in revault_network.mans():
        wait_for(
            lambda: len(m.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                        ) == len(deposits))

    # We'll broadcast the Spend transaction as soon as it's valid
    bitcoind.generate_block(CSV)
    man.wait_for_log(
        f"Succesfully broadcasted Spend tx '{spend_psbt.tx.hash}'", )
    for m in revault_network.mans():
        wait_for(
            lambda: len(m.rpc.listvaults(["spending"], deposits)["vaults"]
                        ) == len(deposits))

    # And will mark it as spent after a single confirmation of the Spend tx
    bitcoind.generate_block(1, wait_for_mempool=[spend_psbt.tx.hash])
    for m in revault_network.mans():
        wait_for(lambda: len(m.rpc.listvaults(["spent"], deposits)["vaults"])
                 == len(deposits))
        for vault in m.rpc.listvaults(["spent"], deposits)["vaults"]:
            assert vault["moved_at"] is not None
Ejemplo n.º 28
0
def test_retrieve_vault_status(revault_network, bitcoind):
    """Test we keep track of coins that moved without us actively noticing it."""
    CSV = 3
    revault_network.deploy(2, 2, csv=CSV)
    stks = revault_network.stk_wallets
    # We don't use mans() here as we need a reference to the actual list in order to
    # modify it.
    mans = revault_network.man_wallets

    # Create a new deposit, makes everyone aware of it. Then stop one of the
    # wallets for it to not notice anything from now on.
    vault = revault_network.fund(0.05)
    man = mans.pop(0)
    man.stop()

    # Now activate and Spend the vault, the manager does not acknowledge it (yet)
    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    deposits = [f"{vault['txid']}:{vault['vout']}"]
    destinations = {bitcoind.rpc.getnewaddress(): vault["amount"] // 2}
    spend_tx = mans[0].rpc.getspendtx(deposits, destinations, 1)["spend_tx"]
    for m in [man] + mans:
        spend_tx = m.man_keychain.sign_spend_psbt(spend_tx,
                                                  [vault["derivation_index"]])
        mans[0].rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    mans[0].rpc.setspendtx(spend_psbt.tx.hash)

    bitcoind.generate_block(1, wait_for_mempool=len(deposits))
    bitcoind.generate_block(CSV)
    mans[0].wait_for_log(
        f"Succesfully broadcasted Spend tx '{spend_psbt.tx.hash}'", )
    wait_for(lambda: len(mans[0].rpc.listvaults(["spending"], deposits)[
        "vaults"]) == 1)

    # The manager should restart, and acknowledge the vault as being "spending"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(mans[0].rpc.listvaults(["spending"], deposits)[
        "vaults"]) == len(deposits))

    # And if we mine it now everyone will see it as "spent"
    bitcoind.generate_block(1, wait_for_mempool=spend_psbt.tx.hash)
    for w in mans + revault_network.stks():
        wait_for(lambda: len(w.rpc.listvaults(["spent"], deposits)["vaults"])
                 == len(deposits))

    # Now do the same dance with a "spent" vault
    vault = revault_network.fund(0.14)
    man = mans.pop(0)
    man.stop()

    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    deposits = [f"{vault['txid']}:{vault['vout']}"]
    destinations = {bitcoind.rpc.getnewaddress(): vault["amount"] // 2}
    spend_tx = mans[0].rpc.getspendtx(deposits, destinations, 1)["spend_tx"]
    for m in [man] + mans:
        spend_tx = m.man_keychain.sign_spend_psbt(spend_tx,
                                                  [vault["derivation_index"]])
        mans[0].rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    mans[0].rpc.setspendtx(spend_psbt.tx.hash)

    bitcoind.generate_block(1, wait_for_mempool=len(deposits))
    bitcoind.generate_block(CSV)
    mans[0].wait_for_log(
        f"Succesfully broadcasted Spend tx '{spend_psbt.tx.hash}'", )
    bitcoind.generate_block(1, wait_for_mempool=spend_psbt.tx.hash)
    for w in mans + revault_network.stks():
        wait_for(lambda: len(w.rpc.listvaults(["spent"], deposits)["vaults"])
                 == len(deposits))

    # The manager should restart, and acknowledge the vault as being "spent"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(mans[0].rpc.listvaults(["spent"], [deposit])["vaults"]
                         ) == len(deposits))

    # Now do the same dance with a "canceling" vault
    vault = revault_network.fund(8)
    man = mans.pop(0)
    man.stop()

    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    deposits = [f"{vault['txid']}:{vault['vout']}"]
    destinations = {bitcoind.rpc.getnewaddress(): vault["amount"] // 2}
    spend_tx = mans[0].rpc.getspendtx(deposits, destinations, 1)["spend_tx"]
    for m in [man] + mans:
        spend_tx = m.man_keychain.sign_spend_psbt(spend_tx,
                                                  [vault["derivation_index"]])
        mans[0].rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    mans[0].rpc.setspendtx(spend_psbt.tx.hash)
    bitcoind.generate_block(1, wait_for_mempool=len(deposits))

    # Cancel it
    for w in mans + revault_network.stks():
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                        ) == len(deposits))
    mans[0].rpc.revault(deposits[0])
    for w in mans + revault_network.stks():
        wait_for(
            lambda: len(w.rpc.listvaults(["canceling"], deposits)["vaults"]
                        ) == len(deposits))
    # The manager should restart, and acknowledge the vault as being "canceling"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(mans[0].rpc.listvaults(["canceling"], [deposit])[
        "vaults"]) == len(deposits))

    # Now do the same dance with a "canceled" vault
    vault = revault_network.fund(19)
    man = mans.pop(0)
    man.stop()

    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    deposits = [f"{vault['txid']}:{vault['vout']}"]
    destinations = {bitcoind.rpc.getnewaddress(): vault["amount"] // 2}
    spend_tx = mans[0].rpc.getspendtx(deposits, destinations, 1)["spend_tx"]
    for m in [man] + mans:
        spend_tx = m.man_keychain.sign_spend_psbt(spend_tx,
                                                  [vault["derivation_index"]])
        mans[0].rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    mans[0].rpc.setspendtx(spend_psbt.tx.hash)
    bitcoind.generate_block(1, wait_for_mempool=len(deposits))

    # Cancel it
    for w in mans + revault_network.stks():
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                        ) == len(deposits))
    mans[0].rpc.revault(deposits[0])
    bitcoind.generate_block(1, wait_for_mempool=1)
    for w in mans + revault_network.stks():
        wait_for(
            lambda: len(w.rpc.listvaults(["canceled"], deposits)["vaults"]
                        ) == len(deposits))
    # The manager should restart, and acknowledge the vault as being "canceled"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(mans[0].rpc.listvaults(["canceled"], [deposit])[
        "vaults"]) == len(deposits))

    # Now do the same dance with a "unvaulting" vault
    vault = revault_network.fund(41)
    man = mans.pop(0)
    man.stop()

    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    deposits = [f"{vault['txid']}:{vault['vout']}"]
    destinations = {bitcoind.rpc.getnewaddress(): vault["amount"] // 2}
    spend_tx = mans[0].rpc.getspendtx(deposits, destinations, 1)["spend_tx"]
    for m in [man] + mans:
        spend_tx = m.man_keychain.sign_spend_psbt(spend_tx,
                                                  [vault["derivation_index"]])
        mans[0].rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    mans[0].rpc.setspendtx(spend_psbt.tx.hash)

    for w in mans + revault_network.stks():
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulting"], deposits)["vaults"]
                        ) == len(deposits))

    # The manager should restart, and acknowledge the vault as being "unvaulting"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(mans[0].rpc.listvaults(["unvaulting"], [deposit])[
        "vaults"]) == len(deposits))

    # Now do the same dance with a "unvaulted" vault
    vault = revault_network.fund(99)
    man = mans.pop(0)
    man.stop()

    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)
    deposits = [f"{vault['txid']}:{vault['vout']}"]
    destinations = {bitcoind.rpc.getnewaddress(): vault["amount"] // 2}
    spend_tx = mans[0].rpc.getspendtx(deposits, destinations, 1)["spend_tx"]
    for m in [man] + mans:
        spend_tx = m.man_keychain.sign_spend_psbt(spend_tx,
                                                  [vault["derivation_index"]])
        mans[0].rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    mans[0].rpc.setspendtx(spend_psbt.tx.hash)

    bitcoind.generate_block(1, wait_for_mempool=len(deposits))
    for w in mans + revault_network.stks():
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                        ) == len(deposits))

    # The manager should restart, and acknowledge the vault as being "unvaulted"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(mans[0].rpc.listvaults(["unvaulted"], [deposit])[
        "vaults"]) == len(deposits))

    # Now do the same dance with an "active" vault
    vault = revault_network.fund(0.0556789)
    man = mans.pop(0)
    man.stop()

    revault_network.secure_vault(vault)
    revault_network.activate_vault(vault)

    # The manager should restart, and acknowledge the vault as being "active"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    mans[0].wait_for_active_vaults([deposit])

    # Now do the same dance with a "secured" vault
    vault = revault_network.fund(0.123456)
    man = mans.pop(0)
    man.stop()

    revault_network.secure_vault(vault)

    # The manager should restart, and acknowledge the vault as being "secured"
    mans.insert(0, man)
    mans[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    mans[0].wait_for_secured_vaults([deposit])

    # Now do the same dance with an "emergencyvaulting" vault
    vault = revault_network.fund(0.98634)
    deposit = f"{vault['txid']}:{vault['vout']}"
    revault_network.secure_vault(vault)
    stk = stks.pop(0)
    stk.stop()

    stks[0].rpc.emergency()
    wait_for(lambda: len(stks[0].rpc.listvaults(["emergencyvaulting"],
                                                [deposit])["vaults"]) == 1)

    # The stakeholder should restart, and acknowledge the vault as being "emergencyvaulting"
    stks.insert(0, stk)
    stks[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(stks[0].rpc.listvaults(["emergencyvaulting"],
                                                [deposit])["vaults"]) == 1)

    # Now do the same dance with an "unvaultemergencyvaulting" vault
    vault = revault_network.fund(1.64329)
    deposit = f"{vault['txid']}:{vault['vout']}"
    revault_network.activate_fresh_vaults([vault])
    revault_network.unvault_vaults_anyhow([vault])
    stk = stks.pop(0)
    stk.stop()

    stks[0].rpc.emergency()
    wait_for(lambda: len(stks[0].rpc.listvaults(["unvaultemergencyvaulting"],
                                                [deposit])["vaults"]) == 1)

    # The stakeholder should restart, and acknowledge the vault as being "emergencyvaulting"
    stks.insert(0, stk)
    stks[0].start()
    deposit = f"{vault['txid']}:{vault['vout']}"
    wait_for(lambda: len(stks[0].rpc.listvaults(["unvaultemergencyvaulting"],
                                                [deposit])["vaults"]) == 1)
Ejemplo n.º 29
0
def test_large_spends(revault_network, bitcoind, executor):
    CSV = 2016  # 2 weeks :tm:
    revault_network.deploy(17, 8, csv=CSV)
    man = revault_network.man(0)

    # Get some more funds
    bitcoind.generate_block(1)

    vaults = []
    deposits = []
    deriv_indexes = []
    total_amount = 0
    for i in range(10):
        amount = random.randint(5, 5000) / 100
        vaults.append(revault_network.fund(amount))
        deposits.append(f"{vaults[i]['txid']}:{vaults[i]['vout']}")
        deriv_indexes.append(vaults[i]["derivation_index"])
        total_amount += vaults[i]["amount"]
    revault_network.activate_fresh_vaults(vaults)

    feerate = 1
    n_outputs = random.randint(1, 3)
    fees = revault_network.compute_spendtx_fees(feerate, len(deposits),
                                                n_outputs)
    destinations = {
        bitcoind.rpc.getnewaddress(): (total_amount - fees) // n_outputs
        for _ in range(n_outputs)
    }
    spend_tx = man.rpc.getspendtx(deposits, destinations, feerate)["spend_tx"]

    for man in revault_network.mans():
        spend_tx = man.man_keychain.sign_spend_psbt(spend_tx, deriv_indexes)
    man.rpc.updatespendtx(spend_tx)

    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    man.rpc.setspendtx(spend_psbt.tx.hash)

    # Killing the daemon and restart it while unvaulting shouldn't cause
    # any issue
    for man in revault_network.mans():
        man.stop()
        man.start()

    wait_for(
        lambda: len(man.rpc.listvaults(["unvaulting"], deposits)["vaults"]
                    ) == len(deposits))
    # We need a single confirmation to consider the Unvault transaction confirmed
    bitcoind.generate_block(1, wait_for_mempool=len(deposits))
    wait_for(
        lambda: len(man.rpc.listvaults(["unvaulted"], deposits)["vaults"]
                    ) == len(deposits))

    # We'll broadcast the Spend transaction as soon as it's valid
    # Note that bitcoind's RPC socket may timeout if it needs to generate too many
    # blocks at once. So, spread them a bit.
    for _ in range(10):
        bitcoind.generate_block(CSV // 10)
    bitcoind.generate_block(CSV % 10 - 1)
    man.wait_for_log(
        f"Succesfully broadcasted Spend tx '{spend_psbt.tx.hash}'", )
    wait_for(lambda: len(man.rpc.listvaults(["spending"], deposits)["vaults"])
             == len(deposits))

    # And will mark it as spent after a single confirmation of the Spend tx
    bitcoind.generate_block(1, wait_for_mempool=[spend_psbt.tx.hash])
    wait_for(lambda: len(man.rpc.listvaults(["spent"], deposits)["vaults"]) ==
             len(deposits))
    for vault in man.rpc.listvaults(["spent"], deposits)["vaults"]:
        assert vault["moved_at"] is not None
Ejemplo n.º 30
0
def reorg_deposit(revault_network, bitcoind, deposit, stop_wallets,
                  target_status):
    """Reorganize the chain around a deposit according to different scenarii.
    The deposit must refer to a vault that is at least confirmed.
    The `stop_wallets` parameter controls whether to stop the daemons during a reorg.
    The `target_status` parameter indicates the expected status of the vault if its
    deposit transaction gets unconfirmed then re-confirmed.
    """
    vault = revault_network.stk(0).rpc.listvaults([], [deposit])["vaults"][0]
    initial_confs = bitcoind.rpc.getblockcount() - vault["blockheight"] + 1
    logging.info(
        f"Initial vault blockheight {vault['blockheight']} ({initial_confs} confs)"
    )

    # Sanity check the timestamps
    for field in timestamps_from_status(vault["status"]):
        assert vault[field] is not None, field
    for field in timestamps_from_status(vault["status"], present=False):
        assert vault[field] is None, field

    # Mine a block and reorg it, it should not affect us since the deposit would still
    # have more than 6 confs.
    bitcoind.generate_block(1)
    height = bitcoind.rpc.getblockcount()
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == height)
    reorg(revault_network, bitcoind, stop_wallets, height)
    new_tip = f"{height + 1}.*{bitcoind.rpc.getblockhash(height + 1)}"
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Found common ancestor at height {height - 1}",
            f"Vault deposit '{deposit}' still has {initial_confs} confirmations at common ancestor",
            "Rescan .*done",
            f"New tip.* {new_tip}",
        ])
        v = w.rpc.listvaults([], [deposit])["vaults"][0]
        assert v["status"] == vault["status"]
        for field in timestamps_from_status(vault["status"]):
            assert v[field] is not None, field
        for field in timestamps_from_status(vault["status"], present=False):
            assert v[field] is None, field
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == height + 1)

    height = bitcoind.rpc.getblockcount()
    vault = w.rpc.listvaults([], [deposit])["vaults"][0]
    confs = height + 1 - vault["blockheight"]
    logging.info(
        f"After first reorg. Vault blockheight {vault['blockheight']} ({confs} confs)"
    )

    # Now actually shift it out.
    # It won't transition to 'funded'...
    reorg(revault_network,
          bitcoind,
          stop_wallets,
          vault["blockheight"],
          shift=-1)
    new_tip = f"{height + 1}.*{bitcoind.rpc.getblockhash(height + 1)}"
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Found common ancestor at height {vault['blockheight'] - 1}",
            f"Vault deposit '{deposit}' has 0 confirmations at common ancestor",
            "Rescan .*done",
            f"New tip.* {new_tip}",
        ])
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == height + 1)
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
                 == "unconfirmed")
        vault = w.rpc.listvaults([], [deposit])["vaults"][0]
        for field in ["funded_at", "secured_at", "delegated_at", "moved_at"]:
            assert vault[field] is None, field

    # ... But it will if we re-confirm it!
    bitcoind.generate_block(6, wait_for_mempool=vault["txid"])
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
                 == target_status)
        vault = w.rpc.listvaults([], [deposit])["vaults"][0]
        for field in timestamps_from_status(target_status):
            assert vault[field] is not None, field
        for field in timestamps_from_status(target_status, present=False):
            assert vault[field] is None, field

    height = bitcoind.rpc.getblockcount()
    vault = w.rpc.listvaults([], [deposit])["vaults"][0]
    confs = height + 1 - vault["blockheight"]
    logging.info(
        f"After second reorg. Vault blockheight {vault['blockheight']} ({confs} confs)"
    )

    # Now reorg 1 block of the 6 making the vault funded. This should get the deposit under
    # the minimum number of confirmations threshold.
    # But since the newly connected chain has as many blocks, the vault will get back to
    # 'funded'. And since the deposit didn't change, the signatures on the coordinator are
    # still valid. It will re-download them and transition back to 'secured' / 'active'. Then
    # if some second-stage transactions were broadcasted, they will be re-broadcast.
    reorged_block_height = vault["blockheight"] + 5
    reorg(revault_network, bitcoind, stop_wallets, reorged_block_height)
    new_tip = f"{height + 1}.*{bitcoind.rpc.getblockhash(height + 1)}"
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Found common ancestor at height {reorged_block_height - 1}",
            f"Vault deposit '{deposit}' has 5 confirmations at common ancestor",
            "Rescan .*done",
            f"New tip.* {new_tip}",
        ])
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == height + 1)
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
                 == target_status)
        vault = w.rpc.listvaults([], [deposit])["vaults"][0]
        for field in timestamps_from_status(target_status):
            assert vault[field] is not None, field
        for field in timestamps_from_status(target_status, present=False):
            assert vault[field] is None, field

    height = bitcoind.rpc.getblockcount()
    vault = w.rpc.listvaults([], [deposit])["vaults"][0]
    confs = height + 1 - vault["blockheight"]
    logging.info(
        f"After third reorg. Vault blockheight {vault['blockheight']} ({confs} confs)"
    )

    # Now reorg up to the deposit. The same will happen.
    reorg(revault_network, bitcoind, stop_wallets, vault["blockheight"])
    new_tip = f"{height + 1}.*{bitcoind.rpc.getblockhash(height + 1)}"
    for w in revault_network.participants():
        w.wait_for_logs([
            "Detected reorg",
            f"Found common ancestor at height {vault['blockheight'] - 1}",
            f"Vault deposit '{deposit}' has 0 confirmations at common ancestor",
            "Rescan .*done",
            f"New tip.* {new_tip}",
        ])
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.getinfo()["blockheight"] == height + 1)
    for w in revault_network.participants():
        wait_for(lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
                 == target_status)
        for field in timestamps_from_status(target_status):
            assert vault[field] is not None, field
        for field in timestamps_from_status(target_status, present=False):
            assert vault[field] is None, field

    height = bitcoind.rpc.getblockcount()
    vault = w.rpc.listvaults([], [deposit])["vaults"][0]
    confs = height + 1 - vault["blockheight"]
    logging.info(
        f"After fourth reorg. Vault blockheight {vault['blockheight']} ({confs} confs)"
    )