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 psbt_add_input(psbt_str):
    psbt = serializations.PSBT()
    psbt.deserialize(psbt_str)
    assert len(psbt.inputs) == 1
    psbt.inputs.append(serializations.PartiallySignedInput())
    psbt.inputs[1].witness_utxo = copy.copy(psbt.inputs[0].witness_utxo)
    psbt.inputs[1].witness_utxo.nValue = 12398
    psbt.inputs[1].witness_script = psbt.inputs[0].witness_script
    psbt.tx.vin.append(serializations.CTxIn())
    return psbt.serialize()
Ejemplo n.º 3
0
def get_unvault_txids(wallet, vaults):
    unvault_txids = []
    for vault in vaults:
        deposit = f"{vault['txid']}:{vault['vout']}"
        unvault_psbt = serializations.PSBT()
        unvault_b64 = wallet.rpc.listpresignedtransactions(
            [deposit])["presigned_transactions"][0]["unvault"]["psbt"]
        unvault_psbt.deserialize(unvault_b64)
        unvault_psbt.tx.calc_sha256()
        unvault_txids.append(unvault_psbt.tx.hash)
    return unvault_txids
Ejemplo n.º 4
0
def psbt_add_invalid_sig(psbt_str):
    psbt = serializations.PSBT()
    psbt.deserialize(psbt_str)
    assert len(psbt.inputs) == 1
    pk = bytes.fromhex(
        "02c83dc7fb3ed0a5dd33cf35d891ba4fcbde" "90ede809a0b247a46f4d989dd14411"
    )
    sig = bytes.fromhex(
        "3045022100894f5c61d1c297227a9a094ea471fd9d84b"
        "61d4fc78eb71376621758df8c4946022073f5c11e62add56c4c9"
        "10bc90d0eadb154919e0c6c67b909897bda13cae3620d"
    )
    psbt.inputs[0].partial_sigs[pk] = sig
    return psbt.serialize()
Ejemplo n.º 5
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.º 6
0
    def sign_unvault_psbt(self, psbt_str, deriv_index):
        """Attach an ALL signature to the PSBT with the key at {deriv_index}"""
        assert isinstance(psbt_str, str)

        psbt = serializations.PSBT()
        psbt.deserialize(psbt_str)
        assert len(psbt.inputs) == 1, "Invalid Unvault PSBT"
        assert (serializations.make_p2wsh(psbt.inputs[0].witness_script) ==
                psbt.inputs[0].witness_utxo.scriptPubKey)

        script_code = psbt.inputs[0].witness_script
        sighash = serializations.sighash_all_witness(script_code, psbt, 0)
        privkey = coincurve.PrivateKey(
            self.hd.get_privkey_from_path([deriv_index]))
        sig = privkey.sign(sighash, hasher=None) + b"\x01"  # ALL

        pubkey = self.hd.get_pubkey_from_path([deriv_index])
        psbt.inputs[0].partial_sigs[pubkey] = sig

        return psbt.serialize()
Ejemplo n.º 7
0
    def sign_spend_psbt(self, psbt_str, deriv_indexes):
        """Attach an ALL signature to each PSBT input with the keys at
        {deriv_indexes}"""
        assert isinstance(psbt_str, str)
        assert isinstance(deriv_indexes, list)

        psbt = serializations.PSBT()
        psbt.deserialize(psbt_str)
        assert len(
            psbt.inputs) == len(deriv_indexes), "Not enough derivation indexes"

        for (i, psbtin) in enumerate(psbt.inputs):
            script_code = psbtin.witness_script
            sighash = serializations.sighash_all_witness(script_code, psbt, i)
            privkey = coincurve.PrivateKey(
                self.hd.get_privkey_from_path([deriv_indexes[i]]))
            sig = privkey.sign(sighash, hasher=None) + b"\x01"  # ALL

            pubkey = self.hd.get_pubkey_from_path([deriv_indexes[i]])
            psbtin.partial_sigs[pubkey] = sig

        return psbt.serialize()
Ejemplo n.º 8
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.º 9
0
    def broadcast_unvaults(self, vaults, destinations, feerate, priority=False):
        """
        Broadcast the Unvault transactions for 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, priority)
        return spend_psbt
Ejemplo n.º 10
0
def test_spends_conflicting(revault_network, bitcoind):
    """
    Here we test two spends which spends 2 vaults each, with one shared and all vaults
    being created from the same Deposit transaction.
    """
    # Get some more coins
    bitcoind.generate_block(12)

    CSV = 112
    revault_network.deploy(5, 3, csv=CSV)
    man = revault_network.man(0)
    amounts = [0.1, 64, 410]
    vaults = revault_network.fundmany(amounts)
    assert len(vaults) == len(amounts)
    # Edge case: bitcoind can actually mess up with the amounts
    amounts = []
    deposits = []
    deriv_indexes = []
    for v in vaults:
        revault_network.secure_vault(v)
        revault_network.activate_vault(v)
        deposits.append(f"{v['txid']}:{v['vout']}")
        deriv_indexes.append(v["derivation_index"])
        amounts.append(v["amount"])

    (deposits_a, deposits_b) = (deposits[:2], deposits[1:])
    (amounts_a, amounts_b) = (amounts[:2], amounts[1:])
    (indexes_a, indexes_b) = (deriv_indexes[:2], deriv_indexes[1:])

    feerate = 5_000
    fees = revault_network.compute_spendtx_fees(feerate, len(deposits_a), 1)
    destinations = {bitcoind.rpc.getnewaddress(): sum(amounts_a) - fees}
    spend_tx_a = man.rpc.getspendtx(deposits_a, destinations, 1)["spend_tx"]
    for man in revault_network.mans():
        spend_tx_a = man.man_keychain.sign_spend_psbt(spend_tx_a, indexes_a)
    man.rpc.updatespendtx(spend_tx_a)

    feerate = 10_000
    fees = revault_network.compute_spendtx_fees(feerate, len(deposits_b), 1,
                                                True)
    destinations = {bitcoind.rpc.getnewaddress(): (sum(amounts_b) - fees) // 2}
    spend_tx_b = man.rpc.getspendtx(deposits_b, destinations, 1)["spend_tx"]
    for man in revault_network.mans():
        spend_tx_b = man.man_keychain.sign_spend_psbt(spend_tx_b, indexes_b)
    man.rpc.updatespendtx(spend_tx_b)

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

    # We can ask the Cosigning Servers their signature again for the very same Spend
    man.rpc.setspendtx(spend_txid_a)

    # The two Spend have conflicting inputs, therefore the Cosigning Server won't
    # accept to sign the second one.
    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx_b)
    spend_psbt.tx.calc_sha256()
    with pytest.raises(
            RpcError,
            match=
            "one Cosigning Server already signed a Spend transaction spending one of these vaults",
    ):
        man.rpc.setspendtx(spend_psbt.tx.hash)

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

    # 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_a}'", )
    wait_for(
        lambda: len(man.rpc.listvaults(["spending"], deposits_a)["vaults"]
                    ) == len(deposits_a))

    # And will mark it as spent after a single confirmation of the Spend tx
    bitcoind.generate_block(1, wait_for_mempool=[spend_txid_a])
    wait_for(lambda: len(man.rpc.listvaults(["spent"], deposits)["vaults"]) ==
             len(deposits_a))
    for vault in man.rpc.listvaults(["spent"], deposits)["vaults"]:
        assert vault["moved_at"] is not None
Ejemplo n.º 11
0
def test_getspendtx(revault_network, bitcoind):
    revault_network.deploy(2, 1)
    man = revault_network.man(0)
    amount = 32.67890
    vault = revault_network.fund(amount)
    deposit = f"{vault['txid']}:{vault['vout']}"

    addr = bitcoind.rpc.getnewaddress()
    spent_vaults = [deposit]
    feerate = 2
    fees = revault_network.compute_spendtx_fees(feerate, len(spent_vaults), 1)
    destination = {addr: vault["amount"] - fees}

    revault_network.secure_vault(vault)

    # If the vault isn't active, it'll fail
    with pytest.raises(RpcError, match="Invalid vault status"):
        man.rpc.getspendtx(spent_vaults, destination, feerate)

    revault_network.activate_vault(vault)

    # If we are not a manager, it'll fail
    with pytest.raises(RpcError, match="This is a manager command"):
        revault_network.stk(0).rpc.getspendtx(spent_vaults, destination, feerate)

    # The amount was not enough to afford a change output, everything went to
    # fees.
    psbt = serializations.PSBT()
    psbt.deserialize(man.rpc.getspendtx(spent_vaults, destination, feerate)["spend_tx"])
    assert len(psbt.inputs) == 1 and len(psbt.outputs) == 2

    # But if we decrease it enough, it'll create a change output
    destinations = {addr: vault["amount"] - fees - 1_000_000}
    psbt = serializations.PSBT()
    psbt.deserialize(
        man.rpc.getspendtx(spent_vaults, destinations, feerate)["spend_tx"]
    )
    assert len(psbt.inputs) == 1 and len(psbt.outputs) == 3

    # Asking for an impossible feerate will error
    with pytest.raises(
        RpcError,
        match="Required feerate .* is significantly higher than actual feerate",
    ):
        man.rpc.getspendtx(spent_vaults, destinations, 100_000)

    # We'll stubbornly refuse they shoot themselves in the foot
    with pytest.raises(
        RpcError,
        match="Fees larger than 20000000 sats",
    ):
        destinations = {addr: vault["amount"] // 10}
        man.rpc.getspendtx(spent_vaults, destinations, 100_000)

    # We can spend many vaults
    deposits = [deposit]
    amounts = [vault["amount"]]
    for _ in range(10):
        amount = round(random.random() * 10**8 % 50, 7)
        vault = revault_network.fund(amount)
        revault_network.secure_vault(vault)
        revault_network.activate_vault(vault)

        deposit = f"{vault['txid']}:{vault['vout']}"
        amount_sat = vault["amount"]
        deposits.append(deposit)
        amounts.append(amount_sat)

        # Note that it passes even with 100k/vb if you disable insane fees
        # sanity checks :)
        feerate = random.randint(1, 10_000)
        sent_amount = sum(amounts) - revault_network.compute_spendtx_fees(
            feerate, len(deposits), 1
        )
        destinations = {addr: sent_amount}
        psbt = serializations.PSBT()
        psbt.deserialize(
            man.rpc.getspendtx(deposits, destinations, feerate)["spend_tx"]
        )
        assert (
            len(psbt.inputs) == len(deposits) and len(psbt.outputs) == 2
        ), "unexpected change output"

    # And we can spend to many destinations
    deposits = [deposit]
    destinations = {}
    for _ in range(10):
        feerate = random.randint(1, 1_000)
        destinations[bitcoind.rpc.getnewaddress()] = vault["amount"] // 20
        psbt = serializations.PSBT()
        psbt.deserialize(
            man.rpc.getspendtx(deposits, destinations, feerate)["spend_tx"]
        )
        assert (
            len(psbt.inputs) == len(deposits)
            # destinations + CPFP + change
            and len(psbt.outputs) == len(destinations.keys()) + 1 + 1
        ), "expected a change output"

    # And we can do both
    deposits = []
    destinations = {}
    for vault in man.rpc.listvaults(["active"])["vaults"]:
        deposits.append(f"{vault['txid']}:{vault['vout']}")
        destinations[bitcoind.rpc.getnewaddress()] = vault["amount"] // 2
    psbt = serializations.PSBT()
    psbt.deserialize(man.rpc.getspendtx(deposits, destinations, feerate)["spend_tx"])
    assert (
        len(psbt.inputs) == len(deposits)
        # destinations + CPFP + change
        and len(psbt.outputs) == len(destinations.keys()) + 1 + 1
    ), "expected a change output"

    # We can't create an insanely large transaction
    destinations = {bitcoind.rpc.getnewaddress(): 200_001 for _ in range(10_000)}
    with pytest.raises(
        RpcError,
        match="Transaction too large: satisfied it could be >400k weight units",
    ):
        man.rpc.getspendtx(deposits, destinations, feerate)
Ejemplo n.º 12
0
def test_revault_command(revault_network, bitcoind, executor):
    """
    Here we manually broadcast the unvualt_tx, followed by the cancel_tx
    """
    revault_network.deploy(3, 1)
    man = revault_network.man(0)
    stks = revault_network.stks()
    vault = revault_network.fund(18)
    deposit = f"{vault['txid']}:{vault['vout']}"

    # Can't cancel an unconfirmed deposit
    with pytest.raises(
        RpcError, match="Invalid vault status: 'funded'. Need 'unvaulting'"
    ):
        stks[0].rpc.revault(deposit)

    # A manager gets the same error: both parties can revault
    with pytest.raises(
        RpcError, match="Invalid vault status: 'funded'. Need 'unvaulting'"
    ):
        man.rpc.revault(deposit)
    revault_network.secure_vault(vault)

    # Secured is not good enough though
    with pytest.raises(
        RpcError, match="Invalid vault status: 'secured'. Need 'unvaulting'"
    ):
        stks[0].rpc.revault(deposit)
    revault_network.activate_vault(vault)

    # Active? Not enough!
    with pytest.raises(
        RpcError, match="Invalid vault status: 'active'. Need 'unvaulting'"
    ):
        stks[0].rpc.revault(deposit)

    # Now we want to broadcast the unvault tx without having an associated spend tx
    # First of all, we need the unvault psbt finalized
    unvault_psbt = stks[0].rpc.listpresignedtransactions([deposit])[
        "presigned_transactions"
    ][0]["unvault"]["psbt"]
    unvault_tx = bitcoind.rpc.finalizepsbt(unvault_psbt)["hex"]
    bitcoind.rpc.sendrawtransaction(unvault_tx)

    # Unvaulting! And there's no associated spend tx! Is revault broken?
    for w in stks + [man]:
        wait_for(
            lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
            == "unvaulting"
        )

    # Nah it's not, just broadcast the cancel
    man.rpc.revault(deposit)

    # Not confirmed yet...
    for w in stks + [man]:
        w.wait_for_log("Unvault transaction at .* is now being canceled")
        wait_for(
            lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"]
            == "canceling"
        )
    bitcoind.generate_block(1, wait_for_mempool=1)

    # Funds are safe, we happy
    for w in stks + [man]:
        wait_for(lambda: w.rpc.call("getinfo")["blockheight"] == 108)
        w.wait_for_log("Cancel tx .* was confirmed at height '108'")
        wait_for(
            lambda: w.rpc.listvaults([], [deposit])["vaults"][0]["status"] == "canceled"
        )
        w.rpc.listvaults([], [deposit])["vaults"][0]["moved_at"] is not None

    # Now, do the same process with two new vaults (thus with different derivation indexes)
    # at the same time, and with the Unvault not being mined yet
    vault_a = revault_network.fund(12)
    vault_b = revault_network.fund(0.4)

    revault_network.secure_vault(vault_a)
    revault_network.secure_vault(vault_b)

    revault_network.activate_vault(vault_a)
    revault_network.activate_vault(vault_b)

    for v in [vault_a, vault_b]:
        deposit = f"{v['txid']}:{v['vout']}"
        unvault_tx = man.rpc.listpresignedtransactions([deposit])[
            "presigned_transactions"
        ][0]["unvault"]["hex"]
        bitcoind.rpc.sendrawtransaction(unvault_tx)

        # On purpose, only wait for the one we want to revault with, to trigger some race conditions
        wait_for(
            lambda: len(stks[0].rpc.listvaults(["unvaulting"], [deposit])["vaults"])
            == 1
        )
        stks[0].rpc.revault(deposit)

    for w in stks + [man]:
        wait_for(lambda: len(stks[0].rpc.listvaults(["canceling"])["vaults"]) == 2)
    bitcoind.generate_block(1, wait_for_mempool=4)
    for w in stks + [man]:
        # 3 cause the first part of the test already had one canceled.
        wait_for(lambda: len(stks[0].rpc.listvaults(["canceled"])["vaults"]) == 3)

    # We have as many new deposits as canceled vaults
    bitcoind.generate_block(6)
    wait_for(
        lambda: len(stks[0].rpc.listvaults(["canceled"])["vaults"])
        == len(stks[0].rpc.listvaults(["funded"])["vaults"])
    )

    # And the deposit txid is the Cancel txid
    for v in stks[0].rpc.listvaults(["canceled"])["vaults"]:
        deposit = f"{v['txid']}:{v['vout']}"
        cancel_psbt = serializations.PSBT()
        cancel_b64 = stks[0].rpc.listpresignedtransactions([deposit])[
            "presigned_transactions"
        ][0]["cancel"]["psbt"]
        cancel_psbt.deserialize(cancel_b64)

        cancel_psbt.tx.calc_sha256()
        cancel_txid = cancel_psbt.tx.hash
        new_deposits = stks[0].rpc.listvaults(["funded"])["vaults"]
        assert cancel_txid in [v["txid"] for v in new_deposits]
Ejemplo n.º 13
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.º 14
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.º 15
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.º 16
0
def test_spends_concurrent(revault_network, bitcoind):
    """
    Here we test the creation and succesful broadcast of both Spend transaction
    concurrently handled but non conflicting.
    """
    CSV = 1024
    revault_network.deploy(3, 2, csv=CSV)
    man = revault_network.man(1)
    # FIXME: there is something up with higher number and the test framework fee
    # computation
    amounts = [0.22, 16, 3, 21]
    vaults = revault_network.fundmany(amounts)
    # Edge case: bitcoind can actually mess up with the amounts
    amounts = []
    deposits = []
    deriv_indexes = []
    for v in vaults:
        revault_network.secure_vault(v)
        revault_network.activate_vault(v)
        deposits.append(f"{v['txid']}:{v['vout']}")
        deriv_indexes.append(v["derivation_index"])
        amounts.append(v["amount"])

    (deposits_a, deposits_b) = (deposits[:2], deposits[2:])
    (amounts_a, amounts_b) = (amounts[:2], amounts[2:])
    (indexes_a, indexes_b) = (deriv_indexes[:2], deriv_indexes[2:])

    # Spending to a P2WSH (effectively a change but hey), with a change output
    destinations = {
        man.rpc.getdepositaddress()["address"]: sum(amounts_a) // 2
    }
    spend_tx_a = man.rpc.getspendtx(deposits_a, destinations, 1)["spend_tx"]
    for man in revault_network.mans():
        spend_tx_a = man.man_keychain.sign_spend_psbt(spend_tx_a, indexes_a)
    man.rpc.updatespendtx(spend_tx_a)

    # Spending to a P2WPKH, with a change output
    destinations = {bitcoind.rpc.getnewaddress(): sum(amounts_b) // 2}
    spend_tx_b = man.rpc.getspendtx(deposits_b, destinations, 1)["spend_tx"]
    for man in revault_network.mans():
        spend_tx_b = man.man_keychain.sign_spend_psbt(spend_tx_b, indexes_b)
    man.rpc.updatespendtx(spend_tx_b)

    # Of course, we can just stop and still broadcast the Spend
    man.stop()
    man.proc.wait(10)
    man.start()

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

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

    for w in revault_network.participants():
        wait_for(
            lambda: len(w.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))
    for w in revault_network.participants():
        wait_for(
            lambda: len(w.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_logs([
        f"Succesfully broadcasted Spend tx '{spend_txid_a}'",
        f"Succesfully broadcasted Spend tx '{spend_txid_b}'",
    ])
    for w in revault_network.participants():
        wait_for(
            lambda: len(w.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_txid_a, spend_txid_b])
    for w in revault_network.participants():
        wait_for(lambda: len(w.rpc.listvaults(["spent"], deposits)["vaults"])
                 == len(deposits))
Ejemplo n.º 17
0
def test_spendtx_management(revault_network, bitcoind):
    CSV = 12
    revault_network.deploy(2, 1, n_stkmanagers=1, csv=CSV)
    man = revault_network.man(0)
    amount = 0.24
    vault = revault_network.fund(amount)
    deposit = f"{vault['txid']}:{vault['vout']}"

    addr = bitcoind.rpc.getnewaddress()
    spent_vaults = [deposit]
    feerate = 2
    fees = revault_network.compute_spendtx_fees(feerate, len(spent_vaults), 1)
    destination = {addr: vault["amount"] - fees}

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

    spend_tx = man.rpc.getspendtx(spent_vaults, destination,
                                  feerate)["spend_tx"]

    # If we are not a manager, it'll fail
    with pytest.raises(RpcError, match="This is a manager command"):
        revault_network.stk_wallets[0].rpc.updatespendtx(spend_tx)

    # But it won't if we are a stakeholder-manager
    revault_network.stkman_wallets[0].rpc.updatespendtx(spend_tx)

    # It will not accept a spend_tx which spends an unknown Unvault
    psbt = serializations.PSBT()
    psbt.deserialize(spend_tx)
    psbt.tx.vin[0].prevout.hash = 0
    insane_spend_tx = psbt.serialize()
    with pytest.raises(RpcError,
                       match="Spend transaction refers an unknown Unvault"):
        man.rpc.updatespendtx(insane_spend_tx)

    # First time, it'll be stored
    man.rpc.updatespendtx(spend_tx)
    man.wait_for_log("Storing new Spend transaction")
    # We can actually update it no matter if it's the same
    man.rpc.updatespendtx(spend_tx)
    man.wait_for_log("Updating Spend transaction")

    assert len(man.rpc.listspendtxs()["spend_txs"]) == 1

    # If we delete it..
    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx)
    spend_psbt.tx.calc_sha256()
    man.rpc.delspendtx(spend_psbt.tx.hash)
    assert len(man.rpc.listspendtxs()["spend_txs"]) == 0
    # When we update it it'll be treated as a new transaction
    man.rpc.updatespendtx(spend_tx)
    man.wait_for_log("Storing new Spend transaction")
    assert len(man.rpc.listspendtxs()["spend_txs"]) == 1

    # Create another Spend transaction spending two vaults
    vault_b = revault_network.fund(amount)
    deposit_b = f"{vault_b['txid']}:{vault_b['vout']}"
    addr_b = bitcoind.rpc.getnewaddress()
    spent_vaults = [deposit, deposit_b]
    feerate = 50
    fees = revault_network.compute_spendtx_fees(feerate, len(spent_vaults), 2)
    destination = {
        addr: (vault_b["amount"] - fees) // 2,
        addr_b: (vault_b["amount"] - fees) // 2,
    }
    revault_network.secure_vault(vault_b)
    revault_network.activate_vault(vault_b)
    spend_tx_b = man.rpc.getspendtx(spent_vaults, destination,
                                    feerate)["spend_tx"]
    man.rpc.updatespendtx(spend_tx_b)
    man.wait_for_log("Storing new Spend transaction")
    assert len(man.rpc.listspendtxs()["spend_txs"]) == 2
    assert {
        "deposit_outpoints": [deposit],
        "psbt": spend_tx,
        "change_index": None,
        "cpfp_index": 0,
    } in man.rpc.listspendtxs()["spend_txs"]
    assert {
        "deposit_outpoints": [deposit, deposit_b],
        "psbt": spend_tx_b,
        "change_index": 3,
        "cpfp_index": 0,
    } in man.rpc.listspendtxs()["spend_txs"]

    # Now we could try to broadcast it..
    # But we couldn't broadcast a random txid
    with pytest.raises(RpcError, match="Unknown Spend transaction"):
        man.rpc.setspendtx(
            "d5eb741a31ebf4d2f5d6ae223900f1bd996e209150d3604fca7d9fa5d6136337")

    # ..And even with an existing one we would have to sign it beforehand!
    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx_b)
    spend_psbt.tx.calc_sha256()
    with pytest.raises(
            RpcError,
            match=
            f"Not enough signatures, needed: {len(revault_network.mans())}, current: 0",
    ):
        man.rpc.setspendtx(spend_psbt.tx.hash)

    # Now, sign the Spend we are going to broadcast
    deriv_indexes = [vault["derivation_index"], vault_b["derivation_index"]]
    for man in revault_network.mans():
        spend_tx_b = man.man_keychain.sign_spend_psbt(spend_tx_b,
                                                      deriv_indexes)

    # Just before broadcasting it, prepare a competing one to later try to make Cosigning Servers
    # sign twice
    vault_c = revault_network.fund(amount / 2)
    deposit_c = f"{vault_c['txid']}:{vault_c['vout']}"
    rogue_spent_vaults = [deposit, deposit_b, deposit_c]
    feerate = 50
    fees = revault_network.compute_spendtx_fees(feerate,
                                                len(rogue_spent_vaults), 2)
    destination = {
        addr: (vault_b["amount"] - fees) // 2,
        addr_b: (vault_b["amount"] - fees) // 2,
    }
    revault_network.secure_vault(vault_c)
    revault_network.activate_vault(vault_c)
    rogue_spend_tx = man.rpc.getspendtx(rogue_spent_vaults, destination,
                                        feerate)["spend_tx"]
    deriv_indexes = deriv_indexes + [vault_c["derivation_index"]]
    for man in revault_network.mans():
        rogue_spend_tx = man.man_keychain.sign_spend_psbt(
            rogue_spend_tx, deriv_indexes)
    man.rpc.updatespendtx(rogue_spend_tx)

    # Then broadcast the actual Spend
    spend_psbt = serializations.PSBT()
    spend_psbt.deserialize(spend_tx_b)
    spend_psbt.tx.calc_sha256()
    spend_tx_b = spend_psbt.serialize()
    man.rpc.updatespendtx(spend_tx_b)
    man.rpc.setspendtx(spend_psbt.tx.hash)

    # If we show good faith (ask again for the same set of outpoints), Cosigning Servers will
    # try to be helpful.
    man.rpc.setspendtx(spend_psbt.tx.hash)

    # However, they won't let us trying to sneak in another outpoint
    rogue_spend_psbt = serializations.PSBT()
    rogue_spend_psbt.deserialize(rogue_spend_tx)
    rogue_spend_psbt.tx.calc_sha256()
    with pytest.raises(
            RpcError,
            match=
            "one Cosigning Server already signed a Spend transaction spending one of these vaults",
    ):
        man.rpc.setspendtx(rogue_spend_psbt.tx.hash)

    # It gets marked as in the process of being unvaulted immediately (next bitcoind
    # poll), and will get marked as succesfully unvaulted after a single confirmation.
    wait_for(lambda: len(
        man.rpc.listvaults(["unvaulting"], spent_vaults)["vaults"]) == len(
            spent_vaults))
    bitcoind.generate_block(1, wait_for_mempool=len(spent_vaults))
    wait_for(
        lambda: len(man.rpc.listvaults(["unvaulted"], spent_vaults)["vaults"]
                    ) == len(spent_vaults))

    # 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_psbt.tx.hash}'")
    wait_for(
        lambda: len(man.rpc.listvaults(["spending"], spent_vaults)["vaults"]
                    ) == len(spent_vaults))

    # And the vault we tried to sneak in wasn't even unvaulted
    assert len(man.rpc.listvaults(["active"], [deposit_c])["vaults"]) == 1
Ejemplo n.º 18
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.º 19
0
def test_listspendtxs(revault_network, bitcoind):
    rn = revault_network
    rn.deploy(n_stakeholders=2, n_managers=2, n_stkmanagers=0, csv=5)
    man = rn.man(0)

    vaults = rn.fundmany([1, 2, 3, 1])
    for v in vaults:
        rn.secure_vault(v)
        rn.activate_vault(v)

    # _any_spend_data never creates change
    destinations, feerate = rn._any_spend_data(vaults)
    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 rn.mans():
        spend_tx = man.man_keychain.sign_spend_psbt(spend_tx, deriv_indexes)
        man.rpc.updatespendtx(spend_tx)
        spend_txs = man.rpc.listspendtxs(["non_final"])["spend_txs"]
        assert len(spend_txs) == 1
        assert spend_txs[0]["change_index"] is None
        assert spend_txs[0]["cpfp_index"] is not None

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

    # The initiator will see the spend as pending
    for w in rn.participants():
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulting"], deposits)["vaults"])
            == len(deposits)
        )
    spend_txs = man.rpc.listspendtxs(["pending"])["spend_txs"]
    assert len(spend_txs) == 1
    assert spend_txs[0]["change_index"] is None
    assert spend_txs[0]["cpfp_index"] is not None

    rn.bitcoind.generate_block(rn.csv - 1, wait_for_mempool=len(deposits))

    # Still pending...
    for w in rn.participants():
        wait_for(
            lambda: len(w.rpc.listvaults(["unvaulted"], deposits)["vaults"])
            == len(deposits)
        )
    spend_txs = man.rpc.listspendtxs(["pending"])["spend_txs"]
    assert len(spend_txs) == 1
    assert spend_txs[0]["change_index"] is None
    assert spend_txs[0]["cpfp_index"] is not None

    rn.bitcoind.generate_block(1)

    # Broadcasted!
    for w in rn.participants():
        wait_for(
            lambda: len(w.rpc.listvaults(["spending"], deposits)["vaults"])
            == len(deposits)
        )
    spend_txs = man.rpc.listspendtxs(["broadcasted"])["spend_txs"]
    assert len(spend_txs) == 1
    assert spend_txs[0]["change_index"] is None
    assert spend_txs[0]["cpfp_index"] is not None

    rn.bitcoind.generate_block(1, wait_for_mempool=[spend_psbt.tx.hash])

    # Transaction is spent, the status is "broadcasted"
    spend_txs = man.rpc.listspendtxs(["broadcasted"])["spend_txs"]
    assert len(spend_txs) == 1
    assert spend_txs[0]["change_index"] is None
    assert spend_txs[0]["cpfp_index"] is not None
    for w in rn.participants():
        wait_for(
            lambda: len(w.rpc.listvaults(["spent"], deposits)["vaults"])
            == len(deposits)
        )

    vaults = rn.fundmany([3, 4, 5])
    for v in vaults:
        rn.secure_vault(v)
        rn.activate_vault(v)
    rn.unvault_vaults_anyhow(vaults)
    rn.cancel_vault(vaults[0])
    # Transaction is canceled, the status is still "pending" as we never
    # broadcasted it
    # (Keep in mind that in the utilities under tests/revault_network.py
    # we usually use the last manager for broadcasting the transactions)
    assert len(rn.man(1).rpc.listspendtxs(["pending"])["spend_txs"]) == 1

    v = rn.fund(6)
    rn.secure_vault(v)
    rn.activate_vault(v)
    rn.spend_vaults_anyhow_unconfirmed([v])
    assert len(rn.man(1).rpc.listspendtxs(["broadcasted"])["spend_txs"]) == 2
    rn.cancel_vault(v)
    # Status of the spend is still broadcasted, even if the transaction is canceled
    assert len(rn.man(1).rpc.listspendtxs(["broadcasted"])["spend_txs"]) == 2