def __init__(self, fname=None, search="", opts=""): Transactions.__init__(self) if not fname: fname = c['ledger-file'] self.search = search self.opts = opts self.fname = fname self.name = self.fname
def test_push_tx(alice, bob, alice_secret, blockr): from transactions import Transactions transactions = Transactions(testnet=True) raw_tx = transactions.create(alice, (bob, 1), min_confirmations=1) signed_tx = transactions.sign(raw_tx, alice_secret) txid = blockr.push_tx(signed_tx) assert txid
def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ Args: testnet (bool): Whether to use the mainnet or testnet. Defaults to the mainnet (:const:`False`). service (str): Bitcoin communication interface: ``'blockr'``, ``'daemon'``, or ``'regtest'``. ``'blockr'`` refers to the public api, whereas ``'daemon'`` and ``'regtest'`` refer to the jsonrpc inteface. Defaults to ``'blockr'``. username (str): username for jsonrpc communications password (str): password for jsonrpc communications hostname (str): hostname of the bitcoin node when using jsonrpc port (str): port number of the bitcoin node when using jsonrpc """ self.testnet = testnet self._netcode = 'XTN' if testnet else 'BTC' self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port) # simple cache for spent outputs. Useful so that rapid firing transactions don't use the same outputs self._spents = Queue(maxsize=self.SPENTS_QUEUE_MAXSIZE)
def test_pay_fees(self): alg = self._init_test() self._prefill_db(alg) trs = Transactions(FILE_NAME) fnds = Funds(FILE_NAME) accs = alg.pay_fees() self.assertEqual(400.0, alg.portfolio_total_fund) accounts = [ (1, '01', 0.0, accs[0][3], 0, 0.7, 0.7, 1, accs[0][-1]), (2, '02', 0.375, accs[1][3], 50, 0.3, 0.9, 1, accs[1][-1]), (3, '03', 0.125, accs[2][3], -50, 0.3, 0.9, 1, accs[2][-1]), (4, '04', 0.5, accs[3][3], 0, 0.3, 0.9, 1, accs[3][-1]), ] self.assertEqual(accounts, accs) acc_trs = trs.retrieve_transactions_for_account(account_id=2) self.assertEqual(50, acc_trs[-1][1]) acc_trs = trs.retrieve_transactions_for_account(account_id=3) self.assertEqual(-50, acc_trs[-1][1]) acc_trs = trs.retrieve_transactions_for_account(account_id=4) self.assertEqual(-50, acc_trs[-2][1]) self.assertEqual(50, acc_trs[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=2) self.assertEqual(150, acc_fnds[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=3) self.assertEqual(50, acc_fnds[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=4) self.assertEqual(150, acc_fnds[-2][1]) self.assertEqual(200, acc_fnds[-1][1])
def test_transaction_creation_via_create_with_blockr(alice, bob): from transactions import Transactions trxs = Transactions(testnet=True) assert trxs.testnet is True simple_transaction = trxs.create(alice, (bob, 6), min_confirmations=1) assert simple_transaction
def transaction_checking(grid: MultiCircuit, transactions: Transactions, agent_id_to_grid_id, dt=1): """ Function that checks the transactions with electrical computations :param grid: GridCal grid Object :param transactions: Transactions object :param agent_id_to_grid_id: dictionary to relate the agent's id with the grid object's id :param dt: market interval in hours :return: """ # declare the final transactions list final_transactions = Transactions() for transaction in transactions: hash = transaction_mine(grid, transaction, agent_id_to_grid_id, dt=dt) # if there are no errors if hash is not None: # modify the transaction, adding a hash based on the voltage transaction.hash = hash # store the transaction final_transactions.append(transaction) else: # since the transactions are sorted, if a critical state is found the rest are curtailed return final_transactions # return the approved transactions return final_transactions
def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ Args: testnet (bool): Whether to use the mainnet or testnet. Defaults to the mainnet (:const:`False`). service (str): Bitcoin communication interface: ``'blockr'``, ``'daemon'``, or ``'regtest'``. ``'blockr'`` refers to the public api, whereas ``'daemon'`` and ``'regtest'`` refer to the jsonrpc inteface. Defaults to ``'blockr'``. username (str): username for jsonrpc communications password (str): password for jsonrpc communications hostname (str): hostname of the bitcoin node when using jsonrpc port (str): port number of the bitcoin node when using jsonrpc """ self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port)
def initialize_federation_wallet(): FederationWallet = apps.get_model(app_label='bitcoin', model_name='FederationWallet') print 'Resetting the FederationWallet' FederationWallet.objects.all().delete() # Reset the ids after deleting all the rows cursor = connection.cursor() cursor.execute( "ALTER SEQUENCE bitcoin_federationwallet_id_seq RESTART WITH 1") print 'Populating federation wallet with unspents' transactions = Transactions(service='daemon', username=settings.BTC_USERNAME, password=settings.BTC_PASSWORD, host=settings.BTC_HOST, port=settings.BTC_PORT, testnet=settings.BTC_TESTNET) unspents = transactions.get(settings.BTC_MAIN_WALLET, min_confirmations=1).get('unspents', []) for unspent in unspents: if unspent['amount'] in [settings.BTC_TOKEN, settings.BTC_FEE]: # main wallet unspent.update({'type': 'main'}) FederationWallet(**unspent).save() unspents = transactions.get(settings.BTC_REFILL_ADDRESS, min_confirmations=1).get('unspents', []) for unspent in unspents: if unspent['amount'] == settings.BTC_CHUNK: # main wallet unspent.update({'type': 'refill'}) FederationWallet(**unspent).save() print 'Finished initializing wallet'
def get_txs(self): txs = Transactions() for field in self.section_names: field = field.lower() if field in self: txs.extend(self[field]) return txs
def test_transaction_creation_via_simple_transaction_with_blockr(alice, bob): from transactions import Transactions trxs = Transactions(testnet=True) assert trxs.testnet is True simple_transaction = trxs.simple_transaction(alice, (bob, 6), min_confirmations=1) assert simple_transaction
def test_sums_transactions_by_sku(self): rates = Rates([]) transactions = [ {"store": "Store", "sku": "SKU123", "amount": Decimal("1.1"), "currency": "FOO"}, {"store": "Store", "sku": "SKU123", "amount": Decimal("1.2"), "currency": "FOO"}, {"store": "Store", "sku": "SKU123", "amount": Decimal("1.3"), "currency": "FOO"}, {"store": "Store", "sku": "SKU234", "amount": Decimal("5.0"), "currency": "FOO"}] trans = Transactions(rates, transactions) self.assertEqual(trans.total("SKU123", "FOO"), Decimal("3.6"))
def delete_transaction(): if request.method == 'POST': id = request.form['transaction-id'] transaction_instance = Transactions() transaction_instance.delete_transaction(id) return redirect('/transactions') else: return render_template('delete_transaction.html')
def add_new_transaction(): if request.method == 'POST': total = request.form['transaction-total'] method = request.form['transaction-method'] transaction_instance = Transactions() transaction_instance.add_transaction(total, method) return redirect('/transactions') else: return render_template('add_new_transaction.html')
def test_converts_transactions_to_currency(self): rates = Rates([ {"from": "FOO", "to": "BAR", "conversion": Decimal("0.98")}, {"from": "BAR", "to": "BAZ", "conversion": Decimal("1.02")}]) transactions = [ {"store": "Store", "sku": "SKU123", "amount": Decimal("1.1"), "currency": "FOO"}, {"store": "Store", "sku": "SKU123", "amount": Decimal("1.2"), "currency": "BAR"}, {"store": "Store", "sku": "SKU123", "amount": Decimal("1.3"), "currency": "BAZ"}] trans = Transactions(rates, transactions) self.assertEqual(trans.total("SKU123", "FOO"), Decimal('3.62501'))
def __init__(self, **kwargs): self.rename_pdfs() self.statements = [] self.statements_dir = kwargs['statements-dir'] self.bank_name = kwargs['bank-name'] self.name = kwargs['name'] self.ledger_account = kwargs['ledger-account'] self.account_num = None self.config = c['banks'][self.bank_name]['accounts'][self.name] Transactions.__init__(self)
def check_status(): """ Checks the status of the federation and refill wallet every hour. Actions performed: 1. In the refill wallets create refill chunks of 2 160 000 satoshi if number of chunks: - 50 * 30000 (fee) + 150 * 3000 (token) + 210 000 (tx fee) - if number of refill chunks < 10 create 5 chunks 2. Refill federation wallet if num fees < 500 or num tokens < 1500: - refill with 2 refill chunks 3. If refill wallet < 0.3 bitcoin which is approximately 10 refill chunks - Send email warning of low funds in the Refill wallet - Ask trent for das money """ # We cannot use base=SpoolAction because it does not work with periodic tasks transactions = Transactions(service=settings.BTC_SERVICE, testnet=settings.BTC_TESTNET, username=settings.BTC_USERNAME, password=settings.BTC_PASSWORD, host=settings.BTC_HOST, port=settings.BTC_PORT) # check refill wallet logger.info('checking refill wallet') unspents = transactions.get(settings.BTC_REFILL_ADDRESS, min_confirmations=1).get('unspents', []) refill_chunks = filter(lambda d: d['amount'] == settings.BTC_CHUNK, unspents) if len(refill_chunks) < 10: # create_refill_chunks -> create 5 refill_chunks logger.info('refill wallet low on refill chunks. Creating ...') txid = create_refill_chunks.delay() logger.info(txid) unspents = transactions.get(settings.BTC_REFILL_ADDRESS, min_confirmations=1).get('unspents', []) sum_unspents = sum([u['amount'] for u in unspents]) if sum_unspents < 30000000: # TODO: we don't want this to be sent every hour, maybe logger.info('funds low in refill wallet. Sending email...') send_low_funds_email.delay(sum_unspents) # check federation wallet unspents = transactions.get(settings.BTC_MAIN_WALLET, min_confirmations=1).get('unspents', []) fees = filter(lambda d: d['amount'] == settings.BTC_FEE, unspents) tokens = filter(lambda d: d['amount'] == settings.BTC_TOKEN, unspents) if len(fees) < 500 or len(tokens) < 1500: # refill federation wallet x 10 times if we have chunks logger.info('federation wallet low on funds. Refilling...') for _ in range(min(2, len(refill_chunks))): refill_main_wallet.delay() initialize_federation_wallet.delay()
def test_create_transaction(self): if os.path.isfile(FILE_NAME): os.remove(FILE_NAME) create_db(FILE_NAME) trs = Transactions(FILE_NAME) c = trs.create_transaction(-20000, 1) ts = c[2] self.assertEqual((1, -20000, ts, 1), c) os.remove(FILE_NAME)
def test_raw_push_tx(alice, bob, alice_secret, blockr): from transactions import Transactions transactions = Transactions(testnet=True) raw_tx = transactions.create(alice, (bob, 1), min_confirmations=1) signed_tx = transactions.sign(raw_tx, alice_secret) response = blockr.push_tx(signed_tx, raw=True) assert response.status_code == 200 assert response.json()['status'] == 'success' assert response.json()['message'] == '' assert response.json()['code'] == 200 assert response.json()['data']
def rescan(): print 'Rescanning the blockchain. This may take several minutes...' transactions = Transactions(service=settings.BTC_SERVICE, testnet=settings.BTC_TESTNET, username=settings.BTC_USERNAME, password=settings.BTC_PASSWORD, host=settings.BTC_HOST, port=settings.BTC_PORT) main_address = BitcoinWallet.mainAdminBtcAddress() print 'Sending rescan command to {} main address {}'.format( settings.BTC_HOST, main_address) transactions.import_address(main_address, 'mainaddress', rescan=True)
def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ :param testnet: :return: """ self.testnet = testnet self._netcode = 'XTN' if testnet else 'BTC' self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port) # simple cache for spent outputs. Useful so that rapid firing transactions don't use the same outputs self._spents = Queue(maxsize=50)
def test_retrieve_id(self): if os.path.isfile(FILE_NAME): os.remove(FILE_NAME) create_db(FILE_NAME) trs = Transactions(FILE_NAME) trs.create_transaction(10, 1) c = trs.retrieve_id(id=1) ts = c[2] self.assertEqual((1, 10, ts, 1), c) os.remove(FILE_NAME)
def test_retrieve_transactions(self): if os.path.isfile(FILE_NAME): os.remove(FILE_NAME) create_db(FILE_NAME) trs = Transactions(FILE_NAME) trs.create_transaction(10, 1) trs.create_transaction(20, 1) c = trs.retrieve_transactions() ts1 = c[0][2] ts2 = c[1][2] self.assertEqual([(1, 10, ts1, 1), (2, 20, ts2, 1)], c) os.remove(FILE_NAME)
def setUp(self): try: # flag to run the tests test = os.environ['TEST_SPOOL'] if test == '2': username = os.environ['TESTNET_USERNAME'] password = os.environ['TESTNET_PASSWORD'] host = os.environ['TESTNET_HOST'] port = os.environ['TESTNET_PORT'] self.refill_pass = os.environ['TEST_REFILL_PASS'] self.federation_pass = os.environ['TEST_FEDERATION_PASS'] self.refill_root = Wallet(self.refill_pass, testnet=True).root_address self.federation_root = Wallet(self.federation_pass, testnet=True).root_address except KeyError: raise unittest.SkipTest('TEST_REFILL_PASS and/or TEST_FEDERATION_PASS environment variables are not set.') # set TEST_SPOOL=2 to test with bitcoind if test == '2': print 'using bitcoind' self.t = Transactions(testnet=True, service='daemon', username=username, password=password, host=host, port=port) self.spool = Spool(testnet=True, service='daemon', username=username, password=password, host=host, port=port) else: print 'using blockr' self.t = Transactions(testnet=True) self.spool = Spool(testnet=True) self.user1_pass = self._get_pass() self.user2_pass = self._get_pass() self.user3_pass = self._get_pass() self.user1_root = Wallet(self.user1_pass, testnet=True).root_address self.user1_leaf = Wallet(self.user1_pass, testnet=True).address_from_path() self.user2_leaf = Wallet(self.user2_pass, testnet=True).address_from_path() self.user3_leaf = Wallet(self.user3_pass, testnet=True).address_from_path() self.file_hash = self._get_file_hash() print 'user1_pass: '******'user2_pass: '******'user3_pass: '******'user1_root: ', self.user1_root print 'user1_leaf: ', self.user1_leaf print 'user2_leaf: ', self.user2_leaf print 'user3_leaf: ', self.user3_leaf print 'file_hash :', self.file_hash self.spool._t.import_address(self.user1_root[1], "test",) self.spool._t.import_address(self.user1_leaf[1], "test",) self.spool._t.import_address(self.user2_leaf[1], "test",) self.spool._t.import_address(self.user3_leaf[1], "test",) self.spool._t.import_address(self.file_hash[0], "test",) self.spool._t.import_address(self.file_hash[1], "test",)
def wallet_status(): print 'Checking Federation wallet status...' transactions = Transactions(service=settings.BTC_SERVICE, testnet=settings.BTC_TESTNET, username=settings.BTC_USERNAME, password=settings.BTC_PASSWORD, host=settings.BTC_HOST, port=settings.BTC_PORT) main_address = BitcoinWallet.mainAdminBtcAddress() unspents = transactions.get(main_address, min_confirmations=1)['unspents'] fees = 0 tokens = 0 for u in unspents: if u['amount'] == settings.BTC_TOKEN: tokens += 1 elif u['amount'] == settings.BTC_FEE: fees += 1 print "Wallet {} has {} tokens and {} fees".format(main_address, tokens, fees)
def check_unconfirmed_transactions(): # Check for unconfirmed transactions and set status to 2 if confirmed transactions = Transactions() count = 0 for t in BitcoinTransaction.objects.filter(Q(status=0) | Q(status=1), tx__isnull=False): try: conf = transactions.get(t.tx).get('confirmations', 0) except Exception as e: conf = 0 if conf > 0: t.status = 2 t.save() count += 1 print "Set status of {} transactions to 2".format(count)
def test_import_address(self): t = Transactions(service=settings.BTC_SERVICE, testnet=settings.BTC_TESTNET, username=settings.BTC_USERNAME, password=settings.BTC_PASSWORD, host=settings.BTC_HOST, port=settings.BTC_PORT) with self.settings(BTC_ENABLED=False): address = BitcoinWallet.walletForUser( self.user1).create_new_address() BitcoinWallet.import_address(address, self.user1).delay() address = address.split(':')[1] # with btc disabled the address should not be imported response = t._service.make_request('getaddressesbyaccount', [self.user1.email]) self.assertIsNone(response['error']) self.assertFalse(address in response['result']) # lets import it import_address(address, self.user1.email) response = t._service.make_request('getaddressesbyaccount', [self.user1.email]) self.assertIsNone(response['error']) self.assertTrue(address in response['result']) # lets create a new address with btc enabled address = BitcoinWallet.walletForUser(self.user1).create_new_address() BitcoinWallet.import_address(address, self.user1).delay() address = address.split(':')[1] response = t._service.make_request('getaddressesbyaccount', [self.user1.email]) self.assertIsNone(response['error']) self.assertTrue(address in response['result'])
def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ :param testnet: testnet flag. Defaults to False :return: An instance of the BlockainSpider """ self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port)
def fight_bandit(player, bandit): if skill_check(player.fighter): # successful fight attempt # take the bandits credits winnings = int(bandit["demand"] * (5 / 4)) player.credit += winnings player.karma += 2 player.transaction_history.append(Transactions("Bandit loot", winnings, "loot", "earnings")) return True else: # fail fight attempt # lose 1/3 credits and get damaged losings = int(player.credit / 3) player.credit = losings player.ship.health_level -= 20 player.transaction_history.append(Transactions("Bandit fee", losings, "fees", "expenses")) return False
def purchase_fuel(self, fuel): cost = fuel * self.fuel_cost if self.credit < cost: return "Not enough cash" elif self.ship.fuel_level == self.ship.max_fuel: return "Already fully fueled!" elif self.ship.fuel_level + fuel > self.ship.max_fuel: new_fuel = self.ship.max_fuel - self.ship.fuel_level cost = new_fuel * self.fuel_cost self.credit -= cost self.ship.fuel_level = self.ship.max_fuel self.transaction_history.append(Transactions("fuel", cost, "fuel", "expenses")) return "Success" else: self.credit -= cost self.ship.refuel(fuel) self.transaction_history.append(Transactions("fuel", cost, "fuel", "expenses")) return "Success"
def transactions(rpcuser, rpcpassword, host, port): return Transactions( service='daemon', username=rpcuser, password=rpcpassword, host=host, port=port, testnet=True, )
def test_create_refill_chunks(self, mock_unsubscribe, mock_subscribe): t = Transactions(service=settings.BTC_SERVICE, testnet=settings.BTC_TESTNET, username=settings.BTC_USERNAME, password=settings.BTC_PASSWORD, host=settings.BTC_HOST, port=settings.BTC_PORT) unspents = t.get(settings.BTC_REFILL_ADDRESS, min_confirmations=1).get('unspents', []) refill_chuncks_before = filter( lambda d: d['amount'] == settings.BTC_CHUNK, unspents) create_refill_chunks.delay() unspents = t.get(settings.BTC_REFILL_ADDRESS, min_confirmations=1).get('unspents', []) refill_chuncks_after = filter( lambda d: d['amount'] == settings.BTC_CHUNK, unspents) self.assertEqual(len(refill_chuncks_after), len(refill_chuncks_before) + 5)
def buy_repairs(self, repairs): cost = utility.repair_cost(repairs, self.engineer) if self.credit < cost: return "Not enough Money!" elif self.ship.health_level == self.ship.max_health: return "Already fully repaired!" elif self.ship.health_level + repairs > self.ship.max_health: damage = self.ship.max_health - self.ship.health_level cost = utility.repair_cost(damage, self.engineer) self.credit -= cost self.ship.health_level = self.ship.max_health self.transaction_history.append(Transactions("repairs", cost, "repairs", "expenses")) return "Success" else: self.credit -= cost self.ship.health_level += repairs self.transaction_history.append(Transactions("repairs", cost, "repairs", "expenses")) return "Success"
def __init__(self, agent_id_to_grid_id, fname='Grid.xlsx', dt=1): self.current_transactions = Transactions() self.chain = [] self.nodes = set() self.agent_id_to_grid_id = agent_id_to_grid_id self.actors_group = ActorsGroup() self.market = Market(actors_group=self.actors_group) self.dt = dt self.grid = MultiCircuit() self.grid.load_file(fname) # Create the genesis block self.new_block(previous_hash='1', proof=100)
def test_init_transactions_class(srv, srv_mod_name, srv_cls_name, is_testnet): from transactions import Transactions service_module = import_module( '.{}'.format(srv_mod_name), package='transactions.services') service_class = getattr(service_module, srv_cls_name) trxs = Transactions(service=srv) assert trxs.testnet is is_testnet assert isinstance(trxs._service, service_class) assert trxs._min_tx_fee == trxs._service._min_transaction_fee assert trxs._dust == trxs._service._min_dust
def test_make_transaction(self): alg = self._init_test() self._prefill_db(alg) trs = Transactions(FILE_NAME) fnds = Funds(FILE_NAME) accs = alg.make_transaction('01', 100) self.assertEqual(500.0, alg.portfolio_total_fund) accounts = [ (1, '01', 0.2, accs[0][3], 0, 0.7, 0.7, 1, accs[0][-1]), (2, '02', 0.2, accs[1][3], 50, 0.3, 0.9, 1, accs[1][-1]), (3, '03', 0.2, accs[2][3], -50, 0.3, 0.9, 1, accs[2][-1]), (4, '04', 0.4, accs[3][3], 0, 0.3, 0.9, 1, accs[3][-1]), ] self.assertEqual(accounts, accs) acc_trs = trs.retrieve_transactions_for_account(account_id=1) self.assertEqual(100, acc_trs[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=1) self.assertEqual(100, acc_fnds[-1][1]) accs = alg.make_transaction('02', -100) self.assertEqual(400.0, alg.portfolio_total_fund) accounts = [ (1, '01', 0.25, accs[0][3], 0, 0.7, 0.7, 1, accs[0][-1]), (2, '02', 0.0, accs[1][3], 50, 0.3, 0.9, 1, accs[1][-1]), (3, '03', 0.25, accs[2][3], -50, 0.3, 0.9, 1, accs[2][-1]), (4, '04', 0.5, accs[3][3], 0, 0.3, 0.9, 1, accs[3][-1]), ] self.assertEqual(accounts, accs) acc_trs = trs.retrieve_transactions_for_account(account_id=2) self.assertEqual(-100, acc_trs[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=2) self.assertEqual(0, acc_fnds[-1][1]) accs = alg.make_transaction('03', -150) self.assertEqual(400.0, alg.portfolio_total_fund) self.assertEqual(False, accs) acc_trs = trs.retrieve_transactions_for_account(account_id=3) self.assertEqual(0, len(acc_trs)) acc_fnds = fnds.retrieve_funds_for_account(account_id=3) self.assertEqual(100, acc_fnds[-1][1])
def collection(): if request.method == "POST": if "addCredits" in request.form: category = request.form["addCredits"] index = state["game"].player.collection.category.index(category) state["game"].player.collection.complete[index] = True state["game"].player.credit += 100 state["game"].player.transaction_history.append(Transactions("Collection reward", 100, "collection", "earnings")) return str(state["game"].player.credit) return render_template( "collection.html", game=state["game"], all_items=Item.__subclasses__() )
def test_internal_transaction(self): alg = self._init_test() self._prefill_db(alg) trs = Transactions(FILE_NAME) fnds = Funds(FILE_NAME) accs = alg.internal_transaction(sender_name='02', reciever_name='01', amount=50) self.assertEqual(400.0, alg.portfolio_total_fund) accounts = [ (1, '01', 0.125, accs[0][3], 0, 0.7, 0.7, 1, accs[0][-1]), (2, '02', 0.125, accs[1][3], 50, 0.3, 0.9, 1, accs[1][-1]), (3, '03', 0.25, accs[2][3], -50, 0.3, 0.9, 1, accs[2][-1]), (4, '04', 0.5, accs[3][3], 0, 0.3, 0.9, 1, accs[3][-1]), ] self.assertEqual(accounts, accs) acc_trs = trs.retrieve_transactions_for_account(account_id=1) self.assertEqual(50, acc_trs[-1][1]) acc_trs = trs.retrieve_transactions_for_account(account_id=2) self.assertEqual(-50, acc_trs[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=1) self.assertEqual(50, acc_fnds[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=2) self.assertEqual(50, acc_fnds[-1][1]) accs = alg.internal_transaction(sender_name='02', reciever_name='01', amount=100) self.assertEqual(400.0, alg.portfolio_total_fund) self.assertEqual(False, accs) acc_trs = trs.retrieve_transactions_for_account(account_id=1) self.assertEqual(50, acc_trs[-1][1]) acc_trs = trs.retrieve_transactions_for_account(account_id=2) self.assertEqual(-50, acc_trs[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=1) self.assertEqual(50, acc_fnds[-1][1]) acc_fnds = fnds.retrieve_funds_for_account(account_id=2) self.assertEqual(50, acc_fnds[-1][1])
def test_decode_transaction_with_blockr(signed_tx_hex): from transactions import Transactions transactions = Transactions(testnet=True) decoded_tx = transactions.decode(signed_tx_hex) assert decoded_tx
class BlockchainSpider(object): """ Spool blockchain explorer. Retrieves from the blockchain the chain of ownership of a hash created with the `SPOOL <https://github.com/ascribe/spool>`_ protocol. """ def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ Args: testnet (bool): Whether to use the mainnet or testnet. Defaults to the mainnet (:const:`False`). service (str): Bitcoin communication interface: ``'blockr'``, ``'daemon'``, or ``'regtest'``. ``'blockr'`` refers to the public api, whereas ``'daemon'`` and ``'regtest'`` refer to the jsonrpc inteface. Defaults to ``'blockr'``. username (str): username for jsonrpc communications password (str): password for jsonrpc communications hostname (str): hostname of the bitcoin node when using jsonrpc port (str): port number of the bitcoin node when using jsonrpc """ self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port) def history(self, hash): """ Retrieve the ownership tree of all editions of a piece given the hash. Args: hash (str): Hash of the file to check. Can be created with the :class:`File` class Returns: dict: Ownsership tree of all editions of a piece. .. note:: For now we only support searching the blockchain by the piece hash. """ txs = self._t.get(hash, max_transactions=10000)['transactions'] tree = defaultdict(list) number_editions = 0 for tx in txs: _tx = self._t.get(tx['txid']) txid = _tx['txid'] verb_str = BlockchainSpider.check_script(_tx['vouts']) verb = Spoolverb.from_verb(verb_str) from_address, to_address, piece_address = BlockchainSpider._get_addresses(_tx) timestamp_utc = _tx['time'] action = verb.action edition_number = 0 if action != 'EDITIONS': edition_number = verb.edition_number else: number_editions = verb.num_editions tree[edition_number].append({'txid': txid, 'verb': verb_str, 'from_address': from_address, 'to_address': to_address, 'piece_address': piece_address, 'timestamp_utc': timestamp_utc, 'action': action, 'number_editions': number_editions, 'edition_number': edition_number}) # lets update the records with the number of editions of the piece since we do not know # this information before the EDITIONS transaction for edition, chain in tree.items(): [d.update({'number_editions': number_editions}) for d in chain] return dict(tree) @staticmethod def chain(tree, edition_number): """ Args: tree (dict): Tree history of all editions of a piece. edition_number (int): The edition number to check for. In the case of a piece (master edition), an empty string (``''``) or zero (``0``) can be passed. Returns: list: The chain of ownsership of a particular edition of the piece ordered by time. """ # return the chain for an edition_number sorted by the timestamp return sorted(tree.get(edition_number, []), key=lambda d: d['timestamp_utc']) @staticmethod def strip_loan(chain): """ Returns the chain without loan. This way we can look at the last transaction to establish ownership. Args: chain (list): Chain for a particular edition. Returns: list: Chain with loan transactions striped from the end of the chain. """ while chain[-1]['action'] == 'LOAN': chain.pop() return chain @staticmethod def pprint(tree): """ Utility function to pretty print the history tree of a piece. Args: tree (dict): History tree of a piece. """ p = PrettyPrinter(indent=2) p.pprint(tree) @staticmethod def decode_op_return(op_return_hex): """ Decodes the given ``op_return`` hexadecimal string representation into a string (:obj:`str`). Args: op_return_hex (str): Hexadecimal string representation of the ``op_return``. Returns: str: String representation of the ``op_return``. """ return binascii.unhexlify(op_return_hex[4:]) @staticmethod def check_script(vouts): """ Looks into the vouts list of a transaction and returns the ``op_return`` if one exists. Args; vouts (list): List of outputs of a transaction. Returns: str: String representation of the ``op_return``. Raises: Exception: If no ``vout`` having a supported verb (:attr:`supported_actions`) is found. """ for vout in [v for v in vouts[::-1] if v['hex'].startswith('6a')]: verb = BlockchainSpider.decode_op_return(vout['hex']) action = Spoolverb.from_verb(verb).action if action in Spoolverb.supported_actions: return verb raise Exception("Invalid ascribe transaction") @staticmethod def _get_addresses(tx): """ Checks for the from, to, and piece address of a SPOOL transaction. Args: tx (dict): Transaction payload, as returned by :meth:`transactions.Transactions.get()`. .. note:: Formats as returned by JSON-RPC API ``decoderawtransaction`` have yet to be supported. Returns: Tuple([str]): Sender, receiver, and piece addresses. """ from_address = set([vin['address'] for vin in tx['vins']]) if len(from_address) != 1: raise InvalidTransactionError("Transaction should have inputs " \ "from only one address {}".format(from_address)) # order vouts. discard the last vout since it's the op_return vouts = sorted(tx['vouts'], key=lambda d: d['n'])[:-1] piece_address = vouts[0]['address'] to_address = vouts[-1]['address'] from_address = from_address.pop() return from_address, to_address, piece_address @staticmethod def _get_time_utc(time_utc_str): """ Convert a string representation of the time (as returned by blockr.io api) into unix timestamp. Args: time_utc_str (str): String representation of the time, with the format: `'%Y-%m-%dT%H:%M:%S %Z'`. Returns: int: Unix timestamp. """ dt = datetime.strptime(time_utc_str, TIME_FORMAT) return int(calendar.timegm(dt.utctimetuple()))
class Spool(object): """ Class that contains all Spool methods. In the SPOOL implementation there is no notion of users only addresses. All addresses come from BIP32 HD wallets. This makes it easier to manage all the keys since we can retrieve everything we need from a master secret (namely the private key to sign the transactions). Since we are dealing with HD wallets we expect all ``from_address`` to be a tuple of ``(path, address)`` so that we can retrieve the private key for that particular leaf address. If we want to use the root address we can just pass an empty string to the first element of the tuple e.g. ``('', address)``. For instance when using the federation wallet address we have no need to create leaf addresses. A file is represented by two hashes: - ``file_hash``: is the hash of the digital file - ``file_hash_metadata``: is the hash of the digital file + metadata The hash is passed to the methods has a tuple: ``(file_hash, file_hash_metadata)`` Attributes: FEE (int): transaction fee TOKEN (int): token SPENTS_QUEUE_MAXSIZE (int): spent outputs queue maximum size """ FEE = 30000 TOKEN = 3000 SPENTS_QUEUE_MAXSIZE = 50 def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ Args: testnet (bool): Whether to use the mainnet or testnet. Defaults to the mainnet (:const:`False`). service (str): Bitcoin communication interface: ``'blockr'``, ``'daemon'``, or ``'regtest'``. ``'blockr'`` refers to the public api, whereas ``'daemon'`` and ``'regtest'`` refer to the jsonrpc inteface. Defaults to ``'blockr'``. username (str): username for jsonrpc communications password (str): password for jsonrpc communications hostname (str): hostname of the bitcoin node when using jsonrpc port (str): port number of the bitcoin node when using jsonrpc """ self.testnet = testnet self._netcode = 'XTN' if testnet else 'BTC' self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port) # simple cache for spent outputs. Useful so that rapid firing transactions don't use the same outputs self._spents = Queue(maxsize=self.SPENTS_QUEUE_MAXSIZE) @dispatch def register_piece(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True): """ Register a piece Args: from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet to_address (str): Address registering the edition hash (Tuple[str]): Hash of the piece. (file_hash, file_hash_metadata) password (str): Federation wallet password. For signing the transaction edition_num (int): The number of the edition to register. User edition_num=0 to register the master edition min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb() unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.piece, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def register(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Register an edition or master edition of a piece Args: from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet to_address (str): Address registering the edition hash (Tuple[str])): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Federation wallet password. For signing the transaction edition_num (int): The number of the edition to register. User edition_num=0 to register the master edition min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.register, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def consigned_registration(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True): """ Register an edition or master edition of a piece consigned to ``from_address`` Args: from_address (Tuple[str])): Federation address. All register transactions originate from the the Federation wallet to_address (str): Address registering the edition hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Federation wallet password. For signing the transaction min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb() unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.consigned_registration, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def editions(self, from_address, to_address, hash, password, num_editions, min_confirmations=6, sync=False, ownership=True): """ Register the number of editions of a piece Args: from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet to_address (str): Address registering the number of editions hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Federation wallet password. For signing the transaction num_editions (int): Number of editions of the piece min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb(num_editions=num_editions) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.editions, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def transfer(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Transfer a piece between addresses Args: from_address (Tuple[str]): Address currently owning the edition to_address: Address to receive the edition hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Password for the wallet currently owning the edition. For signing the transaction edition_num (int): the number of the edition to transfer min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.transfer, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def consign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Consign a piece to an address Args: from_address (Tuple[str]): Address currently owning the edition to_address (str): Address to where the piece will be consigned to hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Password for the wallet currently owning the edition. For signing the transaction edition_num (int): the number of the edition to consign min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.consign, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def unconsign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Unconsign the edition Args: from_address (Tuple[str]): Address where the edition is currently consigned to_address (str): Address that consigned the piece to from_address hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Password for the wallet currently holding the edition. For signing the transaction edition_num (int): the number of the edition to unconsign min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ # In an unconsignment the to_address needs to be the address that created the consign transaction path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.unconsign, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def loan(self, from_address, to_address, hash, password, edition_num, loan_start, loan_end, min_confirmations=6, sync=False, ownership=True): """ Loan the edition Args: from_address (Tuple[str]): Address currently holding the edition to_address (str): Address to loan the edition to hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Password for the wallet currently holding the edition. For signing the transaction edition_num (int): the number of the edition to loan loan_start (str): Start date for the loan. In the form YYMMDD loan_end (str): End date for the loan. In the form YYMMDD min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num, loan_start=loan_start, loan_end=loan_end) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.loan, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def migrate(self, from_address, prev_address, new_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Migrate an edition Args: from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet to_address (str): Address registering the edition hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata) password (str): Federation wallet password. For signing the transaction edition_num (int): The number of the edition to register. User edition_num=0 to register the master edition min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True Returns: str: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, prev_address, new_address], op_return=verb.migrate, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def refill_main_wallet(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False): """ Refill the Federation wallet with tokens and fees. This keeps the federation wallet clean. Dealing with exact values simplifies the transactions. No need to calculate change. Easier to keep track of the unspents and prevent double spends that would result in transactions being rejected by the bitcoin network. Args: from_address (Tuple[str]): Refill wallet address. Refills the federation wallet with tokens and fees to_address (str): Federation wallet address nfees (int): Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions ntokens (int): Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain password (str): Password for the Refill wallet. Used to sign the transaction min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False Returns: str: transaction id """ path, from_address = from_address unsigned_tx = self._t.simple_transaction(from_address, [(to_address, self.FEE)] * nfees + [(to_address, self.TOKEN)] * ntokens, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def refill(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False): """ Refill wallets with the necessary fuel to perform spool transactions Args: from_address (Tuple[str]): Federation wallet address. Fuels the wallets with tokens and fees. All transactions to wallets holding a particular piece should come from the Federation wallet to_address (str): Wallet address that needs to perform a spool transaction nfees (int): Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions ntokens (int): Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain password (str): Password for the Federation wallet. Used to sign the transaction min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False Returns: str: transaction id """ path, from_address = from_address verb = Spoolverb() # nfees + 1: nfees to refill plus one fee for the refill transaction itself inputs = self.select_inputs(from_address, nfees + 1, ntokens, min_confirmations=min_confirmations) outputs = [{'address': to_address, 'value': self.TOKEN}] * ntokens outputs += [{'address': to_address, 'value': self.FEE}] * nfees outputs += [{'script': self._t._op_return_hex(verb.fuel), 'value': 0}] unsigned_tx = self._t.build_transaction(inputs, outputs) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid def simple_spool_transaction(self, from_address, to, op_return, min_confirmations=6): """ Utililty function to create the spool transactions. Selects the inputs, encodes the op_return and constructs the transaction. Args: from_address (str): Address originating the transaction to (str): list of addresses to receive tokens (file_hash, file_hash_metadata, ...) op_return (str): String representation of the spoolverb, as returned by the properties of Spoolverb min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6 Returns: str: unsigned transaction """ # list of addresses to send ntokens = len(to) nfees = old_div(self._t.estimate_fee(ntokens, 2), self.FEE) inputs = self.select_inputs(from_address, nfees, ntokens, min_confirmations=min_confirmations) # outputs outputs = [{'address': to_address, 'value': self.TOKEN} for to_address in to] outputs += [{'script': self._t._op_return_hex(op_return), 'value': 0}] # build transaction unsigned_tx = self._t.build_transaction(inputs, outputs) return unsigned_tx def select_inputs(self, address, nfees, ntokens, min_confirmations=6): """ Selects the inputs for the spool transaction. Args: address (str): bitcoin address to select inputs for nfees (int): number of fees ntokens (int): number of tokens min_confirmations (Optional[int]): minimum number of required confirmations; defaults to 6 """ unspents = self._t.get(address, min_confirmations=min_confirmations)['unspents'] unspents = [u for u in unspents if u not in self._spents.queue] if len(unspents) == 0: raise Exception("No spendable outputs found") fees = [u for u in unspents if u['amount'] == self.FEE][:nfees] tokens = [u for u in unspents if u['amount'] == self.TOKEN][:ntokens] if len(fees) != nfees or len(tokens) != ntokens: raise SpoolFundsError("Not enough outputs to spend. Refill your wallet") if self._spents.qsize() > self.SPENTS_QUEUE_MAXSIZE - (nfees + ntokens): [self._spents.get() for i in range(self._spents.qsize() + nfees + ntokens - self.SPENTS_QUEUE_MAXSIZE)] [self._spents.put(fee) for fee in fees] [self._spents.put(token) for token in tokens] return fees + tokens
def test_check_scripts(self): t = Transactions(testnet=True) tx = t.get(TXID) vouts = tx['vouts'] verb = BlockchainSpider.check_script(vouts) self.assertEqual(verb, 'ASCRIBESPOOL01EDITIONS10')
class BlockchainSpider(object): """ Spool blockain explorer. Retrieves from the blockchain the chain of ownership of a hash created with the SPOOL protocol """ def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ :param testnet: testnet flag. Defaults to False :return: An instance of the BlockainSpider """ self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port) def history(self, hash): """ Retrieve the ownership tree of all editions of a piece given the hash :param hash: Hash of the file to check. Can be created with the File class :return: ownsership tree of all editions of a piece """ # For now we only support searching the blockchain by the piece hash txs = self._t.get(hash, max_transactions=10000)['transactions'] tree = defaultdict(list) number_editions = 0 for tx in txs: _tx = self._t.get(tx['txid']) txid = _tx['txid'] verb_str = BlockchainSpider.check_script(_tx['vouts']) verb = Spoolverb.from_verb(verb_str) from_address, to_address, piece_address = BlockchainSpider._get_addresses(_tx) timestamp_utc = _tx['time'] action = verb.action edition_number = 0 if action != 'EDITIONS': edition_number = verb.edition_number else: number_editions = verb.num_editions tree[edition_number].append({'txid': txid, 'verb': verb_str, 'from_address': from_address, 'to_address': to_address, 'piece_address': piece_address, 'timestamp_utc': timestamp_utc, 'action': action, 'number_editions': number_editions, 'edition_number': edition_number}) # lets update the records with the number of editions of the piece since we do not know # this information before the EDITIONS transaction for edition, chain in tree.iteritems(): [d.update({'number_editions': number_editions}) for d in chain] return dict(tree) @staticmethod def chain(tree, edition_number): """ :param tree: Tree history of all editions of a piece :param edition_number: The edition number to check for :return: The chain of ownsership of a particular edition of the piece ordered by time """ # return the chain for an edition_number sorted by the timestamp return sorted(tree.get(edition_number, []), key=lambda d: d['timestamp_utc']) @staticmethod def strip_loan(chain): """ Returns the chain without loan. This way we can look at the last transaction to establish ownership :param chain: chain for a particular edition :return: chain with loan transactions striped from the end of the chain """ while chain[-1]['action'] == 'LOAN': chain.pop() return chain @staticmethod def pprint(tree): """ Utility function to pretty print the history tree of a piece :param tree: History tree of a piece :return: None """ p = PrettyPrinter(indent=2) p.pprint(tree) @staticmethod def decode_op_return(op_return_hex): """ Decodes the op_return hex representation into a string :param op_return_hex: hex representation of the op_return :return: string representation of the op_return """ return binascii.unhexlify(op_return_hex[4:]) @staticmethod def check_script(vouts): """ Looks into the vouts list of a transaction and returns the op_return if one exists :param vouts: lists of outputs of a transaction :return: string representation of the op_return """ for vout in [v for v in vouts[::-1] if v['hex'].startswith('6a')]: verb = BlockchainSpider.decode_op_return(vout['hex']) action = Spoolverb.from_verb(verb).action if action in Spoolverb.supported_actions: return verb raise Exception("Invalid ascribe transaction") @staticmethod def _get_addresses(tx): """ checks for the from, to, and piece address of a SPOOL transactions """ from_address = set([vin['address'] for vin in tx['vins']]) if len(from_address) != 1: raise InvalidTransactionError("Transaction should have inputs " \ "from only one address {}".format(from_address)) # order vouts. discard the last vout since its the op_return vouts = sorted(tx['vouts'], key=lambda d: d['n'])[:-1] piece_address = vouts[0]['address'] to_address = vouts[-1]['address'] from_address = from_address.pop() return from_address, to_address, piece_address @staticmethod def _get_time_utc(time_utc_str): """ Convert a string representation of the time (as returned by blockr.io api) into unix timestamp :param time_utc_str: string representation of the time :return: unix timestamp """ dt = datetime.strptime(time_utc_str, "%Y-%m-%dT%H:%M:%SZ") return int(calendar.timegm(dt.utctimetuple()))
class Spool(object): FEE = 30000 TOKEN = 3000 """ Class that contains all Spool methods. In the SPOOL implementation there is no notion of users only addresses. All addresses come from BIP32 HD wallets. This makes it easier to manage all the keys since we can retrieve everything we need from a master secret (namely the private key to sign the transactions). Since we are dealing with HD wallets we expect all from_address to be a tuple of (path, address) so that we can retrieve the private key for that particular leaf address. If we want to use the root address we can just pass an empty string to the first element of the tuple e.g. ('', address). For instance when using the federation wallet address we have no need to create leaf addresses. A file is represented by two hashes: - file_hash: is the hash of the digital file - file_hash_metadata: is the hash of the digital file + metadata The hash is passed to the methods has a tuple (file_hash, file_hash_metadata) """ def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''): """ :param testnet: :return: """ self.testnet = testnet self._netcode = 'XTN' if testnet else 'BTC' self._t = Transactions(service=service, testnet=testnet, username=username, password=password, host=host, port=port) # simple cache for spent outputs. Useful so that rapid firing transactions don't use the same outputs self._spents = Queue(maxsize=50) @dispatch def register_piece(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True): """ Register a piece :param from_address: Federation address. All register transactions originate from the the Federation wallet :param to_address: Address registering the edition :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Federation wallet password. For signing the transaction :param edition_num: The number of the edition to register. User edition_num=0 to register the master edition :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb() unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.piece, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def register(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Register an edition or master edition of a piece :param from_address: Federation address. All register transactions originate from the the Federation wallet :param to_address: Address registering the edition :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Federation wallet password. For signing the transaction :param edition_num: The number of the edition to register. User edition_num=0 to register the master edition :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.register, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def consigned_registration(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True): """ Register an edition or master edition of a piece consigned to from_address :param from_address: Federation address. All register transactions originate from the the Federation wallet :param to_address: Address registering the edition :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Federation wallet password. For signing the transaction :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb() unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.consigned_registration, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def editions(self, from_address, to_address, hash, password, num_editions, min_confirmations=6, sync=False, ownership=True): """ Register the number of editions of a piece :param from_address: Federation address. All register transactions originate from the the Federation wallet :param to_address: Address registering the number of editions :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Federation wallet password. For signing the transaction :param num_editions: Number of editions of the piece :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb(num_editions=num_editions) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, file_hash_metadata, to_address], op_return=verb.editions, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def transfer(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Transfer a piece between addresses :param from_address: Address currently owning the edition :param to_address: Address to receive the edition :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Password for the wallet currently owning the edition. For signing the transaction :param edition_num: the number of the edition to transfer :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.transfer, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def consign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Consign a piece to an address :param from_address: Address currently owning the edition :param to_address: Address to where the piece will be consigned to :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Password for the wallet currently owning the edition. For signing the transaction :param edition_num: the number of the edition to consign :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.consign, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def unconsign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Unconsign the edition :param from_address: Address where the edition is currently consigned :param to_address: Address that consigned the piece to from_address :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Password for the wallet currently holding the edition. For signing the transaction :param edition_num: the number of the edition to unconsign :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ # In an unconsignment the to_address needs to be the address that created the consign transaction path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.unconsign, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def loan(self, from_address, to_address, hash, password, edition_num, loan_start, loan_end, min_confirmations=6, sync=False, ownership=True): """ Loan the edition :param from_address: Address currently holding the edition :param to_address: Address to loan the edition to :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Password for the wallet currently holding the edition. For signing the transaction :param edition_num: the number of the edition to unconsign :param loan_start: Start date for the loan. In the form YYMMDD :param loan_end: End date for the loan. In the form YYMMDD :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ path, from_address = from_address file_hash, file_hash_metadata = hash verb = Spoolverb(edition_num=edition_num, loan_start=loan_start, loan_end=loan_end) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, to_address], op_return=verb.loan, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid @dispatch def migrate(self, from_address, prev_address, new_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True): """ Migrate an edition :param from_address: Federation address. All register transactions originate from the the Federation wallet :param to_address: Address registering the edition :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata) :param password: Federation wallet password. For signing the transaction :param edition_num: The number of the edition to register. User edition_num=0 to register the master edition :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True :return: transaction id """ file_hash, file_hash_metadata = hash path, from_address = from_address verb = Spoolverb(edition_num=edition_num) unsigned_tx = self.simple_spool_transaction(from_address, [file_hash, prev_address, new_address], op_return=verb.migrate, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def refill_main_wallet(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False): """ Refill the Federation wallet with tokens and fees. This keeps the federation wallet clean. Dealing with exact values simplifies the transactions. No need to calculate change. Easier to keep track of the unspents and prevent double spends that would result in transactions being rejected by the bitcoin network. :param from_address: Refill wallet address. Refills the federation wallet with tokens and fees :param to_address: Federation wallet address :param nfees: Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions :param ntokens: Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain :param password: Password for the Refill wallet. Used to sign the transaction :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :return: transaction id """ path, from_address = from_address unsigned_tx = self._t.simple_transaction(from_address, [(to_address, self.FEE)] * nfees + [(to_address, self.TOKEN)] * ntokens, min_confirmations=min_confirmations) signed_tx = self._t.sign_transaction(unsigned_tx, password) txid = self._t.push(signed_tx) return txid @dispatch def refill(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False): """ Refill wallets with the necessary fuel to perform spool transactions :param from_address: Federation wallet address. Fuels the wallets with tokens and fees. All transactions to wallets holding a particular piece should come from the Federation wallet :param to_address: Wallet address that needs to perform a spool transaction :param nfees: Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions :param ntokens: Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain :param password: Password for the Federation wallet. Used to sign the transaction :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at least on confirmation on the blockchain. Defaults to False :return: transaction id """ path, from_address = from_address verb = Spoolverb() # nfees + 1: nfees to refill plus one fee for the refill transaction itself inputs = self.select_inputs(from_address, nfees + 1, ntokens, min_confirmations=min_confirmations) outputs = [{'address': to_address, 'value': self.TOKEN}] * ntokens outputs += [{'address': to_address, 'value': self.FEE}] * nfees outputs += [{'script': self._t._op_return_hex(verb.fuel), 'value': 0}] unsigned_tx = self._t.build_transaction(inputs, outputs) signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path) txid = self._t.push(signed_tx) return txid def simple_spool_transaction(self, from_address, to, op_return, min_confirmations=6): """ Utililty function to create the spool transactions. Selects the inputs, encodes the op_return and constructs the transaction. :param from_address: Address originating the the transaction :param to: list of addresses to receive tokens (file_hash, file_hash_metadata, ...) :param op_return: String representation of the spoolverb, as returned by the properties of Spoolverb :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6 :return: unsigned transaction """ # list of addresses to send ntokens = len(to) nfees = self._t.estimate_fee(ntokens, 2) / self.FEE inputs = self.select_inputs(from_address, nfees, ntokens, min_confirmations=min_confirmations) # outputs outputs = [{'address': to_address, 'value': self.TOKEN} for to_address in to] outputs += [{'script': self._t._op_return_hex(op_return), 'value': 0}] # build transaction unsigned_tx = self._t.build_transaction(inputs, outputs) return unsigned_tx def select_inputs(self, address, nfees, ntokens, min_confirmations=6): # selects the inputs for the spool transaction unspents = self._t.get(address, min_confirmations=min_confirmations)['unspents'] unspents = filter(lambda d: d not in self._spents.queue, unspents) if len(unspents) == 0: raise Exception("No spendable outputs found") fees = filter(lambda d: d['amount'] == self.FEE, unspents)[:nfees] tokens = filter(lambda d: d['amount'] == self.TOKEN, unspents)[:ntokens] if len(fees) != nfees or len(tokens) != ntokens: raise SpoolFundsError("Not enough outputs to spend. Refill your wallet") if self._spents.qsize() > 50 - (nfees + ntokens): [self._spents.get() for i in range(self._spents.qsize() + nfees + ntokens - 50)] [self._spents.put(fee) for fee in fees] [self._spents.put(token) for token in tokens] return fees + tokens
def test_get_block_info_with_blockr(block_hash): from transactions import Transactions transactions = Transactions(testnet=True) block_data = transactions.get_block_info(block_hash) assert block_data
def test_that_rates_work(self): trans = Transactions(Rates(TEST_RATES), TEST_TRANSACTIONS) t = trans.total(sku = "DM1182", currency = "USD") self.assertEqual(t, Decimal("134.228"))
def make_rows(self): import dateutil def get_increments(ledger, account): ld = ledger[ledger.idx].get_date(posting=ledger_account_name, return_string=False) if ledger.idx < len(ledger) else dateutil.parser.parse("2099/01/01") ad = account[account.idx].get_date(posting=ledger_account_name, return_string=False) if account.idx < len(account) else dateutil.parser.parse("2099/01/01") lbump = abump = False if ledger.idx == len(ledger): lbump = True if account.idx == len(account): abump = True if ledger.idx < len(ledger) and account.idx < len(account) and self.total(ledger[ledger.idx])==self.total(account[account.idx]): abump = lbump = True if ld <= ad: lbump = True if ld >= ad: abump = True ledger.bump = False if ledger.idx < len(ledger): ledger.bump = lbump account.bump = False if account.idx < len(account): account.bump = abump uncleared = Transactions() ret = "" ledger_account_name = c['banks'][self.account.bank_name]['accounts'][self.account.name]['ledger-account'].lower() while self.ledger.idx < len(self.ledger) or self.account.idx < len(self.account): if self.ledger.idx < len(self.ledger): if (self.ledger[self.ledger.idx]['state'] != "cleared" and not "cleared" in [p['state'] for p in self.ledger[self.ledger.idx]['postings'] if p['account_name'].lower().startswith(ledger_account_name)]): uncleared.append(self.ledger[self.ledger.idx]) self.ledger.idx += 1 continue get_increments(self.ledger, self.account) if self.ledger.bump: self.ledger.total.append(self.ledger.total[-1] + self.total(self.ledger[self.ledger.idx])) if self.account.bump: self.account.total.append(self.account.total[-1] + self.total(self.account[self.account.idx])) if True: # make table row line = "<tr><td align='right'>" if self.ledger.bump: line += '{0}<br />' line += "</td><td>" if self.account.bump: line += '{1}<br />' line += "</td></tr>" ret += line.format(self.ledger[self.ledger.idx] if self.ledger.idx<len(self.ledger) else "", self.account[self.account.idx] if self.account.idx < len(self.account) else "") if self.ledger.total[-1] == self.account.total[-1]: ret += ("<tr><td align='right'><font color='green'>{0}</font><br /></td><td><font color='green'>{1}</font></td></tr>". format(self.ledger.total[-1], self.account.total[-1])) else: if self.ledger.bump == self.account.bump == True: ret += ("<tr><td align='right'><font color='blue'>{0}</font><br /></td><td><font color='blue'>{1}</font></td></tr>". format(self.ledger.total[-1], self.account.total[-1])) else: ret += "<tr><td align='right'>{0}<br /></td><td>{1}</td></tr>".format(self.ledger.total[-1], self.account.total[-1]) if self.ledger.bump: self.ledger.idx += 1 if self.account.bump: self.account.idx += 1 # TODO: print the uncleared transactions in an intelligent way at the end. #if len(self.ledger) >= self.ledger.idx and len(self.account) >= self.account.idx: # if len(uncleared) > 0: # for tx in uncleared: # print "<tr><td align='right'>{0}</td></tr>".format(tx) return ret
def searchByItemName(): transactions = Transactions() return transactions.searchByItemName(retrieveRequestFor('itemName', request))
def cook(): transactions = Transactions() return transactions.cook(retrieveRequestFor('ingredients', request).split(","))
def test_transaction_creation_via_create(): from transactions import Transactions trxs = Transactions(testnet=True) assert trxs.testnet is True simple_transaction = trxs.create(alice, (bob, 6)) assert simple_transaction
class TestSpool(unittest.TestCase): def setUp(self): try: # flag to run the tests test = os.environ['TEST_SPOOL'] if test == '2': username = os.environ['TESTNET_USERNAME'] password = os.environ['TESTNET_PASSWORD'] host = os.environ['TESTNET_HOST'] port = os.environ['TESTNET_PORT'] self.refill_pass = os.environ['TEST_REFILL_PASS'] self.federation_pass = os.environ['TEST_FEDERATION_PASS'] self.refill_root = Wallet(self.refill_pass, testnet=True).root_address self.federation_root = Wallet(self.federation_pass, testnet=True).root_address except KeyError: raise unittest.SkipTest('TEST_REFILL_PASS and/or TEST_FEDERATION_PASS environment variables are not set.') # set TEST_SPOOL=2 to test with bitcoind if test == '2': print 'using bitcoind' self.t = Transactions(testnet=True, service='daemon', username=username, password=password, host=host, port=port) self.spool = Spool(testnet=True, service='daemon', username=username, password=password, host=host, port=port) else: print 'using blockr' self.t = Transactions(testnet=True) self.spool = Spool(testnet=True) self.user1_pass = self._get_pass() self.user2_pass = self._get_pass() self.user3_pass = self._get_pass() self.user1_root = Wallet(self.user1_pass, testnet=True).root_address self.user1_leaf = Wallet(self.user1_pass, testnet=True).address_from_path() self.user2_leaf = Wallet(self.user2_pass, testnet=True).address_from_path() self.user3_leaf = Wallet(self.user3_pass, testnet=True).address_from_path() self.file_hash = self._get_file_hash() print 'user1_pass: '******'user2_pass: '******'user3_pass: '******'user1_root: ', self.user1_root print 'user1_leaf: ', self.user1_leaf print 'user2_leaf: ', self.user2_leaf print 'user3_leaf: ', self.user3_leaf print 'file_hash :', self.file_hash self.spool._t.import_address(self.user1_root[1], "test",) self.spool._t.import_address(self.user1_leaf[1], "test",) self.spool._t.import_address(self.user2_leaf[1], "test",) self.spool._t.import_address(self.user3_leaf[1], "test",) self.spool._t.import_address(self.file_hash[0], "test",) self.spool._t.import_address(self.file_hash[1], "test",) def test_spool(self): # 1. Refill Federation wallet with necessary fuel and tokens print print 'Refilling Federation wallet with necessary fuel and tokens' txid = self.spool.refill_main_wallet(self.refill_root, self.federation_root[1], 7, 11, self.refill_pass, min_confirmations=1, sync=True) print txid # 2. user1 registers master edition print print 'user1 registers master edition' txid = self.spool.register(self.federation_root, self.user1_root[1], self.file_hash, self.federation_pass, 0, min_confirmations=1, sync=True) print txid tx = self.t.get(txid) verb = BlockchainSpider.check_script(tx['vouts']) self.assertEqual(verb, 'ASCRIBESPOOL01REGISTER0') # 3. user1 registers number of editions print print 'user1 registers number of editions' txid = self.spool.editions(self.federation_root, self.user1_root[1], self.file_hash, self.federation_pass, 10, min_confirmations=1, sync=True) print txid tx = self.t.get(txid) verb = BlockchainSpider.check_script(tx['vouts']) self.assertEqual(verb, 'ASCRIBESPOOL01EDITIONS10') # 4. user1 registers edition number 1 print print 'user1 registers edition number 1' txid = self.spool.register(self.federation_root, self.user1_leaf[1], self.file_hash, self.federation_pass, 1, min_confirmations=1, sync=True) print txid tx = self.t.get(txid) verb = BlockchainSpider.check_script(tx['vouts']) self.assertEqual(verb, 'ASCRIBESPOOL01REGISTER1') # 5. Refill user1 wallet before transfer print print 'Refill user1 wallet before transfer' txid = self.spool.refill(self.federation_root, self.user1_leaf[1], 1, 1, self.federation_pass, min_confirmations=1, sync=True) print txid # 5. User1 transfers edition number 1 to user2 print print 'User1 transfers edition number 1 to user 2' txid = self.spool.transfer(self.user1_leaf, self.user2_leaf[1], self.file_hash, self.user1_pass, 1, min_confirmations=1, sync=True) print txid tx = self.t.get(txid) verb = BlockchainSpider.check_script(tx['vouts']) self.assertEqual(verb, 'ASCRIBESPOOL01TRANSFER1') # 6. Refill user2 wallet before consign print print 'Refill user2 wallet before consign' txid = self.spool.refill(self.federation_root, self.user2_leaf[1], 1, 1, self.federation_pass, min_confirmations=1, sync=True) print txid # 6. user2 consigns edition number 1 to user3 print print 'user2 consigns edition number 1 to user3' txid = self.spool.consign(self.user2_leaf, self.user3_leaf[1], self.file_hash, self.user2_pass, 1, min_confirmations=1, sync=True) print txid tx = self.t.get(txid) verb = BlockchainSpider.check_script(tx['vouts']) self.assertEqual(verb, 'ASCRIBESPOOL01CONSIGN1') # 7. Refill user3 wallet before unconsign print print 'Refill user3 wallet before unconsign' txid = self.spool.refill(self.federation_root, self.user3_leaf[1], 1, 1, self.federation_pass, min_confirmations=1, sync=True) print txid # 7. user3 unconsigns edition number 1 to user2 print print 'user3 unconsigns edition number 1 back to user2' txid = self.spool.unconsign(self.user3_leaf, self.user2_leaf[1], self.file_hash, self.user3_pass, 1, min_confirmations=1, sync=True) print txid tx = self.t.get(txid) verb = BlockchainSpider.check_script(tx['vouts']) self.assertEqual(verb, 'ASCRIBESPOOL01UNCONSIGN1') # 8. Refill user2 wallet before loan print print 'Refill user2 wallet before loan' txid = self.spool.refill(self.federation_root, self.user2_leaf[1], 1, 1, self.federation_pass, min_confirmations=1, sync=True) print txid # 8. user2 loans edition number 1 to user3 print print 'user2 loans edition number 1 to user3' txid = self.spool.loan(self.user2_leaf, self.user3_leaf[1], self.file_hash, self.user2_pass, 1, '150522', '150523', min_confirmations=1, sync=True) print txid tx = self.t.get(txid) verb = BlockchainSpider.check_script(tx['vouts']) self.assertEqual(verb, 'ASCRIBESPOOL01LOAN1/150522150523') def _get_pass(self): return ''.join([random.choice(ascii_letters) for i in xrange(10)]) def _get_file_hash(self): title = ''.join([random.choice(ascii_letters) for i in xrange(10)]) with open('/tmp/test', 'w') as f: f.write(random._urandom(100)) f = File('/tmp/test', testnet=True, title=title) return f.file_hash, f.file_hash_metadata