From e01a8de1a57f7d70c101bb3eccae22add7e2dd03 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Mon, 24 Aug 2015 18:05:14 -0400 Subject: [PATCH] Add NAME_IMPORT operation --- blockstore/lib/operations/nameimport.py | 169 ++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 blockstore/lib/operations/nameimport.py diff --git a/blockstore/lib/operations/nameimport.py b/blockstore/lib/operations/nameimport.py new file mode 100644 index 000000000..4f900c921 --- /dev/null +++ b/blockstore/lib/operations/nameimport.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Blockstore + ~~~~~ + copyright: (c) 2014 by Halfmoon Labs, Inc. + copyright: (c) 2015 by Blockstack.org + + This file is part of Blockstore + + Blockstore is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Blockstore is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Blockstore. If not, see . +""" + +from pybitcoin import embed_data_in_blockchain, \ + analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \ + make_pay_to_address_script, b58check_encode, b58check_decode + +from pybitcoin.transactions.outputs import calculate_change_amount +from utilitybelt import is_hex +from binascii import hexlify, unhexlify + +from ..b40 import b40_to_hex, bin_to_b40, is_b40 +from ..config import * +from ..scripts import blockstore_script_to_hex, add_magic_bytes +from ..hashing import hash256_trunc128 + +def calculate_basic_name_tx_fee(): + return DEFAULT_OP_RETURN_FEE + + +def get_import_update_hash_from_outputs( outputs, recipient ): + """ + Given the outputs from a name import operation, and the + recipient's script_pubkey string, find the update hash output. + + By construction, it will be the address of the second non-OP_RETURN + output (i.e. the third output). By process of + elimination, it will be the only non-OP_RETURN output + that is not the recipient. + """ + + ret = None + count = 0 + for output in outputs: + + output_script = output['scriptPubKey'] + output_asm = output_script.get('asm') + output_hex = output_script.get('hex') + output_addresses = output_script.get('addresses') + + if output_asm[0:9] != 'OP_RETURN' and output_hex is not None and output_hex != recipient: + + ret = b58check_decode( str(output_addresses[0]) ) + break + + if ret is None: + raise Exception("No update hash found") + + return ret + + +def build(name, testset=False): + """ + Takes in a name to import. Name must include the namespace ID. + + Record format: + + 0 2 3 39 + |----|--|-----------------------------| + magic op name.ns_id (34 bytes) + + The transaction itself will have two outputs: + * the recipient + * the hash of the name's associated data + """ + + if not is_b40( name ) or "+" in name or name.count(".") > 1: + raise Exception("Name '%s' has non-base-38 characters" % name) + + name_hex = hexlify(name) + if len(name_hex) > LENGTHS['blockchain_id_name'] * 2: + # too long + raise Exception("Name '%s' too long (exceeds %d bytes)" % (fqn, LENGTHS['blockchain_id_name'])) + + readable_script = "NAME_IMPORT 0x%s" % (hexlify(name)) + hex_script = blockstore_script_to_hex(readable_script) + packaged_script = add_magic_bytes(hex_script, testset=testset) + + return packaged_script + + +def make_outputs( data, inputs, new_name_owner_address, change_address, update_hash_b58, format='bin', fee=None, op_return_amount=DEFAULT_OP_RETURN_VALUE, name_owner_amount=DEFAULT_DUST_SIZE): + """ + Builds the outputs for a name import: + * [0] is the OP_RETURN + * [1] is the new owner (recipient) + * [2] is the update hash + * [3] is the debit to the original owner + """ + if fee is None: + fee = calculate_basic_name_tx_fee() + + total_to_send = op_return_amount + name_owner_amount + DEFAULT_DUST_SIZE + + return [ + # main output + {"script_hex": make_op_return_script(data, format=format), + "value": op_return_amount}, + + # new name owner output + {"script_hex": make_pay_to_address_script(new_name_owner_address), + "value": name_owner_amount}, + + # update hash output + {"script_hex": make_pay_to_address_script(update_hash_b58), + "value": DEFAULT_DUST_SIZE}, + + # change output + {"script_hex": make_pay_to_address_script(change_address), + "value": calculate_change_amount(inputs, total_to_send, fee)} + ] + + +def broadcast(name, destination_address, update_hash, private_key, blockchain_client, fee=None, testset=False): + + nulldata = build(name, testset=testset) + + # get inputs and from address + private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client) + + # convert update_hash from a hex string so it looks like an address + update_hash_b58 = b58check_encode( unhexlify(update_hash) ) + + # build custom outputs here + outputs = make_outputs(nulldata, inputs, destination_address, from_address, update_hash_b58, fee=fee, format='hex') + + # serialize, sign, and broadcast the tx + response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_client) + + # response = {'success': True } + response.update({'data': nulldata}) + + # return the response + return response + + +def parse(bin_payload, recipient, update_hash ): + """ + # NOTE: first three bytes were stripped + """ + + fqn = bin_payload + + return { + 'opcode': 'NAME_IMPORT', + 'name': fqn, + 'recipient': hexlify(recipient), + 'update_hash': hexlify(update_hash) + }