以太坊C-源码解析(十)以太坊交易中的nonce

以太坊交易中存在一个特殊的值nonce,此nonce并非计算block难度的nonce,此nonce仅仅表示发送账号发送交易的次数,从0开始,每发送一次交易+1,那么第一次发送nonce为0,第二次为1,以此类推。
nonce的存在可以用来防止重放攻击,也就是同一个交易只能被发送一次,下次发送同一个交易时,因为nonce值和最新的nonce不同,会被区块链拒绝。
我们来从代码层面看看这个nonce的生成和检测。

我们可以从一张图来看这个nonce的来龙去脉。

nonce

可以看到这张图上存在一个关键性的三角关系。

  • 三角形上面顶点是区块链blockchain
  • 左边顶点是postseal()中的noncepostseal()是从区块链获取到的最新的区块,那么左边顶点表示当前区块链中最新区块该发送账号的nonce
  • 右边顶点是交易队列中的nonce
    当我们提交一个交易时,交易的nonce取值是左右两个顶点的nonce值中取最大值。然后再与blockchain中最新块的nonce进行比较,如果不同,则区块链拒绝此交易。那么问题来了,postseal()中不就是最新块的nonce吗?为什么还需要再次比较?这是因为postseal()并不是一直与区块链同步的,只有满足某些条件才会同步。另外当交易成功提交后,该交易在正式被区块链确认前,是被存放在交易队列中的,此时右顶点的nonce值为该交易的nonce+1。

我们来一步一步拆开来看:
第一步,假设该发送者从来没有发送过交易,那么他的nonce值应该为0,上面的图会变成:

第一步

此时blockchainpostseal()中均没有该账号的信息,nonce值为初始值0。 交易队列中没有该发送账号的交易,因此nonce值也是0,那么该发送者第一次提交交易时,nonce值会被设置为max(0, 0),也就是0。然后再比较0与0是相等的,那么此交易被正确发送。

第二步,交易被发送到交易队列,这张图变成:

第二步

此时上顶点和左顶点的nonce值不变,右顶点因为交易队列中已有一个该发送者发送的交易,那么nonce+1,变成1。
此时如果该发送者想再发一个交易,那么新交易的nonce会被设置为max(0, 1),也就是1。然后再与上顶点nonce比较,得出不相等的结论,此交易被拒绝,提交失败!

第三步,第一个交易被区块链确认,这张图变成:

第三步

交易被确认后,上顶点blockchainnonce变成1,左顶点因为同步,nonce也变成1,而右顶点交易队列会删除掉已确认的交易,所以没有该发送者的交易了,nonce就为0。此时如果该发送者再发一个交易,新交易的nonce会被设置为max(1, 0),也就是1。然后再与上顶点nonce比较,结果相等,该交易被成功发出!

交易被发出后被发到交易队列,流程就同第二步了。

下面我们来看看具体涉及到的代码实现:

TransactionSkeleton Client::populateTransactionWithDefaults(TransactionSkeleton const& _t) const
{
    TransactionSkeleton ret(_t);

    // Default gas value meets the intrinsic gas requirements of both
    // send value and create contract transactions and is the same default
    // value used by geth and testrpc.
    const u256 defaultTransactionGas = 90000;
    if (ret.nonce == Invalid256)
        ret.nonce = max<u256>(postSeal().transactionsFrom(ret.from), m_tq.maxNonce(ret.from));
    if (ret.gasPrice == Invalid256)
        ret.gasPrice = gasBidPrice();
    if (ret.gas == Invalid256)
        ret.gas = defaultTransactionGas;

    return ret;
}

这段代码是提交交易时设置交易的一些参数,其中

ret.nonce = max<u256>(postSeal().transactionsFrom(ret.from), m_tq.maxNonce(ret.from));

就是设置交易的nonce,是左顶点和右顶点的nonce值的最大值。

再来看交易队列中的nonce

u256 TransactionQueue::maxNonce(Address const& _a) const
{
    ReadGuard l(m_lock);
    return maxNonce_WITH_LOCK(_a);
}

u256 TransactionQueue::maxNonce_WITH_LOCK(Address const& _a) const
{
    u256 ret = 0;
    auto cs = m_currentByAddressAndNonce.find(_a);
    if (cs != m_currentByAddressAndNonce.end() && !cs->second.empty())
        ret = cs->second.rbegin()->first + 1;
    auto fs = m_future.find(_a);
    if (fs != m_future.end() && !fs->second.empty())
        ret = std::max(ret, fs->second.rbegin()->first + 1);
    return ret;
}

注意那个+1,这个表示当前队列中该发送者最大的nonce再+1。

postseal()从区块链同步的代码在:

void Client::restartMining()
{
    bool preChanged = false;
    Block newPreMine(chainParams().accountStartNonce);
    DEV_READ_GUARDED(x_preSeal)
        newPreMine = m_preSeal;

    // TODO: use m_postSeal to avoid re-evaluating our own blocks.
    preChanged = newPreMine.sync(bc());

    if (preChanged || m_postSeal.author() != m_preSeal.author())
    {
        DEV_WRITE_GUARDED(x_preSeal)
            m_preSeal = newPreMine;
        DEV_WRITE_GUARDED(x_working)
            m_working = newPreMine;
        /// ...
        DEV_READ_GUARDED(x_working) DEV_WRITE_GUARDED(x_postSeal)
            m_postSeal = m_working;

        /// ...
    }

    /// ...
}

省略了一些次要代码,从这里可以看出,先是从区块链同步最新信息到newPreMine,然后拷贝到m_postSeal,也就是postseal()
那么Client::restartMining()是从哪里调用的呢?
有两个地方,但是一般都是从Client::resyncStateFromChain()这里调用的:

void Client::resyncStateFromChain()
{
    /// ...

    restartMining();
}

Client::resyncStateFromChain()函数又是从哪里调用呢?
也有两个地方,一处是Client::onChainChanged()表示区块链同步新块的时候,另一处是Client::syncTransactionQueue()表示同步交易队列中交易的时候。
由此可见,以太坊通过这些机制保证nonce与发送账号的发送次数严格对应,如果发送交易不遵守这条规则,则发送可能会失败,错误码为InvalidNonce

更多更新文章,欢迎访问我的博客 https://xingkong2014.github.io/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容