def calculate_balance_after_payment(cls, balance, peer_balance, payment, hlock_to_rsmc=False, is_htlc_type=False): """ :param balance: payer's balance :param peer_balance: payee's balance :param payment: :param hlock_to_rsmc: exchange htlc lock part to rsmc transaction :return: """ # if payment has occurred, calculate the balance after payment payment = int(payment) if 0 < payment <= int(balance): if hlock_to_rsmc: return True, int(balance), int(peer_balance) + payment elif is_htlc_type: return True, int(balance) - payment, int(peer_balance) else: return True, int(balance) - payment, int( peer_balance) + payment elif payment > int(balance): raise GoTo( EnumResponseStatus. RESPONSE_TRADE_NO_ENOUGH_BALANCE_FOR_PAYMENT, 'Sender has not enough balance<{}> for payment<{}>'.format( balance, payment)) else: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_NEGATIVE_PAYMENT, 'Payment<{}> should not be negative number.'.format(payment))
def check_balance(cls, channel_name, asset_type, address, balance, peer_address, peer_balance, hlock_to_rsmc=False, is_htcl_type=False, **kwargs): """ :param channel_name: :param asset_type: :param address: :param balance: :param peer_address: :param peer_balance: :param hlock_to_rsmc: :param is_htcl_type: :param kwargs: :return: """ try: # get channel if trade has already existed channel_set = Channel.query_channel(channel_name)[0] expected_balance = int( channel_set.balance.get(address).get(asset_type)) expected_peer_balance = int( channel_set.balance.get(peer_address).get(asset_type)) # to calculate the balance after payment if kwargs and kwargs.__contains__('payment'): _, expected_balance, expected_peer_balance = \ cls.calculate_balance_after_payment(expected_balance, expected_peer_balance, kwargs.get('payment'), hlock_to_rsmc, is_htcl_type) if int(balance) == expected_balance and int( peer_balance) == expected_peer_balance: return True, expected_balance, expected_peer_balance else: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_BALANCE_UNMATCHED_BETWEEN_PEERS, 'Channel<{}> balances are unmatched between channel peers<self: {}, expected: {}. peers: {}, expected: {}>' \ .format(channel_name, balance, expected_balance, peer_balance, expected_peer_balance)) except Exception as error: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_BALANCE_ERROR, 'Channel<{}> not found or balance error. Exception: {}'.format( channel_name, error))
def sign_content(cls, wallet, sign_type_list, sign_value_list, *args, start=3, **kwargs): """ :param wallet: :param sign_type_list: :param sign_value_list: :param args: :param start: :param kwargs: :return: """ if not (wallet and sign_type_list and sign_value_list): raise GoTo( EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER, 'Lack of mandatory parameters: wallet<{}>, type_list<{}>, value_list<{}>' \ .format(wallet, sign_type_list, sign_value_list) ) kwargs.update({ 'typeList': sign_type_list, 'valueList': sign_value_list, 'privtKey': wallet._key.private_key_string }) return cls.contract_event_api().sign_content(start, *args, **kwargs)
def check_role(cls, role): # row index related check if int(role) not in [0, 1]: raise GoTo(EnumResponseStatus.RESPONSE_TRADE_INCORRECT_ROLE, 'RsmcSign with illegal role index<{}>'.format(role)) return True
def verify_hr(cls, hashcode, rcode): try: rcode = rcode.replace('0x', '').strip() return hashcode.__contains__(cls.hash_r(rcode).__str__()) except Exception as error: raise GoTo('Invalid RCode<{}> for HashR<{}>. Exception: {}'.format( rcode, hashcode, error))
def validate_transaction(self): """ :return: """ # check the trade with nonce existed or not try: pre_trade = Channel.query_trade(self.channel_name, self.nonce - 1) except Exception as error: # should never go here raise ( EnumResponseStatus.RESPONSE_TRADE_NOT_FOUND, 'Transaction with nonce<{}> is not found'.format(self.nonce - 1)) else: if pre_trade.state in [ EnumTradeState.confirmed.name or EnumTradeState.confirmed_onchain.name ]: return True elif pre_trade.state in [EnumTradeState.confirming.name] \ and EnumTradeType.TRADE_TYPE_HTLC.name == pre_trade.type \ and pre_trade.peer_commitment and pre_trade.peer_delay_commitment: return True raise GoTo( EnumResponseStatus. RESPONSE_TRADE_RESIGN_REQUEST_NOT_IMPLEMENTED, 'Wait for next resign. current confirmed nonce<{}>, request nonce<{}>' .format(Channel.latest_confirmed_nonce(), self.nonce))
def check_nonce(cls, nonce, channel_name='', is_founder=False): """ :param channel_name: :param nonce: :return: """ nonce = int(nonce) nego_trade = Channel.query_trade(channel_name, nonce) pre_trade = Channel.query_trade(channel_name, nonce - 1) # to check whether the nonce is legal one or not if not (TransactionBase._FOUNDER_NONCE < nonce and pre_trade.state in [ EnumTradeState.confirmed.name, EnumTradeState.confirmed_onchain.name ] and ( (nego_trade.state in [EnumTradeState.init.state] and is_founder) or (nego_trade.state in [EnumTradeState.confirming.state] and not is_founder))): raise GoTo( EnumResponseStatus.RESPONSE_TRADE_WITH_INCOMPATIBLE_NONCE, '{}::Channel<{}> has incompatible nonce<{}>, state<previous:{}, negotiated: {}>' \ .format(cls.__name__, channel_name, nonce, pre_trade.state, nego_trade.state)) else: return True, nego_trade
def check_router(self, router, hashcode): if not router: raise GoTo( EnumResponseStatus.RESPONSE_ROUTER_VOID_ROUTER, 'Router<{}> NOT found for htlc trade with HashR<{}>'.format( router, hashcode)) return True
def check_deposit(cls, founder_deposit: int, partner_deposit: int): if not (0 <= partner_deposit <= founder_deposit and 0 < founder_deposit): raise GoTo(EnumResponseStatus.RESPONSE_FOUNDER_DEPOSIT_LESS_THAN_PARTNER, 'Founder deposit<{}> should not be less than partner\'s deposit<{}>'. \ format(founder_deposit,partner_deposit)) return True
def handle(self): super(RsmcResponsesMessage, self).handle() status = EnumResponseStatus.RESPONSE_OK try: # check the response status if not self.check_response_status(self.status): self.rollback_resource(self.channel_name, self.nonce, self.payment, self.status) return # common check self.check_channel_state(self.channel_name) self.verify() self.check_role(self.role_index) if 0 == self.role_index: need_update_balance, rsmc_sign_body = self.handle_partner_response() elif 1 == self.role_index: need_update_balance, rsmc_sign_body = self.handle_founder_response() else: raise GoTo(EnumResponseStatus.RESPONSE_FAIL, 'Invalid Role<{}> of RsmcResponse'.format(self.role_index)) # send RsmcSign to peer if rsmc_sign_body: response_message = self.create_message_header(self.receiver, self.sender, self._message_name, self.channel_name, self.asset_type, self.nego_nonce or self.nonce) response_message.update({'MessageBody': rsmc_sign_body}) response_message.update({'Status': status.name}) self.send(response_message) is_htlc_to_rsmc = self.is_hlock_to_rsmc(self.hashcode) if is_htlc_to_rsmc: # update the htlc transaction state. Channel.confirm_payment(self.channel_name, self.hashcode, is_htlc_to_rsmc) # update the channel balance if need_update_balance: APIStatistics.update_statistics(self.wallet.address, payment=self.payment, payer=(0==self.role_index)) self.update_balance_for_channel(self.channel_name, self.asset_type, self.payer_address, self.payee_address, self.payment, is_htlc_to_rsmc) except GoTo as error: LOG.exception(error) status = error.reason except Exception as error: status = EnumResponseStatus.RESPONSE_EXCEPTION_HAPPENED LOG.exception('Failed to handle RsmcSign for channel<{}> nonce<{}>, role_index<{}>.Exception: {}' \ .format(self.channel_name, self.nonce, self.role_index, error)) finally: # send error response if EnumResponseStatus.RESPONSE_OK != status: if 0 == self.role_index: self.send_error_response(self.sender, self.receiver, self.channel_name, self.asset_type, self.nonce, status, kwargs={'RoleIndex': '1'}) # need rollback some resources self.rollback_resource(self.channel_name, self.nonce, self.payment, status=self.status) return
def check_channel_state(cls, channel_name): channel = Channel(channel_name) if not channel.is_opened: raise GoTo( EnumResponseStatus.RESPONSE_CHANNEL_NOT_OPENED, 'Channel is not OPENED. Current tate<{}>'.format( channel.state)) return channel
def check_hashcode_used(cls, channel_name, hashcode): try: trade_record = Channel.batch_query_trade( channel_name, filters={'hashcode': hashcode})[0] except Exception as error: return True else: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_HASHR_ALREADY_EXISTED, 'HashR<{}> already used by trade with nonce<{}>'.format( hashcode, trade_record.nonce))
def check_asset_type(cls, asset_type): """ :param asset_type: :return: """ if not IS_SUPPORTED_ASSET_TYPE(asset_type): raise GoTo(EnumResponseStatus.RESPONSE_ASSET_TYPE_NOT_SUPPORTED, 'Unsupported Asset type: \'{}\''.format(asset_type)) return True, None
def check_url_format(cls, wallet_url): """ :param wallet_url: :return: """ # TODO: it's better to use regex expression to check the url's validity if not wallet_url.__contains__('@'): raise GoTo( EnumResponseStatus.RESPONSE_INVALID_URL_FORMAT, 'Invalid format of url<{}>. Should be like 0xYYYY@ip:port'. format(wallet_url))
def handle(self): super(RResponse, self).handle() status = EnumResponseStatus.RESPONSE_OK try: self.check_channel_state(self.channel_name) self.check_rcode(self.hashcode, self.rcode) htlc_trade = self.get_htlc_trade_by_hashr(self.channel_name, self.hashcode) Channel.update_trade(self.channel_name, htlc_trade.nonce, rcode=self.rcode) # check payment: stored_payment = int(htlc_trade.payment) if stored_payment != self.payment: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_UNMATCHED_PAYMENT, 'Unmatched payment. self stored payment<{}>, peer payment<{}>' .format(stored_payment, self.payment)) # send response OK message RResponseAck.create(self.channel_name, self.asset_type, self.nonce, self.sender, self.receiver, self.hashcode, self.rcode, self.payment, self.comments, status) except TrinityException as error: LOG.error(error) status = error.reason except Exception as error: status = EnumResponseStatus.RESPONSE_EXCEPTION_HAPPENED LOG.error( 'Failed to handle RResponse with HashR<{}>. Exception: {}'. format(self.hashcode, error)) pass else: # trigger rsmc self.trigger_pay_by_rsmc() # notify rcode to next peer LOG.debug( 'notify the RResponse to next channel<{}>, current channel<{}>' .format(htlc_trade.channel, self.channel_name)) self.notify_rcode_to_next_peer(htlc_trade.channel) finally: if EnumResponseStatus.RESPONSE_OK != status: RResponseAck.send_error_response(self.sender, self.receiver, self.channel_name, self.asset_type, self.nonce, status) return None
def check_nonce(cls, nonce, **kwargs): """ :param nonce: :param kwargs: :return: """ if FounderBase._FOUNDER_NONCE != int(nonce): raise GoTo( EnumResponseStatus.RESPONSE_FOUNDER_WITH_ILLEGAL_NONCE, 'Founder with invalid nonce<{}>. Must be one'.format(nonce)) return True
def check_signature(cls, wallet, type_list, value_list, signature): """""" if not (wallet and type_list and value_list and signature): raise GoTo( EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER, 'Illegal input parameters: type_list<{}>, value_list<{}>, signature<{}>' \ .format(type_list, value_list, signature) ) sign_hash = cls.contract_event_api().solidity_hash( type_list, value_list) return wallet.address == wallet.recoverHash(sign_hash, signature=signature)
def check_rcode(cls, hashcode, rcode): """ :param hashcode: :param rcode: :return: """ if Payment.verify_hr(hashcode, rcode): return True else: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_HASHR_NOT_MATCHED_WITH_RCODE, 'HashR<{}> is not compatible with Rcode<{}>'.format( hashcode, rcode))
def check_network_magic(cls, magic): """ :param magic: :return: """ net_magic = get_magic() if net_magic != magic: raise GoTo( EnumResponseStatus.RESPONSE_INVALID_NETWORK_MAGIC, 'Invalid network ID<{}>. should be equal to {}'.format( magic, net_magic)) return True, net_magic
def check_signature(cls, wallet, expected, type_list, value_list, signature): """""" if not (wallet and type_list and value_list and signature): raise GoTo( EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER, 'Illegal input parameters: type_list<{}>, value_list<{}>, signature<{}>' \ .format(type_list, value_list, signature) ) sign_hash = cls.contract_event_api().solidity_hash( type_list, value_list) peer_wallet_address = wallet.recoverHash(sign_hash, signature) if expected == peer_wallet_address: return True else: LOG.error( 'Error data hash<{}> with value<{}> by signature<{}>'.format( binascii.b2a_hex(sign_hash), value_list, signature)) raise GoTo( EnumResponseStatus.RESPONSE_SIGNATURE_VERIFIED_ERROR, 'Signature verification error: expected<{}>, parsed-address<{}>' .format(expected, peer_wallet_address))
def check_nonce(cls, nonce, **kwargs): """ :param nonce: :param kwargs: :return: """ if SettleBase._SETTLE_NONCE != int(nonce): raise GoTo( EnumResponseStatus. RESPONSE_QUICK_CLOSE_CHANNEL_WITH_ILLEGAL_NONCE, 'Quick close channel with invalid nonce<{}>. Must be zero'. format(nonce)) return True
def check_nonce(cls, nonce, channel_name=''): """ :param channel_name: :param nonce: :return: """ new_nonce = Channel.new_nonce(channel_name) if not (TransactionBase._FOUNDER_NONCE < int(nonce) == new_nonce): raise GoTo( EnumResponseStatus.RESPONSE_TRADE_WITH_INCOMPATIBLE_NONCE, '{}::Channel<{}> has incompatible nonce<self: {}, peer: {}>'. format(cls.__name__, channel_name, new_nonce, nonce)) else: return True, new_nonce
def check_both_urls(cls, sender, receiver): """ :param sender: :param receiver: :return: """ # check whether the sender and receiver are correct url or not # TODO: it's better to use regex expression to check the url's validity try: return sender.__contains__('@') and receiver.__contains__('@') except Exception as error: LOG.error('Illegal URL is found. Exception: {}'.format(error)) raise GoTo(EnumResponseStatus.RESPONSE_INVALID_URL_FORMAT, 'Invalid format of sender<{}> or receiver<{}>. Should be like 0xYYYY@ip:port' \ .format(sender, receiver))
def exclude_wallet_from_router(cls, current_url, router): """ Description: remove current wallet url from the router :param router: router from messages :return: """ try: router_url = [jump[0] for jump in router] this_jump = router_url.index(current_url) except Exception as error: raise GoTo( EnumResponseStatus.RESPONSE_ROUTER_WITHOUT_CURRENT_WALLET, 'Why wallet<{}> receive this messages. Router<{}> without current wallet' .format(current_url, router)) else: return router[this_jump:], router[this_jump]
def validate_negotiated_nonce(self): """ :return: """ # to validate the negotiated nonce valid_trade, valid_nonce = Channel.latest_valid_trade( self.channel_name) nonce = self.nego_nonce or self.nonce if valid_nonce and valid_nonce + 1 == nonce: return valid_trade else: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_COULD_NOT_BE_OVERWRITTEN, 'Could not use negotiated nonce <{}>, current valid nonce<{}>'. format(self.nego_nonce, valid_nonce))
def get_htlc_trade_by_hashr(cls, channel_name, hashcode): """ :param channel_name: :param hashcode: :return: """ try: return Channel.batch_query_trade( channel_name, filters={ 'type': EnumTradeType.TRADE_TYPE_HTLC.name, 'hashcode': hashcode })[0] except Exception as error: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_HASHR_NOT_FOUND, 'Htlc trade not found by the HashR<{}> in channel<{}>'.format( hashcode, channel_name))
def check_payment(cls, channel_name, address, asset_type, payment): """ :param channel_name: :param address: :param asset_type: :param payment: :return: """ channel_balance = Channel(channel_name).balance balance = channel_balance.get(address).get(asset_type.upper()) if not 0 < int(payment) <= int(balance): raise GoTo( EnumResponseStatus. RESPONSE_TRADE_NO_ENOUGH_BALANCE_FOR_PAYMENT, 'Invalid payment<{}>, payer balance<{}>'.format( payment, balance)) return True, int(balance)
def get_rcode(cls, channel_name, hashcode): """ :param hashcode: :return: """ if cls.is_hlock_to_rsmc(hashcode): htlc_trade = Channel.batch_query_trade( channel_name, filters={ 'type': EnumTradeType.TRADE_TYPE_HTLC.name, 'hashcode': hashcode }) if not htlc_trade: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_HASHR_NOT_FOUND, 'Htlc trade with HashR<{}> NOT found'.format(hashcode)) htlc_trade = htlc_trade[0] return hashcode, htlc_trade.rcode else: return cls.get_default_rcode()
def handle(self): super(HtlcResponsesMessage, self).handle() status = EnumResponseStatus.RESPONSE_OK try: # check the response status if not self.check_response_status(self.status): self.rollback_resource(self.channel_name, self.nonce, self.payment, status=self.status) return self.check_channel_state(self.channel_name) self.check_router(self.router, self.hashcode) self.verify() # _, nonce = self.check_nonce(self.nonce + 1, self.channel_name) # _, payer_balance, payee_balance = self.check_balance( # self.channel_name, self.asset_type, self.receiver_address, self.sender_balance, # self.sender_address, self.receiver_balance, is_htcl_type=True, payment=self.payment) # To handle the founder and partner response by role index if 0 == self.role_index: need_update_balance, htlc_sign_body = self.handle_partner_response( ) elif 1 == self.role_index: need_update_balance, htlc_sign_body = self.handle_founder_response( ) else: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_INCORRECT_ROLE, 'Invalid Role<{}> of HtlcResponse'.format(self.role_index)) # send HtlcSign to peer if htlc_sign_body: response_message = self.create_message_header( self.receiver, self.sender, self._message_name, self.channel_name, self.asset_type, self.nego_nonce or self.nonce) response_message.update({'Router': self.router}) response_message.update({'MessageBody': htlc_sign_body}) response_message.update({'Status': status.name}) self.send(response_message) # update the channel balance if need_update_balance: self.update_balance_for_channel(self.channel_name, self.asset_type, self.payer_address, self.payee_address, self.payment, is_htlc_type=True) # now we need trigger htlc to next jump if 1 == self.role_index: # trigger htlc or RResponse self.trigger_htlc_to_next_jump() except TrinityException as error: LOG.exception(error) status = error.reason except Exception as error: LOG.exception( 'Transaction with none<{}> not found. Error: {}'.format( self.nonce, error)) status = EnumResponseStatus.RESPONSE_EXCEPTION_HAPPENED else: # successful action LOG.debug('Succeed to htlc for channel<{}>'.format( self.channel_name)) return finally: # rollback the resources self.rollback_resource(self.channel_name, self.nonce, self.payment, status=status.name)
def update_balance_for_channel(cls, channel_name, asset_type, payer_address, payee_address, payment, is_hlock_to_rsmc=False, is_htlc_type=False): """ :param channel_name: :param payer_address: :param payee_address: :param asset_type: :param payment: :param is_hlock_to_rsmc: :return: """ channel = Channel(channel_name) try: asset_type = asset_type.upper() channel_balance = channel.balance channel_hlock = channel.hlock # calculate the left balance payer_balance = channel_balance.get(payer_address).get(asset_type) payee_balance = channel_balance.get(payee_address).get(asset_type) if is_hlock_to_rsmc: payer_hlock = channel_hlock.get(payer_address).get(asset_type) payer_hlock = cls.big_number_calculate(payer_hlock, payment, False) if 0 > payer_hlock: raise GoTo( EnumResponseStatus. RESPONSE_TRADE_LOCKED_ASSET_LESS_THAN_PAYMENT, 'Why here? Payment<{}> should less than locked asset'. format(payment)) channel_hlock.update( {payer_address: { asset_type: str(payer_hlock) }}) payee_balance = cls.big_number_calculate( payee_balance, payment) elif is_htlc_type: payer_hlock = channel_hlock.get(payer_address).get(asset_type) payer_hlock = cls.big_number_calculate(payer_hlock, payment) channel_hlock.update( {payer_address: { asset_type: str(payer_hlock) }}) payer_balance = cls.big_number_calculate( payer_balance, payment, False) else: payer_balance = cls.big_number_calculate( payer_balance, payment, False) payee_balance = cls.big_number_calculate( payee_balance, payment) if int(payer_balance) >= 0 and int(payee_balance) >= 0: Channel.update_channel(channel_name, balance={ payer_address: { asset_type: str(payer_balance) }, payee_address: { asset_type: str(payee_balance) } }, hlock=channel_hlock) else: raise GoTo( EnumResponseStatus. RESPONSE_TRADE_NO_ENOUGH_BALANCE_FOR_PAYMENT, 'Payer has not enough balance for this payment<{}>'.format( payment)) except Exception as error: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_BALANCE_UPDATE_FAILED, 'Update channel<{}> balance error. payment<{}>. Exception: {}'. format(channel_name, payment, error))