SteemData Notify 代码学习二: Confirmation Worker / Code Study of SteemData Notify: Part two

4년 전

在上一篇文章中,我们学习了SteemData Notify后端代码中的Blockchain Worker

归纳起来就是

  • Blockchain Worker 将blockchain上和账户有关的动态抓取进来
  • 判断是否是SteemData Notify 注册(并确认)用户相关的操作以及是否是用户关心的操作
  • 如果是,写入数据库通知表notifications

今天我们来继续学习 Confirmation Worker, 看看基于尘埃支付认证到底是什么鬼?

源码在这里:
https://github.com/SteemData/notify.steemdata.com/blob/master/src/worker.py

Confirmation Worker

def run_confirmation_worker():
    log.info('Starting the confirmation worker.')
    b = Blockchain()
    for transfer in b.stream(filter_by='transfer'):
        confirm_user_settings(transfer)

关于Blockchain以及stream(self, filter_by: Union[str, list] = list(), *args, **kwargs)
昨天的帖子中已经介绍了
这段代码其实就是过滤区块链中的转账操作,并调用confirm_user_settings(transfer)

def confirm_user_settings(op):
    if op['to'] != steem_wallet:
        return
    if len(op['memo'].strip()) == 24:
        try:
            _id = ObjectId(op['memo'].strip())
        except Exception:
            return
    elif len(op['memo'].strip()) == 40:
        _id = op['memo'].strip()
    else:
        return
    settings = db.settings.find_one({'_id': _id})
    if settings:
        db.settings.update_one(
            {'_id': _id, 'username': op['from']},
            {'$set': {'confirmed': True}}
        )
        message = 'You have made the following changes:\n' + \
                  'Email: %s\n' % (str(settings['email'] or '-')) + \
                  'Telegram: %s\n' % (str(settings['telegram_channel_id'] or '-')) + \
                  'Notify account_update: %s\n' % str(settings['account_update']) + \
                  'Notify change_recovery_account: %s\n' % str(settings['change_recovery_account']) + \
                  'Notify request_account_recovery: %s\n' % str(settings['request_account_recovery']) + \
                  'Notify transfer: %s\n' % str(settings['transfer']) + \
                  'Notify transfer_from_savings: %s\n' % str(settings['transfer_from_savings']) + \
                  'Notify set_withdraw_vesting_route: %s\n' % str(settings['set_withdraw_vesting_route']) + \
                  'Notify withdraw_vesting: %s\n' % str(settings['withdraw_vesting']) + \
                  'Notify fill_order: %s\n' % str(settings['fill_order']) + \
                  'Notify fill_convert_request: %s\n' % str(settings['fill_convert_request']) + \
                  'Notify fill_transfer_from_savings: %s\n' % str(settings['fill_transfer_from_savings']) + \
                  'Notify fill_vesting_withdraw: %s\n' % str(settings['fill_vesting_withdraw'])
        log.info('Confirmed the settings for user %s.' % op['from'])
        if settings['email']:
            send_mail(settings['email'], 'Update confirmed', message)
        if settings['telegram_channel_id']:
            send_telegram(settings['telegram_channel_id'], message)

其中:

    if op['to'] != steem_wallet:
        return

如果不是转网指定钱包,则略过,本例中,steem_wallet 定义为 @null
顺便说一下,如果要改为收费服务,把这个钱包改成接收费用的账户,并且设置一下检查金额即可
不过大牛们都看不上这些钱啦,哈哈

    if len(op['memo'].strip()) == 24:
        try:
            _id = ObjectId(op['memo'].strip())
        except Exception:
            return
    elif len(op['memo'].strip()) == 40:
        _id = op['memo'].strip()
    else:
        return
    settings = db.settings.find_one({'_id': _id})

你可能好奇为啥又有24又有40呢?到底多长呢?
我也好奇,后来分析了一下,应该是历史版本遗留问题,这段一会我们再详细讲,只需知道按转账的memo去数据库中查找设置即可。Memo即_id。

    if settings:
        db.settings.update_one(
            {'_id': _id, 'username': op['from']},
            {'$set': {'confirmed': True}}
        )
        message = 'You have made the following changes:\n' + \
                  'Email: %s\n' % (str(settings['email'] or '-')) + \
                  'Telegram: %s\n' % (str(settings['telegram_channel_id'] or '-')) + \
                  'Notify account_update: %s\n' % str(settings['account_update']) + \
                  'Notify change_recovery_account: %s\n' % str(settings['change_recovery_account']) + \
                  'Notify request_account_recovery: %s\n' % str(settings['request_account_recovery']) + \
                  'Notify transfer: %s\n' % str(settings['transfer']) + \
                  'Notify transfer_from_savings: %s\n' % str(settings['transfer_from_savings']) + \
                  'Notify set_withdraw_vesting_route: %s\n' % str(settings['set_withdraw_vesting_route']) + \
                  'Notify withdraw_vesting: %s\n' % str(settings['withdraw_vesting']) + \
                  'Notify fill_order: %s\n' % str(settings['fill_order']) + \
                  'Notify fill_convert_request: %s\n' % str(settings['fill_convert_request']) + \
                  'Notify fill_transfer_from_savings: %s\n' % str(settings['fill_transfer_from_savings']) + \
                  'Notify fill_vesting_withdraw: %s\n' % str(settings['fill_vesting_withdraw'])
        log.info('Confirmed the settings for user %s.' % op['from'])
        if settings['email']:
            send_mail(settings['email'], 'Update confirmed', message)
        if settings['telegram_channel_id']:
            send_telegram(settings['telegram_channel_id'], message)

如果没有相关设置,当然不必说了,如果有则更新数据库中对应id以及对应用户的设置状态为确认(confirmed)。
后边的代码就很好理解啦,给用户发个通知,告诉他/她的最新设置情况。

BUG, BUG

好像昨天我们已经发现了一个BUG,今天,看了这段代码以后,发现了另外一处BUG。

    settings = db.settings.find_one({'_id': _id})
    if settings:
        db.settings.update_one(
            {'_id': _id, 'username': op['from']},
            {'$set': {'confirmed': True}}
        )

注意这段代码,我们知道用户的设置会生成一个唯一的_id
如果用户自己去确认(转账给null, 并附_id做memo), 这并没有什么问题。

但是我们想一种坏坏的情况: 你的设置,我去确认
呃,好吧,我可能不知晓你生成的_id
那么换一种玩法,我帮你设置,我帮你确认,会是什么情况?

        db.settings.update_one(
            {'_id': _id, 'username': op['from']},
            {'$set': {'confirmed': True}}
        )

好在,系统在更新数据库的时候检查了实际操作的用户 'username': op['from']
所以,我对你的账户进行设置并不会写入到库中,但是:
settings = db.settings.find_one({'_id': _id})
查询的时候,仅仅查询了ID
这样后边代码会继续执行,会给设置的telegram_channel_idemail发信息哦。
所以检查的时候,也应该加上'username': op['from']才更合理一些。

关于Memo长度


语言解释起来太过于苍白,直接上代码吧。
也就是说SHA1生成了就是40位的16进制字符串
至于24,咱就不去研究了,人家都改成新的了,咱在去研究老的,也没啥意思,是不?

关于ObjectId()

我们可以看到

    if len(op['memo'].strip()) == 24:
        try:
            _id = ObjectId(op['memo'].strip())
        except Exception:
            return
    elif len(op['memo'].strip()) == 40:
        _id = op['memo'].strip()
    else:
        return

之前代码中,把memo读取来的数据转换成了 ObjectId 类型
from bson.objectid import ObjectId
这是因为之前的生成的memo值并非通过setting hash而来,而是setting插入数据库后生成的记录的_id
这个类型是ObjectId,所以字符串值必须转换成ObjectId才可以读取。

比如 @a-0 这个用户,在数据库中存储的_id


我用字符串的形式直接查找,是找不到内容的。


而用ObjectId就可以直接查到

至于后来为何长度为40的时候不用转换了,因为存储的方式变了呗。

总结

  • Confirmation Worker 将blockchain上给 @null 转账的数据抓过来
  • 通过memo 判断是否是SteemData Notify的用户设置确认信息
  • 如果是,将数据库中用户设置的状态修改为True

并且我们发现了一处小BUG。
其实我还发现一个问题啊,有点坏坏的,不过我不能说,说了我就成坏人了,至少我还没坏透呢。

这就是所谓的基于尘埃支付的确认啦,我也学会咯。

咦,一总结好像也没啥内容呢,那我为啥写了这么多呢? 咦,为啥这句话这么面熟呢?
初学者水平有限,如有谬误敬请指正,深表谢意啦。


感谢阅读 / Thank you for reading.
欢迎upvote、resteem以及 following me @oflyhigh 😎

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
STEEMKR.COM IS SPONSORED BY
ADVERTISEMENT
Sort Order:  trending

The 24 character check is there for legacy reasons, and will be removed in the future. The 40 char size is the expected memo length, since 40 is the hexadecimal representation of sha1.

Bugs
Unfortunately, I don't understand Chinese, and Google Translate is not very helpful.

For all the bugs, please open the issues on Github.

·

Hi @furion, Thank you very much for providing the SteemData Notify service.

I had already explained the reasons for the different memo lengths in this article, Thank you again for the clarification.

As for Bug,

    settings = db.settings.find_one({'_id': _id})
    if settings:
        db.settings.update_one(
            {'_id': _id, 'username': op['from']},
            {'$set': {'confirmed': True}}
        )

I think it would be better to change above code to:

    settings = db.settings.find_one({'_id': _id, 'username': op['from']})
    if settings:
        db.settings.update_one(
            {'_id': _id, 'username': op['from']},
            {'$set': {'confirmed': True}}
        )

Otherwise, It may cause minor problems in some extreme cases .
I'll submit this issues on Github when I finished the study of your great code.

Good Article

Thank you for giving us this information

Oh ...
Interesting.
Thanks for sharing.

Thank you for giving us this information,
I really like with your information

感谢信息,好的工作

Hm, that's interesting stuff, thanks for writing this.

Oh ...
Interesting.
Thanks for sharing.

[status-waiting for gentlebot]

·

等到了没有?

ya translation would be sweet :)

Siempre he dicho que los programadores son los verdaderos genios , sin ellos la computadora solo seria un monton de fierros.

can you please explain it in English?
i am your follower for a long time.
nice posts

感谢您分享这个伟大的信息。我总是喜欢阅读你的文章 :)

我剛剛試了所有的翻譯功能,都找不到,你這篇文章,使用的是什麼語言啊?都木有辦法翻譯耶~~~ 太強了,可能是拉丁文....

·

克罗地亚语

什麼bug都被你找出來了XDDD

·

找BUG小能手。

Great Work!

Informative post ! thank you for updates !!


Upvoted !

Congratulations @oflyhigh! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

Award for the number of comments

Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

By upvoting this notification, you can help all Steemit users. Learn how here!

thanks your knowledge sharing

Great work!Upv and followed You > follow me :)
Well, have a nice day @deazydee

Congratulations @oflyhigh!
Your post was mentioned in my hit parade in the following category:

  • Pending payout - Ranked 10 with $ 445,21

thanks for this information i dont understand chinese but recommend you do another post in english

I interesting your post but unfortuently can't read Japanese language, Whatever Thanks you

Thanks for this info

Awesome stuff :)

好帖!不愧是奶普啊!

·

我擦,椰油咋冒出来了,吓我一跳

Thanx for this post ! im overwhelmingly amazed coz
i dont have clue what this is about :)

SteemON

已赞!写得太专业了!

这个太专业看不懂。