Merge branch 'rc-0.14.3' of https://github.com/blockstack/blockstack-core into rc-0.14.3

This commit is contained in:
Jude Nelson
2017-07-05 17:40:03 -04:00
25 changed files with 3142 additions and 605 deletions

View File

@@ -29,7 +29,7 @@ import json
current_dir = os.path.abspath(os.path.dirname(__file__))
parent_dir = os.path.abspath(current_dir + "/../")
from ..constants import TX_EXPIRED_INTERVAL, TX_CONFIRMATIONS_NEEDED, TX_MIN_CONFIRMATIONS
from ..constants import TX_EXPIRED_INTERVAL, DEFAULT_TX_CONFIRMATIONS_NEEDED, TX_MIN_CONFIRMATIONS
from ..constants import MAXIMUM_NAMES_PER_ADDRESS
from ..constants import BLOCKSTACK_TEST, BLOCKSTACK_DRY_RUN
from ..constants import CONFIG_PATH, BLOCKSTACK_DEBUG
@@ -154,12 +154,12 @@ def get_tx_fee( tx_hex, config_path=CONFIG_PATH ):
return fee_per_byte * len(tx_hex)/2
def is_tx_accepted( tx_hash, num_needed=TX_CONFIRMATIONS_NEEDED, config_path=CONFIG_PATH ):
def is_tx_accepted( tx_hash, num_needed=DEFAULT_TX_CONFIRMATIONS_NEEDED, config_path=CONFIG_PATH ):
"""
Determine whether or not a transaction was accepted.
"""
tx_confirmations = get_tx_confirmations(tx_hash, config_path=config_path)
if tx_confirmations > num_needed:
if tx_confirmations >= num_needed:
return True
return False

View File

@@ -30,7 +30,7 @@ import keylib
current_dir = os.path.abspath(os.path.dirname(__file__))
parent_dir = os.path.abspath(current_dir + "/../")
from .queue import in_queue, queue_append, queue_findone
from .queue import in_queue, queue_append, queue_findone, extract_entry
from .blockchain import get_tx_confirmations
from .blockchain import get_utxos, get_tx_fee_per_byte
@@ -1280,14 +1280,16 @@ def do_preorder( fqu, payment_privkey_info, owner_privkey_info, cost_satoshis, u
return resp
def do_register( fqu, payment_privkey_info, owner_privkey_info, utxo_client, tx_broadcaster, tx_fee=None, config_path=CONFIG_PATH,
proxy=None, dry_run=BLOCKSTACK_DRY_RUN, safety_checks=True ):
def do_register( fqu, payment_privkey_info, owner_privkey_info, utxo_client, tx_broadcaster, tx_fee=None,
config_path=CONFIG_PATH, proxy=None, dry_run=BLOCKSTACK_DRY_RUN, safety_checks=True,
force_register = False ):
"""
Register a name
payment_privkey_info or payment_address is required.
utxo_client or payment_utxos is required.
force_register still performs SOME safety checks (payment)
Return {'status': True, 'transaction_hash': ...} on success
Return {'status': True, 'tx': ...} if no private key is given, or dry_run is True.
@@ -1314,9 +1316,12 @@ def do_register( fqu, payment_privkey_info, owner_privkey_info, utxo_client, tx_
if not dry_run and (safety_checks or tx_fee is None):
# find tx fee, and do sanity checks
res = check_register(fqu, owner_privkey_info, payment_privkey_info, config_path=config_path, proxy=proxy, min_payment_confs=min_confirmations)
res = check_register(fqu, owner_privkey_info, payment_privkey_info,
config_path=config_path, proxy=proxy, min_payment_confs=min_confirmations,
force_it = force_register)
if 'error' in res and safety_checks:
log.error("Failed to check register: {}".format(res['error']))
return res
tx_fee = res['tx_fee']
@@ -1355,13 +1360,15 @@ def do_register( fqu, payment_privkey_info, owner_privkey_info, utxo_client, tx_
return resp
def do_update( fqu, zonefile_hash, owner_privkey_info, payment_privkey_info, utxo_client, tx_broadcaster, tx_fee_per_byte=None,
config_path=CONFIG_PATH, proxy=None, consensus_hash=None, dry_run=BLOCKSTACK_DRY_RUN, safety_checks=True ):
def do_update( fqu, zonefile_hash, owner_privkey_info, payment_privkey_info, utxo_client, tx_broadcaster,
tx_fee_per_byte=None, config_path=CONFIG_PATH, proxy=None, consensus_hash=None,
dry_run=BLOCKSTACK_DRY_RUN, safety_checks=True, force_update = False ):
"""
Put a new zonefile hash for a name.
utxo_client must be given, or UTXO lists for both owner and payment private keys must be given.
If private key(s) are missing, then dry_run must be True.
force_update skips only some safety checks (but still checks payment)
Return {'status': True, 'transaction_hash': ..., 'value_hash': ...} on success (if dry_run is False)
return {'status': True, 'tx': ..., 'value_hash': ...} on success (if dry_run is True)
@@ -1387,7 +1394,9 @@ def do_update( fqu, zonefile_hash, owner_privkey_info, payment_privkey_info, utx
if not dry_run and (safety_checks or tx_fee_per_byte is None):
# find tx fee, and do sanity checks
res = check_update(fqu, owner_privkey_info, payment_privkey_info, config_path=config_path, proxy=proxy, min_payment_confs=min_confirmations)
res = check_update(fqu, owner_privkey_info, payment_privkey_info,
config_path=config_path, proxy=proxy, min_payment_confs=min_confirmations,
force_it = force_update)
if 'error' in res and safety_checks:
log.error("Failed to check update: {}".format(res['error']))
return res
@@ -2099,6 +2108,13 @@ def async_preorder(fqu, payment_privkey_info, owner_privkey_info, cost, name_dat
log.exception(e)
return {'error': 'Failed to sign and broadcast preorder transaction'}
additionals = {}
if 'unsafe_reg' in name_data:
log.debug("Adding an *aggressive* preorder for {}".format(fqu))
additionals['unsafe_reg'] = name_data['unsafe_reg']
additionals['confirmations_needed'] = 4
if 'min_payment_confs' in name_data:
additionals['min_payment_confs'] = name_data['min_payment_confs']
if 'transaction_hash' in resp:
if not BLOCKSTACK_DRY_RUN:
# watch this preorder, and register it when it gets queued
@@ -2109,7 +2125,7 @@ def async_preorder(fqu, payment_privkey_info, owner_privkey_info, cost, name_dat
zonefile_data=name_data.get('zonefile'),
token_file=name_data.get('token_file'),
config_path=config_path,
path=queue_path)
path=queue_path, **additionals)
else:
assert 'error' in resp
log.error("Error preordering: %s with %s for %s" % (fqu, payment_address, owner_address))
@@ -2137,7 +2153,12 @@ def async_register(fqu, payment_privkey_info, owner_privkey_info, name_data={},
if proxy is None:
proxy = get_default_proxy(config_path=config_path)
utxo_client = get_utxo_provider_client(config_path=config_path)
if 'min_payment_confs' in name_data:
utxo_client = get_utxo_provider_client( config_path=config_path,
min_confirmations=name_data['min_payment_confs'] )
else:
utxo_client = get_utxo_provider_client(config_path=config_path)
tx_broadcaster = get_tx_broadcaster( config_path=config_path )
owner_address = virtualchain.get_privkey_address( owner_privkey_info )
@@ -2167,9 +2188,20 @@ def async_register(fqu, payment_privkey_info, owner_privkey_info, name_data={},
return {'error': 'Waiting on preorder confirmations'}
# configure registrar with information from the preorder
additionals = {}
force_register = False
if 'unsafe_reg' in name_data:
log.debug("Adding an *aggressive* register for {}".format(fqu))
additionals['unsafe_reg'] = name_data['unsafe_reg']
additionals['confirmations_needed'] = 1
force_register = True
if 'min_payment_confs' in name_data:
additionals['min_payment_confs'] = name_data['min_payment_confs']
try:
resp = do_register( fqu, payment_privkey_info, owner_privkey_info, utxo_client, tx_broadcaster,
config_path=config_path, proxy=proxy )
config_path=config_path, proxy=proxy, force_register = force_register )
except Exception, e:
log.exception(e)
return {'error': 'Failed to sign and broadcast registration transaction'}
@@ -2183,7 +2215,7 @@ def async_register(fqu, payment_privkey_info, owner_privkey_info, name_data={},
zonefile_data=name_data.get('zonefile'),
token_file=name_data.get('token_file'),
config_path=config_path,
path=queue_path)
path=queue_path, **additionals)
return resp
@@ -2236,7 +2268,19 @@ def async_update(fqu, zonefile_data, token_file, owner_privkey_info, payment_pri
if proxy is None:
proxy = get_default_proxy(config_path=config_path)
utxo_client = get_utxo_provider_client(config_path=config_path)
# Are we the result of a register? If so, use it to configure our transaction
register_entry = queue_findone( "register", fqu, path=queue_path )
if len(register_entry) == 0:
register_data = {}
else:
register_data = extract_entry( register_entry[0] )
if 'min_payment_confs' in register_data:
utxo_client = get_utxo_provider_client( config_path=config_path,
min_confirmations=register_data['min_payment_confs'] )
else:
utxo_client = get_utxo_provider_client(config_path=config_path)
tx_broadcaster = get_tx_broadcaster(config_path=config_path)
owner_address = virtualchain.get_privkey_address( owner_privkey_info )
@@ -2245,13 +2289,24 @@ def async_update(fqu, zonefile_data, token_file, owner_privkey_info, payment_pri
log.error("Already in update queue: %s" % fqu)
return {'error': 'Already in update queue'}
# configure any additional information about the registrar entry.
additionals = {}
force_update = True
if 'unsafe_reg' in register_data:
log.debug("Adding an *aggressive* update for {}".format(fqu))
additionals['unsafe_reg'] = register_data['unsafe_reg']
additionals['confirmations_needed'] = 1
force_update = True
resp = {}
try:
resp = do_update( fqu, zonefile_hash, owner_privkey_info, payment_privkey_info, utxo_client, tx_broadcaster,
config_path=config_path, proxy=proxy )
config_path=config_path, proxy=proxy, force_update=force_update )
except Exception, e:
log.exception(e)
return {'error': 'Failed to sign and broadcast update transaction'}
return { 'error':
'Failed to sign and broadcast update transaction. Exception: {}'.format(e) }
if 'transaction_hash' in resp:
if not BLOCKSTACK_DRY_RUN:
@@ -2262,7 +2317,7 @@ def async_update(fqu, zonefile_data, token_file, owner_privkey_info, payment_pri
owner_address=owner_address,
transfer_address=name_data.get('transfer_address'),
config_path=config_path,
path=queue_path)
path=queue_path, **additionals)
resp['zonefile_hash'] = zonefile_hash
return resp

View File

@@ -317,7 +317,9 @@ def in_queue( queue_id, fqu, path=DEFAULT_QUEUE_PATH ):
def queue_append(queue_id, fqu, tx_hash, payment_address=None,
owner_address=None, transfer_address=None,
config_path=CONFIG_PATH, block_height=None,
zonefile_data=None, token_file=None, zonefile_hash=None, path=DEFAULT_QUEUE_PATH):
zonefile_data=None, token_file=None, zonefile_hash=None,
unsafe_reg=None, confirmations_needed=None,
min_payment_confs=None, path=DEFAULT_QUEUE_PATH):
"""
Append a processing name operation to the named queue for the given name.
@@ -340,6 +342,13 @@ def queue_append(queue_id, fqu, tx_hash, payment_address=None,
new_entry['owner_address'] = owner_address
new_entry['transfer_address'] = transfer_address
if unsafe_reg is not None:
new_entry['unsafe_reg'] = unsafe_reg
if confirmations_needed is not None:
new_entry['confirmations_needed'] = confirmations_needed
if min_payment_confs is not None:
new_entry['min_payment_confs'] = min_payment_confs
if zonefile_data is not None:
new_entry['zonefile_b64'] = base64.b64encode(zonefile_data)
@@ -380,7 +389,13 @@ def is_entry_accepted( entry, config_path=CONFIG_PATH ):
Return True if so.
Return False on error.
"""
return is_tx_accepted( entry['tx_hash'], config_path=config_path )
if 'confirmations_needed' in entry:
log.debug('Custom confirmations check on {} with {}'.format(
entry['tx_hash'], entry['confirmations_needed']))
return is_tx_accepted( entry['tx_hash'], num_needed = entry['confirmations_needed'],
config_path=config_path )
else:
return is_tx_accepted( entry['tx_hash'], config_path=config_path )
def is_preorder_expired( entry, config_path=CONFIG_PATH ):
@@ -447,7 +462,6 @@ def queue_findall( queue_id, limit=None, path=DEFAULT_QUEUE_PATH ):
"""
return get_queue_state( queue_id, limit=limit, path=path )
def queue_removeall( entries, path=DEFAULT_QUEUE_PATH ):
"""
Remove all given entries form their given queues

View File

@@ -56,6 +56,7 @@ from ..user import is_user_zonefile
from ..storage import put_mutable_data, get_zonefile_data_hash
from ..constants import CONFIG_PATH, DEFAULT_QUEUE_PATH, BLOCKSTACK_DEBUG, BLOCKSTACK_TEST, TX_MIN_CONFIRMATIONS
from ..constants import PREORDER_CONFIRMATIONS
from ..config import get_config
from ..utils import url_to_host_port
from ..logger import get_logger
@@ -190,8 +191,10 @@ class RegistrarWorker(threading.Thread):
# send the registration
log.debug('Send async register for {}'.format(name_data['fqu']))
log.debug("async_register({}, zonefile={}, token_file={}, transfer_address={})".format(name_data['fqu'], name_data.get('zonefile'), name_data.get('token_file'), name_data.get('transfer_address')))
res = async_register( name_data['fqu'], payment_privkey_info, owner_privkey_info, name_data=name_data,
proxy=proxy, config_path=config_path, queue_path=queue_path )
res = async_register( name_data['fqu'], payment_privkey_info, owner_privkey_info,
name_data=name_data, proxy=proxy, config_path=config_path,
queue_path=queue_path )
return res
else:
# already queued
@@ -279,8 +282,10 @@ class RegistrarWorker(threading.Thread):
log.debug("Register for '%s' (%s) is confirmed!" % (register['fqu'], register['tx_hash']))
res = cls.set_zonefile( register, proxy=proxy, queue_path=queue_path, config_path=config_path )
if 'error' in res:
log.error("Failed to make name token file for %s: %s" % (register['fqu'], res['error']))
ret = {'error': 'Failed to set up name token file'}
queue_add_error_msg('register', register['fqu'], res['error'], path=queue_path)
log.error("Failed to make name profile for %s: %s" % (register['fqu'], res['error']))
ret = {'error': 'Failed to set up name profile'}
else:
# success!
@@ -368,6 +373,7 @@ class RegistrarWorker(threading.Thread):
else:
log.error("Failed to register preordered name %s: %s" % (preorder['fqu'], res['error']))
queue_add_error_msg('preorder', preorder['fqu'], res['error'], path=queue_path)
ret = {'error': 'Failed to preorder a name'}
else:
@@ -426,6 +432,8 @@ class RegistrarWorker(threading.Thread):
# use it to remember what we've replicated, so we don't needlessly retry
name_rec = get_name_blockchain_record( name_data['fqu'], proxy=proxy )
if 'error' in name_rec:
if name_rec['error'] == 'Not found.':
return {'error' : 'Name has not appeared on the resolver, cannot issue zonefile until it does.'}
return name_rec
if BLOCKSTACK_TEST:
@@ -516,6 +524,7 @@ class RegistrarWorker(threading.Thread):
res = cls.replicate_name_data( update, atlas_servers, wallet_data, storage_drivers, config_path, proxy=proxy )
if 'error' in res:
log.error("Failed to update %s: %s" % (update['fqu'], res['error']))
queue_add_error_msg('update', update['fqu'], res['error'], path=queue_path)
ret = {'error': 'Failed to finish an update'}
failed_names.append( update['fqu'] )
@@ -1132,7 +1141,7 @@ def get_wallet(config_path=None, proxy=None):
return data
def preorder(fqu, cost_satoshis, zonefile_data, token_file, transfer_address, min_payment_confs, proxy=None, config_path=CONFIG_PATH):
def preorder(fqu, cost_satoshis, zonefile_data, token_file, transfer_address, min_payment_confs, proxy=None, config_path=CONFIG_PATH, unsafe_reg=False):
"""
Send preorder transaction and enter it in queue.
Queue up additional state so we can update and transfer it as well.
@@ -1144,11 +1153,8 @@ def preorder(fqu, cost_satoshis, zonefile_data, token_file, transfer_address, mi
state, config_path, proxy = get_registrar_state(config_path=config_path, proxy=proxy)
data = {}
if min_payment_confs is None:
min_payment_confs = TX_MIN_CONFIRMATIONS
else:
log.warn("Using {} confirmations instead of the default {}".format(min_payment_confs, TX_MIN_CONFIRMATIONS))
if unsafe_reg:
log.debug('Aggressive registration of {}'.format(fqu))
if state.payment_address is None or state.owner_address is None:
log.debug("Wallet is not unlocked")
@@ -1170,8 +1176,18 @@ def preorder(fqu, cost_satoshis, zonefile_data, token_file, transfer_address, mi
'zonefile': zonefile_data,
'token_file': token_file,
}
log.debug("async_preorder({}, zonefile_data={}, token_file={}, transfer_address={})".format(fqu, zonefile_data, token_file, transfer_address))
if min_payment_confs is None:
min_payment_confs = TX_MIN_CONFIRMATIONS
else:
log.warn("Using {} confirmations instead of the default {}".format(min_payment_confs, TX_MIN_CONFIRMATIONS))
name_data['min_payment_confs'] = min_payment_confs # propogate this to later txns
if unsafe_reg:
name_data['confirmations_needed'] = PREORDER_CONFIRMATIONS
name_data['unsafe_reg'] = True
log.debug("async_preorder({}, zonefile_data={}, profile={}, transfer_address={})".format(fqu, zonefile_data, token_file, transfer_address))
resp = async_preorder(fqu, payment_privkey_info, owner_privkey_info, cost_satoshis,
name_data=name_data, min_payment_confs=min_payment_confs,
proxy=proxy, config_path=config_path, queue_path=state.queue_path)

View File

@@ -20,13 +20,40 @@
along with Blockstack-client. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import os, json
import threading
import functools
import virtualchain
from ..constants import (
TX_MIN_CONFIRMATIONS,
CONFIG_PATH,
APPROX_PREORDER_TX_LEN,
APPROX_REGISTER_TX_LEN,
APPROX_UPDATE_TX_LEN,
APPROX_TRANSFER_TX_LEN,
APPROX_REVOKE_TX_LEN,
APPROX_RENEWAL_TX_LEN,
BLOCKSTACK_DEBUG,
BLOCKSTACK_TEST,
APPROX_NAMESPACE_PREORDER_TX_LEN,
APPROX_NAMESPACE_REVEAL_TX_LEN,
APPROX_NAMESPACE_READY_TX_LEN,
)
from ..proxy import (
get_default_proxy,
is_name_registered,
get_names_owned_by_address,
get_name_cost,
is_namespace_revealed,
is_namespace_ready,
json_is_error,
get_namespace_cost,
get_namespace_blockchain_record,
get_num_names_in_namespace,
)
from ..constants import *
from ..keys import *
from ..proxy import *
from ..config import get_utxo_provider_client
from ..b40 import is_b40
from ..logger import get_logger
@@ -37,6 +64,8 @@ from .blockchain import (
can_receive_name, get_tx_fee_per_byte
)
from virtualchain.lib.ecdsalib import ecdsa_private_key
from ..scripts import UTXOException, is_name_valid, is_namespace_valid
log = get_logger('safety')
@@ -152,10 +181,6 @@ def operation_sanity_checks(fqu_or_ns, operations, scatter_gather, payment_privk
Return {'status': True} on success
Return {'error': ...} on error
"""
config_dir = os.path.dirname(config_path)
wallet_path = os.path.join(config_dir, WALLET_FILENAME)
if proxy is None:
proxy = get_default_proxy(config_path)
@@ -541,8 +566,6 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
# fee estimation: cost of name_or_ns + cost of preorder transaction +
# cost of registration transaction + cost of update transaction + cost of transfer transaction
reply = {}
if owner_address:
owner_address = str(owner_address)
if payment_address:
@@ -990,8 +1013,8 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
tx_fee = int(tx_fee)
else:
tx_fee = (len('00' * APPROX_NAME_IMPORT_TX_LEN) * tx_fee_per_byte) / 2
insufficient_funds = True
return {'error' : 'Failed to get good estimate of name import tx fee, and ' +
'there is no fallback estimation'}
return {'status': True, 'tx_fee': tx_fee, 'insufficient': insufficient_funds, 'estimate': estimate}
@@ -1274,19 +1297,23 @@ def check_preorder(fqu, cost_satoshis, owner_privkey_info, payment_privkey_info,
return {'status': True, 'tx_fee': tx_fee, 'tx_fee_per_byte': tx_fee_per_byte, 'opchecks': opchecks}
def check_register(fqu, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ):
def check_register(fqu, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None, force_it=False ):
"""
Verify that a register can go through
"""
required_checks = ['is_name_available', 'owner_can_receive', 'is_payment_address_usable']
required_checks = ['is_name_available', 'is_payment_address_usable']
if not force_it:
required_checks += ['owner_can_receive']
return _check_op(fqu, 'register', required_checks, owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy )
def check_update(fqu, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None ):
def check_update(fqu, owner_privkey_info, payment_privkey_info, min_payment_confs=TX_MIN_CONFIRMATIONS, config_path=CONFIG_PATH, proxy=None, force_it=False ):
"""
Verify that an update can go through
"""
required_checks = ['is_name_registered', 'is_owner_address_usable', 'is_payment_address_usable', 'is_name_owner']
required_checks = ['is_payment_address_usable']
if not force_it:
required_checks += ['is_name_registered', 'is_owner_address_usable', 'is_name_owner']
return _check_op(fqu, 'update', required_checks, owner_privkey_info, payment_privkey_info, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy)