bitshares研究系列【水龙头(faucet)代码分析】

in #bitshares6 years ago (edited)

8D8DB7D0-4769-4B9D-9277-5B8058ED16BE.jpeg

bitshares研究系列【水龙头(faucet)完整安装】这个文章中已经知道faucet用的Ruby On Rails框架,功能其实比较简单,只用来注册帐号,而且注册帐号连接的还是cli_wallet,基本上就是通过HTTP接收web请求,然后命令转到cli_wallet,只是做了一个信息的中转,这也是“水龙头”的本义了!

没怎么接触过Ruby的,简单入下门:

Ruby教程

Ruby On Rails

faucet文件结构

标准的Ruby On Rails程序目录,不同功能的文件放在对应的文件夹下,各个目录功能基本从名字就能看出干什么用的。

  • app Faucet程序主目录,MVC结构
  • bin 用来部署或运行程序的脚本
  • config 配置文件
  • db 数据库相关操作,建表等
  • lib 存放程序扩展模块,封装了graphene的操作接口
  • log 程序的日志文件
  • public 对外显示页面
  • test 单元测试、功能测试及整合测试文件
  • tmp 临时文件,如缓存、PID、会话文件
  • config.ru 用来启动程序,基于Rack服务器的程序设置
  • Gemfile 程序所需的gem依赖组件,用于Bundler gem
  • Gemfile.lock 执行完bundle install后生成文件,记录当前使用gem信息
  • Rakefile 保存并加载可以命令行中执行的任务

各步流程

回顾下faucet消息流,从各步简单分析一下:

bitshares-ui钱包 ==HTTP==> faucet ==RPC==> cli_wallet ==RPC==> bitshares-core

db

rake db:create 用config\database.yml中的定义创建当前 RAILS_ENV 项目环境下的数据库

rake db:migrate 通过db/migrate迁移数据库

rake db:seed 运行db/seeds.rb文件

更多命令参考:http://www.thekunit.com/rails-rake-database-operations/

HTTP请求处理

HTTP路由处理在routes.rb中,指定了请求格式到controllers的关系,直接执行以下命令就容易看明白了:

bash-3.2$ rake routes
Starting graphene websocket communication event-loop 'ws://127.0.0.1:11012'
Established connection to 'ws://127.0.0.1:11012'
               Prefix Verb    URI Pattern                                   Controller#Action
                 root GET     /                                             wallet#index
        refscoreboard GET     /refscoreboard(.:format)                      welcome#refscoreboard
             widget_w GET     /widgets/:widget_id/w(.:format)               widgets#w
        widget_action GET     /widgets/:widget_id/action(.:format)          widgets#action
              widgets GET     /widgets(.:format)                            widgets#index
                      POST    /widgets(.:format)                            widgets#create
           new_widget GET     /widgets/new(.:format)                        widgets#new
          edit_widget GET     /widgets/:id/edit(.:format)                   widgets#edit
               widget GET     /widgets/:id(.:format)                        widgets#show
                      PATCH   /widgets/:id(.:format)                        widgets#update
                      PUT     /widgets/:id(.:format)                        widgets#update
                      DELETE  /widgets/:id(.:format)                        widgets#destroy
                  api OPTIONS /api/*path(.:format)                          api/base#option
api_v1_account_status GET     /api/v1/accounts/:account_id/status(.:format) api/v1/accounts#status
      api_v1_accounts GET     /api/v1/accounts(.:format)                    api/v1/accounts#index
                      POST    /api/v1/accounts(.:format)                    api/v1/accounts#create
   new_api_v1_account GET     /api/v1/accounts/new(.:format)                api/v1/accounts#new
  edit_api_v1_account GET     /api/v1/accounts/:id/edit(.:format)           api/v1/accounts#edit
       api_v1_account GET     /api/v1/accounts/:id(.:format)                api/v1/accounts#show
                      PATCH   /api/v1/accounts/:id(.:format)                api/v1/accounts#update
                      PUT     /api/v1/accounts/:id(.:format)                api/v1/accounts#update
                      DELETE  /api/v1/accounts/:id(.:format)                api/v1/accounts#destroy
                              /*path(.:format)                              welcome#error_404

从日志对照一下:

Started POST "/api/v1/accounts" for 127.0.0.1 at 2018-04-30 00:52:26 +0800
Processing by Api::V1::AccountsController#create as JSON
  Parameters: {"account"=>{"name"=>"barnard002", "owner_key"=>"BTS5KLmg4EQgsk1LnuRbbfQuVqEvSsRQVAJgHqWMtd1EbeD485fdH", "active_key"=>"BTS5g4sSQeb6s2s1Xx8cYWpoEvJQCyxJhNdXy4KHQQZy4FTm481NB", "memo_key"=>"BTS5g4sSQeb6s2s1Xx8cYWpoEvJQCyxJhNdXy4KHQQZy4FTm481NB", "refcode"=>nil, "referrer"=>nil}}

rpc调用cli_wallet

帐号注册代码在 app/services/account_registrator.rb

    def register(account_name, owner_key, active_key, memo_key, referrer)
        @logger.info("---- Registering account: '#{account_name}' #{owner_key}/#{active_key} referrer: #{referrer}")

        if get_account_info(account_name)
            @logger.warn("---- Account exists: '#{account_name}' #{get_account_info(account_name)}")
            return {error: {'message' => 'Account exists'}}
        end

        if !is_cheap_name(account_name)
            @logger.warn("---- Attempt to register premium name: '#{account_name}'")
            return {error: {'message' => 'Premium names registration is not supported by this faucet'}}
        end

        registrar_account = Rails.application.config.faucet.registrar_account
        referrer_account = registrar_account
        referrer_percent = 0
        unless referrer.blank?
            refaccount_info = get_account_info(referrer)
            if refaccount_info && (refaccount_info[:member_status] == 'lifetime' || refaccount_info[:member_status] == 'annual')
                referrer_account = referrer
                referrer_percent = Rails.application.config.faucet.referrer_percent
            else
                @logger.warn("---- Referrer '#{referrer}' is not a member")
            end
        end

        res = {}
        result, error = GrapheneCli.instance.exec('register_account', [account_name, owner_key, active_key, registrar_account, referrer_account, referrer_percent, true])
        if error
            @logger.error("!!! register_account error: #{error.inspect}")
            res[:error] = error
        else
            @logger.debug(result.inspect)
            res[:result] = result
            #GrapheneCli.instance.exec('transfer', [registrar_account, account_name, '1000', 'QBITS', 'Welcome to OpenLedger. Read more about Qbits under asset', true])
        end
        return res
    end

rpc实现在 lib/graphene_client.rb

class GrapheneApi

    def initialize(ws_rpc, api_name)
        @ws_rpc, @api_name, @api_id = ws_rpc, api_name, 0
        if api_name
            @init_promise = @ws_rpc.call([0, api_name, []]).then { |res| @api_id = res.to_i }
        else
            @init_promise = nil
        end
    end

    def exec(method, params)
        if @init_promise
            @init_promise.then { @ws_rpc.call([@api_id, method, params]) }
        else
            @ws_rpc.call([@api_id, method, params])
        end
    end

更多代码不再列出。

python faucet

由于faucet是用Ruby写的,另外这个功能不多,但这边开发人员会Ruby的没有,想着把faucet改成python或者nodejs版本。看看网上有没有类似的版本,结果还真搜索到一个python faucet库,那就先用python库测试一下。

用python3.6,先安装flask相关的几个库,再安装pyyml,执行:

(env3) Chaim:python-faucet Chaim$ python manage.py install
Traceback (most recent call last):
  File "manage.py", line 6, in <module>
    from app import app, db
  File "/Users/Chaim/Documents/workspace/python-faucet/app/__init__.py", line 82, in <module>
    from . import views, models
  File "/Users/Chaim/Documents/workspace/python-faucet/app/views.py", line 1, in <module>
    from transnet.account import Account
ModuleNotFoundError: No module named 'transnet'

找不到transnet库,试图pip安装发现没有这个库,搜索作者建的仓库,找到一个transnet库,再下载这个仓库安装。

(env3) Chaim:python-utransnet Chaim$ python setup.py install
running install
running bdist_egg
running egg_info
creating python_utransnet.egg-info
...
creating 'dist/python_utransnet-0.1.10-py3.6.egg' and adding 'build/bdist.macosx-10.6-intel/egg' to it
removing 'build/bdist.macosx-10.6-intel/egg' (and everything under it)
Processing python_utransnet-0.1.10-py3.6.egg
Copying python_utransnet-0.1.10-py3.6.egg to /Users/Chaim/Documents/workspace/python/env3/lib/python3.6/site-packages
Adding python-utransnet 0.1.10 to easy-install.pth file

python程序是运行起来了,水龙头也配置到python faucet,但是真正注册帐户时还是出现问题:

2018-05-02 16:06:50,282 - flask.app - ERROR - Exception on /api/v1/accounts [POST]
Traceback (most recent call last):
  File "/Users/Chaim/Documents/workspace/python/env3/lib/python3.6/site-packages/python_utransnet-0.1.10-py3.6.egg/transnetapi/transnetnoderpc.py", line 43, in rpcexec
    return super(TransnetNodeRPC, self).rpcexec(payload)
  File "/Users/Chaim/Documents/workspace/python/env3/lib/python3.6/site-packages/grapheneapi/graphenewsrpc.py", line 173, in rpcexec
    raise RPCError(ret['error']['message'])
grapheneapi.exceptions.RPCError: Assert Exception: _local_apis.size() > api_id: 

结论

faucet还是比较简单的,主要是对语言和运行环境不熟悉会浪费一些时间,从处理流程也知道,需要的话完全可以用nodejs、python写这样一个“水龙头”服务。


感谢您阅读 @chaimyu 的帖子,期待您能留言交流!

Sort:  

你好!请接受cn区点赞机器人 @cnbuddy 对你作为cn区一员的感谢。倘若你想让我隐形,请回复“取消”。

Coin Marketplace

STEEM 0.20
TRX 0.13
JST 0.030
BTC 63026.81
ETH 3462.43
USDT 1.00
SBD 2.51