如何將JSON文件存儲在IPFS上,并使用Oraclize訪問智能合約中的數(shù)據(jù)呢?
以太坊是一個成熟的區(qū)塊鏈,使開發(fā)人員能夠創(chuàng)建智能合約,在區(qū)塊鏈上執(zhí)行的程序可以由交易觸發(fā)。人們經常將區(qū)塊鏈稱為數(shù)據(jù)庫,但使用區(qū)塊鏈作為數(shù)據(jù)存儲非常昂貴。
以目前的價格(530美元,4gwei)在以太坊上存儲250GB將花費你106,000,000美元。一般來說,我們可以忍受高成本因為我們:
不會在以太坊區(qū)塊鏈上保存那么多數(shù)據(jù)。
區(qū)塊鏈的審查制度,透明度和穩(wěn)健性是值得的。
如果你是以太坊的新手,請查看此介紹。
去中心化存儲
IPFS(星際文件系統(tǒng))對區(qū)塊鏈存儲有一些保證,即去中心化和防篡改,但不比傳統(tǒng)的磁盤空間花費更多費用。使用EBS 250GB存儲運行EC2 t2.micro實例將花費你大約15美元/月。IPFS的一個獨特功能是它處理文件的方式。它不使用基于位置的尋址(如域名,IP地址,文件路徑等),而是使用基于內容的尋址。將文件(或目錄)添加到IPFS存儲庫后,可以通過其加密哈希來引用它。
$ipfsaddarticle.json addedQmd4PvCKbFbbB8krxajCSeHdLXQamdt7yFxFxzTbedwiYMarticle.json $ipfscatQmd4PvCKbFbbB8krxajCSeHdLXQamdt7yFxFxzTbedwiYM {"title":"Thisisanawesometitle","content":"paragraph1rnrnparagraph2"} $curlhttps://ipfs.io/ipfs/Qmd4PvCKbFbbB8krxajCSeHdLXQamdt7yFxFxzTbedwiYM{"title":"Thisisanawesometitle","content":"paragraph1rnrnparagraph2"}
然后,你可以使用IPFS客戶端或任何公共網關訪問文件。你還可以創(chuàng)建非公共網關,默認情況下使其成為可寫(只讀),并實現(xiàn)授權方案,以便以編程方式訪問IPFS網絡。
重要的是要了解IPFS不是一種服務,其他節(jié)點將存儲你的內容。如果你的內容不受歡迎,如果他們沒有固定哈希(他們不想租用磁盤空間),垃圾收集器會將其從其他節(jié)點中刪除。只要網絡上至少有一個對等體確實關心你的文件并且有興趣存儲它們,網絡上的其他節(jié)點就可以輕松獲取該文件。即使你的文件從網絡中消失,也可以在以后再次添加,除非其內容發(fā)生更改,否則其地址(哈希)將相同。
IPFS和以太坊智能合約
盡管以太坊協(xié)議沒有提供任何連接到IPFS的本地方式,但我們可以回到像Oraclize這樣的離線解決方案來解決這個問題。Oraclize允許使用各種數(shù)據(jù)提供智能合約。其中一個可用的數(shù)據(jù)源是URL。我們可以使用公共網關從IPFS上的JSON文件中讀取。依靠單個網關會很單薄。我們將要使用的另一個數(shù)據(jù)源是IPFS。通過使用JSON解析器(它是查詢的一部分)讀取Oraclize智能合約,我們可以在JSON文檔中提取特定字段。
oraclize_query("IPFS","json(Qmd4PvCKbFbbB8krxajCSeHdLXQamdt7yFxFxzTbedwiYM).title"));
如果Oraclize可以在20秒內獲取文件,則可以預期獲得異步請求。如果使用連接良好的節(jié)點上傳文件,則不需要關注超時。我們的EC2(歐盟法蘭克福)實例連接到大約750個同行。通過公共網關或本地運行守護進程獲取文件幾乎是即時的。響應是異步的,oraclize_query調用返回查詢id(bytes32)。你可以將其作為來自Oraclize的數(shù)據(jù)的標識符。
function__callback(bytes32_queryId,string_data)public{require(msg.sender==oraclize_cbAddress()); process_data(_data); }
出于安全原因,我們希望確保只允許Oraclize調用__callback函數(shù)。
你可以在GitHub上找到博客示例的完整代碼庫:tooploox/ipfs-eth-database
性能和實施
最初,我很擔心性能表現(xiàn)。它是否可以像集中服務發(fā)送響應一樣快速地獲取IPFS上托管的JSON文件?結果令我很驚喜。
$wrk-d10shttps://ipfs.io/ipfs/Qmd4PvCKbFbbB8krxajCSeHdLXQamdt7yFxFxzTbedwiYM Running10stest@https://ipfs.io/ipfs/Qmd4PvCKbFbbB8krxajCSeHdLXQamdt7yFxFxzTbedwiYM 2threadsand10connections ThreadStatsAvgStdevMax+/-Stdev Latency59.18ms24.36ms307.93ms94.73% Req/Sec86.3415.48101.0085.57% 1695requestsin10.05s,1.38MBreadRequests/sec:168.72 Transfer/sec:140.70KB
在我們審查博客時,作者必須在智能合約上調用addPost時僅輸入IPFS哈希值。我們使用IPFS和Oraclize從文件中讀取標題,以使用以太坊事件存儲它。我們不需要為其他智能合約保留標題,因此使用事件對于我們的用例來說已經足夠了。這可能不是最具開創(chuàng)性的例子,但很好地展示了如何優(yōu)化低交易費用。
pragmasolidity0.4.24; import"openzeppelin-solidity/contracts/ownership/Ownable.sol"; import"./lib/usingOraclize.sol"; import"./lib/strings.sol"; contractBlogisusingOraclize,Ownable{usingstringsfor*; mapping(address=>string[])publichashesByAuthor; mapping(bytes32=>string)publichashByQueryId; mapping(bytes32=>address)publicauthorByHash;eventPostAdded(addressindexedauthor,stringhash,uinttimestamp,stringtitle);eventPostSubmitted(addressindexedauthor,stringhash,bytes32queryId);uintprivategasLimit; constructor(uint_gasPrice,uint_gasLimit)public{ setCustomOraclizeGasPrice(_gasPrice); setCustomOraclizeGasLimit(_gasLimit); }functiongetPrice(string_source)publicviewreturns(uint){returnoraclize_getPrice(_source); }functionsetCustomOraclizeGasPrice(uint_gasPrice)publiconlyOwner{ oraclize_setCustomGasPrice(_gasPrice); }functionsetCustomOraclizeGasLimit(uint_gasLimit)publiconlyOwner{ gasLimit=_gasLimit; }functionwithdraw()publiconlyOwner{ owner.transfer(address(this).balance); } function__callback(bytes32_queryId,string_title)public{ require(msg.sender==oraclize_cbAddress()); require(bytes(hashByQueryId[_queryId]).length!=0);stringmemoryhash=hashByQueryId[_queryId]; addressauthor=authorByHash[keccak256(bytes(hash))]; hashesByAuthor[author].push(hash);emitPostAdded(author,hash,now,_title); }functionaddPost(string_hash)publicpayablereturns(bool){ require(authorByHash[keccak256(bytes(_hash))]==address(0),"Thispostalreadyexists"); require(msg.value>=oraclize_getPrice("IPFS"),"Thefeeistoolow"); bytes32queryId=oraclize_query("IPFS","json(".toSlice().concat(_hash.toSlice()).toSlice().concat(").title".toSlice()),gasLimit); authorByHash[keccak256(bytes(_hash))]=msg.sender; hashByQueryId[queryId]=_hash;emitPostSubmitted(msg.sender,_hash,queryId);returntrue; }functiongetPriceOfAddingPost()publicviewreturns(uint){returnoraclize_getPrice("IPFS"); } }
前端使用Web3讀取事件,并為給定作者構建所有博客帖子的列表。
降價商品的內容也存儲在IPFS上。它允許保留添加新博客帖子的固定費用。我們使用一系列公共IPFS,從我們自己開始。這有意義,尤其是當您從同一節(jié)點上傳文件時。如果您決定以寫入模式運行網關,則還可以以編程方式固定文件(默認情況下,它是只讀的)。我們還允許用戶指定自己的網關。 如果用戶安裝了IPFS Companion,他可以利用自己的節(jié)點運行。
BlogEvents.getPastEvents("PostAdded",{fromBlock:0,filter:{author}}).then(events=>{this.setState({addedPosts:events.map(e=>e.returnValues)}); });//...getPost(gatewayIndex=0){this.fetchPostFromIpfs(gateways[gatewayIndex]) .catch(()=>this.retry(gatewayIndex)) }
結論
我們從以太坊智能合約中請求IPFS數(shù)據(jù)的小實驗讓我們深入了解IPFS性能,并為更多生產用例的進一步實施奠定了基礎。
性能問題唯一顧慮的地方可能是IPNS。IPNS是IPFS的命名系統(tǒng),允許可變URL。hash對應于對等ID而不是文件或目錄內容hash。在版本0.4.14中引入的新IPNS解析器和發(fā)布者已經緩解了一些問題。確保你擁有最新版本并使用-enable-namesys-pubsub選項運行守護程序,以便從幾乎即時的IPNS更新中受益。