mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-17 22:24:34 +08:00
remove dead code
This commit is contained in:
@@ -21,176 +21,6 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, make_op_return_tx, make_op_return_outputs, \
|
||||
make_op_return_script, broadcast_transaction, serialize_transaction, \
|
||||
script_hex_to_address, get_unspents
|
||||
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 *
|
||||
|
||||
# consensus hash fields (none for announcements)
|
||||
FIELDS = []
|
||||
|
||||
'''
|
||||
def build(message_hash, testset=False):
|
||||
"""
|
||||
|
||||
Record format:
|
||||
|
||||
0 2 3 23
|
||||
|----|--|-----------------------------|
|
||||
magic op message hash (160-bit)
|
||||
|
||||
"""
|
||||
|
||||
if len(message_hash) != 40:
|
||||
raise Exception("Invalid hash: not 20 bytes")
|
||||
|
||||
if not is_hex(message_hash):
|
||||
raise Exception("Invalid hash: not hex")
|
||||
|
||||
readable_script = "ANNOUNCE 0x%s" % (message_hash)
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, inputs, change_address, pay_fee=True ):
|
||||
"""
|
||||
Make outputs for an announcement.
|
||||
"""
|
||||
|
||||
dust_fee = None
|
||||
op_fee = None
|
||||
dust_value = None
|
||||
|
||||
if pay_fee:
|
||||
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = DEFAULT_DUST_FEE
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
|
||||
else:
|
||||
# will be subsidized
|
||||
dust_fee = 0
|
||||
op_fee = 0
|
||||
dust_value = 0
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format='hex'),
|
||||
"value": 0},
|
||||
|
||||
# change output
|
||||
{"script_hex": make_pay_to_address_script(change_address),
|
||||
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
|
||||
]
|
||||
|
||||
|
||||
def broadcast(message_hash, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, user_public_key=None, subsidize=False):
|
||||
|
||||
# sanity check
|
||||
tx_only = False
|
||||
pay_fee = True
|
||||
if user_public_key is not None:
|
||||
tx_only = True
|
||||
|
||||
if subsidize:
|
||||
pay_fee = False
|
||||
|
||||
if user_public_key is None and private_key is None:
|
||||
raise Exception("Missing both public and private key")
|
||||
|
||||
if not tx_only and private_key is None:
|
||||
raise Exception("Need private key for broadcasting")
|
||||
|
||||
if len(message_hash) != 40:
|
||||
raise Exception("Invalid message hash: not 20 bytes")
|
||||
|
||||
if not is_hex( message_hash ):
|
||||
raise Exception("Invalid message hash: not hex")
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
from_address = None
|
||||
inputs = None
|
||||
private_key_obj = None
|
||||
|
||||
if user_public_key is not None:
|
||||
pubk = BitcoinPublicKey( user_public_key )
|
||||
from_address = pubk.address()
|
||||
inputs = get_unspents( from_address, blockchain_client )
|
||||
|
||||
elif private_key is not None:
|
||||
pubk = BitcoinPrivateKey( private_key ).public_key()
|
||||
public_key = pubk.to_hex()
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
nulldata = build(message_hash, testset=testset)
|
||||
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {'unsigned_tx': unsigned_tx}
|
||||
|
||||
else:
|
||||
|
||||
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
|
||||
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
|
||||
response.update({'data': nulldata})
|
||||
return response
|
||||
|
||||
|
||||
def parse(bin_payload):
|
||||
"""
|
||||
Interpret a block's nulldata back into a SHA256. The first three bytes (2 magic + 1 opcode)
|
||||
will not be present in bin_payload.
|
||||
"""
|
||||
|
||||
message_hash = hexlify(bin_payload)
|
||||
if not is_hex( message_hash ):
|
||||
log.error("Not a message hash")
|
||||
return None
|
||||
|
||||
if len(message_hash) != 40:
|
||||
log.error("Not a 160-bit hash")
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'ANNOUNCE',
|
||||
'message_hash': message_hash
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Given a transaction's outputs, look up its fees:
|
||||
* there should be two outputs: the OP_RETURN and change address
|
||||
|
||||
Return (dust fees, operation fees) on success
|
||||
Return (None, None) on invalid output listing
|
||||
"""
|
||||
if len(outputs) != 2:
|
||||
return (None, None)
|
||||
|
||||
# 0: op_return
|
||||
if not tx_output_is_op_return( outputs[0] ):
|
||||
return (None, None)
|
||||
|
||||
if outputs[0]["value"] != 0:
|
||||
return (None, None)
|
||||
|
||||
# 1: change address
|
||||
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
|
||||
return (None, None)
|
||||
|
||||
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = 0
|
||||
|
||||
return (dust_fee, op_fee)
|
||||
'''
|
||||
|
||||
@@ -21,18 +21,11 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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, serialize_transaction
|
||||
|
||||
from pybitcoin.transactions.outputs import calculate_change_amount
|
||||
from utilitybelt import is_hex
|
||||
from pybitcoin import b58check_decode
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from ..b40 import b40_to_hex, bin_to_b40, is_b40
|
||||
from ..config import *
|
||||
from ..scripts import *
|
||||
from ..hashing import hash256_trunc128
|
||||
|
||||
from ..nameset import NAMEREC_FIELDS
|
||||
|
||||
@@ -75,120 +68,3 @@ def get_import_update_hash_from_outputs( outputs, recipient ):
|
||||
|
||||
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 (37 bytes)
|
||||
|
||||
The transaction itself will have two outputs:
|
||||
* the recipient
|
||||
* the hash of the name's associated data
|
||||
"""
|
||||
|
||||
if not is_name_valid( name ):
|
||||
raise Exception("Invalid name '%s'" % name)
|
||||
|
||||
readable_script = "NAME_IMPORT 0x%s" % (hexlify(name))
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, inputs, recipient_address, sender_address, update_hash_b58, format='bin'):
|
||||
"""
|
||||
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 change sent to the original owner
|
||||
"""
|
||||
|
||||
dust_fee = DEFAULT_OP_RETURN_FEE + (len(inputs) + 3) * DEFAULT_DUST_FEE
|
||||
op_fee = 2 * DEFAULT_DUST_FEE
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format=format),
|
||||
"value": 0},
|
||||
|
||||
# recipient output
|
||||
{"script_hex": make_pay_to_address_script(recipient_address),
|
||||
"value": dust_value},
|
||||
|
||||
# update hash output
|
||||
{"script_hex": make_pay_to_address_script(update_hash_b58),
|
||||
"value": dust_value},
|
||||
|
||||
# change output
|
||||
{"script_hex": make_pay_to_address_script(sender_address),
|
||||
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
|
||||
]
|
||||
|
||||
|
||||
def broadcast(name, recipient_address, update_hash, private_key, blockchain_client, blockchain_broadcaster=None, user_public_key=None, testset=False):
|
||||
|
||||
tx_only = False
|
||||
# TODO: support tx_only
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
nulldata = build(name, testset=testset)
|
||||
|
||||
# convert update_hash from a hex string so it looks like an address
|
||||
update_hash_b58 = b58check_encode( unhexlify(update_hash) )
|
||||
|
||||
# get inputs and from address
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
# NAME_IMPORT outputs
|
||||
outputs = make_outputs(nulldata, inputs, recipient_address, from_address, update_hash_b58, format='hex')
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {"unsigned_tx": unsigned_tx}
|
||||
|
||||
else:
|
||||
# serialize, sign, and broadcast the tx
|
||||
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
|
||||
|
||||
# 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
|
||||
if not is_name_valid( fqn ):
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'NAME_IMPORT',
|
||||
'name': fqn,
|
||||
'recipient': recipient,
|
||||
'update_hash': update_hash
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Blockstack currently does not allow
|
||||
the subsidization of namespaces.
|
||||
"""
|
||||
return (None, None)
|
||||
'''
|
||||
|
||||
@@ -21,20 +21,6 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, serialize_transaction, \
|
||||
analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \
|
||||
make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, hex_hash160
|
||||
|
||||
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 *
|
||||
from ..hashing import hash_name
|
||||
|
||||
# consensus hash fields (ORDER MATTERS!)
|
||||
FIELDS = [
|
||||
'namespace_id_hash', # hash(namespace_id,sender,reveal_addr)
|
||||
@@ -49,149 +35,3 @@ FIELDS = [
|
||||
'address' # address from the scriptPubKey
|
||||
]
|
||||
|
||||
'''
|
||||
def build( namespace_id, script_pubkey, register_addr, consensus_hash, namespace_id_hash=None, testset=False ):
|
||||
"""
|
||||
Preorder a namespace with the given consensus hash. This records that someone has begun to create
|
||||
a namespace, while blinding all other peers to its ID. This operation additionally records the
|
||||
consensus hash in order to ensure that all peers will recognize that this sender has begun the creation.
|
||||
|
||||
Takes an ASCII-encoded namespace ID.
|
||||
NOTE: "namespace_id" must not start with ., but can contain anything else we want
|
||||
|
||||
We put the hash of the namespace ID instead of the namespace ID itself to avoid races with squatters (akin to pre-ordering)
|
||||
|
||||
Format:
|
||||
|
||||
0 2 3 23 39
|
||||
|-----|---|--------------------------------------|----------------|
|
||||
magic op hash(ns_id,script_pubkey,reveal_addr) consensus hash
|
||||
"""
|
||||
|
||||
# sanity check
|
||||
if namespace_id_hash is None:
|
||||
|
||||
# expect inputs to the hash...
|
||||
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
|
||||
raise Exception("Namespace identifier '%s' has non-base-38 characters" % namespace_id)
|
||||
|
||||
if len(namespace_id) == 0 or len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
|
||||
raise Exception("Invalid namespace ID length '%s (expected length between 1 and %s)" % (namespace_id, LENGTHS['blockchain_id_namespace_id']))
|
||||
|
||||
namespace_id_hash = hash_name(namespace_id, script_pubkey, register_addr=register_addr)
|
||||
|
||||
readable_script = "NAMESPACE_PREORDER 0x%s 0x%s" % (namespace_id_hash, consensus_hash)
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, inputs, change_addr, fee, pay_fee=True, format='bin' ):
|
||||
"""
|
||||
Make outputs for a namespace preorder:
|
||||
[0] OP_RETURN with the name
|
||||
[1] change address with the NAME_PREORDER sender's address
|
||||
[2] pay-to-address with the *burn address* with the fee
|
||||
"""
|
||||
|
||||
dust_fee = DEFAULT_OP_RETURN_FEE + (len(inputs) + 2) * DEFAULT_DUST_FEE
|
||||
op_fee = max(fee, DEFAULT_DUST_FEE)
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
|
||||
bill = op_fee
|
||||
|
||||
if not pay_fee:
|
||||
# subsidized
|
||||
dust_fee = 0
|
||||
op_fee = 0
|
||||
dust_value = 0
|
||||
bill = 0
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format=format),
|
||||
"value": 0},
|
||||
|
||||
# change address
|
||||
{"script_hex": make_pay_to_address_script(change_addr),
|
||||
"value": calculate_change_amount(inputs, bill, dust_fee)},
|
||||
|
||||
# burn address
|
||||
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
|
||||
"value": op_fee}
|
||||
]
|
||||
|
||||
|
||||
def broadcast( namespace_id, register_addr, consensus_hash, private_key, blockchain_client, fee, pay_fee=True, user_public_key=None, testset=False, blockchain_broadcaster=None ):
|
||||
"""
|
||||
Propagate a namespace.
|
||||
|
||||
Arguments:
|
||||
namespace_id human-readable (i.e. base-40) name of the namespace
|
||||
register_addr the addr of the key that will reveal the namespace (mixed into the preorder to prevent name preimage attack races)
|
||||
private_key the Bitcoin address that created this namespace, and can populate it.
|
||||
"""
|
||||
|
||||
# TODO: support tx_only
|
||||
tx_only = False
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
pubkey_hex = BitcoinPrivateKey( private_key ).public_key().to_hex()
|
||||
|
||||
script_pubkey = get_script_pubkey( pubkey_hex )
|
||||
nulldata = build( namespace_id, script_pubkey, register_addr, consensus_hash, testset=testset )
|
||||
|
||||
# get inputs and from address
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
# build custom outputs here
|
||||
outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex')
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {"unsigned_tx": unsigned_tx}
|
||||
|
||||
else:
|
||||
# serialize, sign, and broadcast the tx
|
||||
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
|
||||
|
||||
# response = {'success': True }
|
||||
response.update({'data': nulldata})
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def parse( bin_payload ):
|
||||
"""
|
||||
NOTE: the first three bytes will be missing
|
||||
"""
|
||||
|
||||
if len(bin_payload) != LENGTHS['preorder_name_hash'] + LENGTHS['consensus_hash']:
|
||||
log.error("Invalid namespace preorder payload length %s" % len(bin_payload))
|
||||
return None
|
||||
|
||||
namespace_id_hash = bin_payload[ :LENGTHS['preorder_name_hash'] ]
|
||||
consensus_hash = bin_payload[ LENGTHS['preorder_name_hash']: LENGTHS['preorder_name_hash'] + LENGTHS['consensus_hash'] ]
|
||||
|
||||
namespace_id_hash = hexlify( namespace_id_hash )
|
||||
consensus_hash = hexlify( consensus_hash )
|
||||
|
||||
|
||||
return {
|
||||
'opcode': 'NAMESPACE_PREORDER',
|
||||
'namespace_id_hash': namespace_id_hash,
|
||||
'consensus_hash': consensus_hash
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Blockstack currently does not allow
|
||||
the subsidization of namespaces.
|
||||
"""
|
||||
return (None, None)
|
||||
'''
|
||||
|
||||
@@ -21,14 +21,6 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, BlockchainInfoClient, hex_hash160, make_op_return_tx, serialize_transaction, broadcast_transaction, make_op_return_outputs
|
||||
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 *
|
||||
|
||||
import virtualchain
|
||||
log = virtualchain.get_logger("blockstack-server")
|
||||
|
||||
@@ -39,94 +31,3 @@ FIELDS = NAMESPACE_REVEAL_FIELDS + [
|
||||
'ready_block', # block number at which the namespace was readied
|
||||
]
|
||||
|
||||
'''
|
||||
def build( namespace_id, testset=False ):
|
||||
"""
|
||||
Record to mark the end of a namespace import in the blockchain.
|
||||
|
||||
Takes an base40-encoded namespace ID to mark the end.
|
||||
|
||||
Format:
|
||||
|
||||
0 2 3 4 23
|
||||
|-----|--|--|------------|
|
||||
magic op . ns_id
|
||||
"""
|
||||
|
||||
# sanity check
|
||||
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
|
||||
raise Exception("Namespace ID '%s' has non-base-38 characters" % namespace_id)
|
||||
|
||||
if len(namespace_id) == 0 or len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
|
||||
raise Exception("Invalid namespace ID '%s (expected length between 1 and %s)" % (namespace_id, LENGTHS['blockchain_id_namespace_id']))
|
||||
|
||||
readable_script = "NAMESPACE_READY 0x%s" % (hexlify("." + namespace_id))
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def broadcast( namespace_id, private_key, blockchain_client, user_public_key=None, testset=False, blockchain_broadcaster=None ):
|
||||
|
||||
tx_only = False
|
||||
# TODO: support tx_only
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
nulldata = build( namespace_id, testset=testset )
|
||||
|
||||
# get inputs and from address
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
# OP_RETURN outputs
|
||||
outputs = make_op_return_outputs( nulldata, inputs, from_address, fee=DEFAULT_OP_RETURN_FEE, format='hex' )
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {'unsigned_tx': signed_tx}
|
||||
|
||||
else:
|
||||
|
||||
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
|
||||
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
|
||||
response.update({'data': nulldata})
|
||||
return response
|
||||
|
||||
|
||||
def parse( bin_payload ):
|
||||
"""
|
||||
NOTE: the first three bytes will be missing
|
||||
NOTE: the first byte in bin_payload is a '.'
|
||||
"""
|
||||
|
||||
if bin_payload[0] != '.':
|
||||
log.error("Missing namespace delimiter .")
|
||||
return None
|
||||
|
||||
namespace_id = bin_payload[ 1: ]
|
||||
|
||||
# sanity check
|
||||
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
|
||||
log.error("Invalid namespace ID '%s'" % namespace_id)
|
||||
return None
|
||||
|
||||
if len(namespace_id) <= 0 or len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
|
||||
log.error("Invalid namespace of length %s" % len(namespace_id))
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'NAMESPACE_READY',
|
||||
'namespace_id': namespace_id
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Blockstack currently does not allow
|
||||
the subsidization of namespaces.
|
||||
"""
|
||||
return (None, None)
|
||||
'''
|
||||
|
||||
@@ -21,22 +21,6 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, serialize_transaction, \
|
||||
analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \
|
||||
make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, hex_hash160
|
||||
|
||||
from pybitcoin.transactions.outputs import calculate_change_amount
|
||||
|
||||
from utilitybelt import is_hex
|
||||
from binascii import hexlify, unhexlify
|
||||
import types
|
||||
import json
|
||||
|
||||
from ..b40 import b40_to_hex, bin_to_b40, is_b40
|
||||
from ..config import *
|
||||
from ..scripts import blockstack_script_to_hex, add_magic_bytes
|
||||
from ..hashing import hash_name
|
||||
|
||||
import virtualchain
|
||||
log = virtualchain.get_logger("blockstack-log")
|
||||
|
||||
@@ -66,295 +50,3 @@ FIELDS = [
|
||||
'no_vowel_discount', # multiplicative coefficient that drops a name's price if it has no vowels
|
||||
]
|
||||
|
||||
'''
|
||||
def serialize_int( int_field, numbytes ):
|
||||
"""
|
||||
Serialize an integer to a hex string that is padlen characters long.
|
||||
Raise an exception on overflow.
|
||||
"""
|
||||
|
||||
if int_field >= 2**(numbytes*8) or int_field < -(2**(numbytes*8)):
|
||||
raise Exception("Integer overflow (%s bytes)" % (numbytes) )
|
||||
|
||||
format_str = "%%0.%sx" % (numbytes*2)
|
||||
hex_str = format_str % int_field
|
||||
|
||||
if len(hex_str) % 2 != 0:
|
||||
# sometimes python cuts off the leading zero
|
||||
hex_str = '0' + hex_str
|
||||
|
||||
return hex_str
|
||||
|
||||
|
||||
def serialize_buckets( bucket_exponents ):
|
||||
"""
|
||||
Serialize the list of bucket exponents.
|
||||
There should be 16 buckets, and each one should have an integer between 0 and 15.
|
||||
"""
|
||||
ret = ""
|
||||
for i in xrange(0, len(bucket_exponents)):
|
||||
ret += "%X" % bucket_exponents[i]
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def serialize_discounts( nonalpha_discount, no_vowel_discount ):
|
||||
"""
|
||||
Serialize the non-alpha and no-vowel discounts.
|
||||
They must be between 0 and 15
|
||||
"""
|
||||
return "%X%X" % (nonalpha_discount, no_vowel_discount)
|
||||
|
||||
|
||||
def namespacereveal_sanity_check( namespace_id, version, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount ):
|
||||
"""
|
||||
Verify the validity of a namespace reveal.
|
||||
Return True if valid
|
||||
Raise an Exception if not valid.
|
||||
"""
|
||||
# sanity check
|
||||
if not is_b40( namespace_id ) or "+" in namespace_id or namespace_id.count(".") > 0:
|
||||
raise Exception("Namespace ID '%s' has non-base-38 characters" % namespace_id)
|
||||
|
||||
if len(namespace_id) > LENGTHS['blockchain_id_namespace_id']:
|
||||
raise Exception("Invalid namespace ID length for '%s' (expected length between 1 and %s)" % (namespace_id, LENGTHS['blockchain_id_namespace_id']))
|
||||
|
||||
if lifetime < 0 or lifetime > (2**32 - 1):
|
||||
lifetime = NAMESPACE_LIFE_INFINITE
|
||||
|
||||
if coeff < 0 or coeff > 255:
|
||||
raise Exception("Invalid cost multiplier %s: must be in range [0, 256)" % coeff)
|
||||
|
||||
if base < 0 or base > 255:
|
||||
raise Exception("Invalid base price %s: must be in range [0, 256)" % base)
|
||||
|
||||
if len(bucket_exponents) != 16:
|
||||
raise Exception("Exactly 16 buckets required")
|
||||
|
||||
for i in xrange(0, len(bucket_exponents)):
|
||||
if bucket_exponents[i] < 0 or bucket_exponents[i] > 15:
|
||||
raise Exception("Invalid bucket exponent %s (must be in range [0, 16)" % bucket_exponents[i])
|
||||
|
||||
if nonalpha_discount <= 0 or nonalpha_discount > 15:
|
||||
raise Exception("Invalid non-alpha discount %s: must be in range [0, 16)" % nonalpha_discount)
|
||||
|
||||
if no_vowel_discount <= 0 or no_vowel_discount > 15:
|
||||
raise Exception("Invalid no-vowel discount %s: must be in range [0, 16)" % no_vowel_discount)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# version: 2 bytes
|
||||
# namespace ID: up to 19 bytes
|
||||
def build( namespace_id, version, reveal_addr, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount, testset=False ):
|
||||
"""
|
||||
Record to mark the beginning of a namespace import in the blockchain.
|
||||
This reveals the namespace ID, and encodes the preorder's namespace rules.
|
||||
|
||||
The rules for a namespace are as follows:
|
||||
* a name can fall into one of 16 buckets, measured by length. Bucket 16 incorporates all names at least 16 characters long.
|
||||
* the pricing structure applies a multiplicative penalty for having numeric characters, or punctuation characters.
|
||||
* the price of a name in a bucket is ((coeff) * (base) ^ (bucket exponent)) / ((numeric discount multiplier) * (punctuation discount multiplier))
|
||||
|
||||
Example:
|
||||
base = 10
|
||||
coeff = 2
|
||||
nonalpha discount: 10
|
||||
no-vowel discount: 10
|
||||
buckets 1, 2: 9
|
||||
buckets 3, 4, 5, 6: 8
|
||||
buckets 7, 8, 9, 10, 11, 12, 13, 14: 7
|
||||
buckets 15, 16+:
|
||||
|
||||
The price of "john" would be 2 * 10^8, since "john" falls into bucket 4 and has no punctuation or numerics.
|
||||
The price of "john1" would be 2 * 10^6, since "john1" falls into bucket 5 but has a number (and thus receives a 10x discount)
|
||||
The price of "john_1" would be 2 * 10^6, since "john_1" falls into bucket 6 but has a number and puncuation (and thus receives a 10x discount)
|
||||
The price of "j0hn_1" would be 2 * 10^5, since "j0hn_1" falls into bucket 6 but has a number and punctuation and lacks vowels (and thus receives a 100x discount)
|
||||
Namespace ID must be base38.
|
||||
|
||||
Format:
|
||||
|
||||
0 2 3 7 8 9 10 11 12 13 14 15 16 17 18 20 39
|
||||
|-----|---|--------|-----|-----|----|----|----|----|----|-----|-----|-----|--------|----------|-------------------------|
|
||||
magic op life coeff. base 1-2 3-4 5-6 7-8 9-10 11-12 13-14 15-16 nonalpha version namespace ID
|
||||
bucket exponents no-vowel
|
||||
discounts
|
||||
|
||||
"""
|
||||
|
||||
rc = namespacereveal_sanity_check( namespace_id, version, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount )
|
||||
if not rc:
|
||||
raise Exception("Invalid namespace parameters")
|
||||
|
||||
# good to go!
|
||||
life_hex = serialize_int( lifetime, 4 )
|
||||
coeff_hex = serialize_int( coeff, 1 )
|
||||
base_hex = serialize_int( base, 1 )
|
||||
bucket_hex = serialize_buckets( bucket_exponents )
|
||||
discount_hex = serialize_discounts( nonalpha_discount, no_vowel_discount )
|
||||
version_hex = serialize_int( version, 2 )
|
||||
namespace_id_hex = hexlify( namespace_id )
|
||||
|
||||
readable_script = "NAMESPACE_REVEAL 0x%s 0x%s 0x%s 0x%s 0x%s 0x%s 0x%s" % (life_hex, coeff_hex, base_hex, bucket_hex, discount_hex, version_hex, namespace_id_hex)
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, inputs, reveal_addr, change_addr, format='bin', testset=False ):
|
||||
"""
|
||||
Make outputs for a register:
|
||||
[0] OP_RETURN with the name
|
||||
[1] pay-to-address with the *reveal_addr*, not the sender's address.
|
||||
[2] change address with the NAMESPACE_PREORDER sender's address
|
||||
"""
|
||||
|
||||
total_to_send = DEFAULT_OP_RETURN_FEE + DEFAULT_DUST_FEE
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format=format),
|
||||
"value": 0},
|
||||
|
||||
# register address
|
||||
{"script_hex": make_pay_to_address_script(reveal_addr),
|
||||
"value": DEFAULT_DUST_FEE},
|
||||
|
||||
# change address
|
||||
{"script_hex": make_pay_to_address_script(change_addr),
|
||||
"value": calculate_change_amount(inputs, total_to_send, DEFAULT_DUST_FEE * (len(inputs) + 3))},
|
||||
]
|
||||
|
||||
|
||||
|
||||
def broadcast( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, private_key, blockchain_client, user_public_key=None, blockchain_broadcaster=None, testset=False ):
|
||||
"""
|
||||
Propagate a namespace.
|
||||
|
||||
Arguments:
|
||||
namespace_id human-readable (i.e. base-40) name of the namespace
|
||||
reveal_addr address to own this namespace until it is ready
|
||||
lifetime: the number of blocks for which names will be valid (pass a negative value for "infinite")
|
||||
coeff: cost multipler
|
||||
base_cost: the base cost (i.e. cost of a 1-character name), in satoshis
|
||||
bucket_exponents: bucket cost exponents to which to raise the base cost
|
||||
nonalpha_discount: discount multipler for non-alpha-character names
|
||||
no_vowel_discount: discount multipler for no-vowel names
|
||||
"""
|
||||
|
||||
tx_only = False
|
||||
# TODO: support tx_only
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
nulldata = build( namespace_id, BLOCKSTACK_VERSION, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, testset=testset )
|
||||
|
||||
# get inputs and from address
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
# build custom outputs here
|
||||
outputs = make_outputs(nulldata, inputs, reveal_addr, from_address, format='hex')
|
||||
|
||||
if tx_only:
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {"unsigned_tx": unsigned_tx}
|
||||
|
||||
else:
|
||||
# serialize, sign, and broadcast the tx
|
||||
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
|
||||
|
||||
# response = {'success': True }
|
||||
response.update({'data': nulldata})
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def parse( bin_payload, sender, recipient_address ):
|
||||
"""
|
||||
NOTE: the first three bytes will be missing
|
||||
"""
|
||||
|
||||
off = 0
|
||||
life = None
|
||||
coeff = None
|
||||
base = None
|
||||
bucket_hex = None
|
||||
buckets = []
|
||||
discount_hex = None
|
||||
nonalpha_discount = None
|
||||
no_vowel_discount = None
|
||||
version = None
|
||||
namespace_id = None
|
||||
namespace_id_hash = None
|
||||
|
||||
life = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_life']]), 16 )
|
||||
|
||||
off += LENGTHS['blockchain_id_namespace_life']
|
||||
|
||||
coeff = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_coeff']]), 16 )
|
||||
|
||||
off += LENGTHS['blockchain_id_namespace_coeff']
|
||||
|
||||
base = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_base']]), 16 )
|
||||
|
||||
off += LENGTHS['blockchain_id_namespace_base']
|
||||
|
||||
bucket_hex = hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_buckets']])
|
||||
|
||||
off += LENGTHS['blockchain_id_namespace_buckets']
|
||||
|
||||
discount_hex = hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_discounts']])
|
||||
|
||||
off += LENGTHS['blockchain_id_namespace_discounts']
|
||||
|
||||
version = int( hexlify(bin_payload[off:off+LENGTHS['blockchain_id_namespace_version']]), 16)
|
||||
|
||||
off += LENGTHS['blockchain_id_namespace_version']
|
||||
|
||||
namespace_id = bin_payload[off:]
|
||||
namespace_id_hash = None
|
||||
try:
|
||||
namespace_id_hash = hash_name( namespace_id, sender, register_addr=recipient_address )
|
||||
except:
|
||||
log.error("Invalid namespace ID and/or sender")
|
||||
return None
|
||||
|
||||
# extract buckets
|
||||
buckets = [int(x, 16) for x in list(bucket_hex)]
|
||||
|
||||
# extract discounts
|
||||
nonalpha_discount = int( list(discount_hex)[0], 16 )
|
||||
no_vowel_discount = int( list(discount_hex)[1], 16 )
|
||||
|
||||
try:
|
||||
rc = namespacereveal_sanity_check( namespace_id, version, life, coeff, base, buckets, nonalpha_discount, no_vowel_discount )
|
||||
if not rc:
|
||||
raise Exception("Invalid namespace parameters")
|
||||
|
||||
except Exception, e:
|
||||
log.error("Invalid namespace parameters")
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'NAMESPACE_REVEAL',
|
||||
'lifetime': life,
|
||||
'coeff': coeff,
|
||||
'base': base,
|
||||
'buckets': buckets,
|
||||
'version': version,
|
||||
'nonalpha_discount': nonalpha_discount,
|
||||
'no_vowel_discount': no_vowel_discount,
|
||||
'namespace_id': namespace_id,
|
||||
'namespace_id_hash': namespace_id_hash
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Blockstack currently does not allow
|
||||
the subsidization of namespaces.
|
||||
"""
|
||||
return (None, None)
|
||||
'''
|
||||
|
||||
@@ -21,22 +21,6 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, serialize_transaction, \
|
||||
analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \
|
||||
make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, \
|
||||
hex_hash160, bin_hash160, BitcoinPrivateKey, BitcoinPublicKey, script_hex_to_address, get_unspents, \
|
||||
make_op_return_outputs
|
||||
|
||||
|
||||
from pybitcoin.transactions.outputs import calculate_change_amount
|
||||
from utilitybelt import is_hex
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from ..b40 import b40_to_hex, is_b40
|
||||
from ..config import *
|
||||
from ..scripts import *
|
||||
from ..hashing import hash_name
|
||||
|
||||
# consensus hash fields (ORDER MATTERS!)
|
||||
FIELDS = [
|
||||
'preorder_name_hash', # hash(name,sender,register_addr)
|
||||
@@ -52,184 +36,3 @@ FIELDS = [
|
||||
'op_fee', # blockstack fee (sent to burn address)
|
||||
]
|
||||
|
||||
'''
|
||||
def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None, testset=False):
|
||||
"""
|
||||
Takes a name, including the namespace ID (but not the id: scheme), a script_publickey to prove ownership
|
||||
of the subsequent NAME_REGISTER operation, and the current consensus hash for this block (to prove that the
|
||||
caller is not on a shorter fork).
|
||||
|
||||
Returns a NAME_PREORDER script.
|
||||
|
||||
Record format:
|
||||
|
||||
0 2 3 23 39
|
||||
|-----|--|----------------------------------------------|--------------|
|
||||
magic op hash(name.ns_id,script_pubkey,register_addr) consensus hash
|
||||
|
||||
"""
|
||||
|
||||
if name_hash is None:
|
||||
|
||||
# expect inputs to the hash
|
||||
if not is_b40( name ) or "+" in name or name.count(".") > 1:
|
||||
raise Exception("Name '%s' has non-base-38 characters" % name)
|
||||
|
||||
# name itself cannot exceed LENGTHS['blockchain_id_name']
|
||||
if len(NAME_SCHEME) + len(name) > LENGTHS['blockchain_id_name']:
|
||||
raise Exception("Name '%s' is too long; exceeds %s bytes" % (name, LENGTHS['blockchain_id_name'] - len(NAME_SCHEME)))
|
||||
|
||||
name_hash = hash_name(name, script_pubkey, register_addr=register_addr)
|
||||
|
||||
script = 'NAME_PREORDER 0x%s 0x%s' % (name_hash, consensus_hash)
|
||||
hex_script = blockstack_script_to_hex(script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, inputs, sender_addr, fee, format='bin' ):
|
||||
"""
|
||||
Make outputs for a name preorder:
|
||||
[0] OP_RETURN with the name
|
||||
[1] address with the NAME_PREORDER sender's address
|
||||
[2] pay-to-address with the *burn address* with the fee
|
||||
"""
|
||||
|
||||
op_fee = max(fee, DEFAULT_DUST_FEE)
|
||||
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
|
||||
bill = op_fee
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format=format),
|
||||
"value": 0},
|
||||
|
||||
# change address (can be subsidy key)
|
||||
{"script_hex": make_pay_to_address_script(sender_addr),
|
||||
"value": calculate_change_amount(inputs, bill, dust_fee)},
|
||||
|
||||
# burn address
|
||||
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
|
||||
"value": op_fee}
|
||||
]
|
||||
|
||||
|
||||
def broadcast(name, private_key, register_addr, consensus_hash, blockchain_client, fee, blockchain_broadcaster=None, user_public_key=None, testset=False):
|
||||
"""
|
||||
Builds and broadcasts a preorder transaction.
|
||||
"""
|
||||
|
||||
tx_only = False
|
||||
if user_public_key is not None:
|
||||
# subsidizing, and only want the tx
|
||||
tx_only = True
|
||||
|
||||
# sanity check
|
||||
if user_public_key is None and private_key is None:
|
||||
raise Exception("Missing both client public and private key")
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
from_address = None # change address
|
||||
inputs = None
|
||||
private_key_obj = None
|
||||
script_pubkey = None # to be mixed into preorder hash
|
||||
|
||||
if user_public_key is not None:
|
||||
# tx only
|
||||
pubk = BitcoinPublicKey( user_public_key )
|
||||
|
||||
from_address = BitcoinPublicKey( user_public_key ).address()
|
||||
inputs = get_unspents( from_address, blockchain_client )
|
||||
script_pubkey = get_script_pubkey( user_public_key )
|
||||
|
||||
else:
|
||||
# ordering directly
|
||||
pubk = BitcoinPrivateKey( private_key ).public_key()
|
||||
public_key = pubk.to_hex()
|
||||
script_pubkey = get_script_pubkey( public_key )
|
||||
|
||||
# get inputs and from address using private key
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
nulldata = build( name, script_pubkey, register_addr, consensus_hash, testset=testset)
|
||||
outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex')
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {"unsigned_tx": unsigned_tx}
|
||||
|
||||
else:
|
||||
# serialize, sign, and broadcast the tx
|
||||
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
|
||||
response.update({'data': nulldata})
|
||||
return response
|
||||
|
||||
|
||||
def parse(bin_payload):
|
||||
"""
|
||||
Parse a name preorder.
|
||||
NOTE: bin_payload *excludes* the leading 3 bytes (magic + op) returned by build.
|
||||
"""
|
||||
|
||||
if len(bin_payload) != LENGTHS['preorder_name_hash'] + LENGTHS['consensus_hash']:
|
||||
return None
|
||||
|
||||
name_hash = hexlify( bin_payload[0:LENGTHS['preorder_name_hash']] )
|
||||
consensus_hash = hexlify( bin_payload[LENGTHS['preorder_name_hash']:] )
|
||||
|
||||
return {
|
||||
'opcode': 'NAME_PREORDER',
|
||||
'preorder_name_hash': name_hash,
|
||||
'consensus_hash': consensus_hash
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Given a transaction's outputs, look up its fees:
|
||||
* the first output must be an OP_RETURN, and it must have a fee of 0.
|
||||
# the second must be the change address
|
||||
* the third must be a burn fee to the burn address.
|
||||
|
||||
Return (dust fees, operation fees) on success
|
||||
Return (None, None) on invalid output listing
|
||||
"""
|
||||
if len(outputs) != 3:
|
||||
log.debug("Expected 3 outputs; got %s" % len(outputs))
|
||||
return (None, None)
|
||||
|
||||
# 0: op_return
|
||||
if not tx_output_is_op_return( outputs[0] ):
|
||||
log.debug("outputs[0] is not an OP_RETURN")
|
||||
return (None, None)
|
||||
|
||||
if outputs[0]["value"] != 0:
|
||||
log.debug("outputs[0] has value %s'" % outputs[0]["value"])
|
||||
return (None, None)
|
||||
|
||||
# 1: change address
|
||||
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
|
||||
log.error("outputs[1] has no decipherable change address")
|
||||
return (None, None)
|
||||
|
||||
# 2: burn address
|
||||
addr_hash = script_hex_to_address( outputs[2]["script_hex"] )
|
||||
if addr_hash is None:
|
||||
log.error("outputs[2] has no decipherable burn address")
|
||||
return (None, None)
|
||||
|
||||
if addr_hash != BLOCKSTACK_BURN_ADDRESS:
|
||||
log.error("outputs[2] is not the burn address")
|
||||
return (None, None)
|
||||
|
||||
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = outputs[2]["value"]
|
||||
|
||||
return (dust_fee, op_fee)
|
||||
'''
|
||||
|
||||
@@ -21,24 +21,11 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, serialize_transaction, \
|
||||
analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \
|
||||
make_pay_to_address_script, b58check_encode, b58check_decode, BlockchainInfoClient, hex_hash160, \
|
||||
BitcoinPrivateKey, get_unspents, script_hex_to_address
|
||||
|
||||
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 *
|
||||
from ..hashing import hash256_trunc128
|
||||
from ..nameset import NAMEREC_FIELDS
|
||||
|
||||
import virtualchain
|
||||
log = virtualchain.get_logger("blockstack-server")
|
||||
|
||||
from ..nameset import NAMEREC_FIELDS
|
||||
|
||||
# consensus hash fields (ORDER MATTERS!)
|
||||
FIELDS = NAMEREC_FIELDS + [
|
||||
'recipient', # scriptPubKey hex script that identifies the principal to own this name
|
||||
@@ -78,234 +65,3 @@ def get_registration_recipient_from_outputs( outputs ):
|
||||
|
||||
return ret
|
||||
|
||||
'''
|
||||
def build(name, testset=False):
|
||||
"""
|
||||
Takes in the name that was preordered, including the namespace ID (but not the id: scheme)
|
||||
Returns a hex string representing up to LENGTHS['blockchain_id_name'] bytes.
|
||||
|
||||
Record format:
|
||||
|
||||
0 2 3 39
|
||||
|----|--|-----------------------------|
|
||||
magic op name.ns_id (37 bytes)
|
||||
|
||||
"""
|
||||
|
||||
if not is_name_valid( name ):
|
||||
raise Exception("Invalid name '%s'" % name)
|
||||
|
||||
readable_script = "NAME_REGISTRATION 0x%s" % (hexlify(name))
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, change_inputs, register_addr, change_addr, renewal_fee=None, pay_fee=True, format='bin' ):
|
||||
"""
|
||||
Make outputs for a register:
|
||||
[0] OP_RETURN with the name
|
||||
[1] pay-to-address with the *register_addr*, not the sender's address.
|
||||
[2] change address with the NAME_PREORDER sender's address
|
||||
[3] (OPTIONAL) renewal fee, sent to the burn address
|
||||
"""
|
||||
|
||||
dust_fee = None
|
||||
dust_value = None
|
||||
op_fee = None
|
||||
bill = None
|
||||
|
||||
if pay_fee:
|
||||
|
||||
# sender pays
|
||||
if renewal_fee is not None:
|
||||
# renewing
|
||||
dust_fee = (len(change_inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
op_fee = max(renewal_fee, DEFAULT_DUST_FEE)
|
||||
bill = op_fee
|
||||
|
||||
else:
|
||||
# registering
|
||||
dust_fee = (len(change_inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
op_fee = 0
|
||||
bill = DEFAULT_DUST_FEE * 2
|
||||
|
||||
else:
|
||||
|
||||
# subsidized by another address
|
||||
if renewal_fee is not None:
|
||||
# renewing
|
||||
dust_fee = 0
|
||||
dust_value = 0
|
||||
op_fee = max(renewal_fee, DEFAULT_DUST_FEE)
|
||||
bill = 0
|
||||
|
||||
else:
|
||||
# registering
|
||||
dust_fee = 0
|
||||
dust_value = 0
|
||||
op_fee = 0
|
||||
bill = 0
|
||||
|
||||
outputs = [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format=format),
|
||||
"value": 0},
|
||||
|
||||
# register address
|
||||
{"script_hex": make_pay_to_address_script(register_addr),
|
||||
"value": dust_fee},
|
||||
|
||||
# change address (can be the subsidy address)
|
||||
{"script_hex": make_pay_to_address_script(change_addr),
|
||||
"value": calculate_change_amount(change_inputs, bill, dust_fee)},
|
||||
]
|
||||
|
||||
if renewal_fee is not None:
|
||||
outputs.append(
|
||||
|
||||
# burn address (when renewing)
|
||||
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
|
||||
"value": op_fee}
|
||||
)
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def broadcast(name, private_key, register_addr, blockchain_client, renewal_fee=None, blockchain_broadcaster=None, user_public_key=None, subsidy_public_key=None, testset=False):
|
||||
|
||||
# sanity check
|
||||
tx_only = False
|
||||
if subsidy_public_key is not None or user_public_key is not None:
|
||||
tx_only = True
|
||||
|
||||
if subsidy_public_key is None and private_key is None:
|
||||
raise Exception("Missing both public and private key")
|
||||
|
||||
if not tx_only and private_key is None:
|
||||
raise Exception("Need private key for broadcasting")
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
from_address = None
|
||||
change_inputs = None
|
||||
private_key_obj = None
|
||||
subsidized_renewal = False
|
||||
|
||||
if subsidy_public_key is not None:
|
||||
# subsidizing
|
||||
pubk = BitcoinPublicKey( subsidy_public_key )
|
||||
|
||||
if user_public_key is not None and renewal_fee is not None:
|
||||
# renewing, and subsidizing the renewal
|
||||
from_address = BitcoinPublicKey( user_public_key ).address()
|
||||
subsidized_renewal = True
|
||||
|
||||
else:
|
||||
# registering or renewing under the subsidy key
|
||||
from_address = pubk.address()
|
||||
|
||||
change_inputs = get_unspents( from_address, blockchain_client )
|
||||
|
||||
elif user_public_key is not None:
|
||||
pubk = BitcoinPublicKey( user_public_key )
|
||||
from_address = pubk.address()
|
||||
change_inputs = get_unspents( from_address, blockchain_client )
|
||||
|
||||
elif private_key is not None:
|
||||
# ordering directly
|
||||
pubk = BitcoinPrivateKey( private_key ).public_key()
|
||||
public_key = pubk.to_hex()
|
||||
private_key_obj, from_address, change_inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
nulldata = build(name, testset=testset)
|
||||
outputs = make_outputs(nulldata, change_inputs, register_addr, from_address, renewal_fee=renewal_fee, pay_fee=(not subsidized_renewal), format='hex')
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( change_inputs, outputs )
|
||||
return {"unsigned_tx": unsigned_tx}
|
||||
|
||||
else:
|
||||
|
||||
# serialize, sign, and broadcast the tx
|
||||
response = serialize_sign_and_broadcast(change_inputs, outputs, private_key_obj, blockchain_broadcaster)
|
||||
response.update({'data': nulldata})
|
||||
return response
|
||||
|
||||
|
||||
def parse(bin_payload):
|
||||
|
||||
"""
|
||||
Interpret a block's nulldata back into a name. The first three bytes (2 magic + 1 opcode)
|
||||
will not be present in bin_payload.
|
||||
|
||||
The name will be directly represented by the bytes given.
|
||||
"""
|
||||
|
||||
fqn = bin_payload
|
||||
|
||||
if not is_name_valid( fqn ):
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'NAME_REGISTRATION',
|
||||
'name': fqn
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Given a transaction's outputs, look up its fees:
|
||||
* the first output must be an OP_RETURN, and it must have a fee of 0.
|
||||
* the second output must be the reveal address, and it must have a dust fee
|
||||
* the third must be the change address
|
||||
* the fourth, if given, must be a burned fee sent to the burn address
|
||||
|
||||
Return (dust fees, operation fees) on success
|
||||
Return (None, None) on invalid output listing
|
||||
"""
|
||||
|
||||
dust_fee = 0
|
||||
op_fee = 0
|
||||
|
||||
if len(outputs) != 3 and len(outputs) != 4:
|
||||
return (None, None)
|
||||
|
||||
# 0: op_return
|
||||
if not tx_output_is_op_return( outputs[0] ):
|
||||
return (None, None)
|
||||
|
||||
if outputs[0]["value"] != 0:
|
||||
return (None, None)
|
||||
|
||||
# 1: reveal address
|
||||
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
|
||||
return (None, None)
|
||||
|
||||
# 2: change address
|
||||
if script_hex_to_address( outputs[2]["script_hex"] ) is None:
|
||||
return (None, None)
|
||||
|
||||
# 3: burn address, if given
|
||||
if len(outputs) == 4:
|
||||
|
||||
addr_hash = script_hex_to_address( outputs[3]["script_hex"] )
|
||||
if addr_hash is None:
|
||||
return (None, None)
|
||||
|
||||
if addr_hash != BLOCKSTACK_BURN_PUBKEY_HASH:
|
||||
return (None, None)
|
||||
|
||||
dust_fee = (len(inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = outputs[3]["value"]
|
||||
|
||||
else:
|
||||
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
|
||||
return (dust_fee, op_fee)
|
||||
'''
|
||||
|
||||
@@ -21,170 +21,8 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, make_op_return_tx, make_op_return_outputs, \
|
||||
make_op_return_script, broadcast_transaction, serialize_transaction, \
|
||||
script_hex_to_address, get_unspents
|
||||
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 *
|
||||
|
||||
from ..nameset import NAMEREC_FIELDS
|
||||
|
||||
# consensus hash fields (ORDER MATTERS!)
|
||||
FIELDS = NAMEREC_FIELDS
|
||||
|
||||
'''
|
||||
def build(name, testset=False):
|
||||
"""
|
||||
Takes in the name, including the namespace ID (but not the id: scheme)
|
||||
Returns a hex string representing up to LENGTHS['blockchain_id_name'] bytes.
|
||||
|
||||
Record format:
|
||||
|
||||
0 2 3 39
|
||||
|----|--|-----------------------------|
|
||||
magic op name.ns_id (37 bytes)
|
||||
|
||||
"""
|
||||
|
||||
if not is_name_valid( name ):
|
||||
raise Exception("Invalid name '%s'" % name)
|
||||
|
||||
readable_script = "NAME_REVOKE 0x%s" % (hexlify(name))
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, inputs, change_address, pay_fee=True ):
|
||||
"""
|
||||
Make outputs for a revoke.
|
||||
"""
|
||||
|
||||
dust_fee = None
|
||||
op_fee = None
|
||||
dust_value = None
|
||||
|
||||
if pay_fee:
|
||||
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = DEFAULT_DUST_FEE
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
|
||||
else:
|
||||
# will be subsidized
|
||||
dust_fee = 0
|
||||
op_fee = 0
|
||||
dust_value = 0
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format='hex'),
|
||||
"value": 0},
|
||||
|
||||
# change output
|
||||
{"script_hex": make_pay_to_address_script(change_address),
|
||||
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
|
||||
]
|
||||
|
||||
|
||||
def broadcast(name, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, subsidize=False, user_public_key=None ):
|
||||
|
||||
# sanity check
|
||||
tx_only = False
|
||||
pay_fee = True
|
||||
if user_public_key is not None:
|
||||
tx_only = True
|
||||
|
||||
if subsidize:
|
||||
pay_fee = False
|
||||
|
||||
if user_public_key is None and private_key is None:
|
||||
raise Exception("Missing both public and private key")
|
||||
|
||||
if not tx_only and private_key is None:
|
||||
raise Exception("Need private key for broadcasting")
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
from_address = None
|
||||
inputs = None
|
||||
private_key_obj = None
|
||||
|
||||
if user_public_key is not None:
|
||||
# subsidizing
|
||||
pubk = BitcoinPublicKey( user_public_key )
|
||||
from_address = pubk.address()
|
||||
inputs = get_unspents( from_address, blockchain_client )
|
||||
|
||||
elif private_key is not None:
|
||||
# ordering directly
|
||||
pubk = BitcoinPrivateKey( private_key ).public_key()
|
||||
public_key = pubk.to_hex()
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
nulldata = build(name, testset=testset)
|
||||
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {'unsigned_tx': unsigned_tx}
|
||||
|
||||
else:
|
||||
|
||||
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
|
||||
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
|
||||
response.update({'data': nulldata})
|
||||
return response
|
||||
|
||||
|
||||
def parse(bin_payload):
|
||||
"""
|
||||
Interpret a block's nulldata back into a name. The first three bytes (2 magic + 1 opcode)
|
||||
will not be present in bin_payload.
|
||||
|
||||
The name will be directly represented by the bytes given.
|
||||
"""
|
||||
|
||||
fqn = bin_payload
|
||||
if not is_name_valid( fqn ):
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'NAME_REVOKE',
|
||||
'name': fqn
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Given a transaction's outputs, look up its fees:
|
||||
* there should be two outputs: the OP_RETURN and change address
|
||||
|
||||
Return (dust fees, operation fees) on success
|
||||
Return (None, None) on invalid output listing
|
||||
"""
|
||||
if len(outputs) != 2:
|
||||
return (None, None)
|
||||
|
||||
# 0: op_return
|
||||
if not tx_output_is_op_return( outputs[0] ):
|
||||
return (None, None)
|
||||
|
||||
if outputs[0]["value"] != 0:
|
||||
return (None, None)
|
||||
|
||||
# 1: change address
|
||||
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
|
||||
return (None, None)
|
||||
|
||||
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = 0
|
||||
|
||||
return (dust_fee, op_fee)
|
||||
'''
|
||||
|
||||
@@ -21,19 +21,6 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, serialize_transaction, \
|
||||
analyze_private_key, serialize_sign_and_broadcast, make_op_return_script, \
|
||||
make_pay_to_address_script, BitcoinPrivateKey, BitcoinPublicKey, get_unspents, script_hex_to_address
|
||||
|
||||
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 *
|
||||
from ..hashing import hash256_trunc128
|
||||
|
||||
from ..nameset import NAMEREC_FIELDS
|
||||
|
||||
# consensus hash fields (ORDER MATTERS!)
|
||||
@@ -72,209 +59,3 @@ def get_transfer_recipient_from_outputs( outputs ):
|
||||
|
||||
return ret
|
||||
|
||||
'''
|
||||
def transfer_sanity_check( name, consensus_hash ):
|
||||
"""
|
||||
Verify that data for a transfer is valid.
|
||||
|
||||
Return True on success
|
||||
Raise Exception on error
|
||||
"""
|
||||
if name is not None and (not is_b40( name ) or "+" in name or name.count(".") > 1):
|
||||
raise Exception("Name '%s' has non-base-38 characters" % name)
|
||||
|
||||
# without the scheme, name must be 37 bytes
|
||||
if name is not None and (len(name) > LENGTHS['blockchain_id_name']):
|
||||
raise Exception("Name '%s' is too long; expected %s bytes" % (name, LENGTHS['blockchain_id_name']))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def build(name, keepdata, consensus_hash, testset=False):
|
||||
"""
|
||||
Takes in a name to transfer. Name must include the namespace ID, but not the scheme.
|
||||
|
||||
Record format:
|
||||
|
||||
0 2 3 4 20 36
|
||||
|-----|--|----|-------------------|---------------|
|
||||
magic op keep hash128(name.ns_id) consensus hash
|
||||
data?
|
||||
"""
|
||||
|
||||
rc = transfer_sanity_check( name, consensus_hash )
|
||||
if not rc:
|
||||
raise Exception("Invalid transfer data")
|
||||
|
||||
data_disposition = None
|
||||
|
||||
if keepdata:
|
||||
data_disposition = TRANSFER_KEEP_DATA
|
||||
else:
|
||||
data_disposition = TRANSFER_REMOVE_DATA
|
||||
|
||||
name_hash = hash256_trunc128( name )
|
||||
disposition_hex = hexlify(data_disposition)
|
||||
|
||||
readable_script = 'NAME_TRANSFER 0x%s 0x%s 0x%s' % (disposition_hex, name_hash, consensus_hash)
|
||||
hex_script = blockstack_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, pay_fee=True, format='bin' ):
|
||||
"""
|
||||
Builds the outputs for a name transfer operation.
|
||||
"""
|
||||
|
||||
dust_fee = None
|
||||
op_fee = None
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
|
||||
if pay_fee:
|
||||
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = DEFAULT_DUST_FEE
|
||||
|
||||
else:
|
||||
dust_fee = 0
|
||||
op_fee = 0
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format=format),
|
||||
"value": 0},
|
||||
# new name owner output
|
||||
{"script_hex": make_pay_to_address_script(new_name_owner_address),
|
||||
"value": dust_value},
|
||||
# change output
|
||||
{"script_hex": make_pay_to_address_script(change_address),
|
||||
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
|
||||
]
|
||||
|
||||
|
||||
def broadcast(name, destination_address, keepdata, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, subsidize=False, user_public_key=None, testset=False):
|
||||
|
||||
# sanity check
|
||||
tx_only = False
|
||||
if user_public_key is not None:
|
||||
tx_only = True
|
||||
|
||||
pay_fee = True
|
||||
if subsidize:
|
||||
pay_fee = False
|
||||
|
||||
if user_public_key is None and private_key is None:
|
||||
raise Exception("Missing both public and private key")
|
||||
|
||||
if not tx_only and private_key is None:
|
||||
raise Exception("Need private key for broadcasting")
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
from_address = None
|
||||
inputs = None
|
||||
private_key_obj = None
|
||||
|
||||
if user_public_key is not None:
|
||||
# subsidizing
|
||||
pubk = BitcoinPublicKey( user_public_key )
|
||||
from_address = pubk.address()
|
||||
inputs = get_unspents( from_address, blockchain_client )
|
||||
|
||||
elif private_key is not None:
|
||||
# ordering directly
|
||||
pubk = BitcoinPrivateKey( private_key ).public_key()
|
||||
public_key = pubk.to_hex()
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
nulldata = build(name, keepdata, consensus_hash, testset=testset)
|
||||
outputs = make_outputs(nulldata, inputs, destination_address, from_address, pay_fee=pay_fee, format='hex')
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {"unsigned_tx": unsigned_tx}
|
||||
|
||||
else:
|
||||
# serialize, sign, and broadcast the tx
|
||||
response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
|
||||
response.update({'data': nulldata})
|
||||
return response
|
||||
|
||||
|
||||
def parse(bin_payload, recipient):
|
||||
"""
|
||||
# NOTE: first three bytes were stripped
|
||||
"""
|
||||
|
||||
if len(bin_payload) != 1 + LENGTHS['name_hash'] + LENGTHS['consensus_hash']:
|
||||
log.error("Invalid transfer payload length %s" % len(bin_payload))
|
||||
return None
|
||||
|
||||
disposition_char = bin_payload[0:1]
|
||||
name_hash = bin_payload[1:1+LENGTHS['name_hash']]
|
||||
consensus_hash = bin_payload[1+LENGTHS['name_hash']:]
|
||||
|
||||
if disposition_char not in [TRANSFER_REMOVE_DATA, TRANSFER_KEEP_DATA]:
|
||||
log.error("Invalid disposition character")
|
||||
return None
|
||||
|
||||
# keep data by default
|
||||
disposition = True
|
||||
|
||||
if disposition_char == TRANSFER_REMOVE_DATA:
|
||||
disposition = False
|
||||
|
||||
try:
|
||||
rc = transfer_sanity_check( None, consensus_hash )
|
||||
if not rc:
|
||||
raise Exception("Invalid transfer data")
|
||||
|
||||
except Exception, e:
|
||||
log.error("Invalid transfer data")
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'NAME_TRANSFER',
|
||||
'name_hash': hexlify( name_hash ),
|
||||
'consensus_hash': hexlify( consensus_hash ),
|
||||
'recipient': recipient,
|
||||
'keep_data': disposition
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Given a transaction's outputs, look up its fees:
|
||||
* the first output should be an OP_RETURN with the transfer info
|
||||
* the second output should be the new owner's address, with a DEFAULT_DUST_FEE
|
||||
* the third output should be the change address
|
||||
|
||||
Return (dust fees, operation fees) on success
|
||||
Return (None, None) on invalid output listing
|
||||
"""
|
||||
if len(outputs) != 3:
|
||||
return (None, None)
|
||||
|
||||
# 0: op_return
|
||||
if not tx_output_is_op_return( outputs[0] ):
|
||||
return (None, None)
|
||||
|
||||
if outputs[0]["value"] != 0:
|
||||
return (None, None)
|
||||
|
||||
# 1: transfer address
|
||||
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
|
||||
return (None, None)
|
||||
|
||||
# 2: change address
|
||||
if script_hex_to_address( outputs[2]["script_hex"] ) is None:
|
||||
return (None, None)
|
||||
|
||||
dust_fee = (len(inputs) + 2) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = DEFAULT_DUST_FEE
|
||||
|
||||
return (dust_fee, op_fee)
|
||||
'''
|
||||
|
||||
@@ -21,225 +21,14 @@
|
||||
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pybitcoin import embed_data_in_blockchain, make_op_return_tx, BlockchainInfoClient, BitcoinPrivateKey, \
|
||||
BitcoinPublicKey, get_unspents, script_hex_to_address, hex_hash160, broadcast_transaction, serialize_transaction, \
|
||||
make_op_return_outputs, make_op_return_script
|
||||
|
||||
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 *
|
||||
from ..hashing import hash256_trunc128
|
||||
|
||||
from ..nameset import NAMEREC_FIELDS
|
||||
|
||||
import virtualchain
|
||||
log = virtualchain.get_logger("blockstack-server")
|
||||
|
||||
from ..nameset import NAMEREC_FIELDS
|
||||
|
||||
# consensus hash fields (ORDER MATTERS!)
|
||||
FIELDS = NAMEREC_FIELDS + [
|
||||
'name_hash', # hash(name,consensus_hash)
|
||||
'consensus_hash' # consensus hash when this update was sent
|
||||
]
|
||||
|
||||
'''
|
||||
def update_sanity_test( name, consensus_hash, data_hash ):
|
||||
"""
|
||||
Verify the validity of an update's data
|
||||
|
||||
Return True if valid
|
||||
Raise exception if not
|
||||
"""
|
||||
|
||||
if name is not None and (not is_b40( name ) or "+" in name or name.count(".") > 1):
|
||||
raise Exception("Name '%s' has non-base-38 characters" % name)
|
||||
|
||||
if data_hash is not None and not is_hex( data_hash ):
|
||||
raise Exception("Invalid hex string '%s': not hex" % (data_hash))
|
||||
|
||||
if len(data_hash) != 2 * LENGTHS['update_hash']:
|
||||
raise Exception("Invalid hex string '%s': bad length" % (data_hash))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def build(name, consensus_hash, data_hash=None, testset=False):
|
||||
"""
|
||||
Takes in the name to update the data for and the data update itself.
|
||||
Name must include the namespace ID, but not the scheme.
|
||||
|
||||
Record format:
|
||||
|
||||
0 2 3 19 39
|
||||
|-----|--|-----------------------------------|-----------------------|
|
||||
magic op hash128(name.ns_id,consensus hash) hash160(data)
|
||||
"""
|
||||
|
||||
rc = update_sanity_test( name, consensus_hash, data_hash )
|
||||
if not rc:
|
||||
raise Exception("Invalid update data")
|
||||
|
||||
hex_name = hash256_trunc128( name + consensus_hash )
|
||||
|
||||
readable_script = 'NAME_UPDATE 0x%s 0x%s' % (hex_name, data_hash)
|
||||
hex_script = blockstack_script_to_hex(readable_script)
|
||||
packaged_script = add_magic_bytes(hex_script, testset=testset)
|
||||
|
||||
return packaged_script
|
||||
|
||||
|
||||
def make_outputs( data, inputs, change_address, pay_fee=True ):
|
||||
"""
|
||||
Make outputs for an update.
|
||||
"""
|
||||
|
||||
dust_fee = None
|
||||
op_fee = None
|
||||
dust_value = None
|
||||
|
||||
if pay_fee:
|
||||
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = DEFAULT_DUST_FEE
|
||||
dust_value = DEFAULT_DUST_FEE
|
||||
|
||||
else:
|
||||
# will be subsidized
|
||||
dust_fee = 0
|
||||
op_fee = 0
|
||||
dust_value = 0
|
||||
|
||||
return [
|
||||
# main output
|
||||
{"script_hex": make_op_return_script(data, format='hex'),
|
||||
"value": 0},
|
||||
|
||||
# change output
|
||||
{"script_hex": make_pay_to_address_script(change_address),
|
||||
"value": calculate_change_amount(inputs, op_fee, dust_fee)}
|
||||
]
|
||||
|
||||
|
||||
def broadcast(name, data_hash, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, subsidize=False, user_public_key=None, testset=False):
|
||||
"""
|
||||
Write a name update into the blockchain.
|
||||
Returns a JSON object with 'data' set to the nulldata and 'transaction_hash' set to the transaction hash on success.
|
||||
"""
|
||||
|
||||
# sanity check
|
||||
pay_fee = True
|
||||
tx_only = False
|
||||
if user_public_key is not None:
|
||||
tx_only = True
|
||||
|
||||
if subsidize:
|
||||
pay_fee = False
|
||||
|
||||
if user_public_key is None and private_key is None:
|
||||
raise Exception("Missing both public and private key")
|
||||
|
||||
if not tx_only and private_key is None:
|
||||
raise Exception("Need private key for broadcasting")
|
||||
|
||||
if blockchain_broadcaster is None:
|
||||
blockchain_broadcaster = blockchain_client
|
||||
|
||||
from_address = None
|
||||
inputs = None
|
||||
private_key_obj = None
|
||||
|
||||
if user_public_key is not None:
|
||||
# subsidizing
|
||||
pubk = BitcoinPublicKey( user_public_key )
|
||||
from_address = pubk.address()
|
||||
inputs = get_unspents( from_address, blockchain_client )
|
||||
|
||||
elif private_key is not None:
|
||||
# ordering directly
|
||||
pubk = BitcoinPrivateKey( private_key ).public_key()
|
||||
public_key = pubk.to_hex()
|
||||
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
|
||||
|
||||
nulldata = build(name, consensus_hash, data_hash=data_hash, testset=testset)
|
||||
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
|
||||
|
||||
if tx_only:
|
||||
|
||||
unsigned_tx = serialize_transaction( inputs, outputs )
|
||||
return {'unsigned_tx': unsigned_tx}
|
||||
|
||||
else:
|
||||
|
||||
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
|
||||
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
|
||||
response.update({'data': nulldata})
|
||||
return response
|
||||
|
||||
|
||||
def parse(bin_payload):
|
||||
"""
|
||||
Parse a payload to get back the name and update hash.
|
||||
NOTE: bin_payload excludes the leading three bytes.
|
||||
"""
|
||||
|
||||
if len(bin_payload) != LENGTHS['name_hash'] + LENGTHS['data_hash']:
|
||||
log.error("Invalid update length %s" % len(bin_payload))
|
||||
return None
|
||||
|
||||
name_hash_bin = bin_payload[:LENGTHS['name_hash']]
|
||||
update_hash_bin = bin_payload[LENGTHS['name_hash']:]
|
||||
|
||||
name_hash = hexlify( name_hash_bin )
|
||||
update_hash = hexlify( update_hash_bin )
|
||||
|
||||
try:
|
||||
rc = update_sanity_test( None, name_hash, update_hash )
|
||||
if not rc:
|
||||
raise Exception("Invalid update data")
|
||||
except Exception, e:
|
||||
log.error("Invalid update data")
|
||||
return None
|
||||
|
||||
return {
|
||||
'opcode': 'NAME_UPDATE',
|
||||
'name_hash': name_hash,
|
||||
'update_hash': update_hash
|
||||
}
|
||||
|
||||
|
||||
def get_fees( inputs, outputs ):
|
||||
"""
|
||||
Given a transaction's outputs, look up its fees:
|
||||
* there should be two outputs: the OP_RETURN and change address
|
||||
|
||||
Return (dust fees, operation fees) on success
|
||||
Return (None, None) on invalid output listing
|
||||
"""
|
||||
if len(outputs) != 2:
|
||||
return (None, None)
|
||||
|
||||
# 0: op_return
|
||||
if not tx_output_is_op_return( outputs[0] ):
|
||||
return (None, None)
|
||||
|
||||
if outputs[0]["value"] != 0:
|
||||
return (None, None)
|
||||
|
||||
# 1: change address
|
||||
if script_hex_to_address( outputs[1]["script_hex"] ) is None:
|
||||
return (None, None)
|
||||
|
||||
dust_fee = (len(inputs) + 1) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE
|
||||
op_fee = 0
|
||||
|
||||
return (dust_fee, op_fee)
|
||||
|
||||
|
||||
def serialize( nameop ):
|
||||
"""
|
||||
Convert the set of data obtained from parsing the update into a unique string.
|
||||
"""
|
||||
|
||||
return NAME_UPDATE + ":" + str(nameop['name_hash']) + "," + str(nameop['update_hash'])
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user