
本篇是关于“如何使用OmniCore在服务端构建USDT钱包”系列的下篇,主要是对上一篇《使用OmniCore构建USDT钱包(一)》的补充和总结。上一篇主要对安装、配置和基本json-api使用,以及交易所钱包模型做了简略的说明,这一篇将对常见错误、安全机制以及其他扩展做一些技术分享。
1.omni_funded_send -212错误
从v0.3.1开始omnicore提供了新的转账api omni_funded_send 来支持USDT的转账操作。与之前的方式不同在于,omni_funded_send支持从第三方支付手续费。但在实际操作,明明拥有充足的btc用于支付手续费,也有充分的usdt用于转账,还是会频繁出现编号-212错误:
$ ./omnicore-cli omni_funded_send "mrAVAPxdQEZxFkunh56skB6sgJa6vrfrpo" "msJ2h47ZrxFJjksVvPy8ik4h2HFfa9W1zV" 31 "100.01" "mpaumxor659PhoJhXp1VCVHVwbFCZSRmuf"
error code: -212
error message:
Error choosing inputs for the send transaction
issues/760#详细讨论了这个问题。
USDT的转账本质上属于比特币的一种特殊交易(OP_RETUTRN),需要一部分BTC来支付矿工费用。同时,根据omnilayer协议,还需要微量的比特币用于标记omni事务的接受者,这部分btc无法从手续费地址扣除,只能从发送地址扣除。因此,当发送地址缺乏btc(也就是UTXO)时,会出现 “Error choosing inputs for the send transaction”的错误。经过测试,默认情况下用于标记omni事务的BTC为0.0000546。因此解决这个问题的唯一方法是向发送地址中转入>0.0000546的BTC。
但需要注意的是,发送地址上的某个UTXO将全部消耗掉,不会有找零。例如A地址向B地址转移USDT,A地址中拥有两个UTXO,分别为0.0001和0.0002,那么转账成功后,0.0001的UTXO将会被消耗掉,只剩下额度为0.0002的UTXO。
但在交易所模式下,这种错误几乎不会发生。因为在中心化钱包的模式下,充值的过程会自动携带了微量的btc,这一部分比特币足够用于标记omni事务,用于下一次转账,但也仅限一次。因此,只要不使用同一个地址发送两次USDT,这种错误几乎不会出现。
2. 钱包安全
数字资产不同于一般性的资产,一旦被盗或丢失,几乎无法找回,也无法进行数据回滚。对于中心化的钱包而言,用户本身对资产没有直接控制权,所有的资产安全性全部取决于中心化钱包的安全策略,因此非常有必要对安全性进行设计。以下方式仅供参考。
2.1 限制访问权限
omnicore基于bitcore,自带有加密访问权限设置,可以通过bitcoin.conf文件可以进行配置。
rpcuser=你的rpc用户名
rpcpassword=你的rpc密码
rpcallowip=127.0.0.1
rpcport=8332
其中rpcuser
和rpcpassword
分别配置访问用户和访问密码,rpcallowip
和rpcport
分别配置可访问的网络地址。测试环境下,该地址可以为0.0.0.0/0 为全地址访问。正式环境下,需要配置为制定的访问服务器地址或者本地地址。值得注意的是,这里的安全原理是限定钱包访问对象,一旦密码泄漏或服务器被黑,那么整个钱包相当于裸奔。
2.2 离线生成地址
资产的唯一凭证是私钥,因此私钥的保存非常重要。一般情况下,根据业务需求,服务端需要给每个用户分配一个地址。这里的建议是不应该分配地址的时候“即使”的去调用getnewaddresss
,而是通过离线的方式批量生产大量地址,然后连同地址、私钥保存到数据库,同时导出地址列表给服务端进行使用。这样服务端需要分配地址时,只需要从数据库读取即可,而不需要与钱包进行交互。
为了私钥的安全性,保存到数据库时可以进行二次加密。
2.3 钱包文件备份
钱包文件的备份可以直接使用bitcore的导出数据方式dumpwallet
,每天定期保存到文件,例如:
/***
* 导出钱包数据以人类可读的方式
* @param: []
* @return: java.lang.String
**/
public String dumpWallet() {
System.currentTimeMillis();
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String file = String.format("wallet-%s.txt", time);
http.engine("dumpwallet", file);
return file;
}
一旦需要恢复,使用importwallet
即可,例如:
/***
* 钱包数据导入
* @param: []
* @return: void
* https://bitcoin.org/en/developer-reference#importwallet
*对于影响新添加的密钥的事务,调用可能需要重新扫描整个链,可能需要几分钟。
**/
public void importWallet(String fielName) {
http.engine("importwallet", fielName);
}
2.4 冷钱包
冷钱包即不联网的钱包。对于交易所模式下而言,钱包的作业其实就相当于银行的存款系统,充值的过程相当于把金额全部汇总到中央地址。因此可以使用一台不联网的钱包生成中央地址若干,将私钥保存到U盘或其他加密硬件中,充值金额将汇集到冷钱包中保存。一旦需要提现,可以根据金额大小。小额提现可以通过热钱包直接发消息转账;大额可以使用需要使用指定U盘将私钥或钱包文件导入到钱包中,然后联网转账。操作完成后断网,重新保存硬件即可。
这一过程需要注意,冷钱包因为无法联网,自然也无法知道余额。那么可以设置观察钱包对中央钱包地址进行余额观察,以确保充值到账。在实际业务中,提现过程可能还需要人工进行审核。
2.5 钱包与服务端隔离
一般情况下,钱包与服务端会部署在同一台服务器上。这种方式有两个缺陷,第一是钱包需要的存储空间庞大,但需要的网络带宽很低,与业务相关的服务器部署子啊同一服务器上将造成资源的浪费。第二是这种方式有一些安全隐患。所以建议钱包单独部署到一台服务器上,如果业务允许,还可以同时备份到多台冗余服务器上。钱包与服务端的通讯可以使用 “消息队列”。
注意使用Queue,而不是Topics,同一时间内,只有一个消费者和一个生产者。

如果文章的内容对你有所帮助,希望你能点赞、投币、收藏三连。
如果你对区块链技术有兴趣,可以加入我们在杭州的交流群。
网友评论