Example #1
0
class Spool(object):
    FEE = 30000
    TOKEN = 3000

    """
    Class that contains all Spool methods.

    In the SPOOL implementation there is no notion of users only addresses.
    All addresses come from BIP32 HD wallets. This makes it easier to manage all the keys
    since we can retrieve everything we need from a master secret (namely the private key
    to sign the transactions).

    Since we are dealing with HD wallets we expect all from_address to be a tuple of (path, address)
    so that we can retrieve the private key for that particular leaf address.
    If we want to use the root address we can just pass an empty string to the first element of the
    tuple e.g. ('', address). For instance when using the federation wallet address we have no
    need to create leaf addresses.

    A file is represented by two hashes:
        - file_hash: is the hash of the digital file
        - file_hash_metadata: is the hash of the digital file + metadata
    The hash is passed to the methods has a tuple (file_hash, file_hash_metadata)
    """

    def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''):
        """

        :param testnet:
        :return:
        """
        self.testnet = testnet
        self._netcode = 'XTN' if testnet else 'BTC'
        self._t = Transactions(service=service, testnet=testnet, username=username,
                               password=password, host=host, port=port)
        # simple cache for spent outputs. Useful so that rapid firing transactions don't use the same outputs
        self._spents = Queue(maxsize=50)

    @dispatch
    def register_piece(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True):
        """
        Register a piece

        :param from_address: Federation address. All register transactions originate from the the Federation wallet
        :param to_address: Address registering the edition
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Federation wallet password. For signing the transaction
        :param edition_num: The number of the edition to register. User edition_num=0 to register the master edition
        :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb()
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.piece,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def register(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Register an edition or master edition of a piece

        :param from_address: Federation address. All register transactions originate from the the Federation wallet
        :param to_address: Address registering the edition
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Federation wallet password. For signing the transaction
        :param edition_num: The number of the edition to register. User edition_num=0 to register the master edition
        :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.register,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def consigned_registration(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True):
        """
        Register an edition or master edition of a piece consigned to from_address

        :param from_address: Federation address. All register transactions originate from the the Federation wallet
        :param to_address: Address registering the edition
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Federation wallet password. For signing the transaction
        :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb()
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.consigned_registration,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def editions(self, from_address, to_address, hash, password, num_editions, min_confirmations=6, sync=False, ownership=True):
        """
        Register the number of editions of a piece

        :param from_address: Federation address. All register transactions originate from the the Federation wallet
        :param to_address: Address registering the number of editions
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password:  Federation wallet password. For signing the transaction
        :param num_editions: Number of editions of the piece
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(num_editions=num_editions)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.editions,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def transfer(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Transfer a piece between addresses

        :param from_address: Address currently owning the edition
        :param to_address: Address to receive the edition
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Password for the wallet currently owning the edition. For signing the transaction
        :param edition_num: the number of the edition to transfer
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.transfer,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def consign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Consign a piece to an address

        :param from_address: Address currently owning the edition
        :param to_address: Address to where the piece will be consigned to
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Password for the wallet currently owning the edition. For signing the transaction
        :param edition_num: the number of the edition to consign
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.consign,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def unconsign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Unconsign the edition

        :param from_address: Address where the edition is currently consigned
        :param to_address: Address that consigned the piece to from_address
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Password for the wallet currently holding the edition. For signing the transaction
        :param edition_num: the number of the edition to unconsign
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        # In an unconsignment the to_address needs to be the address that created the consign transaction
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.unconsign,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def loan(self, from_address, to_address, hash, password, edition_num, loan_start, loan_end, min_confirmations=6, sync=False, ownership=True):
        """
        Loan the edition

        :param from_address: Address currently holding the edition
        :param to_address: Address to loan the edition to
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Password for the wallet currently holding the edition. For signing the transaction
        :param edition_num: the number of the edition to unconsign
        :param loan_start: Start date for the loan. In the form YYMMDD
        :param loan_end: End date for the loan. In the form YYMMDD
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num, loan_start=loan_start, loan_end=loan_end)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.loan,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def migrate(self, from_address, prev_address, new_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Migrate an edition

        :param from_address: Federation address. All register transactions originate from the the Federation wallet
        :param to_address: Address registering the edition
        :param hash: Hash of the piece. Tuple (file_hash, file_hash_metadata)
        :param password: Federation wallet password. For signing the transaction
        :param edition_num: The number of the edition to register. User edition_num=0 to register the master edition
        :param min_confirmations: Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :param ownership: Check ownsership in the blockchain before pushing the transaction. Defaults to True
        :return: transaction id
        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, prev_address, new_address],
                                                    op_return=verb.migrate,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def refill_main_wallet(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False):
        """
        Refill the Federation wallet with tokens and fees. This keeps the federation wallet clean.
        Dealing with exact values simplifies the transactions. No need to calculate change. Easier to keep track of the
        unspents and prevent double spends that would result in transactions being rejected by the bitcoin network.

        :param from_address: Refill wallet address. Refills the federation wallet with tokens and fees
        :param to_address: Federation wallet address
        :param nfees: Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions
        :param ntokens: Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain
        :param password: Password for the Refill wallet. Used to sign the transaction
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :return: transaction id
        """
        path, from_address = from_address
        unsigned_tx = self._t.simple_transaction(from_address,
                                                 [(to_address, self.FEE)] * nfees + [(to_address, self.TOKEN)] * ntokens,
                                                 min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def refill(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False):
        """
        Refill wallets with the necessary fuel to perform spool transactions

        :param from_address: Federation wallet address. Fuels the wallets with tokens and fees. All transactions to wallets
                holding a particular piece should come from the Federation wallet
        :param to_address: Wallet address that needs to perform a spool transaction
        :param nfees: Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions
        :param ntokens: Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain
        :param password: Password for the Federation wallet. Used to sign the transaction
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :param sync: Perform the transaction in synchronous mode, the call to the function will block until there is at
        least on confirmation on the blockchain. Defaults to False
        :return: transaction id
        """
        path, from_address = from_address
        verb = Spoolverb()
        # nfees + 1: nfees to refill plus one fee for the refill transaction itself
        inputs = self.select_inputs(from_address, nfees + 1, ntokens, min_confirmations=min_confirmations)
        outputs = [{'address': to_address, 'value': self.TOKEN}] * ntokens
        outputs += [{'address': to_address, 'value': self.FEE}] * nfees
        outputs += [{'script': self._t._op_return_hex(verb.fuel), 'value': 0}]
        unsigned_tx = self._t.build_transaction(inputs, outputs)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    def simple_spool_transaction(self, from_address, to, op_return, min_confirmations=6):
        """
        Utililty function to create the spool transactions. Selects the inputs, encodes the op_return and
        constructs the transaction.

        :param from_address: Address originating the the transaction
        :param to: list of addresses to receive tokens (file_hash, file_hash_metadata, ...)
        :param op_return: String representation of the spoolverb, as returned by the properties of Spoolverb
        :param min_confirmations: Number of confirmations when chosing the inputs of the transaction. Defaults to 6
        :return: unsigned transaction
        """
        # list of addresses to send
        ntokens = len(to)
        nfees = self._t.estimate_fee(ntokens, 2) / self.FEE
        inputs = self.select_inputs(from_address, nfees, ntokens, min_confirmations=min_confirmations)
        # outputs
        outputs = [{'address': to_address, 'value': self.TOKEN} for to_address in to]
        outputs += [{'script': self._t._op_return_hex(op_return), 'value': 0}]
        # build transaction
        unsigned_tx = self._t.build_transaction(inputs, outputs)
        return unsigned_tx

    def select_inputs(self, address, nfees, ntokens, min_confirmations=6):
        # selects the inputs for the spool transaction
        unspents = self._t.get(address, min_confirmations=min_confirmations)['unspents']
        unspents = filter(lambda d: d not in self._spents.queue, unspents)
        if len(unspents) == 0:
            raise Exception("No spendable outputs found")

        fees = filter(lambda d: d['amount'] == self.FEE, unspents)[:nfees]
        tokens = filter(lambda d: d['amount'] == self.TOKEN, unspents)[:ntokens]
        if len(fees) != nfees or len(tokens) != ntokens:
            raise SpoolFundsError("Not enough outputs to spend. Refill your wallet")
        if self._spents.qsize() > 50 - (nfees + ntokens):
            [self._spents.get() for i in range(self._spents.qsize() + nfees + ntokens - 50)]
        [self._spents.put(fee) for fee in fees]
        [self._spents.put(token) for token in tokens]
        return fees + tokens
Example #2
0
class Spool(object):
    """
    Class that contains all Spool methods.

    In the SPOOL implementation there is no notion of users only addresses.
    All addresses come from BIP32 HD wallets. This makes it easier to manage all the keys
    since we can retrieve everything we need from a master secret (namely the private key
    to sign the transactions).

    Since we are dealing with HD wallets we expect all ``from_address`` to be a
    tuple of ``(path, address)`` so that we can retrieve the private key for
    that particular leaf address. If we want to use the root address we can
    just pass an empty string to the first element of the tuple e.g.
    ``('', address)``. For instance when using the federation wallet address we
    have no need to create leaf addresses.

    A file is represented by two hashes:
        - ``file_hash``: is the hash of the digital file
        - ``file_hash_metadata``: is the hash of the digital file + metadata

    The hash is passed to the methods has a tuple: ``(file_hash, file_hash_metadata)``

    Attributes:
        FEE (int): transaction fee
        TOKEN (int): token
        SPENTS_QUEUE_MAXSIZE (int): spent outputs queue maximum size

    """
    FEE = 30000
    TOKEN = 3000
    SPENTS_QUEUE_MAXSIZE = 50

    def __init__(self,
                 testnet=False,
                 service='blockr',
                 username='',
                 password='',
                 host='',
                 port=''):
        """
        Args:
            testnet (bool): Whether to use the mainnet or testnet.
                Defaults to the mainnet (:const:`False`).
            service (str): Bitcoin communication interface: ``'blockr'``,
                ``'daemon'``, or ``'regtest'``. ``'blockr'`` refers to the
                public api, whereas ``'daemon'`` and ``'regtest'`` refer
                to the jsonrpc inteface. Defaults to ``'blockr'``.
            username (str): username for jsonrpc communications
            password (str): password for jsonrpc communications
            hostname (str): hostname of the bitcoin node when using jsonrpc
            port (str): port number of the bitcoin node when using jsonrpc

        """
        self.testnet = testnet
        self._netcode = 'XTN' if testnet else 'BTC'
        self._t = Transactions(service=service,
                               testnet=testnet,
                               username=username,
                               password=password,
                               host=host,
                               port=port)
        # simple cache for spent outputs. Useful so that rapid firing transactions don't use the same outputs
        self._spents = Queue(maxsize=self.SPENTS_QUEUE_MAXSIZE)

    @dispatch
    def register_piece(self,
                       from_address,
                       to_address,
                       hash,
                       password,
                       min_confirmations=6,
                       sync=False,
                       ownership=True):
        """
        Register a piece

        Args:
            from_address (Tuple[str]): Federation address. All register transactions
                originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str]): Hash of the piece. (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            edition_num (int): The number of the edition to register. User
                edition_num=0 to register the master edition
            min_confirmations (int): Override the number of confirmations when
                chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the
                function will block until there is at least on confirmation on
                the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the
                transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb()
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, file_hash_metadata, to_address],
            op_return=verb.piece,
            min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def register(self,
                 from_address,
                 to_address,
                 hash,
                 password,
                 edition_num,
                 min_confirmations=6,
                 sync=False,
                 ownership=True):
        """
        Register an edition or master edition of a piece

        Args:
            from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str])): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            edition_num (int): The number of the edition to register. User edition_num=0 to register the master edition
            min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, file_hash_metadata, to_address],
            op_return=verb.register,
            min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def consigned_registration(self,
                               from_address,
                               to_address,
                               hash,
                               password,
                               min_confirmations=6,
                               sync=False,
                               ownership=True):
        """
        Register an edition or master edition of a piece consigned to ``from_address``

        Args:
            from_address (Tuple[str])): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb()
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, file_hash_metadata, to_address],
            op_return=verb.consigned_registration,
            min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def editions(self,
                 from_address,
                 to_address,
                 hash,
                 password,
                 num_editions,
                 min_confirmations=6,
                 sync=False,
                 ownership=True):
        """
        Register the number of editions of a piece

        Args:
            from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the number of editions
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str):  Federation wallet password. For signing the transaction
            num_editions (int): Number of editions of the piece
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(num_editions=num_editions)
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, file_hash_metadata, to_address],
            op_return=verb.editions,
            min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def transfer(self,
                 from_address,
                 to_address,
                 hash,
                 password,
                 edition_num,
                 min_confirmations=6,
                 sync=False,
                 ownership=True):
        """
        Transfer a piece between addresses

        Args:
            from_address (Tuple[str]): Address currently owning the edition
            to_address: Address to receive the edition
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently owning the edition. For signing the transaction
            edition_num (int): the number of the edition to transfer
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, to_address],
            op_return=verb.transfer,
            min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def consign(self,
                from_address,
                to_address,
                hash,
                password,
                edition_num,
                min_confirmations=6,
                sync=False,
                ownership=True):
        """
        Consign a piece to an address

        Args:
            from_address (Tuple[str]): Address currently owning the edition
            to_address (str): Address to where the piece will be consigned to
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently owning the edition. For signing the transaction
            edition_num (int): the number of the edition to consign
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, to_address],
            op_return=verb.consign,
            min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def unconsign(self,
                  from_address,
                  to_address,
                  hash,
                  password,
                  edition_num,
                  min_confirmations=6,
                  sync=False,
                  ownership=True):
        """
        Unconsign the edition

        Args:
            from_address (Tuple[str]): Address where the edition is currently consigned
            to_address (str): Address that consigned the piece to from_address
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently holding the edition. For signing the transaction
            edition_num (int): the number of the edition to unconsign
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        # In an unconsignment the to_address needs to be the address that created the consign transaction
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, to_address],
            op_return=verb.unconsign,
            min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def loan(self,
             from_address,
             to_address,
             hash,
             password,
             edition_num,
             loan_start,
             loan_end,
             min_confirmations=6,
             sync=False,
             ownership=True):
        """
        Loan the edition

        Args:
            from_address (Tuple[str]): Address currently holding the edition
            to_address (str): Address to loan the edition to
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently holding the edition. For signing the transaction
            edition_num (int): the number of the edition to loan
            loan_start (str): Start date for the loan. In the form YYMMDD
            loan_end (str): End date for the loan. In the form YYMMDD
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num,
                         loan_start=loan_start,
                         loan_end=loan_end)
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, to_address],
            op_return=verb.loan,
            min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def migrate(self,
                from_address,
                prev_address,
                new_address,
                hash,
                password,
                edition_num,
                min_confirmations=6,
                sync=False,
                ownership=True):
        """
        Migrate an edition

        Args:
            from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            edition_num (int): The number of the edition to register. User edition_num=0 to register the master edition
            min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(
            from_address, [file_hash, prev_address, new_address],
            op_return=verb.migrate,
            min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def refill_main_wallet(self,
                           from_address,
                           to_address,
                           nfees,
                           ntokens,
                           password,
                           min_confirmations=6,
                           sync=False):
        """
        Refill the Federation wallet with tokens and fees. This keeps the federation wallet clean.
        Dealing with exact values simplifies the transactions. No need to calculate change. Easier to keep track of the
        unspents and prevent double spends that would result in transactions being rejected by the bitcoin network.

        Args:

            from_address (Tuple[str]): Refill wallet address. Refills the federation wallet with tokens and fees
            to_address (str): Federation wallet address
            nfees (int): Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions
            ntokens (int): Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain
            password (str): Password for the Refill wallet. Used to sign the transaction
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False

        Returns:
            str: transaction id
        """
        path, from_address = from_address
        unsigned_tx = self._t.simple_transaction(
            from_address, [(to_address, self.FEE)] * nfees +
            [(to_address, self.TOKEN)] * ntokens,
            min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def refill(self,
               from_address,
               to_address,
               nfees,
               ntokens,
               password,
               min_confirmations=6,
               sync=False):
        """
        Refill wallets with the necessary fuel to perform spool transactions

        Args:
            from_address (Tuple[str]): Federation wallet address. Fuels the wallets with tokens and fees. All transactions to wallets
                holding a particular piece should come from the Federation wallet
            to_address (str): Wallet address that needs to perform a spool transaction
            nfees (int): Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions
            ntokens (int): Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain
            password (str): Password for the Federation wallet. Used to sign the transaction
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        verb = Spoolverb()
        # nfees + 1: nfees to refill plus one fee for the refill transaction itself
        inputs = self.select_inputs(from_address,
                                    nfees + 1,
                                    ntokens,
                                    min_confirmations=min_confirmations)
        outputs = [{'address': to_address, 'value': self.TOKEN}] * ntokens
        outputs += [{'address': to_address, 'value': self.FEE}] * nfees
        outputs += [{'script': self._t._op_return_hex(verb.fuel), 'value': 0}]
        unsigned_tx = self._t.build_transaction(inputs, outputs)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    def simple_spool_transaction(self,
                                 from_address,
                                 to,
                                 op_return,
                                 min_confirmations=6):
        """
        Utililty function to create the spool transactions. Selects the inputs,
        encodes the op_return and constructs the transaction.

        Args:
            from_address (str): Address originating the transaction
            to (str): list of addresses to receive tokens (file_hash, file_hash_metadata, ...)
            op_return (str): String representation of the spoolverb, as returned by the properties of Spoolverb
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6

        Returns:
            str: unsigned transaction

        """
        # list of addresses to send
        ntokens = len(to)
        nfees = old_div(self._t.estimate_fee(ntokens, 2), self.FEE)
        inputs = self.select_inputs(from_address,
                                    nfees,
                                    ntokens,
                                    min_confirmations=min_confirmations)
        # outputs
        outputs = [{
            'address': to_address,
            'value': self.TOKEN
        } for to_address in to]
        outputs += [{'script': self._t._op_return_hex(op_return), 'value': 0}]
        # build transaction
        unsigned_tx = self._t.build_transaction(inputs, outputs)
        return unsigned_tx

    def select_inputs(self, address, nfees, ntokens, min_confirmations=6):
        """
        Selects the inputs for the spool transaction.

        Args:
            address (str): bitcoin address to select inputs for
            nfees (int): number of fees
            ntokens (int): number of tokens
            min_confirmations (Optional[int]): minimum number of required
                confirmations; defaults to 6

        """
        unspents = self._t.get(address,
                               min_confirmations=min_confirmations)['unspents']
        unspents = [u for u in unspents if u not in self._spents.queue]
        if len(unspents) == 0:
            raise Exception("No spendable outputs found")

        fees = [u for u in unspents if u['amount'] == self.FEE][:nfees]
        tokens = [u for u in unspents if u['amount'] == self.TOKEN][:ntokens]
        if len(fees) != nfees or len(tokens) != ntokens:
            raise SpoolFundsError(
                "Not enough outputs to spend. Refill your wallet")
        if self._spents.qsize() > self.SPENTS_QUEUE_MAXSIZE - (nfees +
                                                               ntokens):
            [
                self._spents.get()
                for i in range(self._spents.qsize() + nfees + ntokens -
                               self.SPENTS_QUEUE_MAXSIZE)
            ]
        [self._spents.put(fee) for fee in fees]
        [self._spents.put(token) for token in tokens]
        return fees + tokens
Example #3
0
class Spool(object):
    """
    Class that contains all Spool methods.

    In the SPOOL implementation there is no notion of users only addresses.
    All addresses come from BIP32 HD wallets. This makes it easier to manage all the keys
    since we can retrieve everything we need from a master secret (namely the private key
    to sign the transactions).

    Since we are dealing with HD wallets we expect all ``from_address`` to be a
    tuple of ``(path, address)`` so that we can retrieve the private key for
    that particular leaf address. If we want to use the root address we can
    just pass an empty string to the first element of the tuple e.g.
    ``('', address)``. For instance when using the federation wallet address we
    have no need to create leaf addresses.

    A file is represented by two hashes:
        - ``file_hash``: is the hash of the digital file
        - ``file_hash_metadata``: is the hash of the digital file + metadata

    The hash is passed to the methods has a tuple: ``(file_hash, file_hash_metadata)``

    Attributes:
        FEE (int): transaction fee
        TOKEN (int): token
        SPENTS_QUEUE_MAXSIZE (int): spent outputs queue maximum size

    """
    FEE = 30000
    TOKEN = 3000
    SPENTS_QUEUE_MAXSIZE = 50

    def __init__(self, testnet=False, service='blockr', username='', password='', host='', port=''):
        """
        Args:
            testnet (bool): Whether to use the mainnet or testnet.
                Defaults to the mainnet (:const:`False`).
            service (str): Bitcoin communication interface: ``'blockr'``,
                ``'daemon'``, or ``'regtest'``. ``'blockr'`` refers to the
                public api, whereas ``'daemon'`` and ``'regtest'`` refer
                to the jsonrpc inteface. Defaults to ``'blockr'``.
            username (str): username for jsonrpc communications
            password (str): password for jsonrpc communications
            hostname (str): hostname of the bitcoin node when using jsonrpc
            port (str): port number of the bitcoin node when using jsonrpc

        """
        self.testnet = testnet
        self._netcode = 'XTN' if testnet else 'BTC'
        self._t = Transactions(service=service, testnet=testnet, username=username,
                               password=password, host=host, port=port)
        # simple cache for spent outputs. Useful so that rapid firing transactions don't use the same outputs
        self._spents = Queue(maxsize=self.SPENTS_QUEUE_MAXSIZE)

    @dispatch
    def register_piece(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True):
        """
        Register a piece

        Args:
            from_address (Tuple[str]): Federation address. All register transactions
                originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str]): Hash of the piece. (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            edition_num (int): The number of the edition to register. User
                edition_num=0 to register the master edition
            min_confirmations (int): Override the number of confirmations when
                chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the
                function will block until there is at least on confirmation on
                the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the
                transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb()
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.piece,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def register(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Register an edition or master edition of a piece

        Args:
            from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str])): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            edition_num (int): The number of the edition to register. User edition_num=0 to register the master edition
            min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.register,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def consigned_registration(self, from_address, to_address, hash, password, min_confirmations=6, sync=False, ownership=True):
        """
        Register an edition or master edition of a piece consigned to ``from_address``

        Args:
            from_address (Tuple[str])): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb()
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.consigned_registration,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def editions(self, from_address, to_address, hash, password, num_editions, min_confirmations=6, sync=False, ownership=True):
        """
        Register the number of editions of a piece

        Args:
            from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the number of editions
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str):  Federation wallet password. For signing the transaction
            num_editions (int): Number of editions of the piece
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(num_editions=num_editions)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, file_hash_metadata, to_address],
                                                    op_return=verb.editions,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def transfer(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Transfer a piece between addresses

        Args:
            from_address (Tuple[str]): Address currently owning the edition
            to_address: Address to receive the edition
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently owning the edition. For signing the transaction
            edition_num (int): the number of the edition to transfer
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.transfer,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def consign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Consign a piece to an address

        Args:
            from_address (Tuple[str]): Address currently owning the edition
            to_address (str): Address to where the piece will be consigned to
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently owning the edition. For signing the transaction
            edition_num (int): the number of the edition to consign
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.consign,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def unconsign(self, from_address, to_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Unconsign the edition

        Args:
            from_address (Tuple[str]): Address where the edition is currently consigned
            to_address (str): Address that consigned the piece to from_address
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently holding the edition. For signing the transaction
            edition_num (int): the number of the edition to unconsign
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        # In an unconsignment the to_address needs to be the address that created the consign transaction
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.unconsign,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def loan(self, from_address, to_address, hash, password, edition_num, loan_start, loan_end, min_confirmations=6, sync=False, ownership=True):
        """
        Loan the edition

        Args:
            from_address (Tuple[str]): Address currently holding the edition
            to_address (str): Address to loan the edition to
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Password for the wallet currently holding the edition. For signing the transaction
            edition_num (int): the number of the edition to loan
            loan_start (str): Start date for the loan. In the form YYMMDD
            loan_end (str): End date for the loan. In the form YYMMDD
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        file_hash, file_hash_metadata = hash
        verb = Spoolverb(edition_num=edition_num, loan_start=loan_start, loan_end=loan_end)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, to_address],
                                                    op_return=verb.loan,
                                                    min_confirmations=min_confirmations)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def migrate(self, from_address, prev_address, new_address, hash, password, edition_num, min_confirmations=6, sync=False, ownership=True):
        """
        Migrate an edition

        Args:
            from_address (Tuple[str]): Federation address. All register transactions originate from the the Federation wallet
            to_address (str): Address registering the edition
            hash (Tuple[str]): Hash of the piece. Tuple (file_hash, file_hash_metadata)
            password (str): Federation wallet password. For signing the transaction
            edition_num (int): The number of the edition to register. User edition_num=0 to register the master edition
            min_confirmations (int): Override the number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False
            ownership (bool): Check ownsership in the blockchain before pushing the transaction. Defaults to True

        Returns:
            str: transaction id

        """
        file_hash, file_hash_metadata = hash
        path, from_address = from_address
        verb = Spoolverb(edition_num=edition_num)
        unsigned_tx = self.simple_spool_transaction(from_address,
                                                    [file_hash, prev_address, new_address],
                                                    op_return=verb.migrate,
                                                    min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def refill_main_wallet(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False):
        """
        Refill the Federation wallet with tokens and fees. This keeps the federation wallet clean.
        Dealing with exact values simplifies the transactions. No need to calculate change. Easier to keep track of the
        unspents and prevent double spends that would result in transactions being rejected by the bitcoin network.

        Args:

            from_address (Tuple[str]): Refill wallet address. Refills the federation wallet with tokens and fees
            to_address (str): Federation wallet address
            nfees (int): Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions
            ntokens (int): Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain
            password (str): Password for the Refill wallet. Used to sign the transaction
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False

        Returns:
            str: transaction id
        """
        path, from_address = from_address
        unsigned_tx = self._t.simple_transaction(from_address,
                                                 [(to_address, self.FEE)] * nfees + [(to_address, self.TOKEN)] * ntokens,
                                                 min_confirmations=min_confirmations)

        signed_tx = self._t.sign_transaction(unsigned_tx, password)
        txid = self._t.push(signed_tx)
        return txid

    @dispatch
    def refill(self, from_address, to_address, nfees, ntokens, password, min_confirmations=6, sync=False):
        """
        Refill wallets with the necessary fuel to perform spool transactions

        Args:
            from_address (Tuple[str]): Federation wallet address. Fuels the wallets with tokens and fees. All transactions to wallets
                holding a particular piece should come from the Federation wallet
            to_address (str): Wallet address that needs to perform a spool transaction
            nfees (int): Number of fees to transfer. Each fee is 10000 satoshi. Used to pay for the transactions
            ntokens (int): Number of tokens to transfer. Each token is 600 satoshi. Used to register hashes in the blockchain
            password (str): Password for the Federation wallet. Used to sign the transaction
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6
            sync (bool): Perform the transaction in synchronous mode, the call to the function will block until there is at
                least on confirmation on the blockchain. Defaults to False

        Returns:
            str: transaction id

        """
        path, from_address = from_address
        verb = Spoolverb()
        # nfees + 1: nfees to refill plus one fee for the refill transaction itself
        inputs = self.select_inputs(from_address, nfees + 1, ntokens, min_confirmations=min_confirmations)
        outputs = [{'address': to_address, 'value': self.TOKEN}] * ntokens
        outputs += [{'address': to_address, 'value': self.FEE}] * nfees
        outputs += [{'script': self._t._op_return_hex(verb.fuel), 'value': 0}]
        unsigned_tx = self._t.build_transaction(inputs, outputs)
        signed_tx = self._t.sign_transaction(unsigned_tx, password, path=path)
        txid = self._t.push(signed_tx)
        return txid

    def simple_spool_transaction(self, from_address, to, op_return, min_confirmations=6):
        """
        Utililty function to create the spool transactions. Selects the inputs,
        encodes the op_return and constructs the transaction.

        Args:
            from_address (str): Address originating the transaction
            to (str): list of addresses to receive tokens (file_hash, file_hash_metadata, ...)
            op_return (str): String representation of the spoolverb, as returned by the properties of Spoolverb
            min_confirmations (int): Number of confirmations when chosing the inputs of the transaction. Defaults to 6

        Returns:
            str: unsigned transaction

        """
        # list of addresses to send
        ntokens = len(to)
        nfees = old_div(self._t.estimate_fee(ntokens, 2), self.FEE)
        inputs = self.select_inputs(from_address, nfees, ntokens, min_confirmations=min_confirmations)
        # outputs
        outputs = [{'address': to_address, 'value': self.TOKEN} for to_address in to]
        outputs += [{'script': self._t._op_return_hex(op_return), 'value': 0}]
        # build transaction
        unsigned_tx = self._t.build_transaction(inputs, outputs)
        return unsigned_tx

    def select_inputs(self, address, nfees, ntokens, min_confirmations=6):
        """
        Selects the inputs for the spool transaction.

        Args:
            address (str): bitcoin address to select inputs for
            nfees (int): number of fees
            ntokens (int): number of tokens
            min_confirmations (Optional[int]): minimum number of required
                confirmations; defaults to 6

        """
        unspents = self._t.get(address, min_confirmations=min_confirmations)['unspents']
        unspents = [u for u in unspents if u not in self._spents.queue]
        if len(unspents) == 0:
            raise Exception("No spendable outputs found")

        fees = [u for u in unspents if u['amount'] == self.FEE][:nfees]
        tokens = [u for u in unspents if u['amount'] == self.TOKEN][:ntokens]
        if len(fees) != nfees or len(tokens) != ntokens:
            raise SpoolFundsError("Not enough outputs to spend. Refill your wallet")
        if self._spents.qsize() > self.SPENTS_QUEUE_MAXSIZE - (nfees + ntokens):
            [self._spents.get() for i in range(self._spents.qsize() + nfees + ntokens - self.SPENTS_QUEUE_MAXSIZE)]
        [self._spents.put(fee) for fee in fees]
        [self._spents.put(token) for token in tokens]
        return fees + tokens