<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 智能合約的存儲 將智能合約數據按照編碼規則映射成 MPT,然后將 MPT 的所有節點的 Key 和 Value 構建一個 RLP List 編碼作為數據庫存儲的 Value 值,將該 Value 值進行 Sha3 計算 hash 值作為數據庫存儲的 Key 值進行存儲。 ## 智能合約存儲布局 以太坊合約是經過 EVM 執行后,將從 KV 數據庫中讀寫。這和常見的編程語言的內存數據模型差異很大。 這是因為合約在創建或執行后必須方便存儲到KV 數據庫,也需要能擁有引用指針訪問數據能力。 在主流編程語言中,數據的訪問都可以通過指針訪問,在以太坊中,訪問數據是需要從 KV 數據中實時讀取,因此在訪問前必須知道某個數據確切的存儲位置。 如何讀寫數據是由合約編譯器決定,和 EVM 無關。這里我們討論以太坊**Solidity**智能合約編程語言所設計的數據存儲模型。 Solidity 合約數據存儲采用的是為合約每項數據指定一個可計算的存儲位置,數據存在容量為2256超級數組中,數組中每項數據的初始值為 0。 你不用擔心存儲會占用太多空間,實際上存儲是稀疏的。在存儲到 KV 數據庫中時只有非零(空值)數據才會被寫入。 ![](https://img.kancloud.cn/27/e6/27e639ee2f140983a2dae65e4cec676f_1096x874.png) 每個插槽可存儲 32 字節(256位)數據: ![](https://img.kancloud.cn/69/d8/69d814d0959810c845ac836fdc58f8c9_1374x322.png) 當某項數據超過 32 字節,則需要占用多個連續插槽(data.length/32)。 因此,當數據長度是已知時,則存儲位置將在編譯時指定存儲位置,而對于長度不確定的類型(如 動態數組、字典)則按一定規則計算存儲位置。 2256是一個超級大的數字,足夠容量合約需要任意大小的存儲。 ``` 2256是一個超級大的數字,等于232* 8,而 232約等于40 億。 如果你無法直觀理解的的話,可以做一個比較:地球上的沙子總數量大約為“7.5 * 1015”, 而2256等于1.158*1077,相當于五倍多的全地區沙子總量。 ``` ## 定長數據結構的存儲 在 Solidity 語言中,一部分的值類型所需要占用的存儲是確定的。 比如布爾類型,只需要占用一字節,uint16 只需要占用 2 字節。 Solidity 編譯器在編譯合約時,將嚴格的根據定義順序,依次給他們設定存儲位置。 ~~~solidity pragma solidity >0.5.0; contract StorageExample { uint8 public a = 11; uint256 b=12; uint[2] c= [13,14]; struct Entry { uint id; uint value; } Entry d; } ~~~ 上面合約中,有定義 a、b、c、d 四個存儲字段。a、b 分配在 0,1位置。 而字段 c 是定長 2 且元素類型是 uint,需要用 32 字節存儲一個元素,一共需要占用兩個插槽 2和 3。 字段 d 是一個結構類型數據,其中 Entry 的數據長度也是確定的 64 字節,因此字段 d 也占用兩個插槽 4 和 5。 ![](https://img.kancloud.cn/a2/2a/a22ac9f27f10f7c51b45cc2a3f64b874_1192x490.png) 當數據類型是值類型(固定大小的值)時,編譯時將嚴格根據字段排序順序,給每個要存儲的值類型數據預分配存儲位置。 相當于已提前指定了固定不變的數據指針。 部署上面合約,根據合約地址可以直接通過`eth_getStorageAt(contractAddress,slot)`API 獲取存儲數據。 ~~~js var contractAddr="0xe700184a875390d7c98371769315E9A2504Ad556"; # 我部署上方合約的合約地址。 for(i=0;i<6;i++){ console.log(web3.eth.getStorageAt(contractAddr,i)) } // 輸出 0x000000000000000000000000000000000000000000000000000000000000000b 0x000000000000000000000000000000000000000000000000000000000000000c 0x000000000000000000000000000000000000000000000000000000000000000d 0x000000000000000000000000000000000000000000000000000000000000000e 0x0000000000000000000000000000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000000000000000000000000000 ~~~ 上面是根據存儲位置遍歷合約的所有存儲, 存儲到 DB 的數據是十六進制,我們可以直接使用工具類函數轉換數據。 ~~~ web3.toBigNumber(web3.eth.getStorageAt(contractAddr,0)) // 輸出: 11 ~~~ ``` 注意:本文腳本運行在 geth 控制臺中,和 NodeJs 運行有所差異。geth 控制臺中有修改 web3 方法定義。 ``` ## 動態大小數據結構存儲 當數據大小是不可預知時,無法在編譯期直接確定其存儲位置。因此 Solidity 在編譯動態數字、字典數據時采用的是特定算法。 ### 字符串存儲 字符串 string 和 bytes 實際是一個特殊的 array ,編譯器對這類數據有進行優化。如果`string`和`bytes`的數據很短。那么它們的長度也會和數據一起存儲到同一個插槽。 具體為: 1. 如果數據長度小于等于 31 字節, 則它存儲在高位字節(左對齊),最低位字節存儲 length * 2。 2. 如果數據長度超出 31 字節,則在主插槽存儲 length * 2 + 1, 數據照常存儲在 keccak256(slot) 中。 ~~~solidity contract StorageExample3 { string a = "我比較短"; string b = "我特別特別長,已經超過了一個插槽存儲量"; } ~~~ 合約有兩個字段 a 和 b,他們所需要占用的存儲各不相同。根據規則一,a 的內容和長度一起存儲在插槽 0 中。 ``` data=web3.eth.getStorageAt('0x24aA059A03bC2f1EdC8412f673b6Bd3319A2c5CB',0) //輸出:`0xe68891e6af94e8be83e79fad0000000000000000000000000000000000000018` ``` a 占用存儲 12 (0x18/2) 字節,根據長度可解碼 a 的值: ~~~js web3.toUtf8( data.substr(2,12*2)) ~~~ 而字段 b 需要占用57 字節 (=web3.fromUtf8(‘我特別特別長,已經超過了一個插槽存儲量’).length/2 -1),已超過 31 字節。 那么將在插槽 1 中存儲值 115(= 57 * 2 + 1): “0x0000000000000000000000000000000000000000000000000000000000000073”。 而 b 值起始存儲在 keccak256(0x1) 中,需要使用連續兩個插槽存儲。 調用 SlotHelp 函數`dataSolot(1)`,得到 b 字符串的起始存儲位置:start=0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6。 而 b 字符串需要兩個插槽存儲,下一個存儲位置是 start +1 。 ~~~js b1 = web3.eth.getStorageAt('0x0a4Efc37f85023Fae282DE0c885669DaEF02E02A',"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6") //0xe68891e789b9e588abe789b9e588abe995bfefbc8ce5b7b2e7bb8fe8b685e8bf b2 = web3.eth.getStorageAt('0x0a4Efc37f85023Fae282DE0c885669DaEF02E02A',"0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7") //0x87e4ba86e4b880e4b8aae68f92e6a7bde5ad98e582a8e9878f00000000000000 str = web3.toUtf8(b1+b2.substr(2)) ~~~ ``` keccak256 是 Solidity 中合約中使用的 sha3 函數,不等同于 web3.sha 。 為了計算方便,我定義了一個 Solot 的幫助類來計算存儲位置,見文檔底部。 ``` ### 動態數組 動態數組`T[]`由兩部分組成,數組長度和元素值。在 Solidity 中定義動態數組后,將在定義的插槽位置存儲數組元素數量, 元素數據存儲的起始位置是:keccak256(slot),每個元素需要根據下標和元素大小來讀取數據。 ~~~solidity contract StorageExample4 { uint16[] public a = [401,402,403,405,406]; uint256[] public b = [401,402,403,405,406]; } ~~~ 上面有定義兩個數組 a 和 b,都有 5 個相同初始值。 a 和 b 在插槽 0 和 1 上分別存儲他們的長度值 5,而數組元素值存儲有所不同(緊縮存儲)。 因為數組 a 元素寬度(width)是 2 字節,因此一個插槽可以存儲 16 個元素,而數組 b 則只能是一個插槽存儲一個元素(uint256 需要用 32 字節存儲)。 上面有定義兩個數組 a 和 b,都有 5 個相同初始值。 a 和 b 在插槽 0 和 1 上分別存儲他們的長度值 5,而數組元素值存儲有所不同(緊縮存儲)。 因為數組 a 元素寬度(width)是 2 字節,因此一個插槽可以存儲 16 個元素,而數組 b 則只能是一個插槽存儲一個元素(uint256 需要用 32 字節存儲)。 已知: * keccak256(0)=0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 * keccak256(1)=0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 如果要獲取 a[3] 值,首先確認 a[3]的存儲位置: `keccak256(0)+ index* width / 32`= 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 可得到插槽中存儲的數據: data=0x0000000000000000000000000000000000000000000001960195019301920191 根據第一項以低位對齊(右對齊)的存儲方式,可以知道 a[3] 需要向左偏移`index*width`= 6 個字節,值為`data.substr(32*2+2-3*2*2,2*2)` ![](https://img.kancloud.cn/d2/d5/d2d53682eec3002849d6f3be0517e413_1828x498.png) ### 字典存儲 字典的存儲布局是直接存儲 Key 對應的 value,每個 Key 對應一份存儲。一個 Key 的對應存儲位置是`keccak256(key.slot)`,其中`.`是拼接符合,實際上編碼時進行拼接`abi.encodePacked(key,slot)`; 可直接獲得 map[key] 的存儲位置。 ~~~solidity contract StorageExample5 { mappping(uint256 => string) a; constructor()public { a["u1"]=18; a["u2"]=19; } } ~~~ 如上面示例中,字段 a 定義在 0 插槽,初始化合約時有添加兩個key u1 和 u2。那么 u1的存儲位置就是:`keccak256("u1",0)`,u2 存儲在`keccak256("u2",0)`中。調用`SlotHelp.mappingValueSlotString(0,"u1")`可計算出存儲位置,分別是: 1. keccak256(“u1”,0) = 0x666a0898319983ee51fdb14dca8cb63a131f53ef02192cda872152628bb15fd7 2. keccak256(“u2”,0) = 0xb8f3bac818d08a6d5c3fc2cecdc63de9db8e456c49b3877ea67282ec9d7ef62c 取值為: ~~~js addr="0xB793D15FF1e9F652D66a58E7C963c4c6766DA193" #部署后的合約地址 web3.eth.getStorageAt(addr,'0x666a0898319983ee51fdb14dca8cb63a131f53ef02192cda872152628bb15fd7') // Result // "0x0000000000000000000000000000000000000000000000000000000000000012" web3.eth.getStorageAt(addr,'0xb8f3bac818d08a6d5c3fc2cecdc63de9db8e456c49b3877ea67282ec9d7ef62c') // Result // "0x0000000000000000000000000000000000000000000000000000000000000013" ~~~ ### 組合型數據 當前數據類型不是基礎類型時,是進行內部遞歸處理,遵守上述規則來存儲數據的。 ### slot遵循緊湊存儲原則 區塊鏈存儲很昂貴,遵循緊湊存儲原則: 一大部分值類型實際上不需要用到 32 字節,如布爾型、uint1 到 uint256。 為了節約存儲量,編譯器在發現所用存儲不超過 32 字節時,將會將其和后面字段盡可能的存儲在一個存儲中。 ~~~solidity pragma solidity >0.5.0; contract StorageExample2 { uint256 a = 11; // 插槽 0 uint8 b = 12; // 插槽1,1 字節 uint128 c = 13; // 插槽1,16 字節 bool d = true; // 插槽1,1 字節 uint128 e = 14;//插槽2 } ~~~ 上面合約總共使用 3 個插槽存儲數據。 * 字段 a 需要 32 字節占用 1 個插槽,存于插槽 0 中。 * b 只需要 1 字節,存于插槽 1 中。 * 因為 插槽 1 還剩余 31 字節可用,而 c 只需要 16 字節,因此 c 也可以存儲在插槽 1 中。 * 此時,插槽 1 剩余 15 字節,可以繼續存放 d 的一字節。 * 插槽 1 還剩余 14 字節,但是 e 需要 16 字節存儲,插槽 1 已不能容納 e。需將 e 存放到下一個插槽 2 中。 [![合約的存儲布局](https://img.learnblockchain.cn/book_geth/2019-11-6-21-56-39.png!de?width=600px)](https://img.learnblockchain.cn/book_geth/2019-11-6-21-56-39.png!de?width=600px) 上圖是合約的存儲布局,被緊湊地存放在插槽 1 中的 b、c、d 他們將依次從右往左存儲于插槽 1 中。讀取插槽 1 中的數據得到 data 為: 0x0000000000000000000000000000010000000000000000000000000000000d0c 如果希望得到b、c、d 的值,則需要進行分割讀取。data 是一串 Hex 字符串,兩個字符代表一個字節。 ~~~js data = web3.eth.getStorageAt(contractAddr,1); b = parseInt(data.substr(66-1*2,1*2),16); c = parseInt(data.substr(66-1*2-16*2,16*2),16); d = parseInt(data.substr(66-1*2-16*2-1*2,1*2),16); ~~~ 鑒于這種緊湊存儲原則,有效降低了存儲占用。而因以太坊存儲是昂貴的,因此為了降低存儲占用, 你在編寫合約時,記得注意字段的**定義順序**。 比如在合約中的結構類型字段,依次是 uint256、uint8和 uint8,占用兩個插槽。如果你定義成: age、id、sex 順序,則將占用 3 個插槽。 ~~~solidity contract StorageExample3 { struct User { uint256 id; uint8 age; uint8 sex } } ~~~ 但這種機制也引發了**另一個問題**。因為以太坊虛擬機每次讀取數據都是 32 字節,當你的數據小于 32 字節時需要更多的指令操作才能將所需值取出。 如上面實例中,當你取 c 值時,首先要讀取插槽 1 的 32 字節數據外,還需要截取 32 字節的中間一小部分。 在使得相比取 32 字節值的數據,需要花費更多的 gas 來獲取小于 32 字節的數據。 當然這種開銷,相對于更多的存儲占用要便宜得多。 ### SlotHelp ~~~solidity pragma solidity >0.5.0; contract SlotHelp { // 獲取字符串的存儲起始位置 function dataSolot(uint256 slot) public pure returns (bytes32) { bytes memory slotEncoded = abi.encodePacked(slot); return keccak256(slotEncoded); } // 獲取字符串 Key 的字典值存儲位置 function mappingValueSlotString(uint256 slot,string memory key ) public pure returns (bytes32) { bytes memory slotEncoded = abi.encodePacked(key,slot); return keccak256(slotEncoded); } } ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看