




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
以太坊源碼分析報(bào)告前言以比特幣為代表的虛擬貨幣時(shí)代,代表著區(qū)塊鏈1.0,基于P2P網(wǎng)絡(luò)構(gòu)建,實(shí)現(xiàn)了去中心化的數(shù)字貨幣交易功效。但是1.0只滿足了虛擬貨幣的需要,很難普及到其它行業(yè)。例如比特幣只提供了有限的非圖靈完備的腳本能力,不大可能在其上搭建第三方的應(yīng)用。發(fā)展到區(qū)塊鏈2.0,便出現(xiàn)了以以太坊為代表的智能合約平臺(tái),提供強(qiáng)大的合約編程環(huán)境,能夠?qū)崿F(xiàn)復(fù)雜的業(yè)務(wù)邏輯。與比特幣系統(tǒng)相比以太坊并沒有本質(zhì)的區(qū)別,只是全方面實(shí)現(xiàn)和支持智能合約,讓區(qū)塊鏈技術(shù)不只是發(fā)幣。本文以以太坊的官方go語言版本實(shí)現(xiàn)go-ethereum為目的,分析其源碼實(shí)現(xiàn)。以太坊架構(gòu)介紹以以太坊實(shí)現(xiàn)Web3.js核心去中心化應(yīng)用層智能合約層EVMRPC區(qū)塊鏈管理模塊共識(shí)模塊挖礦模塊網(wǎng)絡(luò)模塊P2P加解密庫levelDBSolidityMath&Number賬戶管理模塊最頂層是去中心化應(yīng)用層,即DApp。它使用truffle開發(fā)測(cè)試框架(最流行)編寫布署和測(cè)試客戶端,并通過web3.js和智能合約層交互;智能合約層通過以太坊虛擬機(jī)EVM交互解決BlockChain及共識(shí)有關(guān)的事務(wù),同時(shí)通過RPC合同進(jìn)行挖礦和網(wǎng)絡(luò)層事務(wù)的交互;區(qū)塊鏈管理模塊圍繞交易、塊和狀態(tài)進(jìn)行管理,涉及區(qū)塊的同時(shí)驗(yàn)證及異常和分叉解決、交易的廣播接受解決和驗(yàn)證及執(zhí)行、底層數(shù)據(jù)的讀寫更新等;共識(shí)模塊是制訂的認(rèn)定區(qū)塊正當(dāng)?shù)臋C(jī)制,涉及PoW(ProofOfWork工作量證明,以太坊使用變種的Ethash算法)及PoS(ProofOfStake權(quán)益證明,只在測(cè)試網(wǎng)絡(luò)中使用),符合共識(shí)算法的新區(qū)塊才會(huì)被節(jié)點(diǎn)承認(rèn)和接納,鏈接到分布式賬本中,同時(shí)才干讓礦工得到收益;挖礦模塊管理挖礦工作,將爭(zhēng)奪記賬權(quán)的過程分解成多個(gè)并行子任務(wù)進(jìn)行;賬戶管理模塊管理以太坊系統(tǒng)中的賬戶,涉及普通賬戶及合約賬戶的生成和管理,尚有錢包及密鑰的生成、導(dǎo)入和導(dǎo)出;網(wǎng)絡(luò)模塊管理著系統(tǒng)中的Peer、Protocol、Downloader、Sync等角色,為整個(gè)分布式網(wǎng)絡(luò)提供節(jié)點(diǎn)間的共識(shí)基礎(chǔ)。涉及節(jié)點(diǎn)對(duì)端連接的動(dòng)態(tài)管理、ETH/LES/LES2合同的支持、各類數(shù)據(jù)包的下載和同時(shí);架構(gòu)的最底層功效為上層模塊提供了基礎(chǔ)P2P網(wǎng)絡(luò)的通訊、secp251和sha3等加解密算法、高效的LevelDB鍵值對(duì)存儲(chǔ)數(shù)據(jù)庫、合約語言基礎(chǔ)及大數(shù)字的基本運(yùn)算。源碼目錄構(gòu)造基本概念在以太坊的YellowPaper中,把整個(gè)以太坊當(dāng)作是一種基于交易的狀態(tài)機(jī)。從創(chuàng)世狀態(tài)開始,在一批交易執(zhí)行后便進(jìn)入到下一種新的狀態(tài),直到現(xiàn)在的終態(tài)。創(chuàng)世態(tài)創(chuàng)世態(tài)狀態(tài)1交易狀態(tài)N……..交易交易Block0HeaderTXsBlock1HeaderTXs
BlockNHeaderTXs
……BlockChain交易當(dāng)一種賬戶向另一種賬戶發(fā)送一筆被簽名的消息數(shù)據(jù)包時(shí),就產(chǎn)生了一筆交易。賬戶能夠是普通賬戶,也能夠是合約賬戶。交易執(zhí)行時(shí)需要耗費(fèi)手續(xù)費(fèi)。交易Transaction定義在core/types/transaction.go中:Transaction的主體定義在txdata中,其它組員都只是交易慣用信息的緩存:hash:交易R(shí)LP編碼后的哈希值;size:交易R(shí)LP編碼后的大??;from:交易的發(fā)送地址,它并不存儲(chǔ)在交易體里,而是由txdata中的V,R,S值推導(dǎo)出來;交易的重要信息包含在txdata中,涉及以下字段:AccountNonce:代表發(fā)送賬戶發(fā)出的第幾筆交易;Price:交易發(fā)送者樂意支付的一單位gas費(fèi)用的價(jià)格;GasLimit:交易執(zhí)行所耗費(fèi)最大的gas值。如果超出該值則交易失敗;Receipient:交易的接受地址;Amount:從發(fā)送地址向接受地址轉(zhuǎn)移的以太幣數(shù)量;Payload:可選,在創(chuàng)立合約時(shí)表達(dá)合約代碼,或者調(diào)用合約時(shí)調(diào)用參數(shù);V|R|S:secp256k1簽名數(shù)據(jù);Hash:同Transaction.hash,在轉(zhuǎn)換為Json格式時(shí)用到;區(qū)塊一種區(qū)塊包含了一系列的交易,礦工節(jié)點(diǎn)收集本地發(fā)起的及網(wǎng)絡(luò)中其它節(jié)點(diǎn)廣播的新交易,驗(yàn)證交易的有效性,然后將它們打包到一種原始區(qū)塊中,最后通過挖礦得到一種數(shù)學(xué)機(jī)制的“工作量證明”寫到該區(qū)塊,從而得到一種新的正當(dāng)區(qū)塊,廣播到網(wǎng)絡(luò)中,在其它礦工驗(yàn)證區(qū)塊有效后添加到主鏈上。區(qū)塊的定義在core/types/block.go中:header:Block的核心,由背面給出其定義;uncles:叔塊,以太坊對(duì)孤塊(發(fā)現(xiàn)晚但是正當(dāng)?shù)男聣K)的解決和比特幣的拋棄式解決不同,由于以太坊十幾秒的出塊間隔會(huì)造成大量的孤塊,因此以太坊激勵(lì)礦工引用孤塊成為叔塊并支付酬勞,減少昂貴成本的浪費(fèi),使得主鏈更重提高安全性,也緩和礦池中心化問題;transactions:區(qū)塊打包的一批交易;td:TotalDifficulty,總難度值,主鏈?zhǔn)莟d值最大的鏈;ReceivedAt:統(tǒng)計(jì)塊的接受時(shí)間;ReceivedFrom:統(tǒng)計(jì)塊的發(fā)送peer;Header的定義也在core/types/block.go中:ParentHash:父區(qū)塊的哈希值,除了創(chuàng)世塊以外每個(gè)區(qū)塊都有且只有一種父區(qū)塊;UncleHash:Block.uncles的RLP編碼后的哈希值;Coinbase:挖出該區(qū)塊的礦工地址,礦工的挖礦收益都是發(fā)給這個(gè)地址;Root:“statetrie”的根節(jié)點(diǎn)的RLP哈希值。全部賬戶對(duì)象逐個(gè)插入一種Merkle-PatricaTrie(MPT)構(gòu)造里形成一棵“statetrie”;TxHash:“txtrie”的根節(jié)點(diǎn)的RLP哈希值。Block.transactions中的全部tx對(duì)象逐個(gè)插入一種MPT構(gòu)造,形成一棵”txtrie”;ReceiptHash:“receipttrie”的根節(jié)點(diǎn)的RLP哈希值。Block的每一筆交易執(zhí)行完后會(huì)生成一種Receipt數(shù)組,這個(gè)數(shù)組中的全部Recipt被逐個(gè)插入一種MPT構(gòu)造里,形成一棵“receipttrie”;Bloom:Bloom過濾器,由Block中的全部交易收據(jù)中的log生成有關(guān)地址和topic的索引,用于快速判斷指定的條件(指定地址或指定的事件)與否存在于一組已知的Log集合;Difficulty:Block的難度值。由父塊的難度值和時(shí)間戳計(jì)算得到;Number:Block的序號(hào),等于其父塊Number+1;GasLimit:Block內(nèi)的全部gas消耗的上限;GasUsed:Block內(nèi)全部交易執(zhí)行后實(shí)際消耗的gas總和;Time:Block的創(chuàng)立時(shí)間;Extra:和該Block有關(guān)的任意字節(jié)數(shù)組;MixDigest:256位的哈希值,和Nonce一起用來證明該塊持有有效的工作量證明;Nonce:64位的哈希值,和MixDigest一起用來證明該塊持有有效的工作量證明;區(qū)塊鏈上節(jié)說到Block的組員header.ParentHash是父區(qū)塊的指針,把全部區(qū)塊按照這種鏈接有關(guān)系連接起來,便形成了一條從創(chuàng)世塊到現(xiàn)在塊的反向鏈表,即區(qū)塊鏈。該區(qū)塊鏈包含了全部的歷史交易信息,且只有在共識(shí)機(jī)制下被礦工承認(rèn)的正當(dāng)區(qū)塊才干被添加到鏈中,稱為主鏈。如果有多個(gè)正當(dāng)區(qū)塊同時(shí)產(chǎn)生,但是由于網(wǎng)絡(luò)延時(shí)問題被不同的礦工節(jié)點(diǎn)接受到添加到鏈上,便會(huì)出現(xiàn)分叉。以太坊使用“GHOST(GreedyHeaviestObservedSubtree)”機(jī)制擬定有效的途徑,即選擇一種擁有最多計(jì)算量的途徑。分叉分叉分叉權(quán)威鏈,主鏈,擁有最多計(jì)算量創(chuàng)世塊BlockChain的定義和維護(hù)操作在core/blockchain.go中:chainConfig:包含鏈配備,涉及以太坊各歷史版本升級(jí)時(shí)的區(qū)塊高度,鏈ID,采用的共識(shí)引擎等;cacheConfig:重要用于控制Trie的緩存開關(guān)和大小;db:底層db操作接口,用于讀寫leveldb數(shù)據(jù);triegc:寄存已插入數(shù)據(jù)庫的Block對(duì)應(yīng)的trie樹根,在超出cacheConfig配備的內(nèi)存限制時(shí)將其回收;gcproc:累計(jì)解決正當(dāng)塊的總時(shí)間,超出配備的trie刷新到磁盤的等待時(shí)間的話,在trie樹內(nèi)存超出上限時(shí)選擇一種舊塊把其trie寫到磁盤,并重置;hc:頭鏈,把區(qū)塊鏈的頭部數(shù)據(jù)連接起來形成的鏈。由于區(qū)塊鏈的諸多操作如驗(yàn)證、獲取塊信息都只需要頭部,因此獨(dú)立出來方便操作調(diào)用;rmLogsFeed/chainFeed/chainSideFeed/logsFeed/scope:事件訂閱有關(guān),其它組件需要監(jiān)聽主鏈的狀態(tài)變化來觸發(fā)對(duì)應(yīng)的解決過程;genesisBlock:創(chuàng)世塊,在peer握手時(shí)的溝通協(xié)商與否是始于同一區(qū)塊;mu/chainmu/procmu:互斥鎖;currentBlock:該節(jié)點(diǎn)的現(xiàn)在塊,即它所承認(rèn)的最新塊;currentFastBock:快速同時(shí)模式下的現(xiàn)在塊;stateCache:封裝trie.Database加了一層緩存cachingDB以快速訪問trie,重要用于根據(jù)trieroot讀入trie樹,而trie樹里包含了全部賬戶狀態(tài);bodyCache/bodyRLPCache/blockCache:body/RLP編碼body/block的緩存,用于快速訪問;futureBlocks:如果新區(qū)塊的時(shí)間戳是在距現(xiàn)在15s之后,或者父區(qū)塊在futureBlocks中則把新區(qū)塊放到futureBlocks中,然后定時(shí)加到區(qū)塊鏈里;quit:接受退出信號(hào);running:服務(wù)正在運(yùn)行標(biāo)志;procInterrupt:服務(wù)中斷運(yùn)行標(biāo)志,如果它為1則停止Block解決;wg:等待鎖,用于服務(wù)停止時(shí)等待運(yùn)行的goroutine結(jié)束;engine:共識(shí)引擎接口,共識(shí)有關(guān)操作都是調(diào)用這個(gè)接口;processor:區(qū)塊解決接口,用于運(yùn)行區(qū)塊里的交易并生成收據(jù);validator:塊和狀態(tài)的檢查接口;vmConfig:EVM虛擬機(jī)配備,在執(zhí)行交易生成新的EVM時(shí)使用;badBlocks:已經(jīng)的壞區(qū)塊。程序啟動(dòng)從磁盤加載鏈時(shí)需要檢查與否包含壞區(qū)塊;BlockChain對(duì)內(nèi)提供Block的管理,如初始化時(shí)loadLastState(…)從磁盤加載區(qū)塊鏈并檢查鏈的對(duì)的性,調(diào)用Validator().ValidateState(..)、Validator().ValidateBody(…)檢查塊中的狀態(tài)和區(qū)塊體數(shù)據(jù),調(diào)用InsertChain(…)插入新的區(qū)塊等,這些都會(huì)在背面的功效流程里分析到。啟動(dòng)流程go-ethereum編譯出來的官方客戶端程序geth,提供了龐大的子命令和命令行參數(shù),分別控制節(jié)點(diǎn)的運(yùn)行模式、挖礦參數(shù)、網(wǎng)絡(luò)參數(shù)、交易及調(diào)試參數(shù)等,這些選項(xiàng)由geth解決后修改對(duì)應(yīng)的默認(rèn)配備項(xiàng),控制geth節(jié)點(diǎn)的行為。我們先從geth的啟動(dòng)流程開始分析,理解節(jié)點(diǎn)運(yùn)行所需的核心組件及互有關(guān)系。geth使用urfave/cli庫封裝了命令行參數(shù)解析過程,抽象出Flags/Commands這些模塊,顧客只需要提供某些模塊的配備即可。導(dǎo)入包后來調(diào)用cli.NewApp()創(chuàng)立一種實(shí)例,然后調(diào)用Run()辦法執(zhí)行app.Action入口函數(shù)。geth的啟動(dòng)入口在main包c(diǎn)md/geth/main.go中,默認(rèn)首先執(zhí)行包體的init()函數(shù),為app指定Action(geth)、Commands、Flags、Copyright等信息,然后在main()中調(diào)用app.Run()正式啟動(dòng),進(jìn)入geth(…)函數(shù):geth(…)函數(shù)所做的事看起來很簡(jiǎn)樸,就是先創(chuàng)立一種Node,然后啟動(dòng)節(jié)點(diǎn)運(yùn)行,直到Node退出,程序結(jié)束。我們先看makeFullNode(ctx)是如何創(chuàng)立一種節(jié)點(diǎn)的。(1).調(diào)用makeConfigNode(ctx)根據(jù)配備創(chuàng)立一種Node(命名為stack)a.先創(chuàng)立默認(rèn)配備,分成四部分:Eth(客戶端有關(guān))、Shh(Whisper有關(guān))、Node(節(jié)點(diǎn)有關(guān))、Dashboard(dashboard有關(guān));b.如果命令行里指定了配備文獻(xiàn),則從配備文獻(xiàn)加載覆蓋對(duì)應(yīng)的配備項(xiàng);c.將node有關(guān)的命令行選項(xiàng)配備應(yīng)用生效,涉及P2P網(wǎng)絡(luò)配備、IPC/HTTP/WebSocket的有關(guān)配備,及數(shù)據(jù)目錄途徑、賬戶密鑰目錄等;d.解決完node配備后便能夠調(diào)用node.New(&cfg.Node)創(chuàng)立一種Node類對(duì)象,該對(duì)象還包含一種AccountManager,用于管理本地賬戶和密鑰;e.將Eth有關(guān)的命令行選項(xiàng)配備生效,涉及coinbase、同時(shí)模式、gcMode、挖礦協(xié)程數(shù)、交易池有關(guān)的配備等;f.將Whisper有關(guān)的命令行選項(xiàng)配備生效;g.將Dashboard有關(guān)的命令行選項(xiàng)配備生效;至此,Eth、Dashboard、Whisper有關(guān)的配備都已經(jīng)準(zhǔn)備好了。(2).向Node注冊(cè)Eth服務(wù)(即創(chuàng)立客戶端類,重點(diǎn)核心)(3).向Node注冊(cè)Dashboard服務(wù)(如果指定了—dashboard選項(xiàng))(4).向Node注冊(cè)Whisper服務(wù)(如果指定了—shh選項(xiàng))所謂注冊(cè),就是把注冊(cè)的服務(wù)的啟動(dòng)函數(shù)添加到Node的服務(wù)啟動(dòng)函數(shù)數(shù)組中,在背面的startNode(..)會(huì)取出并調(diào)用?,F(xiàn)在我們有了配備好的Node節(jié)點(diǎn),和Eth、Dashboard、Whisper服務(wù)有關(guān)的配備,現(xiàn)在調(diào)用startNode(…)在節(jié)點(diǎn)上啟動(dòng)對(duì)應(yīng)的服務(wù)。startNode的重要調(diào)用在utils.StartNode(stack)中,stack是上面創(chuàng)立的Node節(jié)點(diǎn),其它的幾個(gè)部分代碼是賬戶密碼解鎖、設(shè)立錢包打開關(guān)閉事件監(jiān)聽及啟動(dòng)挖礦(如果指定了—mine)。我們直接進(jìn)到stack.Start()函數(shù)里去瞅瞅它的啟動(dòng)過程(node/node.go)。(1).調(diào)用OpenDataDir()在數(shù)據(jù)目錄下創(chuàng)立一種LOCK文獻(xiàn),避免數(shù)據(jù)被多個(gè)程序訪問造成數(shù)據(jù)不一致;(2).配備n.serverConfig,它是P2P網(wǎng)絡(luò)的配備,涉及:節(jié)點(diǎn)私鑰,用于與網(wǎng)絡(luò)中其它P2P節(jié)點(diǎn)握手交換密鑰并生成公共密鑰。從數(shù)據(jù)目錄的nodekey文獻(xiàn)里讀取,并轉(zhuǎn)換成一種橢圓加密算法的私鑰。如果沒有則生成新密鑰并寫到該文獻(xiàn)。節(jié)點(diǎn)名字,如
Logger靜態(tài)節(jié)點(diǎn):p2p節(jié)點(diǎn)啟動(dòng)后會(huì)和靜態(tài)節(jié)點(diǎn)建立連接可信任節(jié)點(diǎn):即使超出最大允許連接數(shù),可信任節(jié)點(diǎn)仍允許繼續(xù)連接(3).使用n.serverConfig配備創(chuàng)立一種p2p.Server,從而便該節(jié)點(diǎn)成為p2p網(wǎng)絡(luò)的一員,能夠監(jiān)聽新連接,與網(wǎng)絡(luò)中別的節(jié)點(diǎn)握手建立新連接,及通信交換數(shù)據(jù)。這里不討論細(xì)節(jié),下一節(jié)會(huì)進(jìn)一步P2P網(wǎng)絡(luò)進(jìn)行分析(4).還記得之前在node上注冊(cè)的那3個(gè)服務(wù)嗎,現(xiàn)在是時(shí)候關(guān)照一下它們了(在我的測(cè)試?yán)锊⑽磫⒂胐ashboard,而whisper也是用于DApp的分布式通信,臨時(shí)不進(jìn)一步。重要以ETH服務(wù)為根本,由于它是節(jié)點(diǎn)功效實(shí)現(xiàn)的核心)。我們先回到過去,看看ETH服務(wù)的啟動(dòng)函數(shù):根據(jù)同時(shí)模式的不同,如果是LightSync則服務(wù)函數(shù)是創(chuàng)立一種輕節(jié)點(diǎn)客戶端les.New(…),否則創(chuàng)立一種全節(jié)點(diǎn)客戶端Eth.New(…)。回到node.Start(),之前被注冊(cè)到node的服務(wù)函數(shù)被取出來并執(zhí)行,對(duì)于ETH服務(wù)則返回一種eth.Ethereum類指針(假設(shè)未指定輕同時(shí)),然后寄存到map[類型]=>對(duì)應(yīng)實(shí)例的services中(5).P2P節(jié)點(diǎn)在握手的時(shí)候還需要其上運(yùn)行的合同信息,因此要把services中每個(gè)service對(duì)應(yīng)的Protocol信息加到p2p.Server中(Protocol信息在實(shí)例創(chuàng)立的時(shí)候已經(jīng)創(chuàng)立了)(6).現(xiàn)在能夠正式開搞了,首先啟動(dòng)p2p節(jié)點(diǎn)服務(wù)(running.Start())(7).依次啟動(dòng)services里的服務(wù),如果有一種服務(wù)啟動(dòng)失敗則全部停止并返回錯(cuò)誤(servcie.Start(running))。對(duì)于全節(jié)點(diǎn)這里調(diào)用的是eth.Ethereum.Start(running)啟動(dòng)了客戶端(8).最后啟動(dòng)RPC有關(guān)服務(wù),IPC/HTTP/WebSocket作為核心的eth.Ethereum,它的Start()正式宣布了節(jié)點(diǎn)的啟動(dòng)完畢。在探究Start()的過程前,有必要先分析一下eth.Ethereum類和它的創(chuàng)立過程,先看它在代碼中的定義(eth/backend.go:62):eth.Ethereum類其實(shí)現(xiàn)以太坊全節(jié)點(diǎn)功效模塊的容器,它內(nèi)含管理動(dòng)態(tài)變化交易的交易池TxPool,完畢ETH合同交互的ProtocolManager,有執(zhí)行挖礦的Mine,有維護(hù)區(qū)塊鏈數(shù)據(jù)的BlockChain,而它需要為這些功效模塊提供對(duì)的運(yùn)行所需的ETH有關(guān)配備、鏈配備、數(shù)據(jù)庫接口、共識(shí)引擎、賬戶管理、gasPrice等。它的創(chuàng)立過程就是創(chuàng)立上述核心模塊并初始化的過程:(1).打開數(shù)據(jù)目錄下的$DATADIR/chaindata數(shù)據(jù)庫(2).從數(shù)據(jù)庫里讀出chain配備和創(chuàng)世塊的哈希值(3).創(chuàng)立一種新的Ethereum構(gòu)造,除了從入?yún)onfig和ctx取出需要的變量config、accountManager、gasPrice等進(jìn)行傳遞賦值,還創(chuàng)立了共識(shí)引擎、BloomIndexer(4).core.NewBlockChain(…)從數(shù)據(jù)庫“chaindata”中加載創(chuàng)世塊、已知的最新塊和對(duì)應(yīng)的TD(TotalDifficulty,用來擬定最重的權(quán)威主鏈)(5).啟動(dòng)bloomIndexer服務(wù)(6).對(duì)于本地交易如果指定了交易池的Journal選項(xiàng),則會(huì)將本地交易持久化到數(shù)據(jù)庫(7).創(chuàng)立交易池TxPool,交易池中寄存本地交易和從網(wǎng)絡(luò)接受到的交易(8).創(chuàng)立ProtocolManager(9).創(chuàng)立礦工Miner(10).創(chuàng)立API解決服務(wù)(11).返回創(chuàng)立的Ethereum指針Now,eth.Ethereum.Start(..): (1).啟動(dòng)bloom位數(shù)據(jù)獲取的協(xié)程提供服務(wù)(2).提供RPC接口中的network有關(guān)命令的解決函數(shù)(3).啟動(dòng)protocolManager(4).如果提供LES請(qǐng)求支持的話啟動(dòng)lesServer為什么這里只啟動(dòng)了protocolManager?TxPool和Miner呢?其實(shí)TxPool在創(chuàng)立后就已經(jīng)開始了無休止的loop循環(huán)解決過程,至于Miner,還記得Node啟動(dòng)時(shí)調(diào)用的StartNode(…)函數(shù)嗎,該函數(shù)最后是這樣說的:如果命令行指定了啟動(dòng)挖礦,或者是開發(fā)模式,就啟動(dòng)挖礦.如果沒有指定的話,尚有一種方式啟動(dòng)挖礦,就是在終端console里輸入命令:web3.miner.start().好了,現(xiàn)在我們說回ProtocolManager的啟動(dòng),也是啟動(dòng)流程的最后任務(wù).它一共啟動(dòng)了4個(gè)go協(xié)程:(1).向TxPool訂閱新交易事件,然后啟動(dòng)BroadcastLoop(),一旦有新交易,它就會(huì)把該交易告知給不包含該交易的節(jié)點(diǎn);(2).訂閱挖出新區(qū)塊的事件,然后啟動(dòng)minedBroadcastLoop(),一旦有新區(qū)塊,它就會(huì)把該區(qū)塊發(fā)送給網(wǎng)絡(luò)中的其它節(jié)點(diǎn),告訴它們本節(jié)點(diǎn)有該區(qū)塊(3).啟動(dòng)syncer()同時(shí)區(qū)塊(4).啟動(dòng)txsyncLoop(),向新連接的節(jié)點(diǎn)發(fā)送本交易池中Pending的交易ProtocolManager合同有關(guān)的部分留到下一節(jié)P2P網(wǎng)絡(luò)中一起分析.到現(xiàn)在為止我們大致梳理了一下geth主啟動(dòng)流程,忽視了某些不太緊要的東西,如沒考慮輕客戶端(類似全客戶端),沒展開whisper和dashboard,也沒有提Bloom位有什么功效,由于現(xiàn)在只考慮啟動(dòng)過程?,F(xiàn)在我們啟動(dòng)了P2P網(wǎng)絡(luò)服務(wù)能夠和網(wǎng)絡(luò)中其它節(jié)點(diǎn)通信交換信息,有了一種運(yùn)行中的eth.Ethereum全節(jié)點(diǎn)客戶端能夠接受管理交易,挖礦賺取收益,同時(shí)區(qū)塊鏈和維護(hù)數(shù)據(jù)庫,還提供了對(duì)外訪問接口的RPC服務(wù)。如果沒有底層的P2P網(wǎng)絡(luò),全節(jié)點(diǎn)只是一種毫無憤怒的死循環(huán)程序,沒有數(shù)據(jù)流動(dòng),沒有交易交換,沒有塊溝通,區(qū)塊鏈不可篡改的分布式賬本定位也是空中樓閣.因此緊接著在下一節(jié)我們就先分析區(qū)塊鏈整以生存的P2P網(wǎng)絡(luò)實(shí)現(xiàn).P2P網(wǎng)絡(luò)和節(jié)點(diǎn)go-ethereum代碼中P2P的實(shí)現(xiàn)在p2p/目錄下?;叵雗ode.Start()啟動(dòng)和p2p有關(guān)的部分:創(chuàng)立了一種p2p.Server,只初始化了它的配備,然后把其它服務(wù)所支持的合同收藏起來,然后啟動(dòng)。p2p.Server構(gòu)造固然不止這樣簡(jiǎn)樸,先看其定義(p2p/server.go:147):newTransport:一種生成一種transport接口的函數(shù),transport提供傳輸層的握手功效和消息讀?。籲tab:kademlia算法的節(jié)點(diǎn)發(fā)現(xiàn)實(shí)現(xiàn)(重要);listener:監(jiān)聽接口;ourHandshake:在peer握手時(shí)發(fā)送的數(shù)據(jù),由于慣用并且不變因此寄存起來;lastLookup:用于控制去網(wǎng)絡(luò)中尋找新節(jié)點(diǎn)的頻率;DiscV5:輕節(jié)點(diǎn)LES使用的節(jié)點(diǎn)發(fā)現(xiàn)合同(未研究);其它組員基本都是內(nèi)部使用的channel,用于支持添加/刪除靜態(tài)節(jié)點(diǎn)、握手告知等;這里先果斷圈個(gè)重點(diǎn),peer.Server中有個(gè)很重要的組員ntab,以太坊使用Kademlia分布式路由存儲(chǔ)合同來進(jìn)行網(wǎng)絡(luò)拓?fù)渚S護(hù),其算法實(shí)現(xiàn)在ntabdiscoverTable中,discoverTable是一種提供Resolve(..)、Lookup(…)、ReadRandomNodes(…)功效的接口。先簡(jiǎn)樸介紹一下這種常見又巧妙Kademlia算法:Kademlia是一種分布式散列表(DHT)技術(shù),以異或運(yùn)算為距離(而不是物理距離)度量基礎(chǔ)。異或有一種重要的性質(zhì):假設(shè)a、b、c為任意三個(gè)數(shù),如果aXorb=aXorc成立,那就一定有b=c。因此,如果給定一種結(jié)點(diǎn)a和距離L,那就有且僅有一種結(jié)點(diǎn)b,會(huì)使得D(a,b)=L。通過這種方式,就能有效度量Kademlia網(wǎng)絡(luò)中不同節(jié)點(diǎn)之間的邏輯距離。Kademlia使用了名為K-桶的概念來儲(chǔ)存其它(臨近)節(jié)點(diǎn)的狀態(tài)信息,這里的狀態(tài)信息重要指的就是節(jié)點(diǎn)ID,IP,和端口。對(duì)于160bit的節(jié)點(diǎn)ID,就有160個(gè)K-桶,對(duì)于每一種K-桶i,它會(huì)儲(chǔ)存與自己距離在區(qū)間[2^i,2^(i+1))范疇內(nèi)的節(jié)點(diǎn)的信息,每個(gè)K-桶中儲(chǔ)存有k個(gè)其它節(jié)點(diǎn)的信息,每個(gè)節(jié)點(diǎn)根據(jù)與鄰居節(jié)點(diǎn)距離之間的距離(NodeID的差距),分別放到不同的桶(bucket)中。下表反映了每個(gè)K-桶所儲(chǔ)存的信息K-桶儲(chǔ)存的距離區(qū)間儲(chǔ)存的距離范疇儲(chǔ)存比率0[20,21)1100%1[21,22)2-3100%2[22,23)4-7100%3[23,24)8-15100%4[24,25)16-3175%5[25,26)32-6357%10[210,211)1024-204713%i[2i,2i+1)/0.75i-3每個(gè)節(jié)點(diǎn)都更傾向于儲(chǔ)存與自己距離近的節(jié)點(diǎn)的信息,形成
儲(chǔ)存的離自己近的節(jié)點(diǎn)多,儲(chǔ)存離自己遠(yuǎn)的節(jié)點(diǎn)少
的局面。從上表能夠看出,在1-15這個(gè)范疇內(nèi)的節(jié)點(diǎn),只要發(fā)現(xiàn),就會(huì)被100%地儲(chǔ)存下來,而離自己距離在1000左右的節(jié)點(diǎn),只會(huì)儲(chǔ)存13%.Kademila使用UDP進(jìn)行節(jié)點(diǎn)間消息通信,它定義了4種消息:*ping-用于探測(cè)其它節(jié)點(diǎn)與否還存在*store-接受者受到后,將信息中key/value對(duì)存儲(chǔ)在本節(jié)點(diǎn)*findnode-接受者向發(fā)送者返回k個(gè)它懂得的與目的結(jié)點(diǎn)距離近來的節(jié)點(diǎn)*findvalue-和findnode差不多,區(qū)別是如果接受者本地存在與目的結(jié)點(diǎn)對(duì)應(yīng)的value,那么就回復(fù)這個(gè)值給發(fā)送者。回到以太坊的Kademila實(shí)現(xiàn),重要圍繞3個(gè)文獻(xiàn)(p2p/dial.go、p2p/discover/udp.go、p2p/discover/table.go):dial.go:要與節(jié)點(diǎn)建立連接,首先得懂得這個(gè)節(jié)點(diǎn),查找、解析節(jié)點(diǎn)的發(fā)起者table.go:算法實(shí)現(xiàn)核心,查找結(jié)點(diǎn)、節(jié)點(diǎn)存活管理、使用距離管理k-桶即bucketsudp.go:底層UDP包的發(fā)送者,你告訴我要發(fā)什么包,我就發(fā)什么包在udp.go中定義了四種類型的數(shù)據(jù)包:pingPacket:用來查看節(jié)點(diǎn)與否存活;pongPacket:對(duì)PingPacket消息的響應(yīng);findnodePacket:節(jié)點(diǎn)查詢包,向目的節(jié)點(diǎn)詢問其臨近的節(jié)點(diǎn)列表NeighborsPacket:響應(yīng)findnodePacket,包中攜帶了此節(jié)點(diǎn)的附近節(jié)點(diǎn)K桶在table.go#Table構(gòu)造中:Table.buckets寄存了nBuckets(len(common.Hash{})*8/15=17)個(gè)桶,每個(gè)桶放16(bucketSize)個(gè)節(jié)點(diǎn),寄存的是Node指針類型。這個(gè)Node不是前面啟動(dòng)時(shí)的Node,正如前面介紹Kademlia算法提到的狀態(tài)信息,此Node(p2p/discover/node.go)包含IP、UDP/TCP端口、ID、sha(ID的hash值)。和本節(jié)點(diǎn)距離不大于239(bucketMinDistance)的節(jié)點(diǎn)都放到第0個(gè)桶,否則放到第(d-239-1)個(gè)桶:這里用于計(jì)算距離的指標(biāo)不是nodeId,而是“sha”,根據(jù)nodeId計(jì)算Keccak256Hash哈希值。邏輯距離的計(jì)算在logdist(…)函數(shù)中完畢:先看lzcount數(shù)組,它表達(dá)一種8bit字節(jié)取值從0~255,出現(xiàn)的第一種1前面有多少個(gè)0。如B00100000=D32,lzcount[32]=2。結(jié)合logdist(…)來看,將a、b按字節(jié)由低到高異或(common.Hash是[32]byte),當(dāng)找到第一種成果非0的byte,則lz累加異或成果的前置0的個(gè)數(shù)然后break,否則累加8。最后返回256-lz作為距離值。實(shí)現(xiàn)上是獲取異或成果的二進(jìn)制表達(dá)時(shí)的最高位1所在的位置(Really?)下面K-桶的維護(hù)工作理解一下。Table通過loop()中的for循環(huán)定時(shí)或外部調(diào)用Refresh和Revalidate操作,進(jìn)行路由表的刷新和檢查存活。Refresh:尋找新節(jié)點(diǎn)的過程有兩種方式,一是以本身的ID為目的尋找我附近的節(jié)點(diǎn),二是隨機(jī)找3個(gè)節(jié)點(diǎn)。注意調(diào)用定時(shí)doRefresh前會(huì)更新隨機(jī)種子,tab.seedRand(),盡量確保隨機(jī)的隨機(jī)性。兩種方式都是調(diào)用tab.lookup(…)進(jìn)行實(shí)際查找:(1).計(jì)算targetID的hash值,前面提到節(jié)點(diǎn)距離的計(jì)算是基于哈希的。result用于存儲(chǔ)需要查詢的節(jié)點(diǎn)(2).從本地桶中找16個(gè)近來的點(diǎn),放到result中。由于桶是按距離排序的,因此只需要依次掃描buckets(3).從result中依次取出要查詢的節(jié)點(diǎn),同時(shí)啟動(dòng)3(alpha)個(gè)findnode協(xié)程向節(jié)點(diǎn)發(fā)出查詢請(qǐng)求。由于查詢得到的成果會(huì)重新放入result,因此查詢過的節(jié)點(diǎn)會(huì)被放入asked,避免重復(fù)查詢。同理重復(fù)的節(jié)點(diǎn)也會(huì)被seen[]過濾掉。Table.findnode再進(jìn)一步調(diào)用udp.findnode向指定節(jié)點(diǎn)發(fā)送findnodePacket,查詢結(jié)束后Table.findnode把返回成果r中的node一一添加到對(duì)應(yīng)的桶中。如果node在桶中已存在,則把它移到頭部;不存在則添加到頭部。那么如果超出單桶大小的話會(huì)被放到Table.replacements中作為候補(bǔ)。Revalidate:(1).隨機(jī)挑選一種桶的最后一種節(jié)點(diǎn),發(fā)pingPacket探測(cè)存活(2).如果有返回響應(yīng),err為nil,則通過b.bump(…)把節(jié)點(diǎn)移到桶的頭部(3).如果無響應(yīng),則從Table.replacements中隨機(jī)找一種節(jié)點(diǎn)替代它,或者直接刪除。除了Table本身的定時(shí)刷新維護(hù),它還提供了其它功效的接口供上層調(diào)用,重要有:ReadRandomNode:取出K桶中的全部Node隨機(jī)選用出指定的NodesResolve(targetIDNodeID):查找指定的ID的節(jié)點(diǎn)Lookup(targetIDNodeID):查找指定的ID節(jié)點(diǎn)附近的節(jié)點(diǎn)到這里就出現(xiàn)個(gè)問題,就雞生蛋還是蛋生雞之,啟動(dòng)的時(shí)候本節(jié)點(diǎn)是一種節(jié)點(diǎn)都不懂的,那我該去問誰找其它節(jié)點(diǎn)?其實(shí)在params/bootnodes.go就已經(jīng)硬編碼了不同的網(wǎng)絡(luò)對(duì)應(yīng)的節(jié)點(diǎn),它們會(huì)在創(chuàng)立Table時(shí)就加到K-桶buckets里。例如主網(wǎng):如果是私鏈的話,能夠手動(dòng)添加節(jié)點(diǎn)。這里就不深究udp.go中的具體代碼,它所完畢的功效基本圍繞Ping/Pong、Findnode/Neighbors這4種請(qǐng)求,定義請(qǐng)求格式,然后完畢發(fā)包,收包,解包這些動(dòng)作。介紹了這樣久的底層P2P之底層節(jié)點(diǎn)發(fā)現(xiàn),我已經(jīng)等不及要回到p2p.Server.Start()把哦下收編到主流程里舒暢地串起來了。記得p2p.Server還是一種只賦值了Config和Protocols的早期構(gòu)造體,Start()函數(shù)首先就把構(gòu)造體里該有的東西都創(chuàng)立好:組員初始化:(1).Log賦值、置running標(biāo)志避免重入(2).newTransport是一種生成P2P通訊的傳輸層握手和數(shù)據(jù)傳輸解決對(duì)象的函數(shù),這里賦值為newRLPX,在后續(xù)調(diào)用中會(huì)生成rlpx對(duì)象(3).創(chuàng)立Dialer用于dialer.go#dialer建立連接,Dialer其實(shí)是封裝了net.Dialer的一種TCPDialer(4).創(chuàng)立quit/addpeer/…等通道用于后續(xù)的內(nèi)部數(shù)據(jù)傳遞和告知(5).啟動(dòng)UDP監(jiān)聽,解決節(jié)點(diǎn)發(fā)現(xiàn)請(qǐng)求(net.ListenUDP)(6).創(chuàng)立節(jié)點(diǎn)發(fā)現(xiàn)功效模塊Table,即srv.ntab(7).先懂得最多允許的動(dòng)態(tài)節(jié)點(diǎn)數(shù)目,默認(rèn)是MaxPeer/3。相對(duì)動(dòng)態(tài)節(jié)點(diǎn),即通過節(jié)點(diǎn)發(fā)現(xiàn)添加的節(jié)點(diǎn),尚有初始化時(shí)指定的靜態(tài)節(jié)點(diǎn),手動(dòng)添加的inbound節(jié)點(diǎn),和信任節(jié)點(diǎn),分別對(duì)應(yīng)有dynDialedConn、staticDialedConn、inboundConn和trustedConn標(biāo)志。然后創(chuàng)立一種任務(wù)調(diào)度器,一共調(diào)度3種任務(wù),dialTask(連接指定節(jié)點(diǎn))、discoverTask(隨機(jī)發(fā)現(xiàn)新節(jié)點(diǎn))、waitExpireTask(無節(jié)點(diǎn)可連時(shí)睡眠)(8).初始化ourHandsShake,包含版本、所支持的合同、節(jié)點(diǎn)ID(由節(jié)點(diǎn)公鑰生成)等信息,用于握手階段發(fā)給對(duì)節(jié)點(diǎn)。由于該值是靜態(tài)不變的,因此能夠預(yù)初始化好,背面只要取用便行peer.Server的運(yùn)行重要涉及到兩個(gè)協(xié)程,一種用于listen新連接,一種用于peer節(jié)點(diǎn)的握手、添加和刪除等功效。兩個(gè)協(xié)程互相協(xié)作動(dòng)態(tài)維護(hù)本節(jié)點(diǎn)的peer連接。它們是在peer.Service.Start()最后階段啟動(dòng)執(zhí)行的:Listen協(xié)程:在peer.Server.listenLoop()中對(duì)處在握手階段的最大節(jié)點(diǎn)數(shù)(max)做了控制,辦法是創(chuàng)立一種最多緩存max個(gè)請(qǐng)求的可阻塞channel,然后預(yù)先填滿,相稱于分派了max個(gè)指標(biāo)。然后每次for循環(huán)取一種指標(biāo),等待Accept()一種新連接,在握手完畢后把指標(biāo)釋放回去。如果指標(biāo)都被占用了則請(qǐng)求被阻塞。以下圖中源碼所示:現(xiàn)在我們就以這為起點(diǎn),假設(shè)已經(jīng)收到一種新的連接請(qǐng)求,Accept()成功。我們從#801行的srv.SetupConn(…)開始,看看接下來會(huì)發(fā)生什么。由上圖能夠看到新連接的解決過程是由listen協(xié)程和run協(xié)程協(xié)作完畢的。SetupConn(…)為新連接fd創(chuàng)立了新的conn,其組員transport即為peer.Server.Start()中賦值的srv.newTransport,即為rlpx對(duì)象然后進(jìn)入srv.setupConn(…)子函數(shù)開始握手,這里進(jìn)行了兩次握手:首先調(diào)用c.doEncHandshake(..)與peer交換密鑰,并生成臨時(shí)共享密鑰用于本次通信加密,還創(chuàng)立了一種幀解決器RLPXFrameRW然后調(diào)用srv.checkpoint(…),它所做的事就是把連接c發(fā)給第二個(gè)參數(shù)指定的通信,然后在c.cont通道上等待解決成果。這里發(fā)給srv.posthandshake,監(jiān)聽這個(gè)通道事務(wù)的正是run協(xié)程。在run中,判斷c與否在可信任節(jié)點(diǎn)列表中,在的話為其正身加上trustedConn,在之后的srv.encHandshakeChecks(..)的檢查中就不受最大允許peer數(shù)的條件限制了,最后將檢查成果(涉及peer數(shù)限制、與否已有連接等)返回給srv.checkpoint。Check無問題,則再進(jìn)行協(xié)商握手,發(fā)送srv.ourHandshake信息給對(duì)方,接受并解包得到對(duì)方的合同。這里通訊用的是RLPXFrameRW幀再次調(diào)用srv.checkpoint(…),這次是發(fā)送給srv.addpeer通道。在run端,先檢查和對(duì)方與否有共同支持的合同,沒有的話就要saybye。同樣再做一下peer限制數(shù)之類的檢查。都通過的話就能夠創(chuàng)立一種新的peer對(duì)象(注意這里攜帶的第二個(gè)參數(shù)srv.Protocols,在前面啟動(dòng)的時(shí)候我們懂得這個(gè)Protocols是全節(jié)點(diǎn)eth.Ethereum的ProtocolManager給它的,這樣就讓peer能夠懂得要解決哪些上層合同了),并調(diào)用srv.runPeer(p)讓它跑起來了。最后把這個(gè)新peer加到run內(nèi)部維護(hù)的peers列表里基本上listen協(xié)程所做的就是這些事情,接受連接,完畢兩次握手生成通訊的共享密鑰,最后得到一種新的peer并運(yùn)行起來。要讓peer跑起來,其實(shí)也是一件不簡(jiǎn)樸的事。runPeer(p*Peer)正常正常狀況p會(huì)始終運(yùn)行,直到連接關(guān)閉或異常告知有關(guān)人員有節(jié)點(diǎn)被移除告知有關(guān)人員有新節(jié)點(diǎn)SendPeerEventTypeDropSendPeerEventTypeAddp.run()gop.readLoop(readErr)gop.pingLoop()p.startProtocols(writeStart,writeErr)定時(shí)發(fā)送ping包的協(xié)程1解決ping請(qǐng)求,返回2解決連接斷開請(qǐng)求,返回3否則按MsgCode分發(fā)給對(duì)應(yīng)的合同數(shù)據(jù)包傳給對(duì)應(yīng)的合同通過API能夠在外部向peer.Server注冊(cè)有關(guān)peer事件的監(jiān)聽。這里先告知訂閱者添加了新節(jié)點(diǎn)調(diào)用p.run()啟動(dòng)協(xié)程從peer連接conn中讀取數(shù)據(jù)并解出msg消息,如果msg.Code表明是ping包則返回pong包,如果是斷開連接消息則再從rlp格式的包中解碼出斷開因素,而都不是的話就根據(jù)msg.Code找到對(duì)應(yīng)的合同分發(fā)出去啟動(dòng)另一種協(xié)程每隔15s向peer發(fā)送一種ping包保持活躍對(duì)于peer支持的每一種合同都啟動(dòng)一種go協(xié)程運(yùn)行(proto.Run(p,rw))如果p.run()返回的話闡明節(jié)點(diǎn)已經(jīng)停止運(yùn)行且失效,同樣告知訂閱者節(jié)點(diǎn)移除事件上圖有兩個(gè)坑沒填,一是在p.readLoop(…)中是怎么找到對(duì)應(yīng)的合同去分發(fā)的?二是p.startProtocols(…)是怎么執(zhí)行自己的合同的?我們一一來填。合同分發(fā)問題在p2p.Server的run協(xié)程從<-addpeer通道收到新連接創(chuàng)立newPeer(conn*conn,protocols[]Protocol)的時(shí)候,會(huì)從conn中取出對(duì)端節(jié)點(diǎn)所支持的合同,和本節(jié)點(diǎn)所支持的合同做交叉比較,共同支持的合同會(huì)放到一種合同映射里,以合同名為鍵,以一種protoRW{…}對(duì)象為值:一種Protocol對(duì)象所包含的內(nèi)容有合同名稱、合同版本,尚有一種屬性是合同長(zhǎng)度,也就是它全部支持的命令的總個(gè)數(shù)。例如說合同A支持10種命令,合同B支持5種命令,那么我們把全部命令排個(gè)序并編號(hào)0~14,從0開始,A的offset是0,0~9是合同A的命令,B的offset是0+10=10,10~14是合同B的命令,如果收到一種msg的code是11,那么它對(duì)應(yīng)合同B,在傳給B合同前會(huì)先11-offset=11-10=1,再給B,確保給B的Code是按B合同定義的命令值。由于合同是雙方都支持的,并且按合同名和版本號(hào)做了排序,因此能夠確保通信雙方使用的合同和offset的一致性?,F(xiàn)在就不難理解尋找匹配合同的函數(shù)getProto(…)的實(shí)現(xiàn)了:找到合同后再發(fā)給對(duì)應(yīng)的合同解決協(xié)程:這里的proto.in,其實(shí)關(guān)聯(lián)到p.startProtocol(…)的合同解決流程,為其提供數(shù)據(jù)包:當(dāng)合同調(diào)用ReadMsg()從peer中取數(shù)據(jù)時(shí),就是從in通道取數(shù)據(jù),由readLoop()把數(shù)據(jù)包分發(fā)到in。正如前面所說,msg.Code-=offset更新為合同對(duì)應(yīng)的Code。p.startProtocols(…)執(zhí)行流程循環(huán)解決每一種合同proto即上面創(chuàng)立的protoRW,轉(zhuǎn)換成MsgReadWriter接口rw,為合同解決提供數(shù)據(jù)來源,提供應(yīng)proto的Run函數(shù)啟動(dòng)新協(xié)程運(yùn)行proto以ETH合同為例,回想一下,在程序啟動(dòng)的時(shí)候,我們創(chuàng)立了eth.Ethereum,它的一種很重要的組員,ProtocolManager,也隨之創(chuàng)立好了,同時(shí)也定義好了它所支持運(yùn)行的合同:找到里面定義的Run函數(shù),首先通過newPeer包裝了一下p2p.peer,變成了eth.peer,加入某些區(qū)塊和交易有關(guān)的信息;然后--->將新peer發(fā)到manager.newPeerCh(在eth/sync.go中進(jìn)行同時(shí),后續(xù)再提)--->進(jìn)入manager.handle(peer)正式開始ETH合同的解決獲取獲取本節(jié)點(diǎn)區(qū)塊鏈的創(chuàng)世塊genesis、現(xiàn)在區(qū)塊的hash、number、td發(fā)送一種狀態(tài)包【protocolVersion,networkID,td,hash,genesis哈?!坑糜谖帐?,并等待返回得到它的TD和head哈希把peer添加到ProtocolManager管理的peer集里,并啟動(dòng)一種新協(xié)程,向peer告知新交易、新區(qū)塊、新區(qū)塊聲明把peer注冊(cè)到ProtocolManager的下載器Downloader,以成為同時(shí)候選者把本節(jié)點(diǎn)交易池中未打包的正當(dāng)交易發(fā)送給peer循環(huán)調(diào)用pm.handleMsg(p)解決peer發(fā)過來的請(qǐng)求從上述流程里能夠提取幾個(gè)核心點(diǎn),一是創(chuàng)世塊、TD(totaldifficulty)、header,二是downloader,三是handleMsg。這涉及到一種對(duì)的的p2p網(wǎng)絡(luò)怎么做對(duì)的的事。首先創(chuàng)世塊相似才干確保我們是在同一條鏈上對(duì)話,另首先我們需要信任和互相溝通,如果你有新的交易和新的區(qū)塊告訴我,同樣我也會(huì)告訴你,同時(shí)我會(huì)響應(yīng)你的塊請(qǐng)求、頭部請(qǐng)求等合同范疇內(nèi)的多種請(qǐng)求,這樣才干達(dá)成共同維護(hù)一本權(quán)威賬本的目的。因此再回頭看上面的流程,先通過握手交換創(chuàng)世塊和所知最新塊的信息,然后把新peer加到列表里同時(shí)啟動(dòng)協(xié)程,只要有新的交易和區(qū)塊便發(fā)送給它。如果你的區(qū)塊鏈更新,則通過Downloader進(jìn)行同時(shí)。最后循環(huán)解決peer的請(qǐng)求和返回。在eth/protocol.go中定義了ETH合同支持的全部命令://Protocolmessagesbelongingtoeth/62舊版本StatusMsg=0x00ETH握手時(shí)發(fā)送的狀態(tài)消息NewBlockHashesMsg=0x01peer聲明它有新的區(qū)塊Hash值TxMsg=0x02新的交易GetBlockHeadersMsg=0x03獲取區(qū)塊頭部消息BlockHeadersMsg=0x04區(qū)塊頭部響應(yīng)消息GetBlockBodiesMsg=0x05獲取區(qū)塊體消息BlockBodiesMsg=0x06區(qū)塊體響應(yīng)消息NewBlockMsg=0x07peer發(fā)送新的區(qū)塊消息//Protocolmessagesbelongingtoeth/63新版本,兼容舊版本命令GetNodeDataMsg=0x0d獲取trienode消息NodeDataMsg=0x0e響應(yīng)trienode消息GetReceiptsMsg=0x0f獲取收據(jù)消息ReceiptsMsg=0x10響應(yīng)收據(jù)消息這些消息都會(huì)在handleMsg(…)中被一一解決。流程走到現(xiàn)在,我們已經(jīng)啟動(dòng)了全節(jié)點(diǎn)和p2p網(wǎng)絡(luò),能夠接受外部節(jié)點(diǎn)的連接請(qǐng)求并握手建立連接,解決ETH合同通訊。但是,如果沒有節(jié)點(diǎn)主動(dòng)發(fā)起連接請(qǐng)求,也就是Listen協(xié)程未收到請(qǐng)求呢?要加入到p2p網(wǎng)絡(luò)中我們就需要主動(dòng)連接節(jié)點(diǎn),除了預(yù)設(shè)的啟動(dòng)節(jié)點(diǎn),我們還要主動(dòng)發(fā)現(xiàn)新節(jié)點(diǎn)加入進(jìn)來,連接靜態(tài)節(jié)點(diǎn),這就是run協(xié)程的任務(wù)。終于輪到run協(xié)程正式出場(chǎng),它的功效就相對(duì)復(fù)雜某些(如果撇開在listen協(xié)程里生成密鑰的復(fù)雜算法過程的話)。我們從頭看起。func(srv*Server)run(dialstatedialer)run的函數(shù)簽名中唯一參數(shù)是p2p.dialer類型的dialstate,dialer是一種接口類型,實(shí)現(xiàn)是p2p.dialstate,在p2p.Server.Start()中初始化。能夠說run的功效基本上是圍繞這個(gè)dialerstate進(jìn)行的。那么,dialerstate長(zhǎng)啥樣?官方解釋說,dialstate用于調(diào)度撥號(hào)和節(jié)點(diǎn)發(fā)現(xiàn)任務(wù),在p2p.Server.run的每次主循環(huán)里被調(diào)用來生成新任務(wù)。看它的構(gòu)造:maxDynDials:最大允許的動(dòng)態(tài)節(jié)點(diǎn)數(shù),在創(chuàng)立時(shí)指定,maxPeer/3ntab:實(shí)現(xiàn)Kademlia算法discover.Table對(duì)象netrestrict:IP黑名單,對(duì)于該名單中的IP不進(jìn)行連接lookupRunning:確保任何時(shí)刻只有一種節(jié)點(diǎn)發(fā)現(xiàn)任務(wù)在執(zhí)行dialing:保存撥號(hào)中的IP,避免二次撥號(hào),連接成功后會(huì)從dialing里去除lookupBuf:保存發(fā)現(xiàn)的新節(jié)點(diǎn)randomNodes:在最大動(dòng)態(tài)節(jié)點(diǎn)數(shù)沒填滿時(shí),從ntab中隨機(jī)讀取節(jié)點(diǎn)來填充static:靜態(tài)節(jié)點(diǎn)列表,必然會(huì)連接的節(jié)點(diǎn)hist:節(jié)點(diǎn)連接成功后來會(huì)加到歷史列表中,30s后過期刪除bootnodes:當(dāng)沒有可用的peer時(shí),使用bootnodes中的節(jié)點(diǎn)它包含maxDynDials控制動(dòng)態(tài)peer數(shù),ntab用于在沒有節(jié)點(diǎn)可用時(shí)去發(fā)現(xiàn)節(jié)點(diǎn),和節(jié)點(diǎn)未知時(shí)發(fā)起查詢,有static和bootnodes收納靜態(tài)節(jié)點(diǎn)和備用節(jié)點(diǎn)。上面提到run會(huì)調(diào)用dialstate生成新任務(wù),調(diào)用的就是newTasks(…)[]task函數(shù),它會(huì)根據(jù)需要決定是不是要生成新任務(wù)、生成哪些任務(wù)。它共分派三種任務(wù):task是接口,dialTask、discoverTask和waitExpireTask都實(shí)現(xiàn)了task的Do(*Server)函數(shù)。dialTask:和指定節(jié)點(diǎn)建立連接的任務(wù)discoverTask:存量節(jié)點(diǎn)局限性時(shí)執(zhí)行的節(jié)點(diǎn)發(fā)現(xiàn)任務(wù)waitExpireTask:超時(shí)任務(wù)newTasks(…)重要關(guān)注三點(diǎn),一是靜態(tài)節(jié)點(diǎn),二是最大動(dòng)態(tài)節(jié)點(diǎn)數(shù),三是節(jié)點(diǎn)要符合連接條件。全部靜態(tài)節(jié)點(diǎn)都要默認(rèn)連接;另首先,它會(huì)盡量多地去連接動(dòng)態(tài)節(jié)點(diǎn),這是函數(shù)的重要工作;最后已連接或是連接中的、IP黑名單的、剛連過并出現(xiàn)在歷史統(tǒng)計(jì)里的節(jié)點(diǎn)都不嘗試連接。展開分析一下函數(shù)是怎么竭力連接多的動(dòng)態(tài)節(jié)點(diǎn)的:需要?jiǎng)討B(tài)連接的總數(shù)=配備的最大動(dòng)態(tài)節(jié)點(diǎn)數(shù)–已連接動(dòng)態(tài)節(jié)點(diǎn)數(shù)–正在連接動(dòng)態(tài)節(jié)點(diǎn)數(shù)needDynDials=s.maxDynDials–peers.rw.is(dynDialedConn)–s.dialing.flag&dynDialedConn如果現(xiàn)在一種peer都沒有且配備了bootnodes,就取出一種來連接,needDynDials減1剩余的needDynDials/2從ntab(即discover.Table)中隨機(jī)取s.maxDynDials/2個(gè)節(jié)點(diǎn)來連接,竭力填充如果還不夠則再從s.lookupBuf(存儲(chǔ)了節(jié)點(diǎn)發(fā)現(xiàn)的新節(jié)點(diǎn))取節(jié)點(diǎn)來連接,竭力填充如果還沒填滿,闡明節(jié)點(diǎn)發(fā)現(xiàn)給的節(jié)點(diǎn)還不夠,就分派discoverTask任務(wù)去發(fā)現(xiàn)更多新節(jié)點(diǎn)如果我們這樣努力了,卻連一種可連接的涉及靜態(tài)的節(jié)點(diǎn)都沒有,但是有歷史連接紀(jì)錄,那就分派一種waitExpireTasks任務(wù)等最老的節(jié)點(diǎn)在歷史紀(jì)錄里過期,再進(jìn)行下一輪嘗試。這里的每個(gè)連接都會(huì)分派一種dialTask任務(wù)。當(dāng)沒有足夠的新節(jié)點(diǎn)時(shí)分派discoverTask任務(wù)調(diào)用ntab隨機(jī)尋找新節(jié)點(diǎn)。而當(dāng)p.Server.run中既沒有舊任務(wù),現(xiàn)在也沒有節(jié)點(diǎn)可連接時(shí),就分派一種waitExpireTasks任務(wù)等待節(jié)點(diǎn)歷史紀(jì)錄過期再嘗試,由于在歷史紀(jì)錄中的節(jié)點(diǎn)也不會(huì)去嘗試連接。說完newTasks函數(shù),再回到p.Server.run協(xié)程看它的主循環(huán),就明朗多了:主循環(huán)每次開始先調(diào)用scheduleTasks()執(zhí)行和分派新任務(wù)從queuedTasks中取出第一種任務(wù)并執(zhí)行,任務(wù)啟動(dòng)后放入runningTask隊(duì)列中,并在任務(wù)結(jié)束后告知taskdone通道以解決任務(wù)成果未超出運(yùn)行中任務(wù)總數(shù)限制的話,調(diào)用dialstate.newTasks分派新任務(wù)放到queuedTasks隊(duì)列接受來自API調(diào)用通道的請(qǐng)求解決添加靜態(tài)節(jié)點(diǎn)、刪除靜態(tài)節(jié)點(diǎn)、獲取peers信息請(qǐng)求任務(wù)完畢后回調(diào)dialstate.taskDone,對(duì)于dialTask會(huì)把節(jié)點(diǎn)加到歷史紀(jì)錄,discoverTask會(huì)把新節(jié)點(diǎn)加到lookupBuf中,waitExpireTasks則什么也不做posthandshake、addpeer通道用于和Listen協(xié)程一起完畢新節(jié)點(diǎn)的握手和創(chuàng)立delpeer用于接受peer停止運(yùn)行后發(fā)出的停止信息通過run協(xié)程的for循環(huán),本節(jié)點(diǎn)得以在P2P網(wǎng)絡(luò)中維持動(dòng)態(tài)變化的關(guān)聯(lián)網(wǎng)。構(gòu)想有一種動(dòng)態(tài)連接的節(jié)點(diǎn)下線了,連接斷開會(huì)告知到srv.delpeer通道,該節(jié)點(diǎn)會(huì)從維護(hù)的peers列表中刪除,這樣再調(diào)用dialstate.newTasks的時(shí)候會(huì)多出一種動(dòng)態(tài)節(jié)點(diǎn)名額,然后通過存量或發(fā)現(xiàn)新節(jié)點(diǎn)去連接一種新的節(jié)點(diǎn)。最后一種問題,dialTask是如何把節(jié)點(diǎn)加入到系統(tǒng)中的?答案就在p2p.dialTask.dial(….)函數(shù)中:如代碼所言,在調(diào)用srv.Dialer.Dial(..)初始化TCP連接后,調(diào)用到srv.SetupConn(…),剩余的流程就和Listen協(xié)程監(jiān)聽新連接的后續(xù)解決同樣,handshake->posthandshake->addpeer…..花了很大的篇幅,我們從p2p.Service的Start()函數(shù)入手,講究竟層用于節(jié)點(diǎn)發(fā)現(xiàn)和查詢的discover.Table和它使用的Kademlia算法及輔助的通訊功效discover.udp,再進(jìn)到p2p.Server運(yùn)行的兩大主協(xié)程listen和run,分別負(fù)責(zé)監(jiān)聽新節(jié)點(diǎn)的連接和通過動(dòng)態(tài)連接新節(jié)點(diǎn),run又是怎么使用dialstate任務(wù)分派功效維護(hù)網(wǎng)絡(luò)。另外我們還提到節(jié)點(diǎn)在連接時(shí)有關(guān)檢查和密鑰握手、合同握手,生成RLPXFrame,在基礎(chǔ)連接完畢后進(jìn)一步完畢上層ETH合同的握手和重要數(shù)據(jù)獲取,再之后數(shù)據(jù)流向有關(guān)合同進(jìn)行解決。一種可運(yùn)行可溝通可動(dòng)態(tài)維護(hù)的P2P網(wǎng)絡(luò)已經(jīng)搭建好了,現(xiàn)在我們需要在網(wǎng)絡(luò)上面加入數(shù)據(jù)讓它變得真正故意義。根據(jù)不同合同ETH或LES(輕以太坊子合同,lightethereumsub-protocol)的不同網(wǎng)絡(luò)上流通的包會(huì)略有不同,但是都是和交易和區(qū)塊有關(guān)的。交易是以太坊的基本元素,區(qū)塊是交易的集合,而區(qū)塊需要由礦工通過挖礦才干被添加到賬本里,礦工需要從內(nèi)存中臨時(shí)存儲(chǔ)交易的交易池中取出交易打包,并且交易池對(duì)交易的管理和驗(yàn)證也是很重要的功效。因此下一節(jié)我們就從交易池出發(fā),關(guān)聯(lián)到挖礦功效,繼續(xù)分析以太坊源碼。交易池和挖礦交易、交易池和挖礦之間的關(guān)系大致以下圖所示:挖礦挖礦模塊Peer傳過來的交易本地發(fā)起的交易交易池交易池在啟動(dòng)流程里,我們創(chuàng)立了eth.Ethereum,提到過它有一種很重要的組員就是交易池TxPool。交易池涉及了現(xiàn)在已知的交易,它收集從網(wǎng)絡(luò)中發(fā)過來的(遠(yuǎn)程交易)、本地提交的交易,進(jìn)行驗(yàn)證、過濾和排序,對(duì)遠(yuǎn)程交易和本地交易使用的檢查條件也是有差別的。然后根據(jù)檢查成果放到內(nèi)部不同的隊(duì)列中,供挖礦模塊使用。挖礦模塊也能夠通過在交易池中定義特定的交易過濾條件,只打包它想要的交易。我們還是從交易池的定義開始(core/tx_pool.go#184):config:交易池的有關(guān)配備,重要是與否要持久化本地發(fā)起的交易、交易gasPrice的下限、每個(gè)賬戶允許寄存的交易個(gè)數(shù)和總的寄存?zhèn)€數(shù)以控制池中的交易總量、遠(yuǎn)程交易的過期時(shí)間等;chainconfig:鏈配備信息chain:鏈接口,用于獲取鏈有關(guān)信息gasPrice:允許進(jìn)入交易池的最小gasPrice,初始化為config.PriceLimit,可通過API更改txFeed/scope:用于告知外部訂閱者有新交易的訂閱告知工具chainHeadCh/chainHeadSub:向BlockChain注冊(cè)監(jiān)聽新區(qū)塊頭事件signer:簽名工具,重要用于從交易中解出發(fā)送者的地址currentState:現(xiàn)在的賬戶狀態(tài),涉及賬戶余額和代表該賬戶已交易次數(shù)的NoncependingState:在currentState.Nonce基礎(chǔ)上為每筆可執(zhí)行交易遞增分派一種虛擬的Nonce,稱它為虛擬是由于Nonce并不更新到賬戶狀態(tài)里,只用于臨時(shí)計(jì)算currentMaxGas:每筆交易的gas限制locals:存儲(chǔ)每個(gè)本地賬戶發(fā)起的交易journals:用于本地交易持久化到磁盤pending:可執(zhí)行交易,礦工從這里取交易打包,每個(gè)發(fā)送者有自己的交易列表,方便管理queue:已接受但臨時(shí)不可執(zhí)行的交易,同樣每個(gè)發(fā)送者有自己的交易列表beats:遠(yuǎn)程賬戶的近來連接時(shí)間all:按[key:交易Hash=>value:交易]的map方式存儲(chǔ)全部交易priced:包含all中全部交易,但是這是按gasPrice由低到高排序,txPricedList內(nèi)部使用小根堆存儲(chǔ)homestead:與否為Homestead版本,homestead版本創(chuàng)立合約需要53000單位的gas,否則只要21000eth.Ethereum調(diào)用NewTxPool(configTxPoolConfig,chainconfig*params.ChainConfig,chainblockChain)創(chuàng)立一種新的*TxPool,根據(jù)入?yún)褍?nèi)部的組員都初始化好,向BlockChain訂閱ChainHeadEvent事件。如果節(jié)點(diǎn)可接受本地交易,且啟動(dòng)了journal選項(xiàng),則從本地加載保存著的未打包的交易到池里。最后啟動(dòng)新協(xié)程執(zhí)行l(wèi)oop(),解決下列事件:監(jiān)聽解決pool.chainHeadCh(由BlockChain告知的新區(qū)塊頭事件,交易池會(huì)刪掉新區(qū)塊中的交易,由于它們已經(jīng)被打包執(zhí)行過,不需要再打包執(zhí)行)每過一分鐘就去除池中過期的不可執(zhí)行的遠(yuǎn)程交易,過期時(shí)間默認(rèn)為3小時(shí),可配每隔一小時(shí)(默認(rèn),可配)就把本地交易刷新到j(luò)ournal文獻(xiàn)每8秒通過日志輸出交易池的狀態(tài)。我們先從一種簡(jiǎn)樸的流程,新交易怎么進(jìn)到交易池,分析交易池的功效要點(diǎn)。交易池包含兩種交易,一是通過網(wǎng)絡(luò)中其它peer給的,另一種是通過web3.eth.SendTransaction或者JONS-RPC調(diào)用eth_sendTransaction發(fā)起一種本地交易。在上一節(jié)P2P網(wǎng)絡(luò)的基礎(chǔ)上,我們先分析第一種方式,其它Peer發(fā)送過來的方式。上一節(jié)最后分析到ETH合同,在握手和初始的同時(shí)之后調(diào)用handleMsg(p*peer)解決請(qǐng)求,還提到有一種請(qǐng)求叫“TxMsg”,這就是peer發(fā)過來的包含新交易的數(shù)據(jù)包。我們進(jìn)到handleMsg的這個(gè)解決分支去看看:從peer中讀出新消息判斷是新交易告知類型,Decode出交易集合把全部交易加到peer的已知交易列表中,這樣本節(jié)點(diǎn)再Relay該交易的時(shí)候就不會(huì)再發(fā)回給它了調(diào)用txpool.AddRemotes(txs)把全部交易加入交易池AddRemotes(txs)AddRemotes(txs)addTxs(txs,false)AddTxsLocked(txs,local)add(tx,local)txs中每個(gè)tx依次調(diào)用promoteExecutables(addr)通過層層調(diào)用先來到AddTxsLocked。它最后分別循環(huán)調(diào)用add(tx,local),然后調(diào)用promoteExecutables(addr)。我們剛介紹過TxPool的構(gòu)造,它包含pending寄存可執(zhí)行的交易、queue寄存臨時(shí)不可執(zhí)行的交易。而只有pending中的交易才會(huì)被拿來打包出塊。首先由add(tx,local)函數(shù)將交易放到queue隊(duì)列,然后由promoteExecutables把queue中的交易放到pending中。注意到local標(biāo)志tx與否為本地交易,從addTxs傳遞下來為false,表明它是遠(yuǎn)程交易。在add的交易檢查過程中會(huì)對(duì)本地tx體現(xiàn)出區(qū)別看待。當(dāng)add完畢5、6步的時(shí)候交易就被成功添加到交易池里了,在這之前要針對(duì)交易的gas、gasPrice、Nonce全方面檢查。這里提一下第5步中的pool.queue,它是一種map[common.Address]*txList構(gòu)造,意味著每個(gè)交易發(fā)送者都有自己獨(dú)自的交易列表,txList內(nèi)部由txSortedMap來管理Nonce和關(guān)聯(lián)交易,體現(xiàn)它的兩個(gè)組員上:map[uint64]*types.Transaction,映射了Nonce和交易的關(guān)聯(lián)性index*nonceHeap,根據(jù)Nonce排序的小根堆這樣可方便地找出Nonce低于某值的交易,方便過濾?,F(xiàn)在再分析promoteExecutables函數(shù),記住它要做的是把能夠執(zhí)行的交易從pool.queue移到pool.pending中:在權(quán)衡維護(hù)交易池中pending的交易總數(shù)在可控范疇內(nèi),和竭力保全pending交易之間,交易池可謂是做了很精細(xì)的考量。對(duì)pending中交易總數(shù)的控制有兩級(jí),一級(jí)全局的總交易數(shù)控制,另一級(jí)是單個(gè)賬戶的交易總數(shù)控制。在不超出全局交易總數(shù)時(shí)單賬戶的交易數(shù)不受限,超出的話就會(huì)以賬戶級(jí)限制為閾值進(jìn)行交易的方略性丟棄。丟棄也是分級(jí)的,首先丟棄擁有最多交易的地址的高Nonce交易,向次多交易的數(shù)量靠攏;然后把賬戶的交易數(shù)控制在單賬戶限制下列;最后按地址的交易時(shí)間,批量刪除老地址的交易,直到pending中的總交易數(shù)低于全局總交易數(shù)限制。本地發(fā)起的交易在交易池中的解決流程差不多,只但是是調(diào)用入口是AddLocal(…),置local標(biāo)志位,在清理交易階段就能夠在安全區(qū)穩(wěn)穩(wěn)地呆著。交易池準(zhǔn)備好了,就能夠開始挖礦了。挖礦在考慮挖礦前,我們會(huì)想懂得它是怎么和程序啟動(dòng)流程、交易池串通起來的呢。從啟動(dòng)階段main.startNode(…)函數(shù)的最后,在P2P、Ethereum、RPC等服務(wù)都啟動(dòng)成功后,會(huì)判斷命令行標(biāo)志,如打開了挖礦功效或處在開發(fā)模式,則啟動(dòng)挖礦(正常主網(wǎng)都是通過終端交互啟動(dòng)挖礦,在我布署的私網(wǎng)是通過啟動(dòng)時(shí)打開挖礦開關(guān)啟動(dòng)挖礦的,且使用的CPU挖礦,因此走的是下列流程):mmain.startNode()Ethereum.StartMing(true)goethereum.miner.Start(eb)miner.worker.start()mitNewWork()通過main->Ethereum->miner一路進(jìn)入到miner模塊。挖礦工作由Miner、worker和Agent共同完畢。Miner是上層管理,它接受外部發(fā)來的請(qǐng)求,啟動(dòng)或停止挖礦、設(shè)立coinbase,保存變化并傳遞給內(nèi)部worker。而worker才是負(fù)責(zé)具體挖礦工作的對(duì)象,它的工作核心是一組agents,由Agent完畢最后的出塊工作。另外worker尚有一種Work表達(dá)現(xiàn)在挖塊的工作環(huán)境,涉及現(xiàn)在待挖的Block、賬戶狀態(tài)、交易集合、收據(jù)等,但跟Agent只關(guān)心和共識(shí)有關(guān)的Block。Agent接口有兩個(gè)實(shí)現(xiàn):CpuAgent和RemoteAgent。在我的測(cè)試機(jī)上使用的是CpuAgent,只有在礦機(jī)上才使用RemoteAgent。在worker和agent都是在創(chuàng)立miner時(shí)由miner創(chuàng)立,并把a(bǔ)gent注冊(cè)到worker中:miner的update()循環(huán)始終監(jiān)聽模塊的同時(shí)狀態(tài),如果正在同時(shí)則停止挖礦,否則同時(shí)正常或異常結(jié)束再啟動(dòng)挖礦。啟動(dòng)挖礦由miner發(fā)起,miner.Start()–>worker.start()->agent.start(),啟動(dòng)Agent從worker與agent的通信管道中不停取原始?jí)K并出新的正當(dāng)塊。具體的打包挖礦功效在mitNewWork()函數(shù)中,也是由miner.Start()調(diào)用,我們重點(diǎn)關(guān)注這個(gè)函數(shù)的有關(guān)功效:實(shí)際的共識(shí)挖礦工作由worker、agent、engine三部分完畢,以太坊正式環(huán)境使用的是類似PoW的ethash共識(shí)引擎:Worker:從交易池取出并執(zhí)行可執(zhí)行交易,然后生成一種包含頭部、叔塊、收據(jù)、狀態(tài)和交易的原始?jí)K,把它交給agents解決,得到一種共識(shí)塊并傳輸?shù)狡渌黳eer節(jié)點(diǎn)和內(nèi)部的其它功效模塊中Agent:以CpuAgent為例,在收到workerpush過來的新Work后,它會(huì)取出Work.Block交給共識(shí)引擎執(zhí)行Seal函數(shù),并得到一種共識(shí)塊返回給worker(如果是remoteAgent的話Seal過程在遠(yuǎn)程礦機(jī)上執(zhí)行,礦機(jī)會(huì)調(diào)用API遠(yuǎn)程從以太坊上獲得新Work和挖礦有關(guān)參數(shù)進(jìn)行挖礦)Ethash:共識(shí)引擎,目的是為了找到一對(duì)Nonce和MixHash填充到Block.Header中,這一對(duì)隨機(jī)數(shù)滿足這樣一種條件:把Block.Header中的父區(qū)塊Hash、叔塊Hash、coinbase、賬戶狀態(tài)根等域使用rlp編碼得到一串二進(jìn)制數(shù)據(jù),再生成Keccak256哈希值,寫為hash得到現(xiàn)在塊的難度值Difficulty(在ethash計(jì)算前的初始化中已經(jīng)通過父區(qū)塊的時(shí)間戳和難度值計(jì)算過),2^256/Difficulty得到一種target值把hash和一種隨機(jī)nonce值放入hashimotoFull(..)進(jìn)行計(jì)算,得到一種MixDigest和result,result需要小等于b中的target值。這樣我們就有了符合工作量證明的Nonce和mixDigest,填到原始?jí)K的對(duì)應(yīng)字段就成了一種被網(wǎng)絡(luò)承認(rèn)的正當(dāng)塊了為了找到這一對(duì)隨機(jī)值,ethash.Seal創(chuàng)立多個(gè)協(xié)程,每個(gè)協(xié)程使用一種隨機(jī)值作為起始種子,不停通過加1作為新值去嘗試hashimotoFull計(jì)算成果與否小等于mitNewWork函數(shù)的第6步會(huì)盡量執(zhí)行交易池中的每一筆可執(zhí)行交易,執(zhí)行過的交易會(huì)被打包進(jìn)新的區(qū)塊。因此區(qū)塊中的交易數(shù)量受限于塊的GasLimit。它是結(jié)合父區(qū)塊的gasLimit和它用掉的gas通過CalcGasLimit()計(jì)算調(diào)節(jié),如果使用的parent.UsedGas>parent.GasLimit*2/3,則增加新塊的GasLimit,否則減小。交易的執(zhí)行由commitTransaction(…)完畢,它調(diào)用core.ApplyTransaction(…)具體執(zhí)行交易,得到交易的收據(jù),保存起來。core.ApplyTransaction()的實(shí)現(xiàn)涉及到以太坊虛擬機(jī)EVM,我們通過它的內(nèi)部執(zhí)行過程先對(duì)EVM預(yù)熱,具體執(zhí)行過程將進(jìn)入下一節(jié)進(jìn)行專門分析。由上圖可知交易的執(zhí)行是在轉(zhuǎn)換成消息類型后進(jìn)行虛擬機(jī)中完畢。由于EVM也接受合約與合約之間的消息調(diào)用,因此交易也被轉(zhuǎn)換成消息統(tǒng)一解決。交易完畢后賬戶狀態(tài)發(fā)生變化,并且可能在合約中產(chǎn)生事件日志,這些都被統(tǒng)計(jì)在收據(jù)Receipt里,方便后來查詢。Receipt也有一種Bloom對(duì)象,它Block的Bloom一起構(gòu)成Bloom多級(jí)構(gòu)造,和塊中其它收據(jù)的Bloom位一起合并到塊Bloom位中,這樣想要查找某個(gè)塊中與否包含某條日志時(shí),只要把查詢目的轉(zhuǎn)換成Bloom位,再核對(duì)塊Bloom的對(duì)應(yīng)位與否置位就能快速地判斷該塊是不是可能包含對(duì)應(yīng)日志。有的話再進(jìn)一步核對(duì)塊中Receipt的Bloom位與否有對(duì)應(yīng)的置位,這樣一步步分級(jí)按位查找,大大加緊了日志的查詢?,F(xiàn)在,我們立即進(jìn)入EVM看看大名鼎鼎的以太坊虛擬機(jī)是怎么執(zhí)行消息(交易)的。EthereumVirtualMachine(EVM)以智能合約為標(biāo)志性更新,以太坊把區(qū)塊鏈帶入2.0時(shí)代。使用智能合約高級(jí)語言Solidity編寫合約代碼,編譯得到元數(shù)據(jù)后布署到以太坊上,后來只要通過調(diào)用合約接口就能夠執(zhí)行合約功效。每個(gè)合約都對(duì)應(yīng)一種合約賬戶,它和普通外部賬戶同樣有以太坊余額,只但是合約賬戶擁有有關(guān)聯(lián)的代碼,代碼的執(zhí)行是通過交易或其它合約的call調(diào)用來激活。合約的執(zhí)行與調(diào)用通過底層的EVM模塊完畢。上一節(jié)最后我們提到交易的執(zhí)行是先把它轉(zhuǎn)換成Message,再由EVM執(zhí)行。我們先一探這兩個(gè)構(gòu)造體。Message定義在core/type/transaction.go#381:結(jié)合AsMessage(..)函數(shù),Transaction交易中的信息被填充到Message中,涉及nonce(跟生成合約地址有關(guān)),gasLimit、gasPrice(gas局限性交易將失敗),to(轉(zhuǎn)賬目的地址),amount(轉(zhuǎn)賬ether數(shù)量),data(創(chuàng)立合約時(shí)為合約代碼,調(diào)用合約時(shí)為合約參數(shù)),checkNonce(true意味著執(zhí)行前先要檢查nonce與賬戶的Nonce一致),from(從簽名中提取出發(fā)送者地址)。Message精簡(jiǎn)地從Tranasction提取出執(zhí)行需要的信息。再來看EVM構(gòu)造:Context:上下文執(zhí)行環(huán)境,提供blockchain有關(guān)信息**********交易執(zhí)行有關(guān)**********CanTransfer:判斷賬戶余額與否足夠的函數(shù)Transfer:實(shí)際轉(zhuǎn)賬函數(shù)GetHash:計(jì)算哈希值函數(shù)Origin:消息發(fā)送者GasPrice:交易設(shè)立的gasPrice*********鏈有關(guān)***********Coinbase:挖礦的收益地址,也就是交易手續(xù)費(fèi)的收益地址GasLimit:區(qū)塊的GasLimitBlockNumber:區(qū)塊鏈高度Time:區(qū)塊頭時(shí)間戳Diffuculty:區(qū)塊難度值StateDB:賬戶trie樹depth:EVM調(diào)用棧深度,不超出1024vmConfig:interpreter有關(guān)配備interpreter:合約代碼的解釋器,真正的執(zhí)行者在EVM中最核心的組員是EVM.interpreter及其配備EVM.vmConfig。vmConfig,即上圖中的Config,其組員JumpTable涉及了EVM指令集,如:合約代碼的執(zhí)行最后映射到每個(gè)指令集的操作,每個(gè)指令集涉及執(zhí)行的調(diào)用的函數(shù)、計(jì)算gas消耗、棧檢查函數(shù)、內(nèi)存使用計(jì)算函數(shù)及一系列控制停止運(yùn)行、跳轉(zhuǎn)等標(biāo)志。如ADD加法指令的執(zhí)行函數(shù):彈出棧頂?shù)膞,獲得y的值計(jì)算x+y,并更新到y(tǒng),y保存的就是ADD的成果釋放int回pool而取指令并執(zhí)行的過程就在EVM.interpreter.Run()里。我們先從上一節(jié)最后的ApplyTransaction()函數(shù)開始一步一步查看調(diào)用通過的流程:AApplyTransction(…)_,gas,failed,err:=ApplyMessage(vmenv,msg,gp)st:=NewStateTransition(evm,msg,gp)st.TransactionDB()ContractCreation?Evm.Create(….)Evm.Call(…)創(chuàng)立合約調(diào)用合約交易以正常的執(zhí)行流程到最后只有兩種,一種是創(chuàng)立合約,二是調(diào)用合約。它們都包含了Ether幣的轉(zhuǎn)賬操作。如果Call調(diào)用的To
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 信托在跨境電商物流人才流動(dòng)管理中的應(yīng)用考核試卷
- 機(jī)械化土壤監(jiān)測(cè)技術(shù)智能化發(fā)展路徑分析考核試卷
- 化學(xué)纖維紡織品變形與纖維結(jié)晶度關(guān)系研究考核試卷
- 光電子器件的光學(xué)微環(huán)諧振器考核試卷
- 出租車企業(yè)社會(huì)責(zé)任與城市交通規(guī)劃協(xié)同發(fā)展考核試卷
- 初中生道德教育中價(jià)值觀引導(dǎo)策略研究考核試卷
- 丙綸纖維環(huán)保認(rèn)證的市場(chǎng)推廣與品牌價(jià)值提升考核試卷
- 財(cái)務(wù)報(bào)表在危機(jī)管理中的應(yīng)用考核試卷
- 2025年中國PC塑膠原料數(shù)據(jù)監(jiān)測(cè)報(bào)告
- 2025年中國HDPE洗滌用品塑料瓶數(shù)據(jù)監(jiān)測(cè)研究報(bào)告
- 育嬰員考試題型及答案
- 科室建立血糖管理制度
- 四川成都東方廣益投資有限公司下屬企業(yè)招聘筆試題庫2025
- 華為公司試用期管理制度
- 保險(xiǎn)合規(guī)知識(shí)課件
- 2025-2030中國云原生保護(hù)平臺(tái)組件行業(yè)前景趨勢(shì)與投資盈利預(yù)測(cè)報(bào)告
- 商業(yè)大廈機(jī)電系統(tǒng)調(diào)試
- 2025企業(yè)并購合同協(xié)議模板
- 【恒順醋業(yè)公司基于杜邦分析的盈利能力淺析14000字論文】
- 電網(wǎng)技術(shù)改造及檢修工程定額和費(fèi)用計(jì)算規(guī)定2020 年版答疑匯編2022
- 2025年生態(tài)文明建設(shè)的考核試卷及答案
評(píng)論
0/150
提交評(píng)論