Ejemplo n.º 1
0
class TransactionV146(BotTransactionBaseClass):
    _SPECIFIER = b'bot nametrans tx'

    def __init__(self):
        self._sender_botid = None
        self._receiver_botid = None
        self._names = []
        self._transaction_fee = None
        self._coin_inputs = None
        self._refund_coin_output = None
        self._sender_signature = None
        self._receiver_signature = None
        self._sender_parent_public_key = None
        self._receiver_parent_public_key = None

        super().__init__()

    @property
    def version(self):
        return TransactionVersion.THREEBOT_NAME_TRANSFER

    @property
    def required_bot_fees(self):
        """
        The fees required to pay for this 3Bot Name Transfer Transaction.
        """
        return BotTransactionBaseClass.BOT_FEE_ADDITIONAL_NAME * len(self.names)

    @property
    def sender_botid(self):
        if self._sender_botid is None:
            return 0
        return self._sender_botid
    @sender_botid.setter
    def sender_botid(self, value):
        if value is None:
            self._sender_botid = None
        elif isinstance(value, int):
            if value <= 0:
                raise ValueError("a (sender) bot identifier has to be at least equal to 1: {} is invalid".format(value))
            self._sender_botid = value
        else:
            raise TypeError("a (sender) bot identifier has to be an integer, cannot be of type {}".format(type(value)))

    @property
    def receiver_botid(self):
        if self._receiver_botid is None:
            return 0
        return self._receiver_botid
    @receiver_botid.setter
    def receiver_botid(self, value):
        if value is None:
            self._receiver_botid = None
        elif isinstance(value, int):
            if value <= 0:
                raise ValueError("a (receiver) bot identifier has to be at least equal to 1: {} is invalid".format(value))
            self._receiver_botid = value
        else:
            raise TypeError("a (receiver) bot identifier has to be an integer, cannot be of type {}".format(type(value)))

    @property
    def coin_inputs(self):
        """
        Coin inputs of this Transaction,
        used as funding for coin outputs, fees and any other kind of coin output.
        """
        return self._coin_inputs
    @coin_inputs.setter
    def coin_inputs(self, value):
        self._coin_inputs = []
        if not value:
            return
        for ci in value:
            self.coin_input_add(ci.parentid, ci.fulfillment, parent_output=ci.parent_output)

    @property
    def refund_coin_output(self):
        if self._refund_coin_output is None:
            return CoinOutput()
        return self._refund_coin_output

    @property
    def coin_outputs(self):
        """
        Empty list, or a singleton with the refund coin output if that one exists.
        """
        if self._refund_coin_output is None:
            return []
        return [self._refund_coin_output]
    @coin_outputs.setter
    def coin_outputs(self, value):
        if isinstance(value, list):
            lvalue = len(value)
            if lvalue == 0:
                value = None
            elif lvalue == 1:
                value = value[0]
            else:
                raise ValueError("ThreeBot only can have one coin output, a refund coin output")
        if value is None:
            self._refund_coin_output = None
        elif isinstance(value, CoinOutput):
            self._refund_coin_output = CoinOutput(value=value.value, condition=value.condition)
            self._refund_coin_output.id = value.id
        else:
            raise TypeError("cannot assign a value of type {} to coin outputs".format(type(value)))

    def coin_input_add(self, parentid, fulfillment, parent_output=None):
        ci = CoinInput(parentid=parentid, fulfillment=fulfillment)
        ci.parent_output = parent_output
        self._coin_inputs.append(ci)

    def refund_coin_output_set(self, value, condition, id=None):
        co = CoinOutput(value=value, condition=condition)
        co.id = id
        self._refund_coin_output = co
    
    @property
    def names(self):
        """
        Bot names that will be transfered from the sender- to the receiver 3Bot.
        """
        return self._names
    @names.setter
    def names(self, value):
        self._names_to_add = []
        if not value:
            return
        for name in value:
            self.name_add(name)

    def name_add(self, name):
        """
        Add a BotName that will be tranfered from the sender- to the receiver 3Bot.
        """
        if len(self._names) == BotTransactionBaseClass.MAX_NAMES_PER_BOT:
            raise Exception("a 3Bot can have a maximum of {} names, there is no more space for {} ({})".format(
                BotTransactionBaseClass.MAX_NAMES_PER_BOT, name, type(name)))
        self._names.append(BotName(value=name))

    @property
    def transaction_fee(self):
        if self._transaction_fee is None:
            return Currency()
        return self._transaction_fee
    @transaction_fee.setter
    def transaction_fee(self, txfee):
        if txfee is None:
            self._transaction_fee = None
        else:
            self._transaction_fee = Currency(value=txfee)

    @property
    def miner_fees(self):
        if self._transaction_fee is None:
            return []
        return [self._transaction_fee]

    @property
    def sender_signature(self):
        if self._sender_signature is None:
            return ED25519Signature(as_array=True)
        return self._sender_signature
    @sender_signature.setter
    def sender_signature(self, value):
        if value is None:
            self._sender_signature = None
            return
        self._sender_signature = ED25519Signature(value=value, as_array=True)

    @property
    def receiver_signature(self):
        if self._receiver_signature is None:
            return ED25519Signature(as_array=True)
        return self._receiver_signature
    @receiver_signature.setter
    def receiver_signature(self, value):
        if value is None:
            self._receiver_signature = None
            return
        self._receiver_signature = ED25519Signature(value=value, as_array=True)

    @property
    def sender_parent_public_key(self):
        if self._sender_parent_public_key is None:
            return PublicKey()
        return self._sender_parent_public_key
    @sender_parent_public_key.setter
    def sender_parent_public_key(self, value):
        if value is None:
            self._sender_parent_public_key = None
            return
        if not isinstance(value, PublicKey):
            raise TypeError("cannot assign value of type {} as BotNameTransferTransaction's sender parent public key (expected type: PublicKey)".format(type(value)))
        self._sender_parent_public_key = PublicKey(specifier=value.specifier, hash=value.hash)

    @property
    def receiver_parent_public_key(self):
        if self._receiver_parent_public_key is None:
            return PublicKey()
        return self._receiver_parent_public_key
    @receiver_parent_public_key.setter
    def receiver_parent_public_key(self, value):
        if value is None:
            self._receiver_parent_public_key = None
            return
        if not isinstance(value, PublicKey):
            raise TypeError("cannot assign value of type {} as BotNameTransferTransaction's receiver parent public key (expected type: PublicKey)".format(type(value)))
        self._receiver_parent_public_key = PublicKey(specifier=value.specifier, hash=value.hash)

    def signature_add(self, public_key, signature):
        """
        Implements SignatureCallbackBase.
        """
        unlockhash = public_key.unlockhash
        if unlockhash == self.sender_parent_public_key.unlockhash:
            self.sender_signature = signature
        elif unlockhash == self.receiver_parent_public_key.unlockhash:
            self.receiver_signature = signature
        else:
            raise ValueError("given public key (unlockhash: {}) is not linked to this BotNameTransfer Transaction".format(str(unlockhash)))

    def _signature_hash_input_get(self, *extra_objects):
        e = encoder_rivine_get()

        # encode the transaction version
        e.add_int8(self.version)

        # encode the specifier
        e.add_array(TransactionV146._SPECIFIER)

        # encode the bot identifiers
        e.add_int32(self.sender_botid)
        e.add_int32(self.receiver_botid)

        # extra objects if any
        if extra_objects:
            e.add_all(*extra_objects)

        # encode names
        e.add(self.names)

        # encode coin inputs
        e.add(len(self.coin_inputs))
        for ci in self.coin_inputs:
            e.add(ci.parentid)

        # encode transaction fee
        e.add(self.transaction_fee)

        # encode refund coin output
        if self._refund_coin_output is None:
            e.add_int8(0)
        else:
            e.add_int8(1)
            e.add(self._refund_coin_output)

        # return data
        return e.data

    def _id_input_compute(self):
        return bytearray(TransactionV146._SPECIFIER) + self._binary_encode_data()

    def _binary_encode_data(self):
        e = encoder_rivine_get()

        # get names
        names = self.names
        names_length = len(names)
        info_value = names_length

        # get has refund status
        has_refund = False
        if self._refund_coin_output is not None:
            has_refund = True
            info_value |= 16

        # encode sender bot info
        e.add_int32(self.sender_botid)
        e.add(self.sender_signature)

        # encode receiver bot info
        e.add_int32(self.receiver_botid)
        e.add(self.receiver_signature)

        # encode info value
        e.add_int8(info_value)

        # encode the names
        e.add_array(names)
        
        # encode transaction fee and coin inputs
        e.add_all(self.transaction_fee, self.coin_inputs)

        # encode refund coin output, if defined
        if has_refund:
            e.add(self._refund_coin_output)

        # return encoded data
        return e.data

    def _from_json_data_object(self, data):
        # decode sender info
        if 'sender' in data:
            bot_data = data['sender']
            self._sender_botid = int(bot_data.get('id', 0) or 0)
            self._sender_signature = ED25519Signature.from_json(bot_data.get('signature', None) or None)
        else:
            self._sender_botid = None
            self._sender_signature = None
        # decode receiver info
        if 'receiver' in data:
            bot_data = data['receiver']
            self._receiver_botid = int(bot_data.get('id', 0) or 0)
            self._receiver_signature = ED25519Signature.from_json(bot_data.get('signature', None) or None)
        else:
            self._receiver_botid = None
            self._receiver_signature = None
        # decode names
        self._names = [BotName.from_json(name) for name in data.get('names', []) or []]
        # decode transaction fee
        if 'txfee' in data:
            self._transaction_fee = Currency.from_json(data['txfee'])
        else:
            self._transaction_fee = None
        # decode coin inputs
        self._coin_inputs = [CoinInput.from_json(ci) for ci in data.get('coininputs', []) or []]
        # decode refund coin output
        if 'refundcoinoutput' in data:
            self._refund_coin_output = CoinOutput.from_json(data['refundcoinoutput'])
        else:
            self._refund_coin_output = None

    def _json_data_object(self):
        output = {
            'sender': {
                'id': self.sender_botid,
                'signature': self.sender_signature.json(),
            },
            'receiver': {
                'id': self.receiver_botid,
                'signature': self.receiver_signature.json(),
            },
            'names': [name.json() for name in self.names],
            'txfee': self.transaction_fee.json(),
            'coininputs': [ci.json() for ci in self.coin_inputs]
        }
        if self._refund_coin_output is not None:
            output['refundcoinoutput'] = self._refund_coin_output.json()
        return output

    def _extra_signature_requests_new(self):
        requests = []
        # collect, if possible, the sender request
        request = self._extra_signature_requests_for(self._sender_parent_public_key, self._sender_signature, BotTransactionBaseClass.SPECIFIER_SENDER)
        if request is not None:
            requests.append(request)
        # collect, if possible, the receiver request
        request = self._extra_signature_requests_for(self._receiver_parent_public_key, self._receiver_signature, BotTransactionBaseClass.SPECIFIER_RECEIVER)
        if request is not None:
            requests.append(request)
        # return all requests, if any
        return requests

    def _extra_signature_requests_for(self, public_key, signature, specifier):
        if public_key is None:
            # if no parent public key is defined, cannot do anything
            return None
        if signature is not None:
            return None
        # generate the input hash func
        factory = InputSignatureHashFactory(self, specifier)
        # define the input_hash_new generator function,
        # used to create the input hash for creating the signature
        unlockhash = public_key.unlockhash
        # create the only signature request
        return SignatureRequest(
            unlockhash=unlockhash,
            input_hash_gen=(lambda public_key: factory.signature_hash_new()),
            callback=self)

    def _extra_is_fulfilled(self):
        return self._sender_signature is not None and self._receiver_signature is not None
Ejemplo n.º 2
0
class TransactionV145(BotTransactionBaseClass):
    _SPECIFIER = b'bot recupdate tx'

    def __init__(self):
        self._botid = None
        self._addresses_to_add = []
        self._addresses_to_remove = []
        self._names_to_add = []
        self._names_to_remove = []
        self._number_of_months = 0
        self._transaction_fee = None
        self._coin_inputs = []
        self._refund_coin_output = None
        self._signature = None
        self._parent_public_key = None

        super().__init__()

    @property
    def version(self):
        return TransactionVersion.THREEBOT_RECORD_UPDATE

    @property
    def required_bot_fees(self):
        """
        The fees required to pay for this 3Bot Record Update Transaction.
        """
        fees = Currency(value=0)
        # all months have to be paid
        if self._number_of_months > 0:
            fees += BotTransactionBaseClass.compute_monthly_bot_fees(self._number_of_months)
        # if addresses have been modified, this has to be paid
        if len(self._addresses_to_add) > 0 or len(self._addresses_to_remove) > 0:
            fees += BotTransactionBaseClass.BOT_FEE_NETWORK_ADDRESS_UPDATE
        # each additional that is added, has to be paid as well
        lnames = len(self._names_to_add)
        if lnames > 0:
            fees += BotTransactionBaseClass.BOT_FEE_ADDITIONAL_NAME * lnames
        # return the total fees
        return fees

    @property
    def botid(self):
        if self._botid is None:
            return 0
        return self._botid
    @botid.setter
    def botid(self, value):
        if value is None:
            self._botid = None
        elif isinstance(value, int):
            if value <= 0:
                raise ValueError("a bot identifier has to be at least equal to 1: {} is invalid".format(value))
            self._botid = value
        else:
            raise TypeError("a bot identifier has to be an integer, cannot be of type {}".format(type(value)))

    @property
    def coin_inputs(self):
        """
        Coin inputs of this Transaction,
        used as funding for coin outputs, fees and any other kind of coin output.
        """
        return self._coin_inputs
    @coin_inputs.setter
    def coin_inputs(self, value):
        self._coin_inputs = []
        if not value:
            return
        for ci in value:
            self.coin_input_add(ci.parentid, ci.fulfillment, parent_output=ci.parent_output)

    @property
    def refund_coin_output(self):
        if self._refund_coin_output is None:
            return CoinOutput()
        return self._refund_coin_output

    @property
    def coin_outputs(self):
        """
        Empty list, or a singleton with the refund coin output if that one exists.
        """
        if self._refund_coin_output is None:
            return []
        return [self._refund_coin_output]
    @coin_outputs.setter
    def coin_outputs(self, value):
        if isinstance(value, list):
            lvalue = len(value)
            if lvalue == 0:
                value = None
            elif lvalue == 1:
                value = value[0]
            else:
                raise ValueError("ThreeBot only can have one coin output, a refund coin output")
        if value is None:
            self._refund_coin_output = None
        elif isinstance(value, CoinOutput):
            self._refund_coin_output = CoinOutput(value=value.value, condition=value.condition)
            self._refund_coin_output.id = value.id
        else:
            raise TypeError("cannot assign a value of type {} to coin outputs".format(type(value)))

    def coin_input_add(self, parentid, fulfillment, parent_output=None):
        ci = CoinInput(parentid=parentid, fulfillment=fulfillment)
        ci.parent_output = parent_output
        self._coin_inputs.append(ci)

    def refund_coin_output_set(self, value, condition, id=None):
        co = CoinOutput(value=value, condition=condition)
        co.id = id
        self._refund_coin_output = co

    @property
    def addresses_to_add(self):
        """
        Network addresses that will be added to the 3Bot's record.
        """
        return self._addresses_to_add
    @addresses_to_add.setter
    def addresses_to_add(self, value):
        self._addresses_to_add = []
        if not value:
            return
        for address in value:
            self.address_add(address)

    def address_add(self, address):
        """
        Add a NetworkAddress that will be added to the 3Bot's record.
        """
        if len(self._addresses_to_add) == BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT:
            raise Exception("a 3Bot can have a maximum of {} addresses, there is no more space for {} ({})".format(
                BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT, address, type(address)))
        self._addresses_to_add.append(NetworkAddress(address=address))
    
    @property
    def addresses_to_remove(self):
        """
        Network addresses that will be removed from the 3Bot's record.
        """
        return self._addresses_to_remove
    @addresses_to_remove.setter
    def addresses_to_remove(self, value):
        self._addresses_to_remove = []
        if not value:
            return
        for address in value:
            self.address_remove(address)

    def address_remove(self, address):
        """
        Add a NetworkAddress that will be removed from the 3Bot's record.
        """
        if len(self._addresses_to_remove) == BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT:
            raise Exception("a 3Bot can have a maximum of {} addresses, there is no more space for {} ({})".format(
                BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT, address, type(address)))
        self._addresses_to_remove.append(NetworkAddress(address=address))
    
    @property
    def names_to_add(self):
        """
        Bot names that will added to the 3Bot's record.
        """
        return self._names_to_add
    @names_to_add.setter
    def names_to_add(self, value):
        self._names_to_add = []
        if not value:
            return
        for name in value:
            self.name_add(name)

    def name_add(self, name):
        """
        Add a BotName that will be added to the 3Bot's record.
        """
        if len(self._names_to_add) == BotTransactionBaseClass.MAX_NAMES_PER_BOT:
            raise Exception("a 3Bot can have a maximum of {} names, there is no more space for {} ({})".format(
                BotTransactionBaseClass.MAX_NAMES_PER_BOT, name, type(name)))
        self._names_to_add.append(BotName(value=name))

    @property
    def names_to_remove(self):
        """
        Bot names that will be removed from the 3Bot's record
        """
        return self._names_to_remove
    @names_to_remove.setter
    def names_to_remove(self, value):
        self._names_to_remove = []
        if not value:
            return
        for name in value:
            self.name_remove(name)

    def name_remove(self, name):
        """
        Add a BotName that will be removed from the 3Bot's record.
        """
        if len(self._names_to_remove) == BotTransactionBaseClass.MAX_NAMES_PER_BOT:
            raise Exception("a 3Bot can have a maximum of {} names, there is no more space for {} ({})".format(
                BotTransactionBaseClass.MAX_NAMES_PER_BOT, name, type(name)))
        self._names_to_remove.append(BotName(value=name))

    @property
    def transaction_fee(self):
        if self._transaction_fee is None:
            return Currency()
        return self._transaction_fee
    @transaction_fee.setter
    def transaction_fee(self, txfee):
        if txfee is None:
            self._transaction_fee = None
        else:
            self._transaction_fee = Currency(value=txfee)

    @property
    def miner_fees(self):
        if self._transaction_fee is None:
            return []
        return [self._transaction_fee]

    @property
    def number_of_months(self):
        return self._number_of_months

    @number_of_months.setter
    def number_of_months(self, n):
        if n < 0 or n > 24:
            raise ValueError("number of months for a 3Bot Record Update has to be in the inclusive range [0,24]")
        self._number_of_months = n

    @property
    def signature(self):
        if self._signature is None:
            return ED25519Signature(as_array=True)
        return self._signature
    @signature.setter
    def signature(self, value):
        if value is None:
            self._signature = None
            return
        self._signature = ED25519Signature(value=value, as_array=True)

    @property
    def parent_public_key(self):
        if self._parent_public_key is None:
            return PublicKey()
        return self._parent_public_key
    @parent_public_key.setter
    def parent_public_key(self, value):
        if value is None:
            self._parent_public_key = None
            return
        if not isinstance(value, PublicKey):
            raise TypeError("cannot assign value of type {} as BotRecordUpdateTransactions's parent public key (expected type: PublicKey)".format(type(value)))
        self._parent_public_key = PublicKey(specifier=value.specifier, hash=value.hash)

    def signature_add(self, public_key, signature):
        """
        Implements SignatureCallbackBase.
        """
        if self._parent_public_key.unlockhash != public_key.unlockhash:
            raise ValueError("given public key ({}) does not equal parent public key ({})".format(
                str(self._parent_public_key.unlockhash), str(public_key.unlockhash)))
        self.signature = signature

    def _signature_hash_input_get(self, *extra_objects):
        e = encoder_rivine_get()

        # encode the transaction version
        e.add_int8(self.version)

        # encode the specifier
        e.add_array(TransactionV145._SPECIFIER)

        # encode the botID
        e.add_int32(self.botid)

        # extra objects if any
        if extra_objects:
            e.add_all(*extra_objects)

        # encode addresses, names and number of months
        e.add_all(self.addresses_to_add, self.addresses_to_remove)
        e.add_all(self.names_to_add, self.names_to_remove)
        e.add_int8(self.number_of_months)

        # encode coin inputs
        e.add(len(self.coin_inputs))
        for ci in self.coin_inputs:
            e.add(ci.parentid)

        # encode transaction fee
        e.add_all(self.transaction_fee)

        # encode refund coin output
        if self._refund_coin_output is None:
            e.add_int8(0)
        else:
            e.add_int8(1)
            e.add(self._refund_coin_output)

        # return data
        return e.data

    def _id_input_compute(self):
        return bytearray(TransactionV145._SPECIFIER) + self._binary_encode_data()

    def _binary_encode_data(self):
        e = encoder_rivine_get()

        # get addresses and names
        addresses_to_add = self.addresses_to_add
        addresses_to_remove = self.addresses_to_remove
        names_to_add = self.names_to_add
        names_to_remove = self.names_to_remove
        addresses_to_add_length = len(addresses_to_add)
        addresses_to_remove_length = len(addresses_to_remove)
        names_to_add_length = len(names_to_add)
        names_to_remove_length = len(names_to_remove)

        # encode the identifier
        e.add_int32(self.botid)

        # encode bot binary encoding prefix (containing length and refund info)
        maf = BotMonthsAndFlagsData(
            number_of_months=self.number_of_months,
            has_addresses=(addresses_to_add_length>0 or addresses_to_remove_length>0),
            has_names=(names_to_add_length>0 or names_to_remove_length>0),
            has_refund=(self._refund_coin_output is not None),
        )
        e.add(maf)

        # if we have addresses, encode it
        if maf.has_addresses:
            e.add_int8(addresses_to_add_length | (addresses_to_remove_length<< 4))
            e.add_array(addresses_to_add)
            e.add_array(addresses_to_remove)

        # if we have names, encode it
        if maf.has_names:
            e.add_int8(names_to_add_length | (names_to_remove_length<< 4))
            e.add_array(names_to_add)
            e.add_array(names_to_remove)
        
        # encode transaction fee and coin inputs
        e.add_all(self.transaction_fee, self.coin_inputs)

        # encode refund coin output, if defined
        if maf.has_refund:
            e.add(self._refund_coin_output)
    
        # encode the signature at the end
        e.add(self.signature)

        # return encoded data
        return e.data

    def _from_json_data_object(self, data):
        self._botid = int(data.get('id', 0) or 0)
        addresses = data.get('addresses', {}) or {}
        self._addresses_to_add = [NetworkAddress.from_json(address) for address in addresses.get('add', []) or []]
        self._addresses_to_remove = [NetworkAddress.from_json(address) for address in addresses.get('remove', []) or []]
        names = data.get('names', {}) or {}
        self._names_to_add = [BotName.from_json(name) for name in names.get('add', []) or []]
        self._names_to_remove = [BotName.from_json(name) for name in names.get('remove', []) or []]
        self._number_of_months = int(data.get('nrofmonths', 0) or 0)
        if 'txfee' in data:
            self._transaction_fee = Currency.from_json(data['txfee'])
        else:
            self._transaction_fee = None
        self._coin_inputs = [CoinInput.from_json(ci) for ci in data.get('coininputs', []) or []]
        if 'refundcoinoutput' in data:
            self._refund_coin_output = CoinOutput.from_json(data['refundcoinoutput'])
        else:
            self._refund_coin_output = None
        self._signature = ED25519Signature.from_json(data.get('signature', None) or None)

    def _json_data_object(self):
        output = {
            'id': self.botid,
            'nrofmonths': self.number_of_months,
            'txfee': self.transaction_fee.json(),
            'coininputs': [ci.json() for ci in self.coin_inputs],
            'signature': self.signature.json(),
        }
        # encode addresses
        addresses_to_add = self.addresses_to_add
        addresses_to_remove = self.addresses_to_remove
        addresses_to_add_length = len(addresses_to_add)
        addresses_to_remove_length = len(addresses_to_remove)
        if addresses_to_add_length > 0:
            output['addresses'] = {
                'add': [address.json() for address in addresses_to_add]
            }
        if addresses_to_remove_length > 0:
            d = output.get('addresses', {})
            d['remove'] = [address.json() for address in addresses_to_remove]
            output['addresses'] = d

        # encode names
        names_to_add = self.names_to_add
        names_to_remove = self.names_to_remove
        names_to_add_length = len(names_to_add)
        names_to_remove_length = len(names_to_remove)
        if names_to_add_length > 0:
            output['names'] = {
                'add': [name.json() for name in names_to_add]
            }
        if names_to_remove_length > 0:
            d = output.get('names', {})
            d['remove'] = [name.json() for name in names_to_remove]
            output['names'] = d

        # encode refund coin output
        if self._refund_coin_output is not None:
            output['refundcoinoutput'] = self._refund_coin_output.json()
        return output

    def _extra_signature_requests_new(self):
        if self._parent_public_key is None:
            # if no parent public key is defined, cannot do anything
            return []
        if self._signature is not None:
            return [] # nothing to do
        # generate the input hash func
        input_hash_func = InputSignatureHashFactory(self, BotTransactionBaseClass.SPECIFIER_SENDER).signature_hash_new
        # define the input_hash_new generator function,
        # used to create the input hash for creating the signature
        unlockhash = self._parent_public_key.unlockhash
        def input_hash_gen(public_key):
            return input_hash_func()
        # create the only signature request
        return [SignatureRequest(unlockhash=unlockhash, input_hash_gen=input_hash_gen, callback=self)]

    def _extra_is_fulfilled(self):
        return self._signature is not None
Ejemplo n.º 3
0
class TransactionV210(TransactionBaseClass, SignatureCallbackBase):
    _SPECIFIER = b'erc20 addrreg tx'

    HARDCODED_REGISTRATION_FEE = '10 TFT'
    SPECIFIER_REGISTRATION_SIGNATURE = BinaryData(value=b'registration',
                                                  fixed_size=0)

    def __init__(self):
        self._public_key = None
        self._signature = None
        self._registration_fee = Currency(
            value=TransactionV210.HARDCODED_REGISTRATION_FEE)
        self._transaction_fee = None
        self._coin_inputs = None
        self._refund_coin_output = None

        super().__init__()

    @property
    def version(self):
        return TransactionVersion.ERC20_ADDRESS_REGISTRATION

    @property
    def public_key(self):
        if self._public_key is None:
            return PublicKey()
        return self._public_key

    @public_key.setter
    def public_key(self, value):
        if value is None:
            self._public_key = None
            return
        if not isinstance(value, PublicKey):
            raise TypeError(
                "cannot assign value of type {} as BotRegistration's public key (expected type: PublicKey)"
                .format(type(value)))
        self._public_key = PublicKey(specifier=value.specifier,
                                     hash=value.hash)

    @property
    def signature(self):
        if self._signature is None:
            return ED25519Signature(as_array=True)
        return self._signature

    @signature.setter
    def signature(self, value):
        if value is None:
            self._signature = None
            return
        self._signature = ED25519Signature(value=value, as_array=True)

    @property
    def coin_inputs(self):
        """
        Coin inputs of this Transaction,
        used as funding for coin outputs, fees and any other kind of coin output.
        """
        return self._coin_inputs

    @coin_inputs.setter
    def coin_inputs(self, value):
        self._coin_inputs = []
        if not value:
            return
        for ci in value:
            self.coin_input_add(ci.parentid,
                                ci.fulfillment,
                                parent_output=ci.parent_output)

    @property
    def refund_coin_output(self):
        if self._refund_coin_output is None:
            return CoinOutput()
        return self._refund_coin_output

    @property
    def coin_outputs(self):
        """
        Empty list, or a singleton with the refund coin output if that one exists.
        """
        if self._refund_coin_output is None:
            return []
        return [self._refund_coin_output]

    @coin_outputs.setter
    def coin_outputs(self, value):
        if isinstance(value, list):
            lvalue = len(value)
            if lvalue == 0:
                value = None
            elif lvalue == 1:
                value = value[0]
            else:
                raise ValueError(
                    "ThreeBot only can have one coin output, a refund coin output"
                )
        if value is None:
            self._refund_coin_output = None
        elif isinstance(value, CoinOutput):
            self._refund_coin_output = CoinOutput(value=value.value,
                                                  condition=value.condition)
            self._refund_coin_output.id = value.id
        else:
            raise TypeError(
                "cannot assign a value of type {} to coin outputs".format(
                    type(value)))

    def coin_input_add(self, parentid, fulfillment, parent_output=None):
        ci = CoinInput(parentid=parentid, fulfillment=fulfillment)
        ci.parent_output = parent_output
        self._coin_inputs.append(ci)

    def refund_coin_output_set(self, value, condition, id=None):
        co = CoinOutput(value=value, condition=condition)
        co.id = id
        self._refund_coin_output = co

    @property
    def registration_fee(self):
        return self._registration_fee

    @registration_fee.setter
    def registration_fee(self, txfee):
        if txfee is not None:
            fee = Currency(value=txfee)
            if fee != self._registration_fee:
                raise ValueError(
                    "registration fee is hardcoded at {}, cannot be set to {}".
                    format(fee.str(with_unit=True),
                           self._registration_fee.str(with_unit=True)))

    @property
    def transaction_fee(self):
        if self._transaction_fee is None:
            return Currency()
        return self._transaction_fee

    @transaction_fee.setter
    def transaction_fee(self, txfee):
        if txfee is None:
            self._transaction_fee = None
        else:
            self._transaction_fee = Currency(value=txfee)

    @property
    def miner_fees(self):
        if self._transaction_fee is None:
            return []
        return [self._transaction_fee]

    def signature_add(self, public_key, signature):
        """
        Implements SignatureCallbackBase.
        """
        if self._public_key.unlockhash != public_key.unlockhash:
            raise ValueError(
                "given public key ({}) does not equal public key ({})".format(
                    str(self._public_key.unlockhash),
                    str(public_key.unlockhash)))
        self.signature = signature

    def _signature_hash_input_get(self, *extra_objects):
        e = encoder_rivine_get()

        # encode the transaction version
        e.add_int8(self.version)

        # encode the specifier
        e.add_array(TransactionV210._SPECIFIER)

        # encode the public key
        e.add_all(self.public_key)

        # extra objects if any
        if extra_objects:
            e.add_all(*extra_objects)

        # encode coin inputs
        e.add(len(self.coin_inputs))
        for ci in self.coin_inputs:
            e.add(ci.parentid)

        # encode registration and transaction fee
        e.add_all(self.registration_fee, self.transaction_fee)

        # encode refund coin output
        if self._refund_coin_output is None:
            e.add_int8(0)
        else:
            e.add_int8(1)
            e.add(self._refund_coin_output)

        # return data
        return e.data

    def _id_input_compute(self):
        return bytearray(
            TransactionV210._SPECIFIER) + self._binary_encode_data()

    def _binary_encode_data(self):
        e = encoder_rivine_get()
        # encode all easy properties
        e.add_all(self.public_key, self.signature, self.registration_fee,
                  self.transaction_fee, self.coin_inputs)
        # encode the only "pointer" property
        if self._refund_coin_output is None:
            e.add_int8(0)
        else:
            e.add_int8(1)
            e.add(self._refund_coin_output)

        # return encoded data
        return e.data

    def _from_json_data_object(self, data):
        # decode public key
        if 'pubkey' in data:
            self._public_key = PublicKey.from_json(data['pubkey'])
        else:
            self._public_key = None
        # decode signature
        if 'signature' in data:
            self._signature = ED25519Signature.from_json(data['signature'])
        else:
            self._signature = None
        # decode registration fee
        if 'regfee' in data:
            self.registration_fee = Currency.from_json(data['regfee'])
        else:
            self.registration_fee = None
        # decode transaction fee
        if 'txfee' in data:
            self._transaction_fee = Currency.from_json(data['txfee'])
        else:
            self._transaction_fee = None
        # decode coin inputs
        self._coin_inputs = [
            CoinInput.from_json(ci) for ci in data.get('coininputs', []) or []
        ]
        # decode refund coin output (if it exists)
        if 'refundcoinoutput' in data:
            self._refund_coin_output = CoinOutput.from_json(
                data['refundcoinoutput'])
        else:
            self._refund_coin_output = None

    def _json_data_object(self):
        tftaddress = self.public_key.unlockhash
        erc20address = ERC20Address.from_unlockhash(tftaddress)
        output = {
            'pubkey': self.public_key.json(),
            'tftaddress': tftaddress.json(),
            'erc20address': erc20address.json(),
            'signature': self.signature.json(),
            'regfee': self.registration_fee.json(),
            'txfee': self.transaction_fee.json(),
            'coininputs': [ci.json() for ci in self.coin_inputs],
        }
        if self._refund_coin_output is not None:
            output['refundcoinoutput'] = self._refund_coin_output.json()
        return output

    def _extra_signature_requests_new(self):
        if self._public_key is None:
            # if no parent public key is defined, cannot do anything
            return []
        if self._signature is not None:
            return []  # nothing to do
        # generate the input hash func
        input_hash_func = InputSignatureHashFactory(
            self, TransactionV210.SPECIFIER_REGISTRATION_SIGNATURE
        ).signature_hash_new
        # define the input_hash_new generator function,
        # used to create the input hash for creating the signature
        unlockhash = self._public_key.unlockhash

        def input_hash_gen(public_key):
            return input_hash_func()

        # create the only signature request
        return [
            SignatureRequest(unlockhash=unlockhash,
                             input_hash_gen=input_hash_gen,
                             callback=self)
        ]

    def _extra_is_fulfilled(self):
        return self._signature is not None
Ejemplo n.º 4
0
class TransactionV144(BotTransactionBaseClass):
    _SPECIFIER = b'bot register tx\0'

    def __init__(self):
        self._addresses = []
        self._names = []
        self._number_of_months = 0
        self._transaction_fee = None
        self._coin_inputs = []
        self._refund_coin_output = None
        self._public_key = None
        self._signature = None

        super().__init__()

    @property
    def version(self):
        return TransactionVersion.THREEBOT_REGISTRATION

    @property
    def required_bot_fees(self):
        """
        The fees required to pay for this 3Bot Registration Transaction.
        """
        # a static registration fee has to be paid
        fees = 0 + BotTransactionBaseClass.BOT_FEE_REGISTRATION
        # the amount of desired months also has to be paid
        fees += BotTransactionBaseClass.compute_monthly_bot_fees(self._number_of_months)
        # if more than one name is defined it also has to be paid
        lnames = len(self._names)
        if lnames > 1:
            fees += BotTransactionBaseClass.BOT_FEE_ADDITIONAL_NAME * (lnames-1)
        # no fee has to be paid for the used network addresses during registration
        # return the total fees
        return fees

    @property
    def coin_inputs(self):
        """
        Coin inputs of this Transaction,
        used as funding for coin outputs, fees and any other kind of coin output.
        """
        return self._coin_inputs
    @coin_inputs.setter
    def coin_inputs(self, value):
        self._coin_inputs = []
        if not value:
            return
        for ci in value:
            self.coin_input_add(ci.parentid, ci.fulfillment, parent_output=ci.parent_output)

    @property
    def refund_coin_output(self):
        if self._refund_coin_output is None:
            return CoinOutput()
        return self._refund_coin_output

    @property
    def coin_outputs(self):
        """
        Empty list, or a singleton with the refund coin output if that one exists.
        """
        if self._refund_coin_output is None:
            return []
        return [self._refund_coin_output]
    @coin_outputs.setter
    def coin_outputs(self, value):
        if isinstance(value, list):
            lvalue = len(value)
            if lvalue == 0:
                value = None
            elif lvalue == 1:
                value = value[0]
            else:
                raise ValueError("ThreeBot only can have one coin output, a refund coin output")
        if value is None:
            self._refund_coin_output = None
        elif isinstance(value, CoinOutput):
            self._refund_coin_output = CoinOutput(value=value.value, condition=value.condition)
            self._refund_coin_output.id = value.id
        else:
            raise TypeError("cannot assign a value of type {} to coin outputs".format(type(value)))

    def coin_input_add(self, parentid, fulfillment, parent_output=None):
        ci = CoinInput(parentid=parentid, fulfillment=fulfillment)
        ci.parent_output = parent_output
        self._coin_inputs.append(ci)

    def refund_coin_output_set(self, value, condition, id=None):
        co = CoinOutput(value=value, condition=condition)
        co.id = id
        self._refund_coin_output = co

    @property
    def addresses(self):
        """
        Network addresses that will be part of the 3Bot Registration.
        """
        return self._addresses
    @addresses.setter
    def addresses(self, value):
        self._addresses = []
        if not value:
            return
        for address in value:
            self.address_add(address)

    def address_add(self, address):
        """
        Add a NetworkAddress that will be added as part of the 3Bot Registration.
        """
        if len(self._addresses) == BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT:
            raise Exception("a 3Bot can have a maximum of {} addresses, there is no more space for {} ({})".format(
                BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT, address, type(address)))
        self._addresses.append(NetworkAddress(address=address))
    
    @property
    def names(self):
        """
        Bot names that will be part of the 3Bot Registration.
        """
        return self._names
    @names.setter
    def names(self, value):
        self._names = []
        if not value:
            return
        for name in value:
            self.name_add(name)

    def name_add(self, name):
        """
        Add a BotName that will be added as part of the 3Bot Registration.
        """
        if len(self._names) == BotTransactionBaseClass.MAX_NAMES_PER_BOT:
            raise Exception("a 3Bot can have a maximum of {} names, there is no more space for {} ({})".format(
                BotTransactionBaseClass.MAX_NAMES_PER_BOT, name, type(name)))
        self._names.append(BotName(value=name))

    @property
    def transaction_fee(self):
        if self._transaction_fee is None:
            return Currency()
        return self._transaction_fee
    @transaction_fee.setter
    def transaction_fee(self, txfee):
        if txfee is None:
            self._transaction_fee = None
        else:
            self._transaction_fee = Currency(value=txfee)

    @property
    def miner_fees(self):
        if self._transaction_fee is None:
            return []
        return [self._transaction_fee]

    @property
    def number_of_months(self):
        return self._number_of_months

    @number_of_months.setter
    def number_of_months(self, n):
        if n < 1 or n > 24:
            raise ValueError("number of months for a 3Bot Registration Transaction has to be in the inclusive range [1,24]")
        self._number_of_months = n
    
    @property
    def signature(self):
        if self._signature is None:
            return ED25519Signature(as_array=True)
        return self._signature
    @signature.setter
    def signature(self, value):
        if value is None:
            self._signature = None
            return
        self._signature = ED25519Signature(value=value, as_array=True)

    @property
    def public_key(self):
        if self._public_key is None:
            return PublicKey()
        return self._public_key
    @public_key.setter
    def public_key(self, value):
        if value is None:
            self._public_key = None
            return
        if not isinstance(value, PublicKey):
            raise TypeError("cannot assign value of type {} as BotRegistration's public key (expected type: PublicKey)".format(type(value)))
        self._public_key = PublicKey(specifier=value.specifier, hash=value.hash)

    def signature_add(self, public_key, signature):
        """
        Implements SignatureCallbackBase.
        """
        if self._public_key.unlockhash != public_key.unlockhash:
            raise ValueError("given public key ({}) does not equal public key ({})".format(
                str(self._public_key.unlockhash), str(public_key.unlockhash)))
        self.signature = signature

    def _signature_hash_input_get(self, *extra_objects):
        e = encoder_rivine_get()

        # encode the transaction version
        e.add_int8(self.version)

        # encode the specifier
        e.add_array(TransactionV144._SPECIFIER)

        # extra objects if any
        if extra_objects:
            e.add_all(*extra_objects)

        # encode addresses, names and number of months
        e.add_all(self.addresses, self.names)
        e.add_int8(self.number_of_months)

        # encode coin inputs
        e.add(len(self.coin_inputs))
        for ci in self.coin_inputs:
            e.add(ci.parentid)

        # encode transaction fee
        e.add_all(self.transaction_fee)

        # encode refund coin output
        if self._refund_coin_output is None:
            e.add_int8(0)
        else:
            e.add_int8(1)
            e.add(self._refund_coin_output)
        
        # encode public key
        e.add(self.public_key)

        # return data
        return e.data

    def _id_input_compute(self):
        return bytearray(TransactionV144._SPECIFIER) + self._binary_encode_data()

    def _binary_encode_data(self):
        e = encoder_rivine_get()

        # get addresses and names
        addresses = self.addresses
        names = self.names
        addresses_length = len(addresses)
        names_length = len(names)

        # encode bot binary encoding prefix (containing length and refund info)
        maf = BotMonthsAndFlagsData(
            number_of_months=self.number_of_months,
            has_addresses=(addresses_length>0),
            has_names=(names_length>0),
            has_refund=(self._refund_coin_output is not None),
        )
        e.add(maf)
        # encode the address and name length
        e.add_int8(addresses_length | (names_length<< 4))
        
        # encode all addresses and names
        e.add_array(addresses)
        e.add_array(names)
        
        # encode transaction fee and coin inputs
        e.add_all(self.transaction_fee, self.coin_inputs)

        # encode refund coin output, if defined
        if maf.has_refund:
            e.add(self._refund_coin_output)
    
        # encode the identification at the end
        e.add_all(self.public_key, self.signature)

        # return encoded data
        return e.data

    def _from_json_data_object(self, data):
        self._addresses = [NetworkAddress.from_json(address) for address in data.get('addresses', []) or []]
        self._names = [BotName.from_json(name) for name in data.get('names', []) or []]
        self._number_of_months = int(data.get('nrofmonths', 0) or 0)
        if 'txfee' in data:
            self._transaction_fee = Currency.from_json(data['txfee'])
        else:
            self._transaction_fee = None
        self._coin_inputs = [CoinInput.from_json(ci) for ci in data.get('coininputs', []) or []]
        if 'refundcoinoutput' in data:
            self._refund_coin_output = CoinOutput.from_json(data['refundcoinoutput'])
        else:
            self._refund_coin_output = None
        if 'identification' not in data or data['identification'] in (None, {}):
            self._public_key = None
            self._signature = None
        else:
            identification = data['identification']
            self._public_key = PublicKey.from_json(identification['publickey'])
            self._signature = ED25519Signature.from_json(identification['signature'], as_array=True)

    def _json_data_object(self):
        output = {
            'nrofmonths': self.number_of_months,
            'txfee': self.transaction_fee.json(),
            'coininputs': [ci.json() for ci in self.coin_inputs],
            'identification': {
                'publickey': self._public_key.json(),
                'signature': self._signature.json(),
            },
        }
        addresses = self.addresses
        if len(addresses) > 0:
            output['addresses'] = [address.json() for address in addresses]
        names = self.names
        if len(names) > 0:
            output['names'] = [name.json() for name in names]
        if self._refund_coin_output is not None:
            output['refundcoinoutput'] = self._refund_coin_output.json()
        return output

    def _extra_signature_requests_new(self):
        if self._public_key is None:
            # if no parent public key is defined, cannot do anything
            return []
        if self._signature is not None:
            return [] # nothing to do
        # generate the input hash func
        input_hash_func = InputSignatureHashFactory(self, BotTransactionBaseClass.SPECIFIER_SENDER).signature_hash_new
        # define the input_hash_new generator function,
        # used to create the input hash for creating the signature
        unlockhash = self._public_key.unlockhash
        def input_hash_gen(public_key):
            return input_hash_func()
        # create the only signature request
        return [SignatureRequest(unlockhash=unlockhash, input_hash_gen=input_hash_gen, callback=self)]

    def _extra_is_fulfilled(self):
        return self._signature is not None
Ejemplo n.º 5
0
class TransactionV208(TransactionBaseClass):
    _SPECIFIER = b'erc20 convert tx'

    def __init__(self):
        self._address = None
        self._value = None
        self._transaction_fee = None
        self._coin_inputs = []
        self._refund_coin_output = None

        super().__init__()

    @property
    def version(self):
        return TransactionVersion.ERC20_CONVERT

    @property
    def address(self):
        if self._address is None:
            return ERC20Address()
        return self._address

    @address.setter
    def address(self, value):
        if value is None:
            self._address = None
        else:
            self._address = ERC20Address(value=value)

    @property
    def value(self):
        if self._value is None:
            return Currency()
        return self._value

    @value.setter
    def value(self, value):
        if value is None:
            self._value = None
        else:
            self._value = Currency(value=value)

    @property
    def coin_inputs(self):
        """
        Coin inputs of this Transaction,
        used as funding for coin outputs, fees and any other kind of coin output.
        """
        return self._coin_inputs

    @coin_inputs.setter
    def coin_inputs(self, value):
        self._coin_inputs = []
        if not value:
            return
        for ci in value:
            self.coin_input_add(ci.parentid,
                                ci.fulfillment,
                                parent_output=ci.parent_output)

    @property
    def refund_coin_output(self):
        if self._refund_coin_output is None:
            return CoinOutput()
        return self._refund_coin_output

    @property
    def coin_outputs(self):
        """
        Empty list, or a singleton with the refund coin output if that one exists.
        """
        if self._refund_coin_output is None:
            return []
        return [self._refund_coin_output]

    @coin_outputs.setter
    def coin_outputs(self, value):
        if isinstance(value, list):
            lvalue = len(value)
            if lvalue == 0:
                value = None
            elif lvalue == 1:
                value = value[0]
            else:
                raise ValueError(
                    "ThreeBot only can have one coin output, a refund coin output"
                )
        if value is None:
            self._refund_coin_output = None
        elif isinstance(value, CoinOutput):
            self._refund_coin_output = CoinOutput(value=value.value,
                                                  condition=value.condition)
            self._refund_coin_output.id = value.id
        else:
            raise TypeError(
                "cannot assign a value of type {} to coin outputs".format(
                    type(value)))

    def coin_input_add(self, parentid, fulfillment, parent_output=None):
        ci = CoinInput(parentid=parentid, fulfillment=fulfillment)
        ci.parent_output = parent_output
        self._coin_inputs.append(ci)

    def refund_coin_output_set(self, value, condition, id=None):
        co = CoinOutput(value=value, condition=condition)
        co.id = id
        self._refund_coin_output = co

    @property
    def transaction_fee(self):
        if self._transaction_fee is None:
            return Currency()
        return self._transaction_fee

    @transaction_fee.setter
    def transaction_fee(self, txfee):
        if txfee is None:
            self._transaction_fee = None
        else:
            self._transaction_fee = Currency(value=txfee)

    @property
    def miner_fees(self):
        if self._transaction_fee is None:
            return []
        return [self._transaction_fee]

    def _signature_hash_input_get(self, *extra_objects):
        e = encoder_rivine_get()

        # encode the transaction version
        e.add_int8(self.version)

        # encode the specifier
        e.add_array(TransactionV208._SPECIFIER)

        # encode the address and value
        e.add_all(self.address, self.value)

        # extra objects if any
        if extra_objects:
            e.add_all(*extra_objects)

        # encode coin inputs
        e.add(len(self.coin_inputs))
        for ci in self.coin_inputs:
            e.add(ci.parentid)

        # encode transaction fee
        e.add(self.transaction_fee)

        # encode refund coin output
        if self._refund_coin_output is None:
            e.add_int8(0)
        else:
            e.add_int8(1)
            e.add(self._refund_coin_output)

        # return data
        return e.data

    def _id_input_compute(self):
        return bytearray(
            TransactionV208._SPECIFIER) + self._binary_encode_data()

    def _binary_encode_data(self):
        e = encoder_rivine_get()
        # encode all easy properties
        e.add_all(self.address, self.value, self.transaction_fee,
                  self.coin_inputs)
        # encode the only "pointer" property
        if self._refund_coin_output is None:
            e.add_int8(0)
        else:
            e.add_int8(1)
            e.add(self._refund_coin_output)

        # return encoded data
        return e.data

    def _from_json_data_object(self, data):
        # decode address
        if 'address' in data:
            self._address = ERC20Address.from_json(data['address'])
        else:
            self._address = None
        # decode value
        if 'value' in data:
            self._value = Currency.from_json(data['value'])
        else:
            self._value = None
        # decode transaction fee
        if 'txfee' in data:
            self._transaction_fee = Currency.from_json(data['txfee'])
        else:
            self._transaction_fee = None
        # decode coin inputs
        self._coin_inputs = [
            CoinInput.from_json(ci) for ci in data.get('coininputs', []) or []
        ]
        # decode refund coin output (if it exists)
        if 'refundcoinoutput' in data:
            self._refund_coin_output = CoinOutput.from_json(
                data['refundcoinoutput'])
        else:
            self._refund_coin_output = None

    def _json_data_object(self):
        output = {
            'address': self.address.json(),
            'value': self.value.json(),
            'coininputs': [ci.json() for ci in self.coin_inputs],
            'txfee': self.transaction_fee.json(),
        }
        if self._refund_coin_output is not None:
            output['refundcoinoutput'] = self._refund_coin_output.json()
        return output