bitsharesjs库详解二:交易广播

上文 解析了ChainStore,本文继续,说一说如何利用用户的私钥来做交易广播。 如何搭建环境本文不再复述,请参考上文。

例子

运行

交易广播没有测试,只有一个例子文件,做的是转账,利用的是测试链。代码文件在这里 examples/transfer.js。运行方法

npm run example:transfer

不过一行不改,运行结果是这样的

> bitsharesjs@1.2.4 example:transfer /home/zzb/bitsharesjs
> babel-node examples/transfer

(node:21851) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: unexpected server response (200)

这个错误的根源在于,测试链的host改了,需要把第7行改成

7
Apis.instance("wss://node.testnet.bitshares.eu/ws", true)

另外连接错误处理没有加(可帮助定位问题)。请读者自行处理。

仅仅修改第5行,重新运行例子,结果还是有问题的

> bitsharesjs@1.2.4 example:transfer /home/zzb/bitsharesjs
> babel-node examples/transfer

Connected to API node: wss://node.testnet.bitshares.eu/ws
connected to: Test network
synced and subscribed, chainstore ready
(node:22400) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): timeout

一个timeout,让人好疑惑,出什么问题了呢?看了代码知道大约知道转账逻辑是从账号bitsharesjs向faucet账号转账0.1TEST币,用uptick看看bitsharesjs账号看看

uptick --node wss://node.testnet.bitshares.eu/ws info bitsharesjs

发现,没有那个账号!(关于那个timeout错误,实际上应该是没有账号错误,这个错误报得不准,与ChainStore的设计有关,读者可以查看ChainStore解析一文。)因此需要改第4行的私钥和第13行的账号,改成哪个呢?自己通过 测试链UI 注册一个最靠谱。注册完账号并且修改了之后,运行结果应该类似这样

> bitsharesjs@1.2.4 example:transfer /home/zzb/bitsharesjs
> babel-node examples/transfer

Connected to API node: wss://node.testnet.bitshares.eu/ws
connected to: Test network
synced and subscribed, chainstore ready
memo pub key: TEST8Sz5PMkSZftXi7qPL5XNeTMG9SQrxRPcsC4DdFRAvZr5qhgf6M
serialized transaction: { ref_block_num: 0,
ref_block_prefix: 0,
expiration: '1970-01-01T00:00:00',
operations: [ [ 0, [Object] ] ],
extensions: [],
signatures: [] }

然后在测试链UI上就可以发现转走了0TEST,解锁后可看到备注里面就是程序里面的。至于0TEST,实际上是0.1TEST,而代码上的量是10000,为什么呢?所有的资产都会设定一个最小单位,由小数点位数决定。例如测试链上TEST的小数点位数为5,而主链上BTS的小数点位数为5,人民币的小数点位数为4。 设定这个位数之后,所有的链上交易都用整数表示资产数量,其中1表示10的-n次方,n为资产的小数点位数。以测试链核心资产TEST来说,交易广播使用1表示1e-5,10000就是0.1了。整数的好处是没有浮点数加减法带来的误差,但对人来说并不直观,因此给用户显示,需要做一个转换。

代码解析

通过运行完成转账之后,解析一下例子代码:

  1. 第7~8行,websocket API初始化

  2. 第11行,ChainStore 初始化

  3. 13~22行,账号、资产、备注初始化

  4. 24~30行,从链上获取相关的账号和资产

  5. 32~69行,构造转账交易,签名,广播。

特别说明:

  1. FetchChain函数来自ChainStore,提供了一个直接向区块链查询的异步(Promise)接口,resolve时,返回的是Immutable的Map类型。

  2. new TransactionBuilder() 用于构造交易对象

  3. tr.add_type_operation填写交易对象的内容,包括类型和根据类型需要填写的字段

  4. tr.set_required_fees() 异步向区块链获取转账费用,

  5. tr.add_signer(priv_key, pub_key) 签名交易

  6. tr.broadcast() 向区块链广播交易

交易对象的填写

通过上文的代码解析,我们发现即使我们通过拷贝例子代码能够发起转账交易,我们也不知道怎么去下一个限价单,其他的步骤都好理解(或者可以直接抄),唯独 add_type_operation这个函数让人摸不着头脑。这时候需要看源代码,通过阅读 add_type_operation的实现代码,可以知道在 lib/serializer/src/operations.js 里面查找所有的操作和参数类型,例如转账参数从401行开始

401
402
403
404
405
406
407
408
409
 export const transfer = new Serializer(
     "transfer",
     {fee: asset,
     from: protocol_id_type("account"),
     to: protocol_id_type("account"),
     amount: asset,
     memo: optional(memo_data),
     extensions: set(future_extensions)}
 );

把这个参数和上面的 add_type_operation函数对比,是不是很清晰呢?如果要做挂单,显然就得看411行开始的定义了

411
412
413
414
415
416
417
418
419
420
 export const limit_order_create = new Serializer(
     "limit_order_create",
     {fee: asset,
     seller: protocol_id_type("account"),
     amount_to_sell: asset,
     min_to_receive: asset,
     expiration: time_point_sec,
     fill_or_kill: bool,
     extensions: set(future_extensions)}
 );

需要注意的是:

  1. 区块链广播的买单和卖单都是卖单:用A资产买B资产,广播为卖出A资产,获得B资产。

  2. asset类型照例子处理

  3. time_point_sec 类型是时间戳,javascript里面构造一个Date对象即可。下层传输格式为 “%Y-%m-%dT%H:%M:%S”, 表示UTC时间,精确到秒。例如 2022-01-01T03:23:39 。

  4. fill_or_kill一般为false,表示等待限价单被对手吃。如果为true,表示不能成交的话立刻失效。

  5. extensions应该没啥用。(也许匿名交易需要,目前我不知道)

设计点评

关于交易部分,我觉得设计也不够人性化,比起python-bitshares来说易用性很差,需要程序员了解很多链上的细节。作为一个UI直接使用的中间库,明显抽象层次还是太低了。

思考

如何写一个程序,用你的私钥挂一个资产交易的限价单?欢迎留言。