EbookCoin 源码 0x8 FIN

这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书,的源码学习笔记

EbookCoin项目地址

这章是本书的,区块链项目的最后的一个部分了

DPoS机制

简介

很多人说,整个区块链就是个分布式的软件嘛。实际不然,区块链的灵魂,就是存在于共识这两个字,共识才是区块链的灵魂。


前面的博文其实也是讲过共识的这个东西。在区链里的这个概念太重要了。

拜占庭将军与共识算法
Py源码_挖矿


“拜占庭将军问题” 就是针对分布式公式算法提出的,这个问题也是区块链产品 的核心问题.

比特币是如何解决拜占庭将军问题的:

  • 维持周期循环,保持节点步调一致
  • 通过算力竞赛,确保网络单点广播
  • 通过区块链使用一个共同账本

上面的三点就是实现一个比特币的PoW机制解决 拜占庭将军问题的答案。这也给其他的答案提供了重要的参考答案:

只要保证时间统一,步调一致,单点广播,一个链条 就可以解决分布式系统的拜占庭将军问题。


DPoS 机制

亿书区块链由受托人来创建区块,受托人是来自于用户节点的信任,通过推广和投票实现前 101名。这一就可以被系统接纳作为为真正的可以处理新区块的节点,并且可以得到区块奖励。至之于PoW的共识算法,需要最大算力。DPoS算是发挥了社区力量。不过实际上,通过票选这样的东西,我不是很看好,这样极大的提高了准入门槛。使得,投票,拉票这样的模式,又一次搬上了BC,区链的节点被固定在了这百来个节点之上。

在DPoS的过程中是存在了这样的几个过程:

  • 注册受托人,接收投票
    • 用户申请自己成为受托人,可以想做就是被选举人
    • 接收投票
  • 维持循环,调整受托人
    • 块周期:时段周期,也就是出块时间
    • 受托人周期:称为循环周期,每101个区块,重新产生了受托节点的名单。
    • 奖励周期:由区块链的高度,来不断的调整区块奖励的量
      块周期最短 10s,受托人周期 16min, 区块奖励周期 不确定
  • 循环产生新区块,广播
    • 源码

文件包含

  • ./modules/delegates.js
  • ./modules/round.js
  • ./modules/accounts.js
  • ./helper/slots.js

delegates.js 受托人的相函数,申请,投票等

round.js 受托人的循环周期

accounts.js 实现用户的投票功能

slots.js

功能实现

  • 注册受托人

    在网络上注册成为一个受托人 的函数实现

    "put /": "addDelegate"

    上面是绑定的路由接口,下面的是具体的功能实现

    shared.addDelegate = function (req, cb) {    var body = req.body;    library.scheme.validate(body, {        ... // 对象结构验证        },        required: ["secret"]    }, 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");            }        }        library.balancesSequence.add(function (cb) {            if (body.multisigAccountPublicKey && body.multisigAccountPublicKey != keypair.publicKey.toString('hex')) {                modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) {                    if (err) {                        return cb(err.toString());                    }                            ... //                    modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) {                        ... //                        try {                            // 这里创建一个交易,里面的type就是委托                            var transaction = library.logic.transaction.create({                                type: TransactionTypes.DELEGATE,                                username: body.username,                                sender: account,                                keypair: keypair,                                secondKeypair: secondKeypair,                                requester: keypair                            });                        } catch (e) {                            return cb(e.toString());                        }                        modules.transactions.receiveTransactions([transaction], cb);                    });                });            } else {                ... //

  • 投票

    同样的这也是一种交易方式,最早是出现在了 0x6 里面,作为一种 TYPE 出现。在 accounts 里面实现的。


  • 块周期

    这个周期可能是类比与 PoW 里面的一个块的计算周期,比特币的周期是 10分钟左右,因为 PoW 是基于概率出块的。所以是左右。但是在DPoS的机制下,在 ebook 里面是准确的 10秒钟的出块时间。

    • Epoch 时期;新纪元;新时代;阶段
    • Slot 槽,窗口

      时间的计算是通过这两个函数得到的时间节点,第一个是硬编码的创世时间,第二个是当前的时间

      function beginEpochTime() {

      var d = new Date(Date.UTC(2016, 5, 20, 0, 0, 0, 0)); //Testnet starts from 2016.6.20return d;

      }

      function getEpochTime(time) {

      if (time === undefined) {    time = (new Date()).getTime();    // 这里存在的问题是,这个是直接获取本地时间,如果不同的机器之间的本地时间是存在偏差的,那么可能导致时间不准}var d = beginEpochTime();var t = d.getTime();return Math.floor((time - t) / 1000);

      }

      前面讲到亿书的。出块时间是 10s 所以这里的块数,直接是返回了快创世时间,对区块周期的取整

      getSlotNumber: function(epochTime) {

      if (epochTime === undefined) {    epochTime = this.getTime()}// 这里就是直接返回了return Math.floor(epochTime / constants.slots.interval);

      },

      参数的意义

      这些值,出块时间必须10S吗?节点个数必须是 101个吗? 如果我们对这个模式进行合理演绎,只有一个节点,这样的效率就得到了极大的提高,不过实际上,这就是一个单点系统,安全性就是 0 了,所以这个是一个折衷的过程。


  • 受托人周期

    作为中心节点的受托人的循环周期,前面有讲是 101 个区块周期,发生一次调整。及时更换公信节点。网络是随机的在101个节点中选取产出者,不过每个节点在一个周期内都是会有一次机会产出新的块的(只是顺序随机)。

    这样在每个区块的信息里面,会有 区块高度 (height) 和 产生机器的公钥(Generator Public Key)是严格对应的.在运行的节点,会有一个表,是用来维护当前的 受托人的内容的,具体的实现如下,看起来有点难受

    Round.prototype.tick = function (block, cb) {    function done(err) {        cb && setImmediate(cb, err);    }    modules.accounts.mergeAccountAndGet({        ... //        // 计算当前的委托人周期轮数,是可以通过其进行计算的 r=h/101        var nextRound = self.calc(block.height + 1);
        if (round !== nextRound || block.height == 1) {            // 如果已经开始了下一轮的受托人循环            if (privated.delegatesByRound[round].length == constants.delegates || block.height == 1 || block.height == 101) {                // 确认满足了,新的一轮的条件                var outsiders = [];                async.series([                    function (cb) {                        // 创世区块                        if (block.height != 1) {                            // 这里是对受托人进行查表。                            modules.delegates.generateDelegateList(block.height, function (err, roundDelegates) {                                if (err) {                                    return cb(err);                                }                                // 遍历查到的                                for (var i = 0; i < roundDelegates.length; i++) {                                    // 在当前的列表中没有新的一轮的节点地址                                    if (privated.delegatesByRound[round].indexOf(roundDelegates[i]) == -1) {                                        outsiders.push(modules.accounts.generateAddressByPublicKey(roundDelegates[i]));                                    }                                }                                cb();                            });                        } else {                            cb();                        }                    },                    function (cb) {                    ... //                    },                    function (cb) {                        // 在后面进行票数更新                        self.getVotes(round, function (err, votes) {                            if (err) {                                return cb(err);                            }                            async.eachSeries(votes, function (vote, cb) {                                // 查询数据库,获取当前状态                                library.dbLite.query('update mem_accounts set vote = vote + $amount where address = $address', {                                    address: modules.accounts.generateAddressByPublicKey(vote.delegate),                                    amount: vote.amount                                }, cb);                            }, function (err) {                                self.flush(round, function (err2) {                                    cb(err || err2);                                });                            })                        });                    },                    function (cb) {                        // 对读出的节点票选数进行更新                        var roundChanges = new RoundChanges(round);                        async.forEachOfSeries(privated.delegatesByRound[round], function (delegate, index, cb) {                            var changes = roundChanges.at(index);                            modules.accounts.mergeAccountAndGet({                                publicKey: delegate,                                balance: changes.balance,                                u_balance: changes.balance,                                blockId: block.id,                                round: modules.round.calc(block.height),                                fees: changes.fees,                                rewards: changes.rewards                            }, function (err) {                                if (err) {                                    return cb(err);                                }                                if (index === privated.delegatesByRound[round].length - 1) {                                    modules.accounts.mergeAccountAndGet({                                        publicKey: delegate,                                        balance: changes.feesRemaining,                                        u_balance: changes.feesRemaining,                                        blockId: block.id,                                        round: modules.round.calc(block.height),                                        fees: changes.feesRemaining                                    }, cb);                                } else {                                    cb();                                }                            });                        }, cb);                    },                    function (cb) {                        // 这里是对每个票选项的数据库回写                        self.getVotes(round, function (err, votes) {                            if (err) {                                return cb(err);                            }                            async.eachSeries(votes, function (vote, cb) {                                library.dbLite.query('update mem_accounts set vote = vote + $amount where address = $address', {                                    address: modules.accounts.generateAddressByPublicKey(vote.delegate),                                    amount: vote.amount                                }, cb);                            }, function (err) {                                library.bus.message('finishRound', round);                                self.flush(round, function (err2) {                                    cb(err || err2);                                });                            })                        });                    }                ], function (err) {                    // 删除临时空间                    delete privated.feesByRound[round];                    delete privated.rewardsByRound[round];                    delete privated.delegatesByRound[round];                    done(err);                });            } else {                done();            }        } else {            done();        }    });}

  • 奖励周期

    在亿书的项目中,类似于BTC的四年一减产1/2的的情况,亿书的区块奖励一样的是,随着不同的时间进行衰减。在亿书中,最后ed区块奖励是衰减到一个定值的,意味着在后面的时期,会保持着不断的同比增发。引起适当的通胀。这也是DPoS的一个特点。

    这种,不同于 BTC 这是一种通胀货币,作者也有提到,说是担心对降低代币的价值。实际上,如果有大量的侧链,必须是要有一定的代币的产生来提供各种侧链产品的使用。不会导致主链和侧链绑定紧密,互相掣肘

    以太坊的运行,侧链应用使用主链币进行众筹时候,此消彼长,价格波动

具体的实现细节很简单,就是维护一个 reward 的值就好了。    function Milestones() {    var milestones = [        500000000, // Initial Reward        400000000, // Milestone 1        300000000, // Milestone 2        200000000, // Milestone 3        100000000  // Milestone 4    ];这里就是奖励衰减的情况的五个里程碑

总结

从共识机制上理解了DPoS这种方法,之于PoW的确有很大的不同,更像是一种民主制度。通过票选我们的代表人,来实现区块的自治.

时间统一,步调一致,单点广播,一个链条 便是一个分布式系统的特性.

而且有 块周期,受托人周期,奖励周期这样几个周期

这一篇,就是关于亿书的这个工程的最后一篇源码的文章了,很美可能会有奇迹对于项目总体的一篇总结文.

一个区块链的体系,从各个部分功能的实现,和一个总体的构成,的确是一个精妙的过程.

这里对 PoW和PoS这种共识的方式,做点自己的见解. 如我们熟知的黄金一样,对于金矿的开采, 处理这一过程,实际上会消耗大量的资源和成本,实际上我们的BTC体系也是如此,会消耗大量的资源.来产生这一的一个东西.所以不赞成,向部分人说的那样,把资源消耗在了没有意义的计算上 .

像是POS这种机制,更多的是人们相信这个币是有价值的,是一种信任.和我们手上的纸币一样,其成本的价格实际上远远不足其本身的价值,只不过,是国家赋予了他的价值,我们信任它,我们认为他是有价值的,所以我们可以使用它,可是如果我们用人民币在美利坚大陆上,可能就是没有那么好使了.所以说,PoS的代币的价值是产生于我们的信任,如果存在着信任危机,那么这种币的价值是岌岌可危的.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注