以太坊源碼分析之隨心筆記

鏈客,專爲開發者而生,有問必答!node

此文章來自區塊鏈技術社區,未經容許拒絕轉載。算法

圖片描述

以太坊索引數據庫

table.go 按期隨機選取一些節點找他們要他們的節點,放到本地,也就是一個隨機找節點的table 裏頭的bucket 和 nodesByDistance都是爲了找節點方便(這裏已經有pingpong了)使用的是udp.gojson

p2p/dial.go目前的理解是table負責用udp發現節點,而dial.go負責發起真正的連接,調用dial和server.go的SetupConnapi

udp.go 主要是收發消息用的好比ping pong 找節點消息等等,點對點消息緩存

discover/node.go 表明了一個網路節點,其中nodeid是一個公鑰的轉換網絡

p2p/peer.go 表明一個節點,主要是pingpong消息的轉發,以及protocols的轉發,這裏的msgcode就是protocol的(offset, offset+length) (注意這裏NewPeer的驅動的是protocol 對一個模擬的conn寫和讀,並無真正的網絡conn,爲了測試用,newPeer纔是真正被用到的,conn是SetupConn建立來的 server.go的conn結構其中transport是rlpx.go newRLPX生成的)tcp

node/node.goide

config.go 主要是一些配置其中node裏邊有網絡節點的一些配置,還有一個account(帳戶)的配置函數

message.go 定義了p2p交互中的msg和一系列封裝 eofSignal 消息字節讀取沒了給chan中發信號 MsgPipe一個雙向交互的消息pipe msgEventer 收發消息時給feed發送一個事件

api.go geth console 處處的js api都在這裏

nat目錄下的nat.go natpmp.go natupnp.go主要是實現nat,也就是讓外網的機器能夠訪問內網的機器有upnp協議pmp協議還有直接ip出去的 nat.map函數就是把內網的端口映射到外網去

p2p/metrics.go 主要是封裝了一層conn 用做儀表,其實就是記錄下進出流量的多少

p2p/rlpx.go 是對conn 發送以前加密,和rlp(加解碼)和conn做用同樣

server.go 表示p2p協議,經過udp協議找到table節點pingpong,而後經過tcp協議真正連接生成peer 其中inboundConn表示連接進來的(本地經過accept的node)而沒有這個標記是本地主動dial的節點,tcp和udp都有本身的pingpong,udp的在udp.go tcp的在peer.go

rpc=======================================

subscription.go 維護Notifier主要是Notify函數經過建立codec.CreateNotification而且codec.Write來發消息

rpc總體來講就是經過json等方式調用註冊的服務函數,並且經過映射的方式獲取函數名等等match到json上

rpc end*

node========================================

node.go 表明了一個節點,內含一個p2pserver 和rpcserver 主要的執行是經過Register註冊的服務

node end

trie===========================================

node 分爲幾類 shortnode 主要是一個前綴key+node fullnode是16個字母(key)+一個通用字母 每個字母對應一個node valnode直接就是葉子節點,還有一類hashnode 是node的hash值,存到db中去了

trie.go 一個樹結構有幾種節點,其中類型參考node的講解,其中 hash 這個函數是把節點都存到db裏面而且返回(node的hashnode,node的拷貝,err) Commit目前分析就是hash函數的調用過程,主要是把節點存db而後轉換成node

hasher.go 用戶hash node,而且將hash存在node 的flag裏面,或者存到db中去,用的hash算法是keccak256

secure_trie.go 對trie.go中的節點簡單的封裝和轉查

trie就是以key和value的形式構造了一顆用key作索引的樹,key被作成了shortnode和fullnode,而後提供了一個hash函數,這個函數遞歸的hash子節點,最後算出來一個hash根(有一個用途就是blockheader裏頭的tx hash)

proof.go 提供兩個關鍵函數 prove 經過key生成一條路徑的證實,VerifyProof 提供證實和key 驗證是否達標 可是具體的驗證原理尚未搞的特別明白

tire end**

types ============================================

transaction.go 表明了一個tx 內部還有一個msg,這裏面有經過price排序的heap 須要注意的是簽名中能夠獲取from地址因此tx的結構中只有個to的字段

receipt.go 表明了tx的執行結果,主要有poststate status 和CumulativeGasUsed(累積使用的gas)一個bloom filter以及logs組成

transaction_signing.go 對tx作簽名,從簽名中獲取到chainId以及sender(from)

block.go 定義一個區塊,區塊包含幾個部分一個是header 一個是uncle headers 一個是tx

types end**

genesis.go 主要是生成genesis block 其中SetupGenesisBlock生成了一個genesis block 而且把相關的信息存放在db中去,好比config信息

les ================================================

peer.go 定義了一個peer和peerset,前者主要用於各類收發協議消息,後者是peer的set,用於對peer進行peerSetNotify的註冊和反註冊

execqueue.go 裏面有一個func的queue,在一個單獨的go線程執行

protocol.go 定義了 announceData 有點像比特幣中的header消息,用於從遠方拉block的消息,而且提供了 announceData 的 sign和驗證的函數sign和checkSignature

distributor.go 主要是distPeer(其實就是peer)和distReq兩個類型的協同工做 distReq表明一個請求,主要是request函數返回了一個func,這個func能夠放到queueSend中去而requestDistributor就是loop distReq隊列而後對其進行queueSend,而queue就是對外的函數,用於放進隊列distReq進而進入loop發送請求,發送完之後經過queue的返回值(一個chan)返回一個peer(dispeer接口對應的對象是peer)

flowcontrol 感受是經過上次發送的時間和對面的handsake 獲得的一些buflimit等信息來決定下次發送須要多久,以讓各個節點都有時間處理,減小發送失敗的狀況(猜的哈)

txrelay.go 一個tx消息的中繼器,主要是peer.go distributor.go兩個邏輯的結合,選取peer發送distReq進而發送tx

serverPool.go 是一個連接池對應的是p2p的peer,維護了這麼一個連接池connect是連接進來的peer調用 registered是完成handshake調用, 其中newQueue表明connect進來的knownQueue表示已經完成registered,其中start函數的兩個參數能夠看出一個server表示啓動p2p開始作dial連接,而topic是一個discorver邏輯發現節點用的對應的是udp發現節點經過tcpserver進行dial鏈接

retrieve.go 主要是有 distributor.go 用於把消息發送出去(retrieve函數) 而後經過自身提供的函數deliver分發到應答的消息到pee

odr_requests.go 表明了集中request 和他們的接口函數,其中每一種類型的req會使用peer裏邊的函數發送,另外每一種類型req都有一個validate函數是在retrive.go的deliver函數中使用用於驗證消息的合法性

odr.go 主要是Retrieve函數收到一個req(odr_requests.go)封裝了一下轉發出去,另外封裝了一些indexer(chain_indexer.go postprocess.go)這個Retrieve 函數不但把req發出了並且還把結果取回來經過req的StoreResult或者Validate把結果存到db或者自身去了,這裏要注意

handler.go 主要是處理消息而後轉發給 retrieve.go解析的,同時也響應部分消息,好比要求發送block的消息,直接經過 peer發送出去

fetcher.go 目前理解是一個記錄了各個peer有多少個node header而後按期從各個peer上獲取東西到本地的一個類

les end**

light================================================

postprocess.go是對chain_indexer.go的一個封裝填充了backend NewBloomTrieIndexer NewChtIndexer 兩個分別是兩個backend

lightchain.go 這個文件中實現了一個鏈的邏輯 InsertHeaderChain這個函數比較重要就是把參數傳進來的一系列的header寫到數據庫中(先要驗證)想法連到主鏈上,若是連不到就連的側鏈上,而後經過chainSideFeed和chainHeadFeed通知外部連接的狀況,經過SubscribeChainHeadEvent和SubscribeChainSideEvent,和SubscribeChainEvent來監聽

trie.go一個封裝了一個trie的結構(odrTrie)),若是發現本地db有這個trie的內容就直接返回,若是沒有就循環的經過Retrieve函數從別的地方獲取

txpool.go 目前瞭解有點相似比特幣中的mempool,是一個tx的緩存池,加入到池子中有一些簡單的驗證,好比驗證nonce和gas值對不對,而後放到pending的map中,並經過txrelay轉發出去,若是有些block被連接到鏈上了還會標記這些penging的tx已經mined或者block被從鏈上退回來了,也會放到rollback列表中去,而後告訴txrelay去處理一些發送的pending節點

另外經過SubscribeNewTxsEvent來通知外部有新的tx加入到pool中,它自身又經過SubscribeChainHeadEvent監聽了lightchain關於head的事件

light end**

core/state=============================================

這個目錄裏面就是以太坊api實際做用到磁盤上的東東,stateobject就是以太坊的狀態對象,包含了對一個帳戶餘額等的操做以及key-value對的設置以及code的設置這三部分

journal.go算是一個回滾列表,容易拍一個state快照而後回滾,對state數據庫的回滾操做

statedb.go 是state_object的集合經過addr 爲key的map, 本身也有trie存放的是state_object

state_object.go 維護了一個stateObject對象 主要是getstate setstate 和帳戶balance維護以及codeshash的維護,內部有一個節點的trie存放的key-value對都是hash,最後這些都不會被放到db中

core/state end*

core/type =============================================

receipt CumulativeGasUsed表明了一個block到這個tx的時候用了多少的gas,而gasused表明這一個tx用了多少的gas, receipt目前我理解的有幾個用途,一個是記錄了一個PostState字段表明了走到這個tx的時候狀態數據庫的roothash是什麼,更方便肯定一個tx的狀態確認,另外是Bloom能夠快速的查看一個日誌是否在這個tx中,而且有logs這個字段,這裏還有一個疑問或者注意點,就是虛擬機有些錯誤有了,可是仍是會收錄到tx裏邊因此receipt有個Status來表明vm是否執行成功,其中我目前理解的有些錯誤如gas用完了之類的,雖然是錯誤可是這個tx依然反映到了db中去,gas不還了哈哈

(todo vm執行失敗,看看vm內部會不會回滾一點db,反正部分失敗是在外部是不會回滾db的)

todo vm執行失敗,看看vm內部會不會回滾一點db,反正部分失敗是在外部是不會回滾db的 ?,在vm/evm.go這個文件的call函數中都有回滾

並且其實ErrInsufficientBalance這個錯誤(也就是轉帳以太坊失敗的這種錯誤),是不會寫入區塊鏈,而別的錯誤vm/error.go,雖然狀態會被回滾(vm/evm.go這個文件的call函數)可是會寫入區塊鏈的,而且返回receipt

core/type end*

core================================================

chain_indexer.go eventLoop等有新的head 出現的時候(目前來看是從headerchain過來的,也就是有head被連到主鏈時候發送消息過來)收到一個event 而後調整本地有多是換鏈最新的head(head的seg) updateLoop 是等待更新消息(eventloop在發現有新的section的時候發送),而後調用processSection 進而調用backend整塊的處理section中的head

section的概念有點相似比特幣中的難度一段一段的那種

chainDb 存放的是實際的數據

indexDb 存放的是index的數據(index就是section的一些信息,如一個section的頭是哪一個blockheader)

children 存放的是一些子的chainindexe

backend 後臺處理程序接口,主要是reset Process 和commit三個函數,這一套函數處理一個section reset接收section num + 上一個section的head, process循環調用處理本section中的head,commit用於提交通常用於寫入數據庫

headerchain.go 一個表明header鏈的結構,其中currentheader 是鏈頭,Canonical這個單詞表示是正鏈,其餘的函數都是隻存數據庫不是主鏈的意思好比WriteCanonicalHash表示在朱臉上寫hash WriteHeadHeaderHash就只是存了一下

state_transition.go 這裏有tx執行的過程調用包括虛擬機的執行,尤爲是gas使用的一些邏輯,不過這裏目前有一個疑問爲啥退款是使用min(gas的一半,剩餘的gas)也就是有封頂,並且若是達到封頂值之後一些錢就消失了沒有給礦主,礦主只拿到了gasused

state_processor.go 目是對block中的tx運行虛擬機生成receipt和state等

blockchain.go 真正的鏈操做,經過downloader等把塊數據傳到這裏如InsertChain,而後經過驗證塊(state_processor.go的 Process等)加入區塊

block_validator.go 應該是主要驗證一些block的body信息

core end**

consensus/ethash========================================

ethash.go 目前看明白的是兩個hash cache 一個就叫cache一個叫dataset,這兩個包含到了ethash這個結構中,epoch這個是一個區段的意思,一個區段有一個cache和dataset

consensus.go 共識engine,主要是看header uncle等結構以及難度肯定和驗證用的 VerifySeal表示驗證了header的難度其中header的MixDigest是一個另類的頭的hash 這個和比特幣不同至關於存了一個hash到header中 verifyHeader檢查header的各個字段若是seal參數爲true就檢查難度是否達標

consensus/ethash end*

eth/downloader=============================================

downloader.go 主要是經過網絡下載block和header和receipt三種信息而後經過chain放到鏈中去

queue.go 一個隊列經過retrievexxx和deliverxxx兩個類型的函數排隊下載最後放結果,deliver函數經過一個haderProcCh把結果高出去

eth/downloader end**

consensus/ethash============================================

sealer.go Seal 經過提供這個函數挖礦

consensus/ethash*

miner====================================================

agent.go負責挖hash,而後把成的模塊給worker.go

worker.go負責從txpool中生成block,而後造成work給agent算hash 生成block以及驗證block驗證tx的邏輯都在這裏

miner.go是對worker的一個封裝

worker 的commitNewWork把區塊準備好(驗證什麼的)可是沒有算hash等

agent 的mine實際算hash

Worker wait函數把挖礦好的block插入到放到鏈中WriteBlockWithState

另一個區塊的目標難度和幾個因素有關係一個是和上一個區塊的隔離時間,一個是上一個區塊有幾個uncle,還有一個是上一個區塊的難度

miner end

vm======================================================

contract.go 抽象了一個智能合約,有caller 有本身的code 有合約帶的gas等

contracts.go 定義了一個map表明裏系統合約PrecompiledContract

ecrecover系統合約從輸入(輸入中包含了sig和hash)中解出來了一個公鑰

 sha256hash系統合約進行了一個sha256的hash計算

 ripemd160hash系統合約進行一次ripemd160hash的計算

 dataCopy系統合約直接返回輸入數據

 bigModExp 對一段數據作exp操做具體的看bigint 的exp函數

 bn256Add 對bn256 作+操做

 bn256ScalarMul 對bn256作*操做

 bn256Pairing 對bn256作pairing操做

opcodes.go 全部指令的定義

analysis.go codeBitmap表示一段代碼中的數據,生成一個bitmap,若是這個位置爲1表示這一個字節是數據不然是代碼 destinations是一個code hash到codebitmap的map,has函數看看索引上的指令是否是jmpdest指令

jump_table.go 定義了指令的operation每個指令對應一個operation結構主要包含 executionFunc執行函數gasFunc gas消耗函數 stackValidationFunc堆棧檢查函數 memorySizeFunc內存消耗函數

instructions.go 指令的執行函數都在這裏 executionFunc

opAdd opSub opMul opDiv --- opSAR 基本運算,把棧頂相關運算數取出來而後將結果放到堆棧去

opSha3 從堆棧中獲取內存的地址(offset+size)從內存中取出來數據,而後作keccak256處理,而後把算出來的hash入棧,有一個梗是會把(hash,data)看成preimage寫到數據庫中

opAddress 獲取contract.Address字段

opBalance 把堆棧頂端的地址換成了該地址對應的餘額

opOrigin 表明evm執行程序的最開始的caller地址

opCaller 這個合約的calle

opCallValue 堆棧中push contract.value

opCallDataLoad 棧頂拿一個地址(offset)從input中獲取一個32位的數,放到棧頂

opCallDataSize 棧頂放一個input的size

opCallDataCopy 把input內的內容copy到內存(棧頂有三個參數內存offset, input offset 和len)

opReturnDataSize 棧頂push interpreter的returnData的size

opReturnDataCopy 把 interpreter的returnData 的部份內容copy到內存(棧頂三個參數 內存offset returndata的offset和size)

opExtCodeSize 棧頂的code地址轉換成codesize

opCodeSize 把合約本身的codesize放到棧頂

opCodeCopy 合約本身的code部份內容copy到內存中(內存offset, code offset, size)

opExtCodeCopy 把一段代碼的部分copy到內存中(code的地址,內存offset, code offset, size)

opGasprice 獲取gas的價格evm的GasPrice參數

opBlockhash 把棧頂的block num轉換成block hash

opCoinbase 把coinbase的地址放到棧頂

opMload 棧頂給一個內存地址取出來一個32字節數據放到棧頂

opMstore 存一個地址到內存

opSload 將棧頂的key轉換成對應的狀態value

opSstore 設置對應的key,value

opJumpi 經過棧頂的跳轉目標和條件變量決定是否跳轉到pc

opPc 把棧頂放上pc的值

opMsize 棧頂放上內存如今的大小

opGas 把合約當前的gas放到棧頂

opCreate 建立一個合約,參數爲給合約的轉帳,合約地址(內存offset 和size),建立合約後消耗gas並把執行結果也就是合約的地址寫到堆棧

opCall 調用合約把返回值寫到內存中指定的位置,而且會在堆棧中寫入0,1表明調用是否成功

opCallCode 調用合約把返回值寫到內存中指定的位置,而且會在堆棧中寫入0,1表明調用是否成功只不過這個調用用的toaddr和calladdr同樣(可是code是被調用合約的)

opDelegateCall 同上調用的函數是DelegateCall

opStaticCall 同上調用的函數是StaticCall

opSuicide 獲取到當前合約的balance而後轉帳到堆棧頂端的地址

opRevert 直接回滾返回錯誤errExecutionReverted,這個邏輯在interpreter.go

gas_table.go gas的消耗函數都在這裏 gasFunc

stack_table.go stackValidationFunc 堆棧檢查函數在這裏

memory_table.go 內存消耗函數都在這裏 memorySizeFunc

stack.go 一個堆棧結構

intpool.go 封裝了一個stack

interpreter.go 主要是一個run函數(註釋裏面有句話很重要,說是全部返回的err都是回滾+消耗掉全部gas除了errExecutionReverted表示回滾可是不消耗gas) 主要是運行一個合約

memory.go 表明內存的一個結構就是setvalue和getvalue之類的函數

合約裏的input是作什麼用的?, 其實就是opCallDataLoad經過堆棧中的一個input的偏移來找一個big32大小的數據而且放到堆棧中去

intpool又是作什麼的?感受就是一個普通的bigint的緩存池,沒有實際意義由於全部的get操做都直接被set成另一個值了

call和callcode等的區別是什麼?

call callcode 和DelegateCall這三個的區別主要是三個合約的成員變量

一、caller表明了本身調用者 二、self表明了本身 三、value表明合約裏有多少錢

這三個又能夠歸結爲兩個一個是狀態數據庫(key value)一個是合約裏有多少錢

狀態數據庫只能寫當前合約地址的也就是self的keyvalue,因此self影響了狀態數據庫

合約裏轉帳也只能轉合約的value其實仍是對應的self這個地址(由於value對應的是數據庫裏的地址的,contract.value僅僅是查詢使用的),因此這裏會影響value(opCreate和opSuiside)

而self在調用別的合約的時候就變成了caller了,因此self會影響calle

這三種調用

call 是caller是傳入的參數caller,self用的傳入的參數to,value用的是傳入的value,而且真實的把caller的value轉帳到了to,而且若是這個合約地址不存在新建一個合約帳戶,這樣寫的數據庫是to的數據庫,用的錢是caller轉給他的錢

callcode 是caller是傳入的參數caller,self用的傳入的參數caller,value用的是傳入的value,可是因爲caller和self都是caller因此沒有實際轉帳,這樣寫的數據庫是caller的數據庫,用的錢是caller的錢

DelegateCall caller是caller的caller,self是caller自己,value爲caller的caller的value,由於是代理調用因此其實不用轉帳,這樣寫的數據庫是caller的數據庫,用的前是caller的錢,其實這個合約調用就至關於從新建了一個caller的合約(除了code不同其餘都同樣)

vm end*

params===================================================

gas_table.go 一些gas訂價的表格參數

params*

問題

每個peer有一個pubkey這個是從哪兒來的?目前理解爲p2p加密通訊協議,用於對消息進行加密,每個peer的nodeid就是pubkey,handshake的時候他們用公私密鑰進行加解密

todo vm執行失敗,看看vm內部會不會回滾一點db,反正部分失敗是在外部是不會回滾db的 ?,在vm/evm.go這個文件的call函數中都有回滾

receipt在哪兒存?(經過blockchain.go的WriteBlockWithState寫到數據庫) block中只有receipt的hash,以及爲啥要設計他?他有啥用?他有啥用目前來看是隻有block數據和receipt數據是在網絡上傳輸的,而state數據是沒有傳輸的

還有從一個地方能夠看出來receipt用途的一個小點,就是api.go中的GetTransactionReceipt會把全部的receipt字段從新封裝了一些字段給到客戶端

合約帳戶的私鑰在哪兒?合約的地址是經過caller的地址加上caller的nonce生成的,不是經過私鑰生成的,因此沒有私鑰,也就不存在 私鑰在哪兒的這個問題(evm/evm.go create函數)可是引出來一個問題,對應合約的錢是經過什麼機制轉出去的?這裏的答案是opSuicide 這個指令也就是隻有合約本身能夠把錢轉出去,其實還有一個opcreate是在建立子合約的時候也會轉帳,另外還有一個方式是調用opcall指令(對應的是addr.transfer這個函數)

對比構造函數和通常的函數調用對應的指令由於看源碼vm/evm.go的代碼好像調用都是從code的第一個字節開始執行的?這裏不知道有啥蹊蹺沒有 (in *Interpreter) Run的起始pc都是0,這個能夠看以太坊技術詳解與實戰第五章

以太坊中的虛擬機執行的err有的沒有回滾緣由是什麼?

以太坊中的挖完的區塊怎麼鏈接到鏈上,也就是對應比特幣中的區塊候選鏈在哪兒 WriteBlockWithState 挖完礦直接進這裏了

其餘備忘:

uncle的選取是,找本身7個以上的祖先,和而後找這7個祖先對應的uncle,本身添加的uncle必須是7個祖先的後代(也就是uncle老爹必須是在7代主線內),並且必須不在7個祖先的uncle裏

挖礦的獎勵目前看只和uncles數有關係(固定階段的兩個分支也有區別)沒有像比特幣同樣和時間有關係

abi調用過程目前知道的是發送調用

通過(b bridge) Send(bridge.go)->(c BoundContract) Call(abi/bind/backends/base.go)-> (b SimulatedBackend) callContract(simulated.go)->(st StateTransition) TransitionDb()(state_transition.go)

返回值經過TransitionDb返回而後abi經過unpack把他轉換成json最後返還給客戶端

相關文章
相關標籤/搜索