예제 #1
0
    def sync_burner_outputs(self, burner_txes):
        mixdepth = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
        address_type = FidelityBondMixin.BIP32_BURN_ID
        self.wallet.set_next_index(mixdepth,
                                   address_type,
                                   self.wallet.gap_limit,
                                   force=True)
        highest_used_index = 0
        known_burner_outputs = self.wallet.get_burner_outputs()

        index = -1
        while index - highest_used_index < self.wallet.gap_limit:
            index += 1
            self.wallet.set_next_index(mixdepth,
                                       address_type,
                                       index,
                                       force=True)
            path = self.wallet.get_path(mixdepth, address_type, index)
            path_privkey, engine = self.wallet._get_key_from_path(path)
            path_pubkey = engine.privkey_to_pubkey(path_privkey)
            path_pubkeyhash = btc.bin_hash160(path_pubkey)
            for burner_tx in burner_txes:
                burner_pubkeyhash, gettx = burner_tx
                if burner_pubkeyhash != path_pubkeyhash:
                    continue
                highest_used_index = index
                path_repr = self.wallet.get_path_repr(path)
                if path_repr.encode() in known_burner_outputs:
                    continue
                txid = gettx["txid"]
                jlog.info("Found a burner transaction txid=" + txid +
                          " path = " + path_repr)
                try:
                    merkle_branch = self.bci.get_tx_merkle_branch(
                        txid, gettx["blockhash"])
                except ValueError as e:
                    jlog.warning(repr(e))
                    jlog.warning("Merkle branch likely not available, use " +
                                 "wallet-tool `addtxoutproof`")
                    merkle_branch = None
                block_height = self.bci.rpc("getblockheader",
                                            [gettx["blockhash"]])["height"]
                if merkle_branch:
                    assert self.bci.verify_tx_merkle_branch(
                        txid, block_height, merkle_branch)
                self.wallet.add_burner_output(path_repr, gettx["hex"],
                                              block_height, merkle_branch,
                                              gettx["blockindex"])

        self.wallet.set_next_index(mixdepth, address_type,
                                   highest_used_index + 1)
예제 #2
0
def direct_send(wallet_service, amount, mixdepth, destination, answeryes=False,
                accept_callback=None, info_callback=None):
    """Send coins directly from one mixdepth to one destination address;
    does not need IRC. Sweep as for normal sendpayment (set amount=0).
    If answeryes is True, callback/command line query is not performed.
    If accept_callback is None, command line input for acceptance is assumed,
    else this callback is called:
    accept_callback:
    ====
    args:
    deserialized tx, destination address, amount in satoshis, fee in satoshis
    returns:
    True if accepted, False if not
    ====
    The info_callback takes one parameter, the information message (when tx is
    pushed), and returns nothing.

    This function returns:
    The txid if transaction is pushed, False otherwise
    """
    #Sanity checks
    assert validate_address(destination)[0] or is_burn_destination(destination)
    assert isinstance(mixdepth, numbers.Integral)
    assert mixdepth >= 0
    assert isinstance(amount, numbers.Integral)
    assert amount >=0
    assert isinstance(wallet_service.wallet, BaseWallet)

    if is_burn_destination(destination):
        #Additional checks
        if not isinstance(wallet_service.wallet, FidelityBondMixin):
            log.error("Only fidelity bond wallets can burn coins")
            return
        if answeryes:
            log.error("Burning coins not allowed without asking for confirmation")
            return
        if mixdepth != FidelityBondMixin.FIDELITY_BOND_MIXDEPTH:
            log.error("Burning coins only allowed from mixdepth " + str(
                FidelityBondMixin.FIDELITY_BOND_MIXDEPTH))
            return
        if amount != 0:
            log.error("Only sweeping allowed when burning coins, to keep the tx " +
                "small. Tip: use the coin control feature to freeze utxos")
            return

    from pprint import pformat
    txtype = wallet_service.get_txtype()
    if amount == 0:
        utxos = wallet_service.get_utxos_by_mixdepth()[mixdepth]
        if utxos == {}:
            log.error(
                "There are no available utxos in mixdepth: " + str(mixdepth) + ", quitting.")
            return

        total_inputs_val = sum([va['value'] for u, va in iteritems(utxos)])

        if is_burn_destination(destination):
            if len(utxos) > 1:
                log.error("Only one input allowed when burning coins, to keep "
                    + "the tx small. Tip: use the coin control feature to freeze utxos")
                return
            address_type = FidelityBondMixin.BIP32_BURN_ID
            index = wallet_service.wallet.get_next_unused_index(mixdepth, address_type)
            path = wallet_service.wallet.get_path(mixdepth, address_type, index)
            privkey, engine = wallet_service.wallet._get_key_from_path(path)
            pubkey = engine.privkey_to_pubkey(privkey)
            pubkeyhash = bin_hash160(pubkey)

            #size of burn output is slightly different from regular outputs
            burn_script = mk_burn_script(pubkeyhash) #in hex
            fee_est = estimate_tx_fee(len(utxos), 0, txtype=txtype, extra_bytes=len(burn_script)/2)

            outs = [{"script": burn_script, "value": total_inputs_val - fee_est}]
            destination = "BURNER OUTPUT embedding pubkey at " \
                + wallet_service.wallet.get_path_repr(path) \
                + "\n\nWARNING: This transaction if broadcasted will PERMANENTLY DESTROY your bitcoins\n"
        else:
            #regular send (non-burn)
            fee_est = estimate_tx_fee(len(utxos), 1, txtype=txtype)
            outs = [{"address": destination, "value": total_inputs_val - fee_est}]
    else:
        #8 inputs to be conservative
        initial_fee_est = estimate_tx_fee(8,2, txtype=txtype)
        utxos = wallet_service.select_utxos(mixdepth, amount + initial_fee_est)
        if len(utxos) < 8:
            fee_est = estimate_tx_fee(len(utxos), 2, txtype=txtype)
        else:
            fee_est = initial_fee_est
        total_inputs_val = sum([va['value'] for u, va in iteritems(utxos)])
        changeval = total_inputs_val - fee_est - amount
        outs = [{"value": amount, "address": destination}]
        change_addr = wallet_service.get_internal_addr(mixdepth)
        outs.append({"value": changeval, "address": change_addr})

    #compute transaction locktime, has special case for spending timelocked coins
    tx_locktime = compute_tx_locktime()
    if mixdepth == FidelityBondMixin.FIDELITY_BOND_MIXDEPTH and \
            isinstance(wallet_service.wallet, FidelityBondMixin):
        for outpoint, utxo in utxos.items():
            path = wallet_service.script_to_path(
                wallet_service.addr_to_script(utxo["address"]))
            if not FidelityBondMixin.is_timelocked_path(path):
                continue
            path_locktime = path[-1]
            tx_locktime = max(tx_locktime, path_locktime+1)
            #compute_tx_locktime() gives a locktime in terms of block height
            #timelocked addresses use unix time instead
            #OP_CHECKLOCKTIMEVERIFY can only compare like with like, so we
            #must use unix time as the transaction locktime

    #Now ready to construct transaction
    log.info("Using a fee of : " + amount_to_str(fee_est) + ".")
    if amount != 0:
        log.info("Using a change value of: " + amount_to_str(changeval) + ".")
    txsigned = sign_tx(wallet_service, make_shuffled_tx(
        list(utxos.keys()), outs, False, 2, tx_locktime), utxos)
    log.info("Got signed transaction:\n")
    log.info(pformat(txsigned))
    tx = serialize(txsigned)
    log.info("In serialized form (for copy-paste):")
    log.info(tx)
    actual_amount = amount if amount != 0 else total_inputs_val - fee_est
    log.info("Sends: " + amount_to_str(actual_amount) + " to destination: " + destination)
    if not answeryes:
        if not accept_callback:
            if input('Would you like to push to the network? (y/n):')[0] != 'y':
                log.info("You chose not to broadcast the transaction, quitting.")
                return False
        else:
            accepted = accept_callback(pformat(txsigned), destination, actual_amount,
                                       fee_est)
            if not accepted:
                return False
    jm_single().bc_interface.pushtx(tx)
    txid = txhash(tx)
    successmsg = "Transaction sent: " + txid
    cb = log.info if not info_callback else info_callback
    cb(successmsg)
    return txid