设计区块链钱包系统的一些想法

in #blockchain8 years ago

各个『自己造一个链』的区块链项目都会提供一个『钱包程序』,例如比特币的 Bitcoin Core,以太坊的 Geth(go-ethereum) 。运行着这些程序的计算机(节点)构成、维护着一个个的区块链。标题中的『钱包系统』指的是和这些『钱包程序』打交道的系统。区块链资产交易所需要这套系统来给用户充值、提现,区块链浏览器(例如etheresacn) 需要这套系统获取、解析区块数据。因此这套系统是交易所、区块链浏览器连接区块链的通道。

这篇文章从连接钱包,通过钱包同步区块链数据,到细分的充值、提现功能谈一谈系统设计时需要考虑的问题。目前这篇文章还是一个 outline 的状态,并没有充分阐述其中每个条目的前因后果。

Ethereum 平台上的资产收集(归集)是一个相对独立的内容,下一篇文章会单独谈到这个话题。

连接钱包

容错

钱包会不可预知的出现不可访问的情况,并且这样的情况可能会出现较长时间(例一个小时),因此要有足够的重试容错机制保证不能获取数据时不让程序崩溃,以及在钱包恢复正常时,程序可以恢复同步区块。

Bitcoin Core 和 Geth 都采用 JSON RPC 接口,因此可以采用同一个 RPC 模块和这两个区块链通信,但是这两个钱包采用的 JSON RPC 版本不一样,需要考虑的差异主要体现在错误返回格式上。因此要兼容好两个钱包的错误返回。

RPC 层设计

建议每个链都采用多个钱包节点以保证稳定。考虑到和钱包通信有两种类型:

  • 读:例如根据 block hash 拿到 block 数据,
  • 写:例如向钱包发送交易

对于第一种接口可以采用遍历多个钱包节点,直到某个节点能正常返回数据(或者等待直到有一个节点可以返回数据);而第二种接口为了避免提交多次不采用遍历节点的方式,而是先选择好一个合适的节点(例如有足够的余额),再直接对那个节点发出请求。这样在设计 RPC 层时需要考虑到可以在链的层面通信(自动地遍历节点)以及在节点层面通信(指定要通信的节点)。

同步

处理 uncle/orphan block

比特币中会存在 uncle block,类似的,以太坊中会有 orphan block ,同步的系统要能处理好这两种 block:

  • 能检测到获取到的区块是否是 uncle/orphan block
  • 不能漏掉主链上附近高度区块中的交易

幂等

重复多次保存同一个 block,对于这个 block 中已经存在的数据不会多次保存。例如在充值的时候,假设某一个交易没有入账,可以将这个交易所在的 block 重新再扫描一遍,这就要要保障已经入账过的交易不会再次保存。

效率 & 数据完整

同步时要算上钱包节点向服务器返回数据的时间,因为数据量较大,这段时间不可以忽略。

当同步程序已经跟上钱包的块高后,设置合理的时间获取新的块。虽然 Bitcoin 通常出块时间是 10 分钟,但也可能 3 分钟出块,Ethereum 也是同样的。因此将 Bitcoin 的间隔检查新块的时间设置为1分钟,会比设置为10分钟更好,虽然检查的更频繁,但是对于偶尔 3 分钟就挖出的一个块,可以更快检查到。

需要在足够短时间内处理完一个 block 中所有的交易。参考的因素有:

  • 链的出块时间,
  • 每个块上的交易数量
  • 每个交易中 txin, txout 的数量。

在把区块数据写入数据库时既要考虑中到数据完整和写入速度效率。意味着不能在一个数据库事务中放入太多数据(例如一个包含 2000 笔交易的比特币区块中所有的交易、txin 和 txout 数据)否则数据库容易超时。因此数据完整要靠程序的重试保证,完整性可以做在 block 层或者时 transaction 层。同时,在程序崩溃时正在同步的块可能没有同步完,因此启动时要把上一次崩溃的块再重新同步过。这一点也是建立在不会重复写入的基础上的。

结构设计

大部分区块链的同步逻辑都是一样的,因此可以抽象成一套处理流程。这要求在设计数据库、同步流程和钱包接口设计时考虑到兼容多种区块链。

充值

充值系统可以集成到同步系统中,可以做成一个单独的系统。

充值时要考虑中到:

  • 在有一个确认数之后就可以告知用户
  • 只有在经过需要的确认数之后,才可以给用户入账
  • 注意链上使用的精度。例如 Ethereum 返回的数量是以 Wei 为单位,1 ETH = 10^18 Wei, 因此 1ETH 会表示会 10^18, 需要在程序中除以 10^18 才能转换为以 ETH 为单位。
  • 多次扫描同一个块,不会给块中的充值重复入账

提现

检查钱包余额

如果采用每个链多钱包节点的结构,可以任意选出一个余额足够的节点发起提现。

检查钱包余额时,直觉的做法是调用钱包接口,但是这样获取的余额还包括了正在等待确认的转账,会比实际的余额更多,因此要扣除正在等待确认的这部分交易的总金额。

避免重复发送提现

提现会包含两个操作:

  • 给钱包提交转账请求,等待返回 txid
  • 更新数据库中这笔提现的状态为『已发送』或『正在处理』

如果将这两个步骤放到一个数据库事务中,这样如果给钱包提交转账请求时报错,就不会更改数据库中提现的状态。这样做的问题是,钱包有可能已经成功地向区块链中发送交易了,但是因为钱包的问题或者网络超时等原因没有返回 txid,这时候这笔提前已经发送出去,但是数据库中的状态任然是『等待提现』,这样仍然会被拿出来发送到区块链中,导致同一笔提现发送了两次。

另一中方式是,在发送到区块链之前,先更改提现的状态,例如改为『处理中』,这样即便钱包出现了上述的问题,在『处理中』的提现也不会再被发送到区块链中。

同时,将提现发送到钱包的代码要做好异常捕捉,原因仍然是上面谈到的问题:钱包在返回错误的情况下,仍然有可能已经将提现成功发送到区块链了。这样在 rescue 的时候,将提现状态标记为 failed,避免了重复发送同一笔提现的错误。

Sort:  

Congratulations @liaocheng! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!

Coin Marketplace

STEEM 0.04
TRX 0.33
JST 0.081
BTC 62347.27
ETH 1613.67
USDT 1.00
SBD 0.47