1. 程式人生 > >以太坊:EVM的儲存結構

以太坊:EVM的儲存結構

版權宣告:本文系博主原創,未經博主許可,禁止轉載。保留所有權利。

引用網址:https://www.cnblogs.com/zhizaixingzou/p/10124209.html

 

 目錄

 

1. EVM的儲存結構

1.1. VM記憶體

記憶體結構像堆疊一樣,也提供了資料快取的功能,但更作用的是提供了合約呼叫合約等過程中,子合約資料的臨時儲存。

 

1)實現類

public class Memory implements ProgramListenerAware

 

2)結構

private List<byte[]> chunks = new LinkedList<>();

記憶體就是一個連結串列。

連結串列的每個結點對應一個記憶體塊,一個記憶體塊1024位元組,鏈塊記憶體是可擴充套件的,擴充套件是以塊為單位的。

private int softSize;

在記憶體中,有一個指標softSize,用於指示儲存資料的末尾。

 

3)外部介面

public byte[] read(int address, int size) {

從記憶體的address(塊抹平了的位元組偏移量,從0開始,如1025代表第1塊第1個位置)開始讀取size個位元組的資料。

address可以超過儲存資料的末尾。

讀取時,如果被讀資料超出了末尾指標的範圍,則會以塊為單位增加塊數,也就是說讀到的新增資料是

0。而末尾指標則以字,即32位元組為單位移動,最終移動到被讀資料的末尾。

 

public void write(int address, byte[] data, int dataSize, boolean limited)

datadataSize位元組寫到address上。

dataSize如果大於data長度,則最多寫data長度個位元組。

limited表示是否根據末尾指標截斷,如果不,即不限制,則擴充套件記憶體,寫入dataSize個位元組,否則寫入從address到末尾的位元組個數。

 

public void extendAndWrite(int address, int allocSize, byte[] data) {

擴充套件然後寫入資料。

 

public void extend(int address, int size) {

擴充套件記憶體,擴充套件了後末尾指標就變化。

 

public DataWord readWord(int address) {

從指定位置讀取32個位元組的字。

 

public byte readByte(int address) {

從指定位置讀取一個位元組。

 

public int size() {

獲取末尾指標的位置。

 

public int internalSize() {

獲取記憶體以分配空間,即連結串列結點數乘以1024

 

public List<byte[]> getChunks() {

獲取記憶體副本。

 

public String toString() {

以列印格式(含16進位制和assii碼形式)返回記憶體資料,softSize結尾。

 

4)程式監聽

programListener.onMemoryExtend(toAllocate)

programListener.onMemoryWrite(address, data, dataSize)

在記憶體擴充套件(這裡的擴充套件指應的字數)和寫資料是會觸發監聽器。

1.2. VM堆疊

EVM的執行模型是基於棧結構的,像JVM一樣,這裡提供的堆疊就是用來儲存位元組碼執行過程中的中間資料等。

 

1)實現類

public class Stack extends java.util.Stack<DataWord> implements ProgramListenerAware {

 

2)結構

直接繼承自JavaStack

protected Object[] elementData;

只是itemDataWord,即32位元組的字。

可以看到這是一個定長陣列,在需要擴充套件時會建立新陣列,然後淺拷貝元素到新陣列。

以太坊棧深設定為1024

 

3)外部介面

public synchronized DataWord pop() {

彈出資料。

 

public DataWord push(DataWord item) {

入棧資料,返回就是item

 

public void swap(int from, int to) {

交換兩個位置的資料。

 

4)程式監聽

programListener.onStackPop();

programListener.onStackPush(item);

programListener.onStackSwap(from, to);

 

1.3. VM持久化儲存

持久化儲存主要是獨立於區塊儲存之外,儲存以太坊的賬戶、合約程式碼以及合約的狀態。

1.3.1. 賬戶

org.ethereum.core.AccountState

可用來表示外部賬戶或合約賬戶。

nonce、餘額、狀態根、程式碼雜湊。

 

private final BigInteger nonce

對於外部賬戶,nonce表示從此賬戶發出的交易數量。

對於合約賬戶,nonce表示此合約內建立的合約數量。

設計nonce的目的是為了防止重放攻擊,也就是防止一個交易被多次執行,因為每執行一個交易時,庫中該交易傳送者賬戶的nonce就會增加1

預設值是從配置常量取出的0

 

private final BigInteger balance

賬戶的餘額,以Wei為單位。

預設為0

 

private final byte[] stateRoot

用於儲存合約內容Trie結構的雜湊根。

預設是sha3(RLP.encodeElement(EMPTY_BYTE_ARRAY))

 

private final byte[] codeHash;

合約程式碼的雜湊值。

預設值是sha3(EMPTY_BYTE_ARRAY)

 

提供了RLP編碼和解碼方法,以及編碼結果快取。

只要程式碼雜湊或nonce不是預設的,則認為合約是存在的。

所謂空賬戶,是指程式碼雜湊、nonce、餘額都是預設的。空賬戶是需要被刪除的。

1.3.2. 資料來源結構體系

這裡的Repository內部實現是比較複雜的,它封裝了底層Key-Value資料庫,並提供了批量提交和讀、寫快取,並以此為基礎提供了區塊執行過程中的多級快取。具體的講解見另一篇文章。