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)
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