def write_dht_profile(profile): resp = None dht_client = get_dht_client() if is_profile_in_legacy_format(profile): key = get_hash(profile) value = json.dumps(profile, sort_keys=True) else: key = hex_hash160(profile) value = profile if len(value) > MAX_DHT_WRITE: log.debug("DHT value too large: %s, %s" % (key, len(value))) return resp log.debug("DHT write (%s, %s)" % (key, value)) try: resp = dht_client.set(key, value) log.debug(pprint(resp)) except Exception as e: log.debug(e) return resp
def store_name_zonefile(name, user_zonefile, txid, storage_drivers=None): """ Store JSON user zonefile data to the immutable storage providers, synchronously. This is only necessary if we've added/changed/removed immutable data. Return (True, hash(user zonefile)) on success Return (False, None) on failure """ storage_drivers = [] if storage_drivers is None else storage_drivers assert not blockstack_profiles.is_profile_in_legacy_format( user_zonefile), 'User zonefile is a legacy profile' assert user_db.is_user_zonefile( user_zonefile), 'Not a user zonefile (maybe a custom legacy profile?)' # serialize and send off user_zonefile_txt = blockstack_zones.make_zone_file(user_zonefile, origin=name, ttl=USER_ZONEFILE_TTL) return store_name_zonefile_data(name, user_zonefile_txt, txid, storage_drivers=storage_drivers)
def write_dht_profile(profile): resp = None dht_client = get_dht_client() if is_profile_in_legacy_format(profile): key = get_hash(profile) value = json.dumps(profile, sort_keys=True) else: key = hex_hash160(profile) value = profile if len(value) > MAX_DHT_WRITE: log.debug("DHT value too large: %s, %s" % (key, len(value))) return resp log.debug("DHT write (%s, %s)" % (key, value)) try: resp = dht_client.set(key, value) log.debug(pprint(resp)) except Exception as e: log.debug(e) return resp
def _get_person_profile(name, proxy=None): """ Get the person's zonefile and profile. Handle legacy zonefiles, but not legacy profiles. Return {'profile': ..., 'zonefile': ..., 'person': ...} on success Return {'error': ...} on error """ res = get_profile(name, proxy=proxy, use_legacy_zonefile=True) if 'error' in res: return {'error': 'Failed to load zonefile: {}'.format(res['error'])} profile = res.pop('profile') zonefile = res.pop('zonefile') if blockstack_profiles.is_profile_in_legacy_format(profile): return {'error': 'Legacy profile'} person = None try: person = blockstack_profiles.Person(profile) except Exception as e: log.exception(e) return {'error': 'Failed to parse profile data into a Person record'} return {'profile': profile, 'zonefile': zonefile, 'person': person}
def _get_person_profile(name, proxy=None): """ Get the person's zonefile and profile. Handle legacy zonefiles, but not legacy profiles. Return {'profile': ..., 'zonefile': ..., 'person': ...} on success Return {'error': ...} on error """ res = get_profile(name, proxy=proxy, use_legacy_zonefile=True) if 'error' in res: return {'error': 'Failed to load zonefile: {}'.format(res['error'])} profile = res.pop('profile') zonefile = res.pop('zonefile') if blockstack_profiles.is_profile_in_legacy_format(profile): return {'error': 'Legacy profile'} person = None try: person = blockstack_profiles.Person(profile) except Exception as e: log.exception(e) return {'error': 'Failed to parse profile data into a Person record'} return {'profile': profile, 'zonefile': zonefile, 'person': person}
def get_immutable(name, data_hash, data_id=None, proxy=None): """ get_immutable Fetch a piece of immutable data. Use @data_hash to look it up in the user's zonefile, and then fetch and verify the data itself from the configured storage providers. Return {'data': the data, 'hash': hash} on success Return {'error': ...} on failure """ if proxy is None: proxy = get_default_proxy() user_zonefile = get_name_zonefile(name, proxy=proxy) if user_zonefile is None: return {'error': 'No user zonefile defined'} if 'error' in user_zonefile: return user_zonefile if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ): # zonefile is really a legacy profile return {'error': 'Profile is in a legacy format that does not support immutable data.'} if data_id is not None: # look up hash by name h = user_db.get_immutable_data_hash( user_zonefile, data_id ) if h is None: return {'error': 'No such immutable datum'} if type(h) == list: # this tool doesn't allow this to happen (one ID matches one hash), # but that doesn't preclude the user from doing this with other tools. if data_hash is not None and data_hash not in h: return {'error': 'Data ID/hash mismatch'} else: return {'error': "Multiple matches for '%s': %s" % (data_id, ",".join(h))} if data_hash is not None: if h != data_hash: return {'error': 'Data ID/hash mismatch'} else: data_hash = h elif not user_db.has_immutable_data( user_zonefile, data_hash ): return {'error': 'No such immutable datum'} data_url_hint = user_db.get_immutable_data_url( user_zonefile, data_hash ) data = storage.get_immutable_data( data_hash, fqu=name, data_id=data_id, data_url=data_url_hint ) if data is None: return {'error': 'No immutable data returned'} return {'data': data, 'hash': data_hash}
def convert_profile_format(user): if is_profile_in_legacy_format(user['profile']): data_value = user['profile'] else: if 'zone_file' in user: data_value = user['zone_file'] else: data_value = {} return data_value
def convert_profile_format(user): if is_profile_in_legacy_format(user['profile']): data_value = user['profile'] else: if 'zone_file' in user: data_value = user['zone_file'] else: data_value = {} return data_value
def delete_mutable(name, data_id, proxy=None, wallet_keys=None): """ delete_mutable Remove a piece of mutable data from the user's profile. Delete it from the storage providers as well. Returns a dict with {'status': True} on success Returns a dict with {'error': ...} on failure """ if proxy is None: proxy = get_default_proxy() fq_data_id = storage.make_fq_data_id( name, data_id ) legacy = False user_profile, user_zonefile = get_name_profile( name, proxy=proxy, wallet_keys=wallet_keys, include_name_record=True ) if user_profile is None: return user_zonefile # will be an error message name_record = user_zonefile['name_record'] del user_zonefile['name_record'] if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ): # zonefile is a legacy profile. There is no immutable data log.info("Profile is in legacy format. No immutable data.") return {'status': True} # already deleted? if not user_db.has_mutable_data( user_profile, data_id ): return {'status': True} # unlink user_db.remove_mutable_data_zonefile( user_profile, data_id ) # put new profile data_privkey = get_data_or_owner_privkey( user_zonefile, name_record['address'], wallet_keys=wallet_keys, config_path=proxy.conf['path'] ) if 'error' in data_privkey: return {'error': data_privkey['error']} else: data_privkey = data_privkey['privatekey'] assert data_privkey is not None rc = storage.put_mutable_data( name, user_profile, data_privkey ) if not rc: return {'error': 'Failed to unlink mutable data from profile'} # remove the data itself rc = storage.delete_mutable_data( fq_data_id, data_privkey ) if not rc: return {'error': 'Failed to delete mutable data from storage providers'} return {'status': True}
def store_name_zonefile(name, user_zonefile, txid, storage_drivers=None): """ Store JSON user zonefile data to the immutable storage providers, synchronously. This is only necessary if we've added/changed/removed immutable data. Return (True, hash(user zonefile)) on success Return (False, None) on failure """ storage_drivers = [] if storage_drivers is None else storage_drivers assert not blockstack_profiles.is_profile_in_legacy_format(user_zonefile), 'User zonefile is a legacy profile' assert user_db.is_user_zonefile(user_zonefile), 'Not a user zonefile (maybe a custom legacy profile?)' # serialize and send off user_zonefile_txt = blockstack_zones.make_zone_file(user_zonefile, origin=name, ttl=USER_ZONEFILE_TTL) return store_name_zonefile_data(name, user_zonefile_txt, txid, storage_drivers=storage_drivers)
def format_profile(profile, username, address): """ Process profile data and 1) Insert verifications 2) Check if profile data is valid JSON """ data = {} # save the original profile, in case it's a zone file zone_file = profile if 'error' in profile: data['profile'] = {} data['error'] = profile['error'] data['verifications'] = [] return data try: profile = resolve_zone_file_to_profile(profile, address) except: if 'message' in profile: data['profile'] = json.loads(profile) data['verifications'] = [] return data if profile is None: data['profile'] = {} data['error'] = "Malformed profile data." data['verifications'] = [] else: if not is_profile_in_legacy_format(profile): data['zone_file'] = zone_file data['profile'] = profile data['verifications'] = profile_v3_to_proofs( data['profile'], username) else: data['profile'] = json.loads(profile) data['verifications'] = profile_to_proofs(data['profile'], username) return data
def list_mutable_data( name, proxy=None, wallet_keys=None ): """ List the names and versions of all mutable data in a user's zonefile Returns {"data": [{"data_id": data ID, "version": version}]} """ if proxy is None: proxy = get_default_proxy() user_profile, user_zonefile = get_name_profile( name, proxy=proxy, wallet_keys=wallet_keys ) if user_zonefile is None: # user_profile will contain an error message return user_profile if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ): # zonefile is really a legacy profile return {"data": []} names_and_versions = user_db.list_mutable_data( user_profile ) listing = [ {"data_id": nv[0], "version": nv[1]} for nv in names_and_versions ] return {"data": listing}
def load_legacy_user_profile(name, expected_hash): """ Load a legacy user profile, and convert it into the new zonefile-esque profile format that can be serialized into a JWT. Verify that the profile hashses to the above expected hash """ # fetch... storage_host = 'onename.com' assert name.endswith('.id') name_without_namespace = '.'.join(name.split('.')[:-1]) storage_path = '/{}.json'.format(name_without_namespace) try: req = httplib.HTTPConnection(storage_host) resp = req.request('GET', storage_path) data = resp.read() except Exception as e: log.error('Failed to fetch http://{}/{}: {}'.format( storage_host, storage_path, e)) return None try: data_json = json.loads(data) except Exception as e: log.error('Unparseable profile data') return None data_hash = storage.get_blockchain_compat_hash(data_json) if expected_hash != data_hash: log.error('Hash mismatch: expected {}, got {}'.format( expected_hash, data_hash)) return None assert blockstack_profiles.is_profile_in_legacy_format(data_json) new_profile = blockstack_profiles.get_person_from_legacy_format(data_json) return new_profile
def list_immutable_data( name, proxy=None ): """ List the names and hashes of all immutable data in a user's zonefile. Returns {"data": [{"data_id": data_id, "hash": hash}]} on success """ if proxy is None: proxy = get_default_proxy() user_zonefile = get_name_zonefile(name, proxy=proxy) if user_zonefile is None: return {'error': 'No user zonefile defined'} if 'error' in user_zonefile: return user_zonefile if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ): # zonefile is really a legacy profile return {"data": []} names_and_hashes = user_db.list_immutable_data( user_zonefile ) listing = [ {"data_id": nh[0], "hash": nh[1]} for nh in names_and_hashes ] return {"data": listing}
def load_legacy_user_profile(name, expected_hash): """ Load a legacy user profile, and convert it into the new zonefile-esque profile format that can be serialized into a JWT. Verify that the profile hashses to the above expected hash """ # fetch... storage_host = 'onename.com' assert name.endswith('.id') name_without_namespace = '.'.join(name.split('.')[:-1]) storage_path = '/{}.json'.format(name_without_namespace) try: req = httplib.HTTPConnection(storage_host) resp = req.request('GET', storage_path) data = resp.read() except Exception as e: log.error('Failed to fetch http://{}/{}: {}'.format(storage_host, storage_path, e)) return None try: data_json = json.loads(data) except Exception as e: log.error('Unparseable profile data') return None data_hash = storage.get_blockchain_compat_hash(data_json) if expected_hash != data_hash: log.error('Hash mismatch: expected {}, got {}'.format(expected_hash, data_hash)) return None assert blockstack_profiles.is_profile_in_legacy_format(data_json) new_profile = blockstack_profiles.get_person_from_legacy_format(data_json) return new_profile
def store_name_zonefile( name, user_zonefile, txid ): """ Store JSON user zonefile data to the immutable storage providers, synchronously. This is only necessary if we've added/changed/removed immutable data. Return (True, hash(user)) on success Return (False, None) on failure """ assert not blockstack_profiles.is_profile_in_legacy_format(user_zonefile), "User zonefile is a legacy profile" assert user_db.is_user_zonefile(user_zonefile), "Not a user zonefile (maybe a custom legacy profile?)" # serialize and send off user_zonefile_txt = blockstack_zones.make_zone_file( user_zonefile, origin=name, ttl=USER_ZONEFILE_TTL ) data_hash = storage.get_zonefile_data_hash( user_zonefile_txt ) result = storage.put_immutable_data(None, txid, data_hash=data_hash, data_text=user_zonefile_txt ) rc = None if result is None: rc = False else: rc = True return (rc, data_hash)
def check( state_engine ): global wallet_keys, datasets, zonefile_hash # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered preorder = state_engine.get_name_preorder( "foo.test", pybitcoin.make_pay_to_address_script(wallets[2].addr), wallets[3].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( "foo.test" ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[3].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[3].addr): print "name has wrong owner" return False # have right hash if name_rec['value_hash'] != zonefile_hash: print "Invalid zonefile hash" return False # zonefile is NOT legacy user_zonefile = blockstack_client.zonefile.load_name_zonefile( 'foo.test', zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False res = testlib.blockstack_cli_lookup("foo.test") if 'error' in res: print 'error looking up profile: {}'.format(res) return False assert 'profile' in res assert 'zonefile' in res return True
def get_and_migrate_profile( name, zonefile_storage_drivers=None, profile_storage_drivers=None, proxy=None, create_if_absent=False, wallet_keys=None, include_name_record=False ): """ Get a name's profile and zonefile, optionally creating a new one along the way. Migrate the profile to a new zonefile, if the profile is in legacy format. Only pass 'create_if_absent=True' for names we own If @include_name_record is set, then the resulting zonefile will have a key called 'name_record' that includes the name record. Return (user_profile, user_zonefile, migrated:bool) on success Return ({'error': ...}, None, False) on error """ if proxy is None: proxy = get_default_proxy() created_new_zonefile = False created_new_profile = False name_record = None user_zonefile = get_name_zonefile( name, storage_drivers=zonefile_storage_drivers, proxy=proxy, wallet_keys=wallet_keys, include_name_record=True ) if user_zonefile is None or 'error' in user_zonefile: if not create_if_absent: return ({'error': 'No such zonefile'}, None, False) # creating. we'd better have a data public key log.debug("Creating new profile and zonefile for name '%s'" % name) data_pubkey = load_data_pubkey_for_new_zonefile( wallet_keys=wallet_keys, config_path=proxy.conf['path'] ) if data_pubkey is None: log.warn("No data keypair set; will fall back to owner private key for data signing") user_profile = user_db.make_empty_user_profile() user_zonefile = user_db.make_empty_user_zonefile( name, data_pubkey ) # look up name too name_record = proxy.get_name_blockchain_record(name) if name_record is None: return ({'error': 'No such name'}, None, False) if 'error' in name_record: return ({'error': 'Failed to look up name: %s' % name_record['error']}, None, False) created_new_zonefile = True created_new_profile = True else: name_record = user_zonefile['name_record'] del user_zonefile['name_record'] if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ): log.debug("Migrating legacy profile to modern zonefile for name '%s'" % name) data_pubkey = load_data_pubkey_for_new_zonefile( wallet_keys=wallet_keys, config_path=proxy.conf['path'] ) if data_pubkey is None: log.warn("No data keypair set; will fall back to owner private key for data signing") user_profile = {} if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): # traditional profile user_profile = blockstack_profiles.get_person_from_legacy_format( user_zonefile ) else: # custom profile user_profile = copy.deepcopy( user_zonefile ) user_zonefile = user_db.make_empty_user_zonefile( name, data_pubkey ) created_new_zonefile = True created_new_profile = True else: if not created_new_profile: user_profile, error_msg = get_name_profile( name, zonefile_storage_drivers=zonefile_storage_drivers, profile_storage_drivers=profile_storage_drivers, proxy=proxy, wallet_keys=wallet_keys, user_zonefile=user_zonefile, name_record=name_record ) if user_profile is None: return (error_msg, None, False) elif create_if_absent: log.debug("Creating new profile for existing zonefile for name '%s'" % name) user_profile = user_db.make_empty_user_profile() created_new_profile = True else: raise Exception("Should be unreachable") if include_name_record: # put it back user_zonefile['name_record'] = name_record return (user_profile, user_zonefile, created_new_zonefile)
def get_profile(name, zonefile_storage_drivers=None, profile_storage_drivers=None, proxy=None, user_zonefile=None, name_record=None, include_name_record=False, include_raw_zonefile=False, use_zonefile_urls=True, use_legacy=False, use_legacy_zonefile=True, decode_profile=True): """ Given a name, look up an associated profile. Do so by first looking up the zonefile the name points to, and then loading the profile from that zonefile's public key. Notes on backwards compatibility (activated if use_legacy=True and use_legacy_zonefile=True): * (use_legacy=True) If the user's zonefile is really a legacy profile from Onename, then the profile returned will be the converted legacy profile. The returned zonefile will still be a legacy profile, however. The caller can check this and perform the conversion automatically. * (use_legacy_zonefile=True) If the name points to a current zonefile that does not have a data public key, then the owner address of the name will be used to verify the profile's authenticity. Returns (profile, zonefile) on success. If include_name_record is True, then zonefile['name_record'] will be defined and will contain the user's blockchain information Returns (None, {'error': ...}) on failure """ proxy = get_default_proxy() if proxy is None else proxy raw_zonefile = None if user_zonefile is None: user_zonefile = get_name_zonefile( name, proxy=proxy, name_record=name_record, include_name_record=True, storage_drivers=zonefile_storage_drivers, include_raw_zonefile=include_raw_zonefile, allow_legacy=True) if 'error' in user_zonefile: return None, user_zonefile raw_zonefile = None if include_raw_zonefile: raw_zonefile = user_zonefile.pop('raw_zonefile') user_zonefile = user_zonefile['zonefile'] # is this really a legacy profile? if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): if not use_legacy: return (None, {'error': 'Profile is in legacy format'}) # convert it log.debug('Converting legacy profile to modern profile') user_profile = blockstack_profiles.get_person_from_legacy_format( user_zonefile) elif not user_db.is_user_zonefile(user_zonefile): if not use_legacy: return (None, {'error': 'Name zonefile is non-standard'}) # not a legacy profile, but a custom profile log.debug('Using custom legacy profile') user_profile = copy.deepcopy(user_zonefile) else: # get user's data public key data_address, owner_address = None, None try: user_data_pubkey = user_db.user_zonefile_data_pubkey(user_zonefile) if user_data_pubkey is not None: user_data_pubkey = str(user_data_pubkey) data_address = virtualchain.BitcoinPublicKey( user_data_pubkey).address() except ValueError: # multiple keys defined; we don't know which one to use user_data_pubkey = None if not use_legacy_zonefile and user_data_pubkey is None: # legacy zonefile without a data public key return (None, {'error': 'Name zonefile is missing a public key'}) # find owner address if name_record is None: name_record = get_name_blockchain_record(name, proxy=proxy) if name_record is None or 'error' in name_record: log.error( 'Failed to look up name record for "{}"'.format(name)) return None, {'error': 'Failed to look up name record'} assert 'address' in name_record.keys(), json.dumps(name_record, indent=4, sort_keys=True) owner_address = name_record['address'] # get user's data public key from the zonefile urls = None if use_zonefile_urls and user_zonefile is not None: urls = user_db.user_zonefile_urls(user_zonefile) user_profile = storage.get_mutable_data( name, user_data_pubkey, data_address=data_address, owner_address=owner_address, urls=urls, drivers=profile_storage_drivers, decode=decode_profile, ) if user_profile is None or json_is_error(user_profile): if user_profile is None: log.debug('WARN: no user profile for {}'.format(name)) else: log.debug('WARN: failed to load profile for {}: {}'.format( name, user_profile['error'])) return None, {'error': 'Failed to load user profile'} # finally, if the caller asked for the name record, and we didn't get a chance to look it up, # then go get it. if include_name_record: if name_record is None: name_record = get_name_blockchain_record(name, proxy=proxy) if name_record is None or 'error' in name_record: log.error('Failed to look up name record for "{}"'.format(name)) return None, {'error': 'Failed to look up name record'} user_zonefile['name_record'] = name_record if include_raw_zonefile: if raw_zonefile is not None: user_zonefile['raw_zonefile'] = raw_zonefile return user_profile, user_zonefile
def check( state_engine ): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2, datastore_name # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i+1) - 1 wallet_owner = 3 * (i+1) wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.get_profile(name, user_zonefile=user_zonefile) if user_profile is None: print "Unable to load user profile for %s" % name return False if 'error' in user_profile: print json.dumps(user_profile, indent=4, sort_keys=True) return False # can fetch latest by name immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/" ) if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if json.loads(immutable_data['data']) != {'hello': 'world'}: print "immutable fetch-latest mismatch:\n%s (%s)\n%s" % (immutable_data['data'], type(immutable_data['data']), {'hello': 'world'}) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-latest hash mismatch: %s != %s" % (immutable_data['hash'], immutable_hash) return False # can fetch by name and hash immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % immutable_hash ) if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if json.loads(immutable_data['data']) != {'hello': 'world'}: print "immutable fetch-by-hash mismatch:\n%s (%s)\n%s" % (immutable_data['data'], type(immutable_data['data']), {'hello': 'world'}) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-by-hash mismatch: %s != %s" % (immutable_data['hash'], immutable_hash) return False # hash must match (if we put the wrong hash, it must fail) try: immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % ("0" * len(immutable_hash))) print "no error" print json.dumps(immutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can list names and hashes immutable_data_list = get_data( "blockstack://foo.test/#immutable" ) if 'error' in immutable_data_list: print json.dumps(immutable_data, indent=4, sort_keys=True ) return False if len(immutable_data_list['data']) != 2: print "multiple immutable data" print json.dumps(immutable_data_list, indent=4, sort_keys=True ) return False # order preserved if immutable_data_list['data'][0]['data_id'] != 'hello_world_immutable' or immutable_data_list['data'][0]['hash'] != immutable_hash: print "wrong data ID and/or hash" print json.dumps(immutable_data_list, indent=4, sort_keys=True ) return False device_id = blockstack_client.config.get_local_device_id() data_id = blockstack_client.storage.make_fq_data_id( device_id, 'hello_world_mutable' ) # can fetch latest mutable by name mutable_data = get_data( "blockstack://bar.test/{}".format(data_id)) if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if json.loads(mutable_data['data']) != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['version'] != 1: print "wrong version: %s" % mutable_data['data']['version'] return False # can fetch by version mutable_data = get_data( "blockstack://bar.test/{}#1".format(data_id)) if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if json.loads(mutable_data['data']) != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False # will fail to fetch if we give the wrong version try: mutable_data = get_data("blockstack://bar.test/{}#2".format(data_id)) print "mutable fetch by wrong version worked" print json.dumps(mutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can fetch mutable data put by URL data_id = blockstack_client.storage.make_fq_data_id(device_id, 'foo_data2') mutable_data = get_data( "blockstack://foo.test/{}".format(data_id) ) if 'error' in mutable_data or 'data' not in mutable_data or 'version' not in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if json.loads(mutable_data['data']) != {'hello2': 'world2'}: print "Invalid mutable data fetched from blockstack://foo.test/{}".format(data_id) print json.dumps(mutable_data, indent=4, sort_keys=True) return False # can fetch immutable data put by URL immutable_data = get_data( "blockstack://foo_immutable.foo.test" ) if 'error' in immutable_data or 'hash' not in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if json.loads(immutable_data['data']) != {'hello3': 'world3'}: print "Invalid immutable data fetched from blockstack://foo_immutable.foo.test" print json.dumps(immutable_data, indent=4, sort_keys=True) return False # can fetch files and directories mutable_data = get_data( "blockstack://{}@foo-app.com/hello_datastore".format(datastore_name) ) if 'error' in mutable_data or 'file' not in mutable_data or mutable_data['file']['idata'] != 'hello datastore': print 'Failed to get blockstack://{}@foo-app.com/hello_datastore'.format(datastore_name) print json.dumps(mutable_data, indent=4, sort_keys=True) return False mutable_data = get_data( "blockstack://{}@foo-app.com/hello_dir/".format(datastore_name) ) if 'error' in mutable_data or 'dir' not in mutable_data or 'hello_dir_datastore' not in mutable_data['dir']['idata'].keys(): print 'Failed to get blockstack://{}@foo-app.com/hello_dir/'.format(datastore_name) print json.dumps(mutable_data, indent=4, sort_keys=True) return False mutable_data = get_data( "blockstack://{}@foo-app.com/hello_dir/hello_dir_datastore".format(datastore_name) ) if 'error' in mutable_data or 'file' not in mutable_data or mutable_data['file']['idata'] != 'hello dir datastore': print 'Failed to get blockstack://{}@foo-app.com/hello_dir/hello_dir_datastore'.format(datastore_name) print json.dumps(mutable_data, indent=4, sort_keys=True) return False return True
def check( state_engine ): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2 # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i+1) - 1 wallet_owner = 3 * (i+1) wallet_data_pubkey = 3 * (i+1) # same as owner key wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.profile.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.profile.load_name_profile( name, user_zonefile, wallets[wallet_data_pubkey].addr, wallets[wallet_owner].addr ) if user_profile is None or 'error' in user_profile: if user_profile is not None: print json.dumps(user_profile, indent=4, sort_keys=True) else: print "\n\nprofile is None\n\n" return False return True
except Exception, e: log.error("Failed to fetch http://%s/%s: %s" % (storage_host, storage_path, e)) return None try: data_json = json.loads(data) except Exception, e: log.error("Unparseable profile data") return None data_hash = storage.get_blockchain_compat_hash( data_json ) if expected_hash != data_hash: log.error("Hash mismatch: expected %s, got %s" % (expected_hash, data_hash)) return None assert blockstack_profiles.is_profile_in_legacy_format( data_json ) new_profile = blockstack_profiles.get_person_from_legacy_format( data_json ) return new_profile def load_name_profile(name, user_zonefile, data_address, owner_address, use_zonefile_urls=True, storage_drivers=None, decode=True): """ Fetch and load a user profile, given the user zonefile. Try to verify using the public key in the zonefile (if one is present), and fall back to the user-address if need be (it should be the hash of the profile JWT's public key). Return the user profile on success (either as a dict, or as a string if decode=False) Return None on error """ # get user's data public key
def get_and_migrate_profile(name, zonefile_storage_drivers=None, profile_storage_drivers=None, proxy=None, create_if_absent=False, wallet_keys=None, include_name_record=False): """ Get a name's profile and zonefile, optionally creating a new one along the way. Migrate the profile to a new zonefile, if the profile is in legacy format. Only pass 'create_if_absent=True' for names we own If @include_name_record is set, then the resulting zonefile will have a key called 'name_record' that includes the name record. @wallet_keys, if given, only needs the data public key set. Return ({'profile': user_profile}, {'zonefile': user_zonefile}, migrated:bool) on success Return ({'error': ...}, None, False) on error """ if proxy is None: proxy = get_default_proxy() created_new_zonefile = False created_new_profile = False name_record = None user_zonefile = get_name_zonefile(name, storage_drivers=zonefile_storage_drivers, proxy=proxy, wallet_keys=wallet_keys, include_name_record=True) if user_zonefile is None or 'error' in user_zonefile: if not create_if_absent: return ({'error': 'No such zonefile'}, None, False) # creating. we'd better have a data public key log.debug("Creating new profile and zonefile for name '%s'" % name) data_pubkey = load_data_pubkey_for_new_zonefile( wallet_keys=wallet_keys, config_path=proxy.conf['path']) if data_pubkey is None: log.warn( "No data keypair set; will fall back to owner private key for data signing" ) user_profile = user_db.make_empty_user_profile() user_zonefile = user_db.make_empty_user_zonefile(name, data_pubkey) # look up name too name_record = proxy.get_name_blockchain_record(name) if name_record is None: return ({'error': 'No such name'}, None, False) if 'error' in name_record: return ({ 'error': 'Failed to look up name: %s' % name_record['error'] }, None, False) created_new_zonefile = True created_new_profile = True else: name_record = user_zonefile['name_record'] del user_zonefile['name_record'] user_zonefile = user_zonefile['zonefile'] if blockstack_profiles.is_profile_in_legacy_format( user_zonefile) or not user_db.is_user_zonefile(user_zonefile): log.debug("Migrating legacy profile to modern zonefile for name '%s'" % name) data_pubkey = load_data_pubkey_for_new_zonefile( wallet_keys=wallet_keys, config_path=proxy.conf['path']) if data_pubkey is None: log.warn( "No data keypair set; will fall back to owner private key for data signing" ) user_profile = {} if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): # traditional profile user_profile = blockstack_profiles.get_person_from_legacy_format( user_zonefile) else: # custom profile user_profile = copy.deepcopy(user_zonefile) user_zonefile = user_db.make_empty_user_zonefile(name, data_pubkey) created_new_zonefile = True created_new_profile = True else: if not created_new_profile: user_profile, error_msg = get_name_profile( name, zonefile_storage_drivers=zonefile_storage_drivers, profile_storage_drivers=profile_storage_drivers, proxy=proxy, user_zonefile=user_zonefile, name_record=name_record) if user_profile is None: return (error_msg, None, False) elif create_if_absent: log.debug( "Creating new profile for existing zonefile for name '%s'" % name) user_profile = user_db.make_empty_user_profile() created_new_profile = True else: raise Exception("Should be unreachable") ret_user_profile = {"profile": user_profile} ret_user_zonefile = {"zonefile": user_zonefile} if include_name_record: # put it back ret_user_zonefile['name_record'] = name_record return (ret_user_profile, ret_user_zonefile, created_new_zonefile)
(storage_host, storage_path, e)) return None try: data_json = json.loads(data) except Exception, e: log.error("Unparseable profile data") return None data_hash = storage.get_blockchain_compat_hash(data_json) if expected_hash != data_hash: log.error("Hash mismatch: expected %s, got %s" % (expected_hash, data_hash)) return None assert blockstack_profiles.is_profile_in_legacy_format(data_json) new_profile = blockstack_profiles.get_person_from_legacy_format(data_json) return new_profile def load_name_profile(name, user_zonefile, data_address, owner_address, use_zonefile_urls=True, storage_drivers=None, decode=True): """ Fetch and load a user profile, given the user zonefile. Try to verify using the public key in the zonefile (if one is present), and fall back to the user-address if need be
def get_profile(name, zonefile_storage_drivers=None, profile_storage_drivers=None, proxy=None, user_zonefile=None, name_record=None, include_name_record=False, include_raw_zonefile=False, use_zonefile_urls=True, use_legacy=False, use_legacy_zonefile=True, decode_profile=True): """ Given a name, look up an associated profile. Do so by first looking up the zonefile the name points to, and then loading the profile from that zonefile's public key. Notes on backwards compatibility (activated if use_legacy=True and use_legacy_zonefile=True): * (use_legacy=True) If the user's zonefile is really a legacy profile from Onename, then the profile returned will be the converted legacy profile. The returned zonefile will still be a legacy profile, however. The caller can check this and perform the conversion automatically. * (use_legacy_zonefile=True) If the name points to a current zonefile that does not have a data public key, then the owner address of the name will be used to verify the profile's authenticity. Returns {'status': True, 'profile': profile, 'zonefile': zonefile} on success. * If include_name_record is True, then include 'name_record': name_record with the user's blockchain information * If include_raw_zonefile is True, then include 'raw_zonefile': raw_zonefile with unparsed zone file Returns {'error': ...} on error """ proxy = get_default_proxy() if proxy is None else proxy raw_zonefile = None if user_zonefile is None: user_zonefile = get_name_zonefile( name, proxy=proxy, name_record=name_record, include_name_record=True, storage_drivers=zonefile_storage_drivers, include_raw_zonefile=include_raw_zonefile, allow_legacy=True ) if 'error' in user_zonefile: return user_zonefile raw_zonefile = None if include_raw_zonefile: raw_zonefile = user_zonefile.pop('raw_zonefile') user_zonefile = user_zonefile['zonefile'] # is this really a legacy profile? if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): if not use_legacy: return {'error': 'Profile is in legacy format'} # convert it log.debug('Converting legacy profile to modern profile') user_profile = blockstack_profiles.get_person_from_legacy_format(user_zonefile) elif not user_db.is_user_zonefile(user_zonefile): if not use_legacy: return {'error': 'Name zonefile is non-standard'} # not a legacy profile, but a custom profile log.debug('Using custom legacy profile') user_profile = copy.deepcopy(user_zonefile) else: # get user's data public key data_address, owner_address = None, None try: user_data_pubkey = user_db.user_zonefile_data_pubkey(user_zonefile) if user_data_pubkey is not None: user_data_pubkey = str(user_data_pubkey) data_address = keylib.ECPublicKey(user_data_pubkey).address() except ValueError: # multiple keys defined; we don't know which one to use user_data_pubkey = None if not use_legacy_zonefile and user_data_pubkey is None: # legacy zonefile without a data public key return {'error': 'Name zonefile is missing a public key'} # find owner address if name_record is None: name_record = get_name_blockchain_record(name, proxy=proxy) if name_record is None or 'error' in name_record: log.error('Failed to look up name record for "{}"'.format(name)) return {'error': 'Failed to look up name record'} assert 'address' in name_record.keys(), json.dumps(name_record, indent=4, sort_keys=True) owner_address = name_record['address'] # get user's data public key from the zonefile urls = None if use_zonefile_urls and user_zonefile is not None: urls = user_db.user_zonefile_urls(user_zonefile) user_profile = storage.get_mutable_data( name, user_data_pubkey, blockchain_id=name, data_address=data_address, owner_address=owner_address, urls=urls, drivers=profile_storage_drivers, decode=decode_profile, ) if user_profile is None or json_is_error(user_profile): if user_profile is None: log.error('no user profile for {}'.format(name)) else: log.error('failed to load profile for {}: {}'.format(name, user_profile['error'])) return {'error': 'Failed to load user profile'} # finally, if the caller asked for the name record, and we didn't get a chance to look it up, # then go get it. ret = { 'status': True, 'profile': user_profile, 'zonefile': user_zonefile } if include_name_record: if name_record is None: name_record = get_name_blockchain_record(name, proxy=proxy) if name_record is None or 'error' in name_record: log.error('Failed to look up name record for "{}"'.format(name)) return {'error': 'Failed to look up name record'} ret['name_record'] = name_record if include_raw_zonefile: if raw_zonefile is not None: ret['raw_zonefile'] = raw_zonefile return ret
def check( state_engine ): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2 # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i+1) - 1 wallet_owner = 3 * (i+1) wallet_data_pubkey = 3 * (i+1) + 1 wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.profile.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.profile.load_name_profile( name, user_zonefile, wallets[wallet_data_pubkey].addr, wallets[wallet_owner].addr ) if user_profile is None: print "Unable to load user profile for %s (%s)" % (name, wallets[wallet_data_pubkey].pubkey_hex) return False if 'error' in user_profile: print json.dumps(user_profile, indent=4, sort_keys=True) return False # can fetch latest by name immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/" ) if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if immutable_data['data'] != {'hello': 'world'}: print "immutable fetch-latest mismatch:\n%s (%s)\n%s" % (immutable_data['data'], type(immutable_data['data']), {'hello': 'world'}) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-latest hash mismatch: %s != %s" % (immutable_data['hash'], immutable_hash) return False # can fetch by name and hash immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % immutable_hash ) if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if immutable_data['data'] != {'hello': 'world'}: print "immutable fetch-by-hash mismatch:\n%s (%s)\n%s" % (immutable_data['data'], type(immutable_data['data']), {'hello': 'world'}) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-by-hash mismatch: %s != %s" % (immutable_data['hash'], immutable_hash) return False # hash must match (if we put the wrong hash, it must fail) try: immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % ("0" * len(immutable_hash))) print "no error" print json.dumps(immutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can list names and hashes immutable_data_list = get_data( "blockstack://foo.test/#immutable" ) if 'error' in immutable_data_list: print json.dumps(immutable_data, indent=4, sort_keys=True ) return False if len(immutable_data_list['data']) != 2: print "multiple immutable data" print json.dumps(immutable_data_list, indent=4, sort_keys=True ) return False # order preserved if immutable_data_list['data'][0]['data_id'] != 'hello_world_immutable' or immutable_data_list['data'][0]['hash'] != immutable_hash: print "wrong data ID and/or hash" print json.dumps(immutable_data_list, indent=4, sort_keys=True ) return False # can fetch latest mutable by name mutable_data = get_data( "blockstack://bar.test/hello_world_mutable") if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['data'] != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['version'] != 1: print "wrong version: %s" % mutable_data['data']['version'] return False # can fetch by version mutable_data = get_data( "blockstack://bar.test/hello_world_mutable#1") if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['data'] != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False # will fail to fetch if we give the wrong version try: mutable_data = get_data("blockstack://bar.test/hello_world_mutable#2") print "mutable fetch by wrong version worked" print json.dumps(mutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can list mutable data mutable_data_list = get_data( "blockstack://bar.test/#mutable" ) if 'error' in mutable_data_list: print json.dumps(mutable_data_list, indent=4, sort_keys=True ) return False if len(mutable_data_list) != 1: print "multiple mutable data" print json.dumps(mutable_data_list, indent=4, sort_keys=True ) return False if mutable_data_list['data'][0]['data_id'] != 'hello_world_mutable' or mutable_data_list['data'][0]['version'] != 1: print "wrong data id and/or version" print json.dumps(mutable_data_list, indent=4, sort_keys=True) return False # can fetch mutable data put by URL mutable_data = get_data( "blockstack://foo.test/foo_data2" ) if 'error' in mutable_data or 'data' not in mutable_data or 'version' not in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['data'] != {'hello2': 'world2'}: print "Invalid mutable data fetched from blockstack://foo.test/foo_data2" print json.dumps(mutable_data, indent=4, sort_keys=True) return False # can fetch immutable data put by URL immutable_data = get_data( "blockstack://foo_immutable.foo.test" ) if 'error' in immutable_data or 'hash' not in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if immutable_data['data'] != {'hello3': 'world3'}: print "Invalid immutable data fetched from blockstack://foo_immutable.foo.test" print json.dumps(immutable_data, indent=4, sort_keys=True) return False # can fetch app data put by URL mutable_data = get_data( "blockstack://[email protected]/foo_app_data" ) if 'error' in mutable_data or 'data' not in mutable_data or 'version' not in mutable_data: print "Failed to get blockstack://[email protected]/foo_app_data" print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['data'] != "foo_app_payload": print "Invalid data for blockstack://[email protected]/foo_app_data" print json.dumps(mutable_data, indent=4, sort_keys=True) return False mutable_data = get_data( "blockstack://[email protected]/foo_app_data2#3" ) if 'error' in mutable_data or 'data' not in mutable_data or 'version' not in mutable_data: print "Failed to get blockstack://[email protected]/foo_app_data2#3" print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['data'] != "foo_app_payload2": print "Failed to get blockstack://[email protected]/foo_app_data2#3" print json.dumps(mutable_data, indent=4, sort_keys=True) return False # fetch by wrong version will fail try: mutable_data = get_data( "blockstack://[email protected]/foo_app_data2#4" ) print "got stale data with no error" print json.dumps(mutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass return True
def check( state_engine ): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2 # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i+1) - 1 wallet_owner = 3 * (i+1) wallet_data_pubkey = 3 * (i+1) # same as owner key wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.zonefile.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.profile.get_profile( name, user_zonefile=user_zonefile ) if user_profile is None or 'error' in user_profile: if user_profile is not None: print json.dumps(user_profile, indent=4, sort_keys=True) else: print "\n\nprofile is None\n\n" return False # can get mutable data res = testlib.blockstack_cli_get_mutable( "bar.test", "hello_world_mutable" ) print 'mutable: {}'.format(res) if 'error' in res: print json.dumps(res, indent=4, sort_keys=True) return False if json.loads(res['data']) != {'hello': 'world'}: print 'invalid data: {}'.format(res['data']) return False # can get immutable data by name res = testlib.blockstack_cli_get_immutable( 'foo.test', 'hello_world_immutable' ) print 'immutable by name: {}'.format(res) if 'error' in res: return res if json.loads(res['data']) != {'hello': 'world_immutable'}: print 'invalid immutable data: {}'.format(res['data']) return False # can get immutable data by hash hsh = res['hash'] res = testlib.blockstack_cli_get_immutable( 'foo.test', hsh ) print 'immutable: {}'.format(res) if 'error' in res: return res if json.loads(res['data']) != {'hello': 'world_immutable'}: print 'invalid immutable data by hash: {}'.format(res['data']) return False return True
def check( state_engine ): global wallet_keys, datasets, zonefile_hash if error: return False # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False name = "foo.test" wallet_payer = 2 wallet_owner = 3 wallet_data_pubkey = 4 # not preordered preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.profile.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have a profile with data user_profile = blockstack_client.profile.load_name_profile( name, user_zonefile, wallets[wallet_data_pubkey].addr, wallets[wallet_owner].addr ) if user_profile is None or 'error' in user_profile: if user_profile is not None: print json.dumps(user_profile, indent=4, sort_keys=True) else: print "\n\nprofile is None\n\n" return False # still have immutable data immutable_data_by_name = blockstack_client.get_immutable_by_name( "foo.test", "hello_world_immutable" ) if immutable_data_by_name is None: print "No data received by name for dataset %s" % i return False if 'error' in immutable_data_by_name: print "No data received for dataset hello_world_immutable" return False if not immutable_data_by_name.has_key('data'): print "Misisng data\n%s" % json.dumps(immutable_data_by_name, indent=4, sort_keys=True) return False data_json = immutable_data_by_name['data'] if data_json != {'hello': 'world'}: print "did not get dataset hello_world_immutable\ngot %s\nexpected %s" % (data_json, {'hello': 'world'}) return False # still have mutable data dat = blockstack_client.get_mutable( "foo.test", "hello_world_mutable" ) if dat is None: print "No hello_world_mutable" return False if 'error' in dat: print json.dumps(dat, indent=4, sort_keys=True) return False if dat['data'] != {'hello': 'world'}: print "did not get mutable dataset" return False return True
def check(state_engine): global wallet_keys, datasets, zonefile_hash # not revealed, but ready ns = state_engine.get_namespace_reveal("test") if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace("test") if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered preorder = state_engine.get_name_preorder( "foo.test", pybitcoin.make_pay_to_address_script(wallets[2].addr), wallets[3].addr) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name("foo.test") if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[3].addr or name_rec[ 'sender'] != pybitcoin.make_pay_to_address_script(wallets[3].addr): print "name has wrong owner" return False # have right hash if name_rec['value_hash'] != zonefile_hash: print "Invalid zonefile hash" return False # zonefile is NOT legacy user_zonefile = blockstack_client.zonefile.load_name_zonefile( 'foo.test', zonefile_hash) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # zonefile has no key zonefile_key = blockstack_client.user.user_zonefile_data_pubkey( user_zonefile) if zonefile_key is not None: print 'still have zonefile key' print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # have right data test_proxy = testlib.TestAPIProxy() blockstack_client.set_default_proxy(test_proxy) for i in xrange(0, len(datasets)): print "get hello_world_%s" % (i + 1) dat = testlib.blockstack_cli_get_mutable( "foo.test", "hello_world_{}".format(i + 1), public_key=wallets[4].pubkey_hex) if dat is None: print "No data '%s'" % ("hello_world_%s" % (i + 1)) return False if 'error' in dat: print json.dumps(dat, indent=4, sort_keys=True) return False if json.loads(dat['data']) != datasets[i]: print "Mismatch %s: %s != %s" % (i, dat, datasets[i]) return False res = testlib.blockstack_cli_lookup("foo.test") if 'error' in res: print 'error looking up profile: {}'.format(res) return False assert 'profile' in res assert 'zonefile' in res return True
def check( state_engine ): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2 # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i+1) - 1 wallet_owner = 3 * (i+1) wallet_data_pubkey = 3 * (i+1) + 1 wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.profile.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.profile.load_name_profile( name, user_zonefile, wallets[wallet_data_pubkey].addr, wallets[wallet_owner].addr ) if user_profile is None or 'error' in user_profile: if user_profile is not None: print json.dumps(user_profile, indent=4, sort_keys=True) else: print "\n\nprofile is None\n\n" return False return True
def check(state_engine): global wallet_keys, datasets, zonefile_hash if error: return False # not revealed, but ready ns = state_engine.get_namespace_reveal("test") if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace("test") if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False name = "foo.test" wallet_payer = 2 wallet_owner = 3 wallet_data_pubkey = 4 # not preordered preorder = state_engine.get_name_preorder( name, virtualchain.make_payment_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name(name) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec[ 'sender'] != virtualchain.make_payment_script( wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.zonefile.load_name_zonefile( name, zonefile_hash) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have a profile with data user_profile = blockstack_client.profile.get_profile( name, user_zonefile=user_zonefile) if user_profile is None or 'error' in user_profile: if user_profile is not None: print json.dumps(user_profile, indent=4, sort_keys=True) else: print "\n\nprofile is None\n\n" return False # still have immutable data immutable_data_by_name = testlib.get_immutable_by_name( 'foo.test', 'hello_world_immutable') if immutable_data_by_name is None: print "No data received by name for dataset %s" % i return False if 'error' in immutable_data_by_name: print "No data received for dataset hello_world_immutable" return False if not immutable_data_by_name.has_key('data'): print "Misisng data\n%s" % json.dumps( immutable_data_by_name, indent=4, sort_keys=True) return False data_json = json.loads(immutable_data_by_name['data']) if data_json != {'hello': 'world'}: print "did not get dataset hello_world_immutable\ngot %s\nexpected %s" % ( data_json, { 'hello': 'world' }) return False # still have mutable data dat = testlib.blockstack_cli_get_mutable('foo.test', 'hello_world_mutable') if dat is None: print "No hello_world_mutable" return False if 'error' in dat: print json.dumps(dat, indent=4, sort_keys=True) return False if json.loads(dat['data']) != {'hello': 'world'}: print "did not get mutable dataset" return False return True
def check(state_engine): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2, datastore_name # not revealed, but ready ns = state_engine.get_namespace_reveal("test") if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace("test") if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i + 1) - 1 wallet_owner = 3 * (i + 1) wallet_data_pubkey = 3 * (i + 1) + 1 wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name(name) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec[ 'sender'] != pybitcoin.make_pay_to_address_script( wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.load_name_zonefile( name, zonefile_hash) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.get_profile( name, user_zonefile=user_zonefile) if user_profile is None: print "Unable to load user profile for %s (%s)" % ( name, wallets[wallet_data_pubkey].pubkey_hex) return False if 'error' in user_profile: print json.dumps(user_profile, indent=4, sort_keys=True) return False # can fetch latest by name immutable_data = get_data("blockstack://hello_world_immutable.foo.test/") if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if json.loads(immutable_data['data']) != {'hello': 'world'}: print "immutable fetch-latest mismatch:\n%s (%s)\n%s" % ( immutable_data['data'], type(immutable_data['data']), { 'hello': 'world' }) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-latest hash mismatch: %s != %s" % ( immutable_data['hash'], immutable_hash) return False # can fetch by name and hash immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % immutable_hash) if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if json.loads(immutable_data['data']) != {'hello': 'world'}: print "immutable fetch-by-hash mismatch:\n%s (%s)\n%s" % ( immutable_data['data'], type(immutable_data['data']), { 'hello': 'world' }) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-by-hash mismatch: %s != %s" % ( immutable_data['hash'], immutable_hash) return False # hash must match (if we put the wrong hash, it must fail) try: immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % ("0" * len(immutable_hash))) print "no error" print json.dumps(immutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can list names and hashes immutable_data_list = get_data("blockstack://foo.test/#immutable") if 'error' in immutable_data_list: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if len(immutable_data_list['data']) != 2: print "multiple immutable data" print json.dumps(immutable_data_list, indent=4, sort_keys=True) return False # order preserved if immutable_data_list['data'][0][ 'data_id'] != 'hello_world_immutable' or immutable_data_list[ 'data'][0]['hash'] != immutable_hash: print "wrong data ID and/or hash" print json.dumps(immutable_data_list, indent=4, sort_keys=True) return False device_id = blockstack_client.config.get_local_device_id() data_id = blockstack_client.storage.make_fq_data_id( device_id, 'hello_world_mutable') # can fetch latest mutable by name mutable_data = get_data("blockstack://bar.test/{}".format(data_id)) if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if json.loads(mutable_data['data']) != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['version'] != 1: print "wrong version: %s" % mutable_data['data']['version'] return False # can fetch by version mutable_data = get_data("blockstack://bar.test/{}#1".format(data_id)) if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if json.loads(mutable_data['data']) != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False # will fail to fetch if we give the wrong version try: mutable_data = get_data("blockstack://bar.test/{}#2".format(data_id)) print "mutable fetch by wrong version worked" print json.dumps(mutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can fetch mutable data put by URL data_id = blockstack_client.storage.make_fq_data_id(device_id, 'foo_data2') mutable_data = get_data("blockstack://foo.test/{}".format(data_id)) if 'error' in mutable_data or 'data' not in mutable_data or 'version' not in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if json.loads(mutable_data['data']) != {'hello2': 'world2'}: print "Invalid mutable data fetched from blockstack://foo.test/{}".format( data_id) print json.dumps(mutable_data, indent=4, sort_keys=True) return False # can fetch immutable data put by URL immutable_data = get_data("blockstack://foo_immutable.foo.test") if 'error' in immutable_data or 'hash' not in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if json.loads(immutable_data['data']) != {'hello3': 'world3'}: print "Invalid immutable data fetched from blockstack://foo_immutable.foo.test" print json.dumps(immutable_data, indent=4, sort_keys=True) return False # can fetch files and directories mutable_data = get_data( "blockstack://{}@foo-app.com/hello_datastore".format(datastore_name)) if 'error' in mutable_data or 'file' not in mutable_data or mutable_data[ 'file']['idata'] != 'hello datastore': print 'Failed to get blockstack://{}@foo-app.com/hello_datastore'.format( datastore_name) print json.dumps(mutable_data, indent=4, sort_keys=True) return False mutable_data = get_data( "blockstack://{}@foo-app.com/hello_dir/".format(datastore_name)) if 'error' in mutable_data or 'dir' not in mutable_data or 'hello_dir_datastore' not in mutable_data[ 'dir']['idata'].keys(): print 'Failed to get blockstack://{}@foo-app.com/hello_dir/'.format( datastore_name) print json.dumps(mutable_data, indent=4, sort_keys=True) return False mutable_data = get_data( "blockstack://{}@foo-app.com/hello_dir/hello_dir_datastore".format( datastore_name)) if 'error' in mutable_data or 'file' not in mutable_data or mutable_data[ 'file']['idata'] != 'hello dir datastore': print 'Failed to get blockstack://{}@foo-app.com/hello_dir/hello_dir_datastore'.format( datastore_name) print json.dumps(mutable_data, indent=4, sort_keys=True) return False return True
def get_name_profile(name, zonefile_storage_drivers=None, profile_storage_drivers=None, create_if_absent=False, proxy=None, user_zonefile=None, name_record=None, include_name_record=False, include_raw_zonefile=False, use_zonefile_urls=True, decode_profile=True): """ Given the name of the user, look up the user's record hash, and then get the record itself from storage. If the user's zonefile is really a legacy profile, then the profile will be the converted legacy profile. The returned zonefile will still be a legacy profile, however. The caller can check this and perform the conversion automatically. Returns (profile, zonefile) on success. If include_name_record is True, then zonefile['name_record'] will be defined and will contain the user's blockchain information Returns (None, {'error': ...}) on failure """ if proxy is None: proxy = get_default_proxy() raw_zonefile = None if user_zonefile is None: user_zonefile = get_name_zonefile( name, create_if_absent=create_if_absent, proxy=proxy, name_record=name_record, include_name_record=True, storage_drivers=zonefile_storage_drivers, include_raw_zonefile=include_raw_zonefile) if user_zonefile is None: return (None, {'error': 'No user zonefile'}) if 'error' in user_zonefile: return (None, user_zonefile) name_record = user_zonefile['name_record'] del user_zonefile['name_record'] raw_zonefile = None if include_raw_zonefile: raw_zonefile = user_zonefile['raw_zonefile'] del user_zonefile['raw_zonefile'] user_zonefile = user_zonefile['zonefile'] # is this really a legacy profile? if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): # convert it log.debug("Converting legacy profile to modern profile") user_profile = blockstack_profiles.get_person_from_legacy_format( user_zonefile) elif not user_db.is_user_zonefile(user_zonefile): # not a legacy profile, but a custom profile log.debug("Using custom legacy profile") user_profile = copy.deepcopy(user_zonefile) else: # get user's data public key user_address = None old_address = None try: user_data_pubkey = user_db.user_zonefile_data_pubkey(user_zonefile) if user_data_pubkey is not None: user_data_pubkey = str(user_data_pubkey) user_address = virtualchain.BitcoinPublicKey( user_data_pubkey).address() except ValueError: # user decided to put multiple keys under the same name into the zonefile. # so don't use them. user_data_pubkey = None # convert to address if name_record is None: name_record = proxy.get_name_blockchain_record(name) if name_record is None or 'error' in name_record: log.error("Failed to look up name record for '%s'" % name) return (None, {'error': 'Failed to look up name record'}) old_address = name_record['address'] if user_address is None: # cut to the chase user_address = old_address user_profile = load_name_profile( name, user_zonefile, user_address, old_address, use_zonefile_urls=use_zonefile_urls, storage_drivers=profile_storage_drivers, decode=decode_profile) if user_profile is not None: if decode_profile: assert isinstance(user_profile, dict) else: assert type(user_profile) in [str, unicode] else: log.debug("WARN: no user profile for %s" % name) if create_if_absent: user_profile = user_db.make_empty_user_profile() else: return (None, {'error': 'Failed to load user profile'}) # finally, if the caller asked for the name record, and we didn't get a chance to look it up, # then go get it. if include_name_record: if name_record is None: name_record = proxy.get_name_blockchain_record(name) if name_record is None or 'error' in name_record: log.error("Failed to look up name record for '%s'" % name) return (None, {'error': 'Failed to look up name record'}) user_zonefile['name_record'] = name_record if include_raw_zonefile: if raw_zonefile is not None: user_zonefile['raw_zonefile'] = raw_zonefile return (user_profile, user_zonefile)
def check( state_engine ): global wallet_keys, datasets, zonefile_hash # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered preorder = state_engine.get_name_preorder( "foo.test", pybitcoin.make_pay_to_address_script(wallets[2].addr), wallets[3].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( "foo.test" ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[3].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[3].addr): print "name has wrong owner" return False # have right hash if name_rec['value_hash'] != zonefile_hash: print "Invalid zonefile hash" return False # zonefile is NOT legacy user_zonefile = blockstack_client.zonefile.load_name_zonefile( 'foo.test', zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # zonefile has no key zonefile_key = blockstack_client.user.user_zonefile_data_pubkey(user_zonefile) if zonefile_key is not None: print 'still have zonefile key' print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # have right data test_proxy = testlib.TestAPIProxy() blockstack_client.set_default_proxy( test_proxy ) for i in xrange(0, len(datasets)): print "get hello_world_%s" % (i+1) dat = testlib.blockstack_cli_get_mutable( "foo.test", "hello_world_{}".format(i+1), public_key=wallets[4].pubkey_hex ) if dat is None: print "No data '%s'" % ("hello_world_%s" % (i+1)) return False if 'error' in dat: print json.dumps(dat, indent=4, sort_keys=True) return False if json.loads(dat['data']) != datasets[i]: print "Mismatch %s: %s != %s" % (i, dat, datasets[i]) return False res = testlib.blockstack_cli_lookup("foo.test") if 'error' in res: print 'error looking up profile: {}'.format(res) return False assert 'profile' in res assert 'zonefile' in res return True
def get_name_profile(name, zonefile_storage_drivers=None, profile_storage_drivers=None, create_if_absent=False, proxy=None, wallet_keys=None, user_zonefile=None, name_record=None, include_name_record=False, use_zonefile_urls=True, decode_profile=True ): """ Given the name of the user, look up the user's record hash, and then get the record itself from storage. If the user's zonefile is really a legacy profile, then the profile will be the converted legacy profile. The returned zonefile will still be a legacy profile, however. The caller can check this and perform the conversion automatically. Returns (profile, zonefile) on success. If include_name_record is True, then zonefile['name_record'] will be defined and will contain the user's blockchain information Returns (None, {'error': ...}) on failure """ if proxy is None: proxy = get_default_proxy() if user_zonefile is None: user_zonefile = get_name_zonefile( name, create_if_absent=create_if_absent, proxy=proxy, wallet_keys=wallet_keys, name_record=name_record, include_name_record=True, storage_drivers=zonefile_storage_drivers ) if user_zonefile is None: return (None, {'error': 'No user zonefile'}) if 'error' in user_zonefile: return (None, user_zonefile) name_record = user_zonefile['name_record'] del user_zonefile['name_record'] # is this really a legacy profile? if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): # convert it log.debug("Converting legacy profile to modern profile") user_profile = blockstack_profiles.get_person_from_legacy_format( user_zonefile ) elif not user_db.is_user_zonefile( user_zonefile ): # not a legacy profile, but a custom profile log.debug("Using custom legacy profile") user_profile = copy.deepcopy(user_zonefile) else: # get user's data public key user_address = None old_address = None try: user_data_pubkey = user_db.user_zonefile_data_pubkey( user_zonefile ) if user_data_pubkey is not None: user_data_pubkey = str(user_data_pubkey) user_address = pybitcoin.BitcoinPublicKey(user_data_pubkey).address() except ValueError: # user decided to put multiple keys under the same name into the zonefile. # so don't use them. user_data_pubkey = None # convert to address if name_record is None: name_record = proxy.get_name_blockchain_record( name ) if name_record is None or 'error' in name_record: log.error("Failed to look up name record for '%s'" % name) return (None, {'error': 'Failed to look up name record'}) old_address = name_record['address'] if user_address is None: # cut to the chase user_address = old_address user_profile = load_name_profile( name, user_zonefile, user_address, old_address, use_zonefile_urls=use_zonefile_urls, storage_drivers=profile_storage_drivers, decode=decode_profile ) if user_profile is None or (type(user_profile) not in [str, unicode] and 'error' in user_profile): if user_profile is None: log.debug("WARN: no user profile for %s" % name) else: log.debug("WARN: failed to load profile for %s: %s" % (name, user_profile['error'])) if create_if_absent: user_profile = user_db.make_empty_user_profile() else: return (None, {'error': 'Failed to load user profile'}) # finally, if the caller asked for the name record, and we didn't get a chance to look it up, # then go get it. if include_name_record: if name_record is None: name_record = proxy.get_name_blockchain_record( name ) if name_record is None or 'error' in name_record: log.error("Failed to look up name record for '%s'" % name) return (None, {'error': 'Failed to look up name record'}) user_zonefile['name_record'] = name_record return (user_profile, user_zonefile)
def run_cli(): """ run cli """ conf = config.get_config() if conf is None: log.error("Failed to load config") sys.exit(1) advanced_mode = conf['advanced_mode'] parser = argparse.ArgumentParser( description='Blockstack cli version {}'.format(config.VERSION)) parser.register('action', 'parsers', AliasedSubParsersAction) subparsers = parser.add_subparsers(dest='action') add_subparsers(subparsers) if advanced_mode == "on": add_advanced_subparsers(subparsers) # Print default help message, if no argument is given if len(sys.argv) == 1: parser.print_help() sys.exit(1) args, unknown_args = parser.parse_known_args() result = {} conf = config.get_config() blockstack_server = conf['server'] blockstack_port = conf['port'] proxy = client.session(conf=conf, server_host=blockstack_server, server_port=blockstack_port, set_global=True) # start the two background processes (rpc daemon and monitor queue) start_background_daemons() if args.action == 'balance': if not os.path.exists(WALLET_PATH): initialize_wallet() total_balance, addresses = get_total_balance() result['total_balance'] = total_balance if args.details: result['addresses'] = addresses elif args.action == 'price': fqu = str(args.name) check_valid_name(fqu) try: resp = client.get_name_cost(fqu) except socket_error: exit_with_error("Error connecting to server") if 'error' in resp: exit_with_error(resp['error']) data = get_total_fees(resp) result = data elif args.action == 'config': data = {} settings_updated = False data["message"] = "Updated settings for" if args.host is not None: config.update_config('blockstack-client', 'server', args.host) data["message"] += " host" settings_updated = True if args.port is not None: config.update_config('blockstack-client', 'port', args.port) data["message"] += " port" settings_updated = True if args.advanced is not None: if args.advanced != "on" and args.advanced != "off": exit_with_error("Use --advanced=on or --advanced=off") else: config.update_config('blockstack-client', 'advanced_mode', args.advanced) data["message"] += " advanced" settings_updated = True # reload conf conf = config.get_config() if settings_updated: result['message'] = data['message'] else: result['message'] = "No config settings were updated." elif args.action == 'deposit': if not os.path.exists(WALLET_PATH): initialize_wallet() result['message'] = 'Send bitcoins to the address specified.' result['address'], owner_address = get_addresses_from_file() elif args.action == 'import': if not os.path.exists(WALLET_PATH): initialize_wallet() result['message'] = 'Send the name you want to receive to the' result['message'] += ' address specified.' payment_address, result['address'] = get_addresses_from_file() elif args.action == 'names': if not os.path.exists(WALLET_PATH): initialize_wallet() result['names_owned'] = get_all_names_owned() if args.details: result['addresses'] = get_owner_addresses() elif args.action in ('info', 'status', 'ping', 'details'): resp = client.getinfo() result = {} result['server_host'] = conf['server'] result['server_port'] = str(conf['port']) result['cli_version'] = config.VERSION result['advanced_mode'] = conf['advanced_mode'] if 'error' in resp: result['server_alive'] = False result['server_error'] = resp['error'] else: result['server_alive'] = True result['server_version'] = resp['blockstore_version'] try: result['last_block_processed'] = resp['last_block'] except: result['last_block_processed'] = resp['blocks'] result['last_block_seen'] = resp['bitcoind_blocks'] result['consensus_hash'] = resp['consensus'] if advanced_mode == 'on': result['testset'] = resp['testset'] proxy = get_local_proxy() if proxy is not False: current_state = json.loads(proxy.state()) queue = {} pending_queue = [] preorder_queue = [] register_queue = [] update_queue = [] transfer_queue = [] def format_new_entry(entry): new_entry = {} new_entry['name'] = entry['fqu'] confirmations = get_tx_confirmations(entry['tx_hash']) if confirmations is None: confirmations = 0 new_entry['confirmations'] = confirmations return new_entry def format_queue_display(preorder_queue, register_queue): for entry in register_queue: name = entry['name'] for check_entry in preorder_queue: if check_entry['name'] == name: preorder_queue.remove(check_entry) for entry in current_state: if 'type' in entry: if entry['type'] == 'preorder': preorder_queue.append(format_new_entry(entry)) elif entry['type'] == 'register': register_queue.append(format_new_entry(entry)) elif entry['type'] == 'update': update_queue.append(format_new_entry(entry)) elif entry['type'] == 'transfer': transfer_queue.append(format_new_entry(entry)) format_queue_display(preorder_queue, register_queue) if len(preorder_queue) != 0: queue['preorder'] = preorder_queue if len(register_queue) != 0: queue['register'] = register_queue if len(update_queue) != 0: queue['update'] = update_queue if len(transfer_queue) != 0: queue['transfer'] = transfer_queue if queue != {}: result['queue'] = queue elif args.action == 'lookup': data = {} blockchain_record = None fqu = str(args.name) check_valid_name(fqu) try: blockchain_record = client.get_name_blockchain_record(fqu) except socket_error: exit_with_error("Error connecting to server.") if 'value_hash' not in blockchain_record: exit_with_error("%s is not registered" % fqu) data_id = blockchain_record['value_hash'] owner_address = blockchain_record['address'] profile = client.get_immutable(str(args.name), data_id)['data'] zone_file = profile profile = resolve_zone_file_to_profile(profile, owner_address) if not is_profile_in_legacy_format(profile): data['data_record'] = profile data['zone_file'] = zone_file else: data['data_record'] = json.loads(profile) #except Exception as e: # print e # data['data_record'] = None result = data elif args.action == 'whois': data = {} record = None fqu = str(args.name) check_valid_name(fqu) try: record = client.get_name_blockchain_record(fqu) except socket_error: exit_with_error("Error connecting to server.") if 'value_hash' not in record: result['registered'] = False else: result['registered'] = True result['block_preordered_at'] = record['preorder_block_number'] result['block_renewed_at'] = record['last_renewed'] result['owner_address'] = record['address'] result['owner_public_key'] = record['sender_pubkey'] result['owner_script'] = record['sender'] result['preorder_transaction_id'] = record['txid'] elif args.action == 'register': if not os.path.exists(WALLET_PATH): initialize_wallet() result = {} fqu = str(args.name) check_valid_name(fqu) cost = client.get_name_cost(fqu) if 'error' in cost: exit_with_error(cost['error']) if nameRegistered(fqu): exit_with_error("%s is already registered." % fqu) if not walletUnlocked(): unlock_wallet() fees = get_total_fees(cost) try: cost = fees['total_estimated_cost'] input_prompt = "Registering %s will cost %s BTC." % (fqu, cost) input_prompt += " Continue? (y/n): " user_input = raw_input(input_prompt) user_input = user_input.lower() if user_input != 'y': print "Not registering." exit(0) except KeyboardInterrupt: print "\nExiting." exit(0) payment_address, owner_address = get_addresses_from_file() if not hasEnoughBalance(payment_address, fees['total_estimated_cost']): msg = "Address %s doesn't have enough balance." % payment_address exit_with_error(msg) if recipientNotReady(owner_address): msg = "Address %s owns too many names already." % owner_address exit_with_error(msg) if dontuseAddress(payment_address): msg = "Address %s has pending transactions." % payment_address msg += " Wait and try later." exit_with_error(msg) proxy = get_local_proxy() try: resp = proxy.preorder(fqu) except: exit_with_error("Error talking to server, try again.") if 'success' in resp and resp['success']: result = resp else: if 'error' in resp: exit_with_error(resp['error']) if 'message' in resp: exit_with_error(resp['message']) elif args.action == 'update': if not os.path.exists(WALLET_PATH): initialize_wallet() fqu = str(args.name) check_valid_name(fqu) user_data = str(args.data) try: user_data = json.loads(user_data) except: exit_with_error("Data is not in JSON format.") tests_for_update_and_transfer(fqu) if profileonBlockchain(fqu, user_data): msg = "Data is same as current data record, update not needed." exit_with_error(msg) if not walletUnlocked(): unlock_wallet() proxy = get_local_proxy() try: resp = proxy.update(fqu, user_data) except: exit_with_error("Error talking to server, try again.") if 'success' in resp and resp['success']: result = resp else: if 'error' in resp: exit_with_error(resp['error']) if 'message' in resp: exit_with_error(resp['message']) elif args.action == 'transfer': if not os.path.exists(WALLET_PATH): initialize_wallet() fqu = str(args.name) check_valid_name(fqu) transfer_address = str(args.address) tests_for_update_and_transfer(fqu, transfer_address=transfer_address) if not walletUnlocked(): unlock_wallet() proxy = get_local_proxy() try: resp = proxy.transfer(fqu, transfer_address) except: exit_with_error("Error talking to server, try again.") if 'success' in resp and resp['success']: result = resp else: if 'error' in resp: exit_with_error(resp['error']) if 'message' in resp: exit_with_error(resp['message']) # ---------------------- Advanced options --------------------------------- elif args.action == 'wallet': if not os.path.exists(WALLET_PATH): result = initialize_wallet() else: unlock_wallet(display_enabled=True) elif args.action == 'consensus': if args.block_height is None: # by default get last indexed block resp = client.getinfo() if 'error' in resp: exit_with_error("Error connecting to server.") elif 'last_block' in resp or 'blocks' in resp: if 'last_block' in resp: args.block_height = client.getinfo()['last_block'] elif 'blocks' in resp: args.block_height = client.getinfo()['blocks'] else: result['error'] = "Server is indexing. Try again" exit(0) resp = client.get_consensus_at(int(args.block_height)) data = {} data['consensus'] = resp data['block_height'] = args.block_height result = data elif args.action == 'register_tx': result = client.register(str(args.name), str(args.privatekey), str(args.addr), tx_only=True) elif args.action == 'register_subsidized': result = client.register_subsidized(str(args.name), str(args.privatekey), str(args.addr), str(args.subsidy_key)) elif args.action == 'update_tx': txid = None if args.txid is not None: txid = str(args.txid) result = client.update(str(args.name), str(args.record_json), str(args.privatekey), txid=txid, tx_only=True) elif args.action == 'update_subsidized': txid = None if args.txid is not None: txid = str(args.txid) result = client.update_subsidized(str(args.name), str(args.record_json), str(args.public_key), str(args.subsidy_key), txid=txid) elif args.action == 'transfer_tx': keepdata = False if args.keepdata.lower() not in ["on", "false"]: print >> sys.stderr, "Pass 'true' or 'false' for keepdata" sys.exit(1) if args.keepdata.lower() == "on": keepdata = True result = client.transfer(str(args.name), str(args.address), keepdata, str(args.privatekey), tx_only=True) elif args.action == 'preorder': register_addr = None if args.address is not None: register_addr = str(args.address) result = client.preorder(str(args.name), str(args.privatekey), register_addr=register_addr) elif args.action == 'preorder_tx': register_addr = None if args.address is not None: register_addr = str(args.address) result = client.preorder(str(args.name), str(args.privatekey), register_addr=register_addr, tx_only=True) elif args.action == 'preorder_subsidized': result = client.preorder_subsidized(str(args.name), str(args.public_key), str(args.address), str(args.subsidy_key)) elif args.action == 'transfer_subsidized': keepdata = False if args.keepdata.lower() not in ["on", "false"]: print >> sys.stderr, "Pass 'true' or 'false' for keepdata" sys.exit(1) if args.keepdata.lower() == "on": keepdata = True result = client.transfer_subsidized(str(args.name), str(args.address), keepdata, str(args.public_key), str(args.subsidy_key)) elif args.action == 'renew': result = client.renew(str(args.name), str(args.privatekey)) elif args.action == 'renew_tx': result = client.renew(str(args.name), str(args.privatekey), tx_only=True) elif args.action == 'renew_subsidized': result = client.renew_subsidized(str(args.name), str(args.public_key), str(args.subsidy_key)) elif args.action == 'revoke': result = client.revoke(str(args.name), str(args.privatekey)) elif args.action == 'revoke_tx': result = client.revoke(str(args.name), str(args.privatekey), tx_only=True) elif args.action == 'revoke_subsidized': result = client.revoke_subsidized(str(args.name), str(args.public_key), str(args.subsidy_key)) elif args.action == 'name_import': result = client.name_import(str(args.name), str(args.address), str(args.hash), str(args.privatekey)) elif args.action == 'namespace_preorder': reveal_addr = None if args.address is not None: reveal_addr = str(args.address) result = client.namespace_preorder(str(args.namespace_id), str(args.privatekey), reveal_addr=reveal_addr) elif args.action == 'namespace_reveal': bucket_exponents = args.bucket_exponents.split(',') if len(bucket_exponents) != 16: raise Exception("bucket_exponents must be a 16-value CSV \ of integers") for i in xrange(0, len(bucket_exponents)): try: bucket_exponents[i] = int(bucket_exponents[i]) except: raise Exception("bucket_exponents must contain integers in \ range [0, 16)") lifetime = int(args.lifetime) if lifetime < 0: lifetime = 0xffffffff # means "infinite" to blockstack-server result = client.namespace_reveal(str(args.namespace_id), str(args.addr), lifetime, int(args.coeff), int(args.base), bucket_exponents, int(args.nonalpha_discount), int(args.no_vowel_discount), str(args.privatekey)) elif args.action == 'namespace_ready': result = client.namespace_ready(str(args.namespace_id), str(args.privatekey)) elif args.action == 'put_mutable': result = client.put_mutable(str(args.name), str(args.data_id), str(args.data), str(args.privatekey)) elif args.action == 'put_immutable': result = client.put_immutable(str(args.name), str(args.data), str(args.privatekey), conf=conf) elif args.action == 'get_mutable': result = client.get_mutable(str(args.name), str(args.data_id), conf=conf) elif args.action == 'get_immutable': result = client.get_immutable(str(args.name), str(args.hash)) elif args.action == 'delete_immutable': result = client.delete_immutable(str(args.name), str(args.hash), str(args.privatekey)) elif args.action == 'delete_mutable': result = client.delete_mutable(str(args.name), str(args.data_id), str(args.privatekey)) elif args.action == 'get_name_blockchain_record': result = client.get_name_blockchain_record(str(args.name)) elif args.action == 'get_namespace_blockchain_record': result = client.get_namespace_blockchain_record(str(args.namespace_id)) elif args.action == 'lookup_snv': result = client.lookup_snv(str(args.name), int(args.block_id), str(args.consensus_hash)) elif args.action == 'get_name_record': result = client.get_name_record(str(args.name)) elif args.action == 'get_names_owned_by_address': result = client.get_names_owned_by_address(str(args.address)) elif args.action == 'get_namespace_cost': result = client.get_namespace_cost(str(args.namespace_id)) elif args.action == 'get_all_names': offset = None count = None if args.offset is not None: offset = int(args.offset) if args.count is not None: count = int(args.count) result = client.get_all_names(offset, count) elif args.action == 'get_names_in_namespace': offset = None count = None if args.offset is not None: offset = int(args.offset) if args.count is not None: count = int(args.count) result = client.get_names_in_namespace(str(args.namespace_id), offset, count) elif args.action == 'get_nameops_at': result = client.get_nameops_at(int(args.block_id)) print_result(result)
def check( state_engine ): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2 # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i+1) - 1 wallet_owner = 3 * (i+1) wallet_data_pubkey = 3 * (i+1) + 1 wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.zonefile.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.profile.get_profile( name, user_zonefile=user_zonefile ) if user_profile is None or 'error' in user_profile: if user_profile is not None: print json.dumps(user_profile, indent=4, sort_keys=True) else: print "\n\nprofile is None\n\n" return False # can get mutable data res = testlib.blockstack_cli_get_mutable( "bar.test", "hello_world_mutable" ) print 'mutable: {}'.format(res) if 'error' in res: print json.dumps(res, indent=4, sort_keys=True) return False if json.loads(res['data']) != {'hello': 'world'}: print 'invalid data: {}'.format(res['data']) return False # can get immutable data by name res = testlib.blockstack_cli_get_immutable( 'foo.test', 'hello_world_immutable' ) print 'immutable by name: {}'.format(res) if 'error' in res: return res if json.loads(res['data']) != {'hello': 'world_immutable'}: print 'invalid immutable data: {}'.format(res['data']) return False # can get immutable data by hash hsh = res['hash'] res = testlib.blockstack_cli_get_immutable( 'foo.test', hsh ) print 'immutable: {}'.format(res) if 'error' in res: return res if json.loads(res['data']) != {'hello': 'world_immutable'}: print 'invalid immutable data by hash: {}'.format(res['data']) return False return True
def get_mutable(name, data_id, proxy=None, ver_min=None, ver_max=None, ver_check=None, conf=None, wallet_keys=None): """ get_mutable Fetch a piece of mutable data. Use @data_id to look it up in the user's profile, and then fetch and erify the data itself from the configured storage providers. If @ver_min is given, ensure the data's version is greater or equal to it. If @ver_max is given, ensure the data's version is less than it. If @ver_check is given, it must be a callable that takes the name, data and version and returns True/False Return {'data': the data, 'version': the version} on success Return {'error': ...} on error """ if proxy is None: proxy = get_default_proxy() if conf is None: conf = proxy.conf fq_data_id = storage.make_fq_data_id( name, data_id ) user_profile, user_zonefile = get_name_profile( name, proxy=proxy, wallet_keys=wallet_keys, include_name_record=True ) if user_profile is None: return user_zonefile # will be an error message # recover name record name_record = user_zonefile['name_record'] del user_zonefile['name_record'] if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ): # profile has not been converted to the new zonefile format yet. return {'error': 'Profile is in a legacy format that does not support mutable data.'} # get the mutable data zonefile if not user_db.has_mutable_data( user_profile, data_id ): return {'error': "No such mutable datum"} mutable_data_zonefile = user_db.get_mutable_data_zonefile( user_profile, data_id ) assert mutable_data_zonefile is not None, "BUG: could not look up mutable datum '%s'.'%s'" % (name, data_id) # get user's data public key and owner address data_pubkey = user_db.user_zonefile_data_pubkey( user_zonefile ) data_address = name_record['address'] if data_pubkey is None: log.warn("Falling back to owner address for authentication") # get the mutable data itself urls = user_db.mutable_data_zonefile_urls( mutable_data_zonefile ) mutable_data = storage.get_mutable_data(fq_data_id, data_pubkey, urls=urls, data_address=data_address ) if mutable_data is None: return {'error': "Failed to look up mutable datum"} expected_version = load_mutable_data_version( conf, name, data_id ) if expected_version is None: expected_version = 0 # check consistency version = user_db.mutable_data_version( user_profile, data_id ) if ver_min is not None and ver_min > version: return {'error': 'Mutable data is stale'} if ver_max is not None and ver_max <= version: return {'error': 'Mutable data is in the future'} if ver_check is not None: rc = ver_check( name, mutable_data, version ) if not rc: return {'error': 'Mutable data consistency check failed'} elif expected_version > version: return {'error': 'Mutable data is stale; a later version was previously fetched'} rc = store_mutable_data_version( conf, fq_data_id, version ) if not rc: return {'error': 'Failed to store consistency information'} return {'data': mutable_data, 'version': version}
def get_profile(name, zonefile_storage_drivers=None, profile_storage_drivers=None, proxy=None, user_zonefile=None, name_record=None, include_name_record=False, include_raw_zonefile=False, use_zonefile_urls=True, use_legacy=False, use_legacy_zonefile=True, decode_profile=True): """ Given a name, look up an associated profile. Do so by first looking up the zonefile the name points to, and then loading the profile from that zonefile's public key. Notes on backwards compatibility (activated if use_legacy=True and use_legacy_zonefile=True): * (use_legacy=True) If the user's zonefile is really a legacy profile from Onename, then the profile returned will be the converted legacy profile. The returned zonefile will still be a legacy profile, however. The caller can check this and perform the conversion automatically. * (use_legacy_zonefile=True) If the name points to a current zonefile that does not have a data public key, then the owner address of the name will be used to verify the profile's authenticity. Returns {'status': True, 'profile': profile, 'zonefile': zonefile, 'public_key': ...} on success. * If include_name_record is True, then include 'name_record': name_record with the user's blockchain information * If include_raw_zonefile is True, then include 'raw_zonefile': raw_zonefile with unparsed zone file Returns {'error': ...} on error """ proxy = get_default_proxy() if proxy is None else proxy user_profile_pubkey = None res = subdomains.is_address_subdomain(str(name)) if res: subdomain, domain = res[1] try: return subdomains.resolve_subdomain(subdomain, domain) except subdomains.SubdomainNotFound as e: log.exception(e) return { 'error': "Failed to find name {}.{}".format(subdomain, domain) } raw_zonefile = None if user_zonefile is None: user_zonefile = get_name_zonefile( name, proxy=proxy, name_record=name_record, include_name_record=True, storage_drivers=zonefile_storage_drivers, include_raw_zonefile=include_raw_zonefile, allow_legacy=True) if 'error' in user_zonefile: return user_zonefile raw_zonefile = None if include_raw_zonefile: raw_zonefile = user_zonefile.pop('raw_zonefile') user_zonefile = user_zonefile['zonefile'] # is this really a legacy profile? if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): if not use_legacy: return {'error': 'Profile is in legacy format'} # convert it log.debug('Converting legacy profile to modern profile') user_profile = blockstack_profiles.get_person_from_legacy_format( user_zonefile) elif not user_db.is_user_zonefile(user_zonefile): if not use_legacy: return {'error': 'Name zonefile is non-standard'} # not a legacy profile, but a custom profile log.debug('Using custom legacy profile') user_profile = copy.deepcopy(user_zonefile) else: # get user's data public key data_address, owner_address = None, None try: user_data_pubkey = user_db.user_zonefile_data_pubkey(user_zonefile) if user_data_pubkey is not None: user_data_pubkey = str(user_data_pubkey) data_address = keylib.ECPublicKey(user_data_pubkey).address() except ValueError: # multiple keys defined; we don't know which one to use user_data_pubkey = None if not use_legacy_zonefile and user_data_pubkey is None: # legacy zonefile without a data public key return {'error': 'Name zonefile is missing a public key'} # find owner address if name_record is None: name_record = get_name_blockchain_record(name, proxy=proxy) if name_record is None or 'error' in name_record: log.error( 'Failed to look up name record for "{}"'.format(name)) return {'error': 'Failed to look up name record'} assert 'address' in name_record.keys(), json.dumps(name_record, indent=4, sort_keys=True) owner_address = name_record['address'] # get user's data public key from the zonefile urls = None if use_zonefile_urls and user_zonefile is not None: urls = user_db.user_zonefile_urls(user_zonefile) user_profile = None user_profile_pubkey = None try: user_profile_res = storage.get_mutable_data( name, user_data_pubkey, blockchain_id=name, data_address=data_address, owner_address=owner_address, urls=urls, drivers=profile_storage_drivers, decode=decode_profile, return_public_key=True) user_profile = user_profile_res['data'] user_profile_pubkey = user_profile_res['public_key'] except Exception as e: log.exception(e) return {'error': 'Failure in parsing and fetching profile'} if user_profile is None or json_is_error(user_profile): if user_profile is None: log.error('no user profile for {}'.format(name)) else: log.error('failed to load profile for {}: {}'.format( name, user_profile['error'])) return {'error': 'Failed to load user profile'} # finally, if the caller asked for the name record, and we didn't get a chance to look it up, # then go get it. ret = { 'status': True, 'profile': user_profile, 'zonefile': user_zonefile, 'public_key': user_profile_pubkey } if include_name_record: if name_record is None: name_record = get_name_blockchain_record(name, proxy=proxy) if name_record is None or 'error' in name_record: log.error('Failed to look up name record for "{}"'.format(name)) return {'error': 'Failed to look up name record'} ret['name_record'] = name_record if include_raw_zonefile: if raw_zonefile is not None: ret['raw_zonefile'] = raw_zonefile return ret
def delete_immutable(name, data_key, data_id=None, proxy=None, txid=None, wallet_keys=None): """ delete_immutable Remove an immutable datum from a name's profile, given by @data_key. Return a dict with {'status': True} on success Return a dict with {'error': ...} on failure """ from backend.nameops import do_update if proxy is None: proxy = get_default_proxy() legacy = False user_zonefile = get_name_zonefile( name, proxy=proxy, include_name_record=True ) if user_zonefile is None or 'error' in user_zonefile: if user_zonefile is None: return {'error': 'No user zonefile'} else: return user_zonefile name_record = user_zonefile['name_record'] del user_zonefile['name_record'] if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ) or not user_db.is_user_zonefile( user_zonefile ): # zonefile is a legacy profile. There is no immutable data log.info("Profile is in legacy format. No immutable data.") return {'status': True} if data_key is None: if data_id is not None: # look up the key (or list of keys) # shouldn't be a list--this tool prevents that--but deal with it nevertheless data_key = user_db.get_immutable_data_hash( user_zonefile, data_id ) if type(data_key) == list: return {'error': "Multiple hashes for '%s': %s" % (data_id, ",".join(data_key)) } if data_key is None: return {'error': "No hash for '%s'" % data_id} else: return {'error': 'No data hash or data ID given'} # already deleted? if not user_db.has_immutable_data( user_zonefile, data_key ): return {'status': True} # remove user_db.remove_immutable_data_zonefile( user_zonefile, data_key ) zonefile_hash = hash_zonefile( user_zonefile ) if txid is None: # actually send the transaction _, payment_privkey = get_payment_keypair(wallet_keys=wallet_keys, config_path=proxy.conf['path']) _, owner_privkey = get_owner_keypair(wallet_keys=wallet_keys, config_path=proxy.conf['path']) utxo_client = get_utxo_provider_client( config_path=proxy.conf['path'] ) broadcaster_client = get_tx_broadcaster( config_path=proxy.conf['path'] ) update_result = do_update( name, zonefile_hash, owner_privkey, payment_privkey, utxo_client, broadcaster_client, config_path=proxy.conf['path'], proxy=proxy ) if 'error' in update_result: # failed to remove from zonefile return update_result txid = update_result['transaction_hash'] result = { 'zonefile_hash': zonefile_hash, 'transaction_hash': txid } # put new zonefile rc = store_name_zonefile( name, user_zonefile, txid ) if not rc: result['error'] = 'Failed to put new zonefile' return result # delete immutable data data_privkey = get_data_or_owner_privkey( user_zonefile, name_record['address'], wallet_keys=wallet_keys, config_path=proxy.conf['path'] ) if 'error' in data_privkey: return {'error': data_privkey['error']} else: data_privkey = data_privkey['privatekey'] assert data_privkey is not None rc = storage.delete_immutable_data( data_key, txid, data_privkey ) if not rc: result['error'] = 'Failed to delete immutable data' return result else: result['status'] = True return result
def check( state_engine ): global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2 # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered names = ['foo.test', 'bar.test'] wallet_keys_list = [wallet_keys, wallet_keys_2] zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]] for i in xrange(0, len(names)): name = names[i] wallet_payer = 3 * (i+1) - 1 wallet_owner = 3 * (i+1) wallet_data_pubkey = 3 * (i+1) # same as owner key wallet_keys = wallet_keys_list[i] zonefile_hash = zonefile_hashes[i] preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name( name ) if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): print "name has wrong owner" return False # zonefile is NOT legacy user_zonefile = blockstack_client.profile.load_name_zonefile( name, zonefile_hash ) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format( user_zonefile ): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False # still have all the right info user_profile = blockstack_client.profile.load_name_profile( name, user_zonefile, wallets[wallet_data_pubkey].addr, wallets[wallet_owner].addr ) if user_profile is None: print "Unable to load user profile for %s (%s)" % (name, wallets[wallet_data_pubkey].pubkey_hex) return False if 'error' in user_profile: print json.dumps(user_profile, indent=4, sort_keys=True) return False # can fetch latest by name immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/" ) if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if immutable_data['data'] != {'hello': 'world'}: print "immutable fetch-latest mismatch:\n%s (%s)\n%s" % (immutable_data['data'], type(immutable_data['data']), {'hello': 'world'}) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-latest hash mismatch: %s != %s" % (immutable_data['hash'], immutable_hash) return False # can fetch by name and hash immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % immutable_hash ) if 'error' in immutable_data: print json.dumps(immutable_data, indent=4, sort_keys=True) return False if immutable_data['data'] != {'hello': 'world'}: print "immutable fetch-by-hash mismatch:\n%s (%s)\n%s" % (immutable_data['data'], type(immutable_data['data']), {'hello': 'world'}) return False if immutable_data['hash'] != immutable_hash: print "immutable fetch-by-hash mismatch: %s != %s" % (immutable_data['hash'], immutable_hash) return False # hash must match (if we put the wrong hash, it must fail) try: immutable_data = get_data( "blockstack://hello_world_immutable.foo.test/#%s" % ("0" * len(immutable_hash))) print "no error" print json.dumps(immutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can list names and hashes immutable_data_list = get_data( "blockstack://foo.test/#immutable" ) if 'error' in immutable_data_list: print json.dumps(immutable_data, indent=4, sort_keys=True ) return False if len(immutable_data_list['data']) != 1: print "multiple immutable data" print json.dumps(immutable_data_list, indent=4, sort_keys=True ) return False if immutable_data_list['data'][0]['data_id'] != 'hello_world_immutable' or immutable_data_list['data'][0]['hash'] != immutable_hash: print "wrong data ID and/or hash" print json.dumps(immutable_data_list, indent=4, sort_keys=True ) return False # can fetch latest mutable by name mutable_data = get_data( "blockstack://bar.test/hello_world_mutable") if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['data'] != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['version'] != 1: print "wrong version: %s" % mutable_data['data']['version'] return False # can fetch by version mutable_data = get_data( "blockstack://bar.test/hello_world_mutable#1") if 'error' in mutable_data: print json.dumps(mutable_data, indent=4, sort_keys=True) return False if mutable_data['data'] != {'hello': 'world'}: print json.dumps(mutable_data, indent=4, sort_keys=True) return False # will fail to fetch if we give the wrong version try: mutable_data = get_data("blockstack://bar.test/hello_world_mutable#2") print "mutable fetch by wrong version worked" print json.dumps(mutable_data, indent=4, sort_keys=True) return False except urllib2.URLError: pass # can list mutable data mutable_data_list = get_data( "blockstack://bar.test/#mutable" ) if 'error' in mutable_data_list: print json.dumps(mutable_data_list, indent=4, sort_keys=True ) return False if len(mutable_data_list) != 1: print "multiple mutable data" print json.dumps(mutable_data_list, indent=4, sort_keys=True ) return False if mutable_data_list['data'][0]['data_id'] != 'hello_world_mutable' or mutable_data_list['data'][0]['version'] != 1: print "wrong data id and/or version" print json.dumps(mutable_data_list, indent=4, sort_keys=True) return False return True
def check(state_engine): global wallet_keys, datasets, zonefile_hash # not revealed, but ready ns = state_engine.get_namespace_reveal("test") if ns is not None: print "namespace not ready" return False ns = state_engine.get_namespace("test") if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered preorder = state_engine.get_name_preorder( "foo.test", pybitcoin.make_pay_to_address_script(wallets[2].addr), wallets[3].addr) if preorder is not None: print "still have preorder" return False # registered name_rec = state_engine.get_name("foo.test") if name_rec is None: print "name does not exist" return False # owned if name_rec['address'] != wallets[3].addr or name_rec[ 'sender'] != pybitcoin.make_pay_to_address_script(wallets[3].addr): print "name has wrong owner" return False # have right hash if name_rec['value_hash'] != zonefile_hash: print "Invalid zonefile hash" return False # zonefile is NOT legacy user_zonefile = blockstack_client.zonefile.load_name_zonefile( 'foo.test', zonefile_hash) if 'error' in user_zonefile: print json.dumps(user_zonefile, indent=4, sort_keys=True) return False if blockstack_profiles.is_profile_in_legacy_format(user_zonefile): print "legacy still" print json.dumps(user_zonefile, indent=4, sort_keys=True) return False res = testlib.blockstack_cli_lookup("foo.test") if 'error' in res: print 'error looking up profile: {}'.format(res) return False assert 'profile' in res assert 'zonefile' in res return True