题外话:这篇文章,耗费了我大量精力,用UML表达javascript类及流程本来就不是什么容易的事情,用来描述加密货币交易
这种验证逻辑非常多的代码更难,加之Nodejs的回调在这些代码里嵌套很深,所以如何把异步调用变成人类容易理解的顺序调用,也做了一番取舍,时间不知不觉就过了一星期。
所幸,赶在比特币减半的今天完成并发布这篇文章,也算在区块链火热的今天,《Nodejs开发加密货币》走到了一个关键节点:触及了加密货币的灵魂和腹地。动辄几千一枚的比特币等加密货币可能会消亡,但是背后的技术却蓬勃发展,玩技术的要善于把握先机,抢占技术高点,让自己时刻成为稀缺的资源,自身价值才能一路高升。
本书是市面上唯一一本讲解Nodejs开发加密货币的实践书籍,与那些纯粹为了举例而提供的代码示例不同,全书代码,哪怕是前端代码实例,都是来自正在运行的真实项目,所以无论您是学习Nodejs技术找寻实践项目,还是学习前端设计、web开发等,或者深入区块链研究,都值得参考。
书中项目亿书完全开源,本书完全开源,链接在文末,敬请关注或参与。
我们在第一部分《了解加密货币》里说过,加密货币是“利益”转移的程序化,其核心目标是保证数字财富或价值安全、透明、快速的转移。因此,交易是加密货币系统中最重要的部分,加密货币的核心就是交易
,加密解密、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.js
module.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
是转入Aapp资金,OUT_TRANSFER
转出Aapp资金,这些是现有版本已经完成的功能。
ARTICALE
是发布文章,EBOOK
是发布电子书,BUY
是购买(电子书或其他商品),READ
是付费阅读(电子书等),这些功能会逐步添加。
这些交易,除了SEND
转账交易外,其他的交易类型,我们暂且称它们为功能性交易(在比特币的圈子里,有人称为伪交易)。
亿书交易类型尽管多样,但是交易的基本逻辑是一样的。整个加密货币都是交易逻辑的有效组成部分,要比传统电子商务网站复杂的多,但与交易直接相关的代码,却又非常简单清晰。从开发角度说,实现一笔交易,亿书需要这样几个步骤:
(1)生成交易数据
交易是人类行为,涉及到甲乙双方(货币发送者和接收者,我们用甲乙方来代替,下文同)和交易数额,这在很多交易,特别是版权交易方面更加重要。甲方是主动发起交易的有效用户,是亿书币的支付方,是交易的支付来源。乙方比较灵活,可以是另一个有合法地址的用户,也可以是亿书系统本身(功能性交易),是亿书币的接收方。
简单的一句话就是:谁与谁交易了多少钱。用下面转账交易部分的代码举例,请看modules/transactions.js
文件里的763和800行,一笔交易必须包含如下字段:
这些数据有的要求用户输入,比如用户密钥,交易数量等,这些数据是否正确,也是非常关键的事情。这是软件程序验证逻辑的一个重要部分,不可或缺。这个很好理解,如果一个人胡乱填写密钥和接受地址,也能把币发送出去,那就笑话了。但具体校验过程较为繁琐,这里主要涉及到:发起交易的用户是否存在、密钥是否正确、是否多重签名帐号、是否有支付密码,以及接受方用户地址是否合法等,都要逐个检验。
详情看这里的流程图:
(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.getTransactions
get /api/transactions/get -> shared.getTransaction
get /api/transactions/unconfirmed/get -> shared.getUnconfirmedTransaction
get /api/transactions/unconfirmed -> shared.getUnconfirmedTransactions
put /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);
}
// 这里 加入区块链 操作
private.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 (private.unconfirmedTransactionsIdIndex[transaction.id] !== undefined || private.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
亿书官方QQ群:185046161(亿书完全开源开放,欢迎各界小伙伴参与)
亿书白皮书: http://ebookchain.org/ebookchain.pdf
精通比特币(英文)
精通比特币(中文)