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 __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 = {}
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 __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)
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
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)
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
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)