在完成整個循環之前,在數組中搜尋較低的值會返回“Out of GAS”
我開發了一個將數據儲存在結構數組中的智能合約。另一個函式經常呼叫該函式來了解所有帳戶
getLowerLastDate()
的下級LastDate
和相關性。Wallet
問題是當
Acounts
數組有大約 500 條記錄時(只是。恐怕因為可能有數百萬條),事務被塊還原out of GAS
我不確定如何解決這個區塊鏈障礙,因為我需要知道
LastDate
正確的智能合約操作的下限。任何想法或提示表示讚賞。
struct StructAccount { uint256 Index; address Wallet; uint256 Balance; uint256 Time; uint256 RegDate; uint256 LastDate; } StructAccount[] public Accounts; function getLowerLastDate() public view returns (uint, address) { uint lowerDate = block.timestamp; uint Key = 0; for (uint i = 0; i < Accounts.length; i++) { if(Accounts[i].LastDate != 0){ if( Accounts[i].LastDate <= lowerDate ){ lowerDate = Accounts[i].LastDate; Key = i; } } } Accounts[Key].LastDate = 0; //reset and search the next lower LastDate return (lowerDate, Accounts[Key].Wallet) ; }
我不確定你是如何創建每個
StructAccount
的,但你可以創建一個名為 的新全域變數lowestLastDate
,並且每次創建一個StructAccount
時,將它與 的值進行比較lowestLastDate
,如果它較低,則將其設為 的新值lowestLastDate
。每當您想要訪問較低 LastDate 的值時,您都可以從變數中獲得它。這使您可以在 O(1) 時間內訪問最低的遲到日期,但將花費更多的氣體來初始化每個
StructAccount
我想我可以幫你解決這個問題。
您可以嘗試的第一種方法,也是最簡單的一種,是製作
Accounts
數組的本地/記憶體副本。因為在循環中讀取和讀取儲存會消耗大量氣體。檢查常見操作碼(如
SLOAD
,SSTORE
,MLOAD
, )的 gas 成本MSTORE
。所以,我會像這樣重構你的程式碼:
contract Contract { struct StructAccount { uint256 Index; address Wallet; uint256 Balance; uint256 Time; uint256 RegDate; uint256 LastDate; } StructAccount[] public Accounts; function getLowerLastDate() public returns (uint, address) { StructAccount[] memory accountsCopy = Accounts; uint lowerDate = block.timestamp; uint Key = 0; for (uint i = 0; i < accountsCopy.length; i++) { if(accountsCopy[i].LastDate != 0){ if( accountsCopy[i].LastDate <= lowerDate ){ lowerDate = accountsCopy[i].LastDate; Key = i; } } } // We can leave this line as is, since we need to update the state Accounts array at least once here. Accounts[Key].LastDate = 0; //reset and search the next lower LastDate return (lowerDate, accountsCopy[Key].Wallet); } }
請注意我是如何在數組的記憶體中製作副本的。這樣,從記憶體中讀取比從儲存中讀取更便宜。
此外,執行以下操作是完全有效的。複製一個狀態數組,對其進行操作,然後將其分配回狀態數組,一次。
contract Contract { uint256[] public numbers; uint256 public counter; function addNumber() public { numbers.push(counter++); } function doOperation() public { uint256[] memory counterCopy = numbers; for(uint256 i = 0; i < counterCopy.length; i++) { counterCopy[i] = counterCopy[i] * 2; } numbers = counterCopy; // This is valid. } }
現在,當您擁有如此多的記錄(數百萬?)時,這可能無法解決您未來的所有問題。如您所見,遍歷具有如此多記錄的數組並不理想,甚至在記憶體中也不理想。這將非常昂貴,而且無論如何您都可能會遇到氣體不足的異常。
這給我們帶來了第二種方法:
每次添加/更新時記錄
StructAccount
最低的實例。LastDate
以功能為例
addStructAccount
(我不知道這是否是您添加帳戶的方式,但這只是您可以做的一個範例)。請注意,我一直在檢查新帳戶是否具有較低的 LastDate,如果是,我將structWithLowestLastDate
狀態變數替換為它。我們也可以在更新任何帳戶時執行此操作。contract Contract { struct StructAccount { uint256 Index; address Wallet; uint256 Balance; uint256 Time; uint256 RegDate; uint256 LastDate; } StructAccount public structWithLowestLastDate; StructAccount[] public Accounts; function addStructAccount(uint256 index, address wallet, uint256 balance, uint256 time, uint256 regDate, uint256 lastDate) public { StructAccount memory account = StructAccount(index, wallet, balance, time, regDate, lastDate); // Keeping the account with the lowest LastData in a storage variable of its own, this way we don't need to look for it in the array if(lastDate < structWithLowestLastDate.LastDate) { structWithLowestLastDate = account; } // Adding the account to the storage array as usual. Accounts.push(account); } function getLowerLastDate() public returns (uint, address) { StructAccount[] memory accountsCopy = Accounts; uint lowerDate = block.timestamp; uint Key = 0; for (uint i = 0; i < accountsCopy.length; i++) { if(accountsCopy[i].LastDate != 0){ if( accountsCopy[i].LastDate <= lowerDate ){ lowerDate = accountsCopy[i].LastDate; Key = i; } } } // We can leave this line as is, since we need to update the state Accounts array at least once here. Accounts[Key].LastDate = 0; //reset and search the next lower LastDate return (lowerDate, accountsCopy[Key].Wallet); } }
也許這種方法對您也不起作用,因為我看到當
getLowerLastDate()
您重置它時,如果您再次呼叫它,您期望找到具有最低 LastDate 的下一個帳戶。為此,您可能希望實現 a
minHeap
,因此您始終將較低的 LastDate 帳戶作為第一個元素,並且您可以peek()
在恆定時間內或及時使用pop()
它O(log n)
。https://www.digitalocean.com/community/tutorials/min-heap-binary-tree
如果你要在智能合約中有這麼多記錄,也許你可以考慮將這些數據移動到正常後端/數據庫,如果它不需要去中心化等,或者移動到 IPFS。
或者,也許您可以有邏輯從您不再需要的數組中刪除數據。這樣,您可以將數組保持在合理的大小。
我將嘗試
minHeap
在 Solidity 中實現一個,看看它是如何工作的……好吧,我在這裡找到了一個最大堆實現:https ://github.com/Dev43/heap-solidity
您可以輕鬆地將其轉換為最小堆。