예제 #1
0
 def __init__(self, plasma_framework, w3, accounts):
     self.root_chain = plasma_framework  # TODO: change root_chain -> plasma_framework
     self.w3 = w3
     self.accounts = accounts
     self.operator = self.accounts[0]
     self.child_chain = ChildChain(operator=self.operator)
     self.events_filters: dict = plasma_framework.event_filters(w3)
예제 #2
0
 def __init__(self, root_chain, ethtester):
     self.root_chain = root_chain
     self.ethtester = ethtester
     self.accounts = get_accounts(ethtester)
     self.operator = self.accounts[0]
     self.child_chain = ChildChain(self.accounts[0].address)
     self.confirmations = {}
예제 #3
0
    def __init__(self, root_chain, ethtester):
        self.root_chain = root_chain
        self.ethtester = ethtester
        self.accounts = get_accounts(ethtester)
        self.operator = self.accounts[0]
        self.child_chain = ChildChain(operator=self.operator.address)
        self.events = []

        def gather_events(event):
            all_contract_topics = self.root_chain.translator.event_data.keys()
            if self.root_chain.address is event.address and event.topics[
                    0] in all_contract_topics:
                self.events.append(
                    self.root_chain.translator.decode_event(
                        event.topics, event.data))

        self.ethtester.chain.head_state.log_listeners.append(gather_events)
예제 #4
0
 def __init__(self, root_chain, ethtester):
     self.root_chain = root_chain
     self.ethtester = ethtester
     self.accounts = ethtester.accounts
     self.operator = self.accounts[0]
     self.child_chain = ChildChain(self.accounts[0].address)
예제 #5
0
class TestingLanguage(object):
    """Represents the testing language.

    Attributes:
        root_chain (ABIContract): Root chain contract instance.
        eththester (tester): Ethereum tester instance.
        accounts (EthereumAccount[]): List of available accounts.
        operator (EthereumAccount): The operator's account.
        child_chain (ChildChain): Child chain instance.
    """
    def __init__(self, root_chain, ethtester):
        self.root_chain = root_chain
        self.ethtester = ethtester
        self.accounts = ethtester.accounts
        self.operator = self.accounts[0]
        self.child_chain = ChildChain(self.accounts[0].address)

    @property
    def timestamp(self):
        """Current chain timestamp"""
        return self.ethtester.chain.head_state.timestamp

    @property
    def current_plasma_block_number(self):
        """Current block number"""
        return self.root_chain.currentPlasmaBlockNumber()

    def commit_plasma_block_root(self, block, signer=None):
        """Commits a Plasma block root to Ethereum.

        Args:
            block (Block): Block to be committed.
            signer (EthereumAccount): Account to commit the root.
        """

        signer = signer or self.operator
        block.sign(signer.key)
        self.root_chain.commitPlasmaBlockRoot(block.root, sender=signer.key)
        self.child_chain.add_block(block)

    def deposit(self, owner, amount):
        """Creates a deposit transaction for a given owner and amount.

        Args:
            owner (EthereumAccount): Account to own the deposit.
            amount (int): Deposit amount.

        Returns:
            int: Unique identifier of the deposit.
        """

        deposit_tx = Transaction(inputs=[], outputs=[(owner.address, amount)])
        blknum = self.root_chain.currentPlasmaBlockNumber()
        self.root_chain.deposit(deposit_tx.encoded,
                                value=amount,
                                sender=owner.key)

        block = Block(transactions=[deposit_tx], number=blknum)
        self.child_chain.add_block(block)
        return blknum

    def spend_utxo(self, utxo_position, new_owner, amount, signer):
        """Creates a spending transaction and inserts it into the chain.

        Args:
            utxo_position (int): Identifier of the UTXO to spend.
            new_owner (EthereumAccount): Account to own the output of this spend.
            amount (int): Amount to spend.
            signer (EthereumAccount): Account to sign this transaction.

        Returns:
            int: Unique identifier of the spend.
        """

        spend_tx = Transaction(inputs=[decode_utxo_position(utxo_position)],
                               outputs=[(new_owner.address, amount)])
        spend_tx.sign(0, signer.key)

        blknum = self.root_chain.currentPlasmaBlockNumber()
        block = Block(transactions=[spend_tx], number=blknum)
        self.commit_plasma_block_root(block)
        return encode_utxo_position(blknum, 0, 0)

    def confirm(self, tx_position, index, signer):
        """Signs a confirmation signature for a spend.

        Args:
            tx_position (int): Identifier of the transaction.
            index (int): Index of the input to confirm.
            signer (EthereumAccount): Account to sign this confirmation.
        """

        spend_tx = self.child_chain.get_transaction(tx_position)
        spend_tx.confirm(index, signer.key)

    def start_exit(self, owner, utxo_position):
        """Starts a standard exit.

        Args:
            owner (EthereumAccount): Account to attempt the exit.
            utxo_position (int): Position of the UTXO to be exited.
        """

        bond = self.root_chain.EXIT_BOND()
        self.root_chain.startExit(utxo_position,
                                  *self.get_exit_proof(utxo_position),
                                  sender=owner.key,
                                  value=bond)

    def get_exit_proof(self, utxo_position):
        """Returns information required to exit

        Args:
            utxo_position (int): Position of the UTXO to be exited.
        
        Returns:
            bytes, bytes, bytes, bytes: Information necessary to exit the UTXO.
        """

        (blknum, _, _) = decode_utxo_position(utxo_position)
        block = self.child_chain.get_block(blknum)

        spend_tx = self.child_chain.get_transaction(utxo_position)
        encoded_tx = spend_tx.encoded
        proof = block.merkle.create_membership_proof(spend_tx.encoded)
        signatures = spend_tx.joined_signatures
        confirmation_signatures = spend_tx.joined_confirmations
        return (encoded_tx, proof, signatures, confirmation_signatures)

    def challenge_exit(self, exiting_utxo_position, spending_tx_position):
        """Challenges an exit with a double spend.

        Args:
            exiting_utxo_position (int): Position of the UTXO being exited.
            spending_tx_position (int): Position of the transaction that spent the UTXO.
        """

        proof_data = self.get_challenge_proof(exiting_utxo_position,
                                              spending_tx_position)
        self.root_chain.challengeExit(exiting_utxo_position, *proof_data)

    def get_challenge_proof(self, exiting_utxo_position, spending_tx_position):
        """Returns information required to submit a challenge.

        Args:
            exiting_utxo_position (int): Position of the UTXO being exited.
            spending_tx_position (int): Position of the transaction that spent the UTXO.

        Returns:
            bytes, bytes: Information necessary to create a challenge proof.
        """

        spend_tx = self.child_chain.get_transaction(spending_tx_position)
        (_, _, oindex) = decode_utxo_position(spending_tx_position)
        confirmation_signature = spend_tx.confirmations[oindex]
        return (spend_tx.encoded, confirmation_signature)

    def process_exits(self):
        """Processes any exits that have completed the exit period"""

        self.root_chain.processExits()

    def get_plasma_block(self, blknum):
        """Queries a plasma block by its number.

        Args:
            blknum (int): Plasma block number to query.

        Returns:
            PlasmaBlock: Formatted plasma block information.
        """

        block_info = self.root_chain.plasmaBlockRoots(blknum)
        return PlasmaBlock(*block_info)

    def get_plasma_exit(self, utxo_position):
        """Queries a plasma exit by its position in the chain.

        Args:
            utxo_position (int): Identifier of the exit to query.

        Returns:
            PlasmaExit: Formatted plasma exit information.
        """

        exit_info = self.root_chain.plasmaExits(utxo_position)
        return PlasmaExit(*exit_info)

    def get_balance(self, account):
        """Queries the balance of an account.

        Args:
            account (EthereumAccount): Account to query,

        Returns:
            int: The account's balance.
        """

        return self.ethtester.chain.head_state.get_balance(account.address)

    def forward_timestamp(self, amount):
        """Forwards the chain's timestamp.

        Args:
            amount (int): Number of seconds to move forward time.
        """

        self.ethtester.chain.head_state.timestamp += amount
예제 #6
0
class TestingLanguage:
    """Represents the testing language.

    Attributes:
        root_chain (ABIContract): Root chain contract instance.
        w3 (Web3): w3 instance.
        accounts (EthereumAccount[]): List of available accounts.
        operator (EthereumAccount): The operator's account.
        child_chain (ChildChain): Child chain instance.
    """
    def __init__(self, plasma_framework, w3, accounts):
        self.root_chain = plasma_framework  # TODO: change root_chain -> plasma_framework
        self.w3 = w3
        self.accounts = accounts
        self.operator = self.accounts[0]
        self.child_chain = ChildChain(operator=self.operator)
        self.events_filters: dict = plasma_framework.event_filters(w3)

    def flush_events(self):
        logs = [(contract, event_filter.get_new_entries())
                for contract, event_filter in self.events_filters.values()]
        events = []
        for contract, contract_logs in logs:
            contract_events = contract.get_contract_events()
            for contract_event in contract_events:
                for log in contract_logs:
                    try:
                        events.append((contract.address,
                                       contract_event().processLog(log)))
                    except MismatchedABI:
                        pass
        return events

    def submit_block(self, transactions, signer=None, force_invalid=False):
        signer = signer or self.operator
        blknum = self.root_chain.nextChildBlock()
        block = Block(transactions, number=blknum)
        signed_block = block.sign(signer.key)
        self.root_chain.submitBlock(signed_block.root,
                                    **{'from': signer.address})
        if force_invalid:
            self.child_chain.blocks[
                self.child_chain.next_child_block] = signed_block
            self.child_chain.next_deposit_block = self.child_chain.next_child_block + 1
            self.child_chain.next_child_block += self.child_chain.child_block_interval
        else:
            assert self.child_chain.add_block(signed_block)
        return blknum

    @property
    def timestamp(self):
        """Current chain timestamp"""
        return self.w3.eth.getBlock('latest').timestamp

    def deposit(self, owner, amount):
        deposit_tx = Transaction(outputs=[(owner.address, NULL_ADDRESS,
                                           amount)])
        blknum = self.root_chain.getDepositBlockNumber()
        self.root_chain.deposit(deposit_tx.encoded, **{
            'from': owner.address,
            'value': amount
        })
        deposit_id = encode_utxo_id(blknum, 0, 0)
        block = Block([deposit_tx], number=blknum)
        self.child_chain.add_block(block)
        return deposit_id

    def deposit_token(self, owner, token, amount):
        """Mints, approves and deposits token for given owner and amount

        Args:
            owner (EthereumAccount): Account to own the deposit.
            token (Contract: ERC20, MintableToken): Token to be deposited.
            amount (int): Deposit amount.

        Returns:
            int: Unique identifier of the deposit.
        """

        deposit_tx = Transaction(outputs=[(owner.address, token.address,
                                           amount)])
        token.mint(owner.address, amount)
        token.approve(self.root_chain.erc20_vault.address, amount,
                      **{'from': owner.address})
        blknum = self.root_chain.getDepositBlockNumber()
        pre_balance = self.get_balance(self.root_chain.erc20_vault, token)
        self.root_chain.depositFrom(deposit_tx.encoded,
                                    **{'from': owner.address})
        balance = self.get_balance(self.root_chain.erc20_vault, token)
        assert balance == pre_balance + amount
        block = Block(transactions=[deposit_tx], number=blknum)
        self.child_chain.add_block(block)
        return encode_utxo_id(blknum, 0, 0)

    def spend_utxo(self,
                   input_ids,
                   accounts,
                   outputs=None,
                   metadata=None,
                   force_invalid=False):
        if outputs is None:
            outputs = []
        inputs = [decode_utxo_id(input_id) for input_id in input_ids]
        spend_tx = Transaction(inputs=inputs,
                               outputs=outputs,
                               metadata=metadata)
        for i in range(0, len(inputs)):
            spend_tx.sign(i,
                          accounts[i],
                          verifying_contract=self.root_chain.plasma_framework)
        blknum = self.submit_block([spend_tx], force_invalid=force_invalid)
        spend_id = encode_utxo_id(blknum, 0, 0)
        return spend_id

    def start_standard_exit(self, output_id, account, bond=None):
        blknum, tx_index, _ = decode_utxo_id(output_id)
        block = self.child_chain.get_block(blknum)
        output_tx = block.transactions[tx_index]
        return self.start_standard_exit_with_tx_body(output_id, output_tx,
                                                     account, bond, block)

    def start_standard_exit_with_tx_body(self,
                                         output_id,
                                         output_tx,
                                         account,
                                         bond=None,
                                         block=None):
        transactions = [output_tx]
        if block:
            transactions = block.transactions
        merkle = FixedMerkle(16, list(map(lambda tx: tx.encoded,
                                          transactions)))
        proof = merkle.create_membership_proof(output_tx.encoded)
        bond = bond if bond is not None else self.root_chain.standardExitBond()
        self.root_chain.startStandardExit(
            output_id, output_tx.encoded, proof, **{
                'value': bond,
                'from': account.address
            })

    def challenge_standard_exit(self,
                                output_id,
                                spend_id,
                                input_index=None,
                                signature=None):
        spend_tx = self.child_chain.get_transaction(spend_id)
        exiting_tx = self.child_chain.get_transaction(output_id)

        if input_index is None:
            for i in range(len(spend_tx.inputs)):
                if spend_tx.inputs[i].identifier == output_id:
                    input_index = i
                    break
        if signature is None:
            signature = spend_tx.signatures[input_index]

        exit_id = self.get_standard_exit_id(output_id)
        self.root_chain.challengeStandardExit(
            exit_id, spend_tx.encoded, input_index, signature,
            exiting_tx.encoded, keccak(hexstr=self.accounts[0].address))

    def start_in_flight_exit(self,
                             tx_id,
                             bond=None,
                             sender=None,
                             spend_tx=None):
        if sender is None:
            sender = self.accounts[0]
        (encoded_spend, encoded_inputs, inputs_pos, proofs,
         signatures) = self.get_in_flight_exit_info(tx_id, spend_tx)
        bond = bond if bond is not None else self.root_chain.inFlightExitBond()
        self.root_chain.startInFlightExit(
            encoded_spend, encoded_inputs, proofs, signatures, inputs_pos, **{
                'value': bond,
                'from': sender.address
            })

    def create_utxo(self, token=NULL_ADDRESS):
        class Utxo:
            def __init__(self, deposit_id, owner, token, amount, spend,
                         spend_id):
                self.deposit_id = deposit_id
                self.owner = owner
                self.amount = amount
                self.token = token
                self.spend_id = spend_id
                self.spend = spend

        owner, amount = self.accounts[0], 100
        if token == NULL_ADDRESS:
            deposit_id = self.deposit(owner, amount)
            token_address = NULL_ADDRESS
        else:
            deposit_id = self.deposit_token(owner, token, amount)
            token_address = token.address
        spend_id = self.spend_utxo([deposit_id], [owner],
                                   [(owner.address, token_address, 100)])
        spend = self.child_chain.get_transaction(spend_id)
        return Utxo(deposit_id, owner, token_address, amount, spend, spend_id)

    def process_exits(self, token, exit_id, count=1, vault_id=None, **kwargs):
        """Finalizes exits that have completed the exit period.

        Args:
            token (address): Address of the token to be processed.
            exit_id (int): Identifier of an exit (optional, pass 0 to ignore the check)
            count (int): Maximum number of exits to be processed.
            vault_id (int): Id of the vault that funds the exit
        """

        return self.root_chain.processExits(token, exit_id, count, vault_id,
                                            **kwargs)

    def get_challenge_proof(self, utxo_id, spend_id):
        """Returns information required to submit a challenge.

        Args:
            utxo_id (int): Identifier of the UTXO being exited.
            spend_id (int): Identifier of the transaction that spent the UTXO.

        Returns:
            int, bytes, bytes, bytes: Information necessary to create a challenge proof.
        """

        spend_tx = self.child_chain.get_transaction(spend_id)
        inputs = [(spend_tx.blknum1, spend_tx.txindex1, spend_tx.oindex1),
                  (spend_tx.blknum2, spend_tx.txindex2, spend_tx.oindex2)]
        try:
            input_index = inputs.index(decode_utxo_id(utxo_id))
        except ValueError:
            input_index = 0
        (blknum, _, _) = decode_utxo_id(spend_id)
        block = self.child_chain.blocks[blknum]
        proof = block.merklized_transaction_set.create_membership_proof(
            spend_tx.merkle_hash)
        sigs = spend_tx.sig1 + spend_tx.sig2
        return input_index, spend_tx.encoded, proof, sigs

    def get_plasma_block(self, blknum):
        """Queries a plasma block by its number.

        Args:
            blknum (int): Plasma block number to query.

        Returns:
            PlasmaBlock: Formatted plasma block information.
        """

        block_info = self.root_chain.blocks(blknum)
        return PlasmaBlock(*block_info)

    def get_standard_exit(self, utxo_pos):
        """Queries a plasma exit by its ID.

        Args:
            utxo_pos (int): position of utxo being exited

        Returns:
            tuple: (owner (address), amount (int))
        """

        exit_id = self.get_standard_exit_id(utxo_pos)
        exit_info = self.root_chain.exits([exit_id])
        return StandardExit(*exit_info[0])

    def get_standard_exit_id(self, utxo_pos):
        tx = self.child_chain.get_transaction(utxo_pos)
        return self.root_chain.getStandardExitId(tx.encoded, utxo_pos)

    def get_balance(self, account, token=NULL_ADDRESS):
        """Queries ETH or token balance of an account.

        Args:
            account (EthereumAccount): Account to query,
            token (str OR ABIContract OR NULL_ADDRESS):
                MintableToken contract: its address or ABIContract representation.

        Returns:
            int: The account's balance.
        """
        if token == NULL_ADDRESS:
            return self.w3.eth.getBalance(account.address)
        if hasattr(token, "balanceOf"):
            return token.balanceOf(account.address)

    def forward_timestamp(self, amount):
        """Forwards the chain's timestamp.

        Args:
            amount (int): Number of seconds to move forward time.
        """
        eth_module = self.w3.eth
        eth_module.increase_time(amount)

    def get_in_flight_exit_info(self, tx_id, spend_tx=None):
        if spend_tx is None:
            spend_tx = self.child_chain.get_transaction(tx_id)
        input_txs = []
        inputs_pos = []
        proofs = []
        signatures = []
        for i in range(0, len(spend_tx.inputs)):
            tx_input = spend_tx.inputs[i]
            inputs_pos.append(tx_input.identifier)
            (blknum, _, _) = decode_utxo_id(tx_input.identifier)
            if blknum == 0:
                continue
            input_tx = self.child_chain.get_transaction(tx_input.identifier)
            input_txs.append(input_tx)
            proofs.append(self.get_merkle_proof(tx_input.identifier))
            signatures.append(spend_tx.signatures[i])
        encoded_inputs = [i.encoded for i in input_txs]
        return spend_tx.encoded, encoded_inputs, inputs_pos, proofs, signatures

    def get_in_flight_exit_id(self, tx_id):
        spend_tx = self.child_chain.get_transaction(tx_id)
        return self.root_chain.getInFlightExitId(spend_tx.encoded)

    def get_merkle_proof(self, tx_id):
        tx = self.child_chain.get_transaction(tx_id)
        (blknum, _, _) = decode_utxo_id(tx_id)
        block = self.child_chain.get_block(blknum)
        merkle = block.merklized_transaction_set
        return merkle.create_membership_proof(tx.encoded)

    def piggyback_in_flight_exit_input(self,
                                       tx_id,
                                       input_index,
                                       account,
                                       bond=None,
                                       spend_tx=None):
        if spend_tx is None:
            spend_tx = self.child_chain.get_transaction(tx_id)
        bond = bond if bond is not None else self.root_chain.piggybackBond()
        self.root_chain.piggybackInFlightExit(
            spend_tx.encoded, input_index, **{
                'value': bond,
                'from': account.address
            })

    def piggyback_in_flight_exit_output(self,
                                        tx_id,
                                        output_index,
                                        account,
                                        bond=None,
                                        spend_tx=None):
        assert output_index in range(4)
        return self.piggyback_in_flight_exit_input(tx_id, output_index + 4,
                                                   account, bond, spend_tx)

    @staticmethod
    def find_shared_input(tx_a, tx_b):
        tx_a_input_index = 0
        tx_b_input_index = 0
        for i in range(len(tx_a.inputs)):
            for j in range(len(tx_b.inputs)):
                tx_a_input = tx_a.inputs[i].identifier
                tx_b_input = tx_b.inputs[j].identifier
                if tx_a_input == tx_b_input and tx_a_input != 0:
                    tx_a_input_index = i
                    tx_b_input_index = j
        return tx_a_input_index, tx_b_input_index

    @staticmethod
    def find_input_index(output_id, tx_b):
        tx_b_input_index = 0
        for i in range(len(tx_b.inputs)):
            tx_b_input = tx_b.inputs[i].identifier
            if tx_b_input == output_id:
                tx_b_input_index = i
        return tx_b_input_index

    def challenge_in_flight_exit_not_canonical(self,
                                               in_flight_tx_id,
                                               competing_tx_id,
                                               account,
                                               in_flight_tx=None):
        if in_flight_tx is None:
            in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        competing_tx = self.child_chain.get_transaction(competing_tx_id)
        (in_flight_tx_input_index,
         competing_tx_input_index) = self.find_shared_input(
             in_flight_tx, competing_tx)
        proof = self.get_merkle_proof(competing_tx_id)
        signature = competing_tx.signatures[competing_tx_input_index]

        shared_input_identifier = in_flight_tx.inputs[
            in_flight_tx_input_index].identifier
        shared_input = self.child_chain.get_transaction(
            shared_input_identifier)

        self.root_chain.challengeInFlightExitNotCanonical(
            in_flight_tx.encoded, in_flight_tx_input_index,
            competing_tx.encoded, competing_tx_input_index, competing_tx_id,
            proof, signature, shared_input.encoded, shared_input_identifier,
            **{'from': account.address})

    def respond_to_non_canonical_challenge(self, in_flight_tx_id, key):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        proof = self.get_merkle_proof(in_flight_tx_id)
        self.root_chain.respondToNonCanonicalChallenge(in_flight_tx.encoded,
                                                       in_flight_tx_id, proof)

    def forward_to_period(self, period):
        forward_time = (period - 1) * IN_FLIGHT_PERIOD
        if forward_time:
            self.forward_timestamp(forward_time)

    def challenge_in_flight_exit_input_spent(self, in_flight_tx_id,
                                             spend_tx_id, key):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        spend_tx = self.child_chain.get_transaction(spend_tx_id)
        (in_flight_tx_input_index,
         spend_tx_input_index) = self.find_shared_input(
             in_flight_tx, spend_tx)
        signature = spend_tx.signatures[spend_tx_input_index]

        shared_input_identifier = in_flight_tx.inputs[
            in_flight_tx_input_index].identifier
        shared_input_tx = self.child_chain.get_transaction(
            shared_input_identifier)

        self.root_chain.challengeInFlightExitInputSpent(
            in_flight_tx.encoded, in_flight_tx_input_index, spend_tx.encoded,
            spend_tx_input_index, signature,
            shared_input_tx.encoded, shared_input_identifier,
            keccak(hexstr=key.address), **{'from': key.address})

    def challenge_in_flight_exit_output_spent(self, in_flight_tx_id,
                                              spending_tx_id, output_index,
                                              key):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        spending_tx = self.child_chain.get_transaction(spending_tx_id)
        in_flight_tx_output_id = in_flight_tx_id + output_index
        spending_tx_input_index = self.find_input_index(
            in_flight_tx_output_id, spending_tx)
        in_flight_tx_inclusion_proof = self.get_merkle_proof(in_flight_tx_id)
        spending_tx_sig = spending_tx.signatures[spending_tx_input_index]
        self.root_chain.challengeInFlightExitOutputSpent(
            in_flight_tx.encoded, in_flight_tx_output_id,
            in_flight_tx_inclusion_proof,
            spending_tx.encoded, spending_tx_input_index, spending_tx_sig,
            keccak(hexstr=key.address), **{'from': key.address})

    def get_in_flight_exit(self, in_flight_tx_id):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        exit_id = self.root_chain.getInFlightExitId(in_flight_tx.encoded)
        exit_info = self.root_chain.inFlightExits([exit_id])
        return InFlightExit(self.root_chain, in_flight_tx, *exit_info[0])

    def delete_in_flight_exit(self, in_flight_tx_id):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        exit_id = self.root_chain.getInFlightExitId(in_flight_tx.encoded)
        self.root_chain.deleteNonPiggybackedInFlightExit(exit_id)
예제 #7
0
class TestingLanguage(object):
    """Represents the testing language.

    Attributes:
        root_chain (ABIContract): Root chain contract instance.
        eththester (tester): Ethereum tester instance.
        accounts (EthereumAccount[]): List of available accounts.
        operator (EthereumAccount): The operator's account.
        child_chain (ChildChain): Child chain instance.
        confirmations (dict): A mapping from transaction IDs to confirmation signatures.
    """

    def __init__(self, root_chain, ethtester):
        self.root_chain = root_chain
        self.ethtester = ethtester
        self.accounts = get_accounts(ethtester)
        self.operator = self.accounts[0]
        self.child_chain = ChildChain(self.accounts[0].address)
        self.confirmations = {}

    @property
    def timestamp(self):
        """Current chain timestamp"""
        return self.ethtester.chain.head_state.timestamp

    def deposit(self, owner, amount):
        """Creates a deposit transaction for a given owner and amount.

        Args:
            owner (EthereumAccount): Account to own the deposit.
            amount (int): Deposit amount.

        Returns:
            int: Unique identifier of the deposit.
        """

        deposit_tx = Transaction(0, 0, 0,
                                 0, 0, 0,
                                 NULL_ADDRESS,
                                 owner.address, amount,
                                 NULL_ADDRESS, 0)

        blknum = self.root_chain.getDepositBlock()
        self.root_chain.deposit(value=amount, sender=owner.key)

        block = Block(transaction_set=[deposit_tx], number=blknum)
        self.child_chain.add_block(block)
        return blknum

    def spend_utxo(self, utxo_id, new_owner, amount, signer):
        """Creates a spending transaction and inserts it into the chain.

        Args:
            utxo_id (int): Identifier of the UTXO to spend.
            new_owner (EthereumAccount): Account to own the output of this spend.
            amount (int): Amount to spend.
            signer (EthereumAccount): Account to sign this transaction.

        Returns:
            int: Unique identifier of the spend.
        """

        spend_tx = Transaction(*decode_utxo_id(utxo_id),
                               0, 0, 0,
                               NULL_ADDRESS,
                               new_owner.address, amount,
                               NULL_ADDRESS, 0)
        spend_tx.sign1(signer.key)

        blknum = self.root_chain.currentChildBlock()
        block = Block(transaction_set=[spend_tx], number=blknum)
        block.sign(self.operator.key)
        self.root_chain.submitBlock(block.root)
        self.child_chain.add_block(block)
        return encode_utxo_id(blknum, 0, 0)

    def confirm_spend(self, tx_id, signer):
        """Signs a confirmation signature for a spend.

        Args:
            tx_id (int): Identifier of the transaction.
            signer (EthereumAccount): Account to sign this confirmation.
        """

        spend_tx = self.child_chain.get_transaction(tx_id)
        (blknum, _, _) = decode_utxo_id(tx_id)
        block = self.child_chain.blocks[blknum]
        confirmation_hash = sha3(spend_tx.hash + block.root)
        self.confirmations[tx_id] = sign(confirmation_hash, signer.key)

    def start_deposit_exit(self, owner, blknum, amount):
        """Starts an exit for a deposit.

        Args:
            owner (EthereumAccount): Account that owns this deposit.
            blknum (int): Deposit block number.
            amount (int): Deposit amount.
        """

        deposit_id = encode_utxo_id(blknum, 0, 0)
        self.root_chain.startDepositExit(deposit_id, NULL_ADDRESS, amount, sender=owner.key)

    def start_fee_exit(self, operator, amount):
        """Starts a fee exit.

        Args:
            operator (EthereumAccount): Account to attempt the fee exit.
            amount (int): Amount to exit.

        Returns:
            int: Unique identifier of the exit.
        """

        fee_exit_id = self.root_chain.currentFeeExit()
        self.root_chain.startFeeExit(NULL_ADDRESS, amount, sender=operator.key)
        return fee_exit_id

    def start_exit(self, owner, utxo_id):
        """Starts a standard exit.

        Args:
            owner (EthereumAccount): Account to attempt the exit.
            utxo_id (int): Unique identifier of the UTXO to be exited.
        """

        spend_tx = self.child_chain.get_transaction(utxo_id)
        (blknum, _, _) = decode_utxo_id(utxo_id)
        block = self.child_chain.blocks[blknum]
        proof = block.merkle_tree.create_membership_proof(spend_tx.merkle_hash)
        sigs = spend_tx.sig1 + spend_tx.sig2 + self.confirmations[utxo_id]
        self.root_chain.startExit(utxo_id, spend_tx.encoded, proof, sigs, sender=owner.key)

    def challenge_exit(self, utxo_id, spend_id):
        """Challenges an exit with a double spend.

        Args:
            utxo_id (int): Identifier of the UTXO being exited.
            spend_id (int): Identifier of the transaction that spent the UTXO.
        """

        self.root_chain.challengeExit(spend_id, *self.get_challenge_proof(utxo_id, spend_id))

    def finalize_exits(self):
        """Finalizes any exits that have completed the exit period"""

        self.root_chain.finalizeExits(NULL_ADDRESS)

    def get_challenge_proof(self, utxo_id, spend_id):
        """Returns information required to submit a challenge.

        Args:
            utxo_id (int): Identifier of the UTXO being exited.
            spend_id (int): Identifier of the transaction that spent the UTXO.

        Returns:
            int, bytes, bytes, bytes, bytes: Information necessary to create a challenge proof.
        """

        spend_tx = self.child_chain.get_transaction(spend_id)
        inputs = [(spend_tx.blknum1, spend_tx.txindex1, spend_tx.oindex1), (spend_tx.blknum2, spend_tx.txindex2, spend_tx.oindex2)]
        try:
            input_index = inputs.index(decode_utxo_id(utxo_id))
        except ValueError:
            input_index = 0
        (blknum, _, _) = decode_utxo_id(spend_id)
        block = self.child_chain.blocks[blknum]
        proof = block.merkle_tree.create_membership_proof(spend_tx.merkle_hash)
        sigs = spend_tx.sig1 + spend_tx.sig2
        confirmation_sig = self.confirmations[spend_id]
        return (input_index, spend_tx.encoded, proof, sigs, confirmation_sig)

    def get_plasma_block(self, blknum):
        """Queries a plasma block by its number.

        Args:
            blknum (int): Plasma block number to query.

        Returns:
            PlasmaBlock: Formatted plasma block information.
        """

        block_info = self.root_chain.childChain(blknum)
        return PlasmaBlock(*block_info)

    def get_plasma_exit(self, utxo_id):
        """Queries a plasma exit by its ID.

        Args:
            utxo_id (int): Identifier of the exit to query.

        Returns:
            PlasmaExit: Formatted plasma exit information.
        """

        exit_info = self.root_chain.exits(utxo_id)
        return PlasmaExit(*exit_info)

    def get_balance(self, account):
        """Queries the balance of an account.

        Args:
            account (EthereumAccount): Account to query,

        Returns:
            int: The account's balance.
        """

        return self.ethtester.chain.head_state.get_balance(account.address)

    def forward_timestamp(self, amount):
        """Forwards the chain's timestamp.

        Args:
            amount (int): Number of seconds to move forward time.
        """

        self.ethtester.chain.head_state.timestamp += amount
예제 #8
0
class TestingLanguage(object):
    """Represents the testing language.

    Attributes:
        root_chain (ABIContract): Root chain contract instance.
        eththester (tester): Ethereum tester instance.
        accounts (EthereumAccount[]): List of available accounts.
        operator (EthereumAccount): The operator's account.
        child_chain (ChildChain): Child chain instance.
    """
    def __init__(self, root_chain, ethtester):
        self.root_chain = root_chain
        self.ethtester = ethtester
        self.accounts = get_accounts(ethtester)
        self.operator = self.accounts[0]
        self.child_chain = ChildChain(operator=self.operator.address)
        self.events = []

        def gather_events(event):
            all_contract_topics = self.root_chain.translator.event_data.keys()
            if self.root_chain.address is event.address and event.topics[
                    0] in all_contract_topics:
                self.events.append(
                    self.root_chain.translator.decode_event(
                        event.topics, event.data))

        self.ethtester.chain.head_state.log_listeners.append(gather_events)

    def flush_events(self):
        events, self.events = self.events, []
        return events

    def submit_block(self, transactions, signer=None, force_invalid=False):
        signer = signer or self.operator
        blknum = self.root_chain.nextChildBlock()
        block = Block(transactions, number=blknum)
        block.sign(signer.key)
        self.root_chain.submitBlock(block.root, sender=signer.key)
        if force_invalid:
            self.child_chain.blocks[self.child_chain.next_child_block] = block
            self.child_chain.next_deposit_block = self.child_chain.next_child_block + 1
            self.child_chain.next_child_block += self.child_chain.child_block_interval
        else:
            assert self.child_chain.add_block(block)
        return blknum

    @property
    def timestamp(self):
        """Current chain timestamp"""
        return self.ethtester.chain.head_state.timestamp

    def deposit(self, owner, amount):
        deposit_tx = Transaction(outputs=[(owner.address, NULL_ADDRESS,
                                           amount)])
        blknum = self.root_chain.getDepositBlockNumber()
        self.root_chain.deposit(deposit_tx.encoded, value=amount)
        deposit_id = encode_utxo_id(blknum, 0, 0)
        block = Block([deposit_tx], number=blknum)
        self.child_chain.add_block(block)
        return deposit_id

    def deposit_token(self, owner, token, amount):
        """Mints, approves and deposits token for given owner and amount

        Args:
            owner (EthereumAccount): Account to own the deposit.
            token (Contract: ERC20, MintableToken): Token to be deposited.
            amount (int): Deposit amount.

        Returns:
            int: Unique identifier of the deposit.
        """

        deposit_tx = Transaction(outputs=[(owner.address, token.address,
                                           amount)])
        token.mint(owner.address, amount)
        self.ethtester.chain.mine()
        token.approve(self.root_chain.address, amount, sender=owner.key)
        self.ethtester.chain.mine()
        blknum = self.root_chain.getDepositBlockNumber()
        pre_balance = self.get_balance(self.root_chain, token)
        self.root_chain.depositFrom(deposit_tx.encoded, sender=owner.key)
        balance = self.get_balance(self.root_chain, token)
        assert balance == pre_balance + amount
        block = Block(transactions=[deposit_tx], number=blknum)
        self.child_chain.add_block(block)
        return encode_utxo_id(blknum, 0, 0)

    def spend_utxo(self,
                   input_ids,
                   keys,
                   outputs=None,
                   metadata=None,
                   force_invalid=False):
        if outputs is None:
            outputs = []
        inputs = [decode_utxo_id(input_id) for input_id in input_ids]
        spend_tx = Transaction(inputs=inputs,
                               outputs=outputs,
                               metadata=metadata)
        for i in range(0, len(inputs)):
            spend_tx.sign(i, keys[i], verifyingContract=self.root_chain)
        blknum = self.submit_block([spend_tx], force_invalid=force_invalid)
        spend_id = encode_utxo_id(blknum, 0, 0)
        return spend_id

    def start_standard_exit(self, output_id, key, bond=None):
        output_tx = self.child_chain.get_transaction(output_id)
        self.start_standard_exit_with_tx_body(output_id, output_tx, key, bond)

    def start_standard_exit_with_tx_body(self,
                                         output_id,
                                         output_tx,
                                         key,
                                         bond=None):
        merkle = FixedMerkle(16, [output_tx.encoded])
        proof = merkle.create_membership_proof(output_tx.encoded)
        bond = bond if bond is not None else self.root_chain.standardExitBond()
        self.root_chain.startStandardExit(output_id,
                                          output_tx.encoded,
                                          proof,
                                          value=bond,
                                          sender=key)

    def challenge_standard_exit(self, output_id, spend_id, input_index=None):
        spend_tx = self.child_chain.get_transaction(spend_id)
        signature = NULL_SIGNATURE
        if input_index is None:
            for i in range(0, 4):
                signature = spend_tx.signatures[i]
                if spend_tx.inputs[
                        i].identifier == output_id and signature != NULL_SIGNATURE:
                    input_index = i
                    break
        if input_index is None:
            input_index = 3
        exit_id = self.get_standard_exit_id(output_id)
        self.root_chain.challengeStandardExit(exit_id, spend_tx.encoded,
                                              input_index, signature)

    def start_in_flight_exit(self, tx_id, bond=None, sender=None):
        if sender is None:
            sender = self.accounts[0]
        (encoded_spend, encoded_inputs, proofs,
         signatures) = self.get_in_flight_exit_info(tx_id)
        bond = bond if bond is not None else self.root_chain.inFlightExitBond()
        self.root_chain.startInFlightExit(encoded_spend,
                                          encoded_inputs,
                                          proofs,
                                          signatures,
                                          value=bond,
                                          sender=sender.key)

    def create_utxo(self, token=NULL_ADDRESS):
        class Utxo(object):
            def __init__(self, deposit_id, owner, token, amount, spend,
                         spend_id):
                self.deposit_id = deposit_id
                self.owner = owner
                self.amount = amount
                self.token = token
                self.spend_id = spend_id
                self.spend = spend

        owner, amount = self.accounts[0], 100
        if token == NULL_ADDRESS:
            deposit_id = self.deposit(owner, amount)
            token_address = NULL_ADDRESS
        else:
            deposit_id = self.deposit_token(owner, token, amount)
            token_address = token.address
        spend_id = self.spend_utxo([deposit_id], [owner.key],
                                   [(owner.address, token_address, 100)])
        spend = self.child_chain.get_transaction(spend_id)
        return Utxo(deposit_id, owner, token_address, amount, spend, spend_id)

    def start_fee_exit(self, operator, amount, token=NULL_ADDRESS, bond=None):
        """Starts a fee exit.

        Args:
            operator (EthereumAccount): Account to attempt the fee exit.
            amount (int): Amount to exit.

        Returns:
            int: Unique identifier of the exit.
        """

        fee_exit_id = self.root_chain.getFeeExitId(
            self.root_chain.nextFeeExit())
        bond = bond if bond is not None else self.root_chain.standardExitBond()
        self.root_chain.startFeeExit(token,
                                     amount,
                                     value=bond,
                                     sender=operator.key)
        return fee_exit_id

    def process_exits(self, token, exit_id, count, **kwargs):
        """Finalizes exits that have completed the exit period.

        Args:
            token (address): Address of the token to be processed.
            exit_id (int): Identifier of an exit (optional, pass 0 to ignore the check)
            count (int): Maximum number of exits to be processed.
        """

        self.root_chain.processExits(token, exit_id, count, **kwargs)

    def get_challenge_proof(self, utxo_id, spend_id):
        """Returns information required to submit a challenge.

        Args:
            utxo_id (int): Identifier of the UTXO being exited.
            spend_id (int): Identifier of the transaction that spent the UTXO.

        Returns:
            int, bytes, bytes, bytes: Information necessary to create a challenge proof.
        """

        spend_tx = self.child_chain.get_transaction(spend_id)
        inputs = [(spend_tx.blknum1, spend_tx.txindex1, spend_tx.oindex1),
                  (spend_tx.blknum2, spend_tx.txindex2, spend_tx.oindex2)]
        try:
            input_index = inputs.index(decode_utxo_id(utxo_id))
        except ValueError:
            input_index = 0
        (blknum, _, _) = decode_utxo_id(spend_id)
        block = self.child_chain.blocks[blknum]
        proof = block.merklized_transaction_set.create_membership_proof(
            spend_tx.merkle_hash)
        sigs = spend_tx.sig1 + spend_tx.sig2
        return input_index, spend_tx.encoded, proof, sigs

    def get_plasma_block(self, blknum):
        """Queries a plasma block by its number.

        Args:
            blknum (int): Plasma block number to query.

        Returns:
            PlasmaBlock: Formatted plasma block information.
        """

        block_info = self.root_chain.blocks(blknum)
        return PlasmaBlock(*block_info)

    def get_standard_exit(self, utxo_pos):
        """Queries a plasma exit by its ID.

        Args:
            utxo_pos (int): position of utxo being exited

        Returns:
            tuple: (owner (address), token (address), amount (int))
        """

        exit_id = self.get_standard_exit_id(utxo_pos)
        exit_info = self.root_chain.exits(exit_id)
        return StandardExit(*exit_info)

    def get_standard_exit_id(self, utxo_pos):
        tx = self.child_chain.get_transaction(utxo_pos)
        return self.root_chain.getStandardExitId(tx.encoded, utxo_pos)

    def get_balance(self, account, token=NULL_ADDRESS):
        """Queries ETH or token balance of an account.

        Args:
            account (EthereumAccount): Account to query,
            token (str OR ABIContract OR NULL_ADDRESS):
                MintableToken contract: its address or ABIContract representation.

        Returns:
            int: The account's balance.
        """
        if token == NULL_ADDRESS:
            return self.ethtester.chain.head_state.get_balance(account.address)
        if hasattr(token, "balanceOf"):
            return token.balanceOf(account.address)
        else:
            token_contract = conftest.watch_contract(self.ethtester,
                                                     'MintableToken', token)
            return token_contract.balanceOf(account.address)

    def forward_timestamp(self, amount):
        """Forwards the chain's timestamp.

        Args:
            amount (int): Number of seconds to move forward time.
        """

        self.ethtester.chain.head_state.timestamp += amount

    def get_in_flight_exit_info(self, tx_id, spend_tx=None):
        if spend_tx is None:
            spend_tx = self.child_chain.get_transaction(tx_id)
        input_txs = []
        proofs = b''
        signatures = b''
        for i in range(0, len(spend_tx.inputs)):
            tx_input = spend_tx.inputs[i]
            (blknum, _, _) = decode_utxo_id(tx_input.identifier)
            if blknum == 0:
                continue
            input_tx = self.child_chain.get_transaction(tx_input.identifier)
            input_txs.append(input_tx)
            proofs += self.get_merkle_proof(tx_input.identifier)
            signatures += spend_tx.signatures[i]
        encoded_inputs = rlp.encode(input_txs,
                                    rlp.sedes.CountableList(Transaction, 4))
        return spend_tx.encoded, encoded_inputs, proofs, signatures

    def get_in_flight_exit_id(self, tx_id):
        spend_tx = self.child_chain.get_transaction(tx_id)
        return self.root_chain.getInFlightExitId(spend_tx.encoded)

    def get_merkle_proof(self, tx_id):
        tx = self.child_chain.get_transaction(tx_id)
        (blknum, _, _) = decode_utxo_id(tx_id)
        block = self.child_chain.get_block(blknum)
        merkle = block.merklized_transaction_set
        return merkle.create_membership_proof(tx.encoded)

    def piggyback_in_flight_exit_input(self,
                                       tx_id,
                                       input_index,
                                       key,
                                       bond=None):
        spend_tx = self.child_chain.get_transaction(tx_id)
        bond = bond if bond is not None else self.root_chain.piggybackBond()
        self.root_chain.piggybackInFlightExit(spend_tx.encoded,
                                              input_index,
                                              sender=key,
                                              value=bond)

    def piggyback_in_flight_exit_output(self,
                                        tx_id,
                                        output_index,
                                        key,
                                        bond=None):
        assert output_index in range(4)
        return self.piggyback_in_flight_exit_input(tx_id, output_index + 4,
                                                   key, bond)

    @staticmethod
    def find_shared_input(tx_a, tx_b):
        tx_a_input_index = 0
        tx_b_input_index = 0
        for i in range(0, 4):
            for j in range(0, 4):
                tx_a_input = tx_a.inputs[i].identifier
                tx_b_input = tx_b.inputs[j].identifier
                if tx_a_input == tx_b_input and tx_a_input != 0:
                    tx_a_input_index = i
                    tx_b_input_index = j
        return tx_a_input_index, tx_b_input_index

    @staticmethod
    def find_input_index(output_id, tx_b):
        tx_b_input_index = 0
        for i in range(0, 4):
            tx_b_input = tx_b.inputs[i].identifier
            if tx_b_input == output_id:
                tx_b_input_index = i
        return tx_b_input_index

    def challenge_in_flight_exit_not_canonical(self, in_flight_tx_id,
                                               competing_tx_id, key):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        competing_tx = self.child_chain.get_transaction(competing_tx_id)
        (in_flight_tx_input_index,
         competing_tx_input_index) = self.find_shared_input(
             in_flight_tx, competing_tx)
        proof = self.get_merkle_proof(competing_tx_id)
        signature = competing_tx.signatures[competing_tx_input_index]
        self.root_chain.challengeInFlightExitNotCanonical(
            in_flight_tx.encoded,
            in_flight_tx_input_index,
            competing_tx.encoded,
            competing_tx_input_index,
            competing_tx_id,
            proof,
            signature,
            sender=key)

    def respond_to_non_canonical_challenge(self, in_flight_tx_id, key):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        proof = self.get_merkle_proof(in_flight_tx_id)
        self.root_chain.respondToNonCanonicalChallenge(in_flight_tx.encoded,
                                                       in_flight_tx_id, proof)

    def forward_to_period(self, period):
        self.forward_timestamp((period - 1) * IN_FLIGHT_PERIOD)

    def challenge_in_flight_exit_input_spent(self, in_flight_tx_id,
                                             spend_tx_id, key):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        spend_tx = self.child_chain.get_transaction(spend_tx_id)
        (in_flight_tx_input_index,
         spend_tx_input_index) = self.find_shared_input(
             in_flight_tx, spend_tx)
        signature = spend_tx.signatures[spend_tx_input_index]
        self.root_chain.challengeInFlightExitInputSpent(
            in_flight_tx.encoded,
            in_flight_tx_input_index,
            spend_tx.encoded,
            spend_tx_input_index,
            signature,
            sender=key)

    def challenge_in_flight_exit_output_spent(self, in_flight_tx_id,
                                              spending_tx_id, output_index,
                                              key):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        spending_tx = self.child_chain.get_transaction(spending_tx_id)
        in_flight_tx_output_id = in_flight_tx_id + output_index
        spending_tx_input_index = self.find_input_index(
            in_flight_tx_output_id, spending_tx)
        in_flight_tx_inclusion_proof = self.get_merkle_proof(in_flight_tx_id)
        spending_tx_sig = spending_tx.signatures[spending_tx_input_index]
        self.root_chain.challengeInFlightExitOutputSpent(
            in_flight_tx.encoded,
            in_flight_tx_output_id,
            in_flight_tx_inclusion_proof,
            spending_tx.encoded,
            spending_tx_input_index,
            spending_tx_sig,
            sender=key)

    def get_in_flight_exit(self, in_flight_tx_id):
        in_flight_tx = self.child_chain.get_transaction(in_flight_tx_id)
        exit_id = self.root_chain.getInFlightExitId(in_flight_tx.encoded)
        exit_info = self.root_chain.inFlightExits(exit_id)
        return InFlightExit(self.root_chain, in_flight_tx, *exit_info)