class CtbCoin(object): """ Coin class for cointip bot """ conn = None conf = None def __init__(self, _conf = None): """ Initialize CtbCoin with given parameters. _conf is a coin config dictionary defined in conf/coins.yml """ # verify _conf is a config dictionary if not _conf or not hasattr(_conf, 'name') or not hasattr(_conf, 'config_file') or not hasattr(_conf, 'txfee'): raise Exception("CtbCoin::__init__(): _conf is empty or invalid") self.conf = _conf # connect to coin daemon try: lg.debug("CtbCoin::__init__(): connecting to %s...", self.conf.name) self.conn = Bitcoind(self.conf.config_file, rpcserver=self.conf.config_rpcserver) except BitcoindException as e: lg.error("CtbCoin::__init__(): error connecting to %s using %s: %s", self.conf.name, self.conf.config_file, e) raise lg.info("CtbCoin::__init__():: connected to %s", self.conf.name) time.sleep(0.5) # set transaction fee lg.info("Setting tx fee of %f", self.conf.txfee) self.conn.settxfee(self.conf.txfee) def getbalance(self, _user = None, _minconf = None, _db = None): """ Get user's tip or withdraw balance. _minconf is number of confirmations to use. Returns (float) balance """ lg.debug("CtbCoin::getbalance(%s, %s)", _user, _minconf) user = self.verify_user(_user=_user) minconf = self.verify_minconf(_minconf=_minconf) balance = float(0) if _db == None: return balance #try: # balance = self.conn.getbalance(user, minconf) #except BitcoindException as e: # lg.error("CtbCoin.getbalance(): error getting %s (minconf=%s) balance for %s: %s", self.conf.name, minconf, user, e) # raise #first get the received by account received = self.getreceivedbyaccount(_user=user, _minconf=minconf) #look up received in database sql = "SELECT * from t_addrs WHERE username = %s AND coin = %s" mysqlrow = _db.execute(sql, (user, self.conf.unit)).fetchone() if not mysqlrow: lg.debug("< CtbCoin::getbalance(%s, %s) DONE (no)", user, coin) return balance else: #get the balance from the database balance = ( received + mysqlrow['tips_received'] ) - ( mysqlrow['addr_sent'] + mysqlrow['tips_sent']) #update received and balance sql = "UPDATE t_addrs SET addr_received = %s, balance = %s WHERE username = %s AND coin = %s" _db.execute(sql, (received, balance, user, self.conf.unit)) lg.debug("< CtbCoin::getbalance(%s) DONE", user) return float(balance) #time.sleep(0.5) #return float(balance) def getreceivedbyaccount(self, _user = None, _minconf = None): """ Get user's tip or withdraw balance. _minconf is number of confirmations to use. Returns (float) balance """ lg.debug("CtbCoin::getreceivedbyaccount(%s, %s)", _user, _minconf) user = self.verify_user(_user=_user) minconf = self.verify_minconf(_minconf=_minconf) received = float(0) try: received = self.conn.getreceivedbyaccount(user, minconf) except BitcoindException as e: lg.error("CtbCoin.getreceivedbyaccount(): error getting %s (minconf=%s) received for %s: %s", self.conf.name, minconf, user, e) raise time.sleep(0.5) return float(received) def sendtouser(self, _userfrom = None, _userto = None, _amount = None, _minconf = 1, _db = None): """ Transfer (move) coins to user Returns (bool) """ lg.debug("CtbCoin::sendtouser(%s, %s, %.9f)", _userfrom, _userto, _amount) userfrom = self.verify_user(_user=_userfrom) userto = self.verify_user(_user=_userto) amount = self.verify_amount(_amount=_amount) if _db == None: return False # dont bother with the coin daemon, we're doing the move in the db # send request to coin daemon #try: # lg.info("CtbCoin::sendtouser(): moving %.9f %s from %s to %s", amount, self.conf.name, userfrom, userto) # result = self.conn.move(userfrom, userto, amount) # time.sleep(0.5) #except Exception as e: # lg.error("CtbCoin::sendtouser(): error moving %.9f %s from %s to %s: %s", amount, self.conf.name, userfrom, userto, e) # return False #subtract the amount from _userfrom in db sql = "SELECT * from t_addrs WHERE username = %s AND coin = %s" mysqlrow = _db.execute(sql, (userfrom, self.conf.unit)).fetchone() if not mysqlrow: lg.debug("< CtbCoin::sendtouser from(%s, %s) DONE (no)", userfrom, self.conf.unit) return None else: tipssent = mysqlrow['tips_sent'] tipssent += amount balance = ( mysqlrow['addr_received'] + mysqlrow['tips_received'] ) - ( mysqlrow['addr_sent'] + tipssent ) sql = "UPDATE t_addrs SET tips_sent = %s, balance = %s WHERE username = %s AND coin = %s" _db.execute(sql, (tipssent, balance, userfrom, self.conf.unit)) #add the ammount to _userto in db sql = "SELECT * from t_addrs WHERE username = %s AND coin = %s" mysqlrow = _db.execute(sql, (userto, self.conf.unit)).fetchone() if not mysqlrow: lg.debug("< CtbCoin::sendtouser to(%s, %s) DONE (no)", userto, self.conf.unit) return None else: tipsrec = mysqlrow['tips_received'] tipsrec += amount balance = ( mysqlrow['addr_received'] + tipsrec ) - ( mysqlrow['addr_sent'] + mysqlrow['tips_sent']) sql = "UPDATE t_addrs SET tips_received = %s, balance = %s WHERE username = %s AND coin = %s" _db.execute(sql, (tipsrec, balance, userto, self.conf.unit)) return True def sendtoaddr(self, _userfrom = None, _addrto = None, _amount = None, _db = None): """ Send coins to address Returns (string) txid """ lg.debug("CtbCoin::sendtoaddr(%s, %s, %.9f)", _userfrom, _addrto, _amount) if _db == None: return False userfrom = self.verify_user(_user=_userfrom) addrto = self.verify_addr(_addr=_addrto) amount = self.verify_amount(_amount=_amount) minconf = self.verify_minconf(_minconf=self.conf.minconf.withdraw) txid = "" #TODO - add the withdrawn ammount to the addr_sent # send request to coin daemon try: lg.info("CtbCoin::sendtoaddr(): sending %.9f %s from %s to %s", amount, self.conf.name, userfrom, addrto) # Unlock wallet, if applicable if hasattr(self.conf, 'walletpassphrase'): lg.debug("CtbCoin::sendtoaddr(): unlocking wallet...") self.conn.walletpassphrase(self.conf.walletpassphrase, 10) # Perform transaction lg.debug("CtbCoin::sendtoaddr(): calling sendtoaddress()...") #txid = self.conn.sendfrom(userfrom, addrto, amount, minconf) txid = self.conn.sendtoaddress(addrto, amount) time.sleep(0.5) # Lock wallet, if applicable if hasattr(self.conf, 'walletpassphrase'): lg.debug("CtbCoin::sendtoaddr(): locking wallet...") self.conn.walletlock() time.sleep(0.5) #subtract the amount from _userfrom in db sql = "SELECT * from t_addrs WHERE username = %s AND coin = %s" mysqlrow = _db.execute(sql, (userfrom, self.conf.unit)).fetchone() if not mysqlrow: lg.debug("< CtbCoin::sendtoaddr from(%s, %s) DONE (no)", userfrom, self.conf.unit) return None else: addrsent = mysqlrow['addr_sent'] addrsent += (amount + self.conf.txfee) balance = ( mysqlrow['addr_received'] + mysqlrow['tips_received'] ) - ( addrsent + mysqlrow['tips_sent']) sql = "UPDATE t_addrs SET addr_sent = %s, balance = %s WHERE username = %s AND coin = %s" _db.execute(sql, (addrsent, balance, userfrom, self.conf.unit)) except Exception as e: lg.error("CtbCoin::sendtoaddr(): error sending %.9f %s from %s to %s: %s", amount, self.conf.name, userfrom, addrto, e) raise time.sleep(0.5) return str(txid) def validateaddr(self, _addr = None): """ Verify that _addr is a valid coin address Returns (bool) """ lg.debug("CtbCoin::validateaddr(%s)", _addr) addr = self.verify_addr(_addr=_addr) addr_valid = self.conn.validateaddress(addr) time.sleep(0.5) if not addr_valid.has_key('isvalid') or not addr_valid['isvalid']: lg.debug("CtbCoin::validateaddr(%s): not valid", addr) return False else: lg.debug("CtbCoin::validateaddr(%s): valid", addr) return True def getnewaddr(self, _user = None): """ Generate a new address for _user Returns (string) address """ user = self.verify_user(_user=_user) addr = "" counter = 0 while True: try: # Unlock wallet for keypoolrefill if hasattr(self.conf, 'walletpassphrase'): self.conn.walletpassphrase(self.conf.walletpassphrase, 1) # Generate new address addr = self.conn.getnewaddress(user) # Lock wallet if hasattr(self.conf, 'walletpassphrase'): self.conn.walletlock() if not addr: raise Exception("CtbCoin::getnewaddr(%s): empty addr", user) time.sleep(0.1) return str(addr) except BitcoindException as e: lg.error("CtbCoin::getnewaddr(%s): BitcoindException: %s", user, e) raise except CannotSendRequest as e: if counter < 3: lg.warning("CtbCoin::getnewaddr(%s): CannotSendRequest, retrying") counter += 1 time.sleep(10) continue else: raise except Exception as e: if str(e) == "timed out" and counter < 3: lg.warning("CtbCoin::getnewaddr(%s): timed out, retrying") counter += 1 time.sleep(10) continue else: lg.error("CtbCoin::getnewaddr(%s): Exception: %s", user, e) raise def verify_user(self, _user = None): """ Verify and return a username """ if not _user or not type(_user) in [str, unicode]: raise Exception("CtbCoin::verify_user(): _user wrong type (%s) or empty (%s)", type(_user), _user) return str(_user.lower()) def verify_addr(self, _addr = None): """ Verify and return coin address """ if not _addr or not type(_addr) in [str, unicode]: raise Exception("CtbCoin::verify_addr(): _addr wrong type (%s) or empty (%s)", type(_addr),_addr) return re.escape(str(_addr)) def verify_amount(self, _amount = None): """ Verify and return amount """ if not _amount or not type(_amount) in [int, float] or not _amount > 0: raise Exception("CtbCoin::verify_amount(): _amount wrong type (%s), empty, or negative (%s)", type(_amount), _amount) return _amount def verify_minconf(self, _minconf = None): """ Verify and return minimum number of confirmations """ if not _minconf or not type(_minconf) == int or not _minconf >= 0: raise Exception("CtbCoin::verify_minconf(): _minconf wrong type (%s), empty, or negative (%s)", type(_minconf), _minconf) return _minconf