def contend(self, slot_id, tx_fee=DEFAULT_CONTEND_SLOT_FEE, fee_scale=DEFAULT_FEE_SCALE, timestamp=0): if not self.privateKey: msg = 'Private key required' pytvspos.throw_error(msg, MissingPrivateKeyException) elif CHECK_FEE_SCALE and fee_scale != DEFAULT_FEE_SCALE: msg = 'Wrong fee scale (currently, fee scale must be %d).' % DEFAULT_FEE_SCALE pytvspos.throw_error(msg, InvalidParameterException) elif self.check_contend(slot_id, tx_fee): if timestamp == 0: timestamp = int(time.time() * 1000000000) sData = struct.pack(">B", CONTEND_SLOT_TX_TYPE) + \ struct.pack(">I", slot_id) + \ struct.pack(">Q", tx_fee) + \ struct.pack(">H", fee_scale) + \ struct.pack(">Q", timestamp) signature = bytes2str(sign(self.privateKey, sData)) data = json.dumps({ "senderPublicKey": self.publicKey, "fee": tx_fee, "feeScale": fee_scale, "slotId": slot_id, "timestamp": timestamp, "signature": signature }) return self.wrapper.request('/spos/broadcast/contend', data)
def check_contend(self, slot_id, tx_fee): if tx_fee < DEFAULT_CONTEND_SLOT_FEE: msg = 'Transaction fee must be >= %d' % DEFAULT_CONTEND_SLOT_FEE pytvspos.throw_error(msg, InvalidParameterException) return False if slot_id >= 60 or slot_id < 0: msg = 'Slot id must be in 0 to 59' pytvspos.throw_error(msg, InvalidParameterException) return False if is_offline(): # if offline, skip other check return True balance_detail = self.get_info() min_effective_balance = MIN_CONTEND_SLOT_BALANCE + tx_fee if balance_detail["effective"] < min_effective_balance: msg = 'Insufficient TV balance. (The effective balance must be >= %d)' % min_effective_balance pytvspos.throw_error(msg, InvalidParameterException) return False slot_info = self.chain.slot_info(slot_id) if not slot_info or slot_info.get("mintingAverageBalance") is None: msg = 'Failed to get slot minting average balance' pytvspos.throw_error(msg, NetworkException) return False elif slot_info["mintingAverageBalance"] >= balance_detail[ "mintingAverage"]: msg = 'The minting average balance of slot %d is greater than or equals to yours. ' \ 'You will contend this slot failed.' % slot_id pytvspos.throw_error(msg, InsufficientBalanceException) return False return True
def calculate_minting_total(self, start_height, end_height): self.start_height = start_height self.end_height = end_height self.minting_total = 0 # must be ready before call this function. now_height = self.chain.height() if not self.address: raise InvalidAddressException("No address") elif start_height <= 0: msg = 'start height must be > 0' pytvspos.throw_error(msg, InvalidParameterException) elif end_height > now_height: msg = 'end height must be <= now_height' % self.now_height pytvspos.throw_error(msg, InvalidParameterException) else: print('calculating', start_height, '==>', end_height) begin = start_height REQSIZE = 100 while begin+REQSIZE <= end_height: self.minting_total += self.calculate_minting(begin, begin+REQSIZE-1) begin += REQSIZE self.minting_total += self.calculate_minting(begin, end_height) print(self.start_height, "==>", self.end_height, "minting_total:", self.minting_total)
def height(self): if is_offline(): pytvspos.throw_error("Cannot check height in offline mode.", NetworkException) return 0 else: return self.api_wrapper.request('/blocks/height')['height']
def balance_detail(self): try: resp = self.wrapper.request('/addresses/balance/details/%s' % self.address) logging.debug(resp) return resp except Exception as ex: msg = "Failed to get balance detail. ({})".format(ex) pytvspos.throw_error(msg, NetworkException) return None
def get_connected_peers(self): if is_offline(): pytvspos.throw_error("Cannot check peers in offline mode.", NetworkException) return [] response = self.api_wrapper.request('/peers/connected') if not response.get("peers"): return [] else: return [peer["address"] for peer in response.get("peers")]
def check_node(self, other_node_host=None): if is_offline(): pytvspos.throw_error("Cannot check node in offline mode.", NetworkException) return False if other_node_host: res = self.chain.check_with_other_node(other_node_host) else: res = self.chain.self_check() # add more check if need return res
def balance(self, confirmations=0): if is_offline(): pytvspos.throw_error("Cannot check balance in offline mode.", NetworkException) return 0 try: confirmations_str = '' if confirmations == 0 else '/%d' % confirmations resp = self.wrapper.request('/addresses/balance/%s%s' % (self.address, confirmations_str)) logging.debug(resp) return resp['balance'] except Exception as ex: msg = "Failed to get balance. ({})".format(ex) pytvspos.throw_error(msg, NetworkException) return 0
def fetch_minting_average_balance(self): if is_offline(): pytvspos.throw_error("Cannot fetch minting_average_balance in offline mode.", NetworkException) return 0 try: resp = self.wrapper.request('/addresses/balance/details/%s' % self.address) logging.debug(resp) #print(resp) self.minting_average_balance = resp['mintingAverage'] self.available_balance = resp['available'] #print(self.minting_average_balance) return None except Exception as ex: msg = "Failed to get minting average balance. ({})".format(ex) pytvspos.throw_error(msg, NetworkException) return 0
def check_with_other_node(self, node_host, super_node_num=DEFAULT_SUPER_NODE_NUM): if is_offline(): pytvspos.throw_error("Cannot check height in offline mode.", NetworkException) return False try: h1 = self.height() except NetworkException: logging.error("Fail to connect {}.".format(node_host)) return False try: other_api = Wrapper(node_host) h2 = other_api.request('/blocks/height')['height'] except NetworkException: logging.error("Fail to connect {}.".format(node_host)) return False # Add more check if need return h2 - h1 <= super_node_num
def lease_cancel(self, lease_id, tx_fee=DEFAULT_CANCEL_LEASE_FEE, fee_scale=DEFAULT_FEE_SCALE, timestamp=0): decode_lease_id = base58.b58decode(lease_id) if not self.privateKey: msg = 'Private key required' pytvspos.throw_error(msg, MissingPrivateKeyException) elif tx_fee < DEFAULT_CANCEL_LEASE_FEE: msg = 'Transaction fee must be > %d' % DEFAULT_CANCEL_LEASE_FEE pytvspos.throw_error(msg, InvalidParameterException) elif len(decode_lease_id) != LEASE_TX_ID_BYTES: msg = 'Invalid lease transaction id' pytvspos.throw_error(msg, InvalidParameterException) elif CHECK_FEE_SCALE and fee_scale != DEFAULT_FEE_SCALE: msg = 'Wrong fee scale (currently, fee scale must be %d).' % DEFAULT_FEE_SCALE pytvspos.throw_error(msg, InvalidParameterException) elif not is_offline() and self.balance() < tx_fee: msg = 'Insufficient TV balance' pytvspos.throw_error(msg, InsufficientBalanceException) else: if timestamp == 0: timestamp = int(time.time() * 1000000000) sData = struct.pack(">B", LEASE_CANCEL_TX_TYPE) + \ struct.pack(">Q", tx_fee) + \ struct.pack(">H", fee_scale) + \ struct.pack(">Q", timestamp) + \ decode_lease_id signature = bytes2str(sign(self.privateKey, sData)) data = json.dumps({ "senderPublicKey": self.publicKey, "txId": lease_id, "fee": tx_fee, "feeScale": fee_scale, "timestamp": timestamp, "signature": signature }) req = self.wrapper.request('/leasing/broadcast/cancel', data) return req
def release(self, slot_id, tx_fee=DEFAULT_RELEASE_SLOT_FEE, fee_scale=DEFAULT_FEE_SCALE, timestamp=0): if not self.privateKey: msg = 'Private key required' pytvspos.throw_error(msg, MissingPrivateKeyException) elif tx_fee < DEFAULT_RELEASE_SLOT_FEE: msg = 'Transaction fee must be >= %d' % DEFAULT_RELEASE_SLOT_FEE pytvspos.throw_error(msg, InvalidParameterException) elif slot_id >= 60 or slot_id < 0: msg = 'Slot id must be in 0 to 59' pytvspos.throw_error(msg, InvalidParameterException) elif CHECK_FEE_SCALE and fee_scale != DEFAULT_FEE_SCALE: msg = 'Wrong fee scale (currently, fee scale must be %d).' % DEFAULT_FEE_SCALE pytvspos.throw_error(msg, InvalidParameterException) elif not is_offline() and self.balance() < tx_fee: msg = 'Insufficient TV balance' pytvspos.throw_error(msg, InsufficientBalanceException) else: if timestamp == 0: timestamp = int(time.time() * 1000000000) sData = struct.pack(">B", RELEASE_SLOT_TX_TYPE) + \ struct.pack(">I", slot_id) + \ struct.pack(">Q", tx_fee) + \ struct.pack(">H", fee_scale) + \ struct.pack(">Q", timestamp) signature = bytes2str(sign(self.privateKey, sData)) data = json.dumps({ "senderPublicKey": self.publicKey, "fee": tx_fee, "feeScale": fee_scale, "slotId": slot_id, "timestamp": timestamp, "signature": signature }) return self.wrapper.request('/spos/broadcast/release', data)
def fetch_leases_to_pay(self, last_height): if is_offline(): pytvspos.throw_error("Cannot fetch leases_to_pay in offline mode.", NetworkException) try: resp = self.wrapper.request('/transactions/activeLeaseList/%s' % self.address) logging.debug(resp) #print(resp[0]) except Exception as ex: msg = "Failed to get activeLeaseList. ({})".format(ex) pytvspos.throw_error(msg, NetworkException) self.leases_to_pay = [] self.leases_total = 0 for leaseidx in range(len(resp[0])): lease = resp[0][leaseidx] if lease['recipient']==self.address and lease['type']==3 and lease['height']<last_height: lease_to_pay = {} address = self.chain.public_key_to_address(base58.b58decode(lease['proofs'][0]['publicKey'])) lease_to_pay['lease_id'] = lease['id'] lease_to_pay['address'] = address lease_to_pay['lease_height'] = lease['height'] lease_to_pay['amount'] = lease['amount'] self.leases_to_pay.append(lease_to_pay) self.leases_total += lease_to_pay['amount']
def check_tx(self, tx_id, confirmations=0): """Confirm tx on chain. Return True if Transaction is fully confirmed. Return False if Transaction is sent but not confirmed or failed. Return None if Transaction does not exist! """ if is_offline(): pytvspos.throw_error("Cannot check transaction in offline mode.", NetworkException) return None utx_res = self.chain.unconfirmed_tx(tx_id) if "id" in utx_res: logging.error( "Transaction {} is pending in UTX pool.".format(tx_id)) return False else: tx_res = self.chain.tx(tx_id) if tx_res.get("status") == "Success": tx_height = tx_res["height"] cur_height = self.chain.height() if cur_height >= tx_height + confirmations: logging.debug( "Transaction {} is fully confirmed.".format(tx_id)) return True else: logging.info( "Transaction {} is sent but not fully confirmed.". format(tx_id)) return False elif "id" not in tx_res: logging.error("Transaction does not exist!") logging.debug("Tx API response: {}".format(tx_res)) return None else: logging.error("Transaction failed to process!") logging.debug("Tx API response: {}".format(tx_res)) return False
def get_tx_history(self, limit=100, type_filter=PAYMENT_TX_TYPE): if is_offline(): pytvspos.throw_error("Cannot check history in offline mode.", NetworkException) return [] if not self.address: msg = 'Address required' pytvspos.throw_error(msg, MissingAddressException) elif limit > MAX_TX_HISTORY_LIMIT: msg = 'Too big sequences requested (Max limitation is %d).' % MAX_TX_HISTORY_LIMIT pytvspos.throw_error(msg, InvalidParameterException) else: url = '/transactions/address/{}/limit/{}'.format( self.address, limit) resp = self.wrapper.request(url) if isinstance(resp, list) and type_filter: resp = [tx for tx in resp[0] if tx['type'] == type_filter] return resp
def get_info(self): if not (self.address and self.publicKey): msg = 'Address required' pytvspos.throw_error(msg, MissingAddressException) return None if not self.publicKey: msg = 'Public key and address required' pytvspos.throw_error(msg, MissingPublicKeyException) return None if is_offline(): info = {"publicKey": self.publicKey, "address": self.address} return info info = self.balance_detail() if not info: msg = 'Failed to get balance detail' pytvspos.throw_error(msg, NetworkException) else: info["publicKey"] = self.publicKey return info
def lease(self, recipient, amount, tx_fee=DEFAULT_LEASE_FEE, fee_scale=DEFAULT_FEE_SCALE, timestamp=0): if not self.privateKey: msg = 'Private key required' pytvspos.throw_error(msg, MissingPrivateKeyException) if not self.chain.validate_address(recipient.address): msg = 'Invalid recipient address' pytvspos.throw_error(msg, InvalidAddressException) elif amount <= 0: msg = 'Amount must be > 0' pytvspos.throw_error(msg, InvalidParameterException) elif tx_fee < DEFAULT_LEASE_FEE: msg = 'Transaction fee must be >= %d' % DEFAULT_LEASE_FEE pytvspos.throw_error(msg, InvalidParameterException) elif CHECK_FEE_SCALE and fee_scale != DEFAULT_FEE_SCALE: msg = 'Wrong fee scale (currently, fee scale must be %d).' % DEFAULT_FEE_SCALE pytvspos.throw_error(msg, InvalidParameterException) elif not is_offline() and self.balance() < amount + tx_fee: msg = 'Insufficient TV balance' pytvspos.throw_error(msg, InsufficientBalanceException) else: if timestamp == 0: timestamp = int(time.time() * 1000000000) sData = struct.pack(">B", LEASE_TX_TYPE) + \ base58.b58decode(recipient.address) + \ struct.pack(">Q", amount) + \ struct.pack(">Q", tx_fee) + \ struct.pack(">H", fee_scale) + \ struct.pack(">Q", timestamp) signature = bytes2str(sign(self.privateKey, sData)) data = json.dumps({ "senderPublicKey": self.publicKey, "recipient": recipient.address, "amount": amount, "fee": tx_fee, "feeScale": fee_scale, "timestamp": timestamp, "signature": signature }) req = self.wrapper.request('/leasing/broadcast/lease', data) return req
def dbput(self, db_key, db_data, db_data_type="ByteArray", tx_fee=DEFAULT_DBPUT_FEE, fee_scale=DEFAULT_FEE_SCALE, timestamp=0): if not self.privateKey: msg = 'Private key required' pytvspos.throw_error(msg, MissingPrivateKeyException) elif tx_fee < DEFAULT_DBPUT_FEE: msg = 'Transaction fee must be >= %d' % DEFAULT_DBPUT_FEE pytvspos.throw_error(msg, InvalidParameterException) elif len(db_key) > MAX_DB_KEY_SIZE or len(db_key) < MIN_DB_KEY_SIZE: msg = 'DB key length must be greater than %d and smaller than %d' % ( MIN_DB_KEY_SIZE, MAX_ATTACHMENT_SIZE) pytvspos.throw_error(msg, InvalidParameterException) elif CHECK_FEE_SCALE and fee_scale != DEFAULT_FEE_SCALE: msg = 'Wrong fee scale (currently, fee scale must be %d).' % DEFAULT_FEE_SCALE pytvspos.throw_error(msg, InvalidParameterException) elif not is_offline() and self.balance() < tx_fee: msg = 'Insufficient TV balance' pytvspos.throw_error(msg, InsufficientBalanceException) else: if timestamp == 0: timestamp = int(time.time() * 1000000000) # "ByteArray" is the only supported type in first version if db_data_type == "ByteArray": data_type_id = b'\x01' # TODO: add more DB data type here else: msg = 'Unsupported data type: {}'.format(db_data_type) pytvspos.throw_error(msg, InvalidParameterException) return sData = struct.pack(">B", DBPUT_TX_TYPE) + \ struct.pack(">H", len(db_key)) + \ str2bytes(db_key) + \ struct.pack(">H", len(db_data)+1) + \ data_type_id + \ str2bytes(db_data) + \ struct.pack(">Q", tx_fee) + \ struct.pack(">H", fee_scale) + \ struct.pack(">Q", timestamp) signature = bytes2str(sign(self.privateKey, sData)) data = json.dumps({ "senderPublicKey": self.publicKey, "dbKey": db_key, "dataType": db_data_type, "data": db_data, "fee": tx_fee, "feeScale": fee_scale, "timestamp": timestamp, "signature": signature }) return self.wrapper.request('/database/broadcast/put', data)