Пример #1
0
def _login():
    from modules.login import generate_jwt_token

    json_form = request.get_json(force=True, silent=True)
    address = json_form.get('address')
    signature = json_form.get('signature')
    signature_version = json_form.get('signature_version', 1)
    user_name = json_form.get('user_name', '')

    platform_type = json_form.get('platform_type')
    if platform_type not in PLATFORM_TYPES:
        return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG)

    web3 = get_web3()

    with db.engine_rdwr.connect() as connection:
        jwt_token = generate_jwt_token(
            connection,
            web3, address, signature,
            platform_type=platform_type,
            signature_version=signature_version,
            default_user_name=user_name
        )

        if jwt_token:
            return helper.response_ok(jwt_token)
        else:
            return helper.response_err(ER.AUTHENTICATION_FAILED, ER.AUTHENTICATION_FAILED_MSG)
Пример #2
0
def _upload_paper():
    """
    Uploads a paper file from client.

    json form:
    {
        "hash": file hash string,
        "price": uint256,
        "encrypted": true | false,
    }

    Server downloads the paper file by its IPFS process from the client IPFS node and it creates a muzika contract
    for creating a paper in the block chain network.

    Server must have an IPFS node and it pins the object file for spreading the paper file.
    """
    json_form = request.get_json(force=True, silent=True)
    file_hash = json_form.get('hash')
    wallet_address = request.user['address']
    price = json_form.get('price')
    encrypted = json_form.get('encrypted', False)

    # check request json
    if not isinstance(file_hash, str) or not isinstance(
            price, int) or not isinstance(encrypted, bool):
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    # pin the file for spreading out the paper
    relay_ipfs = RelayIpfs()
    api = relay_ipfs.get_connection()
    # TODO : process if failed to pin (timeout problem)
    api.pin_add(file_hash)

    # create a paper contract
    web3 = get_web3()
    contract_handler = MuzikaContractHandler()
    paper_contract = contract_handler.get_contract(web3, 'MuzikaPaperContract')

    tx_hash = paper_contract.constructor(
        web3.toChecksumAddress(wallet_address), price, file_hash,
        '').transact()

    return helper.response_ok({'status': 'success'})
Пример #3
0
def _get_user(address):
    """
    Returns an user information by wallet address.

    If user(address) does not exist, give a random message for signing
    """

    s3_base_url = 'https://s3.{region}.amazonaws.com'.format(region=s3_policy['profile']['region'])

    user_query_stmt = """
        SELECT `u`.*, CONCAT(:s3_base_url, '/', `f`.`bucket`, '/', `f`.`object_key`) AS `profile_image` FROM `{}` `u`
        LEFT JOIN `{}` `f`
          ON (`f`.`file_id` = `u`.`profile_file_id` AND `f`.`type` = :file_type)
        WHERE `u`.`address` = :address
        LIMIT 1
    """.format(db.table.USERS, db.table.FILES)

    # if invalid address format, don't generate message
    if not check_address_format(address):
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    web3 = get_web3()
    web3.toChecksumAddress(address)

    with db.engine_rdonly.connect() as connection:
        user = connection.execute(
            text(user_query_stmt),
            s3_base_url=s3_base_url,
            address=address,
            file_type='profile'
        ).fetchone()

        if user is None:
            return helper.response_err(ERR.COMMON.NOT_EXIST)

        return helper.response_ok(db.to_relation_model(user))
Пример #4
0
def _get_paper_file(contract_address):
    """
    Get aes key for decrypt a paper from a contract.

    json form:
    {
        "public_key": public key for encryption
    }
    """
    json_form = request.get_json(force=True, silent=True)
    public_key = json_form.get('public_key')

    # not support downloading without encryption
    if not isinstance(public_key, str):
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    # parse public key
    try:
        public_key = RSA.import_key(public_key)
    except ValueError:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    user_address = request.user['address']

    web3 = get_web3()
    contract_address = web3.toChecksumAddress(contract_address)
    contract = MuzikaPaperContract(web3, contract_address=contract_address)

    if not contract.purchased(user_address, {'from': user_address}):
        # if the user hasn't purchased this paper
        return helper.response_err(ERR.COMMON.AUTHENTICATION_FAILED)

    key_query_statement = """
        SELECT 
          `if`.`ipfs_hash`, `if`.`encrypted`, `fp`.`aes_key` 
        FROM `music_contracts` `mc`
        INNER JOIN `ipfs_files` `if`
          ON (`mc`.`ipfs_file_id` = `if`.`file_id`)
        LEFT JOIN `ipfs_files_private` `fp`
          ON (`if`.`file_id` = `fp`.`file_id`)
        WHERE
          `contract_address` = :contract_address AND `mc`.`status` = :contract_status
        LIMIT 1
    """

    with db.engine_rdonly.connect() as connection:
        key_query = connection.execute(text(key_query_statement),
                                       contract_address=contract_address,
                                       contract_status='success').fetchone()

        # if the contract is not registered in the server or does not have IPFS file
        if not key_query:
            return helper.response_err(ERR.COMMON.NOT_EXIST)

        ipfs_hash = key_query['ipfs_hash']
        encrypted = key_query['encrypted']
        aes_key = key_query['aes_key']

        if not encrypted:
            # if the file is not encrypted, response with null key
            return helper.response_ok({'key': None})
        else:
            # if the file is encrypted, response with AES key encrypted by user's public key.
            from Crypto.Cipher import PKCS1_OAEP
            cipher = PKCS1_OAEP.new(public_key)
            encrypted_aes_key = cipher.encrypt(aes_key)
            return helper.response_ok({'key': encrypted_aes_key})
def update_payments():
    """
    All of transaction for purchase of music is not reliable,
    which can have price, buyer or contract address.
    Therefore we have to wait for transaction to be mined with txHash and get receipt
    of the txHash, then all of information (price, buyer etc.) are obtained by the receipt.
    Then, save the information to our database

    Status
        pending  : Ready for mined transaction given hash.
        success  : The transaction is valid and accepted in the blockchain.
        failed   : The transaction is failed because of out of gas, not enough muzika or any other reason.
        invalid  : The transaction is invalid (e.g. this transaction is not for purchase of music)
        disabled : The transaction is not found in the blockchain (That is, timeout of waiting for transaction)
    """
    web3 = get_web3()
    purchase_event_name = web3.sha3(b'Purchase(address,uint256)')

    # query for updating status of timeout transaction
    update_query_statement = """
        UPDATE `{}` SET `status` = 'disabled'
        WHERE
            `status` = 'pending'
        AND `created_at` < NOW() - INTERVAL 3 HOUR
    """.format(db.table.MUSIC_PAYMENTS)

    # query for deleting timeout transaction
    delete_query_statement = """
        DELETE FROM `{}`
        WHERE
            `status` = 'disabled'
        AND `created_at` < NOW() - INTERVAL 6 HOUR
    """.format(db.table.MUSIC_PAYMENTS)

    with db.engine_rdwr.connect() as connection:

        # get list of payment history that is not mined (wait)
        payments = db.to_relation_model_list(
            db.Statement(db.table.MUSIC_PAYMENTS).where(
                status='pending').select(connection))

        def invalid_payment(__payment):
            # this transaction is not valid
            db.Statement(db.table.MUSIC_PAYMENTS)\
                .set(status='invalid')\
                .where(payment_id=__payment['payment_id'])\
                .update(connection)

        for payment in payments:
            try:
                receipt = web3.eth.waitForTransactionReceipt(
                    transaction_hash=payment['tx_hash'], timeout=1)
            except Timeout:
                continue

            if not receipt:
                continue

            if receipt.status == 0:
                # this transaction is failed
                db.Statement(db.table.MUSIC_PAYMENTS)\
                    .set(status='failed')\
                    .where(payment_id=payment['payment_id'])\
                    .update(connection)

                continue
            """
            Because we use approval function to purchase music, value of `to` in
            transaction is MuzikaCoin contract's address

                MuzikaCoin.address == tx['to']
                
            Contract address is equal to event.address
            """

            purchase_events = [
                event for event in receipt.logs
                if event.topics[0] == purchase_event_name
            ]

            if len(purchase_events) == 0:
                # Purchase event is not emitted
                invalid_payment(payment)
                continue

            event = purchase_events[-1]
            contract_address = event.address
            contract_exists = db.Statement(db.table.MUSIC_CONTRACTS).columns('"_"')\
                .where(contract_address=contract_address, status='success')\
                .limit(1).select(connection).fetchone()

            if contract_exists is None:
                # Contract is not exists in our database. It is not a contract for muzika platform
                invalid_payment(payment)
                continue
            """
            Contract is valid for our platform. Use it!
            """

            # price is equal to event.data (type is str)
            price = event.data

            # buyer is equal to event.topics[1]
            # structure of event is `Purchase(address,uint256)`
            # type is HexBytes
            buyer = web3.toChecksumAddress('0x' + event.topics[1].hex()[-40:])

            db.Statement(db.table.MUSIC_PAYMENTS)\
                .set(buyer_address=buyer,
                     contract_address=contract_address,
                     price=price,
                     status='success')\
                .where(payment_id=payment['payment_id'])\
                .update(connection)

        # execute update query
        connection.execute(text(update_query_statement))

        # execute delete query (timeout is doubled)
        connection.execute(text(delete_query_statement))
Пример #6
0
def _get_paper_file(contract_address):
    """
    Downloads a paper from a contract.

    json form:
    {
        "public_key": public key for encryption
    }
    """
    json_form = request.get_json(force=True, silent=True)
    public_key = json_form.get('public_key')

    # not support downloading without encryption
    if not isinstance(public_key, str):
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    # parse public key
    try:
        public_key = RSA.import_key(public_key)
    except ValueError:
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    user_address = request.user['address']

    web3 = get_web3()
    contract = MuzikaPaperContract(web3, contract_address=contract_address)

    if not contract.purchased(user_address):
        # if the user hasn't purchased this paper
        return helper.response_err(ER.AUTHENTICATION_FAILED,
                                   ER.AUTHENTICATION_FAILED_MSG)

    paper_statement = db.Statement(
        db.table.PAPERS).where(contract_address=contract_address).limit(1)

    with db.engine_rdonly.connect() as connection:
        paper = db.to_relation_model(
            paper_statement.select(connection).fetchone())

        # if not supporting this paper contract
        if not paper:
            return helper.response_err(ER.NOT_EXIST, ER.NOT_EXIST_MSG)

        file_id = paper['file_id']

        # if not having file
        if not file_id:
            # TODO:
            return helper.response_err(ER.NOT_EXIST, ER.NOT_EXIST_MSG)
        else:
            # get ipfs file hash
            file_hash = paper['ipfs_file_hash']

            # download from bucket
            bucket = MuzikaS3Bucket()
            s3_response = bucket.get(connection, file_id)
            file_blob = s3_response['Body'].read()

            block = Block(data=file_blob, hash=file_hash)
            block_request = BlockRequest(block_hash=file_hash,
                                         public_key=public_key)
            encrypted_block = block_request.encrypt(block)

            response_body = encrypted_block.encrypted_key + encrypted_block.data
            response = make_response(response_body)
            response.headers['content-length'] = len(response_body)
            response.headers['content-type'] = 'text/plain'
            return response
                      '--directory',
                      help='directory that has the truffle contract json file')
    args.add_argument('-r',
                      '--recursive',
                      help='Recursively search contract json file',
                      required=False,
                      default=False)
    args.add_argument('-n',
                      '--version',
                      help='Network protocol version',
                      required=False)

    args = args.parse_args()

    if args.version:
        web3 = get_web3()
        network_version = web3.version.network
    else:
        network_version = args.version

    contracts_json = {}
    contracts_address = {}

    # load all contract json files
    for file_path in glob.glob(os.path.join(args.directory, '*'),
                               recursive=args.recursive):
        # if not json file, ignore it
        _, ext = os.path.splitext(file_path)
        if ext != '.json':
            continue
Пример #8
0
def update_contracts():
    web3 = get_web3()

    complete_delete_query_statement = """
        DELETE FROM `{}`
        WHERE `status` NOT IN :delete_status AND `created_at` < NOW() - INTERVAL 6 HOUR
    """.format(db.table.MUSIC_CONTRACTS)

    # query for deleting music contracts and their IPFS files list if not mined
    delete_query_statement = """
        UPDATE `{}` `mc`
        LEFT JOIN `{}` `mb`
          ON (`mb`.`post_id` = `mc`.`post_id`)
        SET
          `mb`.`status` = :set_board_status,
          `mc`.`status` = :set_contract_status
        WHERE
          `mc`.`status` = :delete_status AND `mc`.`created_at` < NOW() - INTERVAL 3 HOUR
        """.format(db.table.MUSIC_CONTRACTS, db.table.board('music'))

    transaction_query_statement = """
        SELECT `mc`.*, `u`.`address`
        FROM
          `{}` `mc`
        LEFT JOIN `{}` `mb`
          ON (`mb`.`post_id` = `mc`.`post_id`)
        LEFT JOIN `{}` `u`
          ON (`u`.`user_id` = `mb`.`user_id`)
        WHERE
          `mc`.`status` = :track_status
    """.format(db.table.MUSIC_CONTRACTS, db.table.board('music'), db.table.USERS)

    with db.engine_rdwr.connect() as connection:
        # DELETE contracts completely that are not mined over specific time
        connection.execute(text(complete_delete_query_statement),
                           delete_status=['success'])

        # DELETE contracts that are not mined over specific time
        connection.execute(text(delete_query_statement),
                           set_board_status='deleted',
                           set_contract_status='disabled',
                           delete_status='pending')

        # QUERY not mined contracts
        contracts = db.to_relation_model_list(
            connection.execute(text(transaction_query_statement), track_status='pending')
        )

        # get original bytecode of the paper contract
        contract_handler = MuzikaContractHandler()
        payment_interface_contract = contract_handler.get_interface('Dispatcher')
        payment_contract = contract_handler.get_interface('MuzikaPaperContract')
        contract_bytecode = payment_contract['bytecode'][:-68]
        contract_bytecode = contract_bytecode.replace(
            '__LibPaperPaymentInterface______________',
            payment_interface_contract['networks'][web3.version.network]['address'][2:]
        )

        for contract in contracts:
            contract_status = 'success'
            board_status = 'posted'

            try:
                tx_receipt = web3.eth.waitForTransactionReceipt(transaction_hash=contract['tx_hash'], timeout=1)
            except Timeout:
                # if failed to get contract (not mined or fake transaction), check next contract
                continue

            if tx_receipt:
                # TODO : validate the contract

                try:
                    contract_address = tx_receipt['contractAddress']
                    tx_contract = MuzikaPaperContract(web3, contract_address=contract_address)
                    seller_address = tx_contract.get_seller()

                    tx = web3.eth.getTransaction(contract['tx_hash'])

                    # if tx data is invalid, set contract status to invalid and board status to deleted
                    if tx.input[:len(contract_bytecode)] != contract_bytecode:
                        contract_status = 'invalid'
                        board_status = 'deleted'

                    # Check seller.
                    # If the contract is derived from music post but the author of it is different from the real
                    # contract owner, set status to success but delete post
                    if contract['address'] and seller_address != contract['address']:
                        board_status = 'deleted'
                except ValueError as e:
                    # if ValueError occurs (may be wrong transaction), set invalid contracvt and delete post
                    contract_status = 'invalid'
                    board_status = 'deleted'

                if board_status == 'deleted':
                    db.Statement(db.table.board('music'))\
                        .set(status='deleted')\
                        .where(post_id=contract['post_id'])\
                        .update(connection)

                if contract_status == 'success':
                    db.Statement(db.table.MUSIC_CONTRACTS)\
                        .set(contract_address=contract_address, seller_address=seller_address, status='success')\
                        .where(contract_id=contract['contract_id'])\
                        .update(connection)
                else:
                    db.Statement(db.table.MUSIC_CONTRACTS)\
                        .set(status=contract_status)\
                        .where(contract_id=contract['contract_id'])\
                        .update(connection)