以太坊C++源码解析(八)交易队列(二)

交易队列的输入

交易队列的输入有两个,分别是接收到其他节点的广播交易和自身节点提交的交易。
分别来看这两种输入方式:

  • 接收广播交易
    在前面区块链同步章节中提到过,接收到交易后会通过调用TransactionQueue::enqueue()来将新交易放入交易队列中,这个函数代码非常简单:

      void TransactionQueue::enqueue(RLP const& _data, h512 const& _nodeId)
      {
          bool queued = false;
          {
              Guard l(x_queue);
              unsigned itemCount = _data.itemCount();
              for (unsigned i = 0; i < itemCount; ++i)
              {
                  if (m_unverified.size() >= c_maxVerificationQueueSize)
                  {
                      LOG(m_logger) << "Transaction verification queue is full. Dropping "
                                  << itemCount - i << " transactions";
                      break;
                  }
                  m_unverified.emplace_back(UnverifiedTransaction(_data[i].data(), _nodeId));
                  queued = true;
              }
          }
          if (queued)
              m_queueReady.notify_all();
      }
    

只是将交易放入m_unverified中,然后通知校验线程来校验。
再来看校验线程:

    void TransactionQueue::verifierBody()
    {
        while (!m_aborting)
        {
            UnverifiedTransaction work;

            {
                unique_lock<Mutex> l(x_queue);
                m_queueReady.wait(l, [&](){ return !m_unverified.empty() || m_aborting; });
                if (m_aborting)
                    return;
                work = move(m_unverified.front());
                m_unverified.pop_front();
            }

            try
            {
                Transaction t(work.transaction, CheckTransaction::Cheap); //Signature will be checked later
                ImportResult ir = import(t);
                m_onImport(ir, t.sha3(), work.nodeId);
            }
            catch (...)
            {
                // should not happen as exceptions are handled in import.
                cwarn << "Bad transaction:" << boost::current_exception_diagnostic_information();
            }
        }
    }

这里是一个简单的生产消费者队列,先将UnverifiedTransaction取出,然后调用import()函数进行校验。由于使用了线程,校验过程是异步的。

  • 自身提交交易
    节点自身提交的交易与上面的交易不同,是同步的,也就是直接会调用import()函数来进行校验。

      ImportResult TransactionQueue::import(bytesConstRef _transactionRLP, IfDropped _ik)
      {
          try
          {
              Transaction t = Transaction(_transactionRLP, CheckTransaction::Everything);
              return import(t, _ik);
          }
          catch (Exception const&)
          {
              return ImportResult::Malformed;
          }
      }
    

交易的校验

我们来看这个校验的过程,也就是TransactionQueue::import()函数。

ImportResult TransactionQueue::import(Transaction const& _transaction, IfDropped _ik)
{
    if (_transaction.hasZeroSignature())
        return ImportResult::ZeroSignature;
    // Check if we already know this transaction.
    h256 h = _transaction.sha3(WithSignature);

    ImportResult ret;
    {
        UpgradableGuard l(m_lock);
        auto ir = check_WITH_LOCK(h, _ik);
        if (ir != ImportResult::Success)
            return ir;

        {
            _transaction.safeSender();  // Perform EC recovery outside of the write lock
            UpgradeGuard ul(l);
            ret = manageImport_WITH_LOCK(h, _transaction);
        }
    }
    return ret;
}

可以看到先是调用TransactionQueue::check_WITH_LOCK()函数来做一个简单检查。检查的过程是看这个交易是否是已经校验过的,是否是之前被删除的。
接着调用_transaction.safeSender(),这个函数是通过签名反推sender,在交易那一节已经说过。最后调用manageImport_WITH_LOCK()函数来处理交易。
manageImport_WITH_LOCK()函数过程稍稍复杂,我们可以一步一步来分析。
第一步:

auto cs = m_currentByAddressAndNonce.find(_transaction.from());
if (cs != m_currentByAddressAndNonce.end())
{
    auto t = cs->second.find(_transaction.nonce());
    if (t != cs->second.end())
    {
        if (_transaction.gasPrice() < (*t->second).transaction.gasPrice())
            return ImportResult::OverbidGasPrice;
        else
        {
            h256 dropped = (*t->second).transaction.sha3();
            remove_WITH_LOCK(dropped);
            m_onReplaced(dropped);
        }
    }
}

这一步是检查m_currentByAddressAndNonce中是否有重复项,判断标准是sendernonce,如果存在重复的则检查gasPrice,如果新的交易gasPrice更低,则校验失败,否则将现有的交易删除,替换为gasPrice更高的交易。
第二步是检查m_future,检查过程与第一步类似。
第三步:

// If valid, append to transactions.
insertCurrent_WITH_LOCK(make_pair(_h, _transaction));
LOG(m_loggerDetail) << "Queued vaguely legit-looking transaction " << _h;

while (m_current.size() > m_limit)
{
    LOG(m_loggerDetail) << "Dropping out of bounds transaction " << _h;
    remove_WITH_LOCK(m_current.rbegin()->transaction.sha3());
}

m_onReady();

这里调用insertCurrent_WITH_LOCK()插入队列,然后将超出容量m_limit的部分删除,并调用m_onReady()表示目前队列有数据了,可以来取了。
我们再来看insertCurrent_WITH_LOCK()函数是怎么将交易插入队列的。

void TransactionQueue::insertCurrent_WITH_LOCK(std::pair<h256, Transaction> const& _p)
{
    if (m_currentByHash.count(_p.first))
    {
        cwarn << "Transaction hash" << _p.first << "already in current?!";
        return;
    }

    Transaction const& t = _p.second;
    // Insert into current
    auto inserted = m_currentByAddressAndNonce[t.from()].insert(std::make_pair(t.nonce(), PriorityQueue::iterator()));
    PriorityQueue::iterator handle = m_current.emplace(VerifiedTransaction(t));
    inserted.first->second = handle;
    m_currentByHash[_p.first] = handle;

    // Move following transactions from future to current
    makeCurrent_WITH_LOCK(t);
    m_known.insert(_p.first);
}

先还是检查是否有重复项,然后把交易插入m_current里,并记下插入的位置(迭代器),再分别加入m_currentByAddressAndNoncem_currentByHash中。
注意到末尾还有个makeCurrent_WITH_LOCK()的调用,这个是看情况将m_future里的交易移到m_current中。

交易队列的输出

交易队列输出只有一个,那就是区块block。在Block::sync()中会调用TransactionQueue::topTransactions()来取队列的前N项数据,再加入block中。

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

推荐阅读更多精彩内容