• 交易
    • 前言
    • 源码
    • 类图
    • 解读
      • 交易的本质
      • 交易生命周期
      • 亿书交易类型
      • 交易基本流程
      • 转账交易分析
  • 总结
  • 链接
  • 参考

    交易

    前言

    我们在第一部分《了解加密货币》里说过,加密货币是“利益”转移的程序化,其核心目标是保证数字财富或价值安全、透明、快速的转移。因此,交易是加密货币系统中最重要的部分,是加密货币的核心功能,加密解密、P2P网络、区块链等一系列技术都是围绕交易展开的。

    这一篇,我们就来研究亿书提供的交易类型及代码实现,集中总结交易的生命周期及实现过程,把我们在《地址》和《签名和多重签名》里故意漏掉的判断逻辑补充完整。

    源码

    transaction-types.js https://github.com/Ebookcoin/ebookcoin/blob/v0.1.3/helpers/transaction-types.js

    transaction.js https://github.com/Ebookcoin/ebookcoin/blob/v0.1.3/logic/transaction.js

    transactions.js https://github.com/Ebookcoin/ebookcoin/blob/v0.1.3/modules/transactions.js

    类图

    transactions-clase.png

    解读

    交易的本质

    从经济学角度来说,交易就是一种价值交换。在《精通比特币》(见参考)一书里,作者是这样定义比特币交易的:简单地说,交易是指把比特币从一个地址转到另一个地址。更准确地说,一笔“交易”就是一个经过签名运算的,表达价值转移的数据结构。每一笔“交易”都经过比特币网络传输,由矿工节点收集并封包至区块中,永久保存在区块链某处。

    交易,在汉语词典里,既可以是名词,代表交易内容的数据信息(技术上叫做数据结构),又可以是动词,代表一个操作过程。把这些重要信息汇总到一起,既让用户容易理解,又要体现加密货币特点,可以这样定义一个交易操作:

    加密货币交易是指人们通过加密货币网络,把加密货币进行有效转移,
    并把交易数据保存到区块链的过程。

    这个定义与我们的直观感受比较接近。通常,大家喜欢把加密货币交易,比做纸质支票,支票本身就是记录一笔交易的数据结构,从签署支票到兑付完成的过程就是一个交易操作行为。一笔加密货币交易就是一个有着货币转移目的的电子支票,只有在交易被执行时才会在金融体系中体现,而且交易发起人并不一定是签署该笔交易的人。

    交易可以被任何人在线上或线下创建,即便创建这笔交易的人不是这个账户的授权签字人。这一点非常好理解,假如有一张空的纸质支票,我们可以自己填写,也可以找人填写,最后只要有支付权限的领导签名,支票就能生效,就可以兑付。加密货币也是如此,无论谁创建的加密货币交易,只要被资金所有者(们)数字签名,交易就能实现。

    交易只是一些经过加密处理的字节码,不含任何机密信息、私钥或密码,可被包括wifi、无线电在内的任何网络公开传播,甚至可以被处理成二维码、表情符号、短信等形式发送。只要这笔交易能进入加密货币网络,那么发送者并不需要信任用来传播该笔交易的任何一个网络节点。同时,这些节点也不需要信任发送者,不用记录发送者的任何身份信息。相反,电子商务网站的交易,不仅包含敏感信息,而且依赖加密网络连接完成信息传输。

    因此,从本质上讲,加密货币交易是价值所有权的变更,价值转移仅仅是这种行为的结果。加密货币总量就是那些,从始至终都不会变化,人为丢失的是人类流通使用的私钥权限,总量仍在网络上不会丢失。记录加密货币总量的区块链就那一条,这个链条可以越来越长,越来越大,但是增加的仅仅是交易信息,即价值所有权变更信息。用个不慎确切的比喻,加密货币就像一列永不停息的火车,上下的是人次,固定的是座位,您只有在自己的人生旅途中才拥有某个座位的所有权(使用权)。

    从设计原理上说,加密货币淡化了交易者帐号,简化为输入输出,所谓的账户也只是存在于客户端钱包这类具体的应用层的软件里,就像那列火车总要有火车站吧,而某一段旅程的火车票是有具体所属的,是要与现实人的帐号或身份对应的,所以火车站是要记录用户信息,要有检票、验票的过程。

    亿书的原理也是如此,只不过亿书通过进一步扩展交易类型,强化了用户帐号的存在,使得更加适合处理各类资产所有权,从而为数字版权保护奠定良好架构基础。

    交易生命周期

    加密货币的整个系统,都是为了确保正确地生成交易、快速地传播和验证交易,并最终写入全球交易总账簿——区块链而设计。因此,从开发设计角度考虑,一笔交易必须包括下列过程:

    1. 生成一笔交易。这里是指一条包含交易双方加密货币地址、数量、时间戳和有效签名等信息,而且不含任何私密信息的合法交易数据;
    2. 广播到网络。几乎每个节点都会获得这笔交易数据。
    3. 验证交易合法性。生成交易的节点和其他节点都要验证,没有得到验证的交易,是不能进入加密货币网络的。
    4. 写入区块链。

    下面,我们来详细阅读分析亿书的交易是如何实现的。

    亿书交易类型

    目前,亿书已经完成或正在开发的交易类型,包括14种(后续会有更多),分别是:

    1. // helpers/transaction-types.js
    2. module.exports = {
    3. SEND : 0,
    4. SIGNATURE : 1,
    5. DELEGATE : 2,
    6. VOTE : 3,
    7. USERNAME : 4,
    8. FOLLOW : 5,
    9. MULTI: 6,
    10. DAPP: 7,
    11. IN_TRANSFER: 8,
    12. OUT_TRANSFER: 9,
    13. ARTICALE : 10,
    14. EBOOK: 11
    15. BUY: 12
    16. READ: 13
    17. }

    其中,

    SEND是最基本的转账交易,SIGNATURE是上一篇提到的“签名”交易,DELEGATE是注册为受托人,VOTE是投票,USERNAME是注册用户别名地址,FOLLOW是添加联系人,MULTI是注册多重签名帐号,DAPP是侧链应用,IN_TRANSFER是转入Dapp资金,OUT_TRANSFER转出Dapp资金,这些是现有版本已经完成的功能。

    ARTICALE是发布文章,EBOOK是发布电子书,BUY是购买(电子书或其他商品),READ是付费阅读(电子书等),这些功能会逐步添加。

    这些交易,除了SEND转账交易外,其他的交易类型,我们暂且称它们为功能性交易(在比特币的圈子里,有人称为伪交易)。

    交易基本流程

    亿书交易类型尽管多样,但是交易的基本逻辑是一样的。整个加密货币都是交易逻辑的有效组成部分,要比传统电子商务网站复杂的多,但与交易直接相关的代码,却又非常简单清晰。从开发角度说,实现一笔交易,亿书需要这样几个步骤:

    (1)生成交易数据

    交易是人类行为,涉及到甲乙双方(货币发送者和接收者,我们用甲乙方来代替,下文同)和交易数额,这在很多交易,特别是版权交易方面更加重要。甲方是主动发起交易的有效用户,是亿书币的支付方,是交易的支付来源。乙方比较灵活,可以是另一个有合法地址的用户,也可以是亿书系统本身(功能性交易),是亿书币的接收方。

    简单的一句话就是:谁与谁交易了多少钱。用下面转账交易部分的代码举例,请看modules/transactions.js文件里的763和800行,一笔交易必须包含如下字段:

    • 交易类型。代码里表示为 type: TransactionTypes.SEND;
    • 支付帐号。代码里指的是 sender: account;
    • 接受帐号。代码里指的是 recipientId: recipientId, 如果用的是别名地址,就是 recipientUsername: recipientUsername,如果是功能性交易,这里就不需要了;
    • 交易数量。代码里指的是 amount: body.amount。

    这些数据有的要求用户输入,比如用户密钥,交易数量等,这些数据是否正确,也是非常关键的事情。这是软件程序验证逻辑的一个重要部分,不可或缺。这个很好理解,如果一个人胡乱填写密钥和接受地址,也能把币发送出去,那就笑话了。但具体校验过程较为繁琐,这里主要涉及到:发起交易的用户是否存在、密钥是否正确、是否多重签名帐号、是否有支付密码,以及接受方用户地址是否合法等,都要逐个检验。

    详情看这里的流程图:

    addTransaction-activity.png

    (2)给合法交易签名

    基本信息正确之后,一笔合法交易,还要使用甲乙方的公钥签名,确保交易所属。同时,还要准确记录它的交易时间戳,方便追溯。还要生成交易ID,每个交易ID都包含了丰富的加密信息,需要复杂的生成过程,绝不像传统的网站系统,让数据库自动生成索引就可以充当ID了。

    详情看这里的流程图:

    signTransaction-activity.png

    (3)验证交易合法性

    通常,一笔交易经过6-10个区块之后,这笔交易被认为是无法更改的,即已确认,因为这时候拒绝、变更的难度已经非常大,理论上已经不可能。这里的交易合法性,除了基本信息正确之外,主要是指保证交易是未确认的交易,也不是用户重复提交的交易,即双花交易。双花交易是加密货币特有的现象,通俗的说,就是用户在交易确认之前(有一段时间,比特币时间更长),又一次提交了相同交易信息,导致一笔钱花两次,这种情况是必须要避免的。

    每笔交易在广播到网络之前必须验证合法性,不合法的交易没有机会广播到网络。节点收到新的交易信息时,要重新验证。如此一来,任何对网络的攻击,都只会影响一个节点,安全性大大提高。

    验证合法的交易就可以直接加入区块链了,因此从上面的第一步到现在,亿书都是在一个节点上完成的。这也为下面的广播处理打下基础,一旦交易被广播到网络,在其他节点,这里的验证和处理过程就会重复执行一次。

    验证的过程,看这里的流程图:

    verifyTransaction-activity.png

    (4)广播到点对点网络

    没有中心服务器,必须借助点对点网络,把交易数据写入分布式公共账本——区块链,保证交易数据永远无法篡改,而且可以轻松查询追溯。这在中心化的服务器上,为了应对个别交易摩擦,保证交易记录可追溯,要采取更多的技术手段,记录更多的数据字段,意味着要保持大量数据冗余,付出更多资金成本。

    因为交易数据不含私密信息,对网络没有苛刻要求,因此加密货币的网络可以覆盖很广,对网络的编程也变得灵活很多。理论上,只要能保证联通的便捷和快速,具体设计中不需要考虑更多复杂的因素。当然,就亿书这款产品而言,独有的用户协作和分享功能,对网络编程的性能有自身的要求,就另当别论,这方面将在下一个版本中体现出来。

    这里,仅仅是加密货币基础网络功能,交易广播到网络的流程如下:

    broadcastTransaction-activity.png

    转账交易分析

    前面几篇,我们接触到几种交易类型,比如:注册别名地址和多重签名地址,不过并没有研究具体的交易过程,下面通过分析转账交易来学习整个交易、验证的过程。

    代码实现在modules/transactions.js文件里,主要Api如下:

    1. // 148行
    2. router.map(shared, {
    3. "get /": "getTransactions",
    4. "get /get": "getTransaction",
    5. "get /unconfirmed/get": "getUnconfirmedTransaction",
    6. "get /unconfirmed": "getUnconfirmedTransactions",
    7. "put /": "addTransactions"
    8. });
    9. // 160行
    10. library.network.app.use('/api/transactions', router);

    解析一下,就是:

    1. get /api/transactions/ -> shared.getTransactions
    2. get /api/transactions/get -> shared.getTransaction
    3. get /api/transactions/unconfirmed/get -> shared.getUnconfirmedTransaction
    4. get /api/transactions/unconfirmed -> shared.getUnconfirmedTransactions
    5. put /api/transactions/ -> shared.addTransactions

    我们仍然把读取数据的Api放一放,因为他们很简单,重点掌握写数据的操作,put /api/transactions/,对应方法shared.addTransactions,代码如下:

    1. // 652行
    2. shared.addTransactions = function (req, cb) {
    3. var body = req.body;
    4. library.scheme.validate(body, {
    5. type: "object",
    6. properties: {
    7. secret: {
    8. type: "string",
    9. minLength: 1,
    10. maxLength: 100
    11. },
    12. amount: {
    13. type: "integer",
    14. minimum: 1,
    15. maximum: constants.totalAmount
    16. },
    17. recipientId: {
    18. type: "string",
    19. minLength: 1
    20. },
    21. publicKey: {
    22. type: "string",
    23. format: "publicKey"
    24. },
    25. secondSecret: {
    26. type: "string",
    27. minLength: 1,
    28. maxLength: 100
    29. },
    30. multisigAccountPublicKey: {
    31. type: "string",
    32. format: "publicKey"
    33. }
    34. },
    35. //
    36. required: ["secret", "amount", "recipientId"]
    37. }, function (err) {
    38. // 验证数据格式
    39. if (err) {
    40. return cb(err[0].message);
    41. }
    42. // 验证密码信息
    43. var hash = crypto.createHash('sha256').update(body.secret, 'utf8').digest();
    44. var keypair = ed.MakeKeypair(hash);
    45. if (body.publicKey) {
    46. if (keypair.publicKey.toString('hex') != body.publicKey) {
    47. return cb("Invalid passphrase");
    48. }
    49. }
    50. var query = {};
    51. // 乙方(接收方)地址转换,保证可以用户名转账
    52. var isAddress = /^[0-9]+[L|l]$/g;
    53. if (isAddress.test(body.recipientId)) {
    54. query.address = body.recipientId;
    55. } else {
    56. query.username = body.recipientId;
    57. }
    58. library.balancesSequence.add(function (cb) {
    59. // 验证乙方用户合法性
    60. modules.accounts.getAccount(query, function (err, recipient) {
    61. if (err) {
    62. return cb(err.toString());
    63. }
    64. if (!recipient && query.username) {
    65. return cb("Recipient not found");
    66. }
    67. var recipientId = recipient ? recipient.address : body.recipientId;
    68. var recipientUsername = recipient ? recipient.username : null;
    69. // 验证甲方(发送方)用户合法性
    70. if (body.multisigAccountPublicKey && body.multisigAccountPublicKey != keypair.publicKey.toString('hex')) {
    71. // 验证多重签名
    72. modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) {
    73. if (err) {
    74. return cb(err.toString());
    75. }
    76. // 多重签名帐号不存在
    77. if (!account || !account.publicKey) {
    78. return cb("Multisignature account not found");
    79. }
    80. // 多重签名帐号未激活
    81. if (!account || !account.multisignatures) {
    82. return cb("Account does not have multisignatures enabled");
    83. }
    84. // 帐号不属于该多重签名组
    85. if (account.multisignatures.indexOf(keypair.publicKey.toString('hex')) < 0) {
    86. return cb("Account does not belong to multisignature group");
    87. }
    88. // 接着验证甲方(发送方)用户合法性
    89. modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) {
    90. if (err) {
    91. return cb(err.toString());
    92. }
    93. // 甲方帐号不存在
    94. if (!requester || !requester.publicKey) {
    95. return cb("Invalid requester");
    96. }
    97. // 甲方支付密码(二次签名)不正确
    98. if (requester.secondSignature && !body.secondSecret) {
    99. return cb("Invalid second passphrase");
    100. }
    101. // 甲方帐号公钥与多重签名帐号公钥是不一样的(因为两个账户是不一样的)
    102. if (requester.publicKey == account.publicKey) {
    103. return cb("Invalid requester");
    104. }
    105. var secondKeypair = null;
    106. if (requester.secondSignature) {
    107. var secondHash = crypto.createHash('sha256').update(body.secondSecret, 'utf8').digest();
    108. secondKeypair = ed.MakeKeypair(secondHash);
    109. }
    110. try {
    111. // 763行 把上述数据整理成需要的交易数据结构,并给交易添加时间戳、签名、生成ID、计算交易费等
    112. var transaction = library.logic.transaction.create({
    113. type: TransactionTypes.SEND,
    114. amount: body.amount,
    115. sender: account,
    116. recipientId: recipientId,
    117. recipientUsername: recipientUsername,
    118. keypair: keypair,
    119. requester: keypair,
    120. secondKeypair: secondKeypair
    121. });
    122. } catch (e) {
    123. return cb(e.toString());
    124. }
    125. // 776行 处理交易
    126. modules.transactions.receiveTransactions([transaction], cb);
    127. });
    128. });
    129. } else {
    130. // 直接验证甲方(发送方)用户合法性,这里的请求者requester就是发出交易者sender
    131. ...
    132. });
    133. }

    上面这段代码涉及到的就是生成交易数据,这与之前的《地址》、《签名和多重签名》里提到的功能性交易差不多,这里把该方法代码完整粘贴出来,具体逻辑请看代码里的注释和前面的流程图。

    接下来,776行,通过receiveTransactions方法处理交易,该方法最终调用的是下面的方法。关键部分,已经添加了注释,请结合上面的流程图阅读,不再详述。

    1. // modules/transactions.js文件
    2. // 337行
    3. Transactions.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) {
    4. modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) {
    5. // 这是个闭包,在下面的程序运行结束的时候才调用,因此是验证完毕,才写入区块链、广播到网络
    6. function done(err) {
    7. if (err) {
    8. return cb(err);
    9. }
    10. // 这里 加入区块链 操作
    11. privated.addUnconfirmedTransaction(transaction, sender, function (err) {
    12. if (err) {
    13. return cb(err);
    14. }
    15. // 触发事件,广播到网络
    16. library.bus.message('unconfirmedTransaction', transaction, broadcast);
    17. cb();
    18. });
    19. }
    20. if (err) {
    21. return done(err);
    22. }
    23. if (transaction.requesterPublicKey && sender && sender.multisignatures && sender.multisignatures.length) {
    24. modules.accounts.getAccount({publicKey: transaction.requesterPublicKey}, function (err, requester) {
    25. if (err) {
    26. return done(err);
    27. }
    28. if (!requester) {
    29. return cb("Invalid requester");
    30. }
    31. // 开始执行一系列验证,包括交易是不是已经存在
    32. library.logic.transaction.process(transaction, sender, requester, function (err, transaction) {
    33. if (err) {
    34. return done(err);
    35. }
    36. // 检查是否交易已经存在(包括双花交易)
    37. if (privated.unconfirmedTransactionsIdIndex[transaction.id] !== undefined || privated.doubleSpendingTransactions[transaction.id]) {
    38. return cb("Transaction already exists");
    39. }
    40. // 这里是 直接验证交易签名等信息,接着调用闭包 done(),把交易写入区块链并广播到网络
    41. library.logic.transaction.verify(transaction, sender, done);
    42. });
    43. });
    44. } else {
    45. ...
    46. }

    总结

    这里的编码逻辑非常清晰,但作为非常核心的部分,使用了大量编程技巧,需要比较熟练的开发技能。代码中涉及到大量的回调和验证,有的回调嵌套很深,需要对异步较为深入的理解,掌握熟练的回调处理方法,不然理解和编码都会有很多困扰。因此,好好熟悉基本编码技巧,从小处着手打好基础很重要。

    本文涉及的流程图相对比较复杂,为了印刷方便,我把完整的流程图拆分成为四张,处理过程花费了大量时间,但是很多细节仍然无法照顾到,也无法保证没有错误和疏漏,请看到问题的小伙伴及时反馈给我。

    交易是怎么写入区块链的,上面仅仅点到为止,不够详细和深入。为了进一步阐述区块链的原理,需要专门拿出一篇来,详细讲述。而且,作为目前加密货币的“网红”,区块链也值得我们好好研究。请看下一篇:《神秘的区块链》

    链接

    本系列文章即时更新,若要掌握最新内容,请关注下面的链接

    本源文地址: https://github.com/imfly/bitcoin-on-nodejs

    电子书阅读: http://bitcoin-on-nodejs.ebookchain.org

    亿书白皮书: http://ebookchain.org/ebookchain.pdf

    亿书官网: http://ebookchain.org

    亿书官方QQ群:185046161(亿书完全开源开放,欢迎各界小伙伴参与)

    参考

    精通比特币(英文)

    精通比特币(中文)