class TestValidateWallet(unittest.TestCase): def setUp(self): self.testnet_api = BtcTxStore(dryrun=True, testnet=True) self.mainnet_api = BtcTxStore(dryrun=True, testnet=False) self.testnet_wallet = self.testnet_api.create_wallet() self.mainnet_wallet = self.mainnet_api.create_wallet() self.testnet_key = self.testnet_api.get_key(self.testnet_wallet) self.mainnet_key = self.mainnet_api.get_key(self.mainnet_wallet) def test_checks_networks(self): self.assertTrue(self.testnet_api.validate_wallet(self.testnet_wallet)) self.assertTrue(self.mainnet_api.validate_wallet(self.mainnet_wallet)) self.assertFalse(self.testnet_api.validate_wallet(self.mainnet_wallet)) self.assertFalse(self.mainnet_api.validate_wallet(self.testnet_wallet)) def test_doesnt_validate_keys(self): self.assertFalse(self.testnet_api.validate_wallet(self.testnet_key)) self.assertFalse(self.mainnet_api.validate_wallet(self.testnet_key)) self.assertFalse(self.testnet_api.validate_wallet(self.mainnet_key)) self.assertFalse(self.mainnet_api.validate_wallet(self.mainnet_key)) def test_correct_types(self): self.assertTrue(self.testnet_api.validate_wallet(S_HWIF)) self.assertTrue(self.testnet_api.validate_wallet(B_HWIF)) self.assertTrue(self.testnet_api.validate_wallet(U_HWIF))
class AppAuthenticationHeadersTest(unittest.TestCase): def setUp(self): app.config["SKIP_AUTHENTICATION"] = False # monkey patch self.app = app.test_client() self.btctxstore = BtcTxStore() db.create_all() def tearDown(self): db.session.remove() db.drop_all() def test_success(self): # create header date and authorization signature wif = self.btctxstore.create_key() btc_addr = self.btctxstore.get_address(wif) header_date = formatdate(timeval=mktime(datetime.now().timetuple()), localtime=True, usegmt=True) message = app.config["ADDRESS"] + " " + header_date header_authorization = self.btctxstore.sign_unicode(wif, message) headers = {"Date": header_date, "Authorization": header_authorization} url = '/api/register/{0}'.format(btc_addr) rv = self.app.get(url, headers=headers) data = json.loads(rv.data.decode("utf-8")) self.assertEqual(btc_addr, data["btc_addr"]) self.assertEqual(rv.status_code, 200) def test_fail(self): # register without auth headres fails btc_addr = self.btctxstore.get_address(self.btctxstore.get_key(self.btctxstore.create_wallet())) rv = self.app.get('/api/register/{0}'.format(btc_addr)) self.assertEqual(rv.status_code, 401) # register first because ping is lazy wif = self.btctxstore.get_key(self.btctxstore.create_wallet()) btc_addr = self.btctxstore.get_address(wif) header_date = formatdate(timeval=mktime(datetime.now().timetuple()), localtime=True, usegmt=True) message = app.config["ADDRESS"] + " " + header_date header_authorization = self.btctxstore.sign_unicode(wif, message) headers = {"Date": header_date, "Authorization": header_authorization} url = '/api/register/{0}'.format(btc_addr) rv = self.app.get(url, headers=headers) self.assertEqual(rv.status_code, 200) # ping without auth headres fails time.sleep(app.config["MAX_PING"]) rv = self.app.get('/api/ping/{0}'.format(btc_addr)) self.assertEqual(rv.status_code, 401) # set height without auth headres fails btc_addr = self.btctxstore.get_address(self.btctxstore.get_key(self.btctxstore.create_wallet())) rv = self.app.get('/api/height/{0}/10'.format(btc_addr)) self.assertEqual(rv.status_code, 401)
class TestGetWalletKey(unittest.TestCase): def setUp(self): self.api = BtcTxStore(dryrun=True, testnet=True) def test_standard(self): hwif = self.api.create_wallet() wif = self.api.get_key(hwif) self.assertTrue(validate.is_wif_valid(wif, allowable_netcodes=['XTN'])) def test_input_validation(self): # test correct types a = self.api.get_key(S_HWIF) b = self.api.get_key(B_HWIF) c = self.api.get_key(U_HWIF) self.assertEqual(a, b, c) # TODO invalid types # TODO invalid input data def test_standards_compliant(self): pass # FIXME check generated against expected output from 3rd parties
class Client(object): def __init__(self, url=common.DEFAULT_URL, debug=False, max_size=common.DEFAULT_MAX_SIZE, store_path=common.DEFAULT_STORE_PATH, config_path=common.DEFAULT_CONFIG_PATH, connection_retry_limit=common.DEFAULT_CONNECTION_RETRY_LIMIT, connection_retry_delay=common.DEFAULT_CONNECTION_RETRY_DELAY): self.url = url self.messanger = None # lazy self.debug = debug self.btctxstore = BtcTxStore() self.retry_limit = deserialize.positive_integer(connection_retry_limit) self.retry_delay = deserialize.positive_integer(connection_retry_delay) self.max_size = deserialize.byte_count(max_size) # paths self.cfg_path = os.path.realpath(config_path) self._ensure_path_exists(os.path.dirname(self.cfg_path)) self.store_path = os.path.realpath(store_path) self._ensure_path_exists(self.store_path) self.cfg = config.get(self.btctxstore, self.cfg_path) def _ensure_path_exists(self, path): if not os.path.exists(path): os.makedirs(path) def _init_messanger(self): if self.messanger is None: wif = self.btctxstore.get_key(self.cfg["wallet"]) self.messanger = messaging.Messaging(self.url, wif, self.retry_limit, self.retry_delay) def version(self): print(__version__) return __version__ def register(self): """Attempt to register the config address.""" self._init_messanger() registered = self.messanger.register(self.cfg["payout_address"]) auth_addr = self.messanger.auth_address() if registered: print("Address {0} now registered on {1}.".format( auth_addr, self.url)) else: print("Failed to register address {0} on {1}.".format( auth_addr, self.url)) return True def config(self, set_wallet=None, set_payout_address=None): """Display saved config.""" config_updated = False # update payout address if requested if set_payout_address: self.cfg["payout_address"] = set_payout_address config_updated = True # FIXME update dataserv here # update wallet if requested if set_wallet: self.cfg["wallet"] = set_wallet config_updated = True if config_updated: # save config if updated config.save(self.btctxstore, self.cfg_path, self.cfg) print(json.dumps(self.cfg, indent=2)) return self.cfg def ping(self): """Attempt keep-alive with the server.""" self._init_messanger() print("Pinging {0} with address {1}.".format( self.messanger.server_url(), self.messanger.auth_address())) self.messanger.ping() return True def poll(self, register_address=False, delay=common.DEFAULT_DELAY, limit=None): """TODO doc string""" stop_time = _now() + timedelta(seconds=int(limit)) if limit else None if register_address: self.register() while True: self.ping() if stop_time and _now() >= stop_time: return True time.sleep(int(delay)) def build(self, cleanup=False, rebuild=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL): """TODO doc string""" self._init_messanger() def _on_generate_shard(height, seed, file_hash): first = height == 1 set_height = (height % int(set_height_interval)) == 0 last = (int(self.max_size / common.SHARD_SIZE) + 1) == height if first or set_height or last: self.messanger.height(height) bldr = builder.Builder(self.cfg["payout_address"], common.SHARD_SIZE, self.max_size, debug=self.debug, on_generate_shard=_on_generate_shard) generated = bldr.build(self.store_path, cleanup=cleanup, rebuild=rebuild) height = len(generated) self.messanger.height(height) return generated
class FarmerUpTime(unittest.TestCase): def setUp(self): app.config["SKIP_AUTHENTICATION"] = True # monkey patch app.config["DISABLE_CACHING"] = True self.btctxstore = BtcTxStore() db.create_all() def tearDown(self): db.session.remove() db.drop_all() def test_register(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() test_json = { "height": 0, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 100 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) # reg_time = max online time -> 100% uptime delta = timedelta(minutes=app.config["ONLINE_TIME"]) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta test_json = { "height": 0, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": delta.seconds, "uptime": 100 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) # reg_time = 2 * max online time -> 50% uptime delta = timedelta(minutes=(2 * app.config["ONLINE_TIME"])) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.ping() test_json = { "height": 0, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 50 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) def test_ping_100(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() # lastest ping for 100% delta = timedelta(minutes=app.config["ONLINE_TIME"]) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.ping() test_json = { "height": 0, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 100 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) def test_ping_50(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() # lastest ping for 100% delta = timedelta(minutes=(2 * app.config["ONLINE_TIME"])) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.ping() test_json = { "height": 0, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 50 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) def test_ping_25(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() # ping to late -> 50% delta = timedelta(minutes=(4 * app.config["ONLINE_TIME"])) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.ping() test_json = { "height": 0, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 25 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) def test_ping_days(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() # ping to late -> 50% delta = timedelta(days=2) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.uptime = 86401 # 1 / 2 days farmer was online farmer.ping() test_json = { "height": 0, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 50 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) def test_height_100(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() # lastest ping for 100% delta = timedelta(minutes=app.config["ONLINE_TIME"]) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.set_height(100) test_json = { "height": 100, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 100 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) def test_height_50(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() # lastest ping for 100% delta = timedelta(minutes=(2 * app.config["ONLINE_TIME"])) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.set_height(50) test_json = { "height": 50, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 50 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload) def test_height_25(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() #ping to late -> 50% delta = timedelta(minutes=(4 * app.config["ONLINE_TIME"])) farmer.last_seen = datetime.utcnow() - delta farmer.reg_time = datetime.utcnow() - delta farmer.set_height(25) test_json = { "height": 25, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 25 } call_payload = json.loads(farmer.to_json()) call_payload["uptime"] = round(call_payload["uptime"]) self.assertEqual(test_json, call_payload)
class FarmerTest(unittest.TestCase): def setUp(self): app.config["SKIP_AUTHENTICATION"] = True # monkey patch app.config["DISABLE_CACHING"] = True self.btctxstore = BtcTxStore() self.bad_addr = 'notvalidaddress' db.create_all() def tearDown(self): db.session.remove() db.drop_all() def test_repr(self): farmer = Farmer('191GVvAaTRxLmz3rW3nU5jAV1rF186VxQc') ans = "<Farmer BTC Address: '191GVvAaTRxLmz3rW3nU5jAV1rF186VxQc'>" self.assertEqual(repr(farmer), ans) def test_sha256(self): an = 'c059c8035bbd74aa81f4c787c39390b57b974ec9af25a7248c46a3ebfe0f9dc8' self.assertEqual(sha256("storj"), an) self.assertNotEqual(sha256("not storj"), an) def test_register(self): # test success btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer1 = Farmer(btc_addr) self.assertFalse(farmer1.exists()) farmer1.register() self.assertTrue(farmer1.exists()) # test duplicate error self.assertRaises(LookupError, farmer1.register) def callback_a(): Farmer(self.bad_addr) self.assertRaises(ValueError, callback_a) def test_ping(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) # test ping before registration self.assertRaises(LookupError, farmer.ping) # register farmer farmer.register() # get register time, and make sure the ping work register_time = farmer.last_seen # ping faster than max_ping would be ignored time.sleep(app.config["MAX_PING"] + 1) farmer.ping() # update last seen ping_time = farmer.last_seen self.assertTrue(register_time < ping_time) def test_ping_time_limit(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() register_time = farmer.last_seen time.sleep(2) farmer.ping() # should still be around 0 delta_seconds = int((farmer.last_seen - register_time).seconds) self.assertEqual(delta_seconds, 0) def test_height(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() # set height and check function output self.assertEqual(farmer.height, 0) self.assertEqual(farmer.set_height(5), 5) self.assertEqual(farmer.height, 5) # check the db object as well farmer2 = farmer.lookup() self.assertEqual(farmer2.height, 5) def test_audit(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) # test audit before registration self.assertRaises(LookupError, farmer.audit) # register farmer farmer.register() # get register time, and make sure the ping work register_time = farmer.last_seen # ping faster than max_ping would be ignored time.sleep(app.config["MAX_PING"] + 1) farmer.audit() ping_time = farmer.last_seen self.assertTrue(register_time < ping_time) def test_to_json(self): btc_addr = self.btctxstore.get_address( self.btctxstore.get_key(self.btctxstore.create_wallet())) farmer = Farmer(btc_addr) farmer.register() farmer.ping() farmer.set_height(50) test_json = { "height": 50, "btc_addr": btc_addr, 'payout_addr': btc_addr, "last_seen": 0, "uptime": 100 } call_payload = json.loads(farmer.to_json()) self.assertEqual(test_json, call_payload)
class Client(object): def __init__(self, url=common.DEFAULT_URL, debug=False, quiet=False, use_folder_tree=False, max_size=common.DEFAULT_MAX_SIZE, store_path=common.DEFAULT_STORE_PATH, config_path=common.DEFAULT_CONFIG_PATH, connection_retry_limit=common.DEFAULT_CONNECTION_RETRY_LIMIT, connection_retry_delay=common.DEFAULT_CONNECTION_RETRY_DELAY): debug = deserialize.flag(debug) quiet = deserialize.flag(quiet) self.url = deserialize.url(url) self.use_folder_tree = deserialize.flag(use_folder_tree) self.max_size = deserialize.byte_count(max_size) self.messenger = None # lazy self.btctxstore = BtcTxStore() self.retry_limit = deserialize.positive_integer(connection_retry_limit) self.retry_delay = deserialize.positive_integer(connection_retry_delay) # paths self.cfg_path = os.path.realpath(config_path) control.util.ensure_path_exists(os.path.dirname(self.cfg_path)) self.store_path = os.path.realpath(store_path) control.util.ensure_path_exists(self.store_path) # check for vfat partions fstype = control.util.get_fs_type(self.store_path) if fstype == "vfat": logger.info("Detected vfat partition, using folder tree.") self.use_folder_tree = True if fstype is None: msg = "Couldn't detected partition type for '{0}'" logger.warning(msg.format(self.store_path)) self.cfg = control.config.get(self.btctxstore, self.cfg_path) @staticmethod def version(): print(__version__) return __version__ def _init_messenger(self): """Make sure messenger exists.""" if self.messenger is None: wif = self.btctxstore.get_key(self.cfg["wallet"]) self.messenger = messaging.Messaging(self.url, wif, self.retry_limit, self.retry_delay) def register(self): """Attempt to register the config address.""" self._init_messenger() payout_address = self.cfg["payout_address"] self.messenger.register(payout_address) logger.info("Registered on server '{0}'.".format(self.url)) return True def config(self, set_wallet=None, set_payout_address=None): """ Set and then show the config settings. :param set_wallet: Set the HWIF for registration/auth address. :param set_payout_address: Set the payout address. :return: Configuation object. """ if((set_payout_address is not None) and (not self.btctxstore.validate_address(set_payout_address))): raise exceptions.InvalidAddress(set_payout_address) if((set_wallet is not None) and (not self.btctxstore.validate_wallet(set_wallet))): raise exceptions.InvalidHWIF(set_wallet) self._init_messenger() config_updated = False # update payout address if requested if set_payout_address: self.cfg["payout_address"] = set_payout_address config_updated = True # update wallet if requested if set_wallet: self.cfg["wallet"] = set_wallet config_updated = True # save config if updated if config_updated: control.config.save(self.btctxstore, self.cfg_path, self.cfg) # display config print(SHOW_CONFIG_TEMPLATE.format( self.messenger.auth_address(), self.cfg["payout_address"] )) return self.cfg def ping(self): """Attempt one keep-alive with the server.""" self._init_messenger() msg = "Pinging server '{0}' at {1:%Y-%m-%d %H:%M:%S}." logger.info(msg.format(self.messenger.server_url(), datetime.now())) self.messenger.ping() return True def poll(self, delay=common.DEFAULT_DELAY, limit=None): """Attempt continuous keep-alive with the server. :param delay: Delay in seconds per ping of the server. :param limit: Number of seconds in the future to stop polling. :return: True, if limit is reached. None, if otherwise. """ delay = deserialize.positive_integer(delay) stop_time = None if limit is not None: stop_time = datetime.now() + timedelta(seconds=int(limit)) while True: # ping the server every X seconds self.ping() if stop_time and datetime.now() >= stop_time: return True time.sleep(int(delay)) def build(self, cleanup=False, rebuild=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL): """Generate test files deterministically based on address. :param cleanup: Remove files in shard directory. :param rebuild: Re-generate any file shards. :param set_height_interval: Number of shards to generate before notifying the server. """ set_height_interval = deserialize.positive_nonzero_integer( set_height_interval ) cleanup = deserialize.flag(cleanup) rebuild = deserialize.flag(rebuild) self._init_messenger() logger.info("Starting build") def _on_generate_shard(cur_height, cur_seed, cur_file_hash): """ Because URL requests are slow, only update the server when we are at the first height, at some height_interval, or the last height. :param cur_height: Current height in the building process. """ first = cur_height == 1 set_height = (cur_height % int(set_height_interval)) == 0 last = int(self.max_size / common.SHARD_SIZE) == cur_height if first or set_height or last: self.messenger.height(cur_height) logger.info("Current height at {0}.".format(cur_height)) # Initialize builder and generate/re-generate shards bldr = builder.Builder(self.cfg["payout_address"], common.SHARD_SIZE, self.max_size, on_generate_shard=_on_generate_shard, use_folder_tree=self.use_folder_tree) generated = bldr.build(self.store_path, cleanup=cleanup, rebuild=rebuild) logger.info("Build finished") return generated def farm(self, cleanup=False, rebuild=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL, delay=common.DEFAULT_DELAY, limit=None): """ Fully automatic client for users wishing a simple turnkey solution. This will run all functions automatically with the most sane defaults and as little user interface as possible. """ set_height_interval = deserialize.positive_nonzero_integer( set_height_interval ) cleanup = deserialize.flag(cleanup) rebuild = deserialize.flag(rebuild) # farmer never gives up self._init_messenger() self.messenger.retry_limit = 99999999999999999999999999999999999999 try: self.register() except exceptions.AddressAlreadyRegistered: pass # already registered ... self.build(cleanup=cleanup, rebuild=rebuild, set_height_interval=set_height_interval) self.poll(delay=delay, limit=limit) return True
class Client(object): def __init__(self, url=common.DEFAULT_URL, debug=False, max_size=common.DEFAULT_MAX_SIZE, store_path=common.DEFAULT_STORE_PATH, config_path=common.DEFAULT_CONFIG_PATH, set_master_secret=None, set_payout_address=None, connection_retry_limit=common.DEFAULT_CONNECTION_RETRY_LIMIT, connection_retry_delay=common.DEFAULT_CONNECTION_RETRY_DELAY): # FIXME validate master_secret self.url = url self.config = None # lazy self.messanger = None # lazy self.debug = debug # TODO validate self.retry_limit = deserialize.positive_integer(connection_retry_limit) self.retry_delay = deserialize.positive_integer(connection_retry_delay) self.max_size = deserialize.byte_count(max_size) # paths self.config_path = os.path.realpath(config_path) # TODO validate self._ensure_path_exists(os.path.dirname(self.config_path)) self.store_path = os.path.realpath(store_path) self._ensure_path_exists(self.store_path) # validate payout address self.btctxstore = BtcTxStore() if set_payout_address and (not self.btctxstore.validate_address(set_payout_address)): raise exceptions.InvalidAddress(set_payout_address) self._initialize_config(set_master_secret, set_payout_address) def _initialize_config(self, set_master_secret, set_payout_address): if os.path.exists(self.config_path): self._load_config() else: # initialize config if not set_master_secret: # create random master secret secret_data = base64.b64encode(os.urandom(256)) set_master_secret = secret_data.decode('utf-8') if not set_payout_address: # use root address if not given set_payout_address = self._get_root_address(set_master_secret) if set_master_secret or set_payout_address: self.config = { "version": __version__, "master_secret": set_master_secret, "payout_address": set_payout_address, } config.save(self.config_path, self.config) def _ensure_path_exists(self, path): if not os.path.exists(path): os.makedirs(path) def _load_config(self): if self.config is None: if not os.path.exists(self.config_path): raise exceptions.ConfigNotFound(self.config_path) self.config = config.load(self.config_path) def _get_root_wif(self, master_secret): master_secret = base64.b64decode(master_secret) hwif = self.btctxstore.create_wallet(master_secret=master_secret) return self.btctxstore.get_key(hwif) def _get_root_address(self, master_secret): wif = self._get_root_wif(master_secret) return self.btctxstore.get_address(wif) def _init_messanger(self): if self.messanger is None: wif = self._get_root_wif(self.config["master_secret"]) self.messanger = messaging.Messaging(self.url, wif, self.retry_limit, self.retry_delay) def version(self): print(__version__) return __version__ def register(self): """Attempt to register the config address.""" self._init_messanger() registered = self.messanger.register(self.config["payout_address"]) auth_addr = self.messanger.auth_address() if registered: print("Address {0} now registered on {1}.".format(auth_addr, self.url)) else: print("Failed to register address {0} on {1}.".format(auth_addr, self.url)) return True def show_config(self): """Display saved config.""" print(json.dumps(self.config, indent=2)) return self.config def ping(self): """Attempt keep-alive with the server.""" self._init_messanger() print("Pinging {0} with address {1}.".format( self.messanger.server_url(), self.messanger.auth_address())) self.messanger.ping() return True def poll(self, register_address=False, delay=common.DEFAULT_DELAY, limit=None): """TODO doc string""" stop_time = _now() + timedelta(seconds=int(limit)) if limit else None if register_address: self.register() while True: self.ping() if stop_time and _now() >= stop_time: return True time.sleep(int(delay)) def build(self, cleanup=False, rebuild=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL): """TODO doc string""" self._init_messanger() def _on_generate_shard(height, seed, file_hash): first = height == 1 set_height = (height % set_height_interval) == 0 last = (int(self.max_size / common.SHARD_SIZE) + 1) == height if first or set_height or last: self.messanger.height(height) bldr = builder.Builder(self.config["payout_address"], common.SHARD_SIZE, self.max_size, debug=self.debug, on_generate_shard=_on_generate_shard) generated = bldr.build(self.store_path, cleanup=cleanup, rebuild=rebuild) height = len(generated) self.messanger.height(height) return generated
class Client(object): def __init__(self, url=common.DEFAULT_URL, debug=False, quiet=False, use_folder_tree=False, max_size=common.DEFAULT_MAX_SIZE, min_free_size=common.DEFAULT_MIN_FREE_SIZE, store_path=common.DEFAULT_STORE_PATH, config_path=common.DEFAULT_CONFIG_PATH, connection_retry_limit=common.DEFAULT_CONNECTION_RETRY_LIMIT, connection_retry_delay=common.DEFAULT_CONNECTION_RETRY_DELAY): debug = deserialize.flag(debug) quiet = deserialize.flag(quiet) self.url = deserialize.url(url) self.use_folder_tree = deserialize.flag(use_folder_tree) self.max_size = deserialize.byte_count(max_size) self.min_free_size = deserialize.byte_count(min_free_size) self.messenger = None # lazy self.btctxstore = BtcTxStore() self.retry_limit = deserialize.positive_integer(connection_retry_limit) self.retry_delay = deserialize.positive_integer(connection_retry_delay) # paths self.cfg_path = os.path.realpath(config_path) storjnode.util.ensure_path_exists(os.path.dirname(self.cfg_path)) self.store_path = os.path.realpath(store_path) storjnode.util.ensure_path_exists(self.store_path) # check for vfat partions try: fstype = storjnode.util.get_fs_type(self.store_path) # FileNotFoundError: [Errno 2] No such file or directory: '/etc/mtab' # psutil: https://code.google.com/p/psutil/issues/detail?id=434 except EnvironmentError as e: logger.warning(e) fstype = None if fstype == "vfat": logger.info("Detected vfat partition, using folder tree.") self.use_folder_tree = True if fstype is None: msg = "Couldn't detected partition type for '{0}'" logger.warning(msg.format(self.store_path)) self.cfg = storjnode.config.get(self.btctxstore, self.cfg_path) @staticmethod def version(): print(__version__) return __version__ def _init_messenger(self): """Make sure messenger exists.""" if self.messenger is None: wif = self.btctxstore.get_key(self.cfg["wallet"]) self.messenger = messaging.Messaging(self.url, wif, self.retry_limit, self.retry_delay) def register(self): """Attempt to register the config address.""" self._init_messenger() payout_address = self.cfg["payout_address"] self.messenger.register(payout_address) logger.info("Registered on server '{0}'.".format(self.url)) return True def config(self, set_wallet=None, set_payout_address=None): """ Set and then show the config settings. :param set_wallet: Set the HWIF for registration/auth address. :param set_payout_address: Set the payout address. :return: Configuation object. """ if((set_payout_address is not None) and (not self.btctxstore.validate_address(set_payout_address))): raise exceptions.InvalidAddress(set_payout_address) if((set_wallet is not None) and (not self.btctxstore.validate_wallet(set_wallet))): raise exceptions.InvalidHWIF(set_wallet) self._init_messenger() config_updated = False # update payout address if requested if set_payout_address: self.cfg["payout_address"] = set_payout_address config_updated = True # update wallet if requested if set_wallet: self.cfg["wallet"] = set_wallet config_updated = True # save config if updated if config_updated: storjnode.config.save(self.btctxstore, self.cfg_path, self.cfg) # display config print(SHOW_CONFIG_TEMPLATE.format( self.messenger.get_nodeid(), self.cfg["payout_address"] )) return self.cfg def ping(self): """Attempt one keep-alive with the server.""" self._init_messenger() msg = "Pinging server '{0}' at {1:%Y-%m-%d %H:%M:%S}." logger.info(msg.format(self.messenger.server_url(), datetime.now())) self.messenger.ping() return True def poll(self, delay=common.DEFAULT_DELAY, limit=None): """Attempt continuous keep-alive with the server. :param delay: Delay in seconds per ping of the server. :param limit: Number of seconds in the future to stop polling. :return: True, if limit is reached. None, if otherwise. """ delay = deserialize.positive_integer(delay) stop_time = None if limit is not None: stop_time = datetime.now() + timedelta(seconds=int(limit)) while True: # ping the server every X seconds self.ping() if stop_time and datetime.now() >= stop_time: return True time.sleep(int(delay)) def freespace(self): freespace = psutil.disk_usage(self.store_path).free print(freespace) return freespace def build(self, workers=1, cleanup=False, rebuild=False, repair=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL): """Generate test files deterministically based on address. :param workers: Number of Number of threadpool workers. :param cleanup: Remove files in shard directory. :param rebuild: Re-generate any file shards. :param set_height_interval: Number of shards to generate before notifying the server. """ workers = deserialize.positive_nonzero_integer(workers) set_height_interval = deserialize.positive_nonzero_integer( set_height_interval ) cleanup = deserialize.flag(cleanup) rebuild = deserialize.flag(rebuild) repair = deserialize.flag(repair) self._init_messenger() logger.info("Starting build") def _on_generate_shard(cur_height, last): """ Because URL requests are slow, only update the server when we are at the first height, at some height_interval, or the last height. :param cur_height: Current height in the building process. """ first = cur_height == 1 set_height = (cur_height % int(set_height_interval)) == 0 if first or set_height or last: self.messenger.height(cur_height) logger.info("Current height at {0}.".format(cur_height)) # Initialize builder and generate/re-generate shards bldr = builder.Builder(address=self.cfg["payout_address"], shard_size=common.SHARD_SIZE, max_size=self.max_size, min_free_size=self.min_free_size, on_generate_shard=_on_generate_shard, use_folder_tree=self.use_folder_tree) generated = bldr.build(self.store_path, workers=workers, cleanup=cleanup, rebuild=rebuild, repair=repair) logger.info("Build finished") return generated def audit(self, delay=common.DEFAULT_AUDIT_DELAY, limit=None): self._init_messenger() # Initialize builder and audit shards bldr = builder.Builder(address=self.cfg["payout_address"], shard_size=common.SHARD_SIZE, max_size=self.max_size, min_free_size=self.min_free_size, use_folder_tree=self.use_folder_tree) delay = deserialize.positive_integer(delay) stop_time = None if limit is not None: stop_time = datetime.now() + timedelta(seconds=int(limit)) btc_index = 0 while True: btc_block = bldr.btc_last_confirmed_block( min_confirmations=common.DEFAULT_MIN_CONFIRMATIONS ) if btc_block['block_no'] != btc_index: btc_hash = btc_block['blockhash'] btc_index = btc_block['block_no'] logger.debug("Using bitcoin block {0} hash {1}.".format( btc_index, btc_hash)) wif = self.btctxstore.get_key(self.cfg["wallet"]) address = self.btctxstore.get_address(wif) response_data = address + btc_hash + str(bldr.audit( self.store_path, btc_block['block_no'], btc_block['blockhash'])) response = hashlib.sha256( response_data.encode('utf-8') ).hexdigest() # New Dataserv Server version is needed self.messenger.audit(btc_block['block_no'], response) else: msg = "Bitcoin block {0} already used. Waiting for new block." logger.debug(msg.format(btc_index)) if stop_time and datetime.now() >= stop_time: return True time.sleep(int(delay)) def farm(self, workers=1, cleanup=False, rebuild=False, repair=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL, delay=common.DEFAULT_DELAY, limit=None): """ Fully automatic client for users wishing a simple turnkey solution. This will run all functions automatically with the most sane defaults and as little user interface as possible. :param workers: Number of Number of threadpool workers. :param cleanup: Remove files in shard directory. :param rebuild: Re-generate any file shards. :param set_height_interval: Number of shards to generate before notifying the server. :param delay: Delay in seconds per ping of the server. :param limit: Number of seconds in the future to stop polling. """ workers = deserialize.positive_nonzero_integer(workers) set_height_interval = deserialize.positive_nonzero_integer( set_height_interval ) cleanup = deserialize.flag(cleanup) rebuild = deserialize.flag(rebuild) repair = deserialize.flag(repair) # farmer never gives up self._init_messenger() self.messenger.retry_limit = 99999999999999999999999999999999999999 try: self.register() except exceptions.AddressAlreadyRegistered: pass # already registered ... self.set_bandwidth() self.build(workers=workers, cleanup=cleanup, rebuild=rebuild, repair=repair, set_height_interval=set_height_interval) self.poll(delay=delay, limit=limit) return True def set_bandwidth(self): results = speedtest() self.messenger.set_bandwidth(results["upload"], results["download"])
class Client(object): def __init__(self, url=common.DEFAULT_URL, debug=False, quiet=False, use_folder_tree=False, max_size=common.DEFAULT_MAX_SIZE, store_path=common.DEFAULT_STORE_PATH, config_path=common.DEFAULT_CONFIG_PATH, connection_retry_limit=common.DEFAULT_CONNECTION_RETRY_LIMIT, connection_retry_delay=common.DEFAULT_CONNECTION_RETRY_DELAY): debug = deserialize.flag(debug) quiet = deserialize.flag(quiet) self.url = deserialize.url(url) self.use_folder_tree = deserialize.flag(use_folder_tree) self.max_size = deserialize.byte_count(max_size) self.messenger = None # lazy self.btctxstore = BtcTxStore() self.retry_limit = deserialize.positive_integer(connection_retry_limit) self.retry_delay = deserialize.positive_integer(connection_retry_delay) # paths self.cfg_path = os.path.realpath(config_path) control.util.ensure_path_exists(os.path.dirname(self.cfg_path)) self.store_path = os.path.realpath(store_path) control.util.ensure_path_exists(self.store_path) # check for vfat partions if control.util.get_fs_type(self.store_path) == "vfat": self.use_folder_tree = True self.cfg = control.config.get(self.btctxstore, self.cfg_path) @staticmethod def version(): print(__version__) return __version__ def _init_messenger(self): """Make sure messenger exists.""" if self.messenger is None: wif = self.btctxstore.get_key(self.cfg["wallet"]) self.messenger = messaging.Messaging(self.url, wif, self.retry_limit, self.retry_delay) def register(self): """Attempt to register the config address.""" self._init_messenger() payout_address = self.cfg["payout_address"] self.messenger.register(payout_address) logger.info("Registered on server '{0}'.".format(self.url)) return True def config(self, set_wallet=None, set_payout_address=None): """ Set and then show the config settings. :param set_wallet: Set the HWIF for registration/auth address. :param set_payout_address: Set the payout address. :return: Configuation object. """ if((set_payout_address is not None) and (not self.btctxstore.validate_address(set_payout_address))): raise exceptions.InvalidAddress(set_payout_address) if((set_wallet is not None) and (not self.btctxstore.validate_wallet(set_wallet))): raise exceptions.InvalidHWIF(set_wallet) self._init_messenger() config_updated = False # update payout address if requested if set_payout_address: self.cfg["payout_address"] = set_payout_address config_updated = True # update wallet if requested if set_wallet: self.cfg["wallet"] = set_wallet config_updated = True # save config if updated if config_updated: control.config.save(self.btctxstore, self.cfg_path, self.cfg) # display config print(SHOW_CONFIG_TEMPLATE.format( self.messenger.auth_address(), self.cfg["payout_address"] )) return self.cfg def ping(self): """Attempt one keep-alive with the server.""" self._init_messenger() msg = "Pinging server '{0}' at {1:%Y-%m-%d %H:%M:%S}." logger.info(msg.format(self.messenger.server_url(), datetime.now())) self.messenger.ping() return True def poll(self, delay=common.DEFAULT_DELAY, limit=None): """Attempt continuous keep-alive with the server. :param delay: Delay in seconds per ping of the server. :param limit: Number of seconds in the future to stop polling. :return: True, if limit is reached. None, if otherwise. """ delay = deserialize.positive_integer(delay) stop_time = None if limit: stop_time = datetime.now() + timedelta(seconds=int(limit)) while True: # ping the server every X seconds self.ping() if stop_time and datetime.now() >= stop_time: return True time.sleep(int(delay)) def build(self, cleanup=False, rebuild=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL): """Generate test files deterministically based on address. :param cleanup: Remove files in shard directory. :param rebuild: Re-generate any file shards. :param set_height_interval: Number of shards to generate before notifying the server. """ set_height_interval = deserialize.positive_nonzero_integer( set_height_interval ) cleanup = deserialize.flag(cleanup) rebuild = deserialize.flag(rebuild) self._init_messenger() logger.info("Starting build") def _on_generate_shard(cur_height, cur_seed, cur_file_hash): """ Because URL requests are slow, only update the server when we are at the first height, at some height_interval, or the last height. :param cur_height: Current height in the building process. """ first = cur_height == 1 set_height = (cur_height % int(set_height_interval)) == 0 last = int(self.max_size / common.SHARD_SIZE) == cur_height if first or set_height or last: self.messenger.height(cur_height) logger.info("Current height at {0}.".format(cur_height)) # Initialize builder and generate/re-generate shards bldr = builder.Builder(self.cfg["payout_address"], common.SHARD_SIZE, self.max_size, on_generate_shard=_on_generate_shard, use_folder_tree=self.use_folder_tree) generated = bldr.build(self.store_path, cleanup=cleanup, rebuild=rebuild) logger.info("Build finished") return generated def farm(self): """ Fully automatic client for users wishing a simple turnkey solution. This will run all functions automatically with the most sane defaults and as little user interface as possible. """ # farmer never gives up self._init_messenger() self.messenger.retry_limit = 99999999999999999999999999999999999999 try: self.register() except exceptions.AddressAlreadyRegistered: pass # already registered ... self.build() self.poll()
class Client(object): def __init__(self, url=common.DEFAULT_URL, debug=False, max_size=common.DEFAULT_MAX_SIZE, store_path=common.DEFAULT_STORE_PATH, config_path=common.DEFAULT_CONFIG_PATH, connection_retry_limit=common.DEFAULT_CONNECTION_RETRY_LIMIT, connection_retry_delay=common.DEFAULT_CONNECTION_RETRY_DELAY): self.url = url self.messanger = None # lazy self.debug = debug self.btctxstore = BtcTxStore() self.retry_limit = deserialize.positive_integer(connection_retry_limit) self.retry_delay = deserialize.positive_integer(connection_retry_delay) self.max_size = deserialize.byte_count(max_size) # paths self.cfg_path = os.path.realpath(config_path) self._ensure_path_exists(os.path.dirname(self.cfg_path)) self.store_path = os.path.realpath(store_path) self._ensure_path_exists(self.store_path) self.cfg = config.get(self.btctxstore, self.cfg_path) def _ensure_path_exists(self, path): if not os.path.exists(path): os.makedirs(path) def _init_messanger(self): if self.messanger is None: wif = self.btctxstore.get_key(self.cfg["wallet"]) self.messanger = messaging.Messaging(self.url, wif, self.retry_limit, self.retry_delay) def version(self): print(__version__) return __version__ def register(self): """Attempt to register the config address.""" self._init_messanger() registered = self.messanger.register(self.cfg["payout_address"]) auth_addr = self.messanger.auth_address() if registered: print("Address {0} now registered on {1}.".format(auth_addr, self.url)) else: print("Failed to register address {0} on {1}.".format(auth_addr, self.url)) return True def config(self, set_wallet=None, set_payout_address=None): """Display saved config.""" config_updated = False # update payout address if requested if set_payout_address: self.cfg["payout_address"] = set_payout_address config_updated = True # FIXME update dataserv here # update wallet if requested if set_wallet: self.cfg["wallet"] = set_wallet config_updated = True if config_updated: # save config if updated config.save(self.btctxstore, self.cfg_path, self.cfg) print(json.dumps(self.cfg, indent=2)) return self.cfg def ping(self): """Attempt keep-alive with the server.""" self._init_messanger() print("Pinging {0} with address {1}.".format( self.messanger.server_url(), self.messanger.auth_address())) self.messanger.ping() return True def poll(self, register_address=False, delay=common.DEFAULT_DELAY, limit=None): """TODO doc string""" stop_time = _now() + timedelta(seconds=int(limit)) if limit else None if register_address: self.register() while True: self.ping() if stop_time and _now() >= stop_time: return True time.sleep(int(delay)) def build(self, cleanup=False, rebuild=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL): """TODO doc string""" self._init_messanger() def _on_generate_shard(height, seed, file_hash): first = height == 1 set_height = (height % int(set_height_interval)) == 0 last = (int(self.max_size / common.SHARD_SIZE) + 1) == height if first or set_height or last: self.messanger.height(height) bldr = builder.Builder(self.cfg["payout_address"], common.SHARD_SIZE, self.max_size, debug=self.debug, on_generate_shard=_on_generate_shard) generated = bldr.build(self.store_path, cleanup=cleanup, rebuild=rebuild) height = len(generated) self.messanger.height(height) return generated
class Client(object): def __init__(self, url=common.DEFAULT_URL, debug=False, max_size=common.DEFAULT_MAX_SIZE, store_path=common.DEFAULT_STORE_PATH, config_path=common.DEFAULT_CONFIG_PATH, connection_retry_limit=common.DEFAULT_CONNECTION_RETRY_LIMIT, connection_retry_delay=common.DEFAULT_CONNECTION_RETRY_DELAY): self.url = url self.debug = debug self.max_size = deserialize.byte_count(max_size) self.messenger = None # lazy self.btctxstore = BtcTxStore() self.retry_limit = deserialize.positive_integer(connection_retry_limit) self.retry_delay = deserialize.positive_integer(connection_retry_delay) # paths self.cfg_path = os.path.realpath(config_path) self._ensure_path_exists(os.path.dirname(self.cfg_path)) self.store_path = os.path.realpath(store_path) self._ensure_path_exists(self.store_path) self.cfg = config.get(self.btctxstore, self.cfg_path) @staticmethod def version(): print(__version__) return __version__ @staticmethod def _ensure_path_exists(path): """To keep front writing to non-existant paths.""" if not os.path.exists(path): os.makedirs(path) def _init_messenger(self): """Make sure messenger exists.""" if self.messenger is None: wif = self.btctxstore.get_key(self.cfg["wallet"]) self.messenger = messaging.Messaging(self.url, wif, self.retry_limit, self.retry_delay) def register(self): """Attempt to register the config address.""" self._init_messenger() auth_address = self.messenger.auth_address() payout_address = self.cfg["payout_address"] registered = self.messenger.register(payout_address) if registered: msg = "Address {0} registered on {1} with payout address {2}." print(msg.format(auth_address, self.url, payout_address)) else: msg = ("Failed to register address {0} " "on {1} with payout address {2}!") print(msg.format(auth_address, self.url, payout_address)) return registered def config(self, set_wallet=None, set_payout_address=None): """ Set and then show the config settings. :param set_wallet: Set the HWIF for registration/auth address. :param set_payout_address: Set the payout address. :return: Configuation object. """ config_updated = False # update payout address if requested if set_payout_address: self.cfg["payout_address"] = set_payout_address config_updated = True # update wallet if requested if set_wallet: self.cfg["wallet"] = set_wallet config_updated = True # save config if updated if config_updated: config.save(self.btctxstore, self.cfg_path, self.cfg) print(json.dumps(self.cfg, indent=2)) # should we output this? return self.cfg def ping(self): """Attempt one keep-alive with the server.""" self._init_messenger() print("Pinging {0} with address {1}.".format( self.messenger.server_url(), self.messenger.auth_address())) self.messenger.ping() return True def poll(self, register_address=False, delay=common.DEFAULT_DELAY, limit=None): """ Attempt continuous keep-alive with the server. :param register_address: Registration/auth Bitcoin address. :param delay: Delay in seconds per ping of the server. :param limit: Number of seconds in the future to stop polling. :return: True, if limit is reached. None, if otherwise. """ stop_time = _now() + timedelta(seconds=int(limit)) if limit else None if register_address: # in case the user forgot to register self.register() while True: # ping the server every X seconds self.ping() if stop_time and _now() >= stop_time: return True time.sleep(int(delay)) def build(self, cleanup=False, rebuild=False, set_height_interval=common.DEFAULT_SET_HEIGHT_INTERVAL): """ Generate test files deterministically based on address. :param cleanup: Remove files in shard directory. :param rebuild: Re-generate any file shards. :param set_height_interval: Number of shards to generate before notifying the server. """ self._init_messenger() def _on_generate_shard(cur_height, cur_seed, cur_file_hash): """ Because URL requests are slow, only update the server when we are at the first height, at some height_interval, or the last height. :param cur_height: Current height in the building process. """ first = cur_height == 1 set_height = (cur_height % int(set_height_interval)) == 0 last = (int(self.max_size / common.SHARD_SIZE) + 1) == cur_height if first or set_height or last: self.messenger.height(cur_height) # Initialize builder and generate/re-generate shards bldr = builder.Builder(self.cfg["payout_address"], common.SHARD_SIZE, self.max_size, debug=self.debug, on_generate_shard=_on_generate_shard) generated = bldr.build(self.store_path, cleanup=cleanup, rebuild=rebuild) return generated
class TestConfig(unittest.TestCase): def setUp(self): self.btctxstore = BtcTxStore() def test_roundtrip_unencrypted(self): path = tempfile.mktemp() saved_data = config.create(self.btctxstore, path) loaded_data = config.get(self.btctxstore, path) self.assertEqual(saved_data, loaded_data) os.remove(path) def test_save_overwrites(self): path = tempfile.mktemp() # create config created_data = config.create(self.btctxstore, path) # update config updated_data = copy.deepcopy(created_data) updated_data["payout_address"] = "1A8WqiJDh3tGVeEefbMN5BVDYxx2XSoWgG" config.save(self.btctxstore, path, updated_data) # confirm overwriten loaded_data = config.get(self.btctxstore, path) self.assertEqual(updated_data, loaded_data) os.remove(path) def test_password_validation(self): pass # TODO implement def test_validation(self): wallet = self.btctxstore.create_wallet() key = self.btctxstore.get_key(wallet) address = self.btctxstore.get_address(key) # must be a dict def callback(): config.validate(self.btctxstore, None) self.assertRaises(exceptions.InvalidConfig, callback) # must have the correct version def callback(): config.validate(self.btctxstore, { "payout_address": address, "wallet": wallet, }) self.assertRaises(exceptions.InvalidConfig, callback) # must have a valid payout address def callback(): config.validate(self.btctxstore, { "version": __version__, "wallet": wallet, }) self.assertRaises(exceptions.InvalidConfig, callback) # must have a valid wallet def callback(): config.validate(self.btctxstore, { "version": __version__, "payout_address": address, }) self.assertRaises(exceptions.InvalidConfig, callback) # valid config self.assertTrue( config.validate( self.btctxstore, { "version": __version__, "payout_address": address, "wallet": wallet, })) def test_migrate(self): path = tempfile.mktemp() # initial unmigrated 2.0.0 config cfg = { "version": "2.0.0", "master_secret": "test_master_secret", "payout_address": "1A8WqiJDh3tGVeEefbMN5BVDYxx2XSoWgG", } # test its invalid with current build def callback(): config.validate(self.btctxstore, cfg) self.assertRaises(exceptions.InvalidConfig, callback) # migrate cfg = config.migrate(self.btctxstore, path, cfg) # test its now valid self.assertTrue(config.validate(self.btctxstore, cfg))