WIP: move to a state-transition API that doesn't force the state logic

to interface with the blockchain or blockchain services.  At the same
time, deprecate support for pybitcoin in favor of primitives that now
live in virtualchain.
This commit is contained in:
Jude Nelson
2016-06-06 15:24:25 -04:00
parent bc6263fa06
commit e12b699dc1
10 changed files with 503 additions and 202 deletions

View File

@@ -21,20 +21,24 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key
import virtualchain
from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \
calculate_change_amount, make_pay_to_address_script
from keylib import ECPrivateKey, ECPublicKey
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 ..blockchain import get_tx_inputs
# consensus hash fields (none for announcements)
FIELDS = []
def build(message_hash, testset=False):
def build(message_hash):
"""
Record format:
@@ -53,7 +57,7 @@ def build(message_hash, testset=False):
readable_script = "ANNOUNCE 0x%s" % (message_hash)
hex_script = blockstack_script_to_hex(readable_script)
packaged_script = add_magic_bytes(hex_script, testset=testset)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
@@ -89,6 +93,40 @@ def make_outputs( data, inputs, change_address, pay_fee=True ):
]
def state_transition(blockchain_name, message_hash, private_key, user_public_key=None):
# sanity check
pay_fee = True
if user_public_key is not None:
pay_fee = False
if user_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
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")
pubk = None
if user_public_key is not None:
# subsidizing
pubk = ECPublicKey( user_public_key )
else:
# ordering directly
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_tx_inputs( blockchain_name, from_address )
nulldata = build(message_hash)
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
return inputs, outputs
def broadcast(message_hash, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, user_public_key=None, tx_only=False):
# sanity check
@@ -112,35 +150,30 @@ def broadcast(message_hash, private_key, blockchain_client, testset=False, block
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
pubk = None
if user_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( user_public_key )
pubk = ECPublicKey( user_public_key )
from_address = pubk.address()
inputs = get_unspents( from_address, blockchain_client )
else:
# ordering directly
pubk = ECPrivateKey( private_key ).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(message_hash, testset=testset)
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
if tx_only:
unsigned_tx = serialize_transaction( inputs, outputs )
unsigned_tx = tx_serialize( inputs, outputs )
return {'unsigned_tx': unsigned_tx}
else:
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
signed_tx = tx_serialize_sign( inputs, outputs, private_key )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
response.update({'data': nulldata})
return response

View File

@@ -21,11 +21,13 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key
import virtualchain
from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \
calculate_change_amount, make_pay_to_address_script
from keylib import b58check_encode
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
@@ -33,8 +35,8 @@ 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
from ..blockchain import get_tx_inputs
from ..nameset import NAMEREC_FIELDS, get_namespace_from_name
# consensus hash fields (ORDER MATTERS!)
FIELDS = NAMEREC_FIELDS + [
@@ -59,7 +61,8 @@ def get_import_update_hash_from_outputs( outputs, 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')
@@ -69,6 +72,13 @@ def get_import_update_hash_from_outputs( outputs, recipient ):
ret = hexlify( b58check_decode( str(output_addresses[0]) ) )
break
"""
if output.type() != "data":
continue
if output.sender_id() != recipient:
ret = hexlify(b58check_decode( output.addresses()[0] ))
break
if ret is None:
raise Exception("No update hash found")
@@ -76,7 +86,7 @@ def get_import_update_hash_from_outputs( outputs, recipient ):
return ret
def build(name, testset=False):
def build(name):
"""
Takes in a name to import. Name must include the namespace ID.
@@ -96,7 +106,7 @@ def build(name, testset=False):
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)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
@@ -133,30 +143,46 @@ def make_outputs( data, inputs, recipient_address, sender_address, update_hash_b
]
def state_transition(name, recipient_address, update_hash, private_key):
blockchain_name = namespace_to_blockchain( get_namespace_from_name( name ) )
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_tx_inputs( blockchain_name, from_address )
# NAME_IMPORT outputs
nulldata = build(name)
update_hash_b58 = b58check_encode( unhexlify(update_hash) )
outputs = make_outputs(nulldata, inputs, recipient_address, from_address, update_hash_b58, format='hex')
return inputs, outputs
def broadcast(name, recipient_address, update_hash, private_key, blockchain_client, blockchain_broadcaster=None, tx_only=False, testset=False):
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)
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_unspents( from_address, blockchain_client )
# NAME_IMPORT outputs
nulldata = build(name, testset=testset)
update_hash_b58 = b58check_encode( unhexlify(update_hash) )
outputs = make_outputs(nulldata, inputs, recipient_address, from_address, update_hash_b58, format='hex')
if tx_only:
unsigned_tx = serialize_transaction( inputs, outputs )
unsigned_tx = tx_serialize( 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)
signed_tx = tx_serialize_sign( inputs, outputs, private_key )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
# response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
# response = {'success': True }
response.update({'data': nulldata})

View File

@@ -21,11 +21,12 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key, serialize_sign_and_broadcast
import virtualchain
from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \
calculate_change_amount, make_pay_to_address_script
from pybitcoin.transactions.outputs import calculate_change_amount
from keylib import ECPrivateKey
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
@@ -34,6 +35,7 @@ from ..b40 import b40_to_hex, bin_to_b40, is_b40
from ..config import *
from ..scripts import *
from ..hashing import hash_name
from ..blockchain import get_tx_inputs
# consensus hash fields (ORDER MATTERS!)
FIELDS = [
@@ -117,11 +119,40 @@ def make_outputs( data, inputs, change_addr, fee, pay_fee=True, format='bin' ):
"value": calculate_change_amount(inputs, bill, dust_fee)},
# burn address
{"script_hex": make_pay_to_address_script(BLOCKSTORE_BURN_ADDRESS),
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
"value": op_fee}
]
def state_transition( namespace_id, register_addr, consensus_hash, private_key, fee ):
"""
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.
"""
blockchain_name = virtualchain.namespace_to_blocokchain( namespace_id )
pk = ECPrivateKey( private_key )
pubk = pk.public_key()
from_address = pubk.address()
pubkey_hex = pubk.to_hex()
inputs = get_tx_outputs( blockchain_name, from_address )
# build custom outputs here
pubkey_hex = pk.public_key().to_hex()
script_pubkey = get_script_pubkey( pubkey_hex )
nulldata = build( namespace_id, script_pubkey, register_addr, consensus_hash )
outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex')
return inputs, outputs
def broadcast( namespace_id, register_addr, consensus_hash, private_key, blockchain_client, fee, pay_fee=True, tx_only=False, testset=False, blockchain_broadcaster=None ):
"""
Propagate a namespace.
@@ -134,26 +165,28 @@ def broadcast( namespace_id, register_addr, consensus_hash, private_key, blockch
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
pubkey_hex = BitcoinPrivateKey( private_key ).public_key().to_hex()
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_unspents( from_address, blockchain_client )
# build custom outputs here
pubkey_hex = ECPrivateKey( 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 )
unsigned_tx = tx_serialize( 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)
signed_tx = tx_serialize_sign( inputs, outputs, private_key )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
# response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
# response = {'success': True }
response.update({'data': nulldata})

View File

@@ -21,15 +21,19 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key
import virtualchain
from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \
calculate_change_amount, make_pay_to_address_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 ..blockchain import get_tx_inputs
import virtualchain
log = virtualchain.get_logger("blockstack-server")
from namespacereveal import FIELDS as NAMESPACE_REVEAL_FIELDS
@@ -66,6 +70,20 @@ def build( namespace_id, testset=False ):
return packaged_script
def state_transition( namespace_id, private_key ):
blockchain_name = namespace_to_blockchain( namespace_id )
nulldata = build( namespace_id )
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_tx_inputs( blockchain_name, from_address )
# OP_RETURN outputs
outputs = make_op_return_outputs( nulldata, inputs, from_address, fee=DEFAULT_OP_RETURN_FEE, format='hex' )
return inputs, outputs
def broadcast( namespace_id, private_key, blockchain_client, testset=False, tx_only=False, blockchain_broadcaster=None ):
if blockchain_broadcaster is None:
@@ -73,15 +91,16 @@ def broadcast( namespace_id, private_key, blockchain_client, testset=False, tx_o
nulldata = build( namespace_id, testset=testset )
# get inputs and from address
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_unspents( from_address, 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 )
unsigned_tx = tx_serialize( inputs, outputs )
return {'unsigned_tx': signed_tx}
else:

View File

@@ -21,11 +21,10 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key
import virtualchain
from virtualchain.lib.blockchain.bitcoin import make_op_return_script, \
calculate_change_amount, make_pay_to_address_script
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
@@ -36,8 +35,8 @@ 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
from ..blockchain import get_tx_inputs
import virtualchain
log = virtualchain.get_logger("blockstack-log")
# consensus hash fields (ORDER MATTERS!)
@@ -145,7 +144,7 @@ def namespacereveal_sanity_check( namespace_id, version, lifetime, coeff, base,
# 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 ):
def build( namespace_id, version, reveal_addr, lifetime, coeff, base, bucket_exponents, nonalpha_discount, no_vowel_discount ):
"""
Record to mark the beginning of a namespace import in the blockchain.
This reveals the namespace ID, and encodes the preorder's namespace rules.
@@ -196,12 +195,12 @@ def build( namespace_id, version, reveal_addr, lifetime, coeff, base, bucket_exp
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)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
def make_outputs( data, inputs, reveal_addr, change_addr, format='bin', testset=False ):
def make_outputs( data, inputs, reveal_addr, change_addr, format='bin' ):
"""
Make outputs for a register:
[0] OP_RETURN with the name
@@ -227,6 +226,33 @@ def make_outputs( data, inputs, reveal_addr, change_addr, format='bin', testset=
def state_transition( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, private_key ):
"""
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
"""
blockchain_name = virtualchain.namespace_to_blockchain(namespace_id)
nulldata = build( namespace_id, BLOCKSTACK_VERSION, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount )
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_tx_inputs( blockchain_name, from_address )
# build custom outputs here
outputs = make_outputs(nulldata, inputs, reveal_addr, from_address, format='hex')
return inputs, outputs
def broadcast( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exponents, nonalpha_discount, no_vowel_discount, private_key, blockchain_client, tx_only=False, blockchain_broadcaster=None, testset=False ):
"""
Propagate a namespace.
@@ -247,19 +273,22 @@ def broadcast( namespace_id, reveal_addr, lifetime, coeff, base_cost, bucket_exp
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)
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_unspents( from_address, 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 )
unsigned_tx = tx_serialize( 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)
signed_tx = tx_serialize_sign( inputs, outputs, private_key )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
# response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
# response = {'success': True }
response.update({'data': nulldata})

View File

@@ -21,14 +21,12 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key, serialize_sign_and_broadcast
import virtualchain
from virtualchain.lib.blockchain.bitcoin import tx_serialize, script_hex_to_address, make_op_return_script, \
calculate_change_amount, make_pay_to_address_script
from pybitcoin.transactions.outputs import calculate_change_amount
from keylib import ECPrivateKey, ECPublicKey
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
@@ -36,6 +34,8 @@ from ..b40 import b40_to_hex, is_b40
from ..config import *
from ..scripts import *
from ..hashing import hash_name
from ..blockchain import get_tx_inputs
from ..nameset import get_namespace_from_name
# consensus hash fields (ORDER MATTERS!)
FIELDS = [
@@ -52,7 +52,7 @@ FIELDS = [
'op_fee', # blockstack fee (sent to burn address)
]
def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None, testset=False):
def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None):
"""
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
@@ -82,7 +82,7 @@ def build(name, script_pubkey, register_addr, consensus_hash, name_hash=None, te
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)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
@@ -111,11 +111,46 @@ def make_outputs( data, inputs, sender_addr, fee, format='bin' ):
"value": calculate_change_amount(inputs, bill, dust_fee)},
# burn address
{"script_hex": make_pay_to_address_script(BLOCKSTORE_BURN_ADDRESS),
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
"value": op_fee}
]
def state_transition(name, private_key, register_addr, consensus_hash, fee, subsidy_public_key=None):
"""
Builds and broadcasts a preorder transaction.
@subsidy_public_key: if given, the public part of the subsidy key
"""
namespace_id = get_namespace_from_name( name )
blockchain_name = namespace_to_blockchain( namespace_id )
# sanity check
if subsidy_public_key is None and private_key is None:
raise Exception("Missing both client public and private key")
pubk = None
script_pubkey = None # to be mixed into preorder hash
if subsidy_public_key is not None:
# subsidizing
pubk = ECPublicKey( subsidy_public_key )
script_pubkey = get_script_pubkey( subsidy_public_key )
else:
# ordering directly
pubk = ECPrivateKey( private_key ).public_key()
script_pubkey = get_script_pubkey( public_key )
from_address = pubk.address()
inputs = get_tx_inputs( blockchain_name, from_address )
nulldata = build( name, script_pubkey, register_addr, consensus_hash, testset=testset)
outputs = make_outputs(nulldata, inputs, from_address, fee, format='hex')
return inputs, outputs
def broadcast(name, private_key, register_addr, consensus_hash, blockchain_client, fee, blockchain_broadcaster=None, subsidy_public_key=None, tx_only=False, testset=False):
"""
Builds and broadcasts a preorder transaction.
@@ -134,40 +169,35 @@ def broadcast(name, private_key, register_addr, consensus_hash, blockchain_clien
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None # change address
inputs = None
private_key_obj = None
pubk = None
script_pubkey = None # to be mixed into preorder hash
if subsidy_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( subsidy_public_key )
from_address = BitcoinPublicKey( subsidy_public_key ).address()
inputs = get_unspents( from_address, blockchain_client )
pubk = ECPublicKey( subsidy_public_key )
script_pubkey = get_script_pubkey( subsidy_public_key )
else:
# ordering directly
pubk = BitcoinPrivateKey( private_key ).public_key()
public_key = pubk.to_hex()
pubk = ECPrivateKey( private_key ).public_key()
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)
from_address = pubk.address()
inputs = get_unspents( from_address, 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 )
unsigned_tx = tx_serialize( 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)
signed_tx = tx_serialize_sign( inputs, outputs, private_key )
response = broadcast_transaction( inputs, outputs, blockchain_broadcaster )
# response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
response.update({'data': nulldata})
return response
@@ -225,7 +255,7 @@ def get_fees( inputs, outputs ):
log.error("outputs[2] has no decipherable burn address")
return (None, None)
if addr_hash != BLOCKSTORE_BURN_ADDRESS:
if addr_hash != BLOCKSTACK_BURN_ADDRESS:
log.error("outputs[2] is not the burn address")
return (None, None)

View File

@@ -21,12 +21,12 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key
import virtualchain
from virtualchain.lib.blockchain.bitcoin import tx_serialize, script_hex_to_address, make_op_return_script, \
calculate_change_amount, make_pay_to_address_script
from pybitcoin.transactions.outputs import calculate_change_amount
from keylib import ECPrivateKey, ECPublicKey
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
@@ -34,9 +34,9 @@ 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
from ..nameset import NAMEREC_FIELDS, get_namespace_from_name
from ..blockchain import get_tx_inputs
import virtualchain
log = virtualchain.get_logger("blockstack-server")
# consensus hash fields (ORDER MATTERS!)
@@ -61,6 +61,7 @@ def get_registration_recipient_from_outputs( outputs ):
ret = None
for output in outputs:
"""
output_script = output['scriptPubKey']
output_asm = output_script.get('asm')
output_hex = output_script.get('hex')
@@ -72,6 +73,12 @@ def get_registration_recipient_from_outputs( outputs ):
# ret = (output_hex, output_addresses[0])
ret = output_hex
break
"""
if output.type() != "data":
continue
ret = output.sender_id()
break
if ret is None:
raise Exception("No registration address found")
@@ -79,7 +86,7 @@ def get_registration_recipient_from_outputs( outputs ):
return ret
def build(name, testset=False):
def build(name):
"""
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.
@@ -97,7 +104,7 @@ def build(name, testset=False):
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)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
@@ -168,12 +175,46 @@ def make_outputs( data, change_inputs, register_addr, change_addr, renewal_fee=N
outputs.append(
# burn address (when renewing)
{"script_hex": make_pay_to_address_script(BLOCKSTORE_BURN_ADDRESS),
{"script_hex": make_pay_to_address_script(BLOCKSTACK_BURN_ADDRESS),
"value": op_fee}
)
return outputs
def state_transition(name, private_key, register_addr, renewal_fee=None, user_public_key=None, subsidy_public_key=None):
namespace_id = get_namespace_from_name( name )
blockchain_name = namespace_to_blockchain( namespace_id )
if subsidy_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
subsidized_renewal = False
if subsidy_public_key is not None:
# subsidizing
if user_public_key is not None and renewal_fee is not None:
# renewing, and subsidizing the renewal
pubk = ECPublicKey( user_public_key )
subsidized_renewal = True
else:
# registering or renewing under the subsidy key
pubk = ECPublicKey( subsidy_public_key )
elif private_key is not None:
# ordering directly
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
unspents = get_tx_inputs( blockchain_name, from_address )
nulldata = build(name)
outputs = make_outputs(nulldata, change_inputs, register_addr, from_address, renewal_fee=renewal_fee, pay_fee=(not subsidized_renewal), format='hex')
return inputs, outputs
def broadcast(name, private_key, register_addr, blockchain_client, renewal_fee=None, blockchain_broadcaster=None, tx_only=False, user_public_key=None, subsidy_public_key=None, testset=False):
@@ -190,47 +231,42 @@ def broadcast(name, private_key, register_addr, blockchain_client, renewal_fee=N
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()
pubk = ECPublicKey( user_public_key )
subsidized_renewal = True
else:
# registering or renewing under the subsidy key
from_address = pubk.address()
change_inputs = get_unspents( from_address, blockchain_client )
pubk = ECPublicKey( subsidy_public_key )
elif private_key is not None:
# ordering directly
pubk = BitcoinPrivateKey( private_key ).public_key()
public_key = pubk.to_hex()
# get inputs and from address using private key
private_key_obj, from_address, change_inputs = analyze_private_key(private_key, blockchain_client)
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
unspents = get_unspents( from_ddress, 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 )
unsigned_tx = tx_serialize( 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)
signed_tx = tx_serialize_sign( change_inputs, outputs, private_key )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
# response = serialize_sign_and_broadcast(change_inputs, outputs, private_key_obj, blockchain_broadcaster)
response.update({'data': nulldata})
return response
@@ -295,7 +331,7 @@ def get_fees( inputs, outputs ):
if addr_hash is None:
return (None, None)
if addr_hash != BLOCKSTORE_BURN_PUBKEY_HASH:
if addr_hash != BLOCKSTACK_BURN_PUBKEY_HASH:
return (None, None)
dust_fee = (len(inputs) + 3) * DEFAULT_DUST_FEE + DEFAULT_OP_RETURN_FEE

View File

@@ -21,23 +21,25 @@
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 blockstack_utxo import get_unspents, broadcast_transaction, analyze_private_key
import virtualchain
from virtualchain.lib.blockchain.bitcoin import tx_serialize, script_hex_to_address, make_op_return_script
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
from keylib import ECPublicKey, ECPrivateKey
from ..b40 import b40_to_hex, bin_to_b40, is_b40
from ..config import *
from ..scripts import *
from ..nameset import NAMEREC_FIELDS
from ..blockchain import get_tx_inputs
from ..nameset import NAMEREC_FIELDS, get_namespace_from_name
# consensus hash fields (ORDER MATTERS!)
FIELDS = NAMEREC_FIELDS
def build(name, testset=False):
def build(name):
"""
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.
@@ -55,7 +57,7 @@ def build(name, testset=False):
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)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
@@ -91,8 +93,40 @@ def make_outputs( data, inputs, change_address, pay_fee=True ):
]
def broadcast(name, private_key, blockchain_client, testset=False, blockchain_broadcaster=None, user_public_key=None, tx_only=False):
def state_transition(name, private_key, user_public_key=None):
namespace_id = get_namespace_from_name( name )
blockchain_name = namespace_to_blockchain( namespace_id )
# sanity check
pay_fee = True
if user_public_key is not None:
pay_fee = False
pubk = None
if user_public_key is not None:
# subsidizing
pubk = ECPublicKey( user_public_key )
else:
# ordering directly
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_unspents( from_address, blockchain_client )
nulldata = build(name, testset=testset)
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
return inputs, outputs
def broadcast(name, private_key, user_public_key=None):
namespace_id = get_namespace_from_name( name )
blockchain_name = namespace_to_blockchain( namespace_id )
# sanity check
pay_fee = True
if user_public_key is not None:
@@ -108,30 +142,25 @@ def broadcast(name, private_key, blockchain_client, testset=False, blockchain_br
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
pubk = None
if user_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( user_public_key )
pubk = ECPublicKey( user_public_key )
from_address = pubk.address()
inputs = get_unspents( from_address, blockchain_client )
elif private_key is not None:
else:
# ordering directly
pubk = BitcoinPrivateKey( private_key ).public_key()
public_key = pubk.to_hex()
pubk = ECPrivateKey( private_key ).public_key()
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
from_address = pubk.address()
inputs = get_unspents( from_address, 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 )
unsigned_tx = tx_serialize( inputs, outputs )
return {'unsigned_tx': unsigned_tx}
else:

View File

@@ -21,20 +21,24 @@
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 keylib import ECPrivateKey, ECPublicKey
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
from blockstack_utxo import analyze_private_key, get_unspents, broadcast_transaction
import virtualchain.lib.blockchain.bitcoin as virtualchain_bitcoin
from virtualchain_bitcoin import make_pay_to_address_script, \
make_op_return_script, script_hex_to_address, calculate_change_amount, \
calculate_change_amount, tx_serialize
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
from ..nameset import NAMEREC_FIELDS, get_namespace_from_name
from ..blockchain import get_tx_inputs
# consensus hash fields (ORDER MATTERS!)
FIELDS = NAMEREC_FIELDS + [
@@ -56,7 +60,8 @@ def get_transfer_recipient_from_outputs( outputs ):
ret = None
for output in outputs:
"""
output_script = output['scriptPubKey']
output_asm = output_script.get('asm')
output_hex = output_script.get('hex')
@@ -66,6 +71,12 @@ def get_transfer_recipient_from_outputs( outputs ):
ret = output_hex
break
"""
if output.type() != "data":
break
ret = output.sender_id()
break
if ret is None:
raise Exception("No recipients found")
@@ -90,7 +101,7 @@ def transfer_sanity_check( name, consensus_hash ):
return True
def build(name, keepdata, consensus_hash, testset=False):
def build(name, keepdata, consensus_hash):
"""
Takes in a name to transfer. Name must include the namespace ID, but not the scheme.
@@ -118,7 +129,7 @@ def build(name, keepdata, consensus_hash, testset=False):
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)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
@@ -153,6 +164,37 @@ def make_outputs( data, inputs, new_name_owner_address, change_address, pay_fee=
]
def state_transition(name, destination_address, keepdata, consensus_hash, private_key, user_public_key=None):
namespace_id = get_namespace_from_name(name)
blockchain_name = namespace_to_blockchain( namespace_id )
# sanity check
pay_fee = True
if user_public_key is not None:
pay_fee = False
if user_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
pubk = None
if user_public_key is not None:
# subsidizing
pubk = ECPublicKey( user_public_key )
else:
# ordering directly
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_tx_inputs( blockchain_name, from_address )
nulldata = build(name, keepdata, consensus_hash )
outputs = make_outputs(nulldata, inputs, destination_address, from_address, pay_fee=pay_fee, format='hex')
return inputs, outputs
def broadcast(name, destination_address, keepdata, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, tx_only=False, user_public_key=None, testset=False):
# sanity check
@@ -169,37 +211,33 @@ def broadcast(name, destination_address, keepdata, consensus_hash, private_key,
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
pubk = None
if user_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( user_public_key )
pubk = ECPublicKey( user_public_key )
from_address = pubk.address()
inputs = get_unspents( from_address, blockchain_client )
elif private_key is not None:
else:
# ordering directly
pubk = BitcoinPrivateKey( private_key ).public_key()
public_key = pubk.to_hex()
# get inputs and from address using private key
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_unspents( from_address, 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 )
unsigned_tx = tx_serialize( 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)
signed_tx = tx_serialize_sign( inputs, outputs, private_key )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
# response = serialize_sign_and_broadcast(inputs, outputs, private_key_obj, blockchain_broadcaster)
response.update({'data': nulldata})
return response

View File

@@ -21,10 +21,8 @@
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 blockstack_utxo import get_unspents, broadcast_transaction
from keylib import ECPrivateKey, ECPublicKey
from utilitybelt import is_hex
from binascii import hexlify, unhexlify
@@ -33,9 +31,13 @@ from ..config import *
from ..scripts import *
from ..hashing import hash256_trunc128
from ..nameset import NAMEREC_FIELDS
from ..nameset import NAMEREC_FIELDS, get_namespace_from_name
from ..blockchain import get_tx_inputs
import virtualchain
from virtualchain import hex_hash160
from virtualchain.lib.blockchain.bitcoin import script_hex_to_address, make_op_return_script, tx_serialize, tx_serialize_and_sign, tx_output_is_op_return
log = virtualchain.get_logger("blockstack-server")
# consensus hash fields (ORDER MATTERS!)
@@ -64,7 +66,7 @@ def update_sanity_test( name, consensus_hash, data_hash ):
return True
def build(name, consensus_hash, data_hash=None, testset=False):
def build(name, consensus_hash, data_hash=None):
"""
Takes in the name to update the data for and the data update itself.
Name must include the namespace ID, but not the scheme.
@@ -84,7 +86,7 @@ def build(name, consensus_hash, data_hash=None, testset=False):
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)
packaged_script = add_magic_bytes(hex_script)
return packaged_script
@@ -120,6 +122,40 @@ def make_outputs( data, inputs, change_address, pay_fee=True ):
]
def state_transition(name, data_hash, consensus_hash, private_key, user_public_key=None):
"""
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.
"""
namespace_id = get_namespace_from_name(name)
blockchain_name = namespace_to_blockchain( namespace_id )
# sanity check
pay_fee = True
if user_public_key is not None:
pay_fee = False
if user_public_key is None and private_key is None:
raise Exception("Missing both public and private key")
pubk = None
if user_public_key is not None:
# subsidizing
pubk = ECPublicKey( user_public_key )
else:
# ordering directly
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_tx_inputs( blockchain_name, from_address )
nulldata = build(name, consensus_hash, data_hash=data_hash)
outputs = make_outputs( nulldata, inputs, from_address, pay_fee=pay_fee )
return inputs, outputs
def broadcast(name, data_hash, consensus_hash, private_key, blockchain_client, blockchain_broadcaster=None, tx_only=False, user_public_key=None, testset=False):
"""
Write a name update into the blockchain.
@@ -141,37 +177,29 @@ def broadcast(name, data_hash, consensus_hash, private_key, blockchain_client, b
if blockchain_broadcaster is None:
blockchain_broadcaster = blockchain_client
from_address = None
inputs = None
private_key_obj = None
pubk = None
if user_public_key is not None:
# subsidizing
pubk = BitcoinPublicKey( user_public_key )
from_address = pubk.address()
# get inputs from utxo provider
inputs = get_unspents( from_address, blockchain_client )
pubk = ECPublicKey( user_public_key )
elif private_key is not None:
else:
# ordering directly
pubk = BitcoinPrivateKey( private_key ).public_key()
public_key = pubk.to_hex()
# get inputs and from address using private key
private_key_obj, from_address, inputs = analyze_private_key(private_key, blockchain_client)
pubk = ECPrivateKey( private_key ).public_key()
from_address = pubk.address()
inputs = get_unspents( from_address, 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 )
unsigned_tx = tx_serialize( inputs, outputs )
return {'unsigned_tx': unsigned_tx}
else:
signed_tx = tx_serialize_and_sign( inputs, outputs, private_key_obj )
signed_tx = tx_serialize_sign( inputs, outputs, private_key )
response = broadcast_transaction( signed_tx, blockchain_broadcaster )
response.update({'data': nulldata})
return response