bitshares研究系列【水龙头(faucet)代码分析】
从bitshares研究系列【水龙头(faucet)完整安装】这个文章中已经知道faucet用的Ruby On Rails框架,功能其实比较简单,只用来注册帐号,而且注册帐号连接的还是cli_wallet,基本上就是通过HTTP接收web请求,然后命令转到cli_wallet,只是做了一个信息的中转,这也是“水龙头”的本义了!
没怎么接触过Ruby的,简单入下门:
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 的帖子,期待您能留言交流!
你好!请接受cn区点赞机器人 @cnbuddy 对你作为cn区一员的感谢。倘若你想让我隐形,请回复“取消”。