From 95787908bc6eef39bdfb79ea18e49d8195985832 Mon Sep 17 00:00:00 2001 From: Boolafish Date: Sat, 18 Aug 2018 23:58:37 +0800 Subject: [PATCH] Add Operator Cron jobs: SubmitBlock, ApplyDesposit Operator needs to have some cron jobs. This commit adds two cron jobs that should be run by operator. First one is a job that submits a block every certain time. Second one is to move "apply deposit" job, which listens to root chain deposit event, from ChildChain to operator cron job. Reason for that is that we want ChildChain server to be scalable. Current design would lead to single node restriction. This commit does NOT include integration tests code change. --- .isort.cfg | 2 + README.md | 8 +++- integration_tests/features/environment.py | 2 +- .../steps/challenge_double_spending_flow.py | 2 +- .../features/steps/challenge_history_flow.py | 2 +- plasma_cash/child_chain/__init__.py | 3 +- plasma_cash/child_chain/block.py | 6 --- plasma_cash/child_chain/child_chain.py | 37 ++++++++++++------ .../child_chain_client.py | 13 +++++-- plasma_cash/child_chain/exceptions.py | 8 ++++ plasma_cash/child_chain/server.py | 36 ++++++++++------- plasma_cash/client/exceptions.py | 4 -- plasma_cash/dependency_config.py | 2 +- plasma_cash/operator_cron_job/__init__.py | 0 plasma_cash/operator_cron_job/__main__.py | 28 +++++++++++++ plasma_cash/operator_cron_job/job_handler.py | 20 ++++++++++ .../operator_cron_job/jobs/__init__.py | 0 .../jobs/apply_deposit_job.py | 15 +++++++ .../operator_cron_job/jobs/job_interface.py | 8 ++++ .../jobs/submit_block_job.py | 23 +++++++++++ unit_tests/child_chain/test_child_chain.py | 26 +++++++------ .../test_child_chain_client.py | 39 ++++++++++++++----- unit_tests/child_chain/test_event.py | 2 +- unit_tests/child_chain/test_server.py | 34 +++++++++++++--- unit_tests/client/test_client.py | 16 -------- unit_tests/operator_cron_job/__init__.py | 0 unit_tests/operator_cron_job/jobs/__init__.py | 0 .../jobs/test_apply_deposit_job.py | 36 +++++++++++++++++ .../jobs/test_submit_block_job.py | 34 ++++++++++++++++ .../operator_cron_job/test_job_handler.py | 39 +++++++++++++++++++ unit_tests/operator_cron_job/test_main.py | 38 ++++++++++++++++++ 31 files changed, 394 insertions(+), 89 deletions(-) create mode 100644 .isort.cfg rename plasma_cash/{client => child_chain}/child_chain_client.py (89%) create mode 100644 plasma_cash/operator_cron_job/__init__.py create mode 100644 plasma_cash/operator_cron_job/__main__.py create mode 100644 plasma_cash/operator_cron_job/job_handler.py create mode 100644 plasma_cash/operator_cron_job/jobs/__init__.py create mode 100644 plasma_cash/operator_cron_job/jobs/apply_deposit_job.py create mode 100644 plasma_cash/operator_cron_job/jobs/job_interface.py create mode 100644 plasma_cash/operator_cron_job/jobs/submit_block_job.py rename unit_tests/{client => child_chain}/test_child_chain_client.py (85%) create mode 100644 unit_tests/operator_cron_job/__init__.py create mode 100644 unit_tests/operator_cron_job/jobs/__init__.py create mode 100644 unit_tests/operator_cron_job/jobs/test_apply_deposit_job.py create mode 100644 unit_tests/operator_cron_job/jobs/test_submit_block_job.py create mode 100644 unit_tests/operator_cron_job/test_job_handler.py create mode 100644 unit_tests/operator_cron_job/test_main.py 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)