未分类

eth_sendTransaction函数分析

这儿是我在2018年暑假在链融工作时的研究成果,在这儿发布的时候对以前的研究又有了新的理解,这儿先发布,其中有些内容不太准确,后续更新再更改。

写在前面:
这块涉及到的几个类的简介:

Block类:
Active model of a block within the block chain. Keeps track of all transactions, receipts and state for a particular block. Can apply all needed transforms of the state for rewards and contains logic for sealing the block.

State类:
Model of an Ethereum state, essentially a facade for the trie. Allows you to query the state of accounts as well as creating and modifying accounts. It has built-in caching for various aspects of the state.
State Changelog
Any atomic change to any account is registered and appended in the changelog. In case some changes must be reverted, the changes are popped from the
changelog and undone. For possible atomic changes list @see Change::Kind.
The changelog is managed by savepoint(), rollback() and commit() methods.

正文:
eth_sendTransaction 函数主要执行交易的验证,包括发送者是否有足够的余额来支付交易所需要的gas和账单,区块啥的是否正常,开启数据库是否成功,以及交易上链后的各种状态是否正常,等等。满足上链的各种需求后,这个函数会把交易放入一个名为m_transaction的队列(应该就是交易池(txpool)),挖到矿的时候会检查这个队列里面是否有东西,有的话就将内容写入挖出的区块。所以,综上所述,这个函数根本就没有写入数据的部分,但我们凡是一下上链的流程,我们就会发现,这个并不奇怪,但当我们专注取研究这个函数的时候,我们往往会忘记这点,所以以后继续分析代码的时候千万别忘记不时想一下我们平时是怎么操作的。(ps:有点意外的发现,就是有时候函数执行某项功能,不一定是在函数体里面,也有可能是在构造器部分,这个以后分析代码的时候一定要注意。)

接下来就开始我们的正文。一切开始于libweb3jsonrpc/Eth.cpp第247行的函数:

string Eth::eth_sendTransaction(Json::Value const& _json)

这个函数接受一个json格式的交易条文eg {from :xxxx, to: xxxxxx,value:xxxxx},后面还可以加上gas等等,如果不加的华系统有默认赋值。

251行到255行为对这个json文件的处理,到了256行

h256 txHash = client()->submitTransaction(t, ar.second)

向submitTransaction函数传入的第一个值是通过toTransactionSkeleton函数处理过的json文件,这个函数字面意思,就是化为一个骨架方便系统处理,这个就和python网络 爬虫里面的beautifusoup库对html文件的处理是一个意思,第二个参数就是账户的密码了。然后我们跳到Client类。

在libethereum/Client.cpp里面,这个函数submitTransaction在第870行,这个函数构造了一个Transaction的实例并将这个实例传入importTransaction函数,接下来我们来看878行的importTransaction函数。

首先是prepareForTransaction()函数,这个没啥好说的。再就是886行的

Block currentBlock = block(bc().currentHash())

这条很好理解,但这个bc()需要注意下,这个bc()是在调用Client构造器的时候赋值,具体位置是Client.cpp第77行,赋值的时候是给m_bc赋值,这个m_bc作为一个返回值在调用bc()时返回,可以通过bc().currentHash获取当前区块的hash头,也可以通过bc.parentHash获取上一个区块的hash头,这个后面验证的时候会用到,也可通过bc().block(hash),获取某个特定的区块。这个构造器在Client.cpp 760行,调用了一个Block类的构造器。 接下来我们来具体分析这个构造器,这个构造器的具体位置在block.cpp 61行。

Block::Block(BlockChain const& _bc, OverlayDB const& _db, BaseState _bs, Address const& _author):
m_state(Invalid256, _db, _bs),
m_precommit(Invalid256),
m_author(_author)
{
noteChain(_bc);
m_previousBlock.clear();//information of the previous block
m_currentBlock.clear();//information of the current block
// assert(m_state.root() == m_previousBlock.stateRoot());
}

这个m_state 呢,主要包括Database 的一系列操作,包括初始化一个DB,就是一个数据库,其中定义了一些列操作,个人觉得没必要取深究,过于繁琐,记不住,而且就是读库了,详细点的分析在本人的本子上,相应步骤源代码里面有简略的注释分析。(说实话,这一块没太看懂,不太清楚作者的用途。)

这个m_precommit呢,被赋值为Invalid256,这个Invalid256是个constant u256类型的值(是以太坊开发者Andrei Maiboroda说的,就是那个蘑菇头)。我不清楚是干啥的,但在后面的操作中,这个值被修改了,但为什么这个constant值会被修改? 玄学?这一块主要是开启数据库,连接数据库,并且储存地址,账户啥的。而且这个m_precommit就是一个State类型的,这个State类本人没有深入分析,就不在这儿瞎BB了。

这个author 就是确定接收账户,这是系统设立的默认值。这儿不做讲解。
到这儿就要开始对交易本身进行验证了,判断是否能正确执行。

在noteChain 函数中,涉及到一个nonce概念,这个nonce中文意思是暂时的,但显然,这儿并不是这个意思,在这儿,nonce是一个随机数,每个交易必须有一个nonce,这个值是为了防止交易重播而设立的,更加详细的信息在这三篇博客中有更详细的介绍。

https://www.jianshu.com/p/dda78b530cca

https://blog.csdn.net/wo541075754/article/details/78081478?locationnum=3&fps=1

https://blog.csdn.net/wo541075754/article/details/79054937

调用这个noteChain 会给m_state以及m_precommit里面的和nonce有关的参数进行赋值而且会设立m_sealEngine。

回到Client.cpp 761行,

ret.populateFromChain(bc(),block)

这个函数主要判断链的各种信息,包括是否是创世区块,判断链是否出错,是否满足上链的条件,
但其中第186行(Block.cpp),有个十分重要的函数enact,字面意思是颁布,发布,虽然在这儿并没有发布,但是源码中作者的注释 All ok with the block generally. Play back the transactions now… 让人不得不重视这个函数,在这个函数的第498行又调用了另外一个Block构造器,在这个构造器中,有个函数特别重要,就是Block.cpp第673行的m_transaction.push_back(_t),这个就是构建一个交易池,在挖矿的时候会检测交易池中是否有交易信息,就是检测这个队列中是否有内容,如果有的华就写入区块信息,所以,广播啥的应该不是一个单独的部分,而是应该和随同区块一同广播的。
这个有待后续研究。enact这个函数后面一部分就是将各种状态的改变写入前面构建好的数据库了,还有一些其他内容,大致就是区块状态的检验,和分叉有关,但由于过于繁琐,就不在这里介绍了。

887(Client.cpp)行的Executive构造器的作用为实例化一个executive的对象,具体情况有点复杂,构造器又套了几个构造器,具体实现方法不展开分析了,以太坊作者本人对这个构造器的注释为

  • Creates executive to operate on the state of end of the given block, populating environment
  • info accordingly, with last hashes given explicitly.

888行initialize()函数就是对交易签名的检验以及对发送者余额是否足够支付交易开销和发送的币,但问题是,如果不能的话,没有发现在哪执行将交易池中的信息删除,只报错并抛出错误,同时如果成立的话,也没有发现在哪更新账户信息。不过更新账户信息这块,有可能是在上链后进行的。

下面那个import函数和广播有关, 有关内容后续会更新。

最后返回交易的hash值。

Leave a Reply

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