From 47cf2b4ceeeef87532722d8197400ded7e59fc16 Mon Sep 17 00:00:00 2001 From: Holger Nahrstaedt <holger@nahrstaedt.de> Date: Wed, 21 Feb 2018 18:34:37 +0100 Subject: [PATCH] account: * reputation added * history improved and fixed block: * ops and ops_statistics added blockchain: * function-names improved * ops_statistics added blockchainobject *json export added Comment class added Discussion class added steem: * follow and account by key api added and fixed storage: * more nodes added utils * Helpfunctions added vote * blockchainobject * refresh fixed wallet * purgeWallet added Witness * printAsTable function added * WitnessesVotedByAccount added * WitnessesRankedByVote added * WitnessesByIds added * LookupWitnesses added Steemnoderpc * register_apis fixed test_wallet * unit tests added test_utils * unittests added --- README.rst | 9 ++ docs/comment.rst | 17 ++++ docs/index.rst | 9 +- docs/vote.rst | 15 ++++ docs/witness.rst | 13 +++ setup.py | 2 +- steempy/__init__.py | 4 +- steempy/account.py | 56 +++++++++---- steempy/block.py | 23 ++++++ steempy/blockchain.py | 61 +++++++++++--- steempy/blockchainobject.py | 10 +++ steempy/comment.py | 146 +++++++++++++++++++++++++++++++++ steempy/discussions.py | 23 ++++++ steempy/exceptions.py | 12 +++ steempy/steem.py | 17 ++-- steempy/storage.py | 5 +- steempy/utils.py | 94 ++++++++++++++++++++- steempy/vote.py | 106 +++++++++++++++++++----- steempy/wallet.py | 9 ++ steempy/witness.py | 159 ++++++++++++++++++++++++++++++++++-- steempyapi/exceptions.py | 4 + steempyapi/steemnoderpc.py | 18 ++-- tests/test_account.py | 7 +- tests/test_amount.py | 7 +- tests/test_asset.py | 7 +- tests/test_base_objects.py | 6 +- tests/test_bip38.py | 22 +++++ tests/test_message.py | 7 +- tests/test_objectcache.py | 7 +- tests/test_price.py | 7 +- tests/test_proposals.py | 7 +- tests/test_steem.py | 7 +- tests/test_transactions.py | 2 - tests/test_txbuffers.py | 5 +- tests/test_utils.py | 50 ++++++++++-- tests/test_wallet.py | 37 ++++++++- 36 files changed, 874 insertions(+), 116 deletions(-) create mode 100644 docs/comment.rst create mode 100644 docs/vote.rst create mode 100644 steempy/comment.py create mode 100644 steempy/discussions.py create mode 100644 tests/test_bip38.py diff --git a/README.rst b/README.rst index 0b0e9c4e..253cbdb9 100644 --- a/README.rst +++ b/README.rst @@ -63,6 +63,15 @@ Documentation is available at http://steempy.readthedocs.io/en/latest/ Changelog ========= + +0.19.3 +------ +* Add Comment/Post +* Add Witness +* Several bugfixes +* Added all transactions that are supported from steem-python +* New library name planned: beem + 0.19.2 ------ * Notify and websocket fixed diff --git a/docs/comment.rst b/docs/comment.rst new file mode 100644 index 00000000..c68ec253 --- /dev/null +++ b/docs/comment.rst @@ -0,0 +1,17 @@ +Comment +~~~~~~~ + +Read data about a Comment/Post + +.. code-block:: python + + from steempy.comment import Comment + Witness("gtg") + +.. autoclass:: steempy.comment.Comment + :members: + +.. autoclass:: steempy.comment.Comments + :members: + + diff --git a/docs/index.rst b/docs/index.rst index eb9f7a79..c358cba8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,14 +12,13 @@ Welcome to steempy's documentation! =============================================== -Steem is a **blockchain-based autonomous company** (i.e. a DAC) that -offers decentralized exchanging as well as sophisticated financial -instruments as *products*. +Steem is a blockchain-based rewards platform for publishers to monetize +content and grow community. It is based on *Graphene* (tm), a blockchain technology stack (i.e. software) that allows for fast transactions and ascalable blockchain -solution. In case of Steem, it comes with decentralized trading of -assets as well as customized on-chain smart contracts. +solution. In case of Steem, it comes with decentralized publishing of +content. About this Library ------------------ diff --git a/docs/vote.rst b/docs/vote.rst new file mode 100644 index 00000000..08e50875 --- /dev/null +++ b/docs/vote.rst @@ -0,0 +1,15 @@ +Vote +~~~~ + +Read data about a vote + +.. code-block:: python + + from steempy.vote import Vote + Witness("gtg") + +.. autoclass:: steempy.vote.Vote + :members: + + + diff --git a/docs/witness.rst b/docs/witness.rst index cac1e93d..f3260776 100644 --- a/docs/witness.rst +++ b/docs/witness.rst @@ -13,3 +13,16 @@ Read data about a witness .. autoclass:: steempy.witness.Witnesses :members: + +.. autoclass:: steempy.witness.WitnessesVotedByAccount + :members: + +.. autoclass:: steempy.witness.WitnessesRankedByVote + :members: + +.. autoclass:: steempy.witness.WitnessesByIds + :members: + +.. autoclass:: steempy.witness.LookupWitnesses + :members: + diff --git a/setup.py b/setup.py index 890b3ba8..d51e992b 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ except LookupError: ascii = codecs.lookup('ascii') codecs.register(lambda name, enc=ascii: {True: enc}.get(name == 'mbcs')) -VERSION = '0.19.2' +VERSION = '0.19.3' def write_version_py(filename): diff --git a/steempy/__init__.py b/steempy/__init__.py index 043e5706..aee1d512 100644 --- a/steempy/__init__.py +++ b/steempy/__init__.py @@ -14,5 +14,7 @@ __all__ = [ "utils", "wallet", "vote", - "message" + "message", + "comment", + "discussions" ] diff --git a/steempy/account.py b/steempy/account.py index 00911e9d..8001df1a 100644 --- a/steempy/account.py +++ b/steempy/account.py @@ -1,7 +1,10 @@ from steempy.instance import shared_steem_instance from .exceptions import AccountDoesNotExistsException from .blockchainobject import BlockchainObject +from .utils import formatTimeString +from datetime import datetime import json +import math class Account(BlockchainObject): @@ -40,7 +43,7 @@ class Account(BlockchainObject): self, account, id_item="name", - full=False, + full=True, lazy=False, steem_instance=None ): @@ -68,7 +71,7 @@ class Account(BlockchainObject): account = account[0] if not account: raise AccountDoesNotExistsException(self.identifier) - # self.identifier = account["id"] + self.identifier = account["name"] super(Account, self).__init__(account, id_item="name") @@ -87,6 +90,22 @@ class Account(BlockchainObject): """ return json.loads(self["json_metadata"])["profile"] + @property + def rep(self): + return self.reputation() + + def reputation(self, precision=2): + rep = int(self['reputation']) + if rep == 0: + return 25 + score = (math.log10(abs(rep)) - 9) * 9 + 25 + if rep < 0: + score = 50 - score + if precision is not None: + return round(score, precision) + else: + return score + @property def available_balances(self): """ List balances of an account. This call returns instances of @@ -173,24 +192,20 @@ class Account(BlockchainObject): self.refresh() def history( - self, first=None, - limit=100, + self, limit=100, only_ops=[], exclude_ops=[] ): """ Returns a generator for individual account transactions. The latest operation will be first. This call can be used in a ``for`` loop. - :param int first: sequence number of the first - transaction to return (*optional*) - :param int limit: limit number of transactions to + :param int/datetime limit: limit number of transactions to return (*optional*) :param array only_ops: Limit generator by these operations (*optional*) :param array exclude_ops: Exclude thse operations from generator (*optional*) """ - from steempybase.operations import getOperationNameForId _limit = 100 cnt = 0 @@ -202,9 +217,7 @@ class Account(BlockchainObject): if not mostrecent: return - if not first: - # first = int(mostrecent[0].get("id").split(".")[2]) + 1 - first = 9999999999 + first = int(mostrecent[0][0]) while True: # RPC call @@ -213,21 +226,30 @@ class Account(BlockchainObject): first, _limit, ) - for i in txs: + for i in reversed(txs): if exclude_ops and i[1]["op"][0] in exclude_ops: continue if not only_ops or i[1]["op"][0] in only_ops: cnt += 1 - yield i - if limit >= 0 and cnt >= limit: - return - + if isinstance(limit, datetime): + timediff = limit - formatTimeString(i[1]["timestamp"]) + if timediff.total_seconds() > 0: + return + yield i + else: + yield i + if limit >= 0 and cnt >= limit: + return if not txs: break if len(txs) < _limit: break # first = int(txs[-1]["id"].split(".")[2]) - first = txs[-1][1]["block"] + first = txs[0][0] + if first < 2: + break + if first < _limit: + _limit = first - 1 # def upgrade(self): # return self.steem.upgrade_account(account=self) diff --git a/steempy/block.py b/steempy/block.py index f983eb09..1b76eba0 100644 --- a/steempy/block.py +++ b/steempy/block.py @@ -40,6 +40,29 @@ class Block(BlockchainObject): """ return parse_time(self['timestamp']) + def ops(self): + ops = [] + for tx in self["transactions"]: + for op in tx["operations"]: + # Replace opid by op name + # op[0] = getOperationNameForId(op[0]) + ops.append(op) + return ops + + def ops_statistics(self, add_to_ops_stat=None): + if add_to_ops_stat is None: + import steempybase.operationids + ops_stat = steempybase.operationids.operations.copy() + for key in ops_stat: + ops_stat[key] = 0 + else: + ops_stat = add_to_ops_stat.copy() + + for tx in self["transactions"]: + for op in tx["operations"]: + ops_stat[op[0]] += 1 + return ops_stat + class BlockHeader(BlockchainObject): def refresh(self): diff --git a/steempy/blockchain.py b/steempy/blockchain.py index c3b915a6..e624a089 100644 --- a/steempy/blockchain.py +++ b/steempy/blockchain.py @@ -29,27 +29,27 @@ class Blockchain(object): else: raise ValueError("invalid value for 'mode'!") - def info(self): + def get_dynamic_global_properties(self): """ This call returns the *dynamic global properties* """ return self.steem.rpc.get_dynamic_global_properties() - def feed_history(self): + def get_feed_history(self): """ Returns the feed_history """ return self.steem.rpc.get_feed_history() - def current_median_history_price(self): + def get_current_median_history_price(self): """ Returns the current median price """ return self.steem.rpc.get_current_median_history_price() - def next_scheduled_hardfork(self): + def get_next_scheduled_hardfork(self): """ Returns Hardfork and live_time of the hardfork """ return self.steem.rpc.get_next_scheduled_hardfork() - def hardfork_version(self): + def get_hardfork_version(self): """ Current Hardfork Version as String """ return self.steem.rpc.get_hardfork_version() @@ -63,12 +63,23 @@ class Blockchain(object): return self.steem.rpc.get_network() def get_chain_properties(self): - """ Return chain properties + """ Return witness elected chain properties + + :: + {'account_creation_fee': '30.000 STEEM', + 'maximum_block_size': 65536, + 'sbd_interest_rate': 250} + """ return self.steem.rpc.get_chain_properties() - def config(self): - """ Returns config + def get_state(self, path="value"): + """ get_state + """ + return self.steem.rpc.get_state(path) + + def get_config(self): + """ Returns internal chain configuration. """ return self.steem.rpc.get_config() @@ -78,7 +89,7 @@ class Blockchain(object): .. note:: The block number returned depends on the ``mode`` used when instanciating from this class. """ - return self.info().get(self.mode) + return self.get_dynamic_global_properties().get(self.mode) def get_current_block(self): """ This call returns the current block @@ -123,7 +134,7 @@ class Blockchain(object): confirmed by 2/3 of all block producers and is thus irreversible) """ # Let's find out how often blocks are generated! - block_interval = self.config().get("STEEMIT_BLOCK_INTERVAL") + block_interval = self.get_config().get("STEEMIT_BLOCK_INTERVAL") if not start: start = self.get_current_block_num() @@ -179,6 +190,36 @@ class Blockchain(object): "timestamp": block["timestamp"] } + def ops_statistics(self, start, stop=None, add_to_ops_stat=None, verbose=True): + """ Generates a statistics for all operations (including virtual operations) starting from + ``start``. + + :param int start: Starting block + :param int stop: Stop at this block, if set to None, the current_block_num is taken + :param dict add_to_ops_stat, if set, the result is added to add_to_ops_stat + :param bool verbose, if True, the current block number and timestamp is printed + This call returns a dict with all possible operations and their occurence. + """ + if add_to_ops_stat is None: + import steempybase.operationids + ops_stat = steempybase.operationids.operations.copy() + for key in ops_stat: + ops_stat[key] = 0 + else: + ops_stat = add_to_ops_stat.copy() + current_block = self.get_current_block_num() + if start > current_block: + return + if stop is None: + stop = current_block + for block in self.blocks(start=start, stop=stop): + if verbose: + print(str(block["block_num"]) + " " + block["timestamp"]) + for tx in block["transactions"]: + for op in tx["operations"]: + ops_stat[op[0]] += 1 + return ops_stat + def stream(self, opNames=[], *args, **kwargs): """ Yield specific operations (e.g. comments) only diff --git a/steempy/blockchainobject.py b/steempy/blockchainobject.py index 7101284c..9b6df825 100644 --- a/steempy/blockchainobject.py +++ b/steempy/blockchainobject.py @@ -1,5 +1,6 @@ from steempy.instance import shared_steem_instance from datetime import datetime, timedelta +import json class ObjectCache(dict): @@ -95,6 +96,12 @@ class BlockchainObject(dict): self[self.id_item] = str(data) # Set identifier again as it is overwritten in super() in refresh() self.identifier = data + elif isinstance(data, str): + self.identifier = data + if not lazy and not self.cached: + self.refresh() + self[self.id_item] = str(data) + self.identifier = data else: self.identifier = data if self.test_valid_objectid(self.identifier): @@ -158,3 +165,6 @@ class BlockchainObject(dict): def __repr__(self): return "<%s %s>" % ( self.__class__.__name__, str(self.identifier)) + + def json(self): + return json.loads(str(json.dumps(self))) diff --git a/steempy/comment.py b/steempy/comment.py new file mode 100644 index 00000000..419e2a51 --- /dev/null +++ b/steempy/comment.py @@ -0,0 +1,146 @@ +from .instance import shared_steem_instance +from .account import Account +from .utils import resolve_authorperm, construct_authorperm +from .blockchainobject import BlockchainObject +from .exceptions import ContentDoesNotExistsException +import json +import logging +log = logging.getLogger(__name__) + + +class Comment(BlockchainObject): + """ Read data about a Comment/Post in the chain + + :param str authorperm: perm link to post/comment + :param steem steem_instance: Steem() instance to use when accesing a RPC + + """ + type_id = 8 + + def __init__( + self, + authorperm, + full=False, + lazy=False, + steem_instance=None + ): + self.full = full + if isinstance(authorperm, str): + [author, permlink] = resolve_authorperm(authorperm) + self["author"] = author + self["permlink"] = permlink + self["authorperm"] = construct_authorperm(author, permlink) + elif isinstance(authorperm, dict) and "author" in authorperm and "permlink" in authorperm: + self["author"] = authorperm["author"] + self["permlink"] = authorperm["permlink"] + self["authorperm"] = construct_authorperm(authorperm) + super().__init__( + authorperm, + id_item="authorperm", + lazy=lazy, + full=full, + steem_instance=steem_instance + ) + + def refresh(self): + [author, permlink] = resolve_authorperm(self.identifier) + content = self.steem.rpc.get_content(author, permlink) + if not content: + raise ContentDoesNotExistsException + super(Comment, self).__init__(content, id_item="authorperm", steem_instance=self.steem) + + self.identifier = self.authorperm + + def json(self): + output = self + output.pop("authorperm") + return json.loads(str(json.dumps(output))) + + @property + def id(self): + return self["id"] + + @property + def author(self): + return self["author"] + + @property + def permlink(self): + return self["permlink"] + + @property + def authorperm(self): + return construct_authorperm(self["author"], self["permlink"]) + + @property + def category(self): + return self["category"] + + @property + def parent_author(self): + return self["parent_author"] + + @property + def parent_permlink(self): + return self["parent_permlink"] + + @property + def title(self): + return self["title"] + + @property + def body(self): + return self["body"] + + @property + def json_metadata(self): + return self["json_metadata"] + + def is_main_post(self): + """ Retuns True if main post, and False if this is a comment (reply). + """ + return self['depth'] == 0 + + def is_comment(self): + """ Retuns True if post is a comment + """ + return self['depth'] > 0 + + +class RecentReplies(list): + """ Obtain a list of recent replies + + :param str author: author + :param steem steem_instance: Steem() instance to use when accesing a RPC + """ + def __init__(self, author, skip_own=True, steem_instance=None): + self.steem = steem_instance or shared_steem_instance() + state = self.steem.rpc.get_state("/@%s/recent-replies" % author) + replies = state["accounts"][author].get("recent_replies", []) + comments = [] + for reply in replies: + post = state["content"][reply] + if skip_own and post["author"] == author: + continue + comments.append(Comment(post, lazy=True, steem_instance=self.steem)) + super(RecentReplies, self).__init__(comments) + + +class RecentByPath(list): + """ Obtain a list of votes for an account + + :param str account: Account name + :param steem steem_instance: Steem() instance to use when accesing a RPC + """ + def __init__(self, path="promoted", category=None, steem_instance=None): + self.steem = steem_instance or shared_steem_instance() + + state = self.steem.rpc.get_state("/" + path) + replies = state["discussion_idx"][''].get(path, []) + comments = [] + for reply in replies: + post = state["content"][reply] + if category is not None and post["category"] != category: + continue + comments.append(Comment(post, lazy=True, steem_instance=self.steem)) + super(RecentByPath, self).__init__(comments) diff --git a/steempy/discussions.py b/steempy/discussions.py new file mode 100644 index 00000000..37c2788f --- /dev/null +++ b/steempy/discussions.py @@ -0,0 +1,23 @@ +from .instance import shared_steem_instance +from .account import Account +from .comment import Comment +from .utils import resolve_authorperm +import logging +log = logging.getLogger(__name__) + + +class Discussions_by_trending(list): + """ get_discussions_by_trending + + :param str discussion_query + :param steem steem_instance: Steem() instance to use when accesing a RPC + """ + def __init__(self, discussion_query, steem_instance=None): + self.steem = steem_instance or shared_steem_instance() + posts = self.steem.rpc.get_discussions_by_trending(discussion_query) + super(Discussions_by_trending, self).__init__( + [ + Comment(x) + for x in posts + ] + ) diff --git a/steempy/exceptions.py b/steempy/exceptions.py index b82924f8..55ee9caf 100644 --- a/steempy/exceptions.py +++ b/steempy/exceptions.py @@ -78,6 +78,18 @@ class WitnessDoesNotExistsException(Exception): pass +class ContentDoesNotExistsException(Exception): + """ The content does not exist + """ + pass + + +class VoteDoesNotExistsException(Exception): + """ The vote does not exist + """ + pass + + class WrongMasterPasswordException(Exception): """ The password provided could not properly unlock the wallet """ diff --git a/steempy/steem.py b/steempy/steem.py index fbb978d0..a7443860 100644 --- a/steempy/steem.py +++ b/steempy/steem.py @@ -3,6 +3,7 @@ import logging from datetime import datetime, timedelta from steempyapi.steemnoderpc import SteemNodeRPC +from steempyapi.exceptions import NoAccessApi from steempybase.account import PrivateKey, PublicKey from steempybase import transactions, operations from .asset import Asset @@ -114,11 +115,11 @@ class Steem(object): kwargs["apis"] = [ "database", "network_broadcast", - "market_history", - "follow", - "account_by_key", - "tag", - "raw_block" + # "market_history", + # "follow", + # "account_by_key", + # "tag", + # "raw_block" ] self.rpc = None @@ -140,6 +141,12 @@ class Steem(object): rpcpassword=rpcpassword, **kwargs) + # Try Optional APIs + try: + self.rpc.register_apis(["account_by_key", "follow"]) + except NoAccessApi as e: + log.info(str(e)) + self.wallet = Wallet(self.rpc, **kwargs) # txbuffers/propbuffer are initialized and cleared diff --git a/steempy/storage.py b/steempy/storage.py index 7352e26b..730a1281 100644 --- a/steempy/storage.py +++ b/steempy/storage.py @@ -217,8 +217,11 @@ class Configuration(DataDir): __tablename__ = "config" #: Default configuration + nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] config_defaults = { - "node": "wss://steemd.pevo.science", + "node": nodes, "rpcpassword": "", "rpcuser": "", "order-expiration": 7 * 24 * 60 * 60, diff --git a/steempy/utils.py b/steempy/utils.py index a9e8f079..5fc5330d 100644 --- a/steempy/utils.py +++ b/steempy/utils.py @@ -10,9 +10,9 @@ def formatTime(t): """ Properly Format Time for permlinks """ if isinstance(t, float): - return datetime.utcfromtimestamp(t).strftime(timeFormat) + return datetime.utcfromtimestamp(t).strftime("%Y%m%dt%H%M%S%Z") if isinstance(t, datetime): - return t.strftime(timeFormat) + return t.strftime("%Y%m%dt%H%M%S%Z") def formatTimeString(t): @@ -50,6 +50,34 @@ def assets_from_string(text): return re.split(r'[\-:/]', text) +def sanitize_permlink(permlink): + permlink = permlink.strip() + permlink = re.sub("_|\s|\.", "-", permlink) + permlink = re.sub("[^\w-]", "", permlink) + permlink = re.sub("[^a-zA-Z0-9-]", "", permlink) + permlink = permlink.lower() + return permlink + + +def derive_permlink(title, parent_permlink=None, parent_author=None): + permlink = "" + + if parent_permlink and parent_author: + permlink += "re-" + permlink += parent_author.replace("@", "") + permlink += "-" + permlink += parent_permlink + permlink += "-" + formatTime(time.time()) + "z" + elif parent_permlink: + permlink += "re-" + permlink += parent_permlink + permlink += "-" + formatTime(time.time()) + "z" + else: + permlink += title + + return sanitize_permlink(permlink) + + def resolve_authorperm(identifier): """Correctly split a string containing an authorperm. @@ -62,6 +90,68 @@ def resolve_authorperm(identifier): return match.group(1), match.group(2) +def construct_authorperm(*args, username_prefix='@'): + """ Create a post identifier from comment/post object or arguments. + Examples: + :: + construct_authorperm('username', 'permlink') + construct_authorperm({'author': 'username', + 'permlink': 'permlink'}) + """ + if len(args) == 1: + op = args[0] + author, permlink = op['author'], op['permlink'] + elif len(args) == 2: + author, permlink = args + else: + raise ValueError( + 'construct_identifier() received unparsable arguments') + + fields = dict(prefix=username_prefix, author=author, permlink=permlink) + return "{prefix}{author}/{permlink}".format(**fields) + + +def resolve_authorpermvoter(identifier): + """Correctly split a string containing an authorpermvoter. + + Splits the string into author and permlink with the + following separator: ``/`` and ``|``. + """ + pos = identifier.find("|") + if pos < 0: + raise ValueError("Invalid identifier") + [author, permlink] = resolve_authorperm(identifier[:pos]) + return author, permlink, identifier[pos + 1:] + + +def construct_authorpermvoter(*args, username_prefix='@'): + """ Create a vote identifier from vote object or arguments. + Examples: + :: + construct_authorpermvoter('username', 'permlink', 'voter') + construct_authorpermvoter({'author': 'username', + 'permlink': 'permlink', 'voter': 'voter'}) + """ + if len(args) == 1: + op = args[0] + if "authorperm" in op: + authorperm, voter = op['authorperm'], op['voter'] + [author, permlink] = resolve_authorperm(authorperm) + else: + author, permlink, voter = op['author'], op['permlink'], op['voter'] + elif len(args) == 2: + authorperm, voter = args + [author, permlink] = resolve_authorperm(authorperm) + elif len(args) == 3: + author, permlink, voter = args + else: + raise ValueError( + 'construct_identifier() received unparsable arguments') + + fields = dict(prefix=username_prefix, author=author, permlink=permlink, voter=voter) + return "{prefix}{author}/{permlink}|{voter}".format(**fields) + + def test_proposal_in_buffer(buf, operation_name, id): from .transactionbuilder import ProposalBuilder from peerplaysbase.operationids import operations diff --git a/steempy/vote.py b/steempy/vote.py index 04b1194c..23fd89df 100644 --- a/steempy/vote.py +++ b/steempy/vote.py @@ -1,17 +1,15 @@ from .instance import shared_steem_instance from .account import Account -from .utils import resolve_authorperm +from .exceptions import VoteDoesNotExistsException +from .utils import resolve_authorperm, resolve_authorpermvoter, construct_authorpermvoter, construct_authorperm +from .blockchainobject import BlockchainObject +from .comment import Comment +import json import logging log = logging.getLogger(__name__) -class VoteObject(dict): - def __repr__(self): - return "<%s %s>" % ( - self.__class__.__name__, str(self.voter)) - - -class Vote(VoteObject): +class Vote(BlockchainObject): """ Read data about a Vote in the chain :param str authorperm: perm link to post/comment @@ -20,21 +18,74 @@ class Vote(VoteObject): """ type_id = 11 - def refresh(self, authorperm=None, voter=None): - vote = self.identifier - if authorperm is not None: - vote["authorperm"] = authorperm - if voter is not None: - vote["voter"] = voter - super(Vote, self).__init__(vote) + def __init__( + self, + voter, + authorperm=None, + full=False, + lazy=False, + steem_instance=None + ): + self.full = full + if isinstance(voter, str) and authorperm is not None: + [author, permlink] = resolve_authorperm(authorperm) + self["voter"] = voter + self["author"] = author + self["permlink"] = permlink + authorpermvoter = construct_authorpermvoter(author, permlink, voter) + self["authorpermvoter"] = authorpermvoter + elif isinstance(voter, dict) and "author" in voter and "permlink" in voter and "voter" in voter: + self["author"] = voter["author"] + self["permlink"] = voter["permlink"] + authorpermvoter = construct_authorpermvoter(voter["author"], voter["permlink"], voter["voter"]) + self["authorpermvoter"] = authorpermvoter + elif isinstance(voter, dict) and "authorperm" in voter and "voter" in voter: + [author, permlink] = resolve_authorperm(voter["authorperm"]) + self["author"] = author + self["permlink"] = permlink + authorpermvoter = construct_authorpermvoter(voter["author"], voter["permlink"], voter["voter"]) + self["authorpermvoter"] = authorpermvoter + elif isinstance(voter, dict) and "voter" in voter and authorperm is not None: + [author, permlink] = resolve_authorperm(authorperm) + self["author"] = author + self["permlink"] = permlink + authorpermvoter = construct_authorpermvoter(author, permlink, voter) + self["authorpermvoter"] = authorpermvoter + else: + authorpermvoter = voter + super().__init__( + authorpermvoter, + id_item="authorpermvoter", + lazy=lazy, + full=full, + steem_instance=steem_instance + ) + + def refresh(self): + [author, permlink, voter] = resolve_authorpermvoter(self.identifier) + votes = self.steem.rpc.get_active_votes(author, permlink) + vote = None + for x in votes: + if x["voter"] == voter: + vote = x + if not vote: + raise VoteDoesNotExistsException + super(Vote, self).__init__(vote, id_item="authorpermvoter", steem_instance=self.steem) + + self.identifier = self.authorpermvoter + + def json(self): + output = self + output.pop("authorpermvoter") + return json.loads(str(json.dumps(output))) @property def voter(self): return self["voter"] @property - def authorperm(self): - return self["authorperm"] + def authorpermvoter(self): + return self["authorpermvoter"] @property def weight(self): @@ -58,15 +109,28 @@ class Vote(VoteObject): class ActiveVotes(list): - """ Obtain a list of pending proposals for a post + """ Obtain a list of votes for a post :param str authorperm: authorperm link :param steem steem_instance: Steem() instance to use when accesing a RPC """ def __init__(self, authorperm, steem_instance=None): self.steem = steem_instance or shared_steem_instance() - [author, permlink] = resolve_authorperm(authorperm) - votes = self.steem.rpc.get_active_votes(author, permlink) + votes = None + if isinstance(authorperm, str): + [author, permlink] = resolve_authorperm(authorperm) + votes = self.steem.rpc.get_active_votes(author, permlink) + elif isinstance(authorperm, list): + votes = authorperm + authorperm = None + elif isinstance(authorperm, Comment): + votes = authorperm["active_votes"] + authorperm = authorperm["authorperm"] + elif isinstance(authorperm, dict): + votes = authorperm["active_votes"] + authorperm = authorperm["authorperm"] + if votes is None: + return super(ActiveVotes, self).__init__( [ @@ -77,7 +141,7 @@ class ActiveVotes(list): class AccountVotes(list): - """ Obtain a list of pending proposals for an account + """ Obtain a list of votes for an account :param str account: Account name :param steem steem_instance: Steem() instance to use when accesing a RPC diff --git a/steempy/wallet.py b/steempy/wallet.py index fb8c31ca..18cb71b0 100644 --- a/steempy/wallet.py +++ b/steempy/wallet.py @@ -178,6 +178,15 @@ class Wallet(): self.masterpassword = self.masterpwd.decrypted_master self.masterpwd.saveEncrytpedMaster() + def purgeWallet(self): + """ Purge all data in wallet database + """ + if self.MasterPassword: + self.MasterPassword.purge(self.MasterPassword) + for key in self.keyStorage.getPublicKeys(): + self.keyStorage.delete(key) + self.configStorage.delete(self.MasterPassword.config_key) + def encrypt_wif(self, wif): """ Encrypt a wif key """ diff --git a/steempy/witness.py b/steempy/witness.py index 5501faed..41571338 100644 --- a/steempy/witness.py +++ b/steempy/witness.py @@ -2,34 +2,82 @@ from steempy.instance import shared_steem_instance from .account import Account from .exceptions import WitnessDoesNotExistsException from .blockchainobject import BlockchainObject +from .utils import formatTimeString, parse_time +from datetime import datetime, timedelta class Witness(BlockchainObject): """ Read data about a witness in the chain :param str account_name: Name of the witness - :param steem steem_instance: BitShares() instance to use when + :param steem steem_instance: Steem() instance to use when accesing a RPC """ - type_ids = [6, 2] + type_id = 3 - def refresh(self): + def __init__( + self, + owner, + id_item="owner", + full=False, + lazy=False, + steem_instance=None + ): + self.full = full + super().__init__( + owner, + lazy=lazy, + full=full, + id_item="owner", + steem_instance=steem_instance + ) + def refresh(self): witness = self.steem.rpc.get_witness_by_account(self.identifier) if not witness: raise WitnessDoesNotExistsException - super(Witness, self).__init__(witness, steem_instance=self.steem) + super(Witness, self).__init__(witness, id_item="owner", steem_instance=self.steem) + self.identifier = self["owner"] @property def account(self): return Account(self["owner"], steem_instance=self.steem) -class Witnesses(list): +class WitnessesObject(list): + def printAsTable(self, sort_key="votes", reverse=True): + if sort_key == 'base': + sortedList = sorted(self, key=lambda self: self['sbd_exchange_rate']['base'], reverse=reverse) + elif sort_key == 'quote': + sortedList = sorted(self, key=lambda self: self['sbd_exchange_rate']['quote'], reverse=reverse) + elif sort_key == 'last_sbd_exchange_update': + sortedList = sorted(self, key=lambda self: (datetime.now() - formatTimeString(self['last_sbd_exchange_update'])).total_seconds(), reverse=reverse) + elif sort_key == 'account_creation_fee': + sortedList = sorted(self, key=lambda self: self['props']['account_creation_fee'], reverse=reverse) + elif sort_key == 'sbd_interest_rate': + sortedList = sorted(self, key=lambda self: self['props']['sbd_interest_rate'], reverse=reverse) + elif sort_key == 'maximum_block_size': + sortedList = sorted(self, key=lambda self: self['props']['maximum_block_size'], reverse=reverse) + elif sort_key == 'votes': + sortedList = sorted(self, key=lambda self: int(self[sort_key]), reverse=reverse) + else: + sortedList = sorted(self, key=lambda self: self[sort_key], reverse=reverse) + for witness in sortedList: + outstr = '' + outstr += witness['owner'][:15].ljust(15) + " \t " + str(round(int(witness['votes']) / 1e15, 2)).ljust(5) + " PV - " + str(witness['total_missed']).ljust(5) + outstr += " missed - feed:" + witness['sbd_exchange_rate']['base'] + "/" + witness['sbd_exchange_rate']['quote'] + td = datetime.now() - formatTimeString(witness['last_sbd_exchange_update']) + outstr += " " + str(td.days) + " days " + str(td.seconds // 3600) + ":" + str((td.seconds // 60) % 60) + " \t " + outstr += str(witness['props']['account_creation_fee']) + " " + str(witness['props']['maximum_block_size']) + " Blocks " + outstr += str(witness['props']['sbd_interest_rate']) + " \t " + witness['running_version'] + print(outstr) + + +class Witnesses(WitnessesObject): """ Obtain a list of **active** witnesses and the current schedule - :param steem steem_instance: BitShares() instance to use when + :param steem steem_instance: Steem() instance to use when accesing a RPC """ def __init__(self, steem_instance=None): @@ -44,3 +92,102 @@ class Witnesses(list): for x in self.active_witnessess ] ) + + +class WitnessesVotedByAccount(WitnessesObject): + """ Obtain a list of witnesses which have been voted by an account + + :param str account: Account name + :param steem steem_instance: Steem() instance to use when + accesing a RPC + """ + def __init__(self, account, steem_instance=None): + self.steem = steem_instance or shared_steem_instance() + self.account = Account(account, full=True) + if "witness_votes" not in self.account: + return + witnessess = self.account["witness_votes"] + + super(WitnessesVotedByAccount, self).__init__( + [ + Witness(x, lazy=True, steem_instance=self.steem) + for x in witnessess + ] + ) + + +class WitnessesRankedByVote(WitnessesObject): + """ Obtain a list of witnesses ranked by Vote + + :param str from_account: Witness name + :param steem steem_instance: Steem() instance to use when + accesing a RPC + """ + def __init__(self, from_account="", limit=100, steem_instance=None): + self.steem = steem_instance or shared_steem_instance() + witnessList = [] + last_limit = limit + last_account = from_account + if limit > 100: + while last_limit > 100: + tmpList = WitnessesRankedByVote(last_account, 100) + if (last_limit < limit): + witnessList.extend(tmpList[1:]) + last_limit -= 99 + else: + witnessList.extend(tmpList) + last_limit -= 100 + last_account = witnessList[-1]["owner"] + if (last_limit < limit): + last_limit += 1 + + witnessess = self.steem.rpc.get_witnesses_by_vote(last_account, last_limit) + # self.witness_count = len(self.voted_witnessess) + if (last_limit < limit): + witnessess = witnessess[1:] + if len(witnessess) > 0: + for x in witnessess: + witnessList.append(Witness(x, lazy=True, steem_instance=self.steem)) + if len(witnessList) == 0: + return + super(WitnessesRankedByVote, self).__init__(witnessList) + + +class WitnessesByIds(WitnessesObject): + """ Obtain a list of witnesses which have been voted by an account + + :param list witness_ids: list of witness_ids + :param steem steem_instance: Steem() instance to use when + accesing a RPC + """ + def __init__(self, witness_ids, steem_instance=None): + self.steem = steem_instance or shared_steem_instance() + witnessess = self.steem.rpc.get_witnesses(witness_ids) + if len(witnessess) == 0: + return + super(WitnessesByIds, self).__init__( + [ + Witness(x, lazy=True, steem_instance=self.steem) + for x in witnessess + ] + ) + + +class LookupWitnesses(WitnessesObject): + """ Obtain a list of witnesses which have been voted by an account + + :param str from_account: Account name + :param steem steem_instance: Steem() instance to use when + accesing a RPC + """ + def __init__(self, from_account, limit, steem_instance=None): + self.steem = steem_instance or shared_steem_instance() + witnessess = self.steem.rpc.lookup_witness_accounts(from_account, limit) + if len(witnessess) == 0: + return + super(LookupWitnesses, self).__init__( + [ + Witness(x, lazy=True, steem_instance=self.steem) + for x in witnessess + ] + ) diff --git a/steempyapi/exceptions.py b/steempyapi/exceptions.py index 91540719..61fda423 100644 --- a/steempyapi/exceptions.py +++ b/steempyapi/exceptions.py @@ -32,5 +32,9 @@ class UnhandledRPCError(RPCError): pass +class NoAccessApi(RPCError): + pass + + class NumRetriesReached(Exception): pass diff --git a/steempyapi/steemnoderpc.py b/steempyapi/steemnoderpc.py index b7a25606..79bc81a6 100644 --- a/steempyapi/steemnoderpc.py +++ b/steempyapi/steemnoderpc.py @@ -21,14 +21,20 @@ class SteemNodeRPC(GrapheneWebsocketRPC): def __init__(self, *args, **kwargs): super(SteemNodeRPC, self).__init__(*args, **kwargs) + self.apis = kwargs.pop( + "apis", + ["database", "network_broadcast"] + ) self.chain_params = self.get_network() - def register_apis(self): - return - # self.api_id["database"] = self.database(api_id=1) - # self.api_id["market_history"] = self.market_history(api_id=1) - # self.api_id["network_broadcast"] = self.network_broadcast(api_id=1) - # self.api_id["follow"] = self.follow(api_id=1) + def register_apis(self, apis=None): + if apis is None: + return + for api in (apis): + api = api.replace("_api", "") + self.api_id[api] = self.get_api_by_name("%s_api" % api, api_id=1) + if not self.api_id[api] and not isinstance(self.api_id[api], int): + raise exceptions.NoAccessApi("No permission to access %s API. " % api) def rpcexec(self, payload): """ Execute a call by sending the payload. diff --git a/tests/test_account.py b/tests/test_account.py index 3d41be8e..71b9397b 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -9,8 +9,9 @@ from steempy.instance import set_shared_steem_instance from steempybase.operationids import getOperationNameForId wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -19,7 +20,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, # We want to bundle many operations into a single transaction bundle=True, diff --git a/tests/test_amount.py b/tests/test_amount.py index 9b131c0c..be447fdc 100644 --- a/tests/test_amount.py +++ b/tests/test_amount.py @@ -3,8 +3,9 @@ from steempy import Steem from steempy.amount import Amount from steempy.asset import Asset from steempy.instance import set_shared_steem_instance, SharedInstance -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -13,7 +14,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, ) set_shared_steem_instance(self.bts) diff --git a/tests/test_asset.py b/tests/test_asset.py index 8690b024..8bc3b916 100644 --- a/tests/test_asset.py +++ b/tests/test_asset.py @@ -3,8 +3,9 @@ from steempy import Steem from steempy.asset import Asset from steempy.instance import set_shared_steem_instance from steempy.exceptions import AssetDoesNotExistsException -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -13,7 +14,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, ) set_shared_steem_instance(self.bts) diff --git a/tests/test_base_objects.py b/tests/test_base_objects.py index bbadf391..67871657 100644 --- a/tests/test_base_objects.py +++ b/tests/test_base_objects.py @@ -3,7 +3,9 @@ from steempy import Steem, exceptions from steempy.instance import set_shared_steem_instance from steempy.account import Account from steempy.witness import Witness -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -12,7 +14,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, ) set_shared_steem_instance(self.bts) diff --git a/tests/test_bip38.py b/tests/test_bip38.py new file mode 100644 index 00000000..ccd39c67 --- /dev/null +++ b/tests/test_bip38.py @@ -0,0 +1,22 @@ +import unittest + +from steempybase.account import PrivateKey +from steempybase.bip38 import encrypt, decrypt + + +class Testcases(unittest.TestCase): + def test_encrypt(self): + self.assertEqual([format(encrypt(PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), "TestingOneTwoThree"), "encwif"), + format(encrypt(PrivateKey("5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"), "TestingOneTwoThree"), "encwif"), + format(encrypt(PrivateKey("5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"), "Satoshi"), "encwif")], + ["6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", + "6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq"]) + + def test_deencrypt(self): + self.assertEqual([format(decrypt("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", "TestingOneTwoThree"), "wif"), + format(decrypt("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg", "TestingOneTwoThree"), "wif"), + format(decrypt("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq", "Satoshi"), "wif")], + ["5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR", + "5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"]) diff --git a/tests/test_message.py b/tests/test_message.py index 40e815b9..5e577c72 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -7,8 +7,9 @@ from steempy.instance import set_shared_steem_instance wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" core_unit = "STM" -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -17,7 +18,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, wif=[wif] ) diff --git a/tests/test_objectcache.py b/tests/test_objectcache.py index 55031210..a2254987 100644 --- a/tests/test_objectcache.py +++ b/tests/test_objectcache.py @@ -3,8 +3,9 @@ import unittest from steempy import Steem, exceptions from steempy.instance import set_shared_steem_instance from steempy.blockchainobject import ObjectCache -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -13,7 +14,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, ) set_shared_steem_instance(self.bts) diff --git a/tests/test_price.py b/tests/test_price.py index 59996318..0e57d8f1 100644 --- a/tests/test_price.py +++ b/tests/test_price.py @@ -4,8 +4,9 @@ from steempy.amount import Amount from steempy.price import Price from steempy.asset import Asset import unittest -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -13,7 +14,7 @@ class Testcases(unittest.TestCase): def __init__(self, *args, **kwargs): super(Testcases, self).__init__(*args, **kwargs) steem = Steem( - steem_node, + nodes, nobroadcast=True, ) set_shared_steem_instance(steem) diff --git a/tests/test_proposals.py b/tests/test_proposals.py index a3e0caeb..c063b5e2 100644 --- a/tests/test_proposals.py +++ b/tests/test_proposals.py @@ -5,8 +5,9 @@ from steempybase.operationids import getOperationNameForId from steempy.instance import set_shared_steem_instance wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -15,7 +16,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, keys={"active": wif}, ) diff --git a/tests/test_steem.py b/tests/test_steem.py index c71c45ae..0efd5ca5 100644 --- a/tests/test_steem.py +++ b/tests/test_steem.py @@ -11,8 +11,9 @@ from steempy.instance import set_shared_steem_instance wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" core_unit = "STM" -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -21,7 +22,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - steem_node, + nodes, nobroadcast=True, keys={"active": wif, "owner": wif, "memo": wif}, ) diff --git a/tests/test_transactions.py b/tests/test_transactions.py index 64107ae2..ac5708ca 100644 --- a/tests/test_transactions.py +++ b/tests/test_transactions.py @@ -24,8 +24,6 @@ wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" ref_block_num = 34294 ref_block_prefix = 3707022213 expiration = "2016-04-06T08:29:27" -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" class Testcases(unittest.TestCase): diff --git a/tests/test_txbuffers.py b/tests/test_txbuffers.py index 2b264e1c..f92b2465 100644 --- a/tests/test_txbuffers.py +++ b/tests/test_txbuffers.py @@ -4,6 +4,9 @@ from steempybase import operations from steempy.instance import set_shared_steem_instance wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -12,7 +15,7 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.bts = Steem( - "wss://testnet.steem.vc", + nodes, nobroadcast=True, keys={"active": wif} ) diff --git a/tests/test_utils.py b/tests/test_utils.py index a49f2daa..9137b12d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,12 +1,46 @@ -from steempy.utils import assets_from_string -from steempy.utils import resolve_authorperm +import unittest +from steempy.utils import ( + assets_from_string, + resolve_authorperm, + resolve_authorpermvoter, + construct_authorperm, + construct_authorpermvoter, + sanitize_permlink, + derive_permlink +) -def test_assets_from_string(): - assert assets_from_string('USD:BTS') == ['USD', 'BTS'] - assert assets_from_string('BTSBOTS.S1:BTS') == ['BTSBOTS.S1', 'BTS'] +class Testcases(unittest.TestCase): + def test_constructAuthorperm(self): + self.assertEqual(construct_authorperm("A", "B"), "@A/B") + self.assertEqual(construct_authorperm({'author': "A", 'permlink': "B"}), "@A/B") + def test_constructAuthorpermvoter(self): + self.assertEqual(construct_authorpermvoter("A", "B", "C"), "@A/B|C") + self.assertEqual(construct_authorpermvoter({'author': "A", 'permlink': "B", 'voter': 'C'}), "@A/B|C") + self.assertEqual(construct_authorpermvoter({'authorperm': "A/B", 'voter': 'C'}), "@A/B|C") -def test_authorperm_resolve(): - assert resolve_authorperm('theaussiegame/cryptokittie-giveaway-number-2') == ('theaussiegame', 'cryptokittie-giveaway-number-2') - assert resolve_authorperm('holger80/virtuelle-cloud-mining-ponzi-schemen-auch-bekannt-als-hypt') == ('holger80', 'virtuelle-cloud-mining-ponzi-schemen-auch-bekannt-als-hypt') + def test_assets_from_string(self): + self.assertEqual(assets_from_string('USD:BTS'), ['USD', 'BTS']) + self.assertEqual(assets_from_string('BTSBOTS.S1:BTS'), ['BTSBOTS.S1', 'BTS']) + + def test_authorperm_resolve(self): + self.assertEqual(resolve_authorperm('theaussiegame/cryptokittie-giveaway-number-2'), + ('theaussiegame', 'cryptokittie-giveaway-number-2')) + self.assertEqual(resolve_authorperm('holger80/virtuelle-cloud-mining-ponzi-schemen-auch-bekannt-als-hypt'), + ('holger80', 'virtuelle-cloud-mining-ponzi-schemen-auch-bekannt-als-hypt')) + + def test_authorpermvoter_resolve(self): + self.assertEqual(resolve_authorpermvoter('theaussiegame/cryptokittie-giveaway-number-2|test'), + ('theaussiegame', 'cryptokittie-giveaway-number-2', 'test')) + self.assertEqual(resolve_authorpermvoter('holger80/virtuelle-cloud-mining-ponzi-schemen-auch-bekannt-als-hypt|holger80'), + ('holger80', 'virtuelle-cloud-mining-ponzi-schemen-auch-bekannt-als-hypt', 'holger80')) + + def test_sanitizePermlink(self): + self.assertEqual(sanitize_permlink("aAf_0.12"), "aaf-0-12") + self.assertEqual(sanitize_permlink("[](){}|"), "") + + def test_derivePermlink(self): + self.assertEqual(derive_permlink("Hello World"), "hello-world") + self.assertEqual(derive_permlink("aAf_0.12"), "aaf-0-12") + self.assertEqual(derive_permlink("[](){}"), "") diff --git a/tests/test_wallet.py b/tests/test_wallet.py index fe2baf24..781f59a1 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -5,12 +5,14 @@ from steempy import Steem from steempy.account import Account from steempy.amount import Amount from steempy.asset import Asset +from steempy.wallet import Wallet from steempy.instance import set_shared_steem_instance from steempybase.operationids import getOperationNameForId wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" -# steem_node = "wss://gtg.steem.house:8090" -steem_node = "wss://steemd.pevo.science" +nodes = ["wss://steemd.pevo.science", "wss://gtg.steem.house:8090", "wss://rpc.steemliberator.com", "wss://rpc.buildteam.io", + "wss://rpc.steemviz.com", "wss://seed.bitcoiner.me", "wss://node.steem.ws", "wss://steemd.steemgigs.org", "wss://steemd.steemit.com", + "wss://steemd.minnowsupportproject.org"] class Testcases(unittest.TestCase): @@ -19,12 +21,39 @@ class Testcases(unittest.TestCase): super().__init__(*args, **kwargs) self.stm = Steem( - steem_node, + nodes, nobroadcast=True, # We want to bundle many operations into a single transaction bundle=True, # Overwrite wallet to use this list of wifs only - wif=[wif] ) self.stm.set_default_account("test") set_shared_steem_instance(self.stm) + # self.stm.newWallet("TestingOneTwoThree") + self.wallet = Wallet(rpc=self.stm.rpc) + self.wallet.purgeWallet() + + self.wallet.newWallet(pwd="TestingOneTwoThree") + + self.wallet.unlock(pwd="TestingOneTwoThree") + self.wallet.addPrivateKey(wif) + + def test_encrypt(self): + self.wallet.masterpassword = "TestingOneTwoThree" + self.assertEqual([self.wallet.encrypt_wif("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd"), + self.wallet.encrypt_wif("5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR")], + ["6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi", + "6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg"]) + self.wallet.masterpassword = "Satoshi" + self.assertEqual([self.wallet.encrypt_wif("5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5")], + ["6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq"]) + + def test_deencrypt(self): + self.wallet.masterpassword = "TestingOneTwoThree" + self.assertEqual([self.wallet.decrypt_wif("6PRN5mjUTtud6fUXbJXezfn6oABoSr6GSLjMbrGXRZxSUcxThxsUW8epQi"), + self.wallet.decrypt_wif("6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg")], + ["5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd", + "5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR"]) + self.wallet.masterpassword = "Satoshi" + self.assertEqual([self.wallet.decrypt_wif("6PRNFFkZc2NZ6dJqFfhRoFNMR9Lnyj7dYGrzdgXXVMXcxoKTePPX1dWByq")], + ["5HtasZ6ofTHP6HCwTqTkLDuLQisYPah7aUnSKfC7h4hMUVw2gi5"]) -- GitLab