Example #1
0
def accept_proposed_rate(request, shared_data):
    # this is node_to_confirm's account
    acct = Account.objects.get(shared_data=shared_data,
                               owner__pk=shared_data.node_to_confirm_id)

    pmt = None

    if acct.getBalance(
    ) and shared_data.interest_rate:  # otherwise no update necessary

        pmt = Payment(payer=acct.owner,
                                    payer_email=acct.owner.getPrimaryEmail(),
                                    recipient=acct.partner,
                                    recipient_email=acct.partner.getPrimaryEmail(),
                                    currency=shared_data.currency,
                                    amount=0.0,
                                    status='PE',
                                    description='Interest rate change: %s%% to %s%%.' % \
                                    (shared_data.displayRate(), shared_data.displayProposedRate()))
        pmt.save()  # set pmt time
        #pmt = Payment.objects.get(pk=pmt.id) # reload because django doesn't update date that is auto-set on save

        interest = computeInterest(acct.getBalance(),
                                   shared_data.interest_rate,
                                   shared_data.last_update, pmt.date)

        path = PaymentPath(payment=pmt, amount=pmt.amount)
        path.save()

        link = PaymentLink(path=path,
                           payer_account=acct,
                           position=1,
                           amount=pmt.amount,
                           interest=-interest,
                           interest_rate=shared_data.interest_rate)
        link.save()

        shared_data.balance += interest * acct.balance_multiplier

    if pmt:
        shared_data.last_update = pmt.date
    else:
        shared_data.last_update = datetime.datetime.now()
    shared_data.interest_rate = shared_data.proposed_rate
    shared_data.proposed_rate = None
    shared_data.node_to_confirm_id = None
    shared_data.save()

    if pmt:  # could do this initially now that this is wrapped in a transaction
        pmt.status = 'OK'
        pmt.save()

    t = template_loader.get_template('emailAcceptRateProposal.txt')
    c = RequestContext(request, {'acct': acct.get_partner_acct()})
    sendEmail("Interest rate proposal accepted", t.render(c),
              acct.partner.getPrimaryEmail())
Example #2
0
def testAccountIntegrity():
    for shared in SharedAccountData.objects.all():
        #print "\n*** %s ***" % shared
        acctCurrency = shared.currency
        fwdAcct = Account.objects.get(shared_data=shared, balance_multiplier=1)
        bwdAcct = Account.objects.get(shared_data=shared, balance_multiplier=-1)
        
        if fwdAcct.getBalanceNoInterest() < -fwdAcct.iou_limit - ERROR:
            print 'Account %d over limit: Balance %.12f, Limit %.12f.' % \
                (fwdAcct.id, fwdAcct.getBalanceNoInterest(), -fwdAcct.iou_limit)
        if bwdAcct.getBalanceNoInterest() < -bwdAcct.iou_limit - ERROR:
            print 'Account %d over limit: Balance %.12f, Limit %.12f.' % \
                (bwdAcct.id, bwdAcct.getBalanceNoInterest(), -bwdAcct.iou_limit)
        
        tally = 0.0
        last_date = None
        for link in PaymentLink.objects.filter(payer_account__id__in=(fwdAcct.id, bwdAcct.id)
                                                                      ).extra(select={'date': 'pmt.date'},
                                                                                      tables=('ripple_payment as pmt', 'ripple_paymentpath as path'),
                                                                                      where=('path.payment_id=pmt.id', 'path.id=ripple_paymentlink.path_id')
                                                                      ).order_by('date', 'id'):
            path = link.path
            payment = path.payment
            acct = link.payer_account
            #print "%d: %.12f (%.12f/%.3f) on %s = %.12f" % \
            #  (link.id, link.amount, link.interest, link.interest_rate, payment.date, tally)
            interest = 0.0
            if last_date: # skip first transaction on account because balance/interest are zero
                pathAmount = path.amount 
                if acctCurrency.value:
                    pathAmount *= payment.currency.value / acctCurrency.value
                interest = computeInterest(tally * acct.balance_multiplier, link.interest_rate, last_date, payment.date)
                if abs(interest + link.interest) > .5e-12: # should be exact, not sure why sometimes get errors here
                    print "Link %d: incorrect interest.\n\tBalance: %.14f.\n\tCorrect value: %.20f\n\tRecorded %.12f" % \
                        (link.id, tally, interest, -link.interest)
                
                if abs(pathAmount - link.amount) > 1e-12:
                    print "Link %d: incorrect amount. Correct value: %.20f, recorded %.12f" % \
                        (link.id, pathAmount, link.amount)
            else:
                if link.interest != 0.0:
                    print "Link %d: first transaction interest not zero.  (%.12f)" % (link.id, link.interest)
            
            last_date = payment.date
            tally -= (link.amount + link.interest) * acct.balance_multiplier
        
        if abs(tally - shared.balance) > ERROR: # sometimes this is not quite exact either, maybe due to summing rouding errors?
            print 'Shared account %d doesn\'t balance with transaction total.  Balance: %.12f, Transactions: %.12f.' % \
                (shared.id, shared.balance, tally)
Example #3
0
def accept_proposed_rate(request, shared_data):
        # this is node_to_confirm's account
        acct = Account.objects.get(shared_data=shared_data, owner__pk=shared_data.node_to_confirm_id)
        
        pmt = None
        
        if acct.getBalance() and shared_data.interest_rate: # otherwise no update necessary

                pmt = Payment(payer=acct.owner,
                                            payer_email=acct.owner.getPrimaryEmail(),
                                            recipient=acct.partner,
                                            recipient_email=acct.partner.getPrimaryEmail(),
                                            currency=shared_data.currency,
                                            amount=0.0,
                                            status='PE',
                                            description='Interest rate change: %s%% to %s%%.' % \
                                            (shared_data.displayRate(), shared_data.displayProposedRate()))
                pmt.save() # set pmt time
                #pmt = Payment.objects.get(pk=pmt.id) # reload because django doesn't update date that is auto-set on save
                
                interest = computeInterest(acct.getBalance(), shared_data.interest_rate, shared_data.last_update, pmt.date)
                
                path = PaymentPath(payment=pmt, amount=pmt.amount)
                path.save()
                
                link = PaymentLink(path=path, payer_account=acct, position=1, 
                                                                                amount=pmt.amount, interest=-interest, 
                                                                                interest_rate=shared_data.interest_rate)
                link.save()
                
                shared_data.balance += interest * acct.balance_multiplier
                
        if pmt:
                shared_data.last_update = pmt.date
        else:
                shared_data.last_update = datetime.datetime.now()
        shared_data.interest_rate = shared_data.proposed_rate
        shared_data.proposed_rate = None
        shared_data.node_to_confirm_id = None
        shared_data.save()

        if pmt: # could do this initially now that this is wrapped in a transaction
                pmt.status = 'OK'
                pmt.save()
  
        t = template_loader.get_template('emailAcceptRateProposal.txt')
        c = RequestContext(request, {'acct': acct.get_partner_acct()})
        sendEmail("Interest rate proposal accepted", t.render(c), acct.partner.getPrimaryEmail())
Example #4
0
def payAlongPath(path, amount, cursor, pmt):
    # use only transaction-safe DB values - means re-get necessary data
    pathCredit = amount

    MULTIPLIER, IOU_LIMIT, BALANCE, INTEREST_RATE, LAST_UPDATE, CURR_VALUE = range(
        6)  # indexes on querySql row
    querySql = "SELECT acct.balance_multiplier, acct.iou_limit, shared.balance, shared.interest_rate, shared.last_update, curr.value \
    FROM ripple_account as acct JOIN ripple_sharedaccountdata as shared ON shared.id = acct.shared_data_id \
    JOIN ripple_currencyunit as curr ON curr.id = shared.currency_id WHERE acct.id = %d"

    valueSql = "SELECT value FROM ripple_currencyunit WHERE id = %d"

    # first, get value of pmt currency
    cursor.execute(valueSql, (pmt.currency_id, ))
    valueRow = cursor.fetchone()
    pmt.currencyValue = valueRow[0]  # value

    # next, get acct info for each acct in path and use to determine path credit
    for acct in path[1:len(path):2]:  # accts are at odd indices in path
        # get acct info
        cursor.execute(querySql, (acct.id, ))
        queryRow = cursor.fetchone()

        # calculate avail credit (in pmt units)
        acct.actual_balance = queryRow[BALANCE] * queryRow[MULTIPLIER]
        acct.last_update = queryRow[LAST_UPDATE]
        # *** causing errors when only one simultaneous payment happening
        #     this is because of django timezone setting -- unset it?  (works on windows)
        if acct.last_update > pmt.date:  # can't properly compute interest if someone else already has since the time this payment was supposed to occur at
            print acct.last_update, pmt.date
            raise PaymentError(
                code=TX_COLLISION,
                message=
                "Another transaction updated this account since the time we wanted to pay at.",
                retry=True)
        acct.interest = computeInterest(acct.actual_balance,
                                        queryRow[INTEREST_RATE],
                                        acct.last_update, pmt.date)
        acct.eff_balance = acct.actual_balance + acct.interest
        acct.availCredit = queryRow[IOU_LIMIT] + acct.eff_balance

        if pmt.currencyValue:  # otherwise, no conversions
            # get acct currency conversion value
            acct.currencyValue = queryRow[CURR_VALUE]

            # calculate available credit in pmt units
            acct.availCredit *= acct.currencyValue / pmt.currencyValue  # convert to pmt units
            acct.availCredit = float(
                "%.12f" % acct.availCredit
            )  # round path's amount to 12 decimal places so all paths add exactly to the total payment amount in db

        if acct.availCredit < pathCredit:
            pathCredit = acct.availCredit
            # check that pathCredit doesn't convert back too high, otherwise updating balance may fail
            # **** occasionally causing minute amounts of payment to not go through on predicted number of paths
            # **** instead, allow for minute amounts over limit
            ##if pmt.currencyValue: # only check if we're doing conversions
            ##  roundBack = pathCredit * pmt.currencyValue / acct.currencyValue
            ##  roundBack = float("%.12f" % roundBack)
            ##  if roundBack > pathCredit:
            ##    pathCredit -= 0.000000000001 # twelve digits down, subtract one
    if pathCredit <= 0: return 0  # no credit, no payment along this path

    # insert Path into database
    sql = "INSERT INTO ripple_paymentpath (payment_id, amount) VALUES (%d, %.12f);"
    sql += "SELECT currval('ripple_paymentpath_id_seq');"  # at the same time get the ID of the path we just inserted
    cursor.execute(sql, (pmt.id, pathCredit))

    # find ID of path we just inserted
    pathId = cursor.fetchone()[0]

    # make payments
    linkInsSql = "INSERT INTO ripple_paymentlink (path_id, payer_account_id, position, amount, interest, interest_rate) "
    linkInsSql += "VALUES (%d, %d, %d, %.12f, %.12f, %.10f);"
    ##linkInsSql += "SELECT currval('ripple_paymentlink_id_seq');"
    position = 1  # start link position sequence at 1
    balUpdSql = "UPDATE ripple_sharedaccountdata SET balance = balance - %.14f, last_update = '%s' "
    balUpdSql += "WHERE ripple_sharedaccountdata.id = %d "
    #balUpdSql += "AND last_update = '%s' " # make sure last_update stays consistent, otherwise interest won't work.
    balUpdSql += "AND last_update - '%s' <= INTERVAL '0:0:0.000001' "  # alternative version because psycopg sometimes loses a microsecond!
    balUpdSql += "AND balance * %d >= %.14f"  # % balance_multiplier, minimum balance needed for payment to succeed
    # makes sure there's enough, even if another transaction has fudged around!
    # above WHERE conditions allow us to use Postgresql in Read Committed (default) mode - see http://www.postgresql.org/files/developer/transactions.pdf
    # *** could do further real-time safety checking on acct.iou_limit too, to be really sure :) ***

    for acct in path[1:len(path):2]:

        # insert PaymentLink
        linkAmount = pathCredit
        if pmt.currencyValue:
            linkAmount *= pmt.currencyValue / acct.currencyValue  # convert path amount to acct units

        cursor.execute(
            linkInsSql,
            (pathId, acct.id, position, linkAmount, -acct.interest,
             acct.sharedData.interest_rate
             ))  # negative interest because this is an outgoing amount
        if not cursor.rowcount == 1:
            raise PaymentError(
                code=LINK_INSERT_FAILED,
                message="Error inserting path link to database.",
                retry=True)  # shouldn't happen
        ##print 'Link %d:' % cursor.fetchone()[0] ###
        ##print '\tAmount: %.14f' % linkAmount
        ##print '\tBalance: %.14f -> %.14f' % (acct.actual_balance, acct.actual_balance - (linkAmount - acct.interest))
        ##print '\tInterest: %.14f' % -acct.interest
        position = position + 1

        # update SharedAccountData balance
        # **** 10e-12 = allow for a minute amount over limit due to rounding error in conversion
        balChange = linkAmount - acct.interest
        cursor.execute(
            balUpdSql,
            (balChange * acct.balance_multiplier, pmt.date,
             acct.shared_data_id, acct.last_update, acct.balance_multiplier,
             balChange - acct.iou_limit - 10e-12))
        if not cursor.rowcount == 1:
            shared = SharedAccountData.objects.get(
                pk=acct.shared_data_id
            )  # find out actual balance, last_update in db
            raise PaymentError(code=BAL_UPDATE_FAILED, # happens if sql conditions fail, great for concurrent transactions!
                message="Error updating account balance in database. SQL: " + \
                balUpdSql % (balChange * acct.balance_multiplier, str(pmt.date), acct.shared_data_id, acct.last_update, \
                acct.balance_multiplier, balChange - acct.iou_limit - 1e-12) + \
                "\nActual balance: %.14f (expected: %.14f)\nIOU limit: %.2f\nLast update: %s (expected %s)" % \
                (shared.balance, acct.actual_balance * acct.balance_multiplier, acct.iou_limit, shared.last_update, acct.last_update),
                retry=True)

    return pathCredit
Example #5
0
def payAlongPath(path, amount, cursor, pmt):
    # use only transaction-safe DB values - means re-get necessary data
    pathCredit = amount
    
    MULTIPLIER, IOU_LIMIT, BALANCE, INTEREST_RATE, LAST_UPDATE, CURR_VALUE = range(6) # indexes on querySql row
    querySql = "SELECT acct.balance_multiplier, acct.iou_limit, shared.balance, shared.interest_rate, shared.last_update, curr.value \
    FROM ripple_account as acct JOIN ripple_sharedaccountdata as shared ON shared.id = acct.shared_data_id \
    JOIN ripple_currencyunit as curr ON curr.id = shared.currency_id WHERE acct.id = %d"

    valueSql = "SELECT value FROM ripple_currencyunit WHERE id = %d"
    
    # first, get value of pmt currency
    cursor.execute(valueSql, (pmt.currency_id,))
    valueRow = cursor.fetchone()
    pmt.currencyValue = valueRow[0] # value

    # next, get acct info for each acct in path and use to determine path credit
    for acct in path[1:len(path):2]: # accts are at odd indices in path
        # get acct info
        cursor.execute(querySql, (acct.id,))
        queryRow = cursor.fetchone()

        # calculate avail credit (in pmt units)
        acct.actual_balance = queryRow[BALANCE] * queryRow[MULTIPLIER]
        acct.last_update = queryRow[LAST_UPDATE]
        # *** causing errors when only one simultaneous payment happening
        #     this is because of django timezone setting -- unset it?  (works on windows)
        if acct.last_update > pmt.date: # can't properly compute interest if someone else already has since the time this payment was supposed to occur at
            print acct.last_update, pmt.date
            raise PaymentError(code=TX_COLLISION, message="Another transaction updated this account since the time we wanted to pay at.", retry=True)
        acct.interest = computeInterest(acct.actual_balance, queryRow[INTEREST_RATE], acct.last_update, pmt.date)
        acct.eff_balance = acct.actual_balance + acct.interest
        acct.availCredit = queryRow[IOU_LIMIT] + acct.eff_balance

        if pmt.currencyValue: # otherwise, no conversions
            # get acct currency conversion value
            acct.currencyValue = queryRow[CURR_VALUE]

            # calculate available credit in pmt units
            acct.availCredit *= acct.currencyValue / pmt.currencyValue # convert to pmt units
            acct.availCredit = float("%.12f" % acct.availCredit) # round path's amount to 12 decimal places so all paths add exactly to the total payment amount in db

        if acct.availCredit < pathCredit:
            pathCredit = acct.availCredit
            # check that pathCredit doesn't convert back too high, otherwise updating balance may fail
            # **** occasionally causing minute amounts of payment to not go through on predicted number of paths
            # **** instead, allow for minute amounts over limit
            ##if pmt.currencyValue: # only check if we're doing conversions
            ##  roundBack = pathCredit * pmt.currencyValue / acct.currencyValue
            ##  roundBack = float("%.12f" % roundBack)
            ##  if roundBack > pathCredit:
            ##    pathCredit -= 0.000000000001 # twelve digits down, subtract one
    if pathCredit <= 0: return 0 # no credit, no payment along this path

    # insert Path into database
    sql = "INSERT INTO ripple_paymentpath (payment_id, amount) VALUES (%d, %.12f);"
    sql += "SELECT currval('ripple_paymentpath_id_seq');" # at the same time get the ID of the path we just inserted
    cursor.execute(sql, (pmt.id, pathCredit))

    # find ID of path we just inserted
    pathId = cursor.fetchone()[0]

    # make payments
    linkInsSql = "INSERT INTO ripple_paymentlink (path_id, payer_account_id, position, amount, interest, interest_rate) " 
    linkInsSql += "VALUES (%d, %d, %d, %.12f, %.12f, %.10f);"
    ##linkInsSql += "SELECT currval('ripple_paymentlink_id_seq');"
    position = 1 # start link position sequence at 1
    balUpdSql = "UPDATE ripple_sharedaccountdata SET balance = balance - %.14f, last_update = '%s' "
    balUpdSql += "WHERE ripple_sharedaccountdata.id = %d "
    #balUpdSql += "AND last_update = '%s' " # make sure last_update stays consistent, otherwise interest won't work.
    balUpdSql += "AND last_update - '%s' <= INTERVAL '0:0:0.000001' " # alternative version because psycopg sometimes loses a microsecond!
    balUpdSql += "AND balance * %d >= %.14f" # % balance_multiplier, minimum balance needed for payment to succeed
    # makes sure there's enough, even if another transaction has fudged around!
    # above WHERE conditions allow us to use Postgresql in Read Committed (default) mode - see http://www.postgresql.org/files/developer/transactions.pdf
    # *** could do further real-time safety checking on acct.iou_limit too, to be really sure :) ***

    for acct in path[1:len(path):2]:

        # insert PaymentLink
        linkAmount = pathCredit
        if pmt.currencyValue:
            linkAmount *= pmt.currencyValue / acct.currencyValue # convert path amount to acct units
            
        cursor.execute(linkInsSql, (pathId, acct.id, position, linkAmount, -acct.interest, acct.sharedData.interest_rate)) # negative interest because this is an outgoing amount
        if not cursor.rowcount == 1:
            raise PaymentError(code=LINK_INSERT_FAILED, message="Error inserting path link to database.", retry=True) # shouldn't happen
        ##print 'Link %d:' % cursor.fetchone()[0] ###
        ##print '\tAmount: %.14f' % linkAmount
        ##print '\tBalance: %.14f -> %.14f' % (acct.actual_balance, acct.actual_balance - (linkAmount - acct.interest))
        ##print '\tInterest: %.14f' % -acct.interest
        position = position + 1

        # update SharedAccountData balance
        # **** 10e-12 = allow for a minute amount over limit due to rounding error in conversion
        balChange = linkAmount - acct.interest
        cursor.execute(balUpdSql, (balChange * acct.balance_multiplier, pmt.date, acct.shared_data_id, 
                                                              acct.last_update, acct.balance_multiplier, balChange - acct.iou_limit - 10e-12))
        if not cursor.rowcount == 1:
            shared = SharedAccountData.objects.get(pk=acct.shared_data_id) # find out actual balance, last_update in db
            raise PaymentError(code=BAL_UPDATE_FAILED, # happens if sql conditions fail, great for concurrent transactions!
                message="Error updating account balance in database. SQL: " + \
                balUpdSql % (balChange * acct.balance_multiplier, str(pmt.date), acct.shared_data_id, acct.last_update, \
                acct.balance_multiplier, balChange - acct.iou_limit - 1e-12) + \
                "\nActual balance: %.14f (expected: %.14f)\nIOU limit: %.2f\nLast update: %s (expected %s)" % \
                (shared.balance, acct.actual_balance * acct.balance_multiplier, acct.iou_limit, shared.last_update, acct.last_update),
                retry=True)

    return pathCredit