def get_tx_info(tx_id: str) -> messages.TransactionType: """Fetch basic transaction info for the signing.""" tx_url = f"{URL}/{tx_id}" tx_src = requests.get(tx_url, headers={ "user-agent": "tx_cache" }).json(parse_float=Decimal) if "error" in tx_src: raise RuntimeError(tx_src["error"]) return btc.from_json(tx_src)
def _get_inputs_interactive(blockbook_url): inputs = [] txes = {} while True: echo() prev = prompt("Previous output to spend (txid:vout)", type=parse_vin, default="") if not prev: break prev_hash, prev_index = prev address_n = prompt("BIP-32 path to derive the key", type=tools.parse_path) txhash = prev_hash.hex() tx_url = blockbook_url + txhash r = requests.get(tx_url) if not r.ok: raise click.ClickException(f"Failed to fetch URL: {tx_url}") tx_json = r.json(parse_float=decimal.Decimal) if "error" in tx_json: raise click.ClickException(f"Transaction not found: {txhash}") tx = btc.from_json(tx_json) txes[txhash] = tx amount = tx.bin_outputs[prev_index].amount echo("Input amount: {}".format(amount)) sequence = prompt( "Sequence Number to use (RBF opt-in enabled by default)", type=int, default=0xFFFFFFFD, ) script_type = prompt( "Input type", type=ChoiceType(INPUT_SCRIPTS), default=_default_script_type(address_n, INPUT_SCRIPTS), ) if isinstance(script_type, str): script_type = INPUT_SCRIPTS[script_type] new_input = messages.TxInputType( address_n=address_n, prev_hash=prev_hash, prev_index=prev_index, amount=amount, script_type=script_type, sequence=sequence, ) inputs.append(new_input) return inputs, txes
def test_coinbase_from_json(): tx_dict = json.loads(TX_JSON_COINBASE, parse_float=Decimal) tx = btc.from_json(tx_dict) assert tx.version == 2 assert tx.lock_time == 0 assert len(tx.inputs) == 1 assert len(tx.bin_outputs) == 4 assert sum(o.amount for o in tx.bin_outputs) == 1256294353 coinbase = tx.inputs[0] assert coinbase.prev_hash == b"\x00" * 32 assert coinbase.prev_index == 2**32 - 1 assert coinbase.script_sig.hex() == tx_dict["vin"][0]["coinbase"]
def cli(tx, coin_name): """Add a transaction to the cache. \b Without URL, default blockbook server will be used: ./tests/tx_cache.py bcash bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78 It is also possible to specify URL explicitly: ./tests/tx_cache.py bcash https://bch1.trezor.io/api/tx-specific/bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78 The transaction will be parsed into Trezor format and saved in tests/txcache/<COIN_NAME>/<TXHASH>.json. Note that only Bitcoin-compatible fields will be filled out. If you are adding a coin with special fields (Dash, Zcash...), it is your responsibility to fill out the missing fields properly. """ if tx.startswith("http"): tx_url = tx tx_hash = tx.split("/")[-1].lower() elif coin_name not in BLOCKBOOKS: raise click.ClickException( f"Could not find blockbook for {coin_name}. Please specify a full URL." ) else: tx_hash = tx.lower() tx_url = BLOCKBOOKS[coin_name].format(tx) click.echo(f"Fetching from {tx_url}...") try: # Get transaction from Blockbook server. The servers refuse requests with an empty user agent. tx_src = requests.get(tx_url, headers={ "user-agent": "tx_cache" }).json(parse_float=Decimal) tx_proto = btc.from_json(tx_src) tx_dict = protobuf.to_dict(tx_proto) tx_json = json.dumps(tx_dict, sort_keys=True, indent=2) + "\n" except Exception as e: raise click.ClickException(str(e)) from e cache_dir = CACHE_PATH / coin_name if not cache_dir.exists(): cache_dir.mkdir() (cache_dir / f"{tx_hash}.json").write_text(tx_json) click.echo(tx_json)
def test_from_json(): tx_dict = json.loads(TX_JSON_BIG, parse_float=Decimal) tx = btc.from_json(tx_dict) assert tx.version == 2 assert tx.lock_time == 620109 assert len(tx.inputs) == 5 assert len(tx.bin_outputs) == 2 assert sum(o.amount for o in tx.bin_outputs) == 2305776353 for v, i in zip(tx_dict["vin"], tx.inputs): assert i.prev_hash.hex() == v["txid"] assert i.prev_index == v["vout"] assert i.script_sig.hex() == v["scriptSig"]["hex"] assert i.sequence == v["sequence"] for v, o in zip(tx_dict["vout"], tx.bin_outputs): assert o.amount == int(Decimal(v["value"]) * (10**8)) assert o.script_pubkey.hex() == v["scriptPubKey"]["hex"]
def json_to_tx(tx_json): t = btc.from_json(tx_json) dip2_type = tx_json.get("type", 0) if t.version == 3 and dip2_type != 0: # It's a DIP2 special TX with payload if "extraPayloadSize" not in tx_json or "extraPayload" not in tx_json: raise ValueError("Payload data missing in DIP2 transaction") if tx_json["extraPayloadSize"] * 2 != len(tx_json["extraPayload"]): raise ValueError( "extra_data_len (%d) does not match calculated length (%d)" % (tx_json["extraPayloadSize"], len(tx_json["extraPayload"]) * 2)) t.extra_data = dash_utils.num_to_varint( tx_json["extraPayloadSize"]) + bytes.fromhex( tx_json["extraPayload"]) # Trezor firmware doesn't understand the split of version and type, so let's mimic the # old serialization format t.version |= dip2_type << 16 return t
def _get_inputs_interactive( blockbook_url: str, ) -> Tuple[List[messages.TxInputType], Dict[str, messages.TransactionType]]: inputs: List[messages.TxInputType] = [] txes: Dict[str, messages.TransactionType] = {} while True: echo() prev = prompt("Previous output to spend (txid:vout)", type=parse_vin, default="") if not prev: break prev_hash, prev_index = prev txhash = prev_hash.hex() tx_url = blockbook_url + txhash r = SESSION.get(tx_url) if not r.ok: raise click.ClickException(f"Failed to fetch URL: {tx_url}") tx_json = r.json(parse_float=decimal.Decimal) if "error" in tx_json: raise click.ClickException(f"Transaction not found: {txhash}") tx = btc.from_json(tx_json) txes[txhash] = tx try: from_address = tx_json["vout"][prev_index]["scriptPubKey"][ "address"] echo(f"From address: {from_address}") except Exception: pass amount = tx.bin_outputs[prev_index].amount echo(f"Input amount: {amount}") address_n = prompt("BIP-32 path to derive the key", type=tools.parse_path) reported_type = tx_json["vout"][prev_index]["scriptPubKey"].get("type") if reported_type in BITCOIN_CORE_INPUT_TYPES: script_type = BITCOIN_CORE_INPUT_TYPES[reported_type] click.echo(f"Script type: {script_type.name}") else: script_type = prompt( "Input type", type=ChoiceType(INPUT_SCRIPTS), default=_default_script_type(address_n, INPUT_SCRIPTS), ) if isinstance(script_type, str): script_type = INPUT_SCRIPTS[script_type] sequence = prompt( "Sequence Number to use (RBF opt-in enabled by default)", type=int, default=0xFFFFFFFD, ) new_input = messages.TxInputType( address_n=address_n, prev_hash=prev_hash, prev_index=prev_index, amount=amount, script_type=script_type, sequence=sequence, ) inputs.append(new_input) return inputs, txes
ins = [ proto.TxInputType(address_n=tools.parse_path(in1_addr_path), prev_hash=in1_prev_hash_b, prev_index=in1_prev_index, amount=in1_amount, script_type=proto.InputScriptType.SPENDWITNESS, sequence=sequence) ] outs = [ proto.TxOutputType(address=out1_address, amount=out1_amount, script_type=proto.OutputScriptType.PAYTOADDRESS) ] txes = None for i in ins: if i.script_type == proto.InputScriptType.SPENDADDRESS: tx = from_json(in1_prev_txn_j) txes = {in1_prev_hash_b: tx} break _, serialized_tx = btc.sign_tx(client, coin, ins, outs, details=signtx, prev_txes=txes) client.close() print(f'{{"hex": "{serialized_tx.hex()}"}}')
def json_to_tx(coin_data, tx_data): # TODO return from_json(tx_data)