背景Alice和Bob已經在他們選擇的資料倉儲中共享資料,而智慧訪問控制著使用者訪問,這些訪問都透過網際網路連結在一起。
我們前面的例子描述了一種解決方案,透過該解決方案,智慧合約使使用者能夠將包含國際象棋棋子的檔案附加到資料夾中。
在那個例子中,Alice是第一個出棋的人,在本例中,可以改為Bob是第一個出棋的人。而且他們可能會玩不止一種遊戲。要實現這一點,需要修改智慧合約,即建立一個新合約。
但是由於資料倉儲的合約不能更改,因此建立新的智慧合約也需要新的資料倉儲。
這也可能意味著將舊的資料倉儲移動(複製和刪除)到新的資料倉儲,並更改對控制智慧合約的任何引用。
Alice和Bob希望能夠升級控制其資料倉儲的智慧合約,而不必移動資料倉儲並更新對新智慧合約的任何其他引用。
這意味著保持相同的資料倉儲,也要保持相同的智慧合約。
可升級資料共享示例
在這個例子中,我們介紹了一個可升級的智慧合約和一個路由器智慧合約。
對於我們的特定示例,由於智慧合約中的功能將由資料倉儲呼叫,因此,智慧合約必須符合datona-lib描述並指定為虛擬函式的智慧資料訪問合約(SDAC)介面規範,這一點很重要。在SDACinterface基本智慧合約中。
因此,我們不需要通用的可升級智慧合約,而是需要一個路由器智慧合約和一個可升級的SDAC。
透過路由器訪問的可升級智慧合約(控制)
而且,我們選擇僅可能的可升級SDAC:那些具有合約所有者和兩個帳戶使用者的sdac。顯然還有很多其他的可能性,比如一個合約所有者和一個其他帳戶使用者,或者多個帳戶使用者等等。最後由於上述參考文獻中的警告,我們還選擇避免在新的智慧合約中重複使用以前的智慧合約的資料。在可能的情況下,我們將資料儲存定位在鏈外,因為在鏈上儲存資料的成本很高。路由器“升級”到一個全新的智慧合約,包括程式碼和資料
這意味著我們必須使用實現SDAC介面的路由器智慧合約和可升級智慧合約的方法,該方法呼叫支援兩個使用者的可升級SDAC。· 建立可升級智慧合約下面是一個抽象的DuoSDAC,它將用於我們的示例可升級智慧合約,它支援一個合約所有者和兩個使用者: DuoSDACimport "SDACinterface.sol";abstract contract DuoSDAC is SDACinterface { address public owner = msg.sender; address public account1; address public account2; constructor( address _account1, address _account2 ) public { account1 = _account1; account2 = _account2; }}必須將其宣告為抽象智慧合約,因為它不提供SDACinterface中指定的虛擬函式的實現。我們可以重寫之前的資料共享智慧合約,然後跳入時間機器並壓縮幾周,這樣我們就可以部署這些智慧合約,而不是原來的智慧合約。以下是共享去中心化資料的示例解決方案,它簡化了變數和建構函式(現在在DuoSDAC中),但在其他方面沒有變化: DataShareSDACimport "DuoSDAC.sol";contract DataShareSDAC is DuoSDAC { bool terminated; constructor( address _account1, address _account2 ) public DuoSDAC(_account1, _account2) { } ...在上一篇文章中,我們已經已對其進行了更改,以方便使用多個遊戲資料夾,但在其他方面保持不變: ChessDataShareSDACimport "DataShareSDAC.sol";contract ChessDataShareSDAC is DataShareSDAC { uint gameFolder = 3; ... function getPermissions( address account, uint object ) public view override virtual returns (uint) { if (!terminated) { if (object == gameFolder) { ...這是本文的示例解決方案,它可以指定第一回合的使用者,並由回合的使用者開始新的遊戲,從而有效地讓步了當前遊戲: StartChessDataShareSDACimport "ChessDataShareSDAC.sol";contract StartChessDataShareSDAC is ChessDataShareSDAC { constructor( address _account1, address _account2, bool _turn2 ) public ChessDataShareSDAC(_account1, _account2) { turn2 = _turn2; } function newGame(uint _gameFolder) public { require(msg.sender == (turn2 ? account2 : account1)); require(gameFolder < _gameFolder); gameFolder = _gameFolder; }}· 建立路由器智慧合約我們現在已經編寫了幾個符合DuoSDAC介面的可升級智慧合約(上面的DataShareSDAC、ChessDataShareSDAC、StartChessDataShareSDAC)。我們將部署一個可升級智慧合約,然後升級到另一個可升級智慧合約。我們將使用路由器智慧合約呼叫可升級的智慧合約。由於路由器智慧合約中的功能將由資料倉儲呼叫,因此它必須符合datona-lib描述的智慧資料訪問合約(SDAC)介面規範。這些功能在下面的智慧合約中被標識為資料倉儲介面。升級策略取決於智慧合約作者決定升級許可權的位置。在許多可升級的智慧合約中,它由智慧合約所有者承擔(例如,請參閱開放Zeppelin升級)。在我們的案例中,由於升級後的智慧合約會影響兩個使用者共享的資料(順便說一句,合約所有者實際上並不可見),因此我們決定,智慧合約所有者和兩個使用者必須同意升級後的智慧合約,然後才能強制執行。我們將在下面的acceptUpgrade函式中強制執行該操作。如前所述,將透過將一個可升級的智慧合約替換為另一個可升級的智慧合約來升級完整的合約(程式碼和資料)。下面是一個路由器智慧合約的示例,它符合我們想要的資料倉儲儲存介面和升級策略: DuoSDACrouterimport "DuoSDAC.sol";contract DuoSDACrouter is SDAC { DuoSDAC duoSDAC; constructor( DuoSDAC _duoSDAC ) public { duoSDAC = _duoSDAC; } // ---------- data vault interface ---------- function hasExpired() public override view virtual returns (bool) { return duoSDAC.hasExpired(); } function terminate() public override virtual { duoSDAC.terminate(); } function getPermissions( address account, uint object ) public override view virtual returns (uint) { return duoSDAC.getPermissions( account, object ); } // ---------- upgrade policy ---------- event newContract(address indexed _from, address indexed _to, bytes32 _value); event updated(address indexed _from, address indexed _to, bytes32 _value); DuoSDAC newDuoSDAC; uint8 votes; function agreeUpgrade(DuoSDAC _duoSDAC) public returns (bool) { uint8 vote; // (1) if (msg.sender == duoSDAC.owner()) vote |= 1; if (msg.sender == duoSDAC.account1()) vote |= 2; if (msg.sender == duoSDAC.account2()) vote |= 4; require(vote != 0, "Unknown account"); // (2) if (newDuoSDAC != _duoSDAC) { newDuoSDAC = _duoSDAC; votes = vote; emit newContract(msg.sender, address(newDuoSDAC), "new contract"); } else { votes |= vote; // (3) if (votes == (1 + 2 + 4)) { terminate(); duoSDAC = newDuoSDAC; votes = 0; emit updated(msg.sender, address(newDuoSDAC), "contract agreed and updated"); return true; } } }}在我們之前的文章中已經描述過資料倉儲介面函式。這裡,我們集中討論路由器的函式。我們引入了一個必須由智慧合約所有者和兩個使用者以任何順序呼叫的函式acceptUpgrade。必須提供新的智慧合約作為引數。(1)之後的行確保為每個參與者的投票設定唯一的位。(2)之後的檢查可確保每當有人嘗試升級到潛在的新合約時,將清除表決集併發出事件。(3)之後的檢查確保了所有3個參與者都同意同一個新的智慧合約時,舊的智慧合約終止,設定了新的智慧合約,發出了一個事件並且函式返回true。合約測試為了測試解決方案合約,我們可以將其部署在區塊鏈上-一個測試網就可以了。但是任何DuoSDAC智慧合約建構函式都需要2個引數,即兩個使用者帳戶。為了協助自動化測試,我們可以建立代理使用者智慧合約來代表兩個使用者的帳戶。這是ProxyUser智慧合約的定義: ProxyUserimport "DuoSDACrouter.sol";contract ProxyUser { function agreeUpdate(DuoSDACrouter duoSDACrouter, DuoSDAC duoSDAC) public returns (bool) { return duoSDACrouter.agreeUpgrade(duoSDAC); }}測試合約本身會建立代理使用者智慧合約,然後建立路由器智慧合約,並提供一些測試功能: TestSDACrouterimport "DuoSDACrouter.sol";contract TestSDACrouter { ProxyUser user1 = new ProxyUser(); ProxyUser user2 = new ProxyUser(); DuoSDACrouter duoSDACrouter = new DuoSDACrouter(); function t1createRouter() public { DataShareSDAC dsSDAC = new DataShareSDAC(address(user1), address(user2)); duoSDACrouter = new DuoSDACrouter(dataShareSDAC); } function t2changeToDataShare() public { DataShareSDAC dsSDAC = new DataShareSDAC(address(user1), address(user2)); require(!duoSDACrouter.agreeUpgrade(dsSDAC)); require(!user1.agreeUpdate(duoSDACrouter, dsSDAC)); require(user2.agreeUpdate(duoSDACrouter, dsSDAC)); } function t3changeToChess() public { ChessDataShareSDAC cdsSDAC = new ChessDataShareSDAC(address(user1), address(user2)); require(!duoSDACrouter.agreeUpgrade(cdsSDAC)); require(!user1.agreeUpdate(duoSDACrouter, cdsSDAC)); require(user2.agreeUpdate(duoSDACrouter, cdsSDAC)); } function t4changeToStartChess() public { StartChessDataShareSDAC scdsSDAC = new StartChessDataShareSDAC(address(user1), address(user2), true); require(!user2.agreeUpdate(duoSDACrouter, scdsSDAC)); require(!user1.agreeUpdate(duoSDACrouter, scdsSDAC)); require(duoSDACrouter.agreeUpgrade(scdsSDAC)); } function t5changeBack() public { DataShareSDAC dsSDAC = new DataShareSDAC(address(user1), address(user2)); require(!user2.agreeUpdate(duoSDACrouter, dsSDAC)); // reset votes ChessDataShareSDAC cdsSDAC = new ChessDataShareSDAC(address(user1), address(user2)); require(!user1.agreeUpdate(duoSDACrouter, cdsSDAC)); // start voting again require(!duoSDACrouter.agreeUpgrade(dsSDAC)); require(!user2.agreeUpdate(duoSDACrouter, dsSDAC)); require(user1.agreeUpdate(duoSDACrouter, dsSDAC)); }}上面的示例提供了一個公共函式t1createRouter,它使用初始的DuoSDAC可升級智慧合約建立路由器智慧合約。公用功能t2changeToDataShare使用同一DuoSDAC可升級智慧合約的不同版本來升級路由器智慧合約。檢查同意更新的返回結果。公用函式t3changeToChess和t4changeToStartChess使用不同的DuoSDAC可升級智慧合約來升級路由器智慧合約。檢查同意更新的返回結果。公共函式t5changeBack使用初始DuoSDAC可升級智慧合約的新例項化升級路由器智慧合約,並測試投票。檢查agreeUpdate的返回結果。建議進行其他測試,以確保DuoSDAC可升級智慧合約的行為符合預期。氣體消耗量天下沒有免費的午餐。· 部署合約直接呼叫gas保險庫函式,而不是直接呼叫gas介面來消耗更多的資料。部署可升級智慧合約的總耗氣體量是部署智慧合約的正常耗氣體量,再加上升級智慧合約的耗氣體量,再加上以下數量的一部分(取決於升級合約的次數):部署路由器。
升級到DataShare(實際上是從初始DataShare)的耗氣體量比後續升級要高一些,因為這是第一次升級,因此涉及新的儲存位置。
· 使用合約
部署合同不僅消耗更多的氣體量,而且資料倉儲使用的公共函式也更加昂貴。
此圖說明了DataShare getPermissions函式的耗氣體量已從略高於5000的氣體增加到將近7500氣體。在更復雜的智慧合約中,相同函式的氣體消耗量也出現了類似的增長。
· 使用智慧合約
如t1createRouter所示,您必須先部署可升級的智慧合約,然後再部署路由器。您還需要建立一個資料倉儲並使用示例智慧合約。這些技術都在我們之前的文章中進行了討論。
· 部署智慧合約
· 建立資料倉儲
· 使用開始棋盤資料共享
結論
Alice和Bob以及其他許多人都希望能夠以分散的方式共享資料。這可能會使資料更安全、更私密、更可控。
但他們也希望能夠升級智慧合約而不必更改其資料保險庫。
我們已經展示了一個實用的解決方案,它使用智慧合約和資料倉儲的組合來實現這一點,這些都符合datona-lib中描述的介面規範。