class PaymentProcessorFunctionalTest(DatabaseFixture): """ In this suite we test Ethereum state changes done by PaymentProcessor. """ def setUp(self): DatabaseFixture.setUp(self) self.state = tester.state() gnt_addr = self.state.evm(decode_hex(TestGNT.INIT_HEX)) self.state.mine() self.gnt = tester.ABIContract(self.state, TestGNT.ABI, gnt_addr) PaymentProcessor.TESTGNT_ADDR = gnt_addr self.privkey = tester.k1 self.client = mock.MagicMock(spec=Client) self.client.get_peer_count.return_value = 0 self.client.is_syncing.return_value = False self.client.get_transaction_count.side_effect = \ lambda addr: self.state.block.get_nonce(decode_hex(addr[2:])) self.client.get_balance.side_effect = \ lambda addr: self.state.block.get_balance(decode_hex(addr[2:])) def call(_from, to, data, **kw): # pyethereum does not have direct support for non-mutating calls. # The implemenation just copies the state and discards it after. # Here we apply real transaction, but with gas price 0. # We assume the transaction does not modify the state, but nonce # will be bumped no matter what. _from = _from[2:].decode('hex') data = data[2:].decode('hex') nonce = self.state.block.get_nonce(_from) value = kw.get('value', 0) tx = Transaction(nonce, 0, 100000, to, value, data) assert _from == tester.a1 tx.sign(tester.k1) block = kw['block'] assert block == 'pending' success, output = apply_transaction(self.state.block, tx) assert success return '0x' + output.encode('hex') def send(tx): success, _ = apply_transaction(self.state.block, tx) assert success # What happens in real RPC eth_send? return '0x' + tx.hash.encode('hex') self.client.call.side_effect = call self.client.send.side_effect = send self.pp = PaymentProcessor(self.client, self.privkey) self.clock = Clock() self.pp._loopingCall.clock = self.clock def test_initial_eth_balance(self): # ethereum.tester assigns this amount to predefined accounts. assert self.pp.eth_balance() == 1000000000000000000000000 def check_synchronized(self): assert not self.pp.synchronized() self.client.get_peer_count.return_value = 1 assert not self.pp.synchronized() I = PaymentProcessor.SYNC_CHECK_INTERVAL = SYNC_TEST_INTERVAL assert self.pp.SYNC_CHECK_INTERVAL == SYNC_TEST_INTERVAL time.sleep(1.5 * PaymentProcessor.SYNC_CHECK_INTERVAL) assert not self.pp.synchronized() time.sleep(1.5 * PaymentProcessor.SYNC_CHECK_INTERVAL) assert self.pp.synchronized() PaymentProcessor.SYNC_CHECK_INTERVAL = I def test_synchronized(self): self.pp.start() self.check_synchronized() self.pp.stop() def test_gnt_faucet(self, *_): self.pp._PaymentProcessor__faucet = True self.pp._run() assert self.pp.eth_balance() > 0 assert self.pp.gnt_balance() == 0 self.check_synchronized() self.state.mine() self.clock.advance(60) self.pp._run() assert self.pp.gnt_balance(True) == 1000 * denoms.ether def test_single_payment(self, *_): self.pp._run() self.gnt.create(sender=self.privkey) self.state.mine() self.check_synchronized() assert self.pp.gnt_balance() == 1000 * denoms.ether payee = urandom(20) b = self.pp.gnt_balance() # FIXME: Big values does not fit into the database value = random.randint(0, b / 1000) p1 = Payment.create(subtask="p1", payee=payee, value=value) assert self.pp._gnt_available() == b assert self.pp._gnt_reserved() == 0 self.pp.add(p1) assert self.pp._gnt_available() == b - value assert self.pp._gnt_reserved() == value # Sendout. self.pp.deadline = int(time.time()) self.pp._run() assert self.pp.gnt_balance(True) == b - value assert self.pp._gnt_available() == b - value assert self.pp._gnt_reserved() == 0 assert self.gnt.balanceOf(payee) == value assert self.gnt.balanceOf(tester.a1) == self.pp._gnt_available() # Confirm. assert self.pp.gnt_balance(True) == b - value assert self.pp._gnt_reserved() == 0 def test_get_ether(self, *_): def exception(*_): raise Exception def failure(*_): return False def success(*_): return True self.pp.monitor_progress = Mock() self.pp.synchronized = lambda *_: True self.pp.sendout = Mock() self.pp.get_gnt_from_faucet = failure self.pp.get_ether_from_faucet = failure self.pp._run() assert not self.pp._waiting_for_faucet assert not self.pp.monitor_progress.called assert not self.pp.sendout.called self.pp.get_ether_from_faucet = success self.pp._run() assert not self.pp._waiting_for_faucet assert not self.pp.monitor_progress.called assert not self.pp.sendout.called self.pp.get_gnt_from_faucet = success self.pp._run() assert not self.pp._waiting_for_faucet assert self.pp.monitor_progress.called assert self.pp.sendout.called def test_no_gnt_available(self): self.pp.start() self.gnt.create(sender=self.privkey) self.state.mine() self.check_synchronized() assert self.pp.gnt_balance() == 1000 * denoms.ether payee = urandom(20) b = self.pp.gnt_balance() value = b / 5 - 100 for i in range(5): subtask_id = 's{}'.format(i) p = Payment.create(subtask=subtask_id, payee=payee, value=value) assert self.pp.add(p) q = Payment.create(subtask='F', payee=payee, value=value) assert not self.pp.add(q)
class EthereumTransactionSystem(TransactionSystem): """ Transaction system connected with Ethereum """ def __init__(self, datadir, node_priv_key): """ Create new transaction system instance for node with given id :param node_priv_key str: node's private key for Ethereum account (32b) """ super(EthereumTransactionSystem, self).__init__() # FIXME: Passing private key all around might be a security issue. # Proper account managment is needed. if not isinstance(node_priv_key, basestring)\ or len(node_priv_key) != 32: raise ValueError("Invalid private key: {}".format(node_priv_key)) self.__node_address = keys.privtoaddr(node_priv_key) log.info("Node Ethereum address: " + self.get_payment_address()) self.__eth_node = Client(datadir) self.__proc = PaymentProcessor(self.__eth_node, node_priv_key, faucet=True) self.__proc.start() def stop(self): if self.__proc.running: self.__proc.stop() if self.__eth_node.node is not None: self.__eth_node.node.stop() def add_payment_info(self, *args, **kwargs): payment = super(EthereumTransactionSystem, self).add_payment_info(*args, **kwargs) self.__proc.add(payment) return payment def get_payment_address(self): """ Human readable Ethereum address for incoming payments.""" return '0x' + self.__node_address.encode('hex') def get_balance(self): if not self.__proc.balance_known(): return None, None, None gnt = self.__proc.gnt_balance() av_gnt = self.__proc._gnt_available() eth = self.__proc.eth_balance() return gnt, av_gnt, eth def pay_for_task(self, task_id, payments): """ Pay for task using Ethereum connector :param task_id: pay for task with given id :param dict payments: all payments group by ethereum address """ pass def sync(self): syncing = True while syncing: try: syncing = self.__eth_node.is_syncing() except Exception as e: log.error("IPC error: {}".format(e)) syncing = False else: sleep(0.5)