From 50827b146074c9ad29708084a5a59235f66c6d72 Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holgernahrstaedt@gmx.de>
Date: Thu, 14 Jun 2018 21:46:12 +0200
Subject: [PATCH] several improvements and optimizations

Account
* lazy and full are correctly passed
* _parse_json_data() added to parse json
Block
* lazy and full are correctly passed
* empty operations are handled
* op_statistics improved
Blockchain
* virtual ops and ops statistics are seperatly calculated
Comment
* _parse_json_data() added to parse dates and amounts
* author_reputation is parsed to int
* lazy and full are correctly passed
Vote
* lazy and full are correctly passed
* _parse_json_data added to parse date and rshares and reputation
* vote.time is a datetime object
* json() returns the original string
Witness
* lazy and full are correctly passed
GrapheneRPC
* time_limit removed, as it supresses KeyboardInterrupt
* ws.settimeout() is set for websocket
* WebSocketTimeoutException  handling added
Unit tests
* test_account fixed
* test_time_limit removed from test_steemnoderpc
* checks added to test_blockchain_batch
---
 beem/account.py                     | 10 ++--
 beem/block.py                       | 27 ++++++-----
 beem/blockchain.py                  |  6 ++-
 beem/comment.py                     | 65 +++++++++++---------------
 beem/vote.py                        | 72 +++++++++++++++++++++--------
 beem/witness.py                     | 19 ++++----
 beemapi/graphenerpc.py              | 49 ++++++--------------
 examples/benchmark_beem.py          |  8 ++--
 tests/beem/test_account.py          | 11 +++--
 tests/beem/test_blockchain_batch.py |  2 +
 tests/beemapi/test_steemnoderpc.py  | 10 ----
 11 files changed, 145 insertions(+), 134 deletions(-)

diff --git a/beem/account.py b/beem/account.py
index 129032bd..820af4c7 100644
--- a/beem/account.py
+++ b/beem/account.py
@@ -77,6 +77,7 @@ class Account(BlockchainObject):
                etc.
         """
         self.full = full
+        self.lazy = lazy
         super(Account, self).__init__(
             account,
             lazy=lazy,
@@ -84,6 +85,7 @@ class Account(BlockchainObject):
             id_item="name",
             steem_instance=steem_instance
         )
+        self._parse_json_data()
 
     def refresh(self):
         """ Refresh/Obtain an account's data from the API server
@@ -109,8 +111,10 @@ class Account(BlockchainObject):
         self.identifier = account["name"]
         # self.steem.refresh_data()
 
-        super(Account, self).__init__(account, id_item="name", steem_instance=self.steem)
+        super(Account, self).__init__(account, id_item="name", lazy=self.lazy, full=self.full, steem_instance=self.steem)
+        self._parse_json_data()
 
+    def _parse_json_data(self):
         parse_times = [
             "last_owner_update", "last_account_update", "created", "last_owner_proved", "last_active_proved",
             "last_account_recovery", "last_vote_time", "sbd_seconds_last_update", "sbd_last_interest_payment",
@@ -2244,7 +2248,7 @@ class Accounts(AccountsObject):
         :param steem steem_instance: Steem() instance to use when
             accessing a RPC
     """
-    def __init__(self, name_list, batch_limit=100, steem_instance=None):
+    def __init__(self, name_list, batch_limit=100, lazy=False, full=True, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         if not self.steem.is_connected():
             return
@@ -2261,7 +2265,7 @@ class Accounts(AccountsObject):
 
         super(Accounts, self).__init__(
             [
-                Account(x, lazy=True, steem_instance=self.steem)
+                Account(x, lazy=lazy, full=full, steem_instance=self.steem)
                 for x in accounts
             ]
         )
diff --git a/beem/block.py b/beem/block.py
index 59ec85fc..17c8ccd1 100644
--- a/beem/block.py
+++ b/beem/block.py
@@ -59,6 +59,7 @@ class Block(BlockchainObject):
 
         """
         self.full = full
+        self.lazy = lazy
         self.only_ops = only_ops
         self.only_virtual_ops = only_virtual_ops
         super(Block, self).__init__(
@@ -85,9 +86,14 @@ class Block(BlockchainObject):
                     ops = self.steem.rpc.get_ops_in_block(self.identifier, self.only_virtual_ops)
             else:
                 ops = self.steem.rpc.get_ops_in_block(self.identifier, self.only_virtual_ops)
-            block = {'block': ops[0]["block"],
-                     'timestamp': ops[0]["timestamp"],
-                     'operations': ops}
+            if bool(ops):
+                block = {'block': ops[0]["block"],
+                         'timestamp': ops[0]["timestamp"],
+                         'operations': ops}
+            else:
+                block = {'block': [],
+                         'timestamp': '',
+                         'operations': []}
         else:
             if self.steem.rpc.get_use_appbase():
                 block = self.steem.rpc.get_block({"block_num": self.identifier}, api="block")
@@ -97,7 +103,7 @@ class Block(BlockchainObject):
                 block = self.steem.rpc.get_block(self.identifier)
         if not block:
             raise BlockDoesNotExistsException(str(self.identifier))
-        super(Block, self).__init__(block, steem_instance=self.steem)
+        super(Block, self).__init__(block, lazy=self.lazy, full=self.full, steem_instance=self.steem)
 
     @property
     def block_num(self):
@@ -145,20 +151,17 @@ class Block(BlockchainObject):
                 ops_stat[key] = 0
         else:
             ops_stat = add_to_ops_stat.copy()
-        if self.only_ops or self.only_virtual_ops:
-            for op in self["operations"]:
-                ops_stat[op["op"][0]] += 1
-            return ops_stat
-        trxs = self["transactions"]
-        for tx in trxs:
-            for op in tx["operations"]:
+        for op in self.operations:
                 if isinstance(op, dict) and 'type' in op:
                     op_type = op["type"]
                     if len(op_type) > 10 and op_type[len(op_type) - 10:] == "_operation":
                         op_type = op_type[:-10]
                     ops_stat[op_type] += 1
                 else:
-                    ops_stat[op[0]] += 1
+                    if "op" in op:
+                        ops_stat[op["op"][0]] += 1
+                    else:
+                        ops_stat[op[0]] += 1
         return ops_stat
 
 
diff --git a/beem/blockchain.py b/beem/blockchain.py
index 8f12ac0c..94f02be2 100644
--- a/beem/blockchain.py
+++ b/beem/blockchain.py
@@ -402,7 +402,11 @@ class Blockchain(object):
             return
         if stop is None:
             stop = current_block
-        for block in self.blocks(start=start, stop=stop, only_ops=True):
+        for block in self.blocks(start=start, stop=stop, only_ops=False, only_virtual_ops=False):
+            if verbose:
+                print(block["identifier"] + " " + block["timestamp"])
+            ops_stat = block.ops_statistics(add_to_ops_stat=ops_stat)
+        for block in self.blocks(start=start, stop=stop, only_ops=True, only_virtual_ops=True):
             if verbose:
                 print(block["identifier"] + " " + block["timestamp"])
             ops_stat = block.ops_statistics(add_to_ops_stat=ops_stat)
diff --git a/beem/comment.py b/beem/comment.py
index 1761d4ff..c8ab3448 100644
--- a/beem/comment.py
+++ b/beem/comment.py
@@ -52,6 +52,7 @@ class Comment(BlockchainObject):
         steem_instance=None
     ):
         self.full = full
+        self.lazy = lazy
         if isinstance(authorperm, string_types) and authorperm != "":
             [author, permlink] = resolve_authorperm(authorperm)
             self["id"] = 0
@@ -68,12 +69,15 @@ class Comment(BlockchainObject):
         )
         if "author" in self and "permlink" in self:
             self.identifier = construct_authorperm(self["author"], self["permlink"])
+        self._parse_json_data()
+
+    def _parse_json_data(self):
         parse_times = [
             "active", "cashout_time", "created", "last_payout", "last_update",
             "max_cashout_time"
         ]
         for p in parse_times:
-            if p in self and isinstance(self.get(p), str):
+            if p in self and isinstance(self.get(p), string_types):
                 self[p] = formatTimeString(self.get(p, "1970-01-01T00:00:00"))
         # Parse Amounts
         sbd_amounts = [
@@ -98,6 +102,13 @@ class Comment(BlockchainObject):
             if 'community' in self['json_metadata']:
                 self['community'] = self['json_metadata']['community']
 
+        parse_int = [
+            "author_reputation",
+        ]
+        for p in parse_int:
+            if p in self and isinstance(self.get(p), string_types):
+                self[p] = int(self.get(p, "0"))
+
     def _metadata_to_dict(self):
         """turn json_metadata into python dict"""
         meta_str = self.get("json_metadata", "{}")
@@ -122,38 +133,10 @@ class Comment(BlockchainObject):
             content = self.steem.rpc.get_content(author, permlink)
         if not content or not content['author'] or not content['permlink']:
             raise ContentDoesNotExistsException(self.identifier)
-        super(Comment, self).__init__(content, id_item="authorperm", steem_instance=self.steem)
+        super(Comment, self).__init__(content, id_item="authorperm", lazy=self.lazy, full=self.full, steem_instance=self.steem)
         self["authorperm"] = construct_authorperm(self["author"], self["permlink"])
         self.identifier = self["authorperm"]
-        parse_times = [
-            "active", "cashout_time", "created", "last_payout", "last_update",
-            "max_cashout_time"
-        ]
-        for p in parse_times:
-            if p in self and isinstance(self.get(p), string_types):
-                self[p] = formatTimeString(self.get(p, "1970-01-01T00:00:00"))
-        # Parse Amounts
-        sbd_amounts = [
-            "total_payout_value",
-            "max_accepted_payout",
-            "pending_payout_value",
-            "curator_payout_value",
-            "total_pending_payout_value",
-            "promoted",
-        ]
-        for p in sbd_amounts:
-            if p in self and isinstance(self.get(p), string_types):
-                self[p] = Amount(self.get(p, "0.000 SBD"), steem_instance=self.steem)
-        # turn json_metadata into python dict
-        self._metadata_to_dict()
-        self["tags"] = []
-        self['community'] = ''
-        if isinstance(self['json_metadata'], dict):
-            if "tags" in self['json_metadata']:
-                self["tags"] = self['json_metadata']["tags"]
-            if 'community' in self['json_metadata']:
-                if p in self:
-                    self['community'] = self['json_metadata']['community']
+        self._parse_json_data()
 
     def json(self):
         output = self.copy()
@@ -187,6 +170,12 @@ class Comment(BlockchainObject):
         for p in sbd_amounts:
             if p in output and isinstance(output[p], Amount):
                 output[p] = output[p].json()
+        parse_int = [
+            "author_reputation",
+        ]
+        for p in parse_int:
+            if p in output and isinstance(output[p], int):
+                output[p] = str(output[p])
         return json.loads(str(json.dumps(output)))
 
     @property
@@ -507,10 +496,12 @@ class Comment(BlockchainObject):
             return content_replies
         return [Comment(c, steem_instance=self.steem) for c in content_replies]
 
-    def get_votes(self):
+    def get_votes(self, raw_data=False):
         """Returns all votes as ActiveVotes object"""
+        if raw_data:
+            return self["active_votes"]
         from .vote import ActiveVotes
-        return ActiveVotes(self, steem_instance=self.steem)
+        return ActiveVotes(self, lazy=False, steem_instance=self.steem)
 
     def upvote(self, weight=+100, voter=None):
         """ Upvote the post
@@ -703,7 +694,7 @@ class RecentReplies(list):
             Default: True
         :param steem steem_instance: Steem() instance to use when accesing a RPC
     """
-    def __init__(self, author, skip_own=True, steem_instance=None):
+    def __init__(self, author, skip_own=True, lazy=False, full=True, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         if not self.steem.is_connected():
             return None
@@ -715,7 +706,7 @@ class RecentReplies(list):
             post = state["content"][reply]
             if skip_own and post["author"] == author:
                 continue
-            comments.append(Comment(post, lazy=True, steem_instance=self.steem))
+            comments.append(Comment(post, lazy=lazy, full=full, steem_instance=self.steem))
         super(RecentReplies, self).__init__(comments)
 
 
@@ -725,7 +716,7 @@ class RecentByPath(list):
         :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):
+    def __init__(self, path="promoted", category=None, lazy=False, full=True, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         if not self.steem.is_connected():
             return None
@@ -736,5 +727,5 @@ class RecentByPath(list):
         for reply in replies:
             post = state["content"][reply]
             if category is None or (category is not None and post["category"] == category):
-                comments.append(Comment(post, lazy=True, steem_instance=self.steem))
+                comments.append(Comment(post, lazy=lazy, full=full, steem_instance=self.steem))
         super(RecentByPath, self).__init__(comments)
diff --git a/beem/vote.py b/beem/vote.py
index e2749473..b6f91816 100644
--- a/beem/vote.py
+++ b/beem/vote.py
@@ -45,6 +45,7 @@ class Vote(BlockchainObject):
         steem_instance=None
     ):
         self.full = full
+        self.lazy = lazy
         if isinstance(voter, string_types) and authorperm is not None:
             [author, permlink] = resolve_authorperm(authorperm)
             self["voter"] = voter
@@ -81,6 +82,7 @@ class Vote(BlockchainObject):
             full=full,
             steem_instance=steem_instance
         )
+        self._parse_json_data()
 
     def refresh(self):
         if self.identifier is None:
@@ -103,9 +105,25 @@ class Vote(BlockchainObject):
                 vote = x
         if not vote:
             raise VoteDoesNotExistsException(self.identifier)
-        super(Vote, self).__init__(vote, id_item="authorpermvoter", steem_instance=self.steem)
+        super(Vote, self).__init__(vote, id_item="authorpermvoter", lazy=self.lazy, full=self.full, steem_instance=self.steem)
 
         self.identifier = construct_authorpermvoter(author, permlink, voter)
+        self._parse_json_data()
+
+    def _parse_json_data(self):
+        parse_int = [
+            "rshares", "reputation",
+        ]
+        for p in parse_int:
+            if p in self and isinstance(self.get(p), string_types):
+                self[p] = int(self.get(p, "0"))
+
+        if "time" in self and isinstance(self.get("time"), string_types) and self.get("time") != '':
+            self["time"] = formatTimeString(self.get("time", "1970-01-01T00:00:00"))
+        elif "timestamp" in self and isinstance(self.get("timestamp"), string_types) and self.get("timestamp") != '':
+            self["time"] = formatTimeString(self.get("timestamp", "1970-01-01T00:00:00"))
+        else:
+            self["time"] = formatTimeString("1970-01-01T00:00:00")
 
     def json(self):
         output = self.copy()
@@ -113,6 +131,22 @@ class Vote(BlockchainObject):
             output.pop("author")
         if "permlink" in output:
             output.pop("permlink")
+        parse_times = [
+            "time"
+        ]
+        for p in parse_times:
+            if p in output:
+                date = output.get(p, datetime(1970, 1, 1, 0, 0))
+                if isinstance(date, (datetime, date)):
+                    output[p] = formatTimeString(date)
+                else:
+                    output[p] = date
+        parse_int = [
+            "rshares", "reputation",
+        ]
+        for p in parse_int:
+            if p in output and isinstance(output[p], int):
+                output[p] = str(output[p])
         return json.loads(str(json.dumps(output)))
 
     @property
@@ -168,10 +202,7 @@ class Vote(BlockchainObject):
 
     @property
     def time(self):
-        t = self.get("time", '')
-        if t == '':
-            t = self.get("timestamp", '')
-        return t
+        return self["time"]
 
 
 class VotesObject(list):
@@ -181,7 +212,7 @@ class VotesObject(list):
         if sort_key == 'sbd':
             sortedList = sorted(self, key=lambda self: self.rshares, reverse=reverse)
         elif sort_key == 'time':
-            sortedList = sorted(self, key=lambda self: (utc.localize(datetime.utcnow()) - formatTimeString(self.time)).total_seconds(), reverse=reverse)
+            sortedList = sorted(self, key=lambda self: (utc.localize(datetime.utcnow()) - self.time).total_seconds(), reverse=reverse)
         elif sort_key == 'votee':
             sortedList = sorted(self, key=lambda self: self.votee, reverse=reverse)
         elif sort_key in ['voter', 'rshares', 'percent', 'weight']:
@@ -200,15 +231,16 @@ class VotesObject(list):
         for vote in self.get_sorted_list(sort_key=sort_key, reverse=reverse):
             if not allow_refresh:
                 vote.cached = True
-            time = vote.time
-            if time != '':
-                d_time = formatTimeString(time)
+
+            d_time = vote.time
+            if d_time != formatTimeString("1970-01-01T00:00:00"):
                 td = utc.localize(datetime.utcnow()) - d_time
                 timestr = str(td.days) + " days " + str(td.seconds // 3600) + ":" + str((td.seconds // 60) % 60)
             else:
                 start = None
                 stop = None
-                timestr = ""
+                timestr = ''
+
             percent = vote.get('percent', '')
             if percent == '':
                 start_percent = None
@@ -234,10 +266,8 @@ class VotesObject(list):
         start = addTzInfo(start)
         stop = addTzInfo(stop)
         for vote in self.get_sorted_list(sort_key=sort_key, reverse=reverse):
-            time = vote.time
-            if time != '':
-                d_time = formatTimeString(time)
-            else:
+            d_time = vote.time
+            if d_time != formatTimeString("1970-01-01T00:00:00"):
                 start = None
                 stop = None
             percent = vote.get('percent', '')
@@ -257,7 +287,7 @@ class VotesObject(list):
                 elif var == "time":
                     v = d_time
                 elif var == "rshares":
-                    v = vote.get("rshares", "")
+                    v = vote.get("rshares", 0)
                 elif var == "percent":
                     v = percent
                 elif var == "weight":
@@ -302,7 +332,7 @@ class ActiveVotes(VotesObject):
         :param str authorperm: authorperm link
         :param steem steem_instance: Steem() instance to use when accesing a RPC
     """
-    def __init__(self, authorperm, steem_instance=None):
+    def __init__(self, authorperm, lazy=False, full=False, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         votes = None
         if not self.steem.is_connected():
@@ -339,7 +369,7 @@ class ActiveVotes(VotesObject):
         self.identifier = authorperm
         super(ActiveVotes, self).__init__(
             [
-                Vote(x, authorperm=authorperm, lazy=True, steem_instance=self.steem)
+                Vote(x, authorperm=authorperm, lazy=lazy, full=full, steem_instance=self.steem)
                 for x in votes
             ]
         )
@@ -352,7 +382,7 @@ class AccountVotes(VotesObject):
         :param str account: Account name
         :param steem steem_instance: Steem() instance to use when accesing a RPC
     """
-    def __init__(self, account, start=None, stop=None, steem_instance=None):
+    def __init__(self, account, start=None, stop=None, lazy=False, full=False, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         start = addTzInfo(start)
         stop = addTzInfo(stop)
@@ -362,11 +392,13 @@ class AccountVotes(VotesObject):
         vote_list = []
         for x in votes:
             time = x.get("time", "")
-            if time != "":
+            if time != "" and isinstance(time, string_types):
                 d_time = formatTimeString(time)
+            elif isinstance(time, datetime):
+                d_time = time
             else:
                 d_time = addTzInfo(datetime(1970, 1, 1, 0, 0, 0))
             if (start is None or d_time >= start) and (stop is None or d_time <= stop):
-                vote_list.append(Vote(x, authorperm=account["name"], steem_instance=self.steem))
+                vote_list.append(Vote(x, authorperm=account["name"], lazy=lazy, full=full, steem_instance=self.steem))
 
         super(AccountVotes, self).__init__(vote_list)
diff --git a/beem/witness.py b/beem/witness.py
index ef37e3af..24e7c188 100644
--- a/beem/witness.py
+++ b/beem/witness.py
@@ -42,6 +42,7 @@ class Witness(BlockchainObject):
         steem_instance=None
     ):
         self.full = full
+        self.lazy = lazy
         super(Witness, self).__init__(
             owner,
             lazy=lazy,
@@ -64,7 +65,7 @@ class Witness(BlockchainObject):
             witness = self.steem.rpc.get_witness_by_account(self.identifier)
         if not witness:
             raise WitnessDoesNotExistsException(self.identifier)
-        super(Witness, self).__init__(witness, id_item="owner", steem_instance=self.steem)
+        super(Witness, self).__init__(witness, id_item="owner", lazy=self.lazy, full=self.full, steem_instance=self.steem)
         self.identifier = self["owner"]
 
     @property
@@ -219,7 +220,7 @@ class Witnesses(WitnessesObject):
         :param steem steem_instance: Steem() instance to use when
             accesing a RPC
     """
-    def __init__(self, steem_instance=None):
+    def __init__(self, lazy=False, full=False, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         self.steem.rpc.set_next_node_on_empty_reply(False)
         if self.steem.rpc.get_use_appbase():
@@ -232,7 +233,7 @@ class Witnesses(WitnessesObject):
         self.identifier = ""
         super(Witnesses, self).__init__(
             [
-                Witness(x, lazy=True, steem_instance=self.steem)
+                Witness(x, lazy=lazy, full=full, steem_instance=self.steem)
                 for x in self.active_witnessess
             ]
         )
@@ -245,7 +246,7 @@ class WitnessesVotedByAccount(WitnessesObject):
         :param steem steem_instance: Steem() instance to use when
             accesing a RPC
     """
-    def __init__(self, account, steem_instance=None):
+    def __init__(self, account, lazy=False, full=False, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         self.account = Account(account, full=True, steem_instance=self.steem)
         account_name = self.account["name"]
@@ -266,7 +267,7 @@ class WitnessesVotedByAccount(WitnessesObject):
 
         super(WitnessesVotedByAccount, self).__init__(
             [
-                Witness(x, lazy=True, steem_instance=self.steem)
+                Witness(x, lazy=lazy, full=full, steem_instance=self.steem)
                 for x in witnessess
             ]
         )
@@ -279,7 +280,7 @@ class WitnessesRankedByVote(WitnessesObject):
         :param steem steem_instance: Steem() instance to use when
             accesing a RPC
     """
-    def __init__(self, from_account="", limit=100, steem_instance=None):
+    def __init__(self, from_account="", limit=100, lazy=False, full=False, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         witnessList = []
         last_limit = limit
@@ -313,7 +314,7 @@ class WitnessesRankedByVote(WitnessesObject):
             witnessess = witnessess[1:]
         if len(witnessess) > 0:
             for x in witnessess:
-                witnessList.append(Witness(x, lazy=True, steem_instance=self.steem))
+                witnessList.append(Witness(x, lazy=lazy, full=full, steem_instance=self.steem))
         if len(witnessList) == 0:
             return
         super(WitnessesRankedByVote, self).__init__(witnessList)
@@ -326,7 +327,7 @@ class ListWitnesses(WitnessesObject):
         :param steem steem_instance: Steem() instance to use when
             accesing a RPC
     """
-    def __init__(self, from_account, limit, steem_instance=None):
+    def __init__(self, from_account, limit, lazy=False, full=False, steem_instance=None):
         self.steem = steem_instance or shared_steem_instance()
         self.identifier = from_account
         self.steem.rpc.set_next_node_on_empty_reply(False)
@@ -338,7 +339,7 @@ class ListWitnesses(WitnessesObject):
             return
         super(ListWitnesses, self).__init__(
             [
-                Witness(x, lazy=True, steem_instance=self.steem)
+                Witness(x, lazy=lazy, full=full, steem_instance=self.steem)
                 for x in witnessess
             ]
         )
diff --git a/beemapi/graphenerpc.py b/beemapi/graphenerpc.py
index 0670cb0f..61633eae 100644
--- a/beemapi/graphenerpc.py
+++ b/beemapi/graphenerpc.py
@@ -17,7 +17,6 @@ import re
 import time
 import warnings
 import six
-from contextlib import contextmanager
 from .exceptions import (
     UnauthorizedError, RPCConnection, RPCError, RPCErrorDoRetry, NumRetriesReached, CallRetriesReached, WorkingNodeMissing, TimeoutException
 )
@@ -36,7 +35,7 @@ WEBSOCKET_MODULE = None
 if not WEBSOCKET_MODULE:
     try:
         import websocket
-        from websocket._exceptions import WebSocketConnectionClosedException
+        from websocket._exceptions import WebSocketConnectionClosedException, WebSocketTimeoutException
         WEBSOCKET_MODULE = "websocket"
     except ImportError:
         WEBSOCKET_MODULE = None
@@ -85,22 +84,6 @@ def create_ws_instance(use_ssl=True, enable_multithread=True):
         return websocket.WebSocket(enable_multithread=enable_multithread)
 
 
-@contextmanager
-def time_limit(seconds, msg=''):
-    timer = threading.Timer(seconds, lambda: interrupt_main())
-    timer.start()
-    try:
-        yield
-    except KeyboardInterrupt:
-        raise TimeoutException("Timed out for operation {}".format(msg))
-    finally:
-        # if the action ends in specified time, timer is canceled
-        try:
-            timer.cancel()
-        except:
-            raise TimeoutException("Timed out for operation {}".format(msg))
-
-
 class GrapheneRPC(object):
     """
     This class allows to call API methods synchronously, without callbacks.
@@ -211,9 +194,11 @@ class GrapheneRPC(object):
                 log.debug("Trying to connect to node %s" % self.url)
                 if self.url[:3] == "wss":
                     self.ws = create_ws_instance(use_ssl=True)
+                    self.ws.settimeout(self.timeout)
                     self.current_rpc = self.rpc_methods["ws"]
                 elif self.url[:2] == "ws":
                     self.ws = create_ws_instance(use_ssl=False)
+                    self.ws.settimeout(self.timeout)
                     self.current_rpc = self.rpc_methods["ws"]
                 else:
                     self.ws = None
@@ -223,12 +208,8 @@ class GrapheneRPC(object):
                                     'content-type': 'application/json'}
             try:
                 if self.ws:
-                    try:
-                        with time_limit(self.timeout, 'ws.connect()'):
-                            self.ws.connect(self.url)
-                            self.rpclogin(self.user, self.password)
-                    except TimeoutException:
-                        raise RPCError("Timeout")
+                    self.ws.connect(self.url)
+                    self.rpclogin(self.user, self.password)
                 try:
                     props = None
                     if not self.use_condenser:
@@ -268,11 +249,7 @@ class GrapheneRPC(object):
         """Close Websocket"""
         if self.ws is None:
             return
-        try:
-            with time_limit(self.timeout, 'ws.close()'):
-                self.ws.close()
-        except TimeoutException:
-            raise RPCError("Timeout")
+        self.ws.close()
 
     def request_send(self, payload):
         response = self.session.post(self.url,
@@ -287,13 +264,9 @@ class GrapheneRPC(object):
     def ws_send(self, payload):
         if self.ws is None:
             raise RPCConnection("No websocket available!")
-        try:
-            with time_limit(self.timeout, 'ws.recv()'):
-                self.ws.send(payload)
-                reply = self.ws.recv()
-                return reply
-        except TimeoutException:
-            raise RPCError("Timeout")
+        self.ws.send(payload)
+        reply = self.ws.recv()
+        return reply
 
     def get_network(self, props=None):
         """ Identify the connected network. This call returns a
@@ -397,6 +370,10 @@ class GrapheneRPC(object):
                 self.nodes.increase_error_cnt()
                 self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
                 self.rpcconnect()
+            except WebSocketTimeoutException as e:
+                self.nodes.increase_error_cnt()
+                self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
+                self.rpcconnect()
             except Exception as e:
                 self.nodes.increase_error_cnt()
                 self.nodes.sleep_and_check_retries(str(e), sleep=False, call_retry=False)
diff --git a/examples/benchmark_beem.py b/examples/benchmark_beem.py
index eea39133..49080105 100644
--- a/examples/benchmark_beem.py
+++ b/examples/benchmark_beem.py
@@ -12,6 +12,7 @@ from beem.blockchain import Blockchain
 from beem.block import Block
 from beem.steem import Steem
 from beem.utils import parse_time, formatTimedelta
+from beem.nodelist import NodeList
 log = logging.getLogger(__name__)
 logging.basicConfig(level=logging.INFO)
 
@@ -19,18 +20,19 @@ logging.basicConfig(level=logging.INFO)
 if __name__ == "__main__":
     node_setup = 0
     how_many_hours = 1
+    nodes = NodeList()
     if node_setup == 0:
-        stm = Steem(node="https://api.steemit.com", num_retries=10)
+        stm = Steem(node=nodes.get_nodes(normal=False, wss=False), num_retries=10)
         max_batch_size = None
         threading = False
         thread_num = 8
     elif node_setup == 1:
-        stm = Steem(node="https://api.steemit.com", num_retries=10)
+        stm = Steem(node=nodes.get_nodes(normal=False, wss=False), num_retries=10)
         max_batch_size = None
         threading = True
         thread_num = 8
     elif node_setup == 2:
-        stm = Steem(num_retries=10)
+        stm = Steem(node=nodes.get_nodes(appbase=False, https=False), num_retries=10)
         max_batch_size = None
         threading = True
         thread_num = 16
diff --git a/tests/beem/test_account.py b/tests/beem/test_account.py
index 6540062e..7ffc289b 100644
--- a/tests/beem/test_account.py
+++ b/tests/beem/test_account.py
@@ -96,7 +96,7 @@ class Testcases(unittest.TestCase):
             zero_element = 0
         else:
             account = self.account_appbase
-            zero_element = 0  # Bug in steem
+            zero_element = 1  # Bug in steem
         h_all_raw = []
         for h in account.history_reverse(raw_output=True):
             h_all_raw.append(h)
@@ -104,7 +104,8 @@ class Testcases(unittest.TestCase):
         h_list = []
         for h in account.history(stop=10, use_block_num=False, batch_size=10, raw_output=True):
             h_list.append(h)
-        zero_element = h_list[0][0]
+        zero_element = h_all_raw[-1][0]
+        self.assertEqual(h_list[0][0], zero_element)
         self.assertEqual(h_list[-1][0], 10)
         self.assertEqual(h_list[0][1]['block'], h_all_raw[-1][1]['block'])
         self.assertEqual(h_list[-1][1]['block'], h_all_raw[-11 + zero_element][1]['block'])
@@ -127,6 +128,7 @@ class Testcases(unittest.TestCase):
         h_list = []
         for h in account.history_reverse(start=10, stop=0, use_block_num=False, batch_size=10, raw_output=False):
             h_list.append(h)
+        # zero_element = h_list[-1]['index']
         self.assertEqual(h_list[0]['index'], 10)
         self.assertEqual(h_list[-1]['index'], zero_element)
         self.assertEqual(h_list[0]['block'], h_all_raw[-11 + zero_element][1]['block'])
@@ -475,7 +477,10 @@ class Testcases(unittest.TestCase):
 
         for k in keys:
             if k not in "json_metadata" and k != 'reputation' and k != 'active_votes':
-                self.assertEqual(content[k], json_content[k])
+                if isinstance(content[k], dict) and isinstance(json_content[k], list):
+                    self.assertEqual(list(content[k].values()), json_content[k])
+                else:
+                    self.assertEqual(content[k], json_content[k])
 
     def test_estimate_virtual_op_num(self):
         stm = self.bts
diff --git a/tests/beem/test_blockchain_batch.py b/tests/beem/test_blockchain_batch.py
index b376068a..579afdd7 100644
--- a/tests/beem/test_blockchain_batch.py
+++ b/tests/beem/test_blockchain_batch.py
@@ -49,6 +49,8 @@ class Testcases(unittest.TestCase):
         opNames = ["transfer", "vote"]
         for op in b.stream(opNames=opNames, start=self.start, stop=self.stop, max_batch_size=self.max_batch_size, threading=False):
             ops_stream.append(op)
+        self.assertTrue(ops_stream[0]["block_num"] >= self.start)
+        self.assertTrue(ops_stream[-1]["block_num"] <= self.stop)
         op_stat = b.ops_statistics(start=self.start, stop=self.stop)
         self.assertEqual(op_stat["vote"] + op_stat["transfer"], len(ops_stream))
         ops_blocks = []
diff --git a/tests/beemapi/test_steemnoderpc.py b/tests/beemapi/test_steemnoderpc.py
index 78e3007d..1e61fa2f 100644
--- a/tests/beemapi/test_steemnoderpc.py
+++ b/tests/beemapi/test_steemnoderpc.py
@@ -14,7 +14,6 @@ import itertools
 from pprint import pprint
 from beem import Steem
 from beemapi.steemnoderpc import SteemNodeRPC
-from beemapi.graphenerpc import time_limit
 from beemapi.websocket import SteemWebsocket
 from beemapi import exceptions
 from beemapi.exceptions import NumRetriesReached, CallRetriesReached
@@ -214,12 +213,3 @@ class Testcases(unittest.TestCase):
             exceptions.NoApiWithName
         ):
             rpc.get_block({"block_num": 1}, api="wrong_api")
-
-    def test_time_limit(self):
-        with time_limit(5, 'sleep'):
-            time.sleep(1)
-        with self.assertRaises(
-            exceptions.TimeoutException
-        ):
-            with time_limit(1, 'sleep'):
-                time.sleep(3)
-- 
GitLab