From 0f18230e299e18bd860bdff6ab30f6fa636a5e1e Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holgernahrstaedt@gmx.de>
Date: Fri, 12 Mar 2021 21:42:09 +0100
Subject: [PATCH] Add Blocks

---
 .circleci/config.yml      |   4 ++
 CHANGELOG.rst             |   4 ++
 beem/block.py             |  37 ++++++++++++
 beem/cli.py               |   5 +-
 beem/utils.py             |  40 +++++++++++++
 examples/blockactivity.py |   7 ++-
 examples/blockstats.py    | 115 ++++++++++++++++++++++++++++++++++++++
 tests/beem/test_utils.py  |  13 ++++-
 8 files changed, 218 insertions(+), 7 deletions(-)
 create mode 100644 examples/blockstats.py

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7aebe02c..b97793a8 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -34,6 +34,7 @@ jobs:
       - run:
           name: install dependencies
           command: |
+            sudo python -m pip install --upgrade pip
             sudo python -m pip install -r requirements-test.txt
             sudo python -m pip install --upgrade secp256k1prp
 
@@ -71,6 +72,7 @@ jobs:
       - run:
           name: install dependencies
           command: |
+            sudo python -m pip install --upgrade pip
             sudo python -m pip install --upgrade -r requirements-test.txt
 
       - run:
@@ -93,6 +95,7 @@ jobs:
       - run:
           name: install dependencies
           command: |
+            sudo python -m pip install --upgrade pip
             sudo python -m pip install --upgrade -r requirements-test.txt
 
       - run:
@@ -115,6 +118,7 @@ jobs:
       - run:
           name: install dependencies
           command: |
+            sudo python -m pip install --upgrade pip
             sudo python -m pip install --upgrade -r requirements-test.txt
       - run:
           name: run tests
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2e50b032..3ddb2681 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,7 +4,11 @@ Changelog
 -------
 * Fix bug in ecda (convert mpz into int when not supported)
 * add coinactivity example script
+* add blockstats example script
+* Add Blocks class, which uses get_block_range
 * PR #272: correct blockchain virtual op batch calls (thanks to @crokkon)
+* PR #276: blockchain: get_account_reputations fix for first (thanks to @crokkon)
+* PR #287: beempy witnessproperties: fix interest rate options
 
 0.24.20
 -------
diff --git a/beem/block.py b/beem/block.py
index dd09ec78..91f95324 100644
--- a/beem/block.py
+++ b/beem/block.py
@@ -4,6 +4,7 @@ import json
 from .exceptions import BlockDoesNotExistsException
 from .utils import parse_time, formatTimeString
 from .blockchainobject import BlockchainObject
+from beem.instance import shared_blockchain_instance
 from beemapi.exceptions import ApiNotSupported
 from beemgraphenebase.py23 import bytes_types, integer_types, string_types, text_type
 
@@ -73,6 +74,8 @@ class Block(BlockchainObject):
             blockchain_instance=blockchain_instance,
             **kwargs
         )
+        if self.identifier is None:
+            self.identifier = self.block_num
 
     def _parse_json_data(self, block):
         parse_times = [
@@ -381,3 +384,37 @@ class BlockHeader(BlockchainObject):
                 else:
                     output[p] = p_date
         return json.loads(str(json.dumps(output)))
+
+
+class Blocks(list):
+    """ Obtain a list of blocks
+
+        :param list name_list: list of accounts to fetch
+        :param int count: (optional) maximum number of accounts
+            to fetch per call, defaults to 100
+        :param Steem/Hive blockchain_instance: Steem() or Hive() instance to use when
+            accessing a RPCcreator = Account(creator, blockchain_instance=self)
+    """
+    def __init__(self, starting_block_num, count=1000, lazy=False, full=True, blockchain_instance=None, **kwargs):
+        
+        if blockchain_instance is None:
+            if kwargs.get("steem_instance"):
+                blockchain_instance = kwargs["steem_instance"]
+            elif kwargs.get("hive_instance"):
+                blockchain_instance = kwargs["hive_instance"]
+        self.blockchain = blockchain_instance or shared_blockchain_instance()
+
+        if not self.blockchain.is_connected():
+            return
+        blocks = []
+
+        self.blockchain.rpc.set_next_node_on_empty_reply(False)
+
+        blocks = self.blockchain.rpc.get_block_range({'starting_block_num': starting_block_num,  "count": count}, api="block")['blocks']
+
+        super(Blocks, self).__init__(
+            [
+                Block(x, lazy=lazy, full=full, blockchain_instance=self.blockchain)
+                for x in blocks
+            ]
+        )
diff --git a/beem/cli.py b/beem/cli.py
index 96b5cefe..e0236436 100644
--- a/beem/cli.py
+++ b/beem/cli.py
@@ -2602,7 +2602,7 @@ def createpost(markdown_file, account, title, tags, community, beneficiaries, pe
     stm = shared_blockchain_instance()
     if stm.rpc is not None:
         stm.rpc.rpcconnect()
-    yaml_prefix = '---\n'
+
     if account is None:
         account = input("author: ")
     if title is None:
@@ -2626,7 +2626,6 @@ def createpost(markdown_file, account, title, tags, community, beneficiaries, pe
                 if int(index) - 1 >= len(comm_cand):
                     continue
                 community = comm_cand[int(index) - 1]
-
             ret = input("Selected community: %s - %s [yes/no]? " % (community["name"], community["title"]))
             if ret in ["y", "yes"]:
                 community_found = True
@@ -2655,7 +2654,7 @@ def createpost(markdown_file, account, title, tags, community, beneficiaries, pe
 
     if max_accepted_payout is None:
         max_accepted_payout = input("max accepted payout [return to skip]: ")
-
+    yaml_prefix = '---\n'
     yaml_prefix += 'title: "%s"\n' % title
     yaml_prefix += 'author: %s\n' % account
     yaml_prefix += 'tags: %s\n' % tags
diff --git a/beem/utils.py b/beem/utils.py
index c7176270..700d5d89 100644
--- a/beem/utils.py
+++ b/beem/utils.py
@@ -381,6 +381,46 @@ def seperate_yaml_dict_from_body(content):
         body = content
     return body, parameter
 
+
+def create_yaml_header(comment, json_metadata={}, reply_identifier=None):
+    yaml_prefix = '---\n'
+    if comment["title"] != "":
+        yaml_prefix += 'title: "%s"\n' % comment["title"]
+    if "permlink" in comment:
+        yaml_prefix += 'permlink: %s\n' % comment["permlink"]
+    yaml_prefix += 'author: %s\n' % comment["author"]
+    if "author" in json_metadata:
+        yaml_prefix += 'authored by: %s\n' % json_metadata["author"]
+    if "description" in json_metadata:
+        yaml_prefix += 'description: "%s"\n' % json_metadata["description"]
+    if "canonical_url" in json_metadata:
+        yaml_prefix += 'canonical_url: %s\n' % json_metadata["canonical_url"]
+    if "app" in json_metadata:
+        yaml_prefix += 'app: %s\n' % json_metadata["app"]
+    if "last_update" in comment:
+        yaml_prefix += 'last_update: %s\n' % comment["last_update"]
+    elif "updated" in comment:
+        yaml_prefix += 'last_update: %s\n' % comment["updated"]
+    yaml_prefix += 'max_accepted_payout: %s\n' % str(comment["max_accepted_payout"])
+    if "percent_steem_dollars" in comment:
+        yaml_prefix += 'percent_steem_dollars: %s\n' %  str(comment["percent_steem_dollars"])
+    elif "percent_hbd" in comment:
+        yaml_prefix += 'percent_hbd: %s\n' %  str(comment["percent_hbd"])
+    if "tags" in json_metadata:
+        if len(json_metadata["tags"]) > 0 and comment["category"] != json_metadata["tags"][0] and len(comment["category"]) > 0:
+            yaml_prefix += 'community: %s\n' % comment["category"]
+        yaml_prefix += 'tags: %s\n' % ",".join(json_metadata["tags"])
+    if "beneficiaries" in comment:
+        beneficiaries = []
+        for b in comment["beneficiaries"]:
+            beneficiaries.append("%s:%.2f%%" % (b["account"], b["weight"] / 10000 * 100))
+        if len(beneficiaries) > 0:
+            yaml_prefix += 'beneficiaries: %s\n' % ",".join(beneficiaries)
+    if reply_identifier is not None:
+        yaml_prefix += 'reply_identifier: %s\n' % reply_identifier
+    yaml_prefix += '---\n'
+    return yaml_prefix
+
     
 def load_dirty_json(dirty_json):
     regex_replace = [(r"([ \{,:\[])(u)?'([^']+)'", r'\1"\3"'), (r" False([, \}\]])", r' false\1'), (r" True([, \}\]])", r' true\1')]
diff --git a/examples/blockactivity.py b/examples/blockactivity.py
index 972a98f1..5902ec5c 100644
--- a/examples/blockactivity.py
+++ b/examples/blockactivity.py
@@ -59,7 +59,8 @@ def main(args=None):
     total_ops = 0
     total_virtual_ops = 0
     total_trx = 0
-    blocksperday = 20 * 60 * 24
+    duration_s = 60 * 60 * 24
+    blocksperday = int(duration_s / 3)
     
     blockchain = Blockchain(blockchain_instance=blk_inst, )
     current_block_num = blockchain.get_current_block_num()
@@ -67,7 +68,7 @@ def main(args=None):
 
     last_block = Block(last_block_id, blockchain_instance=blk_inst)
 
-    stopTime = last_block.time() + timedelta(seconds=60 * 60 * 24)
+    stopTime = last_block.time() + timedelta(seconds=duration_s)
 
     start = timer()
     for entry in blockchain.blocks(start=last_block_id, max_batch_size=max_batch_size, threading=threading, thread_num=thread_num):
@@ -93,7 +94,7 @@ def main(args=None):
 
     duration = timer() - start
     
-    stopTime = last_block.time() + timedelta(seconds=60 * 60 * 24)
+    stopTime = last_block.time() + timedelta(seconds=duration_s)
     start = timer()
     for entry in blockchain.blocks(start=last_block_id, max_batch_size=max_batch_size, threading=threading, thread_num=thread_num, only_virtual_ops=True): 
         block_time = entry["timestamp"]
diff --git a/examples/blockstats.py b/examples/blockstats.py
new file mode 100644
index 00000000..6279c107
--- /dev/null
+++ b/examples/blockstats.py
@@ -0,0 +1,115 @@
+import sys
+from datetime import datetime, timedelta
+from prettytable import PrettyTable
+import argparse
+from timeit import default_timer as timer
+import logging
+from beem.blockchain import Blockchain
+from beem.block import Block
+from beem import Hive, Blurt, Steem
+from beem.utils import parse_time
+from beem.nodelist import NodeList
+
+log = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+
+def parse_args(args=None):
+    d = 'Show op type stats for either hive, blurt or steem.'
+    parser = argparse.ArgumentParser(description=d)
+    parser.add_argument('blockchain', type=str, nargs='?',
+                        default=sys.stdin,
+                        help='Blockchain (hive, blurt or steem)')
+    return parser.parse_args(args)
+
+
+def main(args=None):
+    
+    args = parse_args(args)
+    blockchain = args.blockchain
+    
+    nodelist = NodeList()
+    nodelist.update_nodes(weights={"block": 1})
+    
+    if blockchain == "hive" or blockchain is None:
+        max_batch_size = 50
+        threading = False
+        thread_num = 16
+        block_debug = 1000
+        
+        nodes = nodelist.get_hive_nodes()
+        blk_inst = Hive(node=nodes, num_retries=3, num_retries_call=3, timeout=30)
+    elif blockchain == "blurt":
+        max_batch_size = None
+        threading = False
+        thread_num = 8
+        block_debug = 20
+        nodes = ["https://rpc.blurt.buzz/", "https://api.blurt.blog", "https://rpc.blurtworld.com", "https://rpc.blurtworld.com"]
+        blk_inst = Blurt(node=nodes, num_retries=3, num_retries_call=3, timeout=30)
+    elif blockchain == "steem":
+        max_batch_size = 50
+        threading = False
+        thread_num = 16
+        block_debug = 1000
+        nodes = nodelist.get_steem_nodes()
+        blk_inst = Steem(node=nodes, num_retries=3, num_retries_call=3, timeout=30)
+    else:
+        raise Exception("Wrong parameter, can be hive, blurt or steem")
+    print(blk_inst)
+    block_count = 0
+    total_ops = 0
+    total_trx = 0
+    duration_s = 60 * 60 * 1
+    blocksperday = int(duration_s / 3)
+    
+    blockchain = Blockchain(blockchain_instance=blk_inst, )
+    current_block_num = blockchain.get_current_block_num()
+    last_block_id = current_block_num - blocksperday
+
+    last_block = Block(last_block_id, blockchain_instance=blk_inst)
+
+    stopTime = last_block.time() + timedelta(seconds=duration_s)
+
+    start = timer()
+    op_stats = {}
+    for entry in blockchain.blocks(start=last_block_id, max_batch_size=max_batch_size, threading=threading, thread_num=thread_num):
+        if "block" in entry:
+            block_time = parse_time(entry["block"]["timestamp"])
+        else:
+            block_time = entry["timestamp"]
+        if block_time > stopTime:
+            break
+        block_count += 1
+        if "block" in entry:
+            trxs = entry["block"]["transactions"]
+        else:
+            trxs = entry["transactions"]
+        for tx in trxs:
+            total_trx += 1
+            for op in tx["operations"]:
+                if "_operation" in op["type"]:
+                    op_type = op["type"][:-10]
+                else:
+                    op_type = op["type"]
+                if op_type in op_stats:
+                    op_stats[op_type] += 1
+                else:
+                    op_stats[op_type] = 1
+                total_ops += 1
+
+        ops_per_day = total_ops / block_count * blocksperday
+        if block_count % (block_debug) == 0:
+            print("%d blocks remaining... estimated ops per day: %.1f" % (blocksperday - block_count, ops_per_day))
+
+    duration = timer() - start    
+    t = PrettyTable(["Type", "Count", "percentage"])
+    t.align = "l"
+    op_list = []
+    for o in op_stats:
+        op_list.append({"type": o, "n": op_stats[o], "perc": op_stats[o] / total_ops * 100})
+    op_list_sorted = sorted(op_list, key=lambda x: x['n'], reverse=True)
+    for op in op_list_sorted:
+        t.add_row([op["type"], op["n"], "%.2f %%" % op["perc"]])
+    print(t)
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/tests/beem/test_utils.py b/tests/beem/test_utils.py
index b6b552f8..84719414 100644
--- a/tests/beem/test_utils.py
+++ b/tests/beem/test_utils.py
@@ -2,6 +2,7 @@
 import unittest
 from datetime import datetime, date, timedelta
 import os
+from ruamel.yaml import YAML
 from beem.utils import (
     formatTimedelta,
     assets_from_string,
@@ -23,7 +24,8 @@ from beem.utils import (
     create_new_password,
     generate_password,
     import_coldcard_wif,
-    import_pubkeys
+    import_pubkeys,
+    create_yaml_header
 )
 
 
@@ -181,6 +183,15 @@ class Testcases(unittest.TestCase):
         self.assertEqual(par, {"par1": "data1", "par2": "data2", "par3": 3})
         self.assertEqual(body, " test ---")
 
+    def create_yaml_header(self):
+        comment = {"title": "test", "author": "holger80", "max_accepted_payout": 100}
+        yaml_content = create_yaml_header(comment)
+        yaml_safe = YAML(typ="safe")
+        parameter = yaml_safe.load(yaml_content)
+        self.assertEqual(parameter["title"], "test")
+        self.assertEqual(parameter["author"], "holger80")
+        self.assertEqual(parameter["max_accepted_payout"], "100")  
+
     def test_create_new_password(self):
         new_password = create_new_password()
         self.assertEqual(len(new_password), 32)
-- 
GitLab