目錄
- 介紹
- 區塊鏈
- 世界狀態
- 賬戶
- 交易
- 訊息(Message)
- 去中心化資料庫
- 原子性和順序
- 虛擬機
- 以太坊虛擬機
- 訊息呼叫
- 例外
- Gas和費用
- 輸入和輸出
- 位元組指令
- 指令集
- 雜項
- 附錄A: 實作
- Geth原始碼
- EVM開發應用
- solidity ABI
- 附錄:用戶界面
- Geth, mist, Solc, Remix, Truffle
- 參考
介紹
區塊鏈

- 區塊鏈系統實際上可以看做一個基于交易的狀態機,
- 但是區塊鏈不被稱作狀態鏈,是因為只需要有區塊就能夠推出每一個狀態,
- 所以從區塊鏈系統實作的角度來看,稱其為區塊鏈更加合適,
世界狀態

- 世界狀態中簡化的存盤的結構就是賬戶不同的賬戶地址和不同的賬戶狀態,
- 這些內容就能夠很好的構建整一個區塊鏈體系,
賬戶

- 賬戶狀態中是可以存盤EVM代碼的
- 賬戶又分為兩種,一種是
Externally owned account(EOA)(個人擁有賬戶),一種是Contract account(合約賬戶), - 只有合約賬戶中存放著
EVM code和Storage兩個組件,

- EOA由私鑰控制,但是EOA不包含EVM代碼,
- 合約包含EVM代碼,并且由合約代碼控制,

- 賬戶的地址來源如下圖所示

交易
- 交易是一個經過加密簽名的指令,
- 交易的行為都是又一個獨有的個人和物體發起的,
- 交易有兩種類別,一種是
contract creation,另一種是Message call

- 在創建一個合約的時候,我們實際上執行了兩步操作:
- 創建了一個地址N和一個合約狀態N,
- 然后執行了合約的代碼,更新了合約中的storage,
- 一個交易的結構如下圖所示

資訊(Message)
- 資訊是在兩個不同的賬戶之間傳遞的內容,
- 資訊是一組資料(位元組)和價值(一臺),

- 訊息的傳遞總共只有四種情況,它們分別是

去中心化資料庫
- 所有人都擁有著世界狀態一個資料庫,整個區塊鏈是所有人共同擁有的,面向交易型的資料庫,
- 分布式的節點構成了以太坊P2P的網路,
- 個人通過Web3 API來修改以太坊的世界狀態,
原子性和順序
- 一個交易是一個原子性的交易,它無法再分或者被破壞,
- 所以交易符合All or nothing規則,即交易要不成立,要不就不存在,
- 交易是無法被覆寫的,這說明交易必須順序性的執行,
- 同時,交易的順序是無法被保證的,

- 礦工可以決定區塊交易的順序:

- 此時,其他的礦工可能由不同的打包順序,甚至包含著不同的交易資料,這時就得看最快完成PoW的礦工的選擇是怎么樣的,
虛擬機
以太坊虛擬機
- 以太坊虛擬機的核心邏輯如下所示

- 以太坊虛擬機是一個智能合約的運行時環境,
以太坊虛擬機架構

以太坊虛擬機空間構成

- 堆疊記憶體:1024個元素*256個位元,
- 記憶體:位元組尋址的線性記憶體,
- Storage: 可持久化存盤的記憶體,(256位元的key對應256bit的value)
堆疊

- 所有的操作都在堆疊中體現;
- 操作堆疊的指令有如PUSH/POP/COPY/SWAP等,
記憶體

- 記憶體是線性的并且可以在位元組程度進行尋址,
- 記憶體的處理指令有MSTORE/MSTORE8/MLOAD等,
- 所有的記憶體的位置都初始化為0,
賬戶存盤空間

- 存盤空間是一個鍵值存盤結構,他能夠映射256-bit words的鍵值對,
- 它通過SSTORE/SLOAD指令進行接入,
- 所有的位置都被初始化為0,
以太坊虛擬機代碼

- EVM代碼是一段位元組碼,只有EVM可以本地執行,
執行模型

訊息呼叫(Message call)
- EVM可以發送訊息給其他的用戶,其中包括合約用戶和EOA,
- 資訊呼叫的層級不能超過1024層,

- 首先EVMcode操控堆疊,堆疊再通過操控call message的指令通過arguments匯入到input data,input data通過再操控EVM的記憶體和堆疊,生成新的資料,再作為回傳值回傳給原先的EVM,
例外
- 以太坊虛擬機中經典的例外有
錯誤地址,錯誤指令,燃料不足,堆疊向下溢位等,

位元組順序
Endian for Memory(記憶體端位)

- EVM是大端尋址,也即第N個位置對應著第LSB個位置,
- BYTE指令是
從左到右,從MSB出發, SIGNEXTEND指令是從右到左,即從LSB出發,
Push動作的位元組順序

- PUSH動作從LSB往MSB打入,
指令集
- 有基本的256-bit的操作,
- 合約創建和銷毀
- CREATE, DELEGATECALL
- Hash
- SHA3
- 變換操作
- 使用MUL或者DIV, SDIV,
- DIV操作
- 并沒有非0的例外報錯,
幾種代碼復制的方法

- 這里面有從input data復制到堆疊的
CALLDATALOAD指令, - 又從input data復制到記憶體的
CALLDATACOPY指令, - 從EVM code向Memory傳送代碼的
CODECOPY指令, - 有從記憶體復制到下一個虛擬機代碼的
EXTCODECOPY指令,
來自MUL, DIV和SDIV的移動指令
- 位向左移動可以用MUL表示,
- MUL m(2^n)==m<<n
- 位向右移動可以用DIV或者SDIV表示,
- DIV m (2^n) == m>>n
- DIV用于邏輯右移,
- SDIV用于算數右移,
雜項
- Solidity原始碼,Viper原始碼和LLL原始碼經過編譯之后,都能夠得到
EVM的原始碼, - eWASM會作為下一代的虛擬機,
一些簡單的原始碼決議
[core/state/statedb.go]
type StateDB struct {
db Database
prefetcher *triePrefetcher
originalRoot common.Hash // The pre-state root, before any changes were made
trie Trie
hasher crypto.KeccakState
snaps *snapshot.Tree
snap snapshot.Snapshot
snapDestructs map[common.Hash]struct{}
snapAccounts map[common.Hash][]byte
snapStorage map[common.Hash]map[common.Hash][]byte
// This map holds 'live' objects, which will get modified while processing a state transition.
//↓下面這行存盤著整一個賬號狀態,包括上面的圖提到的所有內容
stateObjects map[common.Address]*stateObject
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
[core/state/state_object.go]
type stateObject struct {
address common.Address //專案地址
addrHash common.Hash
data Account // 賬戶狀態
db *StateDB
// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}
//EVM 代碼
type Code []byte
//...//
//賬戶存盤資訊
type Storage map[common.Hash]common.Hash
堆疊和記憶體
[core/vm/stack.go]
type Stack struct {
data []uint256.Int
}
func newstack() *Stack {
return stackPool.Get().(*Stack)
}
[core/vm/memory.go]
// Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct {
store []byte
lastGasCost uint64
}
// NewMemory returns a new memory model.
func NewMemory() *Memory {
return &Memory{}
}
指令集操作
[core/vm/instruction.go]
func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y := scope.Stack.pop(), scope.Stack.peek()
y.Add(&x, y)
return nil, nil
}
//...//
func opPop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.pop()
return nil, nil
}
//...//
//memory operation
func opMload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
v := scope.Stack.peek()
offset := int64(v.Uint64())
v.SetBytes(scope.Memory.GetPtr(offset, 32))
return nil, nil
}
//storage operation
func opMstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
// pop value of the stack
mStart, val := scope.Stack.pop(), scope.Stack.pop()
scope.Memory.Set32(mStart.Uint64(), &val)
return nil, nil
}
//...//
//information flow opration
func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
stack := scope.Stack
// Pop gas. The actual gas in interpreter.evm.callGasTemp.
// We can use this as a temporary value
temp := stack.pop()
gas := interpreter.evm.callGasTemp
// Pop other call parameters.
addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.Address(addr.Bytes20())
// Get the arguments from the memory.
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
var bigVal = big0
//TODO: use uint256.Int instead of converting with toBig()
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
// but it would make more sense to extend the usage of uint256.Int
if !value.IsZero() {
gas += params.CallStipend
bigVal = value.ToBig()
}
ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal)
if err != nil {
temp.Clear()
} else {
temp.SetOne()
}
stack.push(&temp)
if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
}
scope.Contract.Gas += returnGas
return ret, nil
}
創建EVM和進行交易
[core/state_processor.go]
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, err
}
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
return applyTransaction(msg, config, bc, author, gp, statedb, header, tx, usedGas, vmenv)
}
原始碼呼叫層級

參考檔案
- 以太坊黃皮書: https://ethereum.github.io/yellowpaper/paper.pdf
- 以太坊開發教程:https://github.com/ethereum/wiki/wiki/Ethereum-Development-Tutorial
- 以太坊虛擬機闡述:https://github.com/takenobu-hs/ethereum-evm-illustrated
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/277099.html
標籤:區塊鏈
下一篇:黑馬RabbitMQ高級學習筆記
