兄弟連Go語言+區塊鏈培訓以太坊源碼分析(18)以太坊交易執行分析

來源:互聯網
上載者:User

兄弟連Go語言+區塊鏈培訓學院院長尹成資深區塊鏈技術專家:畢業於清華大學,曾擔任Google演算法工程師,微軟區塊鏈領域全球最具價值專家,微軟Tech.Ed大會金牌講師。精通C/C++、Python、Go語言、Sicikit-Learn與TensorFlow。擁有15年編程經驗與5年的教學經驗,資深軟體架構師,Intel軟體技術專家,著名技術專家,具備多年的世界頂尖IT公司微軟Google的工作經驗。具備多年的軟體編程經驗與講師授課經曆,並在人機互動、教育、資訊安全、廣告、區塊鏈系統開發諸多產品。具備深厚的專案管理經驗以及研發經驗,擁有兩項人工智慧發明專利,與開發電子貨幣部署到微軟WindowsAzure的實戰經驗。教學講解深入淺出,使學員能夠做到學以致用。

#以太坊交易執行分析

在這裡,將其整體串起來,從state_processor.Process函數開始,歸納一下其所作的處理。

##1 Process

Process 根據以太坊規則運行交易資訊來對statedb進行狀態改變,以及獎勵挖礦者或者是其他的叔父節點。

Process返回執行過程中累計的收據和日誌,並返回過程中使用的Gas。 如果由於Gas不足而導致任何交易執行失敗,將返回錯誤。

**處理邏輯:**

~~~

1. 定義及初始化收據、耗費的gas、區塊頭、日誌、gas池等變數;

2. 如果是DAO事件硬分叉相關的處理,則調用misc.ApplyDAOHardFork(statedb)執行處理;

3. 對區塊中的每一個交易,進行迭代處理;處理邏輯:

a. 對當前交易做預先處理,設定交易的hash、索引、區塊hash,供EVM發布新的狀態日誌使用;

b. 執行ApplyTransaction,擷取收據;

c. 若上一步出錯,中斷整個Process,返回錯誤;

d. 若正常,累積記錄收據及日誌。迴圈進入下一個交易的處理。

4. 調用共識模組做Finalize處理;

5. 返回所有的收據、日誌、總共使用的gas。

~~~

##2 ApplyTransaction(1.3.b )

ApplyTransaction嘗試將交易應用於給定的狀態資料庫,並使用輸入參數作為其環境。

它返回交易的收據,使用的Gas和錯誤,如果交易失敗,表明塊是無效的。

**處理邏輯:**

~~~

1. 將types.Transaction結構變數轉為core.Message對象;這過程中會對寄件者做簽名驗證,並獲得寄件者的地址緩衝起來;

2. 建立新的上下文(Context),此上下文將在EVM 環境(EVM environment)中使用;上下文中包含msg,區塊頭、區塊指標、作者(挖礦者、獲益者);

3. 建立新的EVM environment,其中包括了交易相關的所有資訊以及調用機制;

4. ApplyMessage, 將交易應用於當前的狀態中,也就是執行狀態轉換,新的狀態包含在環境對象中;得到執行結果以及花費的gas;

5. 判斷是否分叉情況( `config.IsByzantium(header.Number)` ),如果不是,擷取當前的statedb的狀態樹根雜湊;

6. 建立一個收據, 用來儲存中間狀態的root, 以及交易使用的gas;

7. 如果是建立合約的交易,那麼我們把建立地址儲存到收據裡面;

8. 拿到所有的日誌並建立日誌的布隆過濾器;返回。

~~~

##3 ApplyMessage(2.4)

ApplyMessage將交易應用於當前的狀態中,代碼裡就是建立了一個StateTransition然後調用其TransitionDb()方法。

ApplyMessage返回由任何EVM執行(如果發生)返回的位元組(但這個傳回值在ApplyTransaction中被忽略了),

使用的Gas(包括Gas退款),如果失敗則返回錯誤。 一個錯誤總是表示一個核心錯誤,

意味著這個訊息對於這個特定的狀態將總是失敗,並且永遠不會在一個塊中被接受。

##4 StateTransition.TransitionDb()

~~~

1. 預檢查,出錯則函數返回;

a. 檢查交易的Nonce值是否合規;

b. buyGas:根據寄件者定的gaslimit和GasPrice,從寄件者餘額中扣除以太幣;從區塊gas池中減掉本次gas;並對運行環境做好更新;

2. 支付固定費用 intrinsic gas;

3. 如果是合約建立, 那麼調用evm的Create方法建立新的合約,使用交易的data作為新合約的部署代碼;

4. 否則不是合約建立,增加寄件者的Nonce值,然後調用evm.Call執行交易;

5. 計算並執行退款,將退回的gas對應的以太幣退回給交易寄件者。

~~~

###4.3 evm.Create建立新的合約

~~~

1. 檢查執行深度,若超過params.CallCreateDepth(即1024)就出錯返回;剛開始的執行深度為0,肯定繼續往下執行;

2. 檢查是否可執行轉賬,即檢查賬戶餘額是否≥要轉賬的數額;

3. 寄件者Nonce加1;

4. 建立合約地址並擷取hash,若該合約地址已存在,或不合法(空),則出錯返回;

5. 儲存statedb快照,然後根據合約地址建立賬戶;

6. 執行轉賬evm.Transfer(在statedb中,將value所代表的以太幣從寄件者賬戶轉到新合約賬戶);

7. 根據寄件者、前面建立的合約賬戶,轉賬的錢,已用的gas建立並初始化合約;將交易的data作為合約的代碼;

8. 運行前一步建立的合約

9. 判斷運行結果是否有錯誤。如果合約成功運行並且沒有錯誤返回,則計算儲存返回資料所需的GAS。 如果由於沒有足夠的GAS而導致傳回值不能被儲存則設定錯誤,並通過下面的錯誤檢查條件來處理。

10. 若EVM返回錯誤或上述儲存傳回值出現錯誤,則復原到快照的狀態,並且消耗完剩下的所有gas。

~~~

###4.4 evm.Call執行交易

Call方法, 無論我們轉賬或者是執行合約代碼都會調用到這裡, 同時合約裡面的call指令也會執行到這裡。

Call方法和evm.Create的邏輯類似,但少了一些步驟。

~~~

1. 檢查是否允許遞迴執行以及執行深度,若深度超過params.CallCreateDepth(即1024)就出錯返回;

2. 檢查是否可執行轉賬,即檢查賬戶餘額是否≥要轉賬的數額;

3. 儲存statedb快照,建立接收者賬戶;

4. 如果接收者在statedb中尚不存在,則執行precompiles先行編譯,與編譯結果為nil時出錯返回;無錯誤則在statedb中建立接收者賬戶;

5. 執行轉賬;

6. 根據寄件者、接收者,轉賬的錢,已用的gas建立並初始化合約;將交易的data作為合約的代碼;

7. 運行前一步建立的合約

8. 若EVM返回錯誤,則復原到快照的狀態,並且消耗完剩下的所有gas。

~~~

虛擬機器中合約的執行另行分析。

### eth源碼交易發送接收,校正儲存分析:

```

建立合約指的是將合約部署到區塊鏈上,這也是通過發送交易來實現。在建立合約的交易中,to欄位要留空不填,在data欄位中指定合約的二進位代碼,

from欄位是交易的寄件者也是合約的建立者。

執行合約的交易

調用合約中的方法,需要將交易的to欄位指定為要調用的合約的地址,通過data欄位指定要調用的方法以及向該方法傳遞的參數。

所有對賬戶的變動操作都會先提交到stateDB裡面,這個類似一個行為資料庫,或者是緩衝,最終執行需要提交到底層的資料庫當中,底層資料庫是levelDB(K,V資料庫)

core/interface.go定義了stateDB的介面

ProtocolManager主要成員包括:

peertSet{}類型成員用來緩衝相鄰個體列表,peer{}表示網路中的一個遠端個體。

通過各種通道(chan)和事件訂閱(subscription)的方式,接收和發送包括交易和區塊在內的資料更新。當然在應用中,訂閱也往往利用通道來實現事件通知。

ProtocolManager用到的這些通道的另一端,可能是其他的個體peer,也可能是系統內單例的資料來源比如txPool,或者是事件訂閱的管理者比如event.Mux。

Fetcher類型成員累積所有其他個體發送來的有關新資料的宣布訊息,並在自身對照後,安排相應的擷取請求。

Downloader類型成員負責所有向相鄰個體主動發起的同步流程。

func(pm *ProtocolManager) Start()

以上這四段相對獨立的商務程序的邏輯分別是:

1.廣播新出現的交易對象。txBroadcastLoop()會在txCh通道的收端持續等待,一旦接收到有關新交易的事件,會立即調用BroadcastTx()函數廣播給那些尚無該交易對象的相鄰個體。

2.廣播新挖掘出的區塊。minedBroadcastLoop()持續等待本個體的新挖掘出區塊事件,然後立即廣播給需要的相鄰個體。當不再訂閱新挖掘區塊事件時,這個函數才會結束等待並返回。很有意思的是,在收到新挖掘出區塊事件後,minedBroadcastLoop()會連續調用兩次BroadcastBlock(),兩次調用僅僅一個bool型參數@propagate不一樣,當該參數為true時,會將整個新區塊依次發給相鄰區塊中的一小部分;而當其為false時,僅僅將新區塊的Hash值和Number發送給所有相鄰列表。

3.定時與相鄰個體進行區塊全鏈的強制同步。syncer()首先啟動fetcher成員,然後進入一個無限迴圈,每次迴圈中都會向相鄰peer列表中“最優”的那個peer作一次區塊全鏈同步。發起上述同步的理由分兩種:如果有新登記(加入)的相鄰個體,則在整個peer列表數目大於5時,發起之;如果沒有新peer到達,則以10s為間隔定時的發起之。這裡所謂"最優"指的是peer中所維護區塊鏈的TotalDifficulty(td)最高,由於Td是全鏈中從創世塊到最新頭塊的Difficulty值總和,所以Td值最高就意味著它的區塊鏈是最新的,跟這樣的peer作區塊全鏈同步,顯然改動量是最小的,此即"最優"。

4.將新出現的交易對象均勻的同步給相鄰個體。txsyncLoop()主體也是一個無限迴圈,它的邏輯稍微複雜一些:首先有一個資料類型txsync{p, txs},包含peer和tx列表;通道txsyncCh用來接收txsync{}對象;txsyncLoop()每次迴圈時,如果從通道txsyncCh中收到新資料,則將它存入一個本地map[]結構,k為peer.ID,v為txsync{},並將這組tx對象發送給這個peer;每次向peer發送tx對象的上限數目100*1024,如果txsync{}對象中有剩餘tx,則該txsync{}對象繼續存入map[]並更新tx數目;如果本次迴圈沒有新到達txsync{},則從map[]結構中隨機找出一個txsync對象,將其中的tx組發送給相應的peer,重複以上迴圈。

以上四段流程就是ProtocolManager向相鄰peer主動發起的通訊過程。儘管上述各函數細節從文字閱讀起來容易模糊,不過最重要的內容還是值得留意下的:本個體(peer)向其他peer主動發起的通訊中,按照資料類型可分兩類:交易tx和區塊block;而按照通訊方式劃分,亦可分為廣播新的單個資料和同步一組同類型資料,這樣簡單的兩兩配對,便可組成上述四段流程。

在上文的介紹中,出現了多處有關p2p通訊協定的結構類型,比如eth.peer,p2p.Peer,Server等等。這裡不妨對這些p2p通訊協定族的結構一併作個總解。以太坊中用到的p2p通訊協定族的結構類型,大致可分為三層:

第一層處於pkg eth中,可以直接被eth.Ethereum,eth.ProtocolManager等頂層管理模組使用,在型別宣告上也明顯考慮了eth.Ethereum的使用特點。典型的有eth.peer{}, eth.peerSet{},其中peerSet是peer的集合類型,而eth.peer代表了遠端通訊對象和其所有通訊操作,它封裝更底層的p2p.Peer對象以及讀寫通道等。

第二層屬於pkg p2p,可認為是泛化的p2p通訊結構,比較典型的結構類型包括代表遠端通訊對象的p2p.Peer{}, 封裝自更底層連線物件的conn{},通訊用通道對象protoRW{}, 以及啟動監聽、處理新加入串連或中斷連線的Server{}。這一層中,各種資料類型的界限比較清晰,盡量不出現揉雜的情況,這也是泛化結構的需求。值得關注的是p2p.Protocol{},它應該是針對上層應用特意開闢的類型,主要作用包括容納應用程式所要求的回呼函數等,並通過p2p.Server{}在新串連建立後,將其傳遞給通訊對象peer。從這個類型所起的作用來看,命名為Protocol還是比較貼切的,儘管不應將其與TCP/IP協議等既有概念混淆。

第三層處於golang內建的網路程式碼封裝中,也可分為兩部分:第一部分pkg net,包括代表網路連接的介面,代表網路地址的以及它們的實作類別;第二部分pkg syscall,包括更底層的網路相關係統調用類等,可視為封裝了網路層(IP)和傳輸層(TCP)協議的系統實現。

```

```

Receiptroot我們剛剛在區塊頭有看到,那他具體包含的是什麼呢?它是一個交易的結果,主要包括了poststate,證券交易所花費的gas,bloom和logs

blockchain無結構化查詢需求,僅hash查詢,key/value資料庫最方便,底層用levelDB儲存,效能好

stateDB用來儲存世界狀態

Core/state/statedb.go

注意:1. StateDB完整記錄Transaction的執行情況; 2. StateDB的重點是StateObjects; 3. StateDB中的 stateObjects,Account的Address為 key,記錄其Balance、nonce、code、codeHash ,以及tire中的 {string:Hash}等資訊;

所有的結構湊明朗了,那具體的驗證過程是怎麼樣的呢

Core/state_processor.go

Core/state_transition.go

Core/block_validator.go

StateProcessor 1. 調用StateTransition,驗證(執行)Transaction; 2. 計算Gas、Recipt、Uncle Reward

StateTransition

1. 驗證(執行)Transaction;

3. 扣除transaction.data.payload計算資料所需要消耗的gas;

4. 在vm中執行code(產生contract or 執行contract);vm執 行過程中,其gas會被自動消耗。如果gas不足,vm會自 選退出;

5. 將多餘的gas退回到sender.balance中;

6. 將消耗的gas換成balance加到當前env.Coinbase()中;

BlockValidator

1. 驗證UsedGas

2. 驗證Bloom

3. 驗證receiptSha

4. 驗證stateDB.IntermediateRoot

/core/vm/evm.go

交易的轉帳操作由Context對象中的TransferFunc類型函數來實現,類似的函數類型,還有CanTransferFunc, 和GetHashFunc。

core/vm/contract.go

合約是evm用來執行指令的結構體

入口:/cmd/geth/main.go/main

```

#EVM分析

>EVM不能被重用,非安全執行緒

Context結構體:為EVM提供輔助資訊。一旦提供,不應更改。

~~~

// Context 為EVM提供輔助資訊。一旦提供,不應更改。

type Context struct {

    // CanTransfer 返回 賬戶是否擁有足夠的以太幣以執行轉賬 CanTransfer CanTransferFunc

    // Transfer 轉賬函數,將以太幣從一個賬戶轉到另一個賬戶

    Transfer TransferFunc

    // GetHash 返回n對應的雜湊

    GetHash GetHashFunc

    // Message information

    Origin common.Address // Provides information for ORIGIN

    GasPrice *big.Int // Provides information for GASPRICE

    // Block information

    Coinbase common.Address // Provides information for COINBASE

    GasLimit uint64 // Provides information for GASLIMIT

    BlockNumber *big.Int // Provides information for NUMBER

    Time *big.Int // Provides information for TIME

    Difficulty *big.Int // Provides information for DIFFICULTY

}

~~~

> state_processor.Process開始執行交易處理,就是在那裡為入口進入到evm的執行的,具體見[core-state-process-analysis.md](core-state-process-analysis.md)

##EVM的實現

以太坊的EVM整個完全是自己實現的,能夠直接執行Solidity位元組碼,沒有使用任何第三方運行時。

運行過程是同步的,沒有啟用go協程。

1. evm最終是調用Interpreter運行位元組碼;

2. Interpreter.go實現運行處理;解析出作業碼後,通過JumpTable擷取作業碼對應的函數運行,並維護pc計數器、處理傳回值等;

3. jump_table.go定義了作業碼的跳轉映射;

4. instructions.go實現每一個作業碼的具體的處理;

5. opcodes.go中定義了作業碼常量

對於EVM的測試,以太坊將測試代碼放在了core\vm\runtime目錄下,提供了供測試用的運行時及測試案例。

測試案例的樣本如:

~~~

func TestExecute(t *testing.T) {

    ret, _, err := Execute([]byte{

        byte(vm.PUSH1), 10,

        byte(vm.PUSH1), 0,

        byte(vm.MSTORE),

        byte(vm.PUSH1), 32,

        byte(vm.PUSH1), 0,

        byte(vm.RETURN),

    }, nil, nil)

    if err != nil {

        t.Fatal("didn't expect error", err)

    }

    num := new(big.Int).SetBytes(ret)

    if num.Cmp(big.NewInt(10)) != 0 {

        t.Error("Expected 10, got", num)

    }

}

~~~

  比特幣沒趕上?以太幣沒有買?你錯過了成為了百萬富翁的夢但是不要錯失成為創造者的機會!!!7月7日起每晚8點-9點半兄弟連區塊鏈學院正式開課:http://www.ydma.cn/open/course/16【清華學霸攜全球區塊鏈大賽冠軍團隊】帶你實戰區塊鏈開發!!!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.