我們可以獲取儲存在合約映射中的所有元素嗎
我們可以使用 web3.js 獲取儲存在合約映射中的所有元素嗎?
映射不儲存它們的鍵,僅儲存在由鍵本身的 sha3 雜湊計算的狀態記憶體地址中的值。任何對映射的查找都必須提供該原始密鑰或能夠計算它。
這意味著合約無法在沒有幫助的情況下發現映射數據,這可能導致孤立數據使狀態數據庫膨脹,尤其是在合約為
selfdestructed
.我堅信合約作者應該對垃圾收集負責,並編寫全部可發現的數據合約,如果需要,可以在其中發現和刪除所有數據。對於映射,這需要以某種方式儲存或計算密鑰。
查找表
最簡單的密鑰儲存模式是一個簡單的查找表:
mapping (address => uint) public balances; address[] public addressLUT; // Requires a public getter for array size function size() public returns (uint) { return addressLUT.length; }
關聯的 JS 可以遍歷整個映射:
for(i = 0; i < k.size(); i++) { someFunc( k.balances(k.addressLUT(i)) ); }
數組的一個缺點是,當元素被刪除時,它們會隨著時間的推移而變得模糊。如果契約本身必須尋找這些差距,這可能會導致不可預測的天然氣成本。
鍊錶索引
另一種方法是鍊錶索引。
mapping (address => address) llIndex; mapping (address => uint) public balances; function add(address _addr) public { llIndex[_addr] = llIndex[0x0]; llIndex[0x0] = _addr; }
llIndex[0x0]
在這裡,我們在創建新余額時插入地址鍵。然後,我們可以通過逐步遍歷開始一個鍵的映射來迭代整個balance
映射,並且預設情況下給定第一個節點指向,我們不需要單獨的儲存來限制大小:llIndex``0x0``0x0
var current = k.llIndex(0); while (current) { console.log( k.balances(current) ); current = k.llIndex(current); }
連結列表不會像數組那樣遭受潛在的差距,因此不會浪費機器週期來尋找有效數據。但是,從單個鍊錶中刪除確實需要搜尋才能找到父節點。
function remove(address _addr) { address parent; // Warning: unbounded gas loop while (llIndex[parent] != _addr) parent = llIndex[parent]; llIndex[parent] = llIndex[ llIndex[parent]]; delete llIndex[_addr]; delete balances[_addr]; }
當您確信數據集不會增長到潛在搜尋使用不可接受的氣體量或完全導致 OOG 的大小時,這很好。這裡的“不可接受”有點主觀,所以我將用“比儲存槽的成本更多的氣體”來量化它,即新的 20000 和第二個和經銷商的 5000 ;)。
雙鍊錶索引
在這種情況下,以每個索引元素的額外儲存槽為代價,完全刪除搜尋循環是值得的。為此,我們可以使用雙鍊錶索引,使用嵌套
bool
映射來解釋我們解釋的雙向連結PREV == false
和NEXT == true
):mapping(address => ( mapping(bool => address) ) dllIndex; mapping(address => uint) balances; function add(address _addr) { // Link the new node dllIndex[_addr][PREV] = 0x0; dllIndex[_addr][NEXT] = dllIndex[0x0][NEXT]; // Insert the new node dllIndex[dllIndex[0x0][NEXT]][PREV] = _addr; dllIndex[0x0][NEXT] = _addr; } function remove(address _addr) { // Stitch the neighbours together dllIndex[ dllIndex[_addr][PREV] ][NEXT] = dllIndex[_addr][NEXT]; dllIndex[ dllIndex[_addr][NEXT] ][PREV] = dllIndex[_addr][PREV]; // Delete state storage delete dllIndex[_addr][PREV]; delete dllIndex[_addr][NEXT]; delete balances[_addr]; }
我們現在有一個可直接定址且完全可迭代的儲存結構,它不僅是一個雙鍊錶,而且預設情況下是一個循環雙鍊錶,頭部為
0x0
. 這為我們免費提供了兩個非常理想的屬性…FIFO 和 FILO 隊列
先進先出(FIFO) 和先進後出(FILO) 隊列是計算中非常常見的概念。FIFO 可用於任務隊列,而 FILO 也可用於記憶體“堆棧”。
FIFO 可用於任何需要原子順序處理的合約。在這裡,一個新訂單簡單地插入到頭部之前,而最舊的訂單只是從頭部旁邊獲取。不需要搜尋循環。
如果您認為這些結構可能有用,請查看我的內在可交易代幣 (ITT)合約使用的循環雙鍊錶索引庫。