def test_account_transfer_from_address(): """ Case: transfer tokens from address to address. Expect: account's balances, stored in state, are changed according to transfer amount. """ expected_account_from_balance = ACCOUNT_FROM_BALANCE - TOKENS_AMOUNT_TO_SEND expected_account_to_balance = ACCOUNT_TO_BALANCE + TOKENS_AMOUNT_TO_SEND transfer_payload = TransferPayload() transfer_payload.address_to = ACCOUNT_ADDRESS_TO transfer_payload.value = TOKENS_AMOUNT_TO_SEND transaction_payload = TransactionPayload() transaction_payload.method = AccountMethod.TRANSFER transaction_payload.data = transfer_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = TransactionHeader( signer_public_key=RANDOM_NODE_PUBLIC_KEY, family_name=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get('family_name'), family_version=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get('family_version'), inputs=INPUTS, outputs=OUTPUTS, dependencies=[], payload_sha512=hash512(data=serialized_transaction_payload), batcher_public_key=RANDOM_NODE_PUBLIC_KEY, nonce=time.time().hex().encode(), ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer(private_key=ACCOUNT_FROM_PRIVATE_KEY).sign(serialized_header), ) mock_context = create_context(account_from_balance=ACCOUNT_FROM_BALANCE, account_to_balance=ACCOUNT_TO_BALANCE) AccountHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state(addresses=[ ACCOUNT_ADDRESS_FROM, ACCOUNT_ADDRESS_TO, ]) state_as_dict = {} for entry in state_as_list: acc = Account() acc.ParseFromString(entry.data) state_as_dict[entry.address] = acc assert state_as_dict.get(ACCOUNT_ADDRESS_FROM, Account()).balance == expected_account_from_balance assert state_as_dict.get(ACCOUNT_ADDRESS_TO, Account()).balance == expected_account_to_balance
def _genesis(self, context, pub_key, genesis_payload): signer_key = self.make_address_from_data(pub_key) genesis_status = get_data(context, GenesisStatus, GENESIS_ADDRESS) if not genesis_status: genesis_status = GenesisStatus() elif genesis_status.status: raise InvalidTransaction('Genesis is already initialized.') genesis_status.status = True account = Account() account.balance = genesis_payload.total_supply LOGGER.info( 'Generated genesis transaction. Issued {} tokens to address {}'. format(genesis_payload.total_supply, signer_key)) return {signer_key: account, GENESIS_ADDRESS: genesis_status}
def _store_pub_key(self, context, signer_pubkey, transaction_payload): address = self.make_address_from_data(transaction_payload.public_key) LOGGER.info('Pub key address {}'.format(address)) account_address = AccountHandler().make_address_from_data(signer_pubkey) LOGGER.info('Account address {}'.format(address)) data, account = get_multiple_data(context, [(address, PubKeyStorage), (account_address, Account)]) if data: raise InvalidTransaction('This pub key is already registered.') cert_signer_pubkey = load_pem_public_key(transaction_payload.public_key.encode('utf-8'), backend=default_backend()) try: ehs_bytes = binascii.unhexlify(transaction_payload.entity_hash_signature) eh_bytes = binascii.unhexlify(transaction_payload.entity_hash) except binascii.Error: LOGGER.debug(f'entity_hash_signature {transaction_payload.entity_hash_signature}') LOGGER.debug(f'entity_hash {transaction_payload.entity_hash}') raise InvalidTransaction('Entity hash or signature not a hex format') # FIXME: For support PKCS1v15 and PSS LOGGER.warn('HAZARD: Detecting padding for verification') sigerr = 0 pkcs = padding.PKCS1v15() pss = padding.PSS(mgf=padding.MGF1(hashes.SHA512()), salt_length=padding.PSS.MAX_LENGTH) for _padding in (pkcs, pss): try: cert_signer_pubkey.verify(ehs_bytes, eh_bytes, _padding, hashes.SHA512()) LOGGER.warn('HAZARD: Padding found: %s', _padding.name) except InvalidSignature: sigerr += 1 if sigerr == 2: raise InvalidTransaction('Invalid signature') valid_from = datetime.fromtimestamp(transaction_payload.valid_from) valid_to = datetime.fromtimestamp(transaction_payload.valid_to) if valid_to - valid_from > PUB_KEY_MAX_VALIDITY: raise InvalidTransaction('The public key validity exceeds the maximum value.') data = PubKeyStorage() data.owner = signer_pubkey data.payload.CopyFrom(transaction_payload) data.revoked = False if not account: account = Account() if _get_setting_value(context, 'remme.economy_enabled', 'true').lower() == 'true': if account.balance < PUB_KEY_STORE_PRICE: raise InvalidTransaction('Not enough tokens to register a new pub key. Current balance: {}' .format(account.balance)) account.balance -= PUB_KEY_STORE_PRICE if address not in account.pub_keys: account.pub_keys.append(address) return {address: data, account_address: account}
def test_genesis_success(self): TOTAL_SUPPLY = 10000 self.send_transaction(AccountMethod.GENESIS, AccountClient.get_genesis_payload(TOTAL_SUPPLY), [GENESIS_ADDRESS, self.account_address1]) self.expect_get({GENESIS_ADDRESS: None}) genesis_status = GenesisStatus() genesis_status.status = True account = Account() account.balance = TOTAL_SUPPLY self.expect_set({ self.account_address1: account, GENESIS_ADDRESS: genesis_status }) self.expect_ok()
def _swap_init(self, context, signer_pubkey, swap_init_payload): """ if SecretLockOptionalBob is provided, Bob uses _swap_init to respond to requested swap Otherwise, Alice uses _swap_init to request a swap and thus, Bob can't receive funds until Alice "approves". """ address_swap_info_is_stored_by = self.make_address_from_data(swap_init_payload.swap_id) swap_information = get_data(context, AtomicSwapInfo, address_swap_info_is_stored_by) if swap_information: raise InvalidTransaction('Atomic swap ID has already been taken, please use a different one.') block_info = self._get_latest_block_info(context) block_time = block_info.timestamp swap_information = AtomicSwapInfo() swap_information.swap_id = swap_init_payload.swap_id swap_information.state = AtomicSwapInfo.OPENED swap_information.amount = swap_init_payload.amount swap_information.created_at = block_time swap_information.secret_lock = swap_init_payload.secret_lock_by_solicitor swap_information.email_address_encrypted_optional = swap_init_payload.email_address_encrypted_by_initiator swap_information.sender_address = AccountHandler().make_address_from_data(signer_pubkey) swap_information.sender_address_non_local = swap_init_payload.sender_address_non_local swap_information.receiver_address = swap_init_payload.receiver_address swap_information.is_initiator = not swap_init_payload.secret_lock_by_solicitor commission_amount = int(_get_setting_value(context, SETTINGS_SWAP_COMMISSION)) if commission_amount < 0: raise InvalidTransaction('Wrong commission address.') swap_total_amount = swap_information.amount + commission_amount account = get_data(context, Account, swap_information.sender_address) if account is None: account = Account() if account.balance < swap_total_amount: raise InvalidTransaction( f'Not enough balance to perform the transaction in the amount (with a commission) {swap_total_amount}.' ) transfer_payload = AccountClient.get_transfer_payload(ZERO_ADDRESS, commission_amount) transfer_state = AccountHandler()._transfer_from_address( context, swap_information.sender_address, transfer_payload, ) sender_account = transfer_state.get(swap_information.sender_address) sender_account.balance -= swap_information.amount return { address_swap_info_is_stored_by: swap_information, **transfer_state, }
def _transfer_from_address(self, context, address, transfer_payload): signer_key = address if not transfer_payload.value: raise InvalidTransaction("Could not transfer with zero amount") if not transfer_payload.address_to.startswith(self._prefix) \ and transfer_payload.address_to not in [ZERO_ADDRESS]: raise InvalidTransaction("Receiver address has to be of an account type") if signer_key == transfer_payload.address_to: raise InvalidTransaction("Account cannot send tokens to itself.") signer_account, receiver_account = get_multiple_data(context, [(signer_key, Account), (transfer_payload.address_to, Account)]) # TODO transfer from genesis address using SETTINGS_KEY_GENESIS_OWNERS list of allowed addresses(0x0) # genesis_members_str = _get_setting_value(context, SETTINGS_KEY_GENESIS_OWNERS) # if not genesis_members_str: # raise InvalidTransaction('REMchain is not configured to process genesis transfers.') # # genesis_members_list = genesis_members_str.split() if not receiver_account: receiver_account = Account() if not signer_account: signer_account = Account() if signer_account.balance < transfer_payload.value: raise InvalidTransaction("Not enough transferable balance. Sender's current balance: {}" .format(signer_account.balance)) receiver_account.balance += transfer_payload.value signer_account.balance -= transfer_payload.value LOGGER.info('Transferred {} tokens from {} to {}'.format(transfer_payload.value, signer_key, transfer_payload.address_to)) return { signer_key: signer_account, transfer_payload.address_to: receiver_account }
def test_public_key_handler_non_existing_sender_account(): """ Case: send transaction request, to store certificate public key, from non-existing account. Expect: invalid transaction error is raised with not enough transferable balance error message. """ new_public_key_payload = generate_rsa_payload() transaction_payload = TransactionPayload() transaction_payload.method = PubKeyMethod.STORE transaction_payload.data = new_public_key_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = generate_header(serialized_transaction_payload, INPUTS, OUTPUTS) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer( private_key=SENDER_PRIVATE_KEY).sign(serialized_header), ) zero_account = Account() zero_account.balance = 0 serialized_zero_account = zero_account.SerializeToString() mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ ZERO_ADDRESS: serialized_zero_account, }) with pytest.raises(InvalidTransaction) as error: PubKeyHandler().apply(transaction=transaction_request, context=mock_context) assert 'Not enough transferable balance. Sender\'s current balance: 0.' == str( error.value)
def _transfer_from_address(self, context, address, transfer_payload): signer_key = address if not transfer_payload.value: raise InvalidTransaction("Could not transfer with zero amount") if not transfer_payload.address_to.startswith(self._prefix) \ and transfer_payload.address_to not in [ZERO_ADDRESS]: raise InvalidTransaction( "Receiver address has to be of an account type") if signer_key == transfer_payload.address_to: raise InvalidTransaction("Account cannot send tokens to itself.") signer_account, receiver_account = get_multiple_data( context, [(signer_key, Account), (transfer_payload.address_to, Account)]) if not receiver_account: receiver_account = Account() if not signer_account: signer_account = Account() if signer_account.balance < transfer_payload.value: raise InvalidTransaction( "Not enough transferable balance. Sender's current balance: " f"{signer_account.balance}") receiver_account.balance += transfer_payload.value signer_account.balance -= transfer_payload.value LOGGER.info(f'Transferred {transfer_payload.value} tokens from ' f'{signer_key} to {transfer_payload.address_to}') return { signer_key: signer_account, transfer_payload.address_to: receiver_account }
def _transfer_from_address(self, context, address_from, transfer_payload): if not transfer_payload.value: raise InvalidTransaction('Could not transfer with zero amount.') if not transfer_payload.address_to.startswith(self._prefix) \ and transfer_payload.address_to != ZERO_ADDRESS: raise InvalidTransaction('Receiver address has to be of an account type.') if address_from == transfer_payload.address_to: raise InvalidTransaction('Account cannot send tokens to itself.') signer_account, receiver_account = get_multiple_data(context, [ (address_from, Account), (transfer_payload.address_to, Account), ]) if signer_account is None: signer_account = Account() if receiver_account is None: receiver_account = Account() if signer_account.balance < transfer_payload.value: raise InvalidTransaction( f'Not enough transferable balance. Sender\'s current balance: {signer_account.balance}.', ) receiver_account.balance += transfer_payload.value signer_account.balance -= transfer_payload.value LOGGER.info( f'Transferred {transfer_payload.value} tokens from {address_from} to {transfer_payload.address_to}.', ) return { address_from: signer_account, transfer_payload.address_to: receiver_account, }
def test_store_public_key_for_other_no_payer_account(): """ Case: send transaction request, to store certificate public key for other, when payer account does not exist. Expect: invalid transaction error is raised with not enough transferable balance error message. """ new_public_key_payload = generate_rsa_payload(key=CERTIFICATE_PUBLIC_KEY) serialized_new_public_key_payload = new_public_key_payload.SerializeToString() private_key = Secp256k1PrivateKey.from_hex(OWNER_PRIVATE_KEY) signature_by_owner = Secp256k1Context().sign(serialized_new_public_key_payload, private_key) new_public_key_store_and_pay_payload = NewPubKeyStoreAndPayPayload( pub_key_payload=new_public_key_payload, owner_public_key=bytes.fromhex(OWNER_PUBLIC_KEY), signature_by_owner=bytes.fromhex(signature_by_owner), ) transaction_payload = TransactionPayload() transaction_payload.method = PubKeyMethod.STORE_AND_PAY transaction_payload.data = new_public_key_store_and_pay_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = generate_header( serialized_transaction_payload, INPUTS, OUTPUTS, signer_public_key=PAYER_PUBLIC_KEY, ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer(private_key=PAYER_PRIVATE_KEY).sign(serialized_header), ) owner_account = Account() owner_account.pub_keys.append(RANDOM_ALREADY_STORED_OWNER_PUBLIC_KEY_ADDRESS) serialized_owner_account = owner_account.SerializeToString() zero_account = Account() zero_account.balance = 0 serialized_zero_account = zero_account.SerializeToString() mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ OWNER_ADDRESS: serialized_owner_account, ZERO_ADDRESS: serialized_zero_account, }) with pytest.raises(InvalidTransaction) as error: PubKeyHandler().apply(transaction=transaction_request, context=mock_context) assert 'Not enough transferable balance. Sender\'s current balance: 0.' == str(error.value)
def _swap_expire(self, context, signer_pubkey, swap_expire_payload): """ Transaction initiator (Alice) decides to withdraw deposit in 24 hours, or Bob in 48 hours. """ swap_identifier = swap_expire_payload.swap_id address_swap_info_is_stored_by = self.make_address_from_data(swap_identifier) swap_information = get_data(context, AtomicSwapInfo, address_swap_info_is_stored_by) if not swap_information: raise InvalidTransaction(f'Atomic swap was not initiated for identifier {swap_identifier}!') if swap_information.state in NOT_PERMITTED_TO_CHANGE_SWAP_STATUSES: raise InvalidTransaction( f'No operations can be done upon the swap: {swap_identifier} as it is already closed or expired.', ) signer_address = AccountHandler().make_address_from_data(signer_pubkey) if signer_address != swap_information.sender_address: raise InvalidTransaction('Signer is not the one who opened the swap.') block = self._get_latest_block_info(context) block_time = self.get_datetime_from_timestamp(block.timestamp) created_at = self.get_datetime_from_timestamp(swap_information.created_at) time_delta = INITIATOR_TIME_DELTA_LOCK if swap_information.is_initiator else NON_INITIATOR_TIME_DELTA_LOCK if (created_at + time_delta) > block_time: initiator_name = 'initiator' if swap_information.is_initiator else 'non initiator' initiator_time_lock = INTIATOR_TIME_LOCK if swap_information.is_initiator else NON_INTIATOR_TIME_LOCK raise InvalidTransaction( f'Swap {initiator_name} needs to wait {initiator_time_lock} hours since ' f'timestamp {swap_information.created_at} to withdraw.' ) account = get_data(context, Account, swap_information.sender_address) if account is None: account = Account() account.balance += swap_information.amount swap_information.state = AtomicSwapInfo.EXPIRED return { address_swap_info_is_stored_by: swap_information, swap_information.sender_address: account, }
def _swap_close(self, context, signer_pubkey, swap_close_payload): """ Close atomic swap. Any party (Bob or Alice) can close atomic swap by providing secret key. If hash from secret key matches secret lock, secret key is valid. Closing atomic swap means participant (not initiator) get REMchain tokens instead ERC20 tokens. Closing requires atomic swap to be approved. """ swap_identifier = swap_close_payload.swap_id address_swap_info_is_stored_by = self.make_address_from_data(swap_identifier) swap_information = get_data(context, AtomicSwapInfo, address_swap_info_is_stored_by) if not swap_information: raise InvalidTransaction(f'Atomic swap was not initiated for identifier {swap_identifier}!') if swap_information.state in NOT_PERMITTED_TO_CHANGE_SWAP_STATUSES: raise InvalidTransaction( f'No operations can be done upon the swap: {swap_identifier} as it is already closed or expired.', ) if not swap_information.secret_lock: raise InvalidTransaction('Secret lock is required to close the swap.') if web3_hash(swap_close_payload.secret_key) != swap_information.secret_lock: raise InvalidTransaction('Secret key doesn\'t match specified secret lock.') if swap_information.is_initiator and swap_information.state != AtomicSwapInfo.APPROVED: raise InvalidTransaction('Transaction cannot be closed before it\'s approved.') account = get_data(context, Account, swap_information.receiver_address) if account is None: account = Account() account.balance += swap_information.amount swap_information.secret_key = swap_close_payload.secret_key swap_information.state = AtomicSwapInfo.CLOSED return { address_swap_info_is_stored_by: swap_information, swap_information.receiver_address: account, }
def create_context(account_from_balance, account_to_balance): """ Create stub context with initial data. Stub context is an interface around Sawtooth state, consider as database. State is key-value storage that contains address with its data (i.e. account balance). References: - https://github.com/Remmeauth/remme-core/blob/dev/testing/mocks/stub.py """ account_protobuf = Account() account_protobuf.balance = account_from_balance serialized_account_from_balance = account_protobuf.SerializeToString() account_protobuf.balance = account_to_balance serialized_account_to_balance = account_protobuf.SerializeToString() initial_state = { ACCOUNT_ADDRESS_FROM: serialized_account_from_balance, ACCOUNT_ADDRESS_TO: serialized_account_to_balance, } return StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state=initial_state)
def get_account_by_address(context, address): account = get_data(context, Account, address) if account is None: return Account() return account
def test_store_rsa_public_key_no_owner_account(): """ Case: send transaction request, to store certificate public key (RSA) for other, when owner account does not exist. Expect: public key information is stored to blockchain linked to the newly created owner account's address. """ new_public_key_payload = generate_rsa_payload(key=CERTIFICATE_PUBLIC_KEY) serialized_new_public_key_payload = new_public_key_payload.SerializeToString() private_key = Secp256k1PrivateKey.from_hex(OWNER_PRIVATE_KEY) signature_by_owner = Secp256k1Context().sign(serialized_new_public_key_payload, private_key) new_public_key_store_and_pay_payload = NewPubKeyStoreAndPayPayload( pub_key_payload=new_public_key_payload, owner_public_key=bytes.fromhex(OWNER_PUBLIC_KEY), signature_by_owner=bytes.fromhex(signature_by_owner), ) transaction_payload = TransactionPayload() transaction_payload.method = PubKeyMethod.STORE_AND_PAY transaction_payload.data = new_public_key_store_and_pay_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = generate_header( serialized_transaction_payload, INPUTS, OUTPUTS, signer_public_key=PAYER_PUBLIC_KEY, ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer(private_key=PAYER_PRIVATE_KEY).sign(serialized_header), ) payer_account = Account() payer_account.balance = PAYER_INITIAL_BALANCE serialized_payer_account = payer_account.SerializeToString() zero_account = Account() zero_account.balance = 0 serialized_zero_account = zero_account.SerializeToString() mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ PAYER_ADDRESS: serialized_payer_account, ZERO_ADDRESS: serialized_zero_account, }) expected_public_key_storage = PubKeyStorage() expected_public_key_storage.owner = OWNER_PUBLIC_KEY expected_public_key_storage.payload.CopyFrom(new_public_key_payload) expected_public_key_storage.is_revoked = False expected_serialized_public_key_storage = expected_public_key_storage.SerializeToString() expected_payer_account = Account() expected_payer_account.balance = PAYER_INITIAL_BALANCE - PUB_KEY_STORE_PRICE serialized_expected_payer_account = expected_payer_account.SerializeToString() expected_owner_account = Account() expected_owner_account.pub_keys.append(ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY) serialized_expected_owner_account = expected_owner_account.SerializeToString() expected_zero_account = Account() expected_zero_account.balance = 0 + PUB_KEY_STORE_PRICE expected_serialized_zero_account = expected_zero_account.SerializeToString() expected_state = { OWNER_ADDRESS: serialized_expected_owner_account, PAYER_ADDRESS: serialized_expected_payer_account, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY: expected_serialized_public_key_storage, ZERO_ADDRESS: expected_serialized_zero_account, } PubKeyHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state(addresses=[ OWNER_ADDRESS, PAYER_ADDRESS, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY, ZERO_ADDRESS, ]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
async def get_account(self, address): account = Account() raw_account = await self.get_value(address) account.ParseFromString(raw_account) return account
def get_account_model(self, balance): account = Account() account.balance = int(balance) return account
def test_store_public_key_for_other_economy_is_not_enabled(): """ Case: send transaction request, to store certificate public key for other, when economy isn't enabled. Expect: public key information is stored to blockchain linked to owner address. Owner hasn't paid for storing. """ new_public_key_payload = generate_rsa_payload(key=CERTIFICATE_PUBLIC_KEY) serialized_new_public_key_payload = new_public_key_payload.SerializeToString() private_key = Secp256k1PrivateKey.from_hex(OWNER_PRIVATE_KEY) signature_by_owner = Secp256k1Context().sign(serialized_new_public_key_payload, private_key) new_public_key_store_and_pay_payload = NewPubKeyStoreAndPayPayload( pub_key_payload=new_public_key_payload, owner_public_key=bytes.fromhex(OWNER_PUBLIC_KEY), signature_by_owner=bytes.fromhex(signature_by_owner), ) transaction_payload = TransactionPayload() transaction_payload.method = PubKeyMethod.STORE_AND_PAY transaction_payload.data = new_public_key_store_and_pay_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = generate_header( serialized_transaction_payload, INPUTS, OUTPUTS, signer_public_key=PAYER_PUBLIC_KEY, ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer(private_key=PAYER_PRIVATE_KEY).sign(serialized_header), ) payer_account = Account() payer_account.balance = PAYER_INITIAL_BALANCE serialized_payer_account = payer_account.SerializeToString() owner_account = Account() owner_account.pub_keys.append(RANDOM_ALREADY_STORED_OWNER_PUBLIC_KEY_ADDRESS) serialized_owner_account = owner_account.SerializeToString() zero_account = Account() zero_account.balance = 0 serialized_zero_account = zero_account.SerializeToString() is_economy_enabled_setting = Setting() is_economy_enabled_setting.entries.add(key='remme.economy_enabled', value='false') serialized_is_economy_enabled_setting = is_economy_enabled_setting.SerializeToString() mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ OWNER_ADDRESS: serialized_owner_account, PAYER_ADDRESS: serialized_payer_account, ZERO_ADDRESS: serialized_zero_account, IS_NODE_ECONOMY_ENABLED_ADDRESS: serialized_is_economy_enabled_setting, }) expected_public_key_storage = PubKeyStorage() expected_public_key_storage.owner = OWNER_PUBLIC_KEY expected_public_key_storage.payload.CopyFrom(new_public_key_payload) expected_public_key_storage.is_revoked = False expected_serialized_public_key_storage = expected_public_key_storage.SerializeToString() expected_payer_account = Account() expected_payer_account.balance = PAYER_INITIAL_BALANCE serialized_expected_payer_account = expected_payer_account.SerializeToString() expected_owner_account = Account() expected_owner_account.pub_keys.append(RANDOM_ALREADY_STORED_OWNER_PUBLIC_KEY_ADDRESS) expected_owner_account.pub_keys.append(ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY) serialized_expected_owner_account = expected_owner_account.SerializeToString() expected_zero_account = Account() expected_zero_account.balance = 0 expected_serialized_zero_account = expected_zero_account.SerializeToString() expected_state = { OWNER_ADDRESS: serialized_expected_owner_account, PAYER_ADDRESS: serialized_expected_payer_account, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY: expected_serialized_public_key_storage, ZERO_ADDRESS: expected_serialized_zero_account, } PubKeyHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state(addresses=[ OWNER_ADDRESS, PAYER_ADDRESS, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY, ZERO_ADDRESS, ]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
def test_public_key_handler_rsa_store(): """ Case: send transaction request to store certificate public key. Expect: public key information is stored to blockchain linked to owner address. Owner paid tokens for storing. """ new_public_key_payload = generate_rsa_payload() transaction_payload = TransactionPayload() transaction_payload.method = PubKeyMethod.STORE transaction_payload.data = new_public_key_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = generate_header(serialized_transaction_payload, INPUTS, OUTPUTS) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer( private_key=SENDER_PRIVATE_KEY).sign(serialized_header), ) sender_account = Account() sender_account.balance = SENDER_INITIAL_BALANCE sender_account.pub_keys.append(RANDOM_ALREADY_STORED_SENDER_PUBLIC_KEY) serialized_sender_account = sender_account.SerializeToString() zero_account = Account() zero_account.balance = 0 serialized_zero_account = zero_account.SerializeToString() mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ SENDER_ADDRESS: serialized_sender_account, ZERO_ADDRESS: serialized_zero_account, }) expected_public_key_storage = PubKeyStorage() expected_public_key_storage.owner = SENDER_PUBLIC_KEY expected_public_key_storage.payload.CopyFrom(new_public_key_payload) expected_public_key_storage.is_revoked = False expected_serialized_public_key_storage = expected_public_key_storage.SerializeToString( ) expected_sender_account = Account() expected_sender_account.balance = SENDER_INITIAL_BALANCE - PUB_KEY_STORE_PRICE expected_sender_account.pub_keys.append( RANDOM_ALREADY_STORED_SENDER_PUBLIC_KEY) expected_sender_account.pub_keys.append( ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY) expected_serialized_sender_account = expected_sender_account.SerializeToString( ) expected_zero_account = Account() expected_zero_account.balance = 0 + PUB_KEY_STORE_PRICE expected_serialized_zero_account = expected_zero_account.SerializeToString( ) expected_state = { SENDER_ADDRESS: expected_serialized_sender_account, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY: expected_serialized_public_key_storage, ZERO_ADDRESS: expected_serialized_zero_account, } PubKeyHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state(addresses=[ SENDER_ADDRESS, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY, ZERO_ADDRESS, ]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
def test_public_key_handler_store_sender_is_node(): """ Case: send transaction request, to store certificate public key, when sender is node (same addresses). Expect: public key information is stored to blockchain linked to owner address. Owner hasn't paid for storing. """ new_public_key_payload = generate_rsa_payload() transaction_payload = TransactionPayload() transaction_payload.method = PubKeyMethod.STORE transaction_payload.data = new_public_key_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = generate_header(serialized_transaction_payload, INPUTS, OUTPUTS) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer( private_key=SENDER_PRIVATE_KEY).sign(serialized_header), ) sender_account = Account() sender_account.pub_keys.append(RANDOM_ALREADY_STORED_SENDER_PUBLIC_KEY) serialized_sender_account = sender_account.SerializeToString() zero_account = Account() serialized_zero_account = zero_account.SerializeToString() is_economy_enabled_setting = Setting() is_economy_enabled_setting.entries.add(key='remme.economy_enabled', value='false') serialized_is_economy_enabled_setting = is_economy_enabled_setting.SerializeToString( ) mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ SENDER_ADDRESS: serialized_sender_account, IS_NODE_ECONOMY_ENABLED_ADDRESS: serialized_is_economy_enabled_setting, ZERO_ADDRESS: serialized_zero_account, }) expected_public_key_storage = PubKeyStorage() expected_public_key_storage.owner = SENDER_PUBLIC_KEY expected_public_key_storage.payload.CopyFrom(new_public_key_payload) expected_public_key_storage.is_revoked = False expected_serialized_public_key_storage = expected_public_key_storage.SerializeToString( ) expected_sender_account = Account() expected_sender_account.pub_keys.append( RANDOM_ALREADY_STORED_SENDER_PUBLIC_KEY) expected_sender_account.pub_keys.append( ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY) expected_serialized_sender_account = expected_sender_account.SerializeToString( ) expected_zero_account = Account() expected_serialized_zero_account = expected_zero_account.SerializeToString( ) expected_state = { SENDER_ADDRESS: expected_serialized_sender_account, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY: expected_serialized_public_key_storage, ZERO_ADDRESS: expected_serialized_zero_account, } PubKeyHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state(addresses=[ SENDER_ADDRESS, ADDRESS_FROM_CERTIFICATE_PUBLIC_KEY, ZERO_ADDRESS, ]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
class RpcApiTestCase(AioHTTPTestCase, HelperTestCase): @staticmethod def create_raw_transaction_send_token_payload(pub_key_to, amount=1): client = AccountClient() signer = client._signer address = client.make_address_from_data(pub_key_to) node_address = client.get_user_address() transfer = TransferPayload() transfer.address_to = address transfer.value = amount tr = TransactionPayload() tr.method = AccountMethod.TRANSFER tr.data = transfer.SerializeToString() payload = tr.SerializeToString() header = TransactionHeader( signer_public_key=signer.get_public_key().as_hex(), family_name=client._family_handler.family_name, family_version=client._family_handler.family_versions[-1], inputs=[node_address, address], outputs=[node_address, address], dependencies=[], payload_sha512=hash512(payload), batcher_public_key=signer.get_public_key().as_hex(), nonce=time.time().hex().encode()).SerializeToString() signature = signer.sign(header) transaction = Transaction(header=header, payload=payload, header_signature=signature) return transaction async def get_application(self): app = web.Application() rpc = JsonRpc(loop=self.loop, max_workers=1) rpc.add_methods( ('', get_node_config), ('', send_raw_transaction), ('', get_balance), ('', get_batch_status), ) app.router.add_route('POST', '/', rpc) return app async def create_rpc_request(self, method, params=None): if params is None: params = {} data = encode_request(method, id=int(time.time()), params=params) LOGGER.info(f'JSON RPC request payload: {data}') return await self.client.request( 'POST', '/', data=data, headers={'Content-Type': 'application/json'}) @mock.patch( 'remme.clients.basic.BasicClient.fetch_state', return_value={ 'data': base64.b64encode( Setting(entries=[ Setting.Entry( key=_make_settings_key(SETTINGS_STORAGE_PUB_KEY), value= '03823c7a9e285246985089824f3aaa51fb8675d08d84b151833ca5febce37ad61e' ) ]).SerializeToString()) }) @mock.patch('remme.clients.basic.BasicClient._head_to_root', return_value=(None, 'some_root')) @unittest_run_loop @test async def test_node_key_retrieve_info_and_it_ok(self, root_mock, fetch_state_mock): resp = await self.create_rpc_request('get_node_config') self.assertEqual(resp.status, 200) data = await resp.json() pub_key = PubKeyClient().get_public_key() self.assertEqual(data['result']['node_public_key'], pub_key) @mock.patch( 'remme.clients.basic.BasicClient.submit_batches', return_value={ 'data': 'c6bcb01255c1870a5d42fe2dde5e91fb0c5992ec0b49932cdab901539bf977f75bb7699c053cea16668ba732a7d597dd0c2b80f157f1a2514932078bb761de4b' }) @unittest_run_loop @test async def test_valid_raw_transaction_send_to_the_node(self, req_mock): payload = self.create_raw_transaction_send_token_payload( '03823c7a9e285246985089824f3aaa51fb8675d08d84b151833ca5febce37ad61e', 1) resp = await self.create_rpc_request('send_raw_transaction', { 'data': base64.b64encode(payload.SerializeToString()).decode('utf-8') }) self.assertEqual(resp.status, 200) data = await resp.json() self.assertEqual( data['result'], 'c6bcb01255c1870a5d42fe2dde5e91fb0c5992ec0b49932cdab901539bf977f75bb7699c053cea16668ba732a7d597dd0c2b80f157f1a2514932078bb761de4b' ) @mock.patch('remme.clients.basic.BasicClient.fetch_state', return_value={ 'data': base64.b64encode(Account(balance=100).SerializeToString()) }) @mock.patch('remme.clients.basic.BasicClient._head_to_root', return_value=(None, 'some_root')) @unittest_run_loop @test async def test_get_token_balance(self, root_mock, fetch_state_mock): address = AccountClient().make_address_from_data( '03823c7a9e285246985089824f3aaa51fb8675d08d84b151833ca5febce37ad61a' ) resp = await self.create_rpc_request('get_balance', {'public_key_address': address}) self.assertEqual(resp.status, 200) data = await resp.json() self.assertEqual(data['result'], 100) @mock.patch('remme.clients.basic.BasicClient.list_statuses', return_value={'data': [{ 'status': 'COMMITTED' }]}) @unittest_run_loop @test async def test_check_batch_status(self, batch_status_mock): resp = await self.create_rpc_request( 'get_batch_status', { 'id': '3936f0fa13d008c2b00d04013dfa5e5359fccc117e4c47b1416ee24e115ac08b08707be3b3ce6956ca3d789d245ff0dddf7a39bc2b2f4210ffe81ebd0244c014' }) self.assertEqual(resp.status, 200) data = await resp.json() self.assertEqual(data['result'], 'COMMITTED')
def get_account(self, address): account = Account() account.ParseFromString(self.get_value(address)) return account
def test_account_handler_genesis_apply(): """ Case: send transaction request, to send tokens from genesis address, to the account handler. Expect: """ account = Account() account.balance = TOKENS_AMOUNT_TO_SUPPLY expected_serialized_account_to_balance = account.SerializeToString() genesis_payload = GenesisPayload() genesis_payload.total_supply = TOKENS_AMOUNT_TO_SUPPLY transaction_payload = TransactionPayload() transaction_payload.method = AccountMethod.GENESIS transaction_payload.data = genesis_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = TransactionHeader( signer_public_key=NODE_PUBLIC_KEY, family_name=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get( 'family_name'), family_version=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get( 'family_version'), inputs=INPUTS, outputs=OUTPUTS, dependencies=[], payload_sha512=hash512(data=serialized_transaction_payload), batcher_public_key=NODE_PUBLIC_KEY, nonce=time.time().hex().encode(), ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer( private_key=NODE_PRIVATE_KEY).sign(serialized_header), ) genesis_status = GenesisStatus() genesis_status.status = True expected_state = { GENESIS_ADDRESS: genesis_status.SerializeToString(), ACCOUNT_ADDRESS_TO: expected_serialized_account_to_balance, } mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={}) AccountHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state( addresses=[GENESIS_ADDRESS, ACCOUNT_ADDRESS_TO]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
def test_close_atomic_swap(): """ Case: close atomic swap. Expect: increase Alice account address by swap amount. """ atomic_swap_close_payload = AtomicSwapClosePayload( swap_id=SWAP_ID, secret_key=SECRET_KEY, ) transaction_payload = TransactionPayload() transaction_payload.method = AtomicSwapMethod.CLOSE transaction_payload.data = atomic_swap_close_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = TransactionHeader( signer_public_key=BOT_PUBLIC_KEY, family_name=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get( 'family_name'), family_version=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get( 'family_version'), inputs=INPUTS, outputs=OUTPUTS, dependencies=[], payload_sha512=hash512(data=serialized_transaction_payload), batcher_public_key=RANDOM_NODE_PUBLIC_KEY, nonce=time.time().hex().encode(), ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer( private_key=BOT_PRIVATE_KEY).sign(serialized_header), ) alice_account = Account() alice_account.balance = 0 serialized_alice_account = alice_account.SerializeToString() existing_swap_info_to_close = AtomicSwapInfo() existing_swap_info_to_close.swap_id = SWAP_ID existing_swap_info_to_close.amount = 200 existing_swap_info_to_close.state = AtomicSwapInfo.APPROVED existing_swap_info_to_close.secret_lock = SECRET_LOCK existing_swap_info_to_close.is_initiator = True existing_swap_info_to_close.sender_address = BOT_ADDRESS existing_swap_info_to_close.receiver_address = ALICE_ADDRESS serialized_existing_swap_info_to_lock = existing_swap_info_to_close.SerializeToString( ) genesis_members_setting = Setting() genesis_members_setting.entries.add(key=SETTINGS_KEY_ZERO_ADDRESS_OWNERS, value=f'{BOT_PUBLIC_KEY},') serialized_genesis_members_setting = genesis_members_setting.SerializeToString( ) mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ ADDRESS_TO_GET_GENESIS_MEMBERS_AS_STRING_BY: serialized_genesis_members_setting, ADDRESS_TO_STORE_SWAP_INFO_BY: serialized_existing_swap_info_to_lock, ALICE_ADDRESS: serialized_alice_account, }) expected_alice_account = Account() expected_alice_account.balance = TOKENS_AMOUNT_TO_SWAP serialized_expected_alice_account = expected_alice_account.SerializeToString( ) expected_closed_swap_info = AtomicSwapInfo() expected_closed_swap_info.swap_id = SWAP_ID expected_closed_swap_info.amount = 200 expected_closed_swap_info.state = AtomicSwapInfo.CLOSED expected_closed_swap_info.secret_lock = SECRET_LOCK expected_closed_swap_info.secret_key = SECRET_KEY expected_closed_swap_info.is_initiator = True expected_closed_swap_info.sender_address = BOT_ADDRESS expected_closed_swap_info.receiver_address = ALICE_ADDRESS serialized_expected_closed_swap_info = expected_closed_swap_info.SerializeToString( ) expected_state = { ADDRESS_TO_STORE_SWAP_INFO_BY: serialized_expected_closed_swap_info, ALICE_ADDRESS: serialized_expected_alice_account, } AtomicSwapHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state( addresses=[ADDRESS_TO_STORE_SWAP_INFO_BY, ALICE_ADDRESS]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
def test_store_ecdsa_public_key(): """ Case: send transaction request to store certificate public key (ECDSA) for other. Expect: public key information is stored to blockchain linked to owner address. Transaction sender paid for storing. """ inputs = outputs = [ ADDRESS_FROM_ECDSA_PUBLIC_KEY, OWNER_ADDRESS, PAYER_ADDRESS, ZERO_ADDRESS, IS_NODE_ECONOMY_ENABLED_ADDRESS, ] new_public_key_payload = generate_ecdsa_payload(key=ECDSA_PUBLIC_KEY) serialized_new_public_key_payload = new_public_key_payload.SerializeToString() private_key = Secp256k1PrivateKey.from_hex(OWNER_PRIVATE_KEY) signature_by_owner = Secp256k1Context().sign(serialized_new_public_key_payload, private_key) new_public_key_store_and_pay_payload = NewPubKeyStoreAndPayPayload( pub_key_payload=new_public_key_payload, owner_public_key=bytes.fromhex(OWNER_PUBLIC_KEY), signature_by_owner=bytes.fromhex(signature_by_owner), ) transaction_payload = TransactionPayload() transaction_payload.method = PubKeyMethod.STORE_AND_PAY transaction_payload.data = new_public_key_store_and_pay_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = generate_header( serialized_transaction_payload, inputs, outputs, signer_public_key=PAYER_PUBLIC_KEY, ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer(private_key=PAYER_PRIVATE_KEY).sign(serialized_header), ) payer_account = Account() payer_account.balance = PAYER_INITIAL_BALANCE serialized_payer_account = payer_account.SerializeToString() owner_account = Account() owner_account.pub_keys.append(RANDOM_ALREADY_STORED_OWNER_PUBLIC_KEY_ADDRESS) serialized_owner_account = owner_account.SerializeToString() zero_account = Account() zero_account.balance = 0 serialized_zero_account = zero_account.SerializeToString() mock_context = StubContext(inputs=inputs, outputs=outputs, initial_state={ OWNER_ADDRESS: serialized_owner_account, PAYER_ADDRESS: serialized_payer_account, ZERO_ADDRESS: serialized_zero_account, }) expected_public_key_storage = PubKeyStorage() expected_public_key_storage.owner = OWNER_PUBLIC_KEY expected_public_key_storage.payload.CopyFrom(new_public_key_payload) expected_public_key_storage.is_revoked = False expected_serialized_public_key_storage = expected_public_key_storage.SerializeToString() expected_payer_account = Account() expected_payer_account.balance = PAYER_INITIAL_BALANCE - PUB_KEY_STORE_PRICE serialized_expected_payer_account = expected_payer_account.SerializeToString() expected_owner_account = Account() expected_owner_account.pub_keys.append(RANDOM_ALREADY_STORED_OWNER_PUBLIC_KEY_ADDRESS) expected_owner_account.pub_keys.append(ADDRESS_FROM_ECDSA_PUBLIC_KEY) serialized_expected_owner_account = expected_owner_account.SerializeToString() expected_zero_account = Account() expected_zero_account.balance = 0 + PUB_KEY_STORE_PRICE expected_serialized_zero_account = expected_zero_account.SerializeToString() expected_state = { OWNER_ADDRESS: serialized_expected_owner_account, PAYER_ADDRESS: serialized_expected_payer_account, ADDRESS_FROM_ECDSA_PUBLIC_KEY: expected_serialized_public_key_storage, ZERO_ADDRESS: expected_serialized_zero_account, } PubKeyHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state(addresses=[ OWNER_ADDRESS, PAYER_ADDRESS, ADDRESS_FROM_ECDSA_PUBLIC_KEY, ZERO_ADDRESS, ]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
def test_expire_atomic_swap(): """ Case: to expire atomic swap. Expect: increase bot address balance by swap amount. Leave commission on zero address. """ atomic_swap_expire_payload = AtomicSwapExpirePayload(swap_id=SWAP_ID, ) transaction_payload = TransactionPayload() transaction_payload.method = AtomicSwapMethod.EXPIRE transaction_payload.data = atomic_swap_expire_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = TransactionHeader( signer_public_key=BOT_PUBLIC_KEY, family_name=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get( 'family_name'), family_version=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get( 'family_version'), inputs=INPUTS, outputs=OUTPUTS, dependencies=[], payload_sha512=hash512(data=serialized_transaction_payload), batcher_public_key=RANDOM_NODE_PUBLIC_KEY, nonce=time.time().hex().encode(), ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer( private_key=BOT_PRIVATE_KEY).sign(serialized_header), ) bot_account = Account() bot_account.balance = 4700 serialized_bot_account = bot_account.SerializeToString() genesis_members_setting = Setting() genesis_members_setting.entries.add(key=SETTINGS_KEY_ZERO_ADDRESS_OWNERS, value=f'{BOT_PUBLIC_KEY},') serialized_genesis_members_setting = genesis_members_setting.SerializeToString( ) existing_swap_info = AtomicSwapInfo() existing_swap_info.swap_id = SWAP_ID existing_swap_info.state = AtomicSwapInfo.OPENED existing_swap_info.amount = TOKENS_AMOUNT_TO_SWAP existing_swap_info.created_at = CURRENT_TIMESTAMP // 2 existing_swap_info.sender_address = BOT_ADDRESS existing_swap_info.receiver_address = ALICE_ADDRESS existing_swap_info.is_initiator = True serialized_existing_swap_info = existing_swap_info.SerializeToString() mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ BLOCK_INFO_CONFIG_ADDRESS: SERIALIZED_BLOCK_INFO_CONFIG, BLOCK_INFO_ADDRESS: SERIALIZED_BLOCK_INFO, BOT_ADDRESS: serialized_bot_account, ADDRESS_TO_STORE_SWAP_INFO_BY: serialized_existing_swap_info, ADDRESS_TO_GET_GENESIS_MEMBERS_AS_STRING_BY: serialized_genesis_members_setting, }) expected_bot_account = Account() expected_bot_account.balance = 4700 + TOKENS_AMOUNT_TO_SWAP serialized_expected_bot_account = expected_bot_account.SerializeToString() expected_swap_info = AtomicSwapInfo() expected_swap_info.swap_id = SWAP_ID expected_swap_info.state = AtomicSwapInfo.EXPIRED expected_swap_info.amount = TOKENS_AMOUNT_TO_SWAP expected_swap_info.created_at = CURRENT_TIMESTAMP // 2 expected_swap_info.sender_address = BOT_ADDRESS expected_swap_info.receiver_address = ALICE_ADDRESS expected_swap_info.is_initiator = True serialized_expected_swap_info = expected_swap_info.SerializeToString() expected_state = { BOT_ADDRESS: serialized_expected_bot_account, ADDRESS_TO_STORE_SWAP_INFO_BY: serialized_expected_swap_info, } AtomicSwapHandler().apply(transaction=transaction_request, context=mock_context) state_as_list = mock_context.get_state(addresses=[ ADDRESS_TO_STORE_SWAP_INFO_BY, BOT_ADDRESS, ]) state_as_dict = {entry.address: entry.data for entry in state_as_list} assert expected_state == state_as_dict
def _store_pub_key(self, context, signer_pubkey, transaction_payload): """ Store public key to the blockchain. Flow on client: 1. Create private and public key (for instance, RSA). 2. Create random data and sign it with private key to allows node verify signature, so ensure the address sent transaction is a real owner of public key. 3. Send public key, signature, and other information to the node. Node does checks: if public key already exists in the blockchain, try to deserialize public key, try to verify signature, if validity exceeds. If transaction successfully passed checks, node charges fixed tokens price for storing public keys (if node economy is enabled) and link public key to the account (address). References: - https://docs.remme.io/remme-core/docs/family-pub-key.html - https://github.com/Remmeauth/remme-client-python/blob/develop/remme/remme_public_key_storage.py """ processor = self._get_public_key_processor(transaction_payload=transaction_payload) if not processor.verify(): raise InvalidTransaction('Invalid signature') public_key = processor.get_public_key() public_key_to_store_address = self.make_address_from_data(public_key) sender_account_address = AccountHandler().make_address_from_data(signer_pubkey) public_key_information, sender_account = get_multiple_data(context, [ (public_key_to_store_address, PubKeyStorage), (sender_account_address, Account), ]) if public_key_information: raise InvalidTransaction('This public key is already registered.') if not sender_account: sender_account = Account() if not self._is_public_key_validity_exceeded( valid_from=transaction_payload.valid_from, valid_to=transaction_payload.valid_to, ): raise InvalidTransaction('The public key validity exceeds the maximum value.') public_key_information = PubKeyStorage() public_key_information.owner = signer_pubkey public_key_information.payload.CopyFrom(transaction_payload) public_key_information.is_revoked = False state = { sender_account_address: sender_account, public_key_to_store_address: public_key_information, } charging_state = self._charge_for_storing(context=context, address_from=sender_account_address) if charging_state is not None: state.update(charging_state) sender_account = state.get(sender_account_address) sender_account = self._store_public_key_to_account( public_key_to_store_address=public_key_to_store_address, public_key_to_store_owner_account=sender_account, ) state.update({ sender_account_address: sender_account, }) return state
def _store_public_key_for_other(self, context, signer_pubkey, transaction_payload): """ Store public key for other account. The transaction for account which want to pay for other account public keys storing. A first account -> send payload -> A second account -> send transaction with first account's public key, but sign and pay for storing on own -> Remme-core. So Remme core charges tokens from a second account, but store a first account's public key. Public key owner here is a first account. Arguments: context (sawtooth_sdk.processor.context): context to store updated state (blockchain data). signer_pubkey: transaction sender public key. transaction_payload (pub_key_pb2.NewPubKeyStoreAndPayPayload): payload for storing public key for other. """ new_public_key_payload = transaction_payload.pub_key_payload owner_public_key_as_bytes = transaction_payload.owner_public_key owner_public_key_as_hex = owner_public_key_as_bytes.hex() owner_secp256k1_public_key = Secp256k1PublicKey.from_hex(owner_public_key_as_hex) is_owner_public_key_payload_signature_valid = Secp256k1Context().verify( signature=transaction_payload.signature_by_owner.hex(), message=new_public_key_payload.SerializeToString(), public_key=owner_secp256k1_public_key, ) if not is_owner_public_key_payload_signature_valid: raise InvalidTransaction('Public key owner\'s signature is invalid.') processor = self._get_public_key_processor(transaction_payload=transaction_payload.pub_key_payload) if not processor.verify(): raise InvalidTransaction('Payed public key has invalid signature.') public_key = processor.get_public_key() public_key_to_store_address = self.make_address_from_data(public_key) public_key_to_store_owner_address = AccountHandler().make_address_from_data(owner_public_key_as_hex) payer_for_storing_address = AccountHandler().make_address_from_data(signer_pubkey) public_key_information, public_key_to_store_owner_account, payer_for_storing_account = get_multiple_data(context, [ (public_key_to_store_address, PubKeyStorage), (public_key_to_store_owner_address, Account), (payer_for_storing_address, Account), ]) if public_key_information: raise InvalidTransaction('This public key is already registered.') if public_key_to_store_owner_account is None: public_key_to_store_owner_account = Account() if payer_for_storing_account is None: payer_for_storing_account = Account() if not self._is_public_key_validity_exceeded( valid_from=new_public_key_payload.valid_from, valid_to=new_public_key_payload.valid_to, ): raise InvalidTransaction('The public key validity exceeds the maximum value.') public_key_information = PubKeyStorage() public_key_information.owner = owner_public_key_as_hex public_key_information.payload.CopyFrom(new_public_key_payload) public_key_information.is_revoked = False state = { public_key_to_store_owner_address: public_key_to_store_owner_account, payer_for_storing_address: payer_for_storing_account, public_key_to_store_address: public_key_information, } charging_state = self._charge_for_storing(context=context, address_from=payer_for_storing_address) if charging_state is not None: state.update(charging_state) public_key_to_store_owner_account = self._store_public_key_to_account( public_key_to_store_address=public_key_to_store_address, public_key_to_store_owner_account=public_key_to_store_owner_account, ) state.update({ public_key_to_store_owner_address: public_key_to_store_owner_account, }) return state
class RestApiTestCase(HelperTestCase): @classmethod def setUpClass(cls): super().setUpClass(AccountHandler, AccountClient) flask_app = connexion.FlaskApp('remme.rest_api') flask_app.add_api( resource_filename('remme.rest_api', 'openapi.yml'), resolver=RestMethodsSwitcherResolver('remme.rest_api')) cls.client = flask_app.app.test_client() @staticmethod def create_raw_transaction_send_token_payload(pub_key_to, amount=1): client = AccountClient() signer = client._signer address = client.make_address_from_data(pub_key_to) node_address = client.get_user_address() transfer = TransferPayload() transfer.address_to = address transfer.value = amount tr = TransactionPayload() tr.method = AccountMethod.TRANSFER tr.data = transfer.SerializeToString() payload = tr.SerializeToString() header = TransactionHeader( signer_public_key=signer.get_public_key().as_hex(), family_name=client._family_handler.family_name, family_version=client._family_handler.family_versions[-1], inputs=[node_address, address], outputs=[node_address, address], dependencies=[], payload_sha512=hash512(payload), batcher_public_key=signer.get_public_key().as_hex(), nonce=time.time().hex().encode()).SerializeToString() signature = signer.sign(header) transaction = Transaction(header=header, payload=payload, header_signature=signature) return transaction @test def test_node_key_retrieve_info_and_it_ok(self): response = self.client.get('/api/v1/node_key') self.assertEqual(response.status_code, 200) self.assertTrue('pubkey' in response.get_json()) @test @mock.patch( 'remme.clients.basic.BasicClient.submit_batches', return_value={ 'link': 'http://rest-api:8080/batch_statuses?id=c6bcb01255c1870a5d42fe2dde5e91fb0c5992ec0b49932cdab901539bf977f75bb7699c053cea16668ba732a7d597dd0c2b80f157f1a2514932078bb761de4b' }) def test_valid_raw_transaction_send_to_the_node(self, req_mock): payload = self.create_raw_transaction_send_token_payload( '03823c7a9e285246985089824f3aaa51fb8675d08d84b151833ca5febce37ad61e', 1) response = self.client.post( '/api/v1/transaction', data=json.dumps({ 'transaction': base64.b64encode(payload.SerializeToString()).decode('utf-8') }), content_type='application/json') self.assertEqual(response.status_code, 200, 'Error: %s' % response.get_data()) self.assertTrue('batch_id' in response.get_json()) @test @mock.patch( 'remme.clients.basic.BasicClient.submit_batches', return_value={ 'link': 'http://rest-api:8080/batch_statuses?id=c6bcb01255c1870a5d42fe2dde5e91fb0c5992ec0b49932cdab901539bf977f75bb7699c053cea16668ba732a7d597dd0c2b80f157f1a2514932078bb761de4b' }) def test_token_send(self, req_mock): response = self.client.post( '/api/v1/token', data=json.dumps({ "pub_key_to": "03823c7a9e285246985089824f3aaa51fb8675d08d84b151833ca5febce37ad61e", "amount": 1 }), content_type='application/json') self.assertEqual(response.status_code, 200, 'Error: %s' % response.get_data()) self.assertTrue('batch_id' in response.get_json()) @test @mock.patch('remme.clients.basic.BasicClient.fetch_state', return_value={ 'data': base64.b64encode(Account(balance=100).SerializeToString()) }) @mock.patch('remme.clients.basic.BasicClient.get_root_block', return_value=(None, 'some_root')) def test_get_token_balance(self, root_mock, fetch_state_mock): pubkey = '03823c7a9e285246985089824f3aaa51fb8675d08d84b151833ca5febce37ad61a' response = self.client.get(f'/api/v1/token/{pubkey}') self.assertEqual(response.status_code, 200, 'Error: %s' % response.get_data()) self.assertEqual(response.get_json()['balance'], 100) @test @mock.patch('remme.clients.basic.BasicClient.get_batch_statuses', return_value={'data': [{ 'status': 'COMMITTED' }]}) def test_check_batch_status(self, batch_status_mock): batch_id = '3936f0fa13d008c2b00d04013dfa5e5359fccc117e4c47b1416ee24e115ac08b08707be3b3ce6956ca3d789d245ff0dddf7a39bc2b2f4210ffe81ebd0244c014' response = self.client.get(f'/api/v1/batch_status/{batch_id}') self.assertEqual(response.status_code, 200, 'Error: %s' % response.get_data()) resp = response.get_json() self.assertEqual(resp['batch_id'], batch_id) self.assertEqual(resp['status'], 'COMMITTED')
def test_atomic_swap_init_swap_not_enough_balance(): """ Case: initialize swap of bot's Remme node tokens to Alice's ERC20 Remme tokens with not enough bot address balance. Expect: invalid transaction error is raised with not enough balance error message. """ atomic_swap_init_payload = AtomicSwapInitPayload( receiver_address=ALICE_ADDRESS, sender_address_non_local=BOT_ETHEREUM_ADDRESS, amount=TOKENS_AMOUNT_TO_SWAP, swap_id=SWAP_ID, secret_lock_by_solicitor=BOT_IT_IS_INITIATOR_MARK, email_address_encrypted_by_initiator=ALICE_EMAIL_ADDRESS_ENCRYPTED_BY_INITIATOR, created_at=CURRENT_TIMESTAMP, ) transaction_payload = TransactionPayload() transaction_payload.method = AtomicSwapMethod.INIT transaction_payload.data = atomic_swap_init_payload.SerializeToString() serialized_transaction_payload = transaction_payload.SerializeToString() transaction_header = TransactionHeader( signer_public_key=BOT_PUBLIC_KEY, family_name=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get('family_name'), family_version=TRANSACTION_REQUEST_ACCOUNT_HANDLER_PARAMS.get('family_version'), inputs=INPUTS, outputs=OUTPUTS, dependencies=[], payload_sha512=hash512(data=serialized_transaction_payload), batcher_public_key=RANDOM_NODE_PUBLIC_KEY, nonce=time.time().hex().encode(), ) serialized_header = transaction_header.SerializeToString() transaction_request = TpProcessRequest( header=transaction_header, payload=serialized_transaction_payload, signature=create_signer(private_key=BOT_PRIVATE_KEY).sign(serialized_header), ) bot_account = Account() bot_account.balance = 0 serialized_bot_account_balance = bot_account.SerializeToString() swap_commission_setting = Setting() swap_commission_setting.entries.add(key=SETTINGS_SWAP_COMMISSION, value=str(SWAP_COMMISSION_AMOUNT)) serialized_swap_commission_setting = swap_commission_setting.SerializeToString() mock_context = StubContext(inputs=INPUTS, outputs=OUTPUTS, initial_state={ BLOCK_INFO_CONFIG_ADDRESS: SERIALIZED_BLOCK_INFO_CONFIG, BLOCK_INFO_ADDRESS: SERIALIZED_BLOCK_INFO, BOT_ADDRESS: serialized_bot_account_balance, ADDRESS_TO_GET_SWAP_COMMISSION_AMOUNT_BY: serialized_swap_commission_setting, }) with pytest.raises(InvalidTransaction) as error: AtomicSwapHandler().apply(transaction=transaction_request, context=mock_context) total_amount = TOKENS_AMOUNT_TO_SWAP + SWAP_COMMISSION_AMOUNT assert f'Not enough balance to perform the transaction in the amount (with a commission) {total_amount}.' \ == str(error.value)