From ebc508a12abd4b314697bbc998471dddfae40ec0 Mon Sep 17 00:00:00 2001 From: Holger <holger@nahrstaedt.de> Date: Wed, 26 Sep 2018 00:14:53 +0200 Subject: [PATCH] Release of 0.20.0 * Fully supporting hf20 * add get_resource_params(), get_resource_pool(), claim_account(), create_claimed_account() to Steem * fix 30x fee for create_account * add find_rc_accounts() to Blockchain * get_rc(), get_rc_manabar(), get_manabar() added to Account * get_voting_power() fixed --- CHANGELOG.rst | 9 ++ beem/account.py | 63 +++++++++--- beem/blockchain.py | 28 ++++++ beem/cli.py | 8 +- beem/steem.py | 243 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 331 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 589952d8..4ca2b310 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,14 @@ Changelog ========= +0.20.0 +------ +* Fully supporting hf20 +* add get_resource_params(), get_resource_pool(), claim_account(), create_claimed_account() to Steem +* fix 30x fee for create_account +* add find_rc_accounts() to Blockchain +* get_rc(), get_rc_manabar(), get_manabar() added to Account +* get_voting_power() fixed + 0.19.57 -------- * last hf19 release diff --git a/beem/account.py b/beem/account.py index e607552d..922dfeee 100644 --- a/beem/account.py +++ b/beem/account.py @@ -221,6 +221,20 @@ class Account(BlockchainObject): """Deprecated, please use get_similar_account_names""" return self.get_similar_account_names(limit=limit) + def get_rc(self): + """Return RC of account""" + b = Blockchain(steem_instance=self.steem) + return b.find_rc_accounts(self["name"]) + + def get_rc_manabar(self): + """Returns current_mana and max_mana for RC""" + rc_param = self.get_rc() + max_mana = int(rc_param["max_rc"]) + last_update = datetime.utcfromtimestamp(rc_param["rc_manabar"]["last_update_time"]) + diff_in_seconds = (addTzInfo(datetime.utcnow()) - addTzInfo(last_update)).total_seconds() + current_mana = rc_param["rc_manabar"]["current_mana"] + diff_in_seconds * max_mana / (5 * 24 * 60 * 60) + return {"current_mana": current_mana, "max_mana": max_mana} + def get_similar_account_names(self, limit=5): """ Returns ``limit`` account names similar to the current account name as a list @@ -289,6 +303,11 @@ class Account(BlockchainObject): used_kb = bandwidth["used"] / 1024 allocated_mb = bandwidth["allocated"] / 1024 / 1024 last_vote_time_str = formatTimedelta(addTzInfo(datetime.utcnow()) - self["last_vote_time"]) + try: + rc_mana = self.get_rc_manabar() + except: + rc_mana = None + if use_table: t = PrettyTable(["Key", "Value"]) t.align = "l" @@ -302,6 +321,9 @@ class Account(BlockchainObject): if bandwidth["allocated"] > 0: t.add_row(["Remaining Bandwidth", "%.2f %%" % (remaining)]) t.add_row(["used/allocated Bandwidth", "(%.0f kb of %.0f mb)" % (used_kb, allocated_mb)]) + if rc_mana is not None: + t.add_row(["Remaining RC", "%.2f %%" % (rc_mana["current_mana"] / rc_mana["max_mana"] * 100)]) + t.add_row(["used/allocated RC", "(%.0f of %.0f)" % (rc_mana["current_mana"], rc_mana["max_mana"])]) if return_str: return t.get_string(**kwargs) else: @@ -318,7 +340,11 @@ class Account(BlockchainObject): if bandwidth["allocated"] > 0: ret += "--- Bandwidth ---\n" ret += "Remaining: %.2f %%" % (remaining) - ret += " (%.0f kb of %.0f mb)" % (used_kb, allocated_mb) + ret += " (%.0f kb of %.0f mb)\n" % (used_kb, allocated_mb) + if rc_mana is not None: + ret += "--- RC manabar ---\n" + ret += "Remaining: %.2f %%" % (rc_mana["current_mana"] / rc_mana["max_mana"] * 100) + ret += " (%.0f of %.0f)" % (rc_mana["current_mana"], rc_mana["max_mana"]) if return_str: return ret print(ret) @@ -337,38 +363,51 @@ class Account(BlockchainObject): rep = int(self['reputation']) return reputation_to_score(rep) + def get_manabar(self): + """"Return manabar""" + max_mana = int(self.get_vests()) + last_update = datetime.utcfromtimestamp(self["voting_manabar"]["last_update_time"]) + diff_in_seconds = (addTzInfo(datetime.utcnow()) - addTzInfo(last_update)).total_seconds() + current_mana = int(self["voting_manabar"]["current_mana"]) + diff_in_seconds * max_mana / (5 * 24 * 60 * 60) + return {"current_mana": current_mana, "max_mana": max_mana} + def get_voting_power(self, with_regeneration=True): """ Returns the account voting power in the range of 0-100% """ if with_regeneration: regenerated_vp = 0 - if "last_vote_time" in self: + if "voting_manabar" in self: + last_vote_time = datetime.utcfromtimestamp(self["voting_manabar"]["last_update_time"]) + diff_in_seconds = (addTzInfo(datetime.utcnow()) - addTzInfo(last_vote_time)).total_seconds() + regenerated_vp = diff_in_seconds * STEEM_100_PERCENT / STEEM_VOTING_MANA_REGENERATION_SECONDS / 100 + elif "last_vote_time" in self: last_vote_time = self["last_vote_time"] diff_in_seconds = (addTzInfo(datetime.utcnow()) - (last_vote_time)).total_seconds() regenerated_vp = diff_in_seconds * STEEM_100_PERCENT / STEEM_VOTE_REGENERATION_SECONDS / 100 - elif "voting_manabar" in self: - last_vote_time = self["voting_manabar"]["last_update_time"] - diff_in_seconds = (addTzInfo(datetime.utcnow()) - (last_vote_time)).total_seconds() - regenerated_vp = diff_in_seconds * STEEM_100_PERCENT / STEEM_VOTING_MANA_REGENERATION_SECONDS / 100 else: regenerated_vp = 0 if "voting_power" in self: total_vp = (self["voting_power"] / 100 + regenerated_vp) elif "voting_manabar" in self: - total_vp = int(self["voting_manabar"]["current_mana"]) / 100 + regenerated_vp + total_vp = int(self["voting_manabar"]["current_mana"]) / int(self.get_vests()) + regenerated_vp if total_vp > 100: return 100 if total_vp < 0: return 0 return total_vp - def get_steem_power(self, onlyOwnSP=False): - """ Returns the account steem power + def get_vests(self, only_own_vests=False): + """ Returns the account vests """ vests = (self["vesting_shares"]) - if not onlyOwnSP and "delegated_vesting_shares" in self and "received_vesting_shares" in self: + if not only_own_vests and "delegated_vesting_shares" in self and "received_vesting_shares" in self: vests = vests - (self["delegated_vesting_shares"]) + (self["received_vesting_shares"]) - return self.steem.vests_to_sp(vests) + return vests + + def get_steem_power(self, onlyOwnSP=False): + """ Returns the account steem power + """ + return self.steem.vests_to_sp(self.get_vests(only_own_vests=onlyOwnSP)) def get_voting_value_SBD(self, voting_weight=100, voting_power=None, steem_power=None, not_broadcasted_vote=True): """ Returns the account voting value in SBD @@ -437,7 +476,7 @@ class Account(BlockchainObject): missing_vp = voting_power_goal - self.get_voting_power() if missing_vp < 0: return 0 - recharge_seconds = missing_vp * 100 * STEEM_VOTE_REGENERATION_SECONDS / STEEM_100_PERCENT + recharge_seconds = missing_vp * 100 * STEEM_VOTING_MANA_REGENERATION_SECONDS / STEEM_100_PERCENT return timedelta(seconds=recharge_seconds) def get_recharge_time(self, voting_power_goal=100): diff --git a/beem/blockchain.py b/beem/blockchain.py index 347b59c9..5226da94 100644 --- a/beem/blockchain.py +++ b/beem/blockchain.py @@ -910,3 +910,31 @@ class Blockchain(object): return account["accounts"] else: return self.steem.rpc.lookup_accounts(name, limit) + + def find_rc_accounts(self, name): + """ Returns limit similar accounts with name as list + + :param str name: account name to search rc params for (can also be a list of accounts) + :returns: RC params + :rtype: list + + .. code-block:: python + + >>> from beem.blockchain import Blockchain + >>> blockchain = Blockchain() + >>> ret = blockchain.find_rc_accounts("test") + >>> len(ret) == 1 + True + + """ + if not self.steem.is_connected(): + return None + self.steem.rpc.set_next_node_on_empty_reply(False) + if isinstance(name, list): + account = self.steem.rpc.find_rc_accounts({'accounts': name}, api="rc") + if bool(account): + return account["rc_accounts"] + else: + account = self.steem.rpc.find_rc_accounts({'accounts': [name]}, api="rc") + if bool(account): + return account["rc_accounts"][0] diff --git a/beem/cli.py b/beem/cli.py index cddf28a1..eaaae6d0 100644 --- a/beem/cli.py +++ b/beem/cli.py @@ -2004,12 +2004,12 @@ def witnesscreate(witness, pub_signing_key, maximum_block_size, account_creation @cli.command() @click.argument('witness', nargs=1) @click.argument('wif', nargs=1) -@click.option('--account_creation_fee', help='Account creation fee') -@click.option('--account_subsidy_budget', help='Max block size') -@click.option('--account_subsidy_decay', help='Max block size') +@click.option('--account_creation_fee', help='Account creation fee (float)') +@click.option('--account_subsidy_budget', help='Account subisidy per block') +@click.option('--account_subsidy_decay', help='Per block decay of the account subsidy pool') @click.option('--maximum_block_size', help='Max block size') @click.option('--sbd_interest_rate', help='SBD interest rate in percent') -@click.option('--new_signing_key', help='SBD interest rate in percent') +@click.option('--new_signing_key', help='Set new signing key') @click.option('--url', help='Witness URL') def witnessproperties(witness, wif, account_creation_fee, account_subsidy_budget, account_subsidy_decay, maximum_block_size, sbd_interest_rate, new_signing_key, url): """Update witness properties without pricefeed""" diff --git a/beem/steem.py b/beem/steem.py index 4d256f5a..91ea0a48 100644 --- a/beem/steem.py +++ b/beem/steem.py @@ -452,6 +452,14 @@ class Steem(object): dust_threshold = 0 return dust_threshold + def get_resource_params(self): + """Returns the resource parameter""" + return self.rpc.get_resource_params(api="rc")["resource_params"] + + def get_resource_pool(self): + """Returns the resource pool""" + return self.rpc.get_resource_pool(api="rc")["resource_pool"] + def rshares_to_sbd(self, rshares, not_broadcasted_vote=False, use_stored_data=True): """ Calculates the current SBD value of a vote """ @@ -568,7 +576,7 @@ class Steem(object): vesting_shares = int(self.sp_to_vests(steem_power, use_stored_data=use_stored_data)) return self.vests_to_rshares(vesting_shares, voting_power=voting_power, vote_pct=vote_pct, use_stored_data=use_stored_data) - def vests_to_rshares(self, vests, voting_power=STEEM_100_PERCENT, vote_pct=STEEM_100_PERCENT, use_stored_data=True): + def vests_to_rshares(self, vests, voting_power=STEEM_100_PERCENT, vote_pct=STEEM_100_PERCENT, subtract_dust_threshold=True, use_stored_data=True): """ Obtain the r-shares from vests :param number vests: vesting shares @@ -579,7 +587,7 @@ class Steem(object): used_power = self._calc_resulting_vote(voting_power=voting_power, vote_pct=vote_pct, use_stored_data=use_stored_data) # calculate vote rshares rshares = int(math.copysign(vests * 1e6 * used_power / STEEM_100_PERCENT, vote_pct)) - if self.hardfork >= 20: + if subtract_dust_threshold: if abs(rshares) <= self.get_dust_threshold(use_stored_data=use_stored_data): return 0 rshares -= math.copysign(self.get_dust_threshold(use_stored_data=use_stored_data), vote_pct) @@ -985,6 +993,233 @@ class Steem(object): # ------------------------------------------------------------------------- # Account related calls # ------------------------------------------------------------------------- + def claim_account(self, creator, fee="0 STEEM", **kwargs): + """"Claim account for claimed account creation. + + When fee is 0 STEEM a subsidized account is claimed and can be created + later with create_claimed_account. + The number of subsidized account is limited. + + :param str creator: which account should pay the registration fee (RC or STEEM) + (defaults to ``default_account``) + :param str fee: when set to 0 STEEM, claim account is paid by RC + """ + if not creator and config["default_account"]: + creator = config["default_account"] + if not creator: + raise ValueError( + "Not creator account given. Define it with " + + "creator=x, or set the default_account using beempy") + op = { + "fee": Amount(fee, steem_instance=self), + "creator": creator["name"], + "prefix": self.prefix, + } + op = operations.Claim_account(**op) + return self.finalizeOp(op, creator, "active", **kwargs) + + def create_claimed_account( + self, + account_name, + creator=None, + owner_key=None, + active_key=None, + memo_key=None, + posting_key=None, + password=None, + additional_owner_keys=[], + additional_active_keys=[], + additional_posting_keys=[], + additional_owner_accounts=[], + additional_active_accounts=[], + additional_posting_accounts=[], + storekeys=True, + store_owner_key=False, + json_meta=None, + combine_with_claim_account=False, + fee="0 STEEM", + **kwargs + ): + """ Create new claimed account on Steem + + The brainkey/password can be used to recover all generated keys + (see `beemgraphenebase.account` for more details. + + By default, this call will use ``default_account`` to + register a new name ``account_name`` with all keys being + derived from a new brain key that will be returned. The + corresponding keys will automatically be installed in the + wallet. + + .. warning:: Don't call this method unless you know what + you are doing! Be sure to understand what this + method does and where to find the private keys + for your account. + + .. note:: Please note that this imports private keys + (if password is present) into the wallet by + default when nobroadcast is set to False. + However, it **does not import the owner + key** for security reasons by default. + If you set store_owner_key to True, the + owner key is stored. + Do NOT expect to be able to recover it from + the wallet if you lose your password! + + .. note:: Account creations cost a fee that is defined by + the network. If you create an account, you will + need to pay for that fee! + + :param str account_name: (**required**) new account name + :param str json_meta: Optional meta data for the account + :param str owner_key: Main owner key + :param str active_key: Main active key + :param str posting_key: Main posting key + :param str memo_key: Main memo_key + :param str password: Alternatively to providing keys, one + can provide a password from which the + keys will be derived + :param array additional_owner_keys: Additional owner public keys + :param array additional_active_keys: Additional active public keys + :param array additional_posting_keys: Additional posting public keys + :param array additional_owner_accounts: Additional owner account + names + :param array additional_active_accounts: Additional acctive account + names + :param bool storekeys: Store new keys in the wallet (default: + ``True``) + :param bool combine_with_claim_account: When set to True, a + claim_account operation is additionally broadcasted + :param str fee: When combine_with_claim_account is set to True, + this parameter is used for the claim_account operation + + :param str creator: which account should pay the registration fee + (defaults to ``default_account``) + :raises AccountExistsException: if the account already exists on + the blockchain + + """ + if not creator and config["default_account"]: + creator = config["default_account"] + if not creator: + raise ValueError( + "Not creator account given. Define it with " + + "creator=x, or set the default_account using beempy") + if password and (owner_key or active_key or memo_key): + raise ValueError( + "You cannot use 'password' AND provide keys!" + ) + + try: + Account(account_name, steem_instance=self) + raise AccountExistsException + except AccountDoesNotExistsException: + pass + + creator = Account(creator, steem_instance=self) + + " Generate new keys from password" + from beemgraphenebase.account import PasswordKey + if password: + active_key = PasswordKey(account_name, password, role="active", prefix=self.prefix) + owner_key = PasswordKey(account_name, password, role="owner", prefix=self.prefix) + posting_key = PasswordKey(account_name, password, role="posting", prefix=self.prefix) + memo_key = PasswordKey(account_name, password, role="memo", prefix=self.prefix) + active_pubkey = active_key.get_public_key() + owner_pubkey = owner_key.get_public_key() + posting_pubkey = posting_key.get_public_key() + memo_pubkey = memo_key.get_public_key() + active_privkey = active_key.get_private_key() + posting_privkey = posting_key.get_private_key() + owner_privkey = owner_key.get_private_key() + memo_privkey = memo_key.get_private_key() + # store private keys + try: + if storekeys and not self.nobroadcast: + if store_owner_key: + self.wallet.addPrivateKey(str(owner_privkey)) + self.wallet.addPrivateKey(str(active_privkey)) + self.wallet.addPrivateKey(str(memo_privkey)) + self.wallet.addPrivateKey(str(posting_privkey)) + except ValueError as e: + log.info(str(e)) + + elif (owner_key and active_key and memo_key and posting_key): + active_pubkey = PublicKey( + active_key, prefix=self.prefix) + owner_pubkey = PublicKey( + owner_key, prefix=self.prefix) + posting_pubkey = PublicKey( + posting_key, prefix=self.prefix) + memo_pubkey = PublicKey( + memo_key, prefix=self.prefix) + else: + raise ValueError( + "Call incomplete! Provide either a password or public keys!" + ) + owner = format(owner_pubkey, self.prefix) + active = format(active_pubkey, self.prefix) + posting = format(posting_pubkey, self.prefix) + memo = format(memo_pubkey, self.prefix) + + owner_key_authority = [[owner, 1]] + active_key_authority = [[active, 1]] + posting_key_authority = [[posting, 1]] + owner_accounts_authority = [] + active_accounts_authority = [] + posting_accounts_authority = [] + + # additional authorities + for k in additional_owner_keys: + owner_key_authority.append([k, 1]) + for k in additional_active_keys: + active_key_authority.append([k, 1]) + for k in additional_posting_keys: + posting_key_authority.append([k, 1]) + + for k in additional_owner_accounts: + addaccount = Account(k, steem_instance=self) + owner_accounts_authority.append([addaccount["name"], 1]) + for k in additional_active_accounts: + addaccount = Account(k, steem_instance=self) + active_accounts_authority.append([addaccount["name"], 1]) + for k in additional_posting_accounts: + addaccount = Account(k, steem_instance=self) + posting_accounts_authority.append([addaccount["name"], 1]) + if combine_with_claim_account: + op = { + "fee": Amount(fee, steem_instance=self), + "creator": creator["name"], + "prefix": self.prefix, + } + op = operations.Claim_account(**op) + ops = [op] + op = { + "creator": creator["name"], + "new_account_name": account_name, + 'owner': {'account_auths': owner_accounts_authority, + 'key_auths': owner_key_authority, + "address_auths": [], + 'weight_threshold': 1}, + 'active': {'account_auths': active_accounts_authority, + 'key_auths': active_key_authority, + "address_auths": [], + 'weight_threshold': 1}, + 'posting': {'account_auths': active_accounts_authority, + 'key_auths': posting_key_authority, + "address_auths": [], + 'weight_threshold': 1}, + 'memo_key': memo, + "json_metadata": json_meta or {}, + "prefix": self.prefix, + } + op = operations.Create_claimed_account(**op) + if combine_with_claim_account: + ops.append(op) + return self.finalizeOp(ops, creator, "active", **kwargs) + else: + return self.finalizeOp(op, creator, "active", **kwargs) + def create_account( self, account_name, @@ -1064,8 +1299,8 @@ class Steem(object): creator = config["default_account"] if not creator: raise ValueError( - "Not registrar account given. Define it with " + - "registrar=x, or set the default_account using uptick") + "Not creator account given. Define it with " + + "creator=x, or set the default_account using beempy") if password and (owner_key or active_key or memo_key): raise ValueError( "You cannot use 'password' AND provide keys!" -- GitLab