mirror of
https://github.com/jwinterm/python-monero.git
synced 2026-10-17 05:51:43 +02:00
Also add a site salt to create them, so a given user ID will yield different payment IDs for different sites.
249 lines
8.6 KiB
Python
249 lines
8.6 KiB
Python
#!/bin/python
|
|
#
|
|
# python-monero - utility functions
|
|
# Copyright 2014,2015 moneromooo
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without modification, are
|
|
# permitted provided that the following conditions are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
# conditions and the following disclaimer.
|
|
#
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
# of conditions and the following disclaimer in the documentation and/or other
|
|
# materials provided with the distribution.
|
|
#
|
|
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
# used to endorse or promote products derived from this software without specific
|
|
# prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
|
|
import redis
|
|
import hashlib
|
|
import json
|
|
import httplib
|
|
import time
|
|
import string
|
|
from Crypto.Random.random import getrandbits
|
|
from decimal import *
|
|
from log import log_error, log_warn, log_info, log_log
|
|
from redisdb import *
|
|
|
|
address_length = [95, 95] # min/max size of addresses
|
|
address_prefix = ['4', '9'] # allowed prefixes of addresses
|
|
|
|
cached_wallet_balance=None
|
|
cached_wallet_unlocked_balance=None
|
|
cached_wallet_balance_timestamp=None
|
|
|
|
def GetPaymentIDFromUserID(user_id,deterministic,site_salt=''):
|
|
salt="nveuweaiirv-"+site_salt+'-'
|
|
if deterministic:
|
|
s = salt + user_id
|
|
else:
|
|
s = salt + str(getrandbits(128)) + '-' + user_id
|
|
p = hashlib.sha256(s).hexdigest()
|
|
try:
|
|
redis_hset("paymentid",p,user_id)
|
|
except Exception,e:
|
|
log_error('GetPaymentIDFromUserID: failed to set payment ID for %s to redis: %s' % (user_id,str(e)))
|
|
return p
|
|
|
|
def GetUserIDFromPaymentID(paymentid):
|
|
if not redis_hexists("paymentid",paymentid):
|
|
log_log('PaymentID %s not found' % paymentid)
|
|
return None
|
|
identity = redis_hget("paymentid",paymentid)
|
|
log_log('PaymentID %s => %s' % (paymentid, str(identity)))
|
|
return identity
|
|
|
|
|
|
def IsValidAddress(address):
|
|
if len(address) < address_length[0] or len(address) > address_length[1]:
|
|
return False
|
|
for prefix in address_prefix:
|
|
if address.startswith(prefix):
|
|
return True
|
|
return False
|
|
|
|
def IsValidPaymentID(payment_id):
|
|
if len(payment_id)!=64:
|
|
return False
|
|
for char in payment_id:
|
|
if char not in string.hexdigits:
|
|
return False
|
|
return True
|
|
|
|
def SendJSONRPCCommand(host,port,method,params):
|
|
try:
|
|
http = httplib.HTTPConnection(host,port,timeout=20)
|
|
except Exception,e:
|
|
log_error('SendJSONRPCCommand: Error connecting to %s:%u: %s' % (host, port, str(e)))
|
|
raise
|
|
d = dict(id="0",jsonrpc="2.0",method=method,params=params)
|
|
try:
|
|
j = json.dumps(d).encode()
|
|
except Exception,e:
|
|
log_error('SendJSONRPCCommand: Failed to encode JSON: %s' % str(e))
|
|
http.close()
|
|
raise
|
|
log_log('SendJSONRPCCommand: Sending json as body: %s' % j)
|
|
headers = None
|
|
try:
|
|
http.request("POST","/json_rpc",body=j)
|
|
except Exception,e:
|
|
log_error('SendJSONRPCCommand: Failed to post request: %s' % str(e))
|
|
http.close()
|
|
raise
|
|
response = http.getresponse()
|
|
if response.status != 200:
|
|
log_error('SendJSONRPCCommand: Error, received reply status %s' % str(response.status))
|
|
http.close()
|
|
raise RuntimeError("Error "+response.status)
|
|
s = response.read()
|
|
log_log('SendJSONRPCCommand: Received reply status %s: %s' % (response.status, str(s).replace('\r\n',' ').replace('\n',' ')))
|
|
try:
|
|
j = json.loads(s)
|
|
except Exception,e:
|
|
log_error('SendJSONRPCCommand: Failed to decode JSON: %s' % str(e))
|
|
http.close()
|
|
raise
|
|
http.close()
|
|
return j
|
|
|
|
def SendHTMLCommand(host,port,method):
|
|
try:
|
|
http = httplib.HTTPConnection(host,port,timeout=20)
|
|
except Exception,e:
|
|
log_error('SendHTMLCommand: Error connecting to %s:%u: %s' % (host, port, str(e)))
|
|
raise
|
|
headers = None
|
|
try:
|
|
http.request("POST","/"+method)
|
|
except Exception,e:
|
|
log_error('SendHTMLCommand: Failed to post request: %s' % str(e))
|
|
http.close()
|
|
raise
|
|
response = http.getresponse()
|
|
if response.status != 200:
|
|
log_error('SendHTMLCommand: Error, received reply status %s' % str(response.status))
|
|
http.close()
|
|
raise RuntimeError("Error "+response.status)
|
|
s = response.read()
|
|
log_log('SendHTMLCommand: Received reply status %s: %s' % (response.status,s.replace('\r\n',' ').replace('\n',' ')))
|
|
try:
|
|
j = json.loads(s)
|
|
except Exception,e:
|
|
log_error('SendHTMLCommand: Failed to decode JSON: %s' % str(e))
|
|
http.close()
|
|
raise
|
|
http.close()
|
|
return j
|
|
|
|
def RetrieveWalletBalance(wallet_host,wallet_port,force_refresh=False):
|
|
global cached_wallet_balance, cached_wallet_unlocked_balance, cached_wallet_balance_timestamp
|
|
if not force_refresh and cached_wallet_balance_timestamp and time.time()-cached_wallet_balance_timestamp < 35:
|
|
return cached_wallet_balance, cached_wallet_unlocked_balance
|
|
|
|
j = SendJSONRPCCommand(wallet_host,wallet_port,"getbalance",None)
|
|
if not "result" in j:
|
|
log_error('RetrieveWalletBalance: result not found in reply')
|
|
raise RuntimeError("")
|
|
return
|
|
result = j["result"]
|
|
if not "balance" in result:
|
|
log_error('RetrieveWalletBalance: balance not found in result')
|
|
raise RuntimeError("")
|
|
return
|
|
if not "unlocked_balance" in result:
|
|
log_error('RetrieveWalletBalance: unlocked_balance not found in result')
|
|
raise RuntimeError("")
|
|
return
|
|
balance = result["balance"]
|
|
unlocked_balance = result["unlocked_balance"]
|
|
log_log('RetrieveWalletBalance: balance: %s' % str(balance))
|
|
log_log('RetrieveWalletBalance: unlocked_balance: %s' % str(unlocked_balance))
|
|
pending = long(balance)-long(unlocked_balance)
|
|
if pending < 0:
|
|
log_error('RetrieveWalletBalance: Negative pending balance! balance %s, unlocked %s' % (str(balance),str(unlocked_balance)))
|
|
raise RuntimeError("")
|
|
return
|
|
cached_wallet_balance_timestamp=time.time()
|
|
cached_wallet_balance=balance
|
|
cached_wallet_unlocked_balance=unlocked_balance
|
|
return balance, unlocked_balance
|
|
|
|
# Code taken from the Python documentation
|
|
def moneyfmt(value, places=2, curr='', sep=',', dp='.',
|
|
pos='', neg='-', trailneg=''):
|
|
"""Convert Decimal to a money formatted string.
|
|
|
|
places: required number of places after the decimal point
|
|
curr: optional currency symbol before the sign (may be blank)
|
|
sep: optional grouping separator (comma, period, space, or blank)
|
|
dp: decimal point indicator (comma or period)
|
|
only specify as blank when places is zero
|
|
pos: optional sign for positive numbers: '+', space or blank
|
|
neg: optional sign for negative numbers: '-', '(', space or blank
|
|
trailneg:optional trailing minus indicator: '-', ')', space or blank
|
|
|
|
>>> d = Decimal('-1234567.8901')
|
|
>>> moneyfmt(d, curr='$')
|
|
'-$1,234,567.89'
|
|
>>> moneyfmt(d, places=0, sep='.', dp='', neg='', trailneg='-')
|
|
'1.234.568-'
|
|
>>> moneyfmt(d, curr='$', neg='(', trailneg=')')
|
|
'($1,234,567.89)'
|
|
>>> moneyfmt(Decimal(123456789), sep=' ')
|
|
'123 456 789.00'
|
|
>>> moneyfmt(Decimal('-0.02'), neg='<', trailneg='>')
|
|
'<0.02>'
|
|
|
|
"""
|
|
q = Decimal(10) ** -places # 2 places --> '0.01'
|
|
sign, digits, exp = value.quantize(q).as_tuple()
|
|
result = []
|
|
digits = map(str, digits)
|
|
build, next = result.append, digits.pop
|
|
if sign:
|
|
build(trailneg)
|
|
for i in range(places):
|
|
build(next() if digits else '0')
|
|
build(dp)
|
|
if not digits:
|
|
build('0')
|
|
i = 0
|
|
while digits:
|
|
build(next())
|
|
i += 1
|
|
if i == 3 and digits:
|
|
i = 0
|
|
build(sep)
|
|
build(curr)
|
|
build(neg if sign else pos)
|
|
s = ''.join(reversed(result))
|
|
|
|
if dp in s:
|
|
s=s.strip('0').rstrip(dp)
|
|
if s=="" or s[0]==dp:
|
|
s="0"+s
|
|
return s
|
|
|
|
def AmountToString(amount):
|
|
if amount == None:
|
|
amount = 0
|
|
lamount=long(amount)
|
|
samount = moneyfmt(Decimal(lamount)/Decimal(1e12),places=12) + " monero"
|
|
return samount
|
|
|