作为码农,遇到现象总想探知内部的原理,利用假期,一步步看下下源码,这个假期也就更充实了。
本文初步分析了一个交易在以太坊内部的处理流程,涉及到交易的接收,检查,执行,同步,区块的构建以及挖矿,结合前面一篇基于黄皮书的理解总结,对以太坊有了更多的认识。因为主要的工作在c++层面,所以这里以c++版本的以太坊源码作为学习资源。
如有理解错误的地方,希望看到的人不吝赐教,共同学习,谢谢。
1. 发送交易 eth_sendTransaction
// 一个交易框架的构成,实际就是一个交易构成元素
struct TransactionSkeleton
{
bool creation = false;
Address from;
Address to;
u256 value;
bytes data;
u256 nonce = Invalid256;
u256 gas = Invalid256;
u256 gasPrice = Invalid256;
std::string userReadable(bool _toProxy, std::function<std::pair<bool, std::string>(TransactionSkeleton const&)> const& _getNatSpec, std::function<std::string(Address const&)> const& _formatAddress) const;
};
string Eth::eth_sendTransaction(Json::Value const& _json)
{
try
{
// 从Json结构中构建交易骨架
TransactionSkeleton t = toTransactionSkeleton(_json);
setTransactionDefaults(t);
......
// 向客户端提交这个交易
h256 txHash = client()->submitTransaction(t, ar.second);
// 返回交易hash
return toJS(txHash);
......
}
....
}
学习中省略了很多代码,有些是辅助性的,有些可能是功能性,这里的目的是梳理一个流程,建立一个框架印象,省略的代码可以后续继续学习
这里很简单,就是从json中构建了一个交易框架,然后传给了客户端
2. 接收交易 importTransaction
h256 Client::submitTransaction(TransactionSkeleton const& _t, Secret const& _secret)
{
// 对交易框架 缺省字段进行补充,最后行程一个交易
TransactionSkeleton ts = populateTransactionWithDefaults(_t);
ts.from = toAddress(_secret);
Transaction t(ts, _secret);
// 把交易添加到交易队列中
return importTransaction(t);
}
// 添加交易到交易队列中
h256 Client::importTransaction(Transaction const& _t)
{
// Client继承于Worker,准备线程处理交易列表
prepareForTransaction();
// Use the Executive to perform basic validation of the transaction
// (e.g. transaction signature, account balance) using the state of
// the latest block in the client's blockchain. This can throw but
// we'll catch the exception at the RPC level.
// 这里借用了Executive的initialize来做基本的交易合法性验证
// 实际上Executive就是一个交易真正执行的主体,Evm computation,后面还会看到
Block currentBlock = block(bc().currentHash());
Executive e(currentBlock, bc());
e.initialize(_t);
// 将交易加入到交易队列中
ImportResult res = m_tq.import(_t.rlp());
......
// 最后返回交易的hash值
return _t.sha3();
}
客户端收到交易后,借用 Executive 初步检验了交易构成的合法性,然后加入到自己的交易池(m_tq)中,后面就继续跟踪m_tq是被如何处理的呢?
3. 处理交易
前面说 Client是Worker的子类,启动了处理交易的线程
Client::prepareForTransaction --> void startWorking() { Worker::startWorking(); }; --> Worker::workLoop()
void Worker::workLoop()
{
while (m_state == WorkerState::Started)
{
if (m_idleWaitMs)
this_thread::sleep_for(chrono::milliseconds(m_idleWaitMs));
// 等待一段定时时间后 缺省30ms,执行工作
doWork();
}
}
void Client::doWork(bool _doWait)
{
......
// 同步交易队列,实际内部处理内容比较多
syncTransactionQueue();
tick();
// 开始挖矿 POW
rejigSealing();
.....
}
交易处理到区块处理分了两大步,一个就是同步交易,实际也执行了交易,然后是进行挖矿,计算POW,下面分开来看
3.1 交易同步及执行
void Client::syncTransactionQueue()
{
......
// 这里构建了区块 m_working, 在Block.sync中会执行交易,执行完后,这个m_working就是我们后面要验证的区块了
// 这里就涉及上上面提到过的 Executive 对象了,它是交易的真正执行者,内容也比较复杂,后面看合适位置再详细记录下
// 这里的主要过程罗列一下:
// Block::sync --> ExecutionResult Block::execute(...) --> m_state.execute --> State::executeTransaction ---> _e.initialize _e.execute _e.finalize
// --> m_transactions.push_back(_t); // 交易执行完 加入到交易列表中
// --> m_receipts.push_back(resultReceipt.second);
tie(newPendingReceipts, m_syncTransactionQueue) = m_working.sync(bc(), m_tq, *m_gp);
}
......
m_postSeal = m_working;
DEV_READ_GUARDED(x_postSeal)
// 更新 bloomer filter
for (size_t i = 0; i < newPendingReceipts.size(); i++)
appendFromNewPending(newPendingReceipts[i], changeds, m_postSeal.pending()[i].sha3());
// Tell farm about new transaction (i.e. restart mining).
onPostStateChanged();
// Tell watches about the new transactions.
noteChanged(changeds);
// Tell network about the new transactions.
// 在网络上同步交易,这里跟下去,就会到 P2P网络部分 session host 这些都可以看到了
// 这里置位host的标记位 m_newTransactions
// 到了host内部会有如下流程:
// Host::startedWorking --> h.second->onStarting()(EthereumCapability::onStarting()) --> EthereumCapability::doBackgroundWork() 这里就看到了客户端下面这个函数职位的标记位
// 然后 EthereumCapability::maintainTransactions() --> m_host->sealAndSend --> Session::sealAndSend --> Session::send --> Session::write()
if (auto h = m_host.lock())
h->noteNewTransactions();
......
}
上面再代码中注释了很多,每一步可以顺着看到很多内容。
3.2 区块POW
这里就开始涉及到ethereum的POW过程了,再次之前要增加些其他的流程代码
3.2.1
POW的参与者分为 Farm 和 Miner,即农场和矿工,对应不同的接口
// A miner - a member and adoptee of the Farm.
template <class PoW> class GenericMiner
// Class for hosting one or more Miners.
template <class PoW> class GenericFarmFace
//只有一个工作,就是提交工作量证明
// _p 即找到的解决方案
struct Solution
{
Nonce nonce;
h256 mixHash;
};
// _finder即方案的发现者
virtual bool submitProof(Solution const& _p, Miner* _finder) = 0;
// A collective of Miners. Miners ask for work, then submit proofs
template <class PoW> class GenericFarm: public GenericFarmFace<PoW>
3.2.2
POW的执行算法 Ethash,在 aleth\main.cpp中对Ethash进行了初始化
int main(int argc, char** argv)
{
......
Ethash::init();
......
}
void Ethash::init()
{
ETH_REGISTER_SEAL_ENGINE(Ethash);
}
#define ETH_REGISTER_SEAL_ENGINE(Name) static SealEngineFactory __eth_registerSealEngineFactory ## Name = SealEngineRegistrar::registerSealEngine<Name>(#Name)
template <class SealEngine> static SealEngineFactory registerSealEngine(std::string const& _name) { return (get()->m_sealEngines[_name] = [](){return new SealEngine;}); }
private:
//单例模式
static SealEngineRegistrar* get() { if (!s_this) s_this = new SealEngineRegistrar; return s_this; }
最终是new了 Ethash 对象,即一个 SealEngine,最终放入到 SealEngineRegistrar 的 std::unordered_map<std::string, SealEngineFactory> m_sealEngines;
讲过上面步骤的一步一步执行,最终Ethash实例保存到了 SealEngineRegistrar的m_sealEngines中,名字就叫 Ethash
这里也要看下Ethash的构造过程
Ethash::Ethash()
{
// 这里创建一个叫做cpu的矿工,又叫做sealer
map<string, GenericFarm<EthashProofOfWork>::SealerDescriptor> sealers;
sealers["cpu"] = GenericFarm<EthashProofOfWork>::SealerDescriptor{&EthashCPUMiner::instances, [](GenericMiner<EthashProofOfWork>::ConstructionInfo ci){ return new EthashCPUMiner(ci); }};
// 把矿工加入到农场中
m_farm.setSealers(sealers);
// 为农场设置onSolutionFound 方法,传入Solution,进行检验
m_farm.onSolutionFound([=](EthashProofOfWork::Solution const& sol)
{
std::unique_lock<Mutex> l(m_submitLock);
// cdebug << m_farm.work().seedHash << m_farm.work().headerHash << sol.nonce << EthashAux::eval(m_farm.work().seedHash, m_farm.work().headerHash, sol.nonce).value;
setMixHash(m_sealing, sol.mixHash);
setNonce(m_sealing, sol.nonce);
// 对POW的检验
if (!quickVerifySeal(m_sealing))
return false;
if (m_onSealGenerated)
{
RLPStream ret;
m_sealing.streamRLP(ret);
l.unlock();
m_onSealGenerated(ret.out());
}
return true;
});
}
综上,Ethash里面有农场(GenericFarm<EthashProofOfWork>),农场里面有检验员,名字叫cpu,是一个 EthashCPUMine 实例, EthashCPUMiner 继承了 GenericMiner<EthashProofOfWork> 农场继承了 GenericFarmFace
怎么取用Ethash实例呢,通过名字
static SealEngineFace* create(std::string const& _name) { if (!get()->m_sealEngines.count(_name)) return nullptr; return get()->m_sealEngines[_name](); }
BlockChain又是怎么来取用的呢?
1. 在struct ChainOperationParams 有三类,默认是 NoProof
/// The chain sealer name: e.g. Ethash, NoProof, BasicAuthority (POA)
std::string sealEngineName = "NoProof";
2. 在加载配置时,
ChainParams ChainParams::loadConfig 会确定用哪一种,例如名称为 Ethash (pow)
3. 区块链初始化时,会初始化自己的 m_sealEngine
void BlockChain::init(ChainParams const& _p)
m_sealEngine.reset(m_params.createSealEngine());
SealEngineFace* ChainParams::createSealEngine()
{
SealEngineFace* ret = SealEngineRegistrar::create(sealEngineName);
....
}
4. 至此,BlockChain 有了自己的检验引擎,即 m_sealEngine,又即 Ethash 实例
客户端又怎么获取到BlockChain的Ethash?
SealEngineFace* sealEngine() const override { return bc().sealEngine(); } 这样就获得了上面的检验引擎
3.2.3
上面分析了POW检验者的建立过程,再接上最上面提到的最后一个步骤,开始挖矿
void Client::rejigSealing()
{
if ((wouldSeal() || remoteActive()) && !isMajorSyncing())
{
// 这里实际就获得了Ethash的实例
if (sealEngine()->shouldSeal(this))
{
......
//这里的m_working就是上面执行完交易后的区块,下面的函数是封装区块内容
// 由注释就可以知道,最后区块剩下没有完成的部分就是计算nonce mixhash了
//Sealing
/// Prepares the current state for mining.
/// Commits all transactions into the trie, compiles uncles and transactions list, applies all
/// rewards and populates the current block header with the appropriate hashes.
/// The only thing left to do after this is to actually mine().
///
m_working.commitToSeal(bc(), m_extraData);
......
if (wouldSeal())
{
sealEngine()->onSealGenerated([=](bytes const& _header) {
if (this->submitSealed(_header))
m_onBlockSealed(_header);
else
LOG(m_logger) << "Submitting block failed...";
});
// 开始挖矿
sealEngine()->generateSeal(m_sealingInfo);
}
}
......
}
......
}
void Ethash::generateSeal(BlockHeader const& _bi)
{
Guard l(m_submitLock);
m_sealing = _bi;
m_farm.setWork(m_sealing);
m_farm.start(m_sealer);
m_farm.setWork(m_sealing);
}
上面的流程注释了一些,构建了区块,剩下nonce mixhash,再最后调用Ethash开始挖矿计算,最终到了
void EthashCPUMiner::kickOff()
void EthashCPUMiner::startWorking()
{
if (!m_thread)
{
m_shouldStop = false;
m_thread.reset(new thread(&EthashCPUMiner::minerBody, this));
}
}
void EthashCPUMiner::minerBody()
{
setThreadName("miner" + toString(index()));
auto tid = std::this_thread::get_id();
static std::mt19937_64 s_eng((utcTime() + std::hash<decltype(tid)>()(tid)));
// 先计算一个nonce的随机起始值
uint64_t tryNonce = s_eng();
// FIXME: Use epoch number, not seed hash in the work package.
WorkPackage w = work();
// 根据seedHash找到现在的纪元 DAG
int epoch = ethash::find_epoch_number(toEthash(w.seedHash));
auto& ethashContext = ethash::get_global_epoch_context_full(epoch);
// 获取现在的难度值
h256 boundary = w.boundary;
// 开始穷举nonce,再DAG范围内寻找答案了,即POW的计算过程
for (unsigned hashCount = 1; !m_shouldStop; tryNonce++, hashCount++)
{
auto result = ethash::hash(ethashContext, toEthash(w.headerHash()), tryNonce);
h256 value = h256(result.final_hash.bytes, h256::ConstructFromPointer);
if (value <= boundary && submitProof(EthashProofOfWork::Solution{(h64)(u64)tryNonce,
h256(result.mix_hash.bytes, h256::ConstructFromPointer)}))
break;
if (!(hashCount % 100))
accumulateHashes(100);
}
}
这里到了ethereum的核心 精华部分,POW的计算过程,根据协议的实现会有个难度值的计算,我在前面总结过了,这里就是穷举nonce,找到value使得它小于boundary,找到后,就提交工作量证明 submitProof
onSolutionFound ---> Ethash::quickVerifySeal
暂时代码梳理到这里,基本上算是一个交易的流程,后面深入学习下 Executive 以及 信息的同步过程等
12.31, 2018最后一天,祝福区块链走向未来,祝愿大家越来越好