From 650d1a194070dae8095e710ac9e8fe94d23918a6 Mon Sep 17 00:00:00 2001 From: Boolafish Date: Tue, 4 Sep 2018 00:14:38 +0900 Subject: [PATCH] Build, sign and send tx instead of using `transact` Previously, all our call to smart contract uses web3's `transact` api. However, that api relies on the eth node to have account information in its wallet. This is an unwanted feature that we want to remove. Also, this would block us from using infura (web service), and also parity node would need another manual action to approve account tx, which make previous code impossible to use with the two eth nodes. This commit changes `transact` to `buildTransaction` and then sign and send. --- plasma_cash/child_chain/child_chain.py | 17 ++++--- plasma_cash/client/client.py | 26 +++++++---- plasma_cash/client/history.py | 2 +- plasma_cash/dependency_config.py | 6 +-- plasma_cash/root_chain/deployer.py | 18 +++++--- unit_tests/child_chain/test_child_chain.py | 28 +++++++----- unit_tests/client/test_client.py | 52 +++++++++++++++------- unit_tests/operator_cron_job/test_main.py | 2 +- 8 files changed, 101 insertions(+), 50 deletions(-) diff --git a/plasma_cash/child_chain/child_chain.py b/plasma_cash/child_chain/child_chain.py index b10178a..e44c54c 100644 --- a/plasma_cash/child_chain/child_chain.py +++ b/plasma_cash/child_chain/child_chain.py @@ -18,9 +18,10 @@ class ChildChain(object): - def __init__(self, authority, root_chain, db): + def __init__(self, key, root_chain, db): + self.key = utils.normalize_key(key) + self.authority = utils.privtoaddr(self.key) self.root_chain = root_chain - self.authority = authority self.db = db self.current_block = Block() self.current_block_number = self.db.get_current_block_num() @@ -66,9 +67,15 @@ def submit_block(self, sig): merkle_hash = self.current_block.merklize_transaction_set() authority_address = w3.toChecksumAddress('0x' + self.authority.hex()) - self.root_chain.functions.submitBlock(merkle_hash, self.current_block_number).transact( - {'from': authority_address} - ) + tx = (self.root_chain.functions + .submitBlock(merkle_hash, self.current_block_number) + .buildTransaction({ + 'from': authority_address, + 'nonce': w3.eth.getTransactionCount(authority_address, 'pending') + })) + + signed = w3.eth.account.signTransaction(tx, self.key) + w3.eth.sendRawTransaction(signed.rawTransaction) emit('chain.block', self.current_block_number) self.db.save_block(self.current_block, self.current_block_number) diff --git a/plasma_cash/client/client.py b/plasma_cash/client/client.py index 95e01b5..90eab31 100644 --- a/plasma_cash/client/client.py +++ b/plasma_cash/client/client.py @@ -18,11 +18,17 @@ def __init__(self, root_chain, child_chain, key): def address(self): return w3.toChecksumAddress(utils.privtoaddr(self.key)) + def _sign_and_send_tx(self, tx): + tx['nonce'] = w3.eth.getTransactionCount(self.address, 'pending') + signed = w3.eth.account.signTransaction(tx, self.key) + w3.eth.sendRawTransaction(signed.rawTransaction) + def deposit(self, amount, currency): value = w3.toWei(amount, 'ether') if currency == '0x' + '00' * 20 else 0 - self.root_chain.functions.deposit(currency, amount).transact( + tx = self.root_chain.functions.deposit(currency, amount).buildTransaction( {'from': self.address, 'value': value} ) + self._sign_and_send_tx(tx) def submit_block(self): # TODO: this method should be a cron job in child chain @@ -65,14 +71,15 @@ def start_exit(self, uid, prev_tx_blk_num, tx_blk_num): block.merklize_transaction_set() tx_proof = block.merkle.create_merkle_proof(uid) - self.root_chain.functions.startExit( + tx = self.root_chain.functions.startExit( rlp.encode(prev_tx), prev_tx_proof, prev_tx_blk_num, rlp.encode(tx), tx_proof, tx_blk_num - ).transact({'from': self.address}) + ).buildTransaction({'from': self.address}) + self._sign_and_send_tx(tx) def challenge_exit(self, uid, tx_blk_num): block = self.get_block(tx_blk_num) @@ -81,9 +88,10 @@ def challenge_exit(self, uid, tx_blk_num): block.merklize_transaction_set() tx_proof = block.merkle.create_merkle_proof(uid) - self.root_chain.functions.challengeExit( + tx = self.root_chain.functions.challengeExit( uid, rlp.encode(challenge_tx), tx_proof, tx_blk_num - ).transact({'from': self.address}) + ).buildTransaction({'from': self.address}) + self._sign_and_send_tx(tx) def respond_challenge_exit(self, challenge_tx, uid, tx_blk_num): block = self.get_block(tx_blk_num) @@ -92,9 +100,11 @@ def respond_challenge_exit(self, challenge_tx, uid, tx_blk_num): block.merklize_transaction_set() tx_proof = block.merkle.create_merkle_proof(uid) - self.root_chain.functions.respondChallengeExit( + tx = self.root_chain.functions.respondChallengeExit( uid, challenge_tx, rlp.encode(respond_tx), tx_proof, tx_blk_num - ).transact({'from': self.address}) + ).buildTransaction({'from': self.address}) + self._sign_and_send_tx(tx) def finalize_exit(self, uid): - self.root_chain.functions.finalizeExit(uid).transact({'from': self.address}) + tx = self.root_chain.functions.finalizeExit(uid).buildTransaction({'from': self.address}) + self._sign_and_send_tx(tx) diff --git a/plasma_cash/client/history.py b/plasma_cash/client/history.py index 511a8e3..0760ac1 100644 --- a/plasma_cash/client/history.py +++ b/plasma_cash/client/history.py @@ -1,5 +1,5 @@ import rlp -from rlp.sedes import big_endian_int, binary, CountableList +from rlp.sedes import CountableList, big_endian_int, binary from .exceptions import TxHistoryNotFoundException diff --git a/plasma_cash/dependency_config.py b/plasma_cash/dependency_config.py index f5ac684..a4058f8 100644 --- a/plasma_cash/dependency_config.py +++ b/plasma_cash/dependency_config.py @@ -1,9 +1,9 @@ import os from plasma_cash.child_chain.child_chain import ChildChain +from plasma_cash.child_chain.child_chain_client import ChildChainClient from plasma_cash.child_chain.db.leveldb import LevelDb from plasma_cash.child_chain.db.memory_db import MemoryDb -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 @@ -38,10 +38,10 @@ def get_root_chain(self): def get_child_chain(self): if self._child_chain is None: - authority = plasma_config['AUTHORITY'] + key = plasma_config['AUTHORITY_KEY'] root_chain = self.get_root_chain() db = self.get_db() - self._child_chain = ChildChain(authority, root_chain, db) + self._child_chain = ChildChain(key, root_chain, db) return self._child_chain def get_child_chain_client(self): diff --git a/plasma_cash/root_chain/deployer.py b/plasma_cash/root_chain/deployer.py index 5ea667c..9297279 100644 --- a/plasma_cash/root_chain/deployer.py +++ b/plasma_cash/root_chain/deployer.py @@ -49,14 +49,18 @@ def deploy_contract(self, path, args=(), gas=4410000): abi, bytecode, contract_name = self.compile_contract(path, args) contract = w3.eth.contract(abi=abi, bytecode=bytecode) - # Get transaction hash from deployed contract - tx_hash = contract.deploy( - transaction={'from': w3.eth.accounts[0], 'gas': gas}, - args=args - ) + address = w3.toChecksumAddress(plasma_config['AUTHORITY'].hex()) + key = plasma_config['AUTHORITY_KEY'] + tx = contract.constructor().buildTransaction({ + 'from': address, + 'nonce': w3.eth.getTransactionCount(address, 'pending') + }) + signed = w3.eth.account.signTransaction(tx, key) + tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction) + tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash) - print('Successfully deployed {} contract with tx hash {}!'.format( - contract_name, tx_hash.hex())) + print('Successfully deployed {} contract with tx hash {} in contract address {}!'.format( + contract_name, tx_hash.hex(), tx_receipt.contractAddress)) def get_contract(self, path): file_name = path.split('/')[1] diff --git a/unit_tests/child_chain/test_child_chain.py b/unit_tests/child_chain/test_child_chain.py index ce766b2..df1e0f0 100644 --- a/unit_tests/child_chain/test_child_chain.py +++ b/unit_tests/child_chain/test_child_chain.py @@ -3,7 +3,7 @@ import pytest import rlp from ethereum import utils as eth_utils -from mockito import ANY, expect, mock, verify, when +from mockito import ANY, expect, mock, when from plasma_cash.child_chain.block import Block from plasma_cash.child_chain.child_chain import ChildChain @@ -20,8 +20,7 @@ class TestChildChain(UnstubMixin): - DUMMY_AUTHORITY = b"\x14\x7f\x08\x1b\x1a6\xa8\r\xf0Y\x15(ND'\xc1\xf6\xdd\x98\x84" - DUMMY_SIG = '01' * 65 # sig for DUMMY_AUTHORITY + DUMMY_KEY = '0xa18969817c2cefadf52b93eb20f917dce760ce13b2ac9025e0361ad1e7a1d448' DUMMY_TX_NEW_OWNER = b'\xfd\x02\xec\xeeby~u\xd8k\xcf\xf1d.\xb0\x84J\xfb(\xc7' @pytest.fixture(scope='function') @@ -40,7 +39,7 @@ def child_chain(self, root_chain, db): expect(root_chain).eventFilter('Deposit', {'fromBlock': 0}) expect(Thread).start() - child_chain = ChildChain(self.DUMMY_AUTHORITY, root_chain, db) + child_chain = ChildChain(self.DUMMY_KEY, root_chain, db) # create a dummy transaction tx = Transaction(prev_block=0, uid=1, amount=10, new_owner=DUMMY_TX_OWNER) @@ -55,7 +54,7 @@ def test_constructor(self, root_chain, db): expect(root_chain).eventFilter('Deposit', {'fromBlock': 0}) expect(Thread).start() - ChildChain(self.DUMMY_AUTHORITY, root_chain, db) + ChildChain(self.DUMMY_KEY, root_chain, db) def test_apply_deposit(self, child_chain): DUMMY_AMOUNT = 123 @@ -81,18 +80,27 @@ def test_apply_deposit_should_fail_when_is_already_applied(self, child_chain, ro def test_submit_block(self, child_chain, root_chain): DUMMY_MERKLE = 'merkle hash' - MOCK_TRANSACT = mock() + MOCK_FUNCTION = mock() + DUMMY_NONCE = 100 + DUMMY_TX = {'nonce': DUMMY_NONCE, 'gas': 100, 'gasPrice': 100} block_number = child_chain.current_block_number block = child_chain.current_block when(child_chain.current_block).merklize_transaction_set().thenReturn(DUMMY_MERKLE) (when(root_chain.functions) .submitBlock(DUMMY_MERKLE, block_number) - .thenReturn(MOCK_TRANSACT)) + .thenReturn(MOCK_FUNCTION)) + when(MOCK_FUNCTION).buildTransaction(ANY).thenReturn(DUMMY_TX) + (when('plasma_cash.child_chain.child_chain') + .get_sender(ANY, ANY).thenReturn(child_chain.authority)) + (when('plasma_cash.child_chain.child_chain.w3.eth') + .getTransactionCount(ANY, ANY).thenReturn(DUMMY_NONCE)) + (when('plasma_cash.child_chain.child_chain.w3.eth') + .sendRawTransaction(ANY).thenReturn(None)) + + DUMMY_SIG = '01' * 65 + child_chain.submit_block(DUMMY_SIG) - child_chain.submit_block(self.DUMMY_SIG) - - verify(MOCK_TRANSACT).transact(ANY) assert child_chain.current_block_number == block_number + 1 assert child_chain.db.get_block(block_number) == block assert child_chain.current_block == Block() diff --git a/unit_tests/client/test_client.py b/unit_tests/client/test_client.py index 27f7d1f..c066c55 100644 --- a/unit_tests/client/test_client.py +++ b/unit_tests/client/test_client.py @@ -40,16 +40,37 @@ def test_address(self, client): assert client.address == DUMMY_ADDRESS + def test_sign_and_send_tx(self, client): + DUMMY_NONCE = 123 + DUMMY_TX = { + 'gas': 100, + 'gasPrice': 100, + 'nonce': DUMMY_NONCE + } + (when('plasma_cash.client.client.w3.eth') + .getTransactionCount(ANY, ANY).thenReturn(DUMMY_NONCE)) + when('plasma_cash.client.client.w3.eth').sendRawTransaction(ANY).thenReturn(None) + + client._sign_and_send_tx(DUMMY_TX) + + verify('plasma_cash.client.client.w3.eth').sendRawTransaction(ANY) + def test_deposit(self, client, root_chain): - MOCK_TRANSACT = mock() DUMMY_AMOUNT = 1 DUMMY_CURRENCY = '0x0000000000000000000000000000000000000000' - when(root_chain.functions).deposit(DUMMY_CURRENCY, DUMMY_AMOUNT).thenReturn(MOCK_TRANSACT) + MOCK_FUNCTION = mock() + TX = mock() + when(root_chain.functions).deposit(DUMMY_CURRENCY, DUMMY_AMOUNT).thenReturn(MOCK_FUNCTION) + when(MOCK_FUNCTION).buildTransaction({ + 'from': client.address, + 'value': DUMMY_AMOUNT * 10**18 + }).thenReturn(TX) + when(client)._sign_and_send_tx(ANY).thenReturn(None) client.deposit(DUMMY_AMOUNT, DUMMY_CURRENCY) - verify(MOCK_TRANSACT).transact({'from': client.address, 'value': DUMMY_AMOUNT * 10**18}) + verify(client)._sign_and_send_tx(ANY) def test_submit_block(self, client, child_chain): MOCK_HASH = 'mock hash' @@ -59,6 +80,7 @@ def test_submit_block(self, client, child_chain): when(client).get_current_block().thenReturn(MOCK_BLOCK) when('plasma_cash.client.client').sign(MOCK_HASH, client.key).thenReturn(MOCK_SIG) + when(client)._sign_and_send_tx(ANY).thenReturn(None) client.submit_block() @@ -118,7 +140,6 @@ def test_get_proof(self, child_chain, client): assert client.get_proof(DUMMY_BLOCK_NUM, DUMMY_UID) == DUMMY_PROOF def test_start_exit(self, client, root_chain): - MOCK_TRANSACT = mock() MOCK_PREVIOUS_BLOCK = mock() MOCK_BLOCK = mock() @@ -139,7 +160,7 @@ def test_start_exit(self, client, root_chain): DUMMY_ENCODED_TX, DUMMY_TX_PROOF, DUMMY_TX_BLK_NUM - ).thenReturn(MOCK_TRANSACT) + ).thenReturn(mock()) when(client).get_block(DUMMY_PREVIOUS_TX_BLK_NUM).thenReturn(MOCK_PREVIOUS_BLOCK) when(client).get_block(DUMMY_TX_BLK_NUM).thenReturn(MOCK_BLOCK) when(MOCK_PREVIOUS_BLOCK).get_tx_by_uid(DUMMY_UID).thenReturn(DUMMY_PREVIOUS_TX) @@ -159,14 +180,14 @@ def test_start_exit(self, client, root_chain): (when('plasma_cash.client.client.rlp') .encode(DUMMY_TX) .thenReturn(DUMMY_ENCODED_TX)) + when(client)._sign_and_send_tx(ANY).thenReturn(None) client.start_exit(DUMMY_UID, DUMMY_PREVIOUS_TX_BLK_NUM, DUMMY_TX_BLK_NUM) - verify(MOCK_TRANSACT).transact({'from': client.address}) + verify(client)._sign_and_send_tx(ANY) def test_challenge_exit(self, client, root_chain): MOCK_BLOCK = mock() - MOCK_TRANSACT = mock() DUMMY_UID = 'dummy uid' DUMMY_TX = 'dummy tx' @@ -189,15 +210,15 @@ def test_challenge_exit(self, client, root_chain): DUMMY_ENCODED_TX, DUMMY_TX_PROOF, DUMMY_TX_BLK_NUM - ).thenReturn(MOCK_TRANSACT) + ).thenReturn(mock()) + when(client)._sign_and_send_tx(ANY).thenReturn(None) client.challenge_exit(DUMMY_UID, DUMMY_TX_BLK_NUM) - verify(MOCK_TRANSACT).transact({'from': client.address}) + verify(client)._sign_and_send_tx(ANY) def test_respond_challenge_exit(self, client, root_chain): MOCK_BLOCK = mock() - MOCK_TRANSACT = mock() DUMMY_UID = 'dummy uid' DUMMY_CHALLENGE_TX = 'dummy challenge tx' @@ -222,18 +243,19 @@ def test_respond_challenge_exit(self, client, root_chain): DUMMY_ENCODED_TX, DUMMY_TX_PROOF, DUMMY_TX_BLK_NUM - ).thenReturn(MOCK_TRANSACT) + ).thenReturn(mock()) + when(client)._sign_and_send_tx(ANY).thenReturn(None) client.respond_challenge_exit(DUMMY_CHALLENGE_TX, DUMMY_UID, DUMMY_TX_BLK_NUM) - verify(MOCK_TRANSACT).transact({'from': client.address}) + verify(client)._sign_and_send_tx(ANY) def test_finalize_exit(self, client, root_chain): DUMMY_UID = 'dummy uid' - MOCK_TRANSACT = mock() - when(root_chain.functions).finalizeExit(DUMMY_UID).thenReturn(MOCK_TRANSACT) + when(root_chain.functions).finalizeExit(DUMMY_UID).thenReturn(mock()) + when(client)._sign_and_send_tx(ANY).thenReturn(None) client.finalize_exit(DUMMY_UID) - verify(MOCK_TRANSACT).transact({'from': client.address}) + verify(client)._sign_and_send_tx(ANY) diff --git a/unit_tests/operator_cron_job/test_main.py b/unit_tests/operator_cron_job/test_main.py index 9d1e1f2..10c5100 100644 --- a/unit_tests/operator_cron_job/test_main.py +++ b/unit_tests/operator_cron_job/test_main.py @@ -26,7 +26,7 @@ def child_chain(self): def test_setup_job_handler(self, root_chain, child_chain): (when('plasma_cash.operator_cron_job.__main__.container') - .get_child_chain().thenReturn(child_chain)) + .get_child_chain_client().thenReturn(child_chain)) (when('plasma_cash.operator_cron_job.__main__.container') .get_root_chain().thenReturn(root_chain))