以太坊: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可以超過儲存資料的末尾。
讀取時,如果被讀資料超出了末尾指標的範圍,則會以塊為單位增加塊數,也就是說讀到的新增資料是
public void write(int address, byte[] data, int dataSize, boolean limited)
將data的dataSize位元組寫到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)結構
直接繼承自Java的Stack:
protected Object[] elementData;
只是item是DataWord,即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資料庫,並提供了批量提交和讀、寫快取,並以此為基礎提供了區塊執行過程中的多級快取。具體的講解見另一篇文章。