Пример #1
0
    def handle(self, *args, **options):
        client = Client()
        acc = client.account(settings.CURATION_BOT_ACCOUNT)
        for index, transaction in acc.history(
                filter=["delegate_vesting_shares"],
                order="asc",
                only_operation_data=False,
        ):
            op = transaction["op"][1]
            if op.get("delegator") == settings.CURATION_BOT_ACCOUNT:
                continue

            if op.get("delegator") in BLACKLIST:
                continue

            try:
                sponsor = Sponsor.objects.get(username=op.get("delegator"))
                sponsor.delegation_modified_at = add_tz_info(
                    parse(transaction["timestamp"]))
            except Sponsor.DoesNotExist:
                sponsor = Sponsor(
                    username=op.get("delegator"),
                )
                sponsor.delegation_created_at = add_tz_info(
                    parse(transaction["timestamp"]))
            sponsor.delegation_amount = Amount(op.get("vesting_shares")).amount
            sponsor.save()

            print(f"Delegation of {op['delegator']}:"
                  f" {op['vesting_shares']} is saved.")
Пример #2
0
    def update_info(self, steem_per_mvest=None, account_detail=None):
        c = Client(nodes=["https://api.hivekings.com"])

        if not steem_per_mvest:
            # get chain properties
            dygp = c.get_dynamic_global_properties()
            steem_per_mvest = (
                float(Amount(dygp["total_vesting_fund_steem"]).amount) /
                (float(Amount(dygp["total_vesting_shares"]).amount) / 1e6))

        # get account detail
        if not account_detail:
            account_detail = c.get_accounts([self.username])[0]
        vests = float(Amount(account_detail["vesting_shares"]))

        # calculate account age
        t = parse(account_detail["created"])
        if t.tzinfo is None:
            utc_time = pytz.timezone('UTC')
            t = utc_time.localize(t)

        # account reputation
        acc = Account(c)
        acc.raw_data = account_detail

        self.reputation = acc.reputation(precision=4)
        self.sp = vests / 1e6 * steem_per_mvest
        self.vests = vests
        self.account_age = (timezone.now() - t).total_seconds() / 86400
        self.post_count = account_detail["post_count"]
        self.save()

        return self
Пример #3
0
def fetch_poll_data(author, permlink):
    """
    Fetch a poll from the blockchain and return the poll metadata.
    """
    c = LightSteemClient()
    content = c.get_content(author, permlink)
    if content.get("id") == 0:
        raise ValueError("Not a valid blockchain Comment object")
    metadata = json.loads(content.get("json_metadata"))
    if not metadata:
        raise ValueError("Not a poll")
    if metadata.get("content_type") != "poll":
        raise ValueError("Not a poll")

    votes_casted = False
    comments = c.get_content_replies(author, permlink)
    for comment in comments:
        if not comment.get("json_metadata"):
            continue
        json_metadata = json.loads(comment.get("json_metadata"))
        try:
            if json_metadata and json_metadata.get(
                    "content_type") == "poll_vote":
                votes_casted = True
        except AttributeError:
            continue

    return {
        "question": metadata.get("question"),
        "description": metadata.get("description"),
        "answers": metadata.get("choices"),
        "tags": metadata.get("tags"),
        "votes_casted": votes_casted,
    }
Пример #4
0
class Steemit:
    def __init__(self, username, steem_key):
        self.username = username
        self.client = Client(keys=[steem_key])

    def get_account(self):
        return self.client.account(self.username)

    def get_vp(self):
        account = self.get_account()
        return account.vp()

    def get_rc(self):
        account = self.get_account()
        return account.rc()

    def post_vote(self, voting_user, voting_link, weight=10000):
        try:
            op = Operation(
                'vote', {
                    "voter": self.username,
                    "author": voting_user,
                    "permlink": voting_link.split(f'{voting_user}/')[1],
                    "weight": weight,
                })
            result = self.client.broadcast(op)
            logging.info(result)
        except:
            logging.info('Broadcast Fail!')
            return False
        return True
Пример #5
0
    def __init__(self, account=None, posting_key=None, active_key=None,
                 nodes=None):

        # default node is api.steemit.com
        if not nodes:
            nodes = ["https://api.steemit.com", ]

        self.account = account
        self.vote_client = Client(keys=[posting_key, ], nodes=nodes)
        self.refund_client = Client(keys=[active_key, ], nodes=nodes)
Пример #6
0
def get_lightsteem_client():
    """A generic function to standardize the STEEM blockchain
    interaction.

    The default node can be passed as an environment variable. If it's not
    passed the default is set as `api.steemit.com`.
    """
    nodes = os.getenv("NODE", [
        "https://api.steemit.com",
    ])
    return Client(nodes=nodes)
Пример #7
0
 def __init__(self,
              account,
              posting_key=None,
              active_key=None,
              nodes=None,
              db=None,
              probability_dimensions=None,
              min_age_to_vote=None,
              max_age_to_vote=None,
              minimum_vp_to_vote=None,
              vote_price=None):
     self.db = db
     self.nodes = nodes or ["https://api.steemit.com"]
     self.account = account
     self.client = Client(loglevel=logging.INFO)
     self.vote_client = Client(keys=[
         posting_key,
     ], nodes=nodes)
     self.refund_client = Client(keys=[
         active_key,
     ], nodes=nodes)
     self.min_age_to_vote = min_age_to_vote or 300
     self.max_age_to_vote = max_age_to_vote or 43200
     self.minimum_vp_to_vote = minimum_vp_to_vote or 80
     self.vote_percent = VotePercent(
         probability_dimensions=probability_dimensions)
     self.vote_price = Amount(vote_price) or Amount("1.000 STEEM")
Пример #8
0
 def handle(self, *args, **options):
     users = User.objects.all()
     c = LightsteemClient(nodes=["https://hived.emre.sh"])
     dygp = c.get_dynamic_global_properties()
     steem_per_mvest = (
             float(Amount(dygp["total_vesting_fund_hive"]).amount) /
             (float(Amount(dygp["total_vesting_shares"]).amount) / 1e6))
     c = LightsteemClient(nodes=["https://hived.emre.sh"])
     for chunk in chunks(users, 500):
         account_details = c.get_accounts([c.username for c in chunk])
         for account_detail in account_details:
             try:
                 related_user = User.objects.get(
                     username=account_detail["name"])
             except User.DoesNotExist:
                 print(f"{account_detail['name']} is not found. Skipping.")
                 continue
             print("updating", related_user)
             related_user.update_info(
                 steem_per_mvest=steem_per_mvest,
                 account_detail=account_detail,
             )
Пример #9
0
    def handle(self, *args, **options):
        active_key = getpass.getpass(
            f"Active key of f{settings.SPONSORS_ACCOUNT}")
        client = Client(keys=[active_key, ], nodes=["https://api.hivekings.com"])
        account = client.account(settings.SPONSORS_ACCOUNT)
        one_week_ago = now() - timedelta(days=7)
        sponsors = Sponsor.objects.filter(
            delegation_created_at__lt=one_week_ago,
            delegation_amount__gt=0,
            opt_in_to_rewards=True,
        )
        print(f"{sponsors.count()} sponsors found.")
        total_shares = Sponsor.objects.aggregate(
            total=Sum("delegation_amount"))["total"]
        print(f"{total_shares} VESTS delegated.")
        liquid_funds = Amount(account.raw_data["balance"])
        print(f"dpoll.sponsors has {liquid_funds}.")
        one_percent_share = liquid_funds.amount / 100

        transfers = []
        for sponsor in sponsors:
            shares_in_percent = Decimal(
                sponsor.delegation_amount * 100 / total_shares)
            amount = "%.3f" % (one_percent_share * shares_in_percent)
            transfers.append({
                "from": settings.SPONSORS_ACCOUNT,
                "to": sponsor.username,
                "amount": f"{amount} STEEM",
                "memo": f"Greetings {sponsor.username},"
                        " Thank you for supporting dPoll. "
                        "Here is your weekly rewards."
            })

        op_list = [Operation('transfer', op) for op in transfers]
        client.broadcast(op_list)
        print("Rewards are sent!")
Пример #10
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.config = kwargs.get("dcom_config")
     self.lightsteem_client = LightsteemClient(
         nodes=self.config["steem_nodes"],
         keys=[self.config["bot_posting_key"]])
     self.community_name = self.config.get("community_name")
     self.registration_channel = self.config.get("registration_channel")
     self.registration_account = self.config.get("registration_account")
     self.role_name_for_registered_users = self.config.get(
         "role_name_for_registered_users")
     self.curation_channels = self.config.get("curation_channels")
     self.mongo_client = MongoClient(self.config.get("M0NGO_URI"))
     self.mongo_database = self.mongo_client["dcom"]
     self.patron_role = self.config.get("patron_role")
     self.bot_log_channel = self.config.get("bot_log_channel")
     self.account_for_vp_check = self.config.get("account_for_vp_check")
     self.limit_on_maximum_vp = self.config.get("limit_on_maximum_vp")
     self.bot_account = self.config.get("bot_account")
     self.auto_curation_vote_weight = 20
Пример #11
0
class TransferListener:

    def __init__(self, account=None, posting_key=None, active_key=None,
                 nodes=None):

        # default node is api.steemit.com
        if not nodes:
            nodes = ["https://api.steemit.com", ]

        self.account = account
        self.vote_client = Client(keys=[posting_key, ], nodes=nodes)
        self.refund_client = Client(keys=[active_key, ], nodes=nodes)

    def get_incoming_transfers(self):
        stop_at = datetime.now() - timedelta(hours=24)
        acc = self.vote_client.account(self.account)
        transfers = []
        for _, transaction in acc.history(
                filter=["transfer"],
                only_operation_data=False,
                stop_at=stop_at,
        ):
            op = transaction["op"][1]
            if op["to"] != self.account:
                continue
            transfers.append(transaction)

        return transfers

    def poll_transfers(self):
        while True:
            try:
                for transfer in self.get_incoming_transfers():
                    print(transfer)
            except Exception as error:
                print(error)
            time.sleep(3)
Пример #12
0
    def handle(self, *args, **options):
        """Entry point for the Django management command"""
        client = Client(keys=[
            settings.PROMOTION_ACCOUNT_ACTIVE_KEY,
        ],
                        nodes=["https://api.hivekings.com"])
        acc = client.account(settings.PROMOTION_ACCOUNT)
        for _, transaction in acc.history(
                filter=["transfer"],
                only_operation_data=False,
        ):

            op = transaction["op"][1]

            # only process incoming transactions
            if op["from"] == settings.PROMOTION_ACCOUNT:
                continue

            try:
                promotion_transaction = PromotionTransaction.objects.get(
                    trx_id=transaction["trx_id"])
                # if transaction already exists, means what we already
                #  processed it. so, we can skip it safely.
                print(f"This transaction is already processed."
                      f"Skipping. ({transaction['trx_id']})")
                continue
            except PromotionTransaction.DoesNotExist:
                amount = Amount(op["amount"])
                promotion_amount = '%.3f' % float(amount.amount)
                # create a base transaction first
                promotion_transaction = PromotionTransaction(
                    trx_id=transaction["trx_id"],
                    from_user=op["from"],
                    amount=promotion_amount,
                    memo=op["memo"],
                )
                promotion_transaction.save()

                # check if the asset is valid
                if amount.symbol == "STEEM":
                    print(f"Invalid Asset. Refunding. ({op['amount']})")
                    self.refund(client, op["from"], op["amount"],
                                "Only SBD is accepted.")
                    continue

                # check if the memo is valid
                memo = op["memo"]
                try:
                    author = memo.split("@")[1].split("/")[0]
                    permlink = memo.split("@")[1].split("/")[1]
                except IndexError as e:
                    print(f"Invalid URL. Refunding. ({memo})")
                    self.refund(client, op["from"], op["amount"],
                                "Invalid URL")
                    continue

                # check if the poll exists
                try:
                    question = Question.objects.get(username=author,
                                                    permlink=permlink)
                except Question.DoesNotExist:
                    print(f"Invalid poll. Refunding. ({memo})")
                    self.refund(client, op["from"], op["amount"],
                                "Invalid poll.")
                    continue

                # if the poll is closed, don't mind promoting it.
                if question.expire_at < timezone.now():
                    print(f"Expired poll. Refunding. ({memo})")
                    self.refund(client, op["from"], op["amount"],
                                "Expired poll.")
                    continue

                promotion_transaction.author = author
                promotion_transaction.permlink = permlink
                promotion_transaction.save()

                # update the related poll's promotion amount
                if not question.promotion_amount:
                    question.promotion_amount = float(amount.amount)
                else:
                    question.promotion_amount += float(amount.amount)
                question.save()

                print(f"{author}/{permlink} promoted with "
                      f"{promotion_amount} STEEM.")
Пример #13
0
class TestAccountHelper(unittest.TestCase):
    def setUp(self):
        self.client = Client(nodes=TestClient.NODES)

    def test_vp_with_hf20(self):
        last_vote_time = datetime.datetime.utcnow() - datetime.timedelta(
            hours=24)

        utc = pytz.timezone('UTC')
        last_vote_time = utc.localize(last_vote_time)

        result = {
            'voting_manabar': {
                'current_mana': 7900,
                'last_update_time': int(last_vote_time.timestamp())
            }
        }

        with requests_mock.mock() as m:
            m.post(TestClient.NODES[0], json={"result": [result]})
            account = self.client.account('emrebeyler')

        self.assertEqual(99, account.vp())

    def test_vp(self):
        last_vote_time = datetime.datetime.utcnow() - datetime.timedelta(
            hours=24)

        result = {
            "last_vote_time": last_vote_time.strftime("%Y-%m-%dT%H:%M:%S"),
            "voting_power": 7900,
        }
        with requests_mock.mock() as m:
            m.post(TestClient.NODES[0], json={"result": [result]})
            account = self.client.account('emrebeyler')

        self.assertEqual(99.0, account.vp())

    def test_rc(self):
        def match_get_accounts(request):
            method = json.loads(request.text)["method"]
            return method == "condenser_api.get_accounts"

        def match_find_rc_accounts(request):
            method = json.loads(request.text)["method"]
            return method == "rc_api.find_rc_accounts"

        last_update_time = datetime.datetime.utcnow() - datetime.timedelta(
            hours=24)
        last_update_timestamp = last_update_time.replace(
            tzinfo=datetime.timezone.utc).timestamp()

        result = {
            "rc_accounts": [{
                'account': 'emrebeyler',
                'rc_manabar': {
                    'current_mana': '750',
                    'last_update_time': last_update_timestamp
                },
                'max_rc_creation_adjustment': {
                    'amount': '1029141630',
                    'precision': 6,
                    'nai': '@@000000037'
                },
                'max_rc': '1000'
            }]
        }

        with requests_mock.mock() as m:
            m.post(TestClient.NODES[0],
                   json={"result": [{
                       "foo": "bar"
                   }]},
                   additional_matcher=match_get_accounts)
            m.post(TestClient.NODES[0],
                   json={"result": result},
                   additional_matcher=match_find_rc_accounts)

            self.assertEqual(float(95), self.client.account('emrebeyler').rc())

            self.assertEqual(
                float(75),
                self.client.account('emrebeyler').rc(
                    consider_regeneration=False))

    def test_reputation(self):
        reputation_sample = '74765490672156'  # 68.86
        with requests_mock.mock() as m:
            m.post(TestClient.NODES[0],
                   json={"result": [{
                       "reputation": reputation_sample
                   }]})
            account = self.client.account('emrebeyler')

        self.assertEqual(68.86, account.reputation())

    def test_account_history_simple(self):
        def match_max_index_request(request):
            params = json.loads(request.text)["params"]
            return params[1] == -1

        def match_non_max_index_request(request):
            params = json.loads(request.text)["params"]
            return params[1] != -1

        with requests_mock.mock() as m:
            m.post(TestClient.NODES[0],
                   json={"result": mock_history_max_index},
                   additional_matcher=match_max_index_request)
            m.post(TestClient.NODES[0],
                   json={"result": mock_history},
                   additional_matcher=match_non_max_index_request)

            account = Account(self.client)
            history = list(account.history(account="hellosteem"))
            self.assertEqual(3, len(history))

            # check filter
            history = list(
                account.history(account="hellosteem", filter=["transfer"]))

            self.assertEqual(2, len(history))

            # check exclude
            history = list(
                account.history(account="hellosteem", exclude=["transfer"]))

            self.assertEqual(1, len(history))

            # check only_operation_data

            history = list(
                account.history(account="hellosteem",
                                only_operation_data=False))

            self.assertEqual(3, history[0][0])
Пример #14
0
class TransferListener:
    def __init__(self,
                 account,
                 posting_key=None,
                 active_key=None,
                 nodes=None,
                 db=None,
                 probability_dimensions=None,
                 min_age_to_vote=None,
                 max_age_to_vote=None,
                 minimum_vp_to_vote=None,
                 vote_price=None):
        self.db = db
        self.nodes = nodes or ["https://api.steemit.com"]
        self.account = account
        self.client = Client(loglevel=logging.INFO)
        self.vote_client = Client(keys=[
            posting_key,
        ], nodes=nodes)
        self.refund_client = Client(keys=[
            active_key,
        ], nodes=nodes)
        self.min_age_to_vote = min_age_to_vote or 300
        self.max_age_to_vote = max_age_to_vote or 43200
        self.minimum_vp_to_vote = minimum_vp_to_vote or 80
        self.vote_percent = VotePercent(
            probability_dimensions=probability_dimensions)
        self.vote_price = Amount(vote_price) or Amount("1.000 STEEM")

    def get_incoming_transfers(self):
        stop_at = datetime.now() - timedelta(hours=3)
        acc = self.client.account(self.account)
        transfers = []
        for _, transaction in acc.history(
                filter=["transfer"],
                only_operation_data=False,
                stop_at=stop_at,
        ):
            op = transaction["op"][1]
            if op["to"] != self.account:
                continue
            transfers.append(transaction)

        return transfers

    def poll_transfers(self):
        while True:
            try:
                for transfer in self.get_incoming_transfers():
                    self.process_transfer(transfer)
            except Exception as error:
                print(error)
            time.sleep(3)

    def in_global_blacklist(self, author):
        url = "http://blacklist.usesteem.com/user/" + author
        response = requests.get(url).json()
        return bool(len(response["blacklisted"]))

    def get_content(self, author, permlink, retries=None):
        if not retries:
            retries = 0

        try:
            content = self.client.get_content(author, permlink)
        except Exception as error:
            if retries > 5:
                raise
            return self.get_content(author, permlink, retries=retries + 1)

        return content

    def get_active_votes(self, author, permlink, retries=None):
        if not retries:
            retries = 0

        try:
            active_votes = self.client.get_active_votes(author, permlink)
        except Exception as error:
            if retries > 5:
                raise
            return self.get_active_votes(author, permlink, retries=retries + 1)

        return [v["voter"] for v in active_votes]

    def refund(self, to, amount, memo, incoming_trx_id):

        if self.db.database.transfers.count({
                "incoming_trx_id":
                incoming_trx_id,
                "status":
                TransferStatus.REFUNDED.value
        }):
            logger.info("Already refunded. (TRX id: %s)", incoming_trx_id)
            return

        try:
            op = Operation('transfer', {
                "from": self.account,
                "to": to,
                "amount": amount,
                "memo": memo,
            })
            self.refund_client.broadcast(op)
            status = TransferStatus.REFUNDED.value
        except Exception as e:
            print(e)
            status = TransferStatus.REFUND_FAILED.value

        self.db.database.transfers.update_one(
            {"incoming_tx_id": incoming_trx_id},
            {"$set": {
                "status": status
            }},
        )

    def process_transfer(self, transaction_data):

        op = transaction_data["op"][1]
        amount = Amount(op["amount"])

        # check if transaction is already registered in the database
        if self.db.is_transfer_already_registered(transaction_data["trx_id"]):
            logger.info(
                "Transaction is already registered. Skipping. (TRX: %s)",
                transaction_data["trx_id"])
            return

        # register the transaction into db
        self.db.register_incoming_transaction(
            op["from"],
            op["memo"],
            transaction_data["block"],
            transaction_data["trx_id"],
        )

        # check if the asset is valid
        if amount.symbol != "STEEM":
            logger.info(
                "Invalid asset. Refunding %s with %s. (TRX: %s)",
                op["from"],
                amount,
                transaction_data["trx_id"],
            )
            self.refund(
                op["from"],
                op["amount"],
                RefundReason.INVALID_ASSET.value,
                transaction_data["trx_id"],
            )
            return

        # check if the VP is suitable
        # Check the VP
        acc = self.client.account(self.account)
        if acc.vp() < self.minimum_vp_to_vote:
            print(acc.vp(), self.minimum_vp_to_vote)
            logger.info("Rando is sleeping. Refunding. (TRX: %s)",
                        transaction_data["trx_id"])
            self.refund(
                op["from"],
                op["amount"],
                RefundReason.SLEEP_MODE.value,
                transaction_data["trx_id"],
            )
            return

        # check vote price
        if amount.amount != self.vote_price.amount:
            logger.info("Invalid amount. Refunding. (TRX: %s)",
                        transaction_data["trx_id"])
            self.refund(
                op["from"],
                op["amount"],
                "Invalid amount. You need to send %s" % self.vote_price,
                transaction_data["trx_id"],
            )
            return

        # check if the sender is in a blacklist
        if self.in_global_blacklist(op["from"]):
            logger.info("Sender is in blacklist. Refunding. (TRX: %s)",
                        transaction_data["trx_id"])

            self.refund(
                op["from"],
                op["amount"],
                RefundReason.SENDER_IN_BLACKLIST.value,
                transaction_data["trx_id"],
            )
            return

        # check if the memo is valid
        memo = op["memo"]
        try:
            author = memo.split("@")[1].split("/")[0]
            permlink = memo.split("@")[1].split("/")[1]
        except IndexError as e:
            logger.info("Invalid URL. Refunding. Memo: %s", memo)
            self.refund(
                op["from"],
                op["amount"],
                RefundReason.INVALID_URL.value,
                transaction_data["trx_id"],
            )
            return

        # check if the author is in a blacklist
        if self.in_global_blacklist(author):
            logger.info("Author is in blacklist. Refunding. (TRX: %s)",
                        transaction_data["trx_id"])

            self.refund(
                op["from"],
                op["amount"],
                RefundReason.AUTHOR_IN_BLACKLIST.value,
                transaction_data["trx_id"],
            )
            return

        # check is the Comment is a valid Comment
        comment_content = self.get_content(author, permlink)
        if comment_content["id"] == 0:
            logger.info("Invalid post. Refunding. (TRX id: %s)",
                        transaction_data["trx_id"])
            self.refund(
                op["from"],
                op["amount"],
                RefundReason.INVALID_URL.value,
                transaction_data["trx_id"],
            )
            return

        # check comment is valid
        if comment_content.get("parent_author"):
            logger.info("Not a main post. Refunding. (TRX id: %s)",
                        transaction_data["trx_id"])
            self.refund(
                op["from"],
                op["amount"],
                RefundReason.INVALID_URL.value,
                transaction_data["trx_id"],
            )
            return

        # check if we've already voted for that post
        active_voters = self.get_active_votes(author, permlink)
        if self.account in active_voters:
            logger.info("Already voted. Refunding. (TRX id: %s)",
                        transaction_data["trx_id"])
            self.refund(
                op["from"],
                op["amount"],
                RefundReason.POST_IS_ALREADY_VOTED.value,
                transaction_data["trx_id"],
            )
            return

        # check if the Comment age is suitable
        created = parse(comment_content["created"])
        comment_age = (datetime.now() - created).total_seconds()
        if not (self.min_age_to_vote < comment_age < self.max_age_to_vote):
            self.refund(
                op["from"],
                op["amount"],
                f"Post age must be between {self.min_age_to_vote} and"
                f" {self.max_age_to_vote} seconds",
                transaction_data["trx_id"],
            )
            logger.info("Post age is invalid. Refunding. (TRX id: %s)",
                        transaction_data["trx_id"])
            return

        # vote
        self.vote(author, permlink, amount, transaction_data)

    def vote(self, author, permlink, amount, transaction_data):
        random_vote_weight = self.vote_percent.pick_percent()
        logger.info("Voting for %s/%s. Random vote weight: %%%s", author,
                    permlink, random_vote_weight)
        vote_op = Operation(
            'vote', {
                "voter": self.account,
                "author": author,
                "permlink": permlink,
                "weight": random_vote_weight * 100,
            })

        burn_op = Operation(
            'transfer', {
                "from": self.account,
                "to": "null",
                "amount": str(amount),
                "memo": f"Burning STEEM for {author}/{permlink}",
            })

        # vote
        try:
            self.vote_client.broadcast(vote_op)
            time.sleep(3)
            status = TransferStatus.VOTED.value
        except Exception as e:
            print(e)
            status = TransferStatus.VOTE_FAILED.value

        # burn
        try:
            self.refund_client.broadcast(burn_op)
            time.sleep(3)
        except Exception as e:
            print(e)
            status = TransferStatus.BURN_FAILED.value

        self.db.database.transfers.update_one(
            {"incoming_tx_id": transaction_data["trx_id"]},
            {"$set": {
                "status": status
            }},
        )
Пример #15
0
def steem_per_mvests():
    c = Client(nodes=["https://api.hivekings.com"])
    info = c.get_dynamic_global_properties()
    return (float(Amount(info["total_vesting_fund_steem"]).amount) /
            (float(Amount(info["total_vesting_shares"]).amount) / 1e6))
Пример #16
0
 def __init__(self, username, steem_key):
     self.username = username
     self.client = Client(keys=[steem_key])
Пример #17
0
 def setUp(self):
     self.client = Client(nodes=TestClient.NODES)
Пример #18
0
def steem_per_mvests():
    c = Client()
    info = c.get_dynamic_global_properties()
    return (float(Amount(info["total_vesting_fund_steem"]).amount) /
            (float(Amount(info["total_vesting_shares"]).amount) / 1e6))
Пример #19
0
class DcomClient(commands.Bot):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.config = kwargs.get("dcom_config")
        self.lightsteem_client = LightsteemClient(
            nodes=self.config["steem_nodes"],
            keys=[self.config["bot_posting_key"]])
        self.community_name = self.config.get("community_name")
        self.registration_channel = self.config.get("registration_channel")
        self.registration_account = self.config.get("registration_account")
        self.role_name_for_registered_users = self.config.get(
            "role_name_for_registered_users")
        self.curation_channels = self.config.get("curation_channels")
        self.mongo_client = MongoClient(self.config.get("M0NGO_URI"))
        self.mongo_database = self.mongo_client["dcom"]
        self.patron_role = self.config.get("patron_role")
        self.bot_log_channel = self.config.get("bot_log_channel")
        self.account_for_vp_check = self.config.get("account_for_vp_check")
        self.limit_on_maximum_vp = self.config.get("limit_on_maximum_vp")
        self.bot_account = self.config.get("bot_account")
        self.auto_curation_vote_weight = 20

    @asyncio.coroutine
    def on_ready(self):
        print(self.user.name)
        print(self.user.id)
        if len(self.servers) > 1:
            sys.exit('This bot may run in only one server.')
        print(f'Running on {self.running_on.name}')

    @asyncio.coroutine
    async def on_member_update(self, before, after):
        # This callback works every time a member is updated on Discord.
        # We use this to sync members having "patron" as a role.

        before_roles = [r.name for r in before.roles]
        after_roles = [r.name for r in after.roles]
        channel = discord.Object(self.bot_log_channel)

        if self.patron_role in before_roles and \
                self.patron_role not in after_roles:
            # looks like the user lost access to patron role
            await self.send_message(
                channel, f":broken_heart: {after.mention} lost patron rights.")
            self.mongo_database["patrons"].delete_many(
                {"discord_id": str(after)})
        elif self.patron_role in after_roles and \
                self.patron_role not in before_roles:
            # we have a new patron
            await self.send_message(
                channel,
                f":green_heart: {after.mention} gained patron rights.")
            self.mongo_database["patrons"].insert({"discord_id": str(after)})

    def say_error(self, error):
        return self.say(f"**Error:** {error}")

    def say_success(self, message):
        return self.say(f":thumbsup: {message}")

    def upvote(self, post_content, weight, author=None, permlink=None):
        vote_op = Operation(
            'vote', {
                'voter': self.config.get("bot_account"),
                'author': author or post_content.get("author"),
                'permlink': permlink or post_content.get("permlink"),
                'weight': weight * 100
            })
        self.lightsteem_client.broadcast(vote_op)

    def refund(self, to, amount):
        transfer_op = Operation(
            'transfer', {
                'from': self.registration_account,
                'to': to,
                'memo': 'Successful registration. '
                f'Welcome to {self.community_name}.',
                'amount': amount,
            })
        # swap posting key with active key
        # workaround for a lightsteem quirk
        old_keys = self.lightsteem_client.keys
        try:
            self.lightsteem_client.keys = [
                self.config.get("registration_account_active_key")
            ]
            self.lightsteem_client.broadcast(transfer_op)
        finally:
            self.lightsteem_client.keys = old_keys

    def steem_username_is_valid(self, username):
        try:
            resp = self.lightsteem_client('condenser_api').get_accounts(
                [username])

            return bool(len(resp))
        except Exception as e:
            # retry logic on node failures
            return self.steem_username_is_valid(username)

    def get_verification_code(self, steem_username, discord_author):
        old_verification_code = self.mongo_database["verification_codes"]. \
            find_one({
            "verified": False,
            "steem_username": steem_username,
            "discord_id": str(discord_author),
        })
        if old_verification_code:
            verification_code = old_verification_code["code"]
            self.mongo_database["verification_codes"].update_one(
                {"code": old_verification_code["code"]},
                {'$set': {
                    "last_update": datetime.datetime.utcnow()
                }})
        else:
            verification_code = str(uuid.uuid4())
            self.mongo_database["verification_codes"].insert({
                "steem_username":
                steem_username,
                "discord_id":
                str(discord_author),
                "discord_backend_id":
                discord_author.id,
                "code":
                verification_code,
                "verified":
                False,
                "last_update":
                datetime.datetime.utcnow(),
            })

        return verification_code

    def get_a_random_patron_post(self):

        # Get a list of verified discord members having the role "patron:
        patron_users = list(self.mongo_database["patrons"].find())
        patron_users_ids = [u["discord_id"] for u in patron_users]
        verified_patrons = list(self.mongo_database["verification_codes"] \
                                .find({
            "verified": True,
            "discord_id": {"$in": patron_users_ids}}
        ).distinct("steem_username"))

        # Remove the patrons already voted in the last 24h.
        curated_authors = self.get_curated_authors_in_last_24_hours()
        verified_patrons = set(verified_patrons) - curated_authors

        print("Patrons", verified_patrons)
        # Prepare a list of patron posts
        posts = []
        for patron in verified_patrons:
            posts.append(self.get_last_votable_post(patron))

        if len(posts):
            # We have found some posts, shuffle it and
            # return the first element.
            random.shuffle(posts)
            return posts[0]

    def get_curated_authors_in_last_24_hours(self):
        """
        Returns a set of authors curated
        by the self.bot_account.
        """
        account = self.lightsteem_client.account(self.bot_account)
        one_day_ago = datetime.datetime.utcnow() - \
                      datetime.timedelta(days=1)
        voted_authors = set()
        for op in account.history(filter=["vote"], stop_at=one_day_ago):
            if op["voter"] != self.bot_account:
                continue

            voted_authors.add(op["author"])

        return voted_authors

    def get_last_votable_post(self, patron):
        """
        Returns a list of [author, permlink] lists.
        Output of this function is designed to be used in automatic curation.
        """
        posts = self.lightsteem_client.get_discussions_by_blog({
            "limit": 7,
            "tag": patron
        })
        for post in posts:

            # exclude reblogs
            if post["author"] != patron:
                continue

            # check if it's votable
            created = parse(post["created"])
            diff_in_seconds = (datetime.datetime.utcnow() - created). \
                total_seconds()

            # check if the post's age is lower than 6.5 days
            if diff_in_seconds > 561600:
                break

            # check if we already voted on that.
            voters = [v["voter"] for v in post["active_votes"]]
            if self.account_for_vp_check in voters or \
                    self.bot_account in voters:
                continue

            return post["author"], post["permlink"]

    @property
    def running_on(self):
        return list(self.servers)[0]

    async def verify(self, memo, amount, _from):
        # check the memo is a valid verification code, first.
        verification_code = self.mongo_database["verification_codes"]. \
            find_one({
            "code": memo,
            "verified": False,
            "steem_username": _from,
        })

        if not verification_code:
            return

        # add the "registered" role to the user
        server = self.running_on
        member = server.get_member(verification_code["discord_backend_id"])
        role = discord.utils.get(server.roles,
                                 name=self.role_name_for_registered_users)

        await self.add_roles(member, role)

        # send an informative message to the channel about the verification
        # status
        channel = discord.Object(self.registration_channel)
        await self.send_message(
            channel,
            f":wave: Success! **{verification_code['steem_username']}**"
            f" has been successfully registered with "
            f" <@{verification_code['discord_backend_id']}>.")

        # mark the code as verified
        self.mongo_database["verification_codes"].update_one(
            {"code": memo}, {'$set': {
                "verified": True
            }})

        # refund the user
        self.refund(verification_code["steem_username"], amount)

    async def check_transfers(self):
        processed_memos = set()
        await self.wait_until_ready()
        while not self.is_closed:
            print("[task start] check_transfers()")
            # If there are no waiting verifications
            # There is no need to poll the account history
            one_hour_ago = datetime.datetime.utcnow() - \
                           datetime.timedelta(minutes=60)
            waiting_verifications = self.mongo_database["verification_codes"]. \
                count({"last_update": {
                "$gte": one_hour_ago}, "verified": False})

            if waiting_verifications > 0:
                print(f"Waiting {waiting_verifications} verifications. "
                      f"Checking transfers")
                try:
                    # Poll the account history and check for the
                    # STEEM transfers
                    account = self.lightsteem_client.account(
                        self.registration_account)
                    for op in account.history(stop_at=one_hour_ago,
                                              filter=["transfer"]):
                        if op.get("memo") in processed_memos:
                            continue
                        if op.get("from") == self.registration_account:
                            continue

                        await self.verify(op.get("memo"), op.get("amount"),
                                          op.get("from"))
                        processed_memos.add(op.get("memo"))

                except Exception as e:
                    print(e)

            print("[task finish] check_transfers()")
            await asyncio.sleep(10)

    async def auto_curation(self):
        channel = discord.Object(self.bot_log_channel)
        await self.wait_until_ready()
        while not self.is_closed:
            try:
                print("[task start] auto_curation()")
                # vp must be eligible for automatic curation
                acc = self.lightsteem_client.account(self.account_for_vp_check)
                if acc.vp() >= int(self.limit_on_maximum_vp):

                    # get the list of registered patrons
                    post = self.get_a_random_patron_post()
                    if post:
                        author, permlink = post
                        self.upvote(None,
                                    self.auto_curation_vote_weight,
                                    author=author,
                                    permlink=permlink)
                        await self.send_message(
                            channel,
                            f"**[auto-curation round]**",
                            embed=get_vote_details(
                                author, permlink,
                                self.auto_curation_vote_weight,
                                self.bot_account))
                    else:
                        await self.send_message(
                            channel,
                            f"**[auto-curation round]** Couldn't find any "
                            f"suitable post. Skipping.")
                else:
                    await self.send_message(
                        channel, f"**[auto-curation round]** Vp is not enough."
                        f" ({acc.vp()}) Skipping.")
                print("[task finish] auto_curation()")
            except Exception as e:
                print(e)
            await asyncio.sleep(900)
Пример #20
0
def sync_vote(request):
    trx_id = request.GET.get("trx_id")

    try:
        # block numbers must be integer
        block_num = int(request.GET.get("block_num"))
    except (TypeError, ValueError):
        return HttpResponse('Invalid block ID', status=400)

    c = LightsteemClient(nodes=["https://api.hive.blog"])
    block_data = c.get_block(block_num)
    if not block_data:
        # block data may return null if it's invalid
        return HttpResponse('Invalid block ID', status=400)

    vote_tx = None
    for transaction in block_data.get("transactions", []):
        if transaction.get("transaction_id") == trx_id:
            vote_tx = transaction
            break

    if not vote_tx:
        return HttpResponse('Invalid transaction ID', status=400)

    vote_op = None
    for op_type, op_value in transaction.get("operations", []):
        if op_type != "comment":
            continue
        vote_op = op_value

    if not vote_op:
        return HttpResponse("Couldn't find valid vote operation.", status=400)

    # validate json metadata
    if not vote_op.get("json_metadata"):
        return HttpResponse("json_metadata is missing.", status=400)

    json_metadata = json.loads(vote_op.get("json_metadata", ""))

    # json_metadata should indicate content type
    if json_metadata.get("content_type") != "poll_vote":
        return HttpResponse("content_type field is missing.", status=400)

    # check votes
    votes = json_metadata.get("votes", [])
    if not len(votes):
        return HttpResponse("votes field is missing.", status=400)

    # check the poll exists
    try:
        question = Question.objects.get(
            username=vote_op.get("parent_author"),
            permlink=vote_op.get("parent_permlink"),
        )
    except Question.DoesNotExist:
        return HttpResponse("parent_author/parent_permlink is not a poll.",
                            status=400)

    # Validate the choice
    choices = Choice.objects.filter(question=question, )
    selected_choices = []
    for choice in choices:
        for user_vote in votes:
            if choice.text == user_vote:
                selected_choices.append(choice)

    if not selected_choices:
        return HttpResponse("Invalid choices in votes field.", status=400)

    # check if the user exists in our database
    # if it doesn't, create it.
    try:
        user = User.objects.get(username=vote_op.get("author"))
    except User.DoesNotExist:
        user = User.objects.create_user(username=vote_op.get("author"))
        user.save()

    # check if we already registered a vote from that user
    if Choice.objects.filter(voted_users__username=vote_op.get("author"),
                             question=question).count() != 0:
        return HttpResponse("You have already voted on that poll.", status=400)

    # register the vote
    for selected_choice in selected_choices:
        selected_choice.voted_users.add(user)

    # add vote audit entry
    vote_audit = VoteAudit(question=question,
                           voter=user,
                           block_id=block_num,
                           trx_id=trx_id)
    vote_audit.save()

    return HttpResponse("Vote is registered to the database.", status=200)
Пример #21
0
class TestClient(unittest.TestCase):
    NODES = ["https://api.steemit.com"]

    def setUp(self):
        self.client = Client(nodes=TestClient.NODES)

    def test_dynamic_api_selection(self):
        self.client('tags_api')
        self.assertEqual('tags_api', self.client.api_type)

    def test_default_api_selection(self):
        with requests_mock.mock() as m:
            m.post(TestClient.NODES[0], json={"result": {}})
            self.client.get_block(12323)
            self.assertEqual('condenser_api', self.client.api_type)

    def test_get_rpc_request_body_condenser_multiple_args(self):
        self.client('condenser_api')
        rpc_body = self.client.get_rpc_request_body(
            ('get_account_bandwidth', 'steemit', 'forum'), {
                'batch': True,
                'id': 1
            })

        self.assertEqual(
            "condenser_api.get_account_bandwidth",
            rpc_body["method"],
        )

        self.assertEqual(
            ('steemit', 'forum'),
            rpc_body["params"],
        )

    def test_get_rpc_request_body_condenser_single_arg(self):
        self.client('condenser_api')
        rpc_body = self.client.get_rpc_request_body(
            ('get_block', '123'),
            {},
        )

        self.assertEqual(
            ('123', ),
            rpc_body["params"],
        )

    def test_get_rpc_request_body_non_condenser_api_with_arg(self):
        self.client('database_api')
        rpc_body = self.client.get_rpc_request_body(
            ('list_vesting_delegations', {
                "start": [None],
                "limit": 20,
                "order": "by_delegation"
            }),
            {},
        )

        self.assertEqual(
            {
                'start': [None],
                'limit': 20,
                'order': 'by_delegation'
            }, rpc_body["params"])

    def test_get_rpc_request_body_non_condenser_api_no_arg(self):
        self.client('database_api')
        rpc_body = self.client.get_rpc_request_body(
            ('get_active_witnesses', ),
            {},
        )

        self.assertEqual({}, rpc_body["params"])

    def test_get_rpc_request_body_condenser_api_no_arg(self):
        rpc_body = self.client.get_rpc_request_body(
            ('get_active_witnesses', ),
            {},
        )

        self.assertEqual([], rpc_body["params"])

    def test_batch_rpc_calls(self):
        self.client.get_block(1, batch=True)
        self.client.get_block_header(2, batch=True)

        self.assertEqual(2, len(self.client.queue))
        self.assertEqual("condenser_api.get_block",
                         self.client.queue[0]["method"])
        self.assertEqual("condenser_api.get_block_header",
                         self.client.queue[1]["method"])

    def test_validate_response_rpc_error(self):
        resp = {
            'jsonrpc': '2.0',
            'error': {
                'code': -32000,
                'message': "Parse Error:Couldn't parse uint64_t",
                'data': ""
            },
            'id': 'f0acccf6-ebf6-4952-97da-89b248dfb0d0'
        }

        with self.assertRaises(lightsteem.exceptions.RPCNodeException):
            self.client.validate_response(resp)

    def test_validate_repsonse_batch_call(self):
        resp = [{
            'previous': '017d08b4416e4ea77d5f582ddf4fc06bcf888eef',
            'timestamp': '2018-08-11T10:25:00',
            'witness': 'thecryptodrive',
            'transaction_merkle_root': '23676c4bdc0074489392892bcf'
            '1a5b779f280c8e',
            'extensions': []
        }, {
            'previous': '017d08b55aa2520bc3a777eaec77e872bb6b8943',
            'timestamp': '2018-08-11T10:25:03',
            'witness': 'drakos',
            'transaction_merkle_root': 'a4be1913157a1be7e4ab'
            'c36a22ffde1c110e683c',
            'extensions': []
        }]
        validated_resp = self.client.validate_response(resp)

        self.assertEqual(True, isinstance(validated_resp, list))
        self.assertEqual(2, len(validated_resp))

    def test_validate_repsonse_batch_call_one_error_one_fail(self):
        resp = [{
            'previous': '017d08b4416e4ea77d5f582ddf4fc06bcf888eef',
            'timestamp': '2018-08-11T10:25:00',
            'witness': 'thecryptodrive',
            'transaction_merkle_root': '23676c4bdc0074489392892bcf'
            '1a5b779f280c8e',
            'extensions': []
        }, {
            'jsonrpc': '2.0',
            'error': {
                'code': -32000,
                'message': "Parse Error:Couldn't parse uint64_t",
                'data': ""
            },
            'id': 'f0acccf6-ebf6-4952-97da-89b248dfb0d0'
        }]

        with self.assertRaises(lightsteem.exceptions.RPCNodeException):
            self.client.validate_response(resp)

    def test_process_batch(self):
        with requests_mock.mock() as m:
            m.post(TestClient.NODES[0], json={"result": {}})
            self.client.get_block(12323, batch=True)
            self.client.get_block(1234, batch=True)

            self.assertEqual(2, len(self.client.queue))
            self.client.process_batch()
            self.assertEqual(0, len(self.client.queue))