交易队列的输入
交易队列的输入有两个,分别是接收到其他节点的广播交易和自身节点提交的交易。
分别来看这两种输入方式:
- 接收广播交易
在前面区块链同步章节中提到过,接收到交易后会通过调用TransactionQueue::enqueue()
来将新交易放入交易队列中,这个函数代码非常简单:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void 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
中,然后通知校验线程来校验。
再来看校验线程:
1 | void TransactionQueue::verifierBody() |
这里是一个简单的生产消费者队列,先将UnverifiedTransaction
取出,然后调用import()
函数进行校验。由于使用了线程,校验过程是异步的。
- 自身提交交易
节点自身提交的交易与上面的交易不同,是同步的,也就是直接会调用import()
函数来进行校验。1
2
3
4
5
6
7
8
9
10
11
12ImportResult 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()
函数。
1 | ImportResult TransactionQueue::import(Transaction const& _transaction, IfDropped _ik) |
可以看到先是调用TransactionQueue::check_WITH_LOCK()
函数来做一个简单检查。检查的过程是看这个交易是否是已经校验过的,是否是之前被删除的。
接着调用_transaction.safeSender()
,这个函数是通过签名反推sender
,在交易那一节已经说过。最后调用manageImport_WITH_LOCK()
函数来处理交易。manageImport_WITH_LOCK()
函数过程稍稍复杂,我们可以一步一步来分析。
第一步:
1 | auto cs = m_currentByAddressAndNonce.find(_transaction.from()); |
这一步是检查m_currentByAddressAndNonce
中是否有重复项,判断标准是sender
和nonce
,如果存在重复的则检查gasPrice
,如果新的交易gasPrice
更低,则校验失败,否则将现有的交易删除,替换为gasPrice
更高的交易。
第二步是检查m_future
,检查过程与第一步类似。
第三步:
1 | // If valid, append to transactions. |
这里调用insertCurrent_WITH_LOCK()
插入队列,然后将超出容量m_limit
的部分删除,并调用m_onReady()
表示目前队列有数据了,可以来取了。
我们再来看insertCurrent_WITH_LOCK()
函数是怎么将交易插入队列的。
1 | void TransactionQueue::insertCurrent_WITH_LOCK(std::pair<h256, Transaction> const& _p) |
先还是检查是否有重复项,然后把交易插入m_current
里,并记下插入的位置(迭代器),再分别加入m_currentByAddressAndNonce
和m_currentByHash
中。
注意到末尾还有个makeCurrent_WITH_LOCK()
的调用,这个是看情况将m_future
里的交易移到m_current
中。
交易队列的输出
交易队列输出只有一个,那就是区块block。在Block::sync()
中会调用TransactionQueue::topTransactions()
来取队列的前N项数据,再加入block
中。