def put_immutable(name, data, privatekey, txid=None, proxy=None): """ put_immutable Optionally include a txid from the user record update, in order to retry a failed data replication (in which case, this txid corresponds to the succeeded name update operation). This is to avoid needing to pay for each replication retry. """ if proxy is None: global default_proxy proxy = default_proxy # need to put the transaction ID into the data record we put user = get_name_record(name, create_if_absent=True) if 'error' in user: # no user data return {'error': "Unable to load user record: %s" % user['error']} data_hash = storage.get_data_hash(data) rc = user_db.add_immutable_data(user, data_hash) if not rc: return {'error': 'Invalid hash'} user_json = user_db.serialize_user(user) if user_json is None: raise Exception("BUG: failed to serialize user record") value_hash = None if txid is None: # haven't updated the user record yet. Do so now. # put the new user record hash update_result = update(name, user_json, privatekey, proxy=proxy) if 'error' in update_result: # failed to replicate user record # NOTE: result will have the txid in it; pass it as txid to try again! return update_result txid = update_result['transaction_hash'] value_hash = update_result['value_hash'] result = {'data_hash': data_hash, 'transaction_hash': txid} # propagate update() data if value_hash is not None: result['value_hash'] = value_hash # replicate the data rc = storage.put_immutable_data(data, txid) if not rc: result['error'] = 'Failed to store immutable data' return result else: result['status'] = True return result
def delete_immutable(name, data_key, privatekey, proxy=None, txid=None): """ delete_immutable """ if proxy is None: proxy = get_default_proxy() result = {} user = get_name_record(name) if 'error' in user: # no user data return {'error': "Unable to load user record: %s" % user['error']} # does the user record have this data? if not user_db.has_immutable_data(user, data_key): # already deleted return {'status': True} # remove hash from the user record and update it user_db.remove_immutable_data(user, data_key) user_json = user_db.serialize_user(user) update_result = update(name, user_json, privatekey, txid=txid, proxy=proxy) if 'error' in update_result: # update failed; caller should try again return update_result txid = update_result['transaction_hash'] # remove the data itself data delete_result = storage.delete_immutable_data(data_key, txid) if delete_result: result['status'] = True else: # be sure to give back the update transaction hash, so this call can be retried result['error'] = 'Failed to delete immutable data' result['transaction_hash'] = txid result['value_hash'] = update_result['value_hash'] return result
def remove_name_record(user, txid): """ Delete JSON user record data from immutable storage providers, synchronously. Return (True, hash(user)) on success Return (False, hash(user)) on error """ username = user_db.name(user) # serialize user_json = None try: user_json = user_db.serialize_user(user) except Exception, e: log.error("Failed to serialize '%s'" % user) return False
def update(name, user_json_or_hash, privatekey, txid=None, proxy=None, tx_only=False, public_key=None, subsidy_key=None): """ update Optionally supply a txid. The reason for doing so is to try to replicate user data to new storage systems, or to recover from a transient error encountered earlier. """ # sanity check if privatekey is None and public_key is None: return {'error': 'Missing public and private key'} if proxy is None: proxy = get_default_proxy() user_record_hash = None user_data = None # 160-bit hex string? if len(user_json_or_hash) == 40 and len(user_json_or_hash.translate(None, "0123456789ABCDEFabcdef")) == 0: user_record_hash = user_json_or_hash.lower() else: # user record json. hash it user_data = user_db.parse_user(user_json_or_hash) if user_data is None: return {'error': 'Invalid user record JSON'} user_record_hash = pybitcoin.hash.hex_hash160(user_db.serialize_user(user_data)) # go get the current user record current_user_record = get_name_record( name, create_if_absent=True ) if current_user_record is None: return {'error': 'No such user'} if current_user_record.has_key('error'): # some other error return current_user_record result = {} old_hash = pybitcoin.hash.hex_hash160(user_db.serialize_user(user_data)) # only want transaction? if tx_only: if privatekey is None and public_key is not None and subsidy_key is not None: return proxy.update_tx_subsidized( name, user_record_hash, public_key, subsidy_key ) else: return proxy.update_tx( name, user_record_hash, privatekey ) # no transaction: go put one if txid is None: if tx_only: result = proxy.update_tx( name, user_record_hash, privatekey ) return result else: result = proxy.update(name, user_record_hash, privatekey) if 'error' in result: # failed return result if 'transaction_hash' not in result: # failed result['error'] = "No transaction hash given" return result txid = result['transaction_hash'] else: # embed the txid into the result nevertheless result['transaction_hash'] = txid # store new user data rc = True new_data_hash = None if user_data is not None: rc, new_data_hash = store_name_record(user_data, txid) else: # was already a hash new_data_hash = user_json_or_hash if not rc: result['error'] = "Failed to store updated user record." return result result['status'] = True result['value_hash'] = new_data_hash result['transaction_hash'] = txid return result
def delete_mutable(name, data_id, privatekey, proxy=default_proxy, txid=None, route=None): """ delete_mutable """ if proxy is None: proxy = get_default_proxy() result = {} user = get_name_record(name) if 'error' in user: # no user data return {'error': "Unable to load user record: %s" % user['error']} # does the user have a route to this data? if not user_db.has_mutable_data_route(user, data_id) and txid is None: # nope--we're good return {'status': True} # blow away the data storage_rc = storage.delete_mutable_data(data_id, privatekey) if not storage_rc: result['error'] = "Failed to delete mutable data" return result # remove the route from the user record user_db.remove_mutable_data_route(user, data_id) user_json = user_db.serialize_user(user) # update the user record update_status = update(name, user_json, privatekey, txid=txid, proxy=proxy) if 'error' in update_status: # failed; caller should try again return update_status if txid is None: txid = update_status['transaction_hash'] # blow away the route if route is None: route = user_db.get_mutable_data_route(user, data_id) route_hash = storage.get_mutable_data_route_hash(route) storage_rc = storage.delete_immutable_data(route_hash, txid) if not storage_rc: result['error'] = "Failed to delete immutable data route" result['route'] = route else: result['status'] = True result['transaction_hash'] = txid result['value_hash'] = update_status['value_hash'] # uncache local version delete_mutable_data_version( config.get_config(), data_id ) return result
def put_mutable(name, data_id, data_text, privatekey, proxy=None, create=True, txid=None, ver=None, make_ver=None, conf=None ): """ put_mutable ** Consistency ** ver, if given, is the version to include in the data. make_ver, if given, is a callback that takes the data_id, data_text, and current version as arguments, and generates the version to be included in the data record uploaded. If ver is not given, but make_ver is, then make_ver will be used to generate the version. If neither ver nor make_ver are given, the mutable data (if it already exists) is fetched, and the version is calculated as the larget known version + 1. ** Durability ** Replication is best-effort. If one storage provider driver succeeds, the put_mutable succeeds. If they all fail, then put_mutable fails. More complex behavior can be had by creating a "meta-driver" that calls existing drivers' methods in the desired manner. """ if proxy is None: proxy = get_default_proxy() result = {} user = get_name_record(name, create_if_absent=create) if 'error' in user: return {'error': "Unable to load user record: %s" % user['error']} route = None exists = user_db.has_mutable_data_route(user, data_id) old_hash = None cur_hash = None new_ver = ver if ver is None: if exists: # mutable record already exists. # generate one automatically. # use the existing locally-stored version, # and fall back to using the last-known version # from the existing mutable data record. new_ver = load_mutable_data_version( config.get_config(), name, data_id, try_remote=True ) if new_ver is None: # data exists, but we couldn't figure out the version return {'error': "Unable to determine version"} if make_ver is not None: # generate version new_ver = make_ver( data_id, data_text, new_ver ) else: # no version known, and no way to generate it. # by default, start at 1. we'll manage it ourselves. if new_ver is None: new_ver = 1 else: new_ver += 1 # do we have a route for this data yet? if not exists: if not create: # won't create; expect it to exist return {'error': 'No such route'} # need to put one urls = storage.make_mutable_urls(data_id) if len(urls) == 0: return {"error": "No routes constructed"} writer_pubkey = pybitcointools.privkey_to_pubkey(privatekey) route = storage.mutable_data_route(data_id, urls, writer_pubkey=writer_pubkey) user_db.add_mutable_data_route(user, route) user_json = user_db.serialize_user(user) # update the user record with the new route update_result = update(name, user_json, privatekey, txid=txid, proxy=proxy) if 'error' in update_result: # update failed; caller should try again return update_result txid = update_result['transaction_hash'] cur_hash = update_result['value_hash'] else: route = user_db.get_mutable_data_route(user, data_id) if route is None: return {"error": "No such route"} # generate the data data = storage.mutable_data(data_id, data_text, new_ver, privkey=privatekey) if data is None: return {"error": "Failed to generate data record"} # serialize... data_json = parsing.json_stable_serialize(data) # replicate... store_rc = storage.put_mutable_data( data, privatekey ) if not store_rc: result['error'] = "Failed to store mutable data" else: result['status'] = True result['transaction_hash'] = txid if cur_hash: # propagate result['value_hash'] = cur_hash # cache new version store_mutable_data_version( conf, data_id, new_ver ) return result
def put_immutable(name, data, privatekey, txid=None, proxy=None): """ put_immutable Optionally include a txid from the user record update, in order to retry a failed data replication (in which case, this txid corresponds to the succeeded name update operation). This is to avoid needing to pay for each replication retry. """ if proxy is None: global default_proxy proxy = default_proxy # need to put the transaction ID into the data record we put user = get_name_record(name, create_if_absent=True) if 'error' in user: # no user data return {'error': "Unable to load user record: %s" % user['error']} data_hash = storage.get_data_hash( data ) rc = user_db.add_immutable_data(user, data_hash) if not rc: return {'error': 'Invalid hash'} user_json = user_db.serialize_user(user) if user_json is None: raise Exception("BUG: failed to serialize user record") value_hash = None if txid is None: # haven't updated the user record yet. Do so now. # put the new user record hash update_result = update(name, user_json, privatekey, proxy=proxy) if 'error' in update_result: # failed to replicate user record # NOTE: result will have the txid in it; pass it as txid to try again! return update_result txid = update_result['transaction_hash'] value_hash = update_result['value_hash'] result = { 'data_hash': data_hash, 'transaction_hash': txid } # propagate update() data if value_hash is not None: result['value_hash'] = value_hash # replicate the data rc = storage.put_immutable_data(data, txid) if not rc: result['error'] = 'Failed to store immutable data' return result else: result['status'] = True return result
def update(name, user_json_or_hash, privatekey, txid=None, proxy=None, tx_only=False, public_key=None, subsidy_key=None): """ update Optionally supply a txid. The reason for doing so is to try to replicate user data to new storage systems, or to recover from a transient error encountered earlier. """ # sanity check if privatekey is None and public_key is None: return {'error': 'Missing public and private key'} if proxy is None: proxy = get_default_proxy() user_record_hash = None user_data = None # 160-bit hex string? if len(user_json_or_hash) == 40 and len( user_json_or_hash.translate(None, "0123456789ABCDEFabcdef")) == 0: user_record_hash = user_json_or_hash.lower() else: # user record json. hash it user_data = user_db.parse_user(user_json_or_hash) if user_data is None: return {'error': 'Invalid user record JSON'} user_record_hash = pybitcoin.hash.hex_hash160( user_db.serialize_user(user_data)) # go get the current user record current_user_record = get_name_record(name, create_if_absent=True) if current_user_record is None: return {'error': 'No such user'} if current_user_record.has_key('error'): # some other error return current_user_record result = {} old_hash = pybitcoin.hash.hex_hash160(user_db.serialize_user(user_data)) # only want transaction? if tx_only: if privatekey is None and public_key is not None and subsidy_key is not None: return proxy.update_tx_subsidized(name, user_record_hash, public_key, subsidy_key) else: return proxy.update_tx(name, user_record_hash, privatekey) # no transaction: go put one if txid is None: if tx_only: result = proxy.update_tx(name, user_record_hash, privatekey) return result else: result = proxy.update(name, user_record_hash, privatekey) if 'error' in result: # failed return result if 'transaction_hash' not in result: # failed result['error'] = "No transaction hash given" return result txid = result['transaction_hash'] else: # embed the txid into the result nevertheless result['transaction_hash'] = txid # store new user data rc = True new_data_hash = None if user_data is not None: rc, new_data_hash = store_name_record(user_data, txid) else: # was already a hash new_data_hash = user_json_or_hash if not rc: result['error'] = "Failed to store updated user record." return result result['status'] = True result['value_hash'] = new_data_hash result['transaction_hash'] = txid return result
def delete_mutable(name, data_id, privatekey, proxy=default_proxy, txid=None, route=None): """ delete_mutable """ if proxy is None: proxy = get_default_proxy() result = {} user = get_name_record(name) if 'error' in user: # no user data return {'error': "Unable to load user record: %s" % user['error']} # does the user have a route to this data? if not user_db.has_mutable_data_route(user, data_id) and txid is None: # nope--we're good return {'status': True} # blow away the data storage_rc = storage.delete_mutable_data(data_id, privatekey) if not storage_rc: result['error'] = "Failed to delete mutable data" return result # remove the route from the user record user_db.remove_mutable_data_route(user, data_id) user_json = user_db.serialize_user(user) # update the user record update_status = update(name, user_json, privatekey, txid=txid, proxy=proxy) if 'error' in update_status: # failed; caller should try again return update_status if txid is None: txid = update_status['transaction_hash'] # blow away the route if route is None: route = user_db.get_mutable_data_route(user, data_id) route_hash = storage.get_mutable_data_route_hash(route) storage_rc = storage.delete_immutable_data(route_hash, txid) if not storage_rc: result['error'] = "Failed to delete immutable data route" result['route'] = route else: result['status'] = True result['transaction_hash'] = txid result['value_hash'] = update_status['value_hash'] # uncache local version delete_mutable_data_version(config.get_config(), data_id) return result
def put_mutable(name, data_id, data_text, privatekey, proxy=None, create=True, txid=None, ver=None, make_ver=None, conf=None): """ put_mutable ** Consistency ** ver, if given, is the version to include in the data. make_ver, if given, is a callback that takes the data_id, data_text, and current version as arguments, and generates the version to be included in the data record uploaded. If ver is not given, but make_ver is, then make_ver will be used to generate the version. If neither ver nor make_ver are given, the mutable data (if it already exists) is fetched, and the version is calculated as the larget known version + 1. ** Durability ** Replication is best-effort. If one storage provider driver succeeds, the put_mutable succeeds. If they all fail, then put_mutable fails. More complex behavior can be had by creating a "meta-driver" that calls existing drivers' methods in the desired manner. """ if proxy is None: proxy = get_default_proxy() result = {} user = get_name_record(name, create_if_absent=create) if 'error' in user: return {'error': "Unable to load user record: %s" % user['error']} route = None exists = user_db.has_mutable_data_route(user, data_id) old_hash = None cur_hash = None new_ver = ver if ver is None: if exists: # mutable record already exists. # generate one automatically. # use the existing locally-stored version, # and fall back to using the last-known version # from the existing mutable data record. new_ver = load_mutable_data_version(config.get_config(), name, data_id, try_remote=True) if new_ver is None: # data exists, but we couldn't figure out the version return {'error': "Unable to determine version"} if make_ver is not None: # generate version new_ver = make_ver(data_id, data_text, new_ver) else: # no version known, and no way to generate it. # by default, start at 1. we'll manage it ourselves. if new_ver is None: new_ver = 1 else: new_ver += 1 # do we have a route for this data yet? if not exists: if not create: # won't create; expect it to exist return {'error': 'No such route'} # need to put one urls = storage.make_mutable_urls(data_id) if len(urls) == 0: return {"error": "No routes constructed"} writer_pubkey = pybitcointools.privkey_to_pubkey(privatekey) route = storage.mutable_data_route(data_id, urls, writer_pubkey=writer_pubkey) user_db.add_mutable_data_route(user, route) user_json = user_db.serialize_user(user) # update the user record with the new route update_result = update(name, user_json, privatekey, txid=txid, proxy=proxy) if 'error' in update_result: # update failed; caller should try again return update_result txid = update_result['transaction_hash'] cur_hash = update_result['value_hash'] else: route = user_db.get_mutable_data_route(user, data_id) if route is None: return {"error": "No such route"} # generate the data data = storage.mutable_data(data_id, data_text, new_ver, privkey=privatekey) if data is None: return {"error": "Failed to generate data record"} # serialize... data_json = parsing.json_stable_serialize(data) # replicate... store_rc = storage.put_mutable_data(data, privatekey) if not store_rc: result['error'] = "Failed to store mutable data" else: result['status'] = True result['transaction_hash'] = txid if cur_hash: # propagate result['value_hash'] = cur_hash # cache new version store_mutable_data_version(conf, data_id, new_ver) return result