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 handle(self): super(FounderResponsesMessage, self).handle() # check response status if not self.check_response_status(self.status): return try: # some checks and verification for founder message self.verify() self.check_nonce(self.nonce) founder_trade = Channel.query_trade(self.channel_name, self.nonce) self.check_signature(self.wallet, type_list=self._sign_type_list, value_list=[ self.channel_name, self.nonce, self.receiver_address, int(founder_trade.balance), self.sender_address, int(founder_trade.peer_balance) ], signature=self.commitment) # update the trade Channel.update_trade(self.channel_name, self.nonce, peer_commitment=self.commitment) except GoTo as error: LOG.error(error) except Exception as error: LOG.error( 'Exception occurred during creating channel<{}>. Exception: {}' .format(self.channel_name, error)) else: self.register_deposit_event()
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 register_quick_close_event(self, is_founder=True): """ :param is_founder: :return: """ try: channel_event = ChannelQuickSettleEvent(self.channel_name, is_founder) if is_founder: settle_trade = Channel.query_trade(self.channel_name, SettleBase._SETTLE_NONCE) # register arguments for execution action channel_event.register_args( EnumEventAction.EVENT_EXECUTE, self.receiver_address, self.channel_name, self.nonce, self.receiver_address, settle_trade.balance, self.sender_address, settle_trade.peer_balance, settle_trade.commitment, self.commitment, self.wallet._key.private_key_string) # register arguments for termination action channel_event.register_args(EnumEventAction.EVENT_TERMINATE, asset_type=self.asset_type) except Exception as error: LOG.error( 'Failed to regiser quick close channel event since {}'.format( error)) else: # register and trigger the event event_machine.register_event(self.channel_name, channel_event) event_machine.trigger_start_event(self.channel_name)
def register_deposit_event(self, is_founder=True): """ :param is_founder: :return: """ try: channel_event = ChannelDepositEvent(self.channel_name, self.wallet.address, is_founder) # get the transaction record of founder message founder_trade = Channel.query_trade(self.channel_name, FounderBase._FOUNDER_NONCE) # set some arguments by the role type self_address = self.receiver_address self_deposit = founder_trade.balance if is_founder: founder_address = self.receiver_address founder_deposit = founder_trade.balance founder_commitment = founder_trade.commitment partner_address = self.sender_address partner_deposit = founder_trade.peer_balance partner_commitment = founder_trade.peer_commitment else: founder_address = self.sender_address founder_deposit = founder_trade.peer_balance founder_commitment = founder_trade.peer_commitment partner_address = self.receiver_address partner_deposit = founder_trade.balance partner_commitment = founder_trade.commitment # register the preparation action for deposit event channel_event.register_args(EnumEventAction.EVENT_PREPARE, self_address, self_deposit, self.wallet._key.private_key_string) # add execution action for deposit event channel_event.register_args( EnumEventAction.EVENT_EXECUTE, self_address, self.channel_name, FounderBase._FOUNDER_NONCE, founder_address, founder_deposit, partner_address, partner_deposit, founder_commitment, partner_commitment, self.wallet._key.private_key_string) channel_event.register_args(EnumEventAction.EVENT_TERMINATE, asset_type=self.asset_type) except Exception as error: LOG.exception( 'Failed to register deposit event since {}'.format(error)) else: # register and trigger event event_machine.register_event(self.channel_name, channel_event) event_machine.trigger_start_event(self.channel_name) return
def rollback_resource(cls, channel_name, nonce, payment=None, status=None): """ :param channel_name: :param nonce: :param payment: :return: """ if status is not None and status != EnumResponseStatus.RESPONSE_OK.name: trade = Channel.query_trade(channel_name, nonce) if trade: Channel.delete_trade(channel_name, int(nonce))
def record_transaction(self, nonce, **update_args): """ :param nonce: :param update_args: :return: """ # check the trade with nonce existed or not try: new_trade = Channel.query_trade(self.channel_name, nonce) except Exception as error: new_trade = None # add new transaction if new_trade: Channel.update_trade(self.channel_name, nonce=nonce, **update_args) else: Channel.add_trade(self.channel_name, nonce=nonce, **update_args) return
def add_or_update_quick_settle_trade(cls, channel_name, expected_role, **kwargs): """ :param channel_name: :param expected_role: :param kwargs: :return: """ nonce = cls._SETTLE_NONCE try: settle_trade_db = Channel.query_trade(channel_name, nonce)[0] except Exception as error: LOG.debug( 'No quick settle is created before. Exception: {}'.format( error)) Channel.add_trade(channel_name, nonce=nonce, **kwargs) else: # check the role of the database: if settle_trade_db.role != expected_role.name: # update the settle database Channel.update_trade(channel_name, nonce, **kwargs)
def handle(self): super(SettleResponseMessage, self).handle() # check response status if not self.check_response_status(self.status): return try: # common check arguments self.verify() self.check_nonce(self.nonce) settle_trade = Channel.query_trade(self.channel_name, self.nonce) self.check_signature(self.wallet, self.sender_address, type_list=self._sign_type_list, value_list=[ self.channel_name, self.nonce, self.receiver_address, int(settle_trade.balance), self.sender_address, int(settle_trade.peer_balance) ], signature=self.commitment) # update the trade Channel.update_trade(self.channel_name, SettleResponseMessage._SETTLE_NONCE, peer_commitment=self.commitment) except GoTo as error: LOG.exception(error) except Exception as error: LOG.exception( 'Handle Settle Response error. Exception: {}'.format(error)) else: # register the event to quick close channel self.register_quick_close_event() return
def handle_partner_response(self): """ :return: response to send to peer """ # local variables need_update_balance = False # handle the resign body firstly resign_ack = None resign_trade = None if self.resign_body: resign_ack, resign_trade = self.handle_resign_body( self.wallet, self.channel_name, self.resign_body) # here if the peer not provide the signature, we need re-calculate the balance based on resign trade if not (self.commitment and self.delay_commitment): _, self.sender_balance, self.receiver_balance = self.calculate_balance_after_payment( resign_trade.balance, resign_trade.peer_balance, self.payment, is_htlc_type=True) # to check whether the negotiated nonce is legal or not self.validate_negotiated_nonce() # create htlc response message htlc_sign_body = self.response(self.sender_balance, self.receiver_balance, self.payment, self.hashcode, 1, self.delay_block) # get some common local variables sign_hashcode, sign_rcode = self.get_default_rcode() payer_balance = int(self.sender_balance) payee_balance = int(self.receiver_balance) payment = int(self.payment) # use this nonce for following message of current transaction nonce = self.nego_nonce or self.nonce # special case when nego-nonce is setting if nonce != self.nonce: # get previous record old_trade = Channel.query_trade(self.channel_name, self.nonce) # generate the htlc trade htlc_trade = Channel.htlc_trade( type=EnumTradeType.TRADE_TYPE_HTLC, role=EnumTradeRole.TRADE_ROLE_FOUNDER, asset_type=self.asset_type, balance=payer_balance, peer_balance=payee_balance, payment=payment, hashcode=self.hashcode, delay_block=old_trade.delay_block, channel=old_trade.channel) # # Update the trade or add new one self.record_transaction(nonce, **htlc_trade) # delete old one if nonce < self.nonce: Channel.delete_trade(self.channel_name, self.nonce) # Get current transaction record current_trade = Channel.query_trade(self.channel_name, nonce) if current_trade.commitment and current_trade.delay_commitment: # it means the htlc has already signed before. commitment = current_trade.commitment delay_commitment = current_trade.delay_commitment else: # start to sign this new transaction and save it # check balance self.check_balance(self.channel_name, self.asset_type, self.payer_address, payer_balance, self.payee_address, payee_balance, is_htcl_type=True, payment=payment) # sign the transaction with 2 parts: rsmc and htlc-locked part commitment = self.sign_content( self.wallet, RsmcMessage._sign_type_list, [ self.channel_name, nonce, self.payer_address, payer_balance, self.payee_address, payee_balance, sign_hashcode, sign_rcode ]) delay_commitment = self.sign_content( self.wallet, self._sign_type_list, [ self.channel_name, self.payer_address, self.payee_address, int(self.delay_block), int(payment), self.hashcode ], start=5) # update the transaction info Channel.update_trade(self.channel_name, nonce, commitment=commitment, delay_commitment=delay_commitment) # peer has already sign the new transaction with nonce if self.commitment and self.delay_commitment: # check the signature of rsmc part self.check_signature(self.wallet, self.sender_address, type_list=RsmcMessage._sign_type_list, value_list=[ self.channel_name, nonce, self.payer_address, int(self.sender_balance), self.payee_address, int(self.receiver_balance), sign_hashcode, sign_rcode ], signature=self.commitment) # check signature of htlc-lock part self.check_signature(self.wallet, self.sender_address, type_list=self._sign_type_list, value_list=[ self.channel_name, self.payer_address, self.payee_address, int(self.delay_block), int(payment), self.hashcode ], signature=self.delay_commitment) # update this trade confirmed state Channel.update_trade(self.channel_name, nonce, commitment=commitment, delay_commitment=delay_commitment, peer_commitment=self.commitment, peer_delay_commitment=self.delay_commitment, state=EnumTradeState.confirming.name) need_update_balance = True htlc_sign_body.update({ 'Commitment': commitment, 'DelayCommitment': delay_commitment }) # has resign response ?? if resign_ack: htlc_sign_body.update({'ResignBody': resign_ack}) return need_update_balance, htlc_sign_body
def handle_partner_response(self): """ :return: response to send to peer """ # local variables need_update_balance = False is_htlc_to_rsmc = self.is_hlock_to_rsmc(self.hashcode) # handle the resign body firstly resign_ack = None if self.resign_body: resign_ack, resign_trade = self.handle_resign_body(self.wallet, self.channel_name, self.resign_body) # here if the peer not provide the signature, we need re-calculate the balance if not self.commitment: _, self.sender_balance, self.receiver_balance = self.calculate_balance_after_payment( resign_trade.balance, resign_trade.peer_balance, self.payment, is_htlc_to_rsmc ) # to check whether the negotiated nonce is legal or not self.validate_negotiated_nonce() # create RSMC response message with RoleIndex==1 rsmc_sign_body = self.response(self.asset_type, self.payment, self.sender_balance, self.receiver_balance, 1, self.hashcode) # get some common local variables sign_hashcode, sign_rcode = self.get_rcode(self.channel_name, self.hashcode) payer_balance = int(self.sender_balance) payee_balance = int(self.receiver_balance) payment = int(self.payment) # use this nonce for following message of current transaction nonce = self.nego_nonce or self.nonce # if negotiated nonce by peer, delete the transaction with self.nonce if nonce != self.nonce: # generate the rsmc trade part rsmc_trade = Channel.rsmc_trade( type=EnumTradeType.TRADE_TYPE_RSMC, role=EnumTradeRole.TRADE_ROLE_FOUNDER, asset_type=self.asset_type, balance=payer_balance, peer_balance=payee_balance, payment=payment, hashcode=sign_hashcode, rcode=sign_rcode ) self.record_transaction(nonce, **rsmc_trade) if nonce < self.nonce: Channel.delete_trade(self.channel_name, self.nonce) # query the trade by nonce current_trade = Channel.query_trade(self.channel_name, nonce) if current_trade.commitment: # It means that the transaction is already signed during resign # previous transaction record. commitment = current_trade.commitment else: # start to sign this new transaction and save it # check balance self.check_balance(self.channel_name, self.asset_type, self.payer_address, payer_balance, self.payee_address, payee_balance, hlock_to_rsmc=is_htlc_to_rsmc, payment=payment) # sign the transaction commitment = RsmcResponsesMessage.sign_content( self.wallet, self._sign_type_list, [self.channel_name, nonce, self.payer_address, payer_balance, self.payee_address, payee_balance, sign_hashcode, sign_rcode] ) # update the transaction Channel.update_trade(self.channel_name, nonce, commitment=commitment) # peer has already sign the new transaction with nonce if self.commitment: # check the signature self.check_signature( self.wallet, self.sender_address, type_list=self._sign_type_list, value_list=[self.channel_name, nonce, self.payer_address, int(self.sender_balance), self.payee_address, int(self.receiver_balance), sign_hashcode, sign_rcode], signature=self.commitment ) # update this trade confirmed state Channel.update_trade( self.channel_name, nonce, peer_commitment=self.commitment, state=EnumTradeState.confirmed.name ) need_update_balance = True rsmc_sign_body.update({'Commitment': commitment}) # has resign response ?? if resign_ack: rsmc_sign_body.update({'ResignBody': resign_ack}) return need_update_balance, rsmc_sign_body
def handle_resign_body(cls, wallet, channel_name, resign_body): """""" if not resign_body: return None if not (wallet and channel_name): raise GoTo( EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER, 'Void wallet<{}> or Illegal channel name<{}>'.format( wallet, channel_name)) resign_nonce = int(resign_body.get('Nonce')) update_channel_db = {} update_trade_db = {} # get transaction by the resign_nonce resign_trade = Channel.query_trade(channel_name, resign_nonce) asset_type = resign_trade.asset_type channel = Channel(channel_name) self_address, _, _ = uri_parser(wallet.url) peer_address, _, _ = uri_parser(channel.peer_uri(wallet.url)) # Is this wallet sender side of this trade ????? if EnumTradeRole.TRADE_ROLE_PARTNER.name == resign_trade.role: payer_address = peer_address payee_address = self_address payer_balance = resign_trade.peer_balance payee_balance = resign_trade.balance elif EnumTradeRole.TRADE_ROLE_FOUNDER.name == resign_trade.role: payer_address = self_address payee_address = peer_address payer_balance = resign_trade.balance payee_balance = resign_trade.peer_balance else: raise GoTo( EnumResponseStatus.RESPONSE_DATABASE_ERROR_TRADE_ROLE, 'Error trade role<{}> for nonce<{}>'.format( resign_trade.role, resign_nonce)) if EnumTradeType.TRADE_TYPE_HTLC.name == resign_trade.type: rsmc_sign_hashcode, rsmc_sign_rcode = cls.get_default_rcode() elif EnumTradeType.TRADE_TYPE_RSMC.name == resign_trade.type: rsmc_sign_hashcode, rsmc_sign_rcode = resign_trade.hashcode, resign_trade.rcode else: raise GoTo( EnumResponseStatus.RESPONSE_TRADE_RESIGN_NOT_SUPPORTED_TYPE, 'Only support HTLC and RSMC resign. Current transaction type: {}' .format(resign_trade.type)) rsmc_list = [ channel_name, resign_nonce, payer_address, int(payer_balance), payee_address, int(payee_balance), rsmc_sign_hashcode, rsmc_sign_rcode ] # self commitment is None if not resign_trade.commitment: commitment = cls.sign_content(wallet, cls._rsmc_sign_type_list, rsmc_list) update_trade_db.update({'commitment': commitment}) # self lock commitment is none htlc_list = None if EnumTradeType.TRADE_TYPE_HTLC.name == resign_trade.type: htlc_list = [ channel_name, payer_address, payee_address, int(resign_trade.delay_block), int(resign_trade.payment), resign_trade.hashcode ] delay_commitment = cls.sign_content(wallet, cls._htlc_sign_type_list, htlc_list) update_trade_db.update({'delay_commitment': delay_commitment}) # check whether the trade need update or not if resign_trade.state not in [ EnumTradeState.confirmed.name, EnumTradeState.confirmed_onchain.name ]: peer_commitment = resign_body.get('Commitment') peer_delay_commitment = resign_body.get('DelayCommitment') # check peer signature cls.check_signature(wallet, peer_address, cls._rsmc_sign_type_list, rsmc_list, peer_commitment) # update transaction and channel balance update_trade_db.update({'peer_commitment': peer_commitment}) update_channel_db = { 'balance': { payer_address: { asset_type: payer_balance }, payee_address: { asset_type: payee_balance } } } # check the htlc part signature if EnumTradeType.TRADE_TYPE_HTLC.name == resign_trade.type: cls.check_signature(wallet, peer_address, cls._htlc_sign_type_list, htlc_list, peer_delay_commitment) update_trade_db.update( {'peer_delay_commitment': peer_delay_commitment}) update_trade_db.update( {'state': EnumTradeState.confirming.name}) # need update the hlock part if payer_address == self_address: channel = Channel(channel_name) channel_hlock = channel.hlock if channel_hlock and channel_hlock.get(payer_address): payer_hlock = int(resign_trade.payment) + int( channel_hlock.get(payer_address).get(asset_type)) channel_hlock.update( {payer_address: { asset_type: str(payer_hlock) }}) update_channel_db.update({'hlock': channel_hlock}) else: # RSMC type transaction update_trade_db.update( {'state': EnumTradeState.confirmed.name}) # update transaction Channel.update_trade(channel_name, resign_nonce, **update_trade_db) # update Channel balance to new one Channel.update_channel(channel_name, **update_channel_db) # return resign message body return cls.create_resign_message_body(wallet.url, channel_name, resign_nonce, True)
def create_resign_message_body(cls, wallet_url, channel_name, nonce, is_resign_response=False): """ :param wallet_url: current opened wallet url :param channel_name: :param nonce: :param is_resign_response: if True, it means has received the peer resign request :return: """ if not (channel_name and isinstance(nonce, int)): raise GoTo( EnumResponseStatus.RESPONSE_ILLEGAL_INPUT_PARAMETER, 'Illegal parameters: channel_name<{}> or nonce<{}, type:{}>'. format(channel_name, nonce, type(nonce))) # record the nonce negotiated by partner of the transaction with nonce # required is True if is_resign_response: nego_nonce = nonce pre_nonce = nonce pre_trade = Channel.query_trade(channel_name, nego_nonce) else: pre_trade, pre_nonce = Channel.latest_valid_trade(channel_name) nego_nonce = pre_nonce + 1 if isinstance(pre_nonce, int) else None # does founder practise fraud ??? if not (pre_trade and nego_nonce) or cls._FOUNDER_NONCE >= nego_nonce: raise GoTo(EnumResponseStatus.RESPONSE_TRADE_WITH_INCORRECT_NONCE, 'Could not finish this transaction because ') # initialize some local variables trade_role = pre_trade.role trade_type = pre_trade.type trade_state = pre_trade.state # local variables resign_body = {'Nonce': str(pre_nonce)} need_update_balance = False # is partner role of this transaction if EnumTradeRole.TRADE_ROLE_PARTNER.name == trade_role and EnumTradeState.confirming.name == trade_state: # update the resign_body by the different trade type if EnumTradeType.TRADE_TYPE_RSMC.name == trade_type: if pre_trade.peer_commitment: # update the trade to confirmed state directly Channel.update_trade(channel_name, pre_nonce, state=EnumTradeState.confirmed.name) # need update channel balance # need_update_balance = True else: # need resign this RSMC transaction by peer resign_body.update({'Commitment': pre_trade.commitment}) elif EnumTradeType.TRADE_TYPE_HTLC.name == trade_type: if pre_trade.peer_commitment and pre_trade.peer_delay_commitment: # update the trade to confirmed state directly Channel.update_trade(channel_name, pre_nonce, state=EnumTradeState.confirming.name) # need update channel balance # need_update_balance = True else: # need resign this HTLC transaction by peer resign_body.update({'Commitment': pre_trade.commitment}) resign_body.update( {'DelayCommitment': pre_trade.delay_commitment}) else: LOG.error( 'Unsupported trade type<{}> to resign in partner<{}> side'. format(trade_type, wallet_url)) return None, pre_trade # need update the channel balance or not??? # if need_update_balance: # channel = Channel(channel_name) # self_address, _, _ = uri_parser(wallet_url) # peer_address, _, _ = uri_parser(channel.peer_uri(wallet_url)) # # # ToDo: if need, here need add asset type check in future # Channel.update_channel(channel_name, balance={ # self_address: {pre_trade.asset_type: pre_trade.balance}, # peer_address: {pre_trade.asset_type: pre_trade.peer_balance} # }) elif is_resign_response is True and EnumTradeRole.TRADE_ROLE_FOUNDER.name == trade_role and \ (EnumTradeState.confirmed.name == trade_state or (EnumTradeState.confirming.name == trade_state and EnumTradeType.TRADE_TYPE_HTLC.name == trade_type)): if EnumTradeType.TRADE_TYPE_RSMC.name == trade_type: if pre_trade.peer_commitment: resign_body.update({'Commitment': pre_trade.commitment}) else: # stop transaction ???? pass elif EnumTradeType.TRADE_TYPE_HTLC.name == trade_type: if pre_trade.peer_commitment and pre_trade.peer_delay_commitment: resign_body.update({'Commitment': pre_trade.commitment}) resign_body.update( {'DelayCommitment': pre_trade.delay_commitment}) else: LOG.error( 'Unsupported trade type<{}> to resign in founder<{}> side'. format(trade_type, wallet_url)) return None, pre_trade else: return None, pre_trade # Previous transaction need to be resigned by peer if 'Commitment' in resign_body: return resign_body, pre_trade return None, pre_trade