- 交易
- 前言
- 源码
- 类图
- 解读
- 交易的本质
- 交易生命周期
- 亿书交易类型
- 交易基本流程
- 转账交易分析
- 总结
- 链接
- 参考
交易
前言
我们在第一部分《了解加密货币》里说过,加密货币是“利益”转移的程序化,其核心目标是保证数字财富或价值安全、透明、快速的转移。因此,交易是加密货币系统中最重要的部分,是加密货币的核心功能,加密解密、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
类图

解读
交易的本质
从经济学角度来说,交易就是一种价值交换。在《精通比特币》(见参考)一书里,作者是这样定义比特币交易的:简单地说,交易是指把比特币从一个地址转到另一个地址。更准确地说,一笔“交易”就是一个经过签名运算的,表达价值转移的数据结构。每一笔“交易”都经过比特币网络传输,由矿工节点收集并封包至区块中,永久保存在区块链某处。
交易,在汉语词典里,既可以是名词,代表交易内容的数据信息(技术上叫做数据结构),又可以是动词,代表一个操作过程。把这些重要信息汇总到一起,既让用户容易理解,又要体现加密货币特点,可以这样定义一个交易操作:
加密货币交易是指人们通过加密货币网络,把加密货币进行有效转移,
并把交易数据保存到区块链的过程。
这个定义与我们的直观感受比较接近。通常,大家喜欢把加密货币交易,比做纸质支票,支票本身就是记录一笔交易的数据结构,从签署支票到兑付完成的过程就是一个交易操作行为。一笔加密货币交易就是一个有着货币转移目的的电子支票,只有在交易被执行时才会在金融体系中体现,而且交易发起人并不一定是签署该笔交易的人。
交易可以被任何人在线上或线下创建,即便创建这笔交易的人不是这个账户的授权签字人。这一点非常好理解,假如有一张空的纸质支票,我们可以自己填写,也可以找人填写,最后只要有支付权限的领导签名,支票就能生效,就可以兑付。加密货币也是如此,无论谁创建的加密货币交易,只要被资金所有者(们)数字签名,交易就能实现。
交易只是一些经过加密处理的字节码,不含任何机密信息、私钥或密码,可被包括wifi、无线电在内的任何网络公开传播,甚至可以被处理成二维码、表情符号、短信等形式发送。只要这笔交易能进入加密货币网络,那么发送者并不需要信任用来传播该笔交易的任何一个网络节点。同时,这些节点也不需要信任发送者,不用记录发送者的任何身份信息。相反,电子商务网站的交易,不仅包含敏感信息,而且依赖加密网络连接完成信息传输。
因此,从本质上讲,加密货币交易是价值所有权的变更,价值转移仅仅是这种行为的结果。加密货币总量就是那些,从始至终都不会变化,人为丢失的是人类流通使用的私钥权限,总量仍在网络上不会丢失。记录加密货币总量的区块链就那一条,这个链条可以越来越长,越来越大,但是增加的仅仅是交易信息,即价值所有权变更信息。用个不慎确切的比喻,加密货币就像一列永不停息的火车,上下的是人次,固定的是座位,您只有在自己的人生旅途中才拥有某个座位的所有权(使用权)。
从设计原理上说,加密货币淡化了交易者帐号,简化为输入输出,所谓的账户也只是存在于客户端钱包这类具体的应用层的软件里,就像那列火车总要有火车站吧,而某一段旅程的火车票是有具体所属的,是要与现实人的帐号或身份对应的,所以火车站是要记录用户信息,要有检票、验票的过程。
亿书的原理也是如此,只不过亿书通过进一步扩展交易类型,强化了用户帐号的存在,使得更加适合处理各类资产所有权,从而为数字版权保护奠定良好架构基础。
交易生命周期
加密货币的整个系统,都是为了确保正确地生成交易、快速地传播和验证交易,并最终写入全球交易总账簿——区块链而设计。因此,从开发设计角度考虑,一笔交易必须包括下列过程:
- 生成一笔交易。这里是指一条包含交易双方加密货币地址、数量、时间戳和有效签名等信息,而且不含任何私密信息的合法交易数据;
- 广播到网络。几乎每个节点都会获得这笔交易数据。
- 验证交易合法性。生成交易的节点和其他节点都要验证,没有得到验证的交易,是不能进入加密货币网络的。
- 写入区块链。
下面,我们来详细阅读分析亿书的交易是如何实现的。
亿书交易类型
目前,亿书已经完成或正在开发的交易类型,包括14种(后续会有更多),分别是:
// helpers/transaction-types.jsmodule.exports = {SEND : 0,SIGNATURE : 1,DELEGATE : 2,VOTE : 3,USERNAME : 4,FOLLOW : 5,MULTI: 6,DAPP: 7,IN_TRANSFER: 8,OUT_TRANSFER: 9,ARTICALE : 10,EBOOK: 11,BUY: 12,READ: 13}
其中,
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。
这些数据有的要求用户输入,比如用户密钥,交易数量等,这些数据是否正确,也是非常关键的事情。这是软件程序验证逻辑的一个重要部分,不可或缺。这个很好理解,如果一个人胡乱填写密钥和接受地址,也能把币发送出去,那就笑话了。但具体校验过程较为繁琐,这里主要涉及到:发起交易的用户是否存在、密钥是否正确、是否多重签名帐号、是否有支付密码,以及接受方用户地址是否合法等,都要逐个检验。
详情看这里的流程图:

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

(3)验证交易合法性
通常,一笔交易经过6-10个区块之后,这笔交易被认为是无法更改的,即已确认,因为这时候拒绝、变更的难度已经非常大,理论上已经不可能。这里的交易合法性,除了基本信息正确之外,主要是指保证交易是未确认的交易,也不是用户重复提交的交易,即双花交易。双花交易是加密货币特有的现象,通俗的说,就是用户在交易确认之前(有一段时间,比特币时间更长),又一次提交了相同交易信息,导致一笔钱花两次,这种情况是必须要避免的。
每笔交易在广播到网络之前必须验证合法性,不合法的交易没有机会广播到网络。节点收到新的交易信息时,要重新验证。如此一来,任何对网络的攻击,都只会影响一个节点,安全性大大提高。
验证合法的交易就可以直接加入区块链了,因此从上面的第一步到现在,亿书都是在一个节点上完成的。这也为下面的广播处理打下基础,一旦交易被广播到网络,在其他节点,这里的验证和处理过程就会重复执行一次。
验证的过程,看这里的流程图:

(4)广播到点对点网络
没有中心服务器,必须借助点对点网络,把交易数据写入分布式公共账本——区块链,保证交易数据永远无法篡改,而且可以轻松查询追溯。这在中心化的服务器上,为了应对个别交易摩擦,保证交易记录可追溯,要采取更多的技术手段,记录更多的数据字段,意味着要保持大量数据冗余,付出更多资金成本。
因为交易数据不含私密信息,对网络没有苛刻要求,因此加密货币的网络可以覆盖很广,对网络的编程也变得灵活很多。理论上,只要能保证联通的便捷和快速,具体设计中不需要考虑更多复杂的因素。当然,就亿书这款产品而言,独有的用户协作和分享功能,对网络编程的性能有自身的要求,就另当别论,这方面将在下一个版本中体现出来。
这里,仅仅是加密货币基础网络功能,交易广播到网络的流程如下:

转账交易分析
前面几篇,我们接触到几种交易类型,比如:注册别名地址和多重签名地址,不过并没有研究具体的交易过程,下面通过分析转账交易来学习整个交易、验证的过程。
代码实现在modules/transactions.js文件里,主要Api如下:
// 148行router.map(shared, {"get /": "getTransactions","get /get": "getTransaction","get /unconfirmed/get": "getUnconfirmedTransaction","get /unconfirmed": "getUnconfirmedTransactions","put /": "addTransactions"});// 160行library.network.app.use('/api/transactions', router);
解析一下,就是:
get /api/transactions/ -> shared.getTransactionsget /api/transactions/get -> shared.getTransactionget /api/transactions/unconfirmed/get -> shared.getUnconfirmedTransactionget /api/transactions/unconfirmed -> shared.getUnconfirmedTransactionsput /api/transactions/ -> shared.addTransactions
我们仍然把读取数据的Api放一放,因为他们很简单,重点掌握写数据的操作,put /api/transactions/,对应方法shared.addTransactions,代码如下:
// 652行shared.addTransactions = function (req, cb) {var body = req.body;library.scheme.validate(body, {type: "object",properties: {secret: {type: "string",minLength: 1,maxLength: 100},amount: {type: "integer",minimum: 1,maximum: constants.totalAmount},recipientId: {type: "string",minLength: 1},publicKey: {type: "string",format: "publicKey"},secondSecret: {type: "string",minLength: 1,maxLength: 100},multisigAccountPublicKey: {type: "string",format: "publicKey"}},//required: ["secret", "amount", "recipientId"]}, function (err) {// 验证数据格式if (err) {return cb(err[0].message);}// 验证密码信息var hash = crypto.createHash('sha256').update(body.secret, 'utf8').digest();var keypair = ed.MakeKeypair(hash);if (body.publicKey) {if (keypair.publicKey.toString('hex') != body.publicKey) {return cb("Invalid passphrase");}}var query = {};// 乙方(接收方)地址转换,保证可以用户名转账var isAddress = /^[0-9]+[L|l]$/g;if (isAddress.test(body.recipientId)) {query.address = body.recipientId;} else {query.username = body.recipientId;}library.balancesSequence.add(function (cb) {// 验证乙方用户合法性modules.accounts.getAccount(query, function (err, recipient) {if (err) {return cb(err.toString());}if (!recipient && query.username) {return cb("Recipient not found");}var recipientId = recipient ? recipient.address : body.recipientId;var recipientUsername = recipient ? recipient.username : null;// 验证甲方(发送方)用户合法性if (body.multisigAccountPublicKey && body.multisigAccountPublicKey != keypair.publicKey.toString('hex')) {// 验证多重签名modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) {if (err) {return cb(err.toString());}// 多重签名帐号不存在if (!account || !account.publicKey) {return cb("Multisignature account not found");}// 多重签名帐号未激活if (!account || !account.multisignatures) {return cb("Account does not have multisignatures enabled");}// 帐号不属于该多重签名组if (account.multisignatures.indexOf(keypair.publicKey.toString('hex')) < 0) {return cb("Account does not belong to multisignature group");}// 接着验证甲方(发送方)用户合法性modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) {if (err) {return cb(err.toString());}// 甲方帐号不存在if (!requester || !requester.publicKey) {return cb("Invalid requester");}// 甲方支付密码(二次签名)不正确if (requester.secondSignature && !body.secondSecret) {return cb("Invalid second passphrase");}// 甲方帐号公钥与多重签名帐号公钥是不一样的(因为两个账户是不一样的)if (requester.publicKey == account.publicKey) {return cb("Invalid requester");}var secondKeypair = null;if (requester.secondSignature) {var secondHash = crypto.createHash('sha256').update(body.secondSecret, 'utf8').digest();secondKeypair = ed.MakeKeypair(secondHash);}try {// 763行 把上述数据整理成需要的交易数据结构,并给交易添加时间戳、签名、生成ID、计算交易费等var transaction = library.logic.transaction.create({type: TransactionTypes.SEND,amount: body.amount,sender: account,recipientId: recipientId,recipientUsername: recipientUsername,keypair: keypair,requester: keypair,secondKeypair: secondKeypair});} catch (e) {return cb(e.toString());}// 776行 处理交易modules.transactions.receiveTransactions([transaction], cb);});});} else {// 直接验证甲方(发送方)用户合法性,这里的请求者requester就是发出交易者sender...});}
上面这段代码涉及到的就是生成交易数据,这与之前的《地址》、《签名和多重签名》里提到的功能性交易差不多,这里把该方法代码完整粘贴出来,具体逻辑请看代码里的注释和前面的流程图。
接下来,776行,通过receiveTransactions方法处理交易,该方法最终调用的是下面的方法。关键部分,已经添加了注释,请结合上面的流程图阅读,不再详述。
// modules/transactions.js文件// 337行Transactions.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) {modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) {// 这是个闭包,在下面的程序运行结束的时候才调用,因此是验证完毕,才写入区块链、广播到网络function done(err) {if (err) {return cb(err);}// 这里 加入区块链 操作privated.addUnconfirmedTransaction(transaction, sender, function (err) {if (err) {return cb(err);}// 触发事件,广播到网络library.bus.message('unconfirmedTransaction', transaction, broadcast);cb();});}if (err) {return done(err);}if (transaction.requesterPublicKey && sender && sender.multisignatures && sender.multisignatures.length) {modules.accounts.getAccount({publicKey: transaction.requesterPublicKey}, function (err, requester) {if (err) {return done(err);}if (!requester) {return cb("Invalid requester");}// 开始执行一系列验证,包括交易是不是已经存在library.logic.transaction.process(transaction, sender, requester, function (err, transaction) {if (err) {return done(err);}// 检查是否交易已经存在(包括双花交易)if (privated.unconfirmedTransactionsIdIndex[transaction.id] !== undefined || privated.doubleSpendingTransactions[transaction.id]) {return cb("Transaction already exists");}// 这里是 直接验证交易签名等信息,接着调用闭包 done(),把交易写入区块链并广播到网络library.logic.transaction.verify(transaction, sender, done);});});} else {...}
总结
这里的编码逻辑非常清晰,但作为非常核心的部分,使用了大量编程技巧,需要比较熟练的开发技能。代码中涉及到大量的回调和验证,有的回调嵌套很深,需要对异步较为深入的理解,掌握熟练的回调处理方法,不然理解和编码都会有很多困扰。因此,好好熟悉基本编码技巧,从小处着手打好基础很重要。
本文涉及的流程图相对比较复杂,为了印刷方便,我把完整的流程图拆分成为四张,处理过程花费了大量时间,但是很多细节仍然无法照顾到,也无法保证没有错误和疏漏,请看到问题的小伙伴及时反馈给我。
交易是怎么写入区块链的,上面仅仅点到为止,不够详细和深入。为了进一步阐述区块链的原理,需要专门拿出一篇来,详细讲述。而且,作为目前加密货币的“网红”,区块链也值得我们好好研究。请看下一篇:《神秘的区块链》
链接
本系列文章即时更新,若要掌握最新内容,请关注下面的链接
本源文地址: https://github.com/imfly/bitcoin-on-nodejs
电子书阅读: http://bitcoin-on-nodejs.ebookchain.org
亿书白皮书: http://ebookchain.org/ebookchain.pdf
亿书官网: http://ebookchain.org
亿书官方QQ群:185046161(亿书完全开源开放,欢迎各界小伙伴参与)
参考
精通比特币(英文)
精通比特币(中文)
