Skip to content
This repository has been archived by the owner on Jun 13, 2019. It is now read-only.

Commit

Permalink
Fix deposit flaw
Browse files Browse the repository at this point in the history
  • Loading branch information
bun919tw committed Oct 10, 2018
1 parent 650d1a1 commit 16dbc32
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 27 deletions.
3 changes: 0 additions & 3 deletions integration_tests/features/steps/basic_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ def userA_deposit_some_eth_to_plasma(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@then('userA has around {amount:d} eth in root chain')
def userA_has_around_some_amount_of_eth_in_root_chain(context, amount):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ def userA_deposits_some_amount_of_eth_in_plasma_cash(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@given('userA transfers {amount:d} eth to userB')
def userA_transfers_some_eth_to_userB(context, amount):
Expand All @@ -52,7 +49,7 @@ def userA_tries_to_double_spend_some_eth_to_userC(context, amount):
invalid_tx_merkle = SparseMerkleTree(257, {uid: invalid_tx.merkle_hash})

root_chain = container.get_root_chain()
root_chain.functions.submitBlock(invalid_tx_merkle.root, TRANSFER_TX_2_BLOCK).transact({
root_chain.functions.submitBlock(invalid_tx_merkle.root, TRANSFER_TX_2_BLOCK, False, b'', b'').transact({
'from': operator
})

Expand Down
3 changes: 0 additions & 3 deletions integration_tests/features/steps/challenge_history_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ def userA_deposits_some_amount_of_eth_in_plasma_cash(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@given('userA transfers {amount:d} eth to userB')
def userA_transfers_some_eth_to_userB(context, amount):
Expand Down
3 changes: 0 additions & 3 deletions integration_tests/features/steps/challenge_spent_coin_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ def userA_deposits_some_amount_of_eth_in_plasma_cash(context, amount):
client.deposit(amount=amount, currency=eth_currency)
time.sleep(5)

operator = Client(container.get_root_chain(), container.get_child_chain_client(), operator_key)
operator.submit_block()


@given('userA transfers {amount:d} eth to userB')
def userA_transfers_some_eth_to_userB(context, amount):
Expand Down
26 changes: 18 additions & 8 deletions plasma_cash/child_chain/child_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ethereum import utils
from web3.auto import w3

from plasma_cash.utils.utils import get_sender
from plasma_cash.utils.utils import get_sender, sign

from .block import Block
from .event import emit
Expand Down Expand Up @@ -53,26 +53,36 @@ def apply_deposit(self, depositor, amount, uid):
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)
sig = sign(self.current_block.hash, self.key)
self.submit_block(sig.hex(), True, uid)
return deposit_tx.hash

err_msg = 'deposit of uid: {} is already applied previously'.format(uid)
raise DepositAlreadyAppliedException(err_msg)

def submit_block(self, sig):
def submit_block(self, sig, isDepositBlock=False, uid=None):
signature = bytes.fromhex(sig)
if (signature == b'\x00' * 65 or
get_sender(self.current_block.hash, signature) != self.authority):
raise InvalidBlockSignatureException('failed to submit a block')

merkle_hash = self.current_block.merklize_transaction_set()
deposit_tx, deposit_tx_proof = b'', b''
if uid != None:
deposit_tx = self.current_block.get_tx_by_uid(uid)
deposit_tx_proof = self.current_block.merkle.create_merkle_proof(uid)

authority_address = w3.toChecksumAddress('0x' + self.authority.hex())
tx = (self.root_chain.functions
.submitBlock(merkle_hash, self.current_block_number)
.buildTransaction({
'from': authority_address,
'nonce': w3.eth.getTransactionCount(authority_address, 'pending')
}))
tx = self.root_chain.functions.submitBlock(
merkle_hash,
self.current_block_number,
isDepositBlock,
rlp.encode(deposit_tx),
deposit_tx_proof
).buildTransaction({
'from': authority_address,
'nonce': w3.eth.getTransactionCount(authority_address, 'pending')
})

signed = w3.eth.account.signTransaction(tx, self.key)
w3.eth.sendRawTransaction(signed.rawTransaction)
Expand Down
18 changes: 18 additions & 0 deletions plasma_cash/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ def start_exit(self, uid, prev_tx_blk_num, tx_blk_num):
).buildTransaction({'from': self.address})
self._sign_and_send_tx(tx)

def abort_deposit(self, uid):
tx = self.root_chain.functions.abortDeposit(uid).buildTransaction({'from': self.address})
self._sign_and_send_tx(tx)

def start_deposit_exit(self, uid, tx_blk_num):
block = self.get_block(tx_blk_num)

tx = block.get_tx_by_uid(uid)
block.merklize_transaction_set()
tx_proof = block.merkle.create_merkle_proof(uid)

tx = self.root_chain.functions.startDepositExit(
rlp.encode(tx),
tx_proof,
tx_blk_num
).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)

Expand Down
76 changes: 72 additions & 4 deletions plasma_cash/root_chain/contracts/RootChain/RootChain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract RootChain {
uint public depositCount;
uint public currentBlkNum;
mapping(uint => bytes32) public childChain;
mapping(uint => uint) public wallet;
mapping(uint => funds) public wallet;
mapping(uint => exit) public exits;
mapping(uint => Challenge.challenge[]) public challenges;

Expand All @@ -36,6 +36,12 @@ contract RootChain {
address owner;
}

struct funds {
bool isConfirmed;
uint amount;
address depositor;
}

/*
* Modifiers
*/
Expand All @@ -56,10 +62,27 @@ contract RootChain {
// @dev Allows Plasma chain operator to submit block root
// @param blkRoot The root of a child chain block
// @param blknum The child chain block number
function submitBlock(bytes32 blkRoot, uint blknum)
function submitBlock(
bytes32 blkRoot,
uint blknum,
bool isDepositBlock,
bytes depositTx,
bytes depositTxProof
)
public
isAuthority
{
if (isDepositBlock) {
Transaction.Tx memory txObj = depositTx.createTx();
bytes32 merkleHash = keccak256(depositTx);
require(merkleHash.checkMembership(txObj.uid, blkRoot, depositTxProof));
wallet[txObj.uid].isConfirmed = true;

// If depositor tries to abort the deposit, cancel it
if (exits[txObj.uid].hasValue) {
delete exits[txObj.uid].hasValue;
}
}
require(currentBlkNum + 1 == blknum);
childChain[blknum] = blkRoot;
currentBlkNum += 1;
Expand All @@ -78,12 +101,57 @@ contract RootChain {
require(amount * 10**18 == msg.value);
}
uint uid = uint256(keccak256(currency, msg.sender, depositCount));
wallet[uid] = amount;
wallet[uid] = funds({isConfirmed: false, amount: amount, depositor: msg.sender});
depositCount += 1;
emit Deposit(msg.sender, amount, uid);
return uid;
}

// @dev Abort an deposit which is not included in child chain
// @param uid The id to specify the deposit
function abortDeposit(uint uid) public {
require(!wallet[uid].isConfirmed);
require(wallet[uid].depositor == msg.sender);

// Deposit abort should also wait for a while.
require(!exits[uid].hasValue);
exits[uid] = exit({
hasValue: true,
exitTime: block.timestamp + 1 days,
exitTxBlkNum: 0,
exitTx: "",
txBeforeExitTxBlkNum: 0,
txBeforeExitTx: "",
owner: msg.sender
});
}

// @dev Starts to exit a deposit transaction
// @param tx The transaction in bytes that user wants to exit
// @param txProof The merkle proof of the tx
// @param txBlkNum The block number of the tx
function startDepositExit(bytes tx, bytes txProof, uint txBlkNum) public {
Transaction.Tx memory txObj = tx.createTx();
require(txObj.prevBlock == 0);
require(msg.sender == txObj.newOwner);

bytes32 merkleHash = keccak256(tx);
bytes32 root = childChain[txBlkNum];
require(merkleHash.checkMembership(txObj.uid, root, txProof));

// Record the exit tx.
require(!exits[txObj.uid].hasValue);
exits[txObj.uid] = exit({
hasValue: true,
exitTime: block.timestamp + 2 weeks,
exitTxBlkNum: txBlkNum,
exitTx: tx,
txBeforeExitTxBlkNum: 0,
txBeforeExitTx: "",
owner: msg.sender
});
}

// @dev Starts to exit a transaction
// @param prevTx The previous transaction in bytes of the transaction that user wants to exit
// @param prevTxProof The merkle proof of the prevTx
Expand Down Expand Up @@ -211,7 +279,7 @@ contract RootChain {
require(!challenges[uid][i].hasValue);
}

exits[uid].owner.transfer(wallet[uid]*10**18);
exits[uid].owner.transfer(wallet[uid].amount*10**18);
delete exits[uid].hasValue;
}

Expand Down
14 changes: 13 additions & 1 deletion unit_tests/child_chain/test_child_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ def test_apply_deposit(self, child_chain):
DUMMY_AMOUNT = 123
DUMMY_UID = 0
DUMMY_ADDR = b'\xfd\x02\xec\xeeby~u\xd8k\xcf\xf1d.\xb0\x84J\xfb(\xc7'
DUMMY_SIG = ('e79be9e20e121a8447b845c1b95b30b9bc4ed33db1de8e0f2c4401f56660506b7' +
'f67dde4068f0a3a3763ef15d0c86988db8bbdaddfa9f42a36a9721349433e051b')
DUMMY_IS_DEPOSIT_BLOCK = True

(when(child_chain)
.submit_block(DUMMY_SIG, DUMMY_IS_DEPOSIT_BLOCK, DUMMY_UID)
.thenReturn(None))
tx_hash = child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID)

tx = child_chain.current_block.transaction_set[0]
Expand All @@ -73,7 +79,13 @@ def test_apply_deposit_should_fail_when_is_already_applied(self, child_chain, ro
DUMMY_AMOUNT = 123
DUMMY_UID = 0
DUMMY_ADDR = b'\xfd\x02\xec\xeeby~u\xd8k\xcf\xf1d.\xb0\x84J\xfb(\xc7'
DUMMY_SIG = ('e79be9e20e121a8447b845c1b95b30b9bc4ed33db1de8e0f2c4401f56660506b7' +
'f67dde4068f0a3a3763ef15d0c86988db8bbdaddfa9f42a36a9721349433e051b')
DUMMY_IS_DEPOSIT_BLOCK = True

(when(child_chain)
.submit_block(DUMMY_SIG, DUMMY_IS_DEPOSIT_BLOCK, DUMMY_UID)
.thenReturn(None))
child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID)
with pytest.raises(DepositAlreadyAppliedException):
child_chain.apply_deposit(DUMMY_ADDR, DUMMY_AMOUNT, DUMMY_UID)
Expand All @@ -88,7 +100,7 @@ def test_submit_block(self, child_chain, root_chain):
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)
.submitBlock(DUMMY_MERKLE, block_number, ANY, ANY, ANY)
.thenReturn(MOCK_FUNCTION))
when(MOCK_FUNCTION).buildTransaction(ANY).thenReturn(DUMMY_TX)
(when('plasma_cash.child_chain.child_chain')
Expand Down
38 changes: 38 additions & 0 deletions unit_tests/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,44 @@ def test_start_exit(self, client, root_chain):

verify(client)._sign_and_send_tx(ANY)

def test_abort_deposit(self, client, root_chain):
DUMMY_UID = 'dummy uid'
when(root_chain.functions).abortDeposit(DUMMY_UID).thenReturn(mock())
when(client)._sign_and_send_tx(ANY).thenReturn(None)

client.abort_deposit(DUMMY_UID)
verify(client)._sign_and_send_tx(ANY)

def test_start_deposit_exit(self, client, root_chain):
MOCK_BLOCK = mock()

DUMMY_TX = 'dummy tx'
DUMMY_ENCODED_TX = 'dummy encoded tx'
DUMMY_TX_PROOF = 'dummy tx proof'
DUMMY_TX_BLK_NUM = 'dummy tx blk num'
DUMMY_UID = 'dummy uid'

when(root_chain.functions).startDepositExit(
DUMMY_ENCODED_TX,
DUMMY_TX_PROOF,
DUMMY_TX_BLK_NUM
).thenReturn(mock())
when(client).get_block(DUMMY_TX_BLK_NUM).thenReturn(MOCK_BLOCK)
when(MOCK_BLOCK).get_tx_by_uid(DUMMY_UID).thenReturn(DUMMY_TX)

MOCK_BLOCK.merkle = mock()
(when(MOCK_BLOCK.merkle)
.create_merkle_proof(DUMMY_UID)
.thenReturn(DUMMY_TX_PROOF))
(when('plasma_cash.client.client.rlp')
.encode(DUMMY_TX)
.thenReturn(DUMMY_ENCODED_TX))
when(client)._sign_and_send_tx(ANY).thenReturn(None)

client.start_deposit_exit(DUMMY_UID, DUMMY_TX_BLK_NUM)

verify(client)._sign_and_send_tx(ANY)

def test_challenge_exit(self, client, root_chain):
MOCK_BLOCK = mock()

Expand Down
2 changes: 1 addition & 1 deletion unit_tests/root_chain/test_root_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ def test_deposit(self, tester_chain, contract):
sender=tester_chain.k1
)
assert contract.depositCount() == 1
assert contract.wallet(uid) == DUMMY_AMOUNT
assert contract.wallet(uid) == [False, DUMMY_AMOUNT, '0x' + tester_chain.a1.hex()]

0 comments on commit 16dbc32

Please sign in to comment.