diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..5256a13 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +line_length = 100 diff --git a/README.md b/README.md index 4b6789e..6ac0aa0 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,17 @@ Deploy contract: python deployment.py ``` -Run child chain: +Run child chain Server: ``` python -m plasma_cash.child_chain ``` +Run operator cron jobs: +(TODO: the following commands does not support running with cron job yet) +``` +python -m plasma_cash.operator_cron_job +``` + Client: ``` python diff --git a/integration_tests/features/environment.py b/integration_tests/features/environment.py index e7f0f48..d2ffa60 100644 --- a/integration_tests/features/environment.py +++ b/integration_tests/features/environment.py @@ -1,7 +1,7 @@ import os import signal import time -from subprocess import Popen, PIPE +from subprocess import PIPE, Popen from plasma_cash.root_chain.deployer import Deployer diff --git a/integration_tests/features/steps/challenge_double_spending_flow.py b/integration_tests/features/steps/challenge_double_spending_flow.py index 7b620f2..04b376b 100644 --- a/integration_tests/features/steps/challenge_double_spending_flow.py +++ b/integration_tests/features/steps/challenge_double_spending_flow.py @@ -1,6 +1,6 @@ -import rlp import time +import rlp from behave import given, then, when from ethereum import utils diff --git a/integration_tests/features/steps/challenge_history_flow.py b/integration_tests/features/steps/challenge_history_flow.py index 7034cbd..f41df4d 100644 --- a/integration_tests/features/steps/challenge_history_flow.py +++ b/integration_tests/features/steps/challenge_history_flow.py @@ -1,6 +1,6 @@ -import rlp import time +import rlp from behave import given, then, when from integration_tests.features.utils import has_value diff --git a/plasma_cash/child_chain/__init__.py b/plasma_cash/child_chain/__init__.py index e05ade5..d6e5f76 100644 --- a/plasma_cash/child_chain/__init__.py +++ b/plasma_cash/child_chain/__init__.py @@ -10,6 +10,7 @@ def create_app(is_unit_test=False): container.get_child_chain() from . import server - app.register_blueprint(server.bp) + app.register_blueprint(server.api) + app.register_blueprint(server.operator, url_prefix='/operator') return app diff --git a/plasma_cash/child_chain/block.py b/plasma_cash/child_chain/block.py index c4b38b9..4942669 100644 --- a/plasma_cash/child_chain/block.py +++ b/plasma_cash/child_chain/block.py @@ -14,12 +14,6 @@ class Block(rlp.Serializable): ] def __init__(self, transaction_set=None): - # There's a weird bug that when using - # def __init__(self, transaction_set=[],...) - # `transaction_set` would sometimes NOT be an empty list - # this happens after calling `add_tx(tx)` - # whenever new a Block(), the transaction_set would not be empty - # as a result, use if None statement to enforce empty list instead if transaction_set is None: transaction_set = [] diff --git a/plasma_cash/child_chain/child_chain.py b/plasma_cash/child_chain/child_chain.py index 230f4a6..b10178a 100644 --- a/plasma_cash/child_chain/child_chain.py +++ b/plasma_cash/child_chain/child_chain.py @@ -7,11 +7,10 @@ from plasma_cash.utils.utils import get_sender -from .event import emit from .block import Block -from .exceptions import (InvalidBlockNumException, - InvalidBlockSignatureException, - InvalidTxSignatureException, +from .event import emit +from .exceptions import (DepositAlreadyAppliedException, InvalidBlockNumException, + InvalidBlockSignatureException, InvalidTxSignatureException, PreviousTxNotFoundException, TxAlreadySpentException, TxAmountMismatchException, TxWithSameUidAlreadyExists) from .transaction import Transaction @@ -26,23 +25,37 @@ def __init__(self, authority, root_chain, db): self.current_block = Block() self.current_block_number = self.db.get_current_block_num() - # Register a filter for deposit event + """ + TODO: should be removed as there's operator cron job that's doing the job + here. Temperary keep this to not break integration test. + """ deposit_filter = self.root_chain.eventFilter('Deposit', {'fromBlock': 0}) worker = Thread(target=self.log_loop, args=(deposit_filter, 0.1), daemon=True) worker.start() def log_loop(self, event_filter, poll_interval): + """ + TODO: should be removed as there's operator cron job that's doing the job + here. Temperary keep this to not break integration test. + """ while True: for event in event_filter.get_new_entries(): - self.apply_deposit(event) + depositor = event['args']['depositor'] + amount = event['args']['amount'] + uid = event['args']['uid'] + self.apply_deposit(depositor, amount, uid) time.sleep(poll_interval) - def apply_deposit(self, event): - new_owner = utils.normalize_address(event['args']['depositor']) - amount = event['args']['amount'] - uid = event['args']['uid'] - deposit_tx = Transaction(0, uid, amount, new_owner) - self.current_block.add_tx(deposit_tx) + def apply_deposit(self, depositor, amount, uid): + new_owner = utils.normalize_address(depositor) + + if not self.current_block.get_tx_by_uid(uid): + deposit_tx = Transaction(0, uid, amount, new_owner) + self.current_block.add_tx(deposit_tx) + return deposit_tx.hash + + err_msg = 'deposit of uid: {} is already applied previously'.format(uid) + raise DepositAlreadyAppliedException(err_msg) def submit_block(self, sig): signature = bytes.fromhex(sig) diff --git a/plasma_cash/client/child_chain_client.py b/plasma_cash/child_chain/child_chain_client.py similarity index 89% rename from plasma_cash/client/child_chain_client.py rename to plasma_cash/child_chain/child_chain_client.py index 48db23b..2e68398 100644 --- a/plasma_cash/client/child_chain_client.py +++ b/plasma_cash/child_chain/child_chain_client.py @@ -65,12 +65,17 @@ def get_proof(self, blknum, uid): response = self.request(end_point, 'GET', params=params) return response.text + def send_transaction(self, tx): + end_point = '/send_tx' + data = {'tx': tx} + self.request(end_point, 'POST', data=data) + def submit_block(self, sig): - end_point = '/submit_block' + end_point = '/operator/submit_block' data = {'sig': sig} self.request(end_point, 'POST', data=data) - def send_transaction(self, tx): - end_point = '/send_tx' - data = {'tx': tx} + def apply_deposit(self, depositor, amount, uid): + end_point = '/operator/apply_deposit' + data = {'depositor': depositor, 'amount': amount, 'uid': uid} self.request(end_point, 'POST', data=data) diff --git a/plasma_cash/child_chain/exceptions.py b/plasma_cash/child_chain/exceptions.py index 3f11fd1..a7a49fa 100644 --- a/plasma_cash/child_chain/exceptions.py +++ b/plasma_cash/child_chain/exceptions.py @@ -24,3 +24,11 @@ class InvalidBlockNumException(Exception): class TxWithSameUidAlreadyExists(Exception): """the block already has one tx with the uid""" + + +class RequestFailedException(Exception): + """request failed without success http status""" + + +class DepositAlreadyAppliedException(Exception): + """the deposit is already applied""" diff --git a/plasma_cash/child_chain/server.py b/plasma_cash/child_chain/server.py index a6d74ca..f93ff60 100644 --- a/plasma_cash/child_chain/server.py +++ b/plasma_cash/child_chain/server.py @@ -2,43 +2,39 @@ from flask import Blueprint, request -from plasma_cash.child_chain import websocket, event +from plasma_cash.child_chain import event, websocket from plasma_cash.dependency_config import container -bp = Blueprint('api', __name__) +api = Blueprint('api', __name__) +operator = Blueprint('operator', __name__) + clients = {} -@bp.route('/block', methods=['GET']) +@api.route('/block', methods=['GET']) def get_current_block(): return container.get_child_chain().get_current_block() -@bp.route('/block/', methods=['GET']) +@api.route('/block/', methods=['GET']) def get_block(blknum): return container.get_child_chain().get_block(int(blknum)) -@bp.route('/proof', methods=['GET']) +@api.route('/proof', methods=['GET']) def get_proof(): blknum = int(request.args.get('blknum')) uid = int(request.args.get('uid')) return container.get_child_chain().get_proof(blknum, uid) -@bp.route('/submit_block', methods=['POST']) -def submit_block(): - sig = request.form['sig'] - return container.get_child_chain().submit_block(sig) - - -@bp.route('/send_tx', methods=['POST']) +@api.route('/send_tx', methods=['POST']) def send_tx(): tx = request.form['tx'] return container.get_child_chain().apply_transaction(tx) -@bp.route('/', methods=['GET']) +@api.route('/', methods=['GET']) def root(): global clients @@ -52,6 +48,20 @@ def root(): return '' +@operator.route('/submit_block', methods=['POST']) +def submit_block(): + sig = request.form['sig'] + return container.get_child_chain().submit_block(sig) + + +@operator.route('/apply_deposit', methods=['POST']) +def apply_deposit(): + depositor = request.form['depositor'] + amount = int(request.form['amount']) + uid = int(request.form['uid']) + return container.get_child_chain().apply_deposit(depositor, amount, uid) + + @event.on('websocket.join') def join(ws, arg): clients[arg] = ws diff --git a/plasma_cash/client/exceptions.py b/plasma_cash/client/exceptions.py index ac3be2b..370b788 100644 --- a/plasma_cash/client/exceptions.py +++ b/plasma_cash/client/exceptions.py @@ -1,6 +1,2 @@ -class RequestFailedException(Exception): - """request failed without success http status""" - - class TxHistoryNotFoundException(Exception): """tx history is not found for specific block number""" diff --git a/plasma_cash/dependency_config.py b/plasma_cash/dependency_config.py index 32bec6c..f5ac684 100644 --- a/plasma_cash/dependency_config.py +++ b/plasma_cash/dependency_config.py @@ -3,7 +3,7 @@ from plasma_cash.child_chain.child_chain import ChildChain from plasma_cash.child_chain.db.leveldb import LevelDb from plasma_cash.child_chain.db.memory_db import MemoryDb -from plasma_cash.client.child_chain_client import ChildChainClient +from plasma_cash.child_chain.child_chain_client import ChildChainClient from plasma_cash.config import PROJECT_DIR, db_config, plasma_config from plasma_cash.root_chain.deployer import Deployer diff --git a/plasma_cash/operator_cron_job/__init__.py b/plasma_cash/operator_cron_job/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plasma_cash/operator_cron_job/__main__.py b/plasma_cash/operator_cron_job/__main__.py new file mode 100644 index 0000000..82d810f --- /dev/null +++ b/plasma_cash/operator_cron_job/__main__.py @@ -0,0 +1,28 @@ +from plasma_cash.config import plasma_config +from plasma_cash.dependency_config import container + +from .job_handler import JobHandler +from .jobs.apply_deposit_job import ApplyDepositJob +from .jobs.submit_block_job import SubmitBlockJob + +SUBMIT_BLOCK_INTERVAL = 5 +APPLY_DEPOSIT_INTERVAL = 1 + + +def setup_job_handler(job_handler): + root_chain = container.get_root_chain() + child_chain_client = container.get_child_chain_client() + + apply_deposit_job = ApplyDepositJob(root_chain, child_chain_client) + submit_block_job = SubmitBlockJob(child_chain_client, plasma_config['AUTHORITY_KEY']) + + job_handler.add_job(submit_block_job, time_interval=SUBMIT_BLOCK_INTERVAL) + job_handler.add_job(apply_deposit_job, time_interval=APPLY_DEPOSIT_INTERVAL) + + return job_handler + + +if __name__ == '__main__': + job_handler = JobHandler() + job_handler = setup_job_handler(job_handler) + job_handler.start() diff --git a/plasma_cash/operator_cron_job/job_handler.py b/plasma_cash/operator_cron_job/job_handler.py new file mode 100644 index 0000000..2f22abe --- /dev/null +++ b/plasma_cash/operator_cron_job/job_handler.py @@ -0,0 +1,20 @@ +import time +from threading import Thread + + +class JobHandler(object): + def __init__(self): + self.workers = [] + + def add_job(self, job, time_interval): + worker = Thread(target=self._schedule_the_job, args=(job, time_interval,), daemon=True) + self.workers.append(worker) + + def start(self): + for worker in self.workers: + worker.start() + + def _schedule_the_job(self, job, time_interval): + while True: + job.run() + time.sleep(time_interval) diff --git a/plasma_cash/operator_cron_job/jobs/__init__.py b/plasma_cash/operator_cron_job/jobs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plasma_cash/operator_cron_job/jobs/apply_deposit_job.py b/plasma_cash/operator_cron_job/jobs/apply_deposit_job.py new file mode 100644 index 0000000..4b14272 --- /dev/null +++ b/plasma_cash/operator_cron_job/jobs/apply_deposit_job.py @@ -0,0 +1,15 @@ +from .job_interface import JobInterface + + +class ApplyDepositJob(JobInterface): + + def __init__(self, root_chain, child_chain): + self.child_chain = child_chain + self.deposit_filter = root_chain.eventFilter('Deposit', {'fromBlock': 0}) + + def run(self): + for event in self.deposit_filter.get_new_entries(): + depositor = event['args']['depositor'] + amount = event['args']['amount'] + uid = event['args']['uid'] + self.child_chain.apply_deposit(depositor, amount, uid) diff --git a/plasma_cash/operator_cron_job/jobs/job_interface.py b/plasma_cash/operator_cron_job/jobs/job_interface.py new file mode 100644 index 0000000..62e7654 --- /dev/null +++ b/plasma_cash/operator_cron_job/jobs/job_interface.py @@ -0,0 +1,8 @@ +import abc + + +class JobInterface(abc.ABC): + + @abc.abstractmethod + def run(self): + return NotImplemented diff --git a/plasma_cash/operator_cron_job/jobs/submit_block_job.py b/plasma_cash/operator_cron_job/jobs/submit_block_job.py new file mode 100644 index 0000000..ce4cc98 --- /dev/null +++ b/plasma_cash/operator_cron_job/jobs/submit_block_job.py @@ -0,0 +1,23 @@ +import rlp +from ethereum import utils + +from plasma_cash.child_chain.block import Block +from plasma_cash.utils.utils import sign + +from .job_interface import JobInterface + + +class SubmitBlockJob(JobInterface): + + def __init__(self, child_chain, key): + self.child_chain = child_chain + self.key = utils.normalize_key(key) + + def run(self): + block = self._get_current_block() + sig = sign(block.hash, self.key) + self.child_chain.submit_block(sig.hex()) + + def _get_current_block(self): + block = self.child_chain.get_current_block() + return rlp.decode(utils.decode_hex(block), Block) diff --git a/unit_tests/child_chain/test_child_chain.py b/unit_tests/child_chain/test_child_chain.py index b4b6029..ce766b2 100644 --- a/unit_tests/child_chain/test_child_chain.py +++ b/unit_tests/child_chain/test_child_chain.py @@ -8,12 +8,12 @@ from plasma_cash.child_chain.block import Block from plasma_cash.child_chain.child_chain import ChildChain from plasma_cash.child_chain.db.memory_db import MemoryDb -from plasma_cash.child_chain.exceptions import (InvalidBlockNumException, +from plasma_cash.child_chain.exceptions import (DepositAlreadyAppliedException, + InvalidBlockNumException, InvalidBlockSignatureException, InvalidTxSignatureException, PreviousTxNotFoundException, - TxAlreadySpentException, - TxAmountMismatchException, + TxAlreadySpentException, TxAmountMismatchException, TxWithSameUidAlreadyExists) from plasma_cash.child_chain.transaction import Transaction from unit_tests.unstub_mixin import UnstubMixin @@ -59,22 +59,26 @@ def test_constructor(self, root_chain, db): def test_apply_deposit(self, child_chain): DUMMY_AMOUNT = 123 - DUMMY_UID = 'dummy uid' + DUMMY_UID = 0 DUMMY_ADDR = b'\xfd\x02\xec\xeeby~u\xd8k\xcf\xf1d.\xb0\x84J\xfb(\xc7' - event = {'args': { - 'amount': DUMMY_AMOUNT, - 'uid': DUMMY_UID, - 'depositor': DUMMY_ADDR, - }} - - child_chain.apply_deposit(event) + tx_hash = child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID) tx = child_chain.current_block.transaction_set[0] + assert tx_hash == tx.hash assert tx.amount == DUMMY_AMOUNT assert tx.uid == DUMMY_UID assert tx.new_owner == eth_utils.normalize_address(DUMMY_ADDR) + def test_apply_deposit_should_fail_when_is_already_applied(self, child_chain, root_chain): + DUMMY_AMOUNT = 123 + DUMMY_UID = 0 + DUMMY_ADDR = b'\xfd\x02\xec\xeeby~u\xd8k\xcf\xf1d.\xb0\x84J\xfb(\xc7' + + child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID) + with pytest.raises(DepositAlreadyAppliedException): + child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID) + def test_submit_block(self, child_chain, root_chain): DUMMY_MERKLE = 'merkle hash' MOCK_TRANSACT = mock() diff --git a/unit_tests/client/test_child_chain_client.py b/unit_tests/child_chain/test_child_chain_client.py similarity index 85% rename from unit_tests/client/test_child_chain_client.py rename to unit_tests/child_chain/test_child_chain_client.py index 7adb63a..3509530 100644 --- a/unit_tests/client/test_child_chain_client.py +++ b/unit_tests/child_chain/test_child_chain_client.py @@ -1,8 +1,8 @@ import pytest from mockito import mock, verify, when -from plasma_cash.client.child_chain_client import ChildChainClient -from plasma_cash.client.exceptions import RequestFailedException +from plasma_cash.child_chain.child_chain_client import ChildChainClient +from plasma_cash.child_chain.exceptions import RequestFailedException from unit_tests.unstub_mixin import UnstubMixin @@ -64,7 +64,7 @@ def test_request_success(self, client): MOCK_RESPONSE = mock({'ok': True}) - (when('plasma_cash.client.child_chain_client.requests') + (when('plasma_cash.child_chain.child_chain_client.requests') .request( method=DUMMY_METHOD, url=URL, @@ -96,7 +96,7 @@ def test_request_failed(self, client): MOCK_RESPONSE = mock({'ok': False}) - (when('plasma_cash.client.child_chain_client.requests') + (when('plasma_cash.child_chain.child_chain_client.requests') .request( method=DUMMY_METHOD, url=URL, @@ -144,22 +144,41 @@ def test_get_proof(self, client): resp = client.get_proof(DUMMY_BLK_NUM, DUMMY_UID) assert resp == RESP_TEXT + def test_send_transaction(self, client): + DUMMY_TX = 'tx' + when(client).request(any, any, data=any).thenReturn(None) + client.send_transaction(DUMMY_TX) + verify(client).request( + '/send_tx', + 'POST', + data={'tx': DUMMY_TX} + ) + def test_submit_block(self, client): DUMMY_SIG = 'sig' when(client).request(any, any, data=any).thenReturn(None) client.submit_block(DUMMY_SIG) verify(client).request( - '/submit_block', + '/operator/submit_block', 'POST', data={'sig': DUMMY_SIG} ) - def test_send_transaction(self, client): - DUMMY_TX = 'tx' + def test_apply_deposit(self, client): + DUMMY_DEPOSITOR = 'depositor' + DUMMY_AMOUNT = 123 + DUMMY_UID = 0 + when(client).request(any, any, data=any).thenReturn(None) - client.send_transaction(DUMMY_TX) + client.apply_deposit(DUMMY_DEPOSITOR, DUMMY_AMOUNT, DUMMY_UID) + + request_data = { + 'depositor': DUMMY_DEPOSITOR, + 'amount': DUMMY_AMOUNT, + 'uid': DUMMY_UID + } verify(client).request( - '/send_tx', + '/operator/apply_deposit', 'POST', - data={'tx': DUMMY_TX} + data=request_data ) diff --git a/unit_tests/child_chain/test_event.py b/unit_tests/child_chain/test_event.py index 30dfce1..6aa9c16 100644 --- a/unit_tests/child_chain/test_event.py +++ b/unit_tests/child_chain/test_event.py @@ -1,6 +1,6 @@ from mockito import ANY, mock, verify, when -from plasma_cash.child_chain.event import on, emit +from plasma_cash.child_chain.event import emit, on from unit_tests.unstub_mixin import UnstubMixin handler = None diff --git a/unit_tests/child_chain/test_server.py b/unit_tests/child_chain/test_server.py index c6db38f..9404a5a 100644 --- a/unit_tests/child_chain/test_server.py +++ b/unit_tests/child_chain/test_server.py @@ -51,6 +51,17 @@ def test_get_proof(self, client): resp = client.get('/proof', query_string={'blknum': 1, 'uid': 1}) assert resp.data == DUMMY_PROOF.encode() + def test_send_tx(self, client): + (when('plasma_cash.child_chain.server.container') + .get_child_chain().thenReturn(self.CHILD_CHAIN)) + + DUMMY_TX = 'tx' + DUMMY_TX_HASH = 'tx hash' + when(self.CHILD_CHAIN).apply_transaction(DUMMY_TX).thenReturn(DUMMY_TX_HASH) + + resp = client.post('send_tx', data={'tx': DUMMY_TX}) + assert resp.data == DUMMY_TX_HASH.encode() + def test_submit_block(self, client): (when('plasma_cash.child_chain.server.container') .get_child_chain().thenReturn(self.CHILD_CHAIN)) @@ -59,16 +70,27 @@ def test_submit_block(self, client): DUMMY_MERKLE_HASH = 'merkle hash' when(self.CHILD_CHAIN).submit_block(SIG).thenReturn(DUMMY_MERKLE_HASH) - resp = client.post('submit_block', data={'sig': SIG}) + resp = client.post('/operator/submit_block', data={'sig': SIG}) assert resp.data == DUMMY_MERKLE_HASH.encode() - def test_send_tx(self, client): + def test_apply_deposit(self, client): (when('plasma_cash.child_chain.server.container') .get_child_chain().thenReturn(self.CHILD_CHAIN)) - DUMMY_TX = 'tx' - DUMMY_TX_HASH = 'tx hash' - when(self.CHILD_CHAIN).apply_transaction(DUMMY_TX).thenReturn(DUMMY_TX_HASH) + DUMMY_DEPOSITOR = 'depositor' + DUMMY_AMOUNT = 123 + DUMMY_UID = 0 - resp = client.post('send_tx', data={'tx': DUMMY_TX}) + data = { + 'depositor': DUMMY_DEPOSITOR, + 'amount': DUMMY_AMOUNT, + 'uid': DUMMY_UID + } + + DUMMY_TX_HASH = 'dummy tx hash' + (when(self.CHILD_CHAIN) + .apply_deposit(DUMMY_DEPOSITOR, DUMMY_AMOUNT, DUMMY_UID) + .thenReturn(DUMMY_TX_HASH)) + + resp = client.post('/operator/apply_deposit', data=data) assert resp.data == DUMMY_TX_HASH.encode() diff --git a/unit_tests/client/test_client.py b/unit_tests/client/test_client.py index 571583b..27f7d1f 100644 --- a/unit_tests/client/test_client.py +++ b/unit_tests/client/test_client.py @@ -109,22 +109,6 @@ def test_get_current_block(self, child_chain, client): assert client.get_current_block() == DUMMY_DECODED_BLOCK - def test_get_block(self, child_chain, client): - DUMMY_BLOCK = 'dummy block' - DUMMY_BLOCK_NUM = 'dummy block num' - DUMMY_BLOCK_HEX = 'dummy block hex' - DUMMY_DECODED_BLOCK = 'decoded block' - - when(child_chain).get_block(DUMMY_BLOCK_NUM).thenReturn(DUMMY_BLOCK) - (when('plasma_cash.client.client.utils') - .decode_hex(DUMMY_BLOCK) - .thenReturn(DUMMY_BLOCK_HEX)) - (when('plasma_cash.client.client.rlp') - .decode(DUMMY_BLOCK_HEX, Block) - .thenReturn(DUMMY_DECODED_BLOCK)) - - assert client.get_block(DUMMY_BLOCK_NUM) == DUMMY_DECODED_BLOCK - def test_get_proof(self, child_chain, client): DUMMY_BLOCK_NUM = 'dummy block num' DUMMY_PROOF = 'dummy proof' diff --git a/unit_tests/operator_cron_job/__init__.py b/unit_tests/operator_cron_job/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unit_tests/operator_cron_job/jobs/__init__.py b/unit_tests/operator_cron_job/jobs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unit_tests/operator_cron_job/jobs/test_apply_deposit_job.py b/unit_tests/operator_cron_job/jobs/test_apply_deposit_job.py new file mode 100644 index 0000000..3373ab2 --- /dev/null +++ b/unit_tests/operator_cron_job/jobs/test_apply_deposit_job.py @@ -0,0 +1,36 @@ +import pytest +from mockito import mock, verify, when + +from plasma_cash.operator_cron_job.jobs.apply_deposit_job import ApplyDepositJob +from unit_tests.unstub_mixin import UnstubMixin + + +class TestApplyDepositJob(UnstubMixin): + + @pytest.fixture(scope='function') + def root_chain(self): + return mock() + + @pytest.fixture(scope='function') + def child_chain(self): + return mock() + + def test_run(self, root_chain, child_chain): + deposit_filter = mock() + (when(root_chain).eventFilter('Deposit', {'fromBlock': 0}) + .thenReturn(deposit_filter)) + + DUMMY_DEPOSITOR = 'dummy depositor' + DUMMY_AMOUNT = 100 + DUMMY_UID = 0 + events = [{ + 'args': { + 'depositor': DUMMY_DEPOSITOR, 'amount': DUMMY_AMOUNT, 'uid': DUMMY_UID + }} + ] + when(deposit_filter).get_new_entries().thenReturn(events) + + job = ApplyDepositJob(root_chain, child_chain) + job.run() + + verify(child_chain).apply_deposit(DUMMY_DEPOSITOR, DUMMY_AMOUNT, DUMMY_UID) diff --git a/unit_tests/operator_cron_job/jobs/test_submit_block_job.py b/unit_tests/operator_cron_job/jobs/test_submit_block_job.py new file mode 100644 index 0000000..5a14ebe --- /dev/null +++ b/unit_tests/operator_cron_job/jobs/test_submit_block_job.py @@ -0,0 +1,34 @@ +import pytest +import rlp +from mockito import mock, verify, when + +from plasma_cash.child_chain.block import Block +from plasma_cash.child_chain.transaction import Transaction +from plasma_cash.operator_cron_job.jobs.submit_block_job import SubmitBlockJob +from plasma_cash.utils.utils import sign +from unit_tests.unstub_mixin import UnstubMixin + + +class TestSubmitBlockJob(UnstubMixin): + + @pytest.fixture(scope='function') + def child_chain(self): + return mock() + + def test_run(self, child_chain): + block = self._generate_dummy_block() + when(child_chain).get_current_block().thenReturn(rlp.encode(block).hex()) + + key = (b'\xa1\x89i\x81|,\xef\xad\xf5+\x93\xeb \xf9\x17\xdc\xe7`\xce' + b'\x13\xb2\xac\x90%\xe06\x1a\xd1\xe7\xa1\xd4H') + job = SubmitBlockJob(child_chain, key) + job.run() + + sig = sign(block.hash, key) + verify(child_chain).submit_block(sig.hex()) + + def _generate_dummy_block(self): + owner = b'\x8cT\xa4\xa0\x17\x9f$\x80\x1fI\xf92-\xab<\x87\xeb\x19L\x9b' + tx = Transaction(prev_block=0, uid=1, amount=10, new_owner=owner) + block = Block(transaction_set=[tx]) + return block diff --git a/unit_tests/operator_cron_job/test_job_handler.py b/unit_tests/operator_cron_job/test_job_handler.py new file mode 100644 index 0000000..9971f49 --- /dev/null +++ b/unit_tests/operator_cron_job/test_job_handler.py @@ -0,0 +1,39 @@ +import time + +import pytest + +from plasma_cash.operator_cron_job.job_handler import JobHandler +from plasma_cash.operator_cron_job.jobs.job_interface import JobInterface +from unit_tests.unstub_mixin import UnstubMixin + + +class FakeJob(JobInterface): + + def __init__(self): + self.run_count = 0 + + def run(self): + self.run_count += 1 + + +class TestJobHandler(UnstubMixin): + + @pytest.fixture(scope='function') + def job_handler(self): + return JobHandler() + + def test_add_job(self, job_handler): + job = FakeJob() + job_handler.add_job(job, 1) + assert len(job_handler.workers) == 1 + + def test_start(self, job_handler): + job = FakeJob() + + JOB_TIME_INTERVAL = 0.05 + job_handler.add_job(job, JOB_TIME_INTERVAL) + job_handler.start() + + SLIGHLY_LONGER_INTERVAL_FOR_RUN_JOB_TWICE = JOB_TIME_INTERVAL + 0.001 + time.sleep(SLIGHLY_LONGER_INTERVAL_FOR_RUN_JOB_TWICE) + assert job.run_count == 2 diff --git a/unit_tests/operator_cron_job/test_main.py b/unit_tests/operator_cron_job/test_main.py new file mode 100644 index 0000000..9d1e1f2 --- /dev/null +++ b/unit_tests/operator_cron_job/test_main.py @@ -0,0 +1,38 @@ +import pytest +from mockito import ANY, mock, spy, verify, when + +from plasma_cash.operator_cron_job.__main__ import (APPLY_DEPOSIT_INTERVAL, SUBMIT_BLOCK_INTERVAL, + setup_job_handler) +from plasma_cash.operator_cron_job.job_handler import JobHandler +from plasma_cash.operator_cron_job.jobs.apply_deposit_job import ApplyDepositJob +from plasma_cash.operator_cron_job.jobs.submit_block_job import SubmitBlockJob +from unit_tests.unstub_mixin import UnstubMixin + + +class TestMain(UnstubMixin): + + @pytest.fixture(scope='function') + def root_chain(self): + root_chain = mock() + deposit_filter = mock() + (when(root_chain).eventFilter('Deposit', {'fromBlock': 0}) + .thenReturn(deposit_filter)) + when(deposit_filter).get_new_entries().thenReturn([]) + return root_chain + + @pytest.fixture(scope='function') + def child_chain(self): + return mock() + + def test_setup_job_handler(self, root_chain, child_chain): + (when('plasma_cash.operator_cron_job.__main__.container') + .get_child_chain().thenReturn(child_chain)) + + (when('plasma_cash.operator_cron_job.__main__.container') + .get_root_chain().thenReturn(root_chain)) + + job_handler = spy(JobHandler()) + job_handler = setup_job_handler(job_handler) + + verify(job_handler).add_job(ANY(SubmitBlockJob), time_interval=SUBMIT_BLOCK_INTERVAL) + verify(job_handler).add_job(ANY(ApplyDepositJob), time_interval=APPLY_DEPOSIT_INTERVAL)