當前位置:
首頁 > 最新 > 區塊鏈開發實戰:如何從零打造一個去中心化應用

區塊鏈開發實戰:如何從零打造一個去中心化應用

編譯 | 區塊鏈大本營

整理 | reason_W

區塊鏈會是一場革命嗎?

今天,很多成功的互聯網企業都是在以中介者的形式存在。比如Google——你和互聯網之間的中介,比如亞馬遜——買家和賣家之間的中介。但區塊鏈——以「去中心化」為最基本特徵的技術的發展卻很有可能破壞這些互聯網巨頭賴以成功的結構。

互聯網巨頭的事咱們暫且不操心,那麼從一個開發者的角度呢?我們需要關心什麼呢?作為一個開發者,我們該如何使用區塊鏈構建應用程序? 我們可以在這麼複雜的基礎概念之上創造出非常易用的工具嗎?這個工具的開發體驗又有多友好呢?

我們知道,最好的教程就是帶你去從頭開發一個應用程序。所以我們將使用區塊鏈技術製作一個簡單的去中心化廣告伺服器,稱為「零美元主頁」。本文將告訴你一群開發者在摸索使用區塊鏈技術時最完整的試錯經過和心路歷程

什麼是區塊鏈應用?

要做區塊鏈應用,首先要知道區塊鏈有什麼特性。而它最突出的特性就是「去中心化」。簡單來說,就是不再像以前的技術一樣需要一個中心節點負責管理,而是通過一系列複雜且可靠的技術,由網路中所有的節點共同認可,共同記錄,共同維護,解決網路中交易雙方的信任問題。這是一條神奇的鏈,有了這個鏈就真的可以實現沒有中間商賺差價了。

回到正文,區塊鏈正在以去中心化的特質威脅著互聯網巨頭們的基礎。而本文將集中於廣告平台。廣告平台是廣告位購買方和廣告渠道提供商之間的中介。我們的項目是通過區塊鏈技術建立一個去中心化的廣告平台。也就是跳過廣告中間商,通過利用區塊鏈技術本身的特性解決在互聯網環境中進行交易時相互之間的信任問題。

自著名的百萬美元主頁實驗以來,想要在付費廣告領域通過創新變的富有已經越來越困難了。

2005年,一個21歲的英國男孩Alex創建了一個名為「百萬美元主頁」的網站,將頁面做成100萬個小格子組成的廣告牌,每個格子售價1美元。這一別出心裁的創意吸引了不少廣告商注意,不到一年時間內,100萬個格子全部售出。

相反,我們選擇建立一個允許免費展示廣告的平台——零美元主頁。 免費,但並不代表什麼都不付出:廣告商需要通過開源貢獻來換取廣告位。因此,我們構建了一個去中心化的應用來管理廣告在特定網頁上的展示方式。 廣告商需要具備編程能力,以便能夠將他們的廣告放在此頁面上。

用戶工作流程

具體而言,當我們在marmelab的開源庫(https://github.com/marmelab)之一合併Pull請求(PR)時,會有一個GitHub機器人對PR進行評論,並邀請PR作者在廣告平台管理面板上發布他們的廣告。

打開評論中包含的鏈接之後,PR作者會被要求使用他們的GitHub憑證進行登錄。然後,他們可以上傳廣告——實際上就是一張簡單的圖片。此圖片將按照時間順序添加到同樣由其他PR作者上傳的圖片隊列中。

每天半夜的時候,腳本都會自動地獲取下一個圖像(使用先進先出排序),並在接下來的24小時內將其顯示在http://marmelab.com/ZeroDollarHomepage/上。

注意:這整個過程不需要中介參與,但為了避免在網站上出現成人內容,我們會在圖像上傳之後,發布之前通過Google Vision API進行驗證。

應用架構

以下是我們廣告平台的四步的架構:

開源貢獻者通知只要開源PR合併到了我們的某個存儲庫中,GitHub就會將PR詳細信息發送給負責管理的app。app會在PR下方進行評論以通知貢獻者。該評論包含一個可以進入管理界面的鏈接,以及PR的詳細信息。

聲明和圖片上傳在打開評論鏈接之後,貢獻者會轉到管理員界面。他必須使用他的GitHub憑證登錄才能通過身份驗證。管理程序然後會通過GitHub獲取PR詳細信息,並檢查貢獻者是否是PR作者。如果沒有問題,管理程序會顯示圖片上傳欄。當貢獻者上傳圖片時,管理程序會將PR ID推送到區塊鏈,並將圖片上傳到CDN(以PR id命名)。管理程序同時會根據區塊鏈中仍在等待發布的圖像的有效PR數量計算並顯示圖像發布的大致日期。

廣告位置每隔24小時,cron(一個執行周期性任務計劃的程序)就會要求區塊鏈查找尚未發布的下一個PR。 區塊鏈將此PR標記為已發布並發送該ID。 cron將以PR id命名的圖像重命名為「current image」。

廣告顯示每次訪問者想要在「零美元主頁」中發布廣告,該主頁都會向CDN詢問當前圖像。如果它恰好是區塊鏈上最新發布的廣告,那麼該廣告就會至少保留1天(直到另一個貢獻者聲明了一個PR為止)。

看起來有點意外的是,區塊鏈在這個過程中起著非常小的作用。這是因為在實際編程過程中,我們發現無法把廣告平台的整個代碼都建立在區塊鏈之上。事實上,區塊鏈在連接互聯網和處理方面的能力都非常有限。所以我們只用區塊鏈來實現最關鍵的廣告投放任務:

註冊經過認證的貢獻者的Pull請求

獲取最後一次沒有發布的Pull請求,並將其標記為發布

由於各種原因,管理程序中的其他任務最終沒有使用區塊鏈:

從webhook對Pull的請求進行註冊在Pull請求之前進行註冊是沒有用的,因為Pull作者可能從未聲明它。此外,將數據存儲在區塊鏈中並不是沒有代價的,所以我們只存儲我們必須存儲的數據。但這樣做的缺點是,我們的公共軟體倉庫(包括在此實驗之前創建的公共軟體倉庫)中的任何公共資源都有資格進行下一步操作。

通過在GitHub發布評論來通知用戶智能合約不能調用外部API,所以我們不能通過這項技術來實現本項任務。因此,我們將此任務交給管理程序來完成。

驗證聲明的PR的作者同樣是因為智能合約不能調用GitHub API。所以,我們也把這個邏輯任務移至管理程序,並將其作為調用區塊鏈的先決條件。

存儲圖像理論上,我們可以將任何東西存儲在區塊鏈中,包括圖像。 但實際而言,圖像需要很多存儲空間,而且我們沒有辦法在我們的智能合約中存儲多個「表格」(數據陣列)。

將顯示的廣告更新為隊列中的下一個區塊鏈中沒有與setTimeout函數或cron工具等類似的功能。然而,你可能每隔x個區塊就需要執行一次代碼,不過這與時間無關。所以,我們在API上使用了類似cron的庫。

研究,記錄和第一次嘗試

考慮到區塊鏈網路的成熟度和設計目的,我們在選擇要開發的區塊鏈網路時最終選擇了以太坊。

很快,我們就遇到了第一個困難。直到幾個星期前,哪怕只是簡單的測試,如果沒有購買以太幣,我們也無法使用以太坊區塊鏈。此外,以太坊在之前的版本(名為Frontier)中並沒有真正許可私有鏈,這就使開發變得非常複雜。因為沒有私有鏈,只能在公共網路上進行開發,任何訪問以太坊網路的人都可能會調用你的測試合約。更重要的是,這份文件只是一個志願倡議,並且與發展狀態不同步。

註:在我們開始開發程序後,以太坊就發布了新版本,從Frontier切換到Homestead,並且改善了Homestead的文檔質量。

Homesteadhttp://www.ethdocs.org/en/latest/introduction/contributors.html

儘管存在這些缺陷,我們仍然在Nancy,Paris和Dijon的乙太網網路上成功註冊了三個節點,並在這些節點之間共享ping。

在文檔搜索中,我們最終找到了 Eris 文檔(https://docs.erisindustries.com/)。Eris在解釋區塊鏈和合約方面做得非常出色。此外,他們專門在以太坊之上建立了一個層級,並開源了一系列工具以簡化智能合約的開發過程。

Eris是一個命令行工具,你可以使用它來初始化你需要的任意數量的本地區塊鏈。

如何操作智能合約

智能合約與API非常相似。它有幾個公共函數,可以被在區塊鏈網路上註冊過的任何人調用。但與API不同的是,智能合約不能調用外部Web API(區塊鏈是封閉的生態系統)。但是,智能合約可能會調用其他智能合約,只要知道他們的地址。

與API一樣,公共函數只是它們的冰山一角。實際上合約可能由許多私有函數,變數等組成。

智能合約按照以太坊專有的二進位格式託管在區塊鏈中,可由以太坊虛擬機執行。用於編寫合約的語言和編輯器有:

Serpent,跟Python很像(https://github.com/ethereum/wiki/wiki/Serpent)

Solidity,跟Javascript很像(http://solidity.readthedocs.org/en/latest/)

在marmelab中,我們已經使用Javascript編寫了很多代碼,因此在編寫合約時,我們選擇了與Javascript類似的Solidity。Solidity合約存儲在.sol文件中。

「零美元主頁」合約

「零美元主頁」合約存儲了已聲明的Pull請求,以及待發布的請求隊列。 Solidity合約的第一個版本如下所示:

// in src/ethereum/ZeroDollarHomePage.sol

contract ZeroDollarHomePage {

uintconstant ShaLength =40;

enum ResponseCodes {

Ok,

InvalidPullRequestId,

InvalidAuthorName,

InvalidImageUrl,

RequestNotFound,

EmptyQueue,

PullRequestAlreadyClaimed

}

structRequest {

uintid;

stringauthorName;

stringimageUrl;

uintcreatedAt;

uintdisplayedAt;

}

// what the contract stores

mapping (uint=> Request) _requests;// key is the pull request id

uintpublic numberOfRequests;

uint[] _queue;

uintpublic queueLength;

uint_current;

address owner;

// constructor

function ZeroDollarHomePage() {

owner = msg.sender;

numberOfRequests =;

queueLength =;

_current =;

}

// a contract must give a way to destroy itself once uploaded to the blockchain

function remove() {

if(msg.sender == owner){

suicide(owner);

}

}

// the following three methods are public contracts entry points

function newRequest(uintpullRequestId,stringauthorName,stringimageUrl) returns (uint8code,uintdisplayDate) {

if(pullRequestId

// Solidity is a strong typed language. You get compilation errors when types mismatch

code =uint8(ResponseCodes.InvalidPullRequestId);

return;

}

if(_requests[pullRequestId].id == pullRequestId) {

code =uint8(ResponseCodes.PullRequestAlreadyClaimed);

return;

}

if(bytes(authorName).length

code =uint8(ResponseCodes.InvalidAuthorName);

return;

}

if(bytes(imageUrl).length

code =uint8(ResponseCodes.InvalidImageUrl);

return;

}

// store new pull request details

numberOfRequests +=1;

_requests[pullRequestId].id = pullRequestId;

_requests[pullRequestId].authorName = authorName;

_requests[pullRequestId].imageUrl = imageUrl;

_requests[pullRequestId].createdAt = now;

_queue.push(pullRequestId);

queueLength +=1;

code =uint8(ResponseCodes.Ok);

displayDate = now + (queueLength *1days);

// no need to explicitly return code and displayDate as they are in the method signature

}

function closeRequest() returns (uint8) {

if(queueLength ==) {

returnuint8(ResponseCodes.EmptyQueue);

}

_requests[_queue[_current]].displayedAt = now;

delete_queue[];

queueLength -=1;

_current = _current +1;

returnuint8(ResponseCodes.Ok);

}

function getLastNonPublished() returns (uint8code,uintid,stringauthorName,stringimageUrl,uintcreatedAt) {

if(queueLength ==) {

code =uint8(ResponseCodes.EmptyQueue);

return;

}

varrequest = _requests[_queue[_current]];

id = request.id;

authorName = request.authorName;

imageUrl = request.imageUrl;

createdAt = request.createdAt;

code =uint8(ResponseCodes.Ok);

}

}

在第一次嘗試時,我們使用Eris JS庫與我們的區塊鏈進行通信。從Node.js文件中解析合約就變得非常簡單:

importerisfrom"eris";

functiongetContract(url, account){

constaddress =// Read address file stored on disk by the eris CLI;

constabi =// Read abi file stored on disk by the eris CLI;

constmanager = eris.newContractManagerDev(url, account);

returnmanager.newContractFactory(abi).at(address);

}

而且它的調用也不困難:

function*newRequest(pullrequestId, authorName, imageUrl){

constcontract = getContract(url, account);

// First gotcha, when a function returns several named variables, they are returned as an Arrays

// Second gotcha, numbers are returned as instances of BigNumber, do not forget to convert when standard numbers are expected

const[codeAsBigNumber, displayDateAsBigNumber] =yieldcontract.newRequest(pullrequestId, authorName, imageUrl);

constcode = codeAsBigNumber.toNumber();

if(code !==) {

thrownewError(getErrorMessageFromCode(code));

}

// Return the displayDate for UI confirmation screen

returndisplayDate.toNumber();

}

有關Eris JS庫的更多信息,可以參閱Eris文檔(https://docs.erisindustries.com/)。

單元測試合約

我們非常喜歡測試驅動開發,但遇到的第一個問題就是:我們如何測試Solidity智能合約?

Eris的項目成員也為此提供了一個工具:sol-unit(https://github.com/smartcontractproduction/sol-unit)。 它在Docker容器中(確保每次測試都在乾淨的環境中運行)為每次測試運行一個新的本地區塊鏈網路,並執行測試。測試也會寫成一個合約。

當然,這個工具也沒那麼快就能用。 sol-unit是一個npm軟體包,為了使用測試功能(assertions,等),我們必須在測試合約中導入由這個軟體包提供的合約。這可以用一個簡單的Solidity語法實現:

import"../node_modules/sol-unit/.../Asserter.sol";

實際的編譯過程並沒有看起來那麼順利。編譯合約時我們遇到了一個奇怪的情形。即,不能用上面這樣的路徑導入合約。 我們最終在測試的makefile目標中添加了一個命令,將這些sol-unit 合約複製到跟我們的項目相同的文件夾中。之後再運行sol-unit就很簡單了,我們可以開始繼續寫代碼了。

copy-sol-unit:

@cp -f ./node_modules/sol-unit/contracts/src/* ./src/ethereum/

compile-contract:

solc --bin --abi -o ./src/ethereum ./src/ethereum/ZeroDollarHomePage.sol ./src/ethereum/ZeroDollarHomePageTest.sol

test-ethereum: copy-sol-unit compile-contract

./node_modules/.bin/solunit --dir ./src/ethereum

運行測試區塊鏈

運行區塊鏈和部署我們的合約只要按照Eris文檔就會非常簡單。通過使用之前已經集成在makefile中的一些命令,我們又進一步解決了遇到的一些麻煩。基於我們的合約運行新區塊鏈的整個過程如下所示:

重置任何正在運行的eris docker容器,並刪除一些臨時文件

啟動eris密鑰服務

生成我們的賬戶密鑰,並將其地址存儲在一個便於稍後由JS API載入的文件中,

生成genesis.json,這是區塊鏈的「區塊0」

創建並啟動新的區塊鏈

將合約上傳至區塊鏈並保存其地址,以便在需要時調用

幾天的工作之後,我們就能夠在本地的Eris區塊鏈上運行合約了。

從Eris到以太坊

我們希望可以在本地以太坊區塊鏈上嘗試我們的合約。

要與以太坊區塊鏈內的合約進行通信,我們必須使用Web3庫。在嘗試使用它們的過程中,我們也學到了很多,並意識到了Eris的許多潛在的複雜性。

首先,之前假設合約與API類似的想法是不正確的。我們必須區分僅從區塊鏈讀取數據的函數,以及將數據寫入區塊鏈的函數。

第一種(只讀函數),像API所做的那樣,會非同步返回結果數據。第二種(寫入函數)只會返回一個交易散列。並且在相應的塊被開採之前(這可能需要一些時間,最差情況是在10秒到1分鐘之間),寫入函數的預期副作用(區塊鏈內部的更改)不會生效。另外,讓這些寫入函數返回值的能力我們也還沒有。所以我們不得不改變我們的solidity代碼來首先調用寫入函數,然後調用只讀函數來獲得結果。

我們還發現了一些事件,可以在智能合約中發生情況時進行通知。智能合約負責觸發事件。它們在solidity的代碼可以這樣寫:

eventPullRequestClaimed(unit pullRequestId,uintestimatedDisplayDate);

他們可以在任何智能合約函數中觸發,例如:

PullRequestClaimed(pullRequestId, estimatedDisplayDate);

這些事件會被永久存儲在區塊鏈中。這意味著我們可以使用區塊鏈存儲事件。這可能是判斷函數調用是否已成功執行的最簡單方法:智能合約可以在其進程結束時觸發事件,觸發條件包括執行失敗、計算結果等等。值得注意的是,Meteor 的某些集成包已經可以直接用了。

最終,雖然實現的功能幾乎相同,但我們已經可以將我們的智能合約重構的相當簡單。我們必須擺脫映射(這些映射無法使用——因為我們的交易不是由以太坊網路開採的)。

solidity這種語言可能和JavaScript很接近,但它仍然非常年輕,並且不完整。數組對象還沒有我們在JavaScript中使用的那些函數(甚至沒有indexOf),字元串對象也沒有任何函數。這在不久的將來或許可以通過社區的貢獻來解決。

以太坊的操作如下:

// in src/ethereum/ZeroDollarHomePage.sol

contract ZeroDollarHomePage {

eventInvalidPullRequest(uintindexed pullRequestId);

eventPullRequestAlreadyClaimed(uintindexed pullRequestId,uinttimeBeforeDisplay,boolpast);

eventPullRequestClaimed(uintindexed pullRequestId,uinttimeBeforeDisplay);

eventQueueIsEmpty();

bool_handledFirst;

uint[] _queue;

uint_current;

address owner;

functionZeroDollarHomePage(){

owner = msg.sender;

_handledFirst =false;

_current =;

}

functionremove(){

if(msg.sender == owner){

suicide(owner);

}

}

functionnewRequest(uintpullRequestId){

if(pullRequestId

InvalidPullRequest(pullRequestId);

return;

}

// Check that the pr hasn"t already been claimed

boolfound =false;

uintindex =;

while(!found && index

if(_queue[index] == pullRequestId) {

found =true;

}else{

index++;

}

}

if(found) {

PullRequestAlreadyClaimed(pullRequestId, (index - _current) *1days, _current > index);

return;

}

_queue.push(pullRequestId);

PullRequestClaimed(pullRequestId, (_queue.length - _current) *1days);

}

functioncloseRequest(){

if(_handledFirst && _current

_current +=1;

}

_handledFirst =true;

}

functiongetLastNonPublished() constantreturns(uintpullRequestId){

if(_current >= _queue.length) {

return;

}

return_queue[_current];

}

}

對Pull的請求進行聲明並返回估計的顯示日期的演變過程為:

// make a [transaction](https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethsendtransaction) call to our smart-contract write function

contract.newRequest.sendTransaction(pullrequestId, {

to: client.eth.coinbase,

}, (err, tx) => {

if(err) {

throwerror;

}

// wait for it to be mined using [code](https://github.com/ethereum/web3.js/issues/393) from [@croqaz](https://github.com/croqaz)

returnwaitForTransationToBeMined(client, tx)

.then(txHash=>{

if(!txHash)thrownewError("Transaction failed (no transaction hash)");

// get its receipt which might contains informations about event triggered by the contract"s code

// this function might also check wether the transaction was successful by analyzing the receipt for ethereum specific error cases (insufficient funds, etc.)

returngetReceipt(client, txHash);

})

.then(receipt=>{

// parse those logs to extract only event data

returnparseReceiptLogs(receipt.logs, contractAbi));

})

.then(logs=>{

if(logs.length ===) {

thrownewError("Transaction failed (Invalid logs)");

}

constlog = logs[];

if(log.event ==="PullRequestClaimed") {

// timeBeforeDisplay is a BigNumber instance

returnlog.args.timeBeforeDisplay.toNumber();

}

if(log.event ==="PullRequestAlreadyClaimed") {

constnumber = log.args.timeBeforeDisplay;

if(log.args.past) {

// timeBeforeDisplay is a BigNumber instance

returnnumber.negated().toNumber();

}

// timeBeforeDisplay is a BigNumber instance

returnnumber.toNumber();

}

if(log.event ==="InvalidPullRequest") {

thrownewError("Invalid pull request id");

}

});

})

通過上面的代碼,我們的去中心化應用就可以在本地以太坊網路中工作了。

部署到生產環境

如果說在本地環境中運行我們的應用是一項挑戰,那麼將其部署到真正的以太坊網路中的生產就是一場戰鬥。

這其中有幾點非常值得注意。最重要的一點是合約在代碼中是不可變的。這意味著:

你部署到區塊鏈的合約會永遠保持在那裡。如果你發現你的合約存在缺陷,那也無法修復——必須部署新的合約。

部署現有合約的新版本時,以前合約中存儲的任何數據都不會自動傳輸過去——除非你主動使用過去的數據初始化新合約。在我們的案例中,糾正合約中的錯誤實際上是抹去了已記錄的PR(無論是已發布的廣告還是待發布的廣告)。

每個合約版本都有一個id(例如,我們的「零美元主頁」合約id是0xd18e21bb13d154a16793c6f89186a034a8116b74)。 由於過去的版本也可能包含數據,如果你不想丟失數據(我們也一樣),請繼續跟蹤過去的合約ID。

由於你無法更新合約,因此也無法回滾更新。在重新部署之前需要確保合約有效。

當你部署現有合約的新版本時,舊的(錯誤的)合約仍然可以被調用。 任何引用合約的區塊鏈以外的系統(例如我們在零美元主頁中的節點管理應用)都必須更新為指向新合約。我們一開始忘了這麼做,還非常困惑為什麼我們的新代碼沒有運行,後來才明白。

如果在代碼中包含自殺(suicide )調用,合約作者可以終止合約。但合約的所有現有交易都會保留在區塊鏈中——永遠存在。如果不希望它消失,請確保終止開關已經處理了合約中的其餘以太幣。

還有一個問題是,區塊鏈中的每個合約部署和寫入操作都會產生數量不一的乙太網絡。我們設法獲得了5個以太幣,但並不知道到底需要多少以太幣才能夠部署我們的合約或者調用一個交易。要測試出來每次失敗產生的成本是很難的。

對於Node.js部分,像前面大部分的項目一樣,我們決定在AWS EC2實例上運行它。為此,我們必須:

在伺服器上運行以太坊節點

將整個區塊鏈下載到此伺服器

在節點上使用一些以太幣解鎖一個帳戶

部署我們的應用程序並將其鏈接到節點

通過節點將我們的智能合約註冊到區塊鏈中

確保你的區塊鏈節點伺服器有充足的存儲空間。目前區塊鏈的大小約為15GB。 EC2實例的默認卷大小為8GB,確實也很大。因為一開始沒有下載完整的區塊鏈(我們也沒有馬上意識到),所以我們遇到了很多麻煩。例如,我們有一個有5個以太幣的帳戶,但很長一段時間,系統的狀態都會顯示我們沒有解鎖帳戶,或者好像我們沒有以太幣。 直到我們將區塊鏈的其餘部分下載下來才解決了這個問題。

同樣,解鎖我們那個包含5個以太幣的寶貴賬戶也不是一件容易的事。我們不想在應用程序中對我們的密碼進行硬編碼,而是想用supervisord運行節點來簡化部署。艱辛的摸索之後(唉,累啊),我們終於找到了一種方式,可以在改變配置的同時,不會暴露密碼。以下就是我們所用的supervisord配置:

[program:geth]

command=geth --ipcdisable --rpc --fast --unlock--password /path/to/our/password/in/a/file

autostart=false

autorestart=true

startsecs=10

stopsignal=TERM

user=ubuntu

stdout_logfile=/var/log/ethereum-node.out.log

stderr_logfile=/var/log/ethereum-node.err.log

最後一個安全提示:區塊鏈的遠程過程調用(RPC)埠為8545。請勿在EC2實例上打開此埠!否則任何知道實例IP的人都可以控制你的以太坊節點,並竊取你的乙太網。

以太幣和瓦斯(Gas)

在以太坊區塊鏈中部署和調用合約並不是免費的,需要承擔一定的計算代價。而且由於區塊鏈運行代價高昂,任何寫入操作都要承擔代價。在乙太網中,調用寫入合約的方法的代價取決於方法的複雜性。以太坊附帶了一份瓦斯費用列表(http://ether.fund/tool/gas-fees),告訴你應該在合約調用時需要添加多少以太幣才能讓它執行。

實際上,這裡的以太幣消耗量其實非常小,只佔一個以太幣的一小部分。以太坊區塊鏈還引入了另一種運行合約的貨幣:瓦斯(Gas)。

1瓦斯= 0.00001以太幣

1 以太幣= 100,000瓦斯

根據計算能力的供應情況和計算需求,未來瓦斯和以太幣的轉化率會有所不同。

處理一次交易的費用並不是強制收取的,但卻非常推薦。以太坊的文件中說:「礦工可以選擇忽略瓦斯價格太低的交易」。成功開採好一個新區塊會給礦工獎勵5個以太幣。

為了調用我們自己的合約,以太坊區塊鏈大概需要0.00045個到0.00098個以太幣(這個實際價格取決於瓦斯價格和交易使用的瓦斯)。

那麼怎樣才能獲得以太幣和瓦斯呢?你可以購買以太幣(當然主要是通過交換比特幣獲得),也可以自己去挖礦。在法國,比特幣或以太幣的購買需要的程序幾乎和開設銀行賬戶一樣麻煩。這個過程很慢(大概要幾天),也很痛苦,取決於由報價和需求確定的匯率。

挖礦以太幣

最後我們決定自己挖掘以太幣。如果想了解一下在以太坊挖礦到底是不是有利可圖,自己挖確實也是一個很好的方法。我們製作了一個非常大的亞馬遜EC2實例,它具有強大的GPU計算能力(是一個g2.2xlarge實例)。這個實例的價格是每天17美元。我們安裝了乙太網,並啟動了我們的節點。由於高內存和存儲需求,我們很快就必須增強這個實例。加入區塊鏈時節點做的第一件事就是下載過去交易的全部歷史記錄。這需要大量的存儲空間:區塊鏈歷史記錄超過14GB,Ethash工作證明大約需要3GB。

一旦以太坊節點啟動,我們就必須要挖掘3天才能創建出一個有效的區塊:

提醒一下,以太坊區塊鏈每10秒鐘挖一塊。開採一個區塊可以獲得5個以太幣,售價大約為55美元(作者寫文章時的價格)。我們的增強版EC2實例運行3天的成本約為51美元。總而言之,在AWS上挖以太幣比直接買以太幣更便宜。但是我們非常幸運:我們開發這個區塊的時候挖掘難度並不很大,在開發完之後,網路的挖掘難度就增加了三倍。

5個以太幣可以讓我們運行「零美元主頁」多長時間呢?現在我們來計算一下。

「零美元主頁」的工作流程意味著每天都會有一筆交易,另外每個聲明的PR都會有一筆交易。假設貢獻者每天聲明一個PR,那麼運行該平台每年最多將花費365 * 2 * 0.00098 = 0.72 以太幣。5個以太幣可以讓我們運行該平台近7年。

正如你所看到的,在以太坊運行合約並不是免費的,不過以目前的價格來說,它仍然很便宜。當然,以太幣價值的變化很大。由於挖比特幣的利潤越來越低,一些大型比特幣礦場開始轉向以太坊。這也讓採礦變得越來越困難,並且使得乙太網每天都在變得更加昂貴。

最終的驚喜

最終,我們的智能合約在EC2上託管的現實世界以太坊節點中運行非常好。

但當我們完成這個項目的時候,以太坊發布了它們的Homestead版本,這帶來了很多新東西,完全破壞了我們的代碼。我們花了大約一個星期的時間才明白,並且通過反覆試驗修復了因不明原因而不兼容的代碼。

Tip

Homestead發布了一個隱藏的以太坊功能——私有網路——來簡化開發。之前以太坊缺乏私有網路是我們當時選擇使用Eris的原因之一。

「零美元主頁」平台現在已經啟動並且開始運行了。你可以通過在GitHub上的marmelab的開源庫之一上開一個Pull請求來使用它,查看http://marmelab.com/ZeroDollarHomepage/ 上當前顯示的廣告,或瀏覽marmelab / ZeroDollarHomePage上的應用程序代碼。是的,我們正在開源整個廣告平台,以便你可以詳細了解其工作原理,並在本地進行複製。

調試

以太坊留給開發者的體驗其實是非常糟糕的。想像一下沒有日誌,也沒有調試工具,你發現程序失敗的唯一方法是通過一行一行輸出「I"m here」字元串來查找問題。甚至有時(例如在Solidity合約中),你都不能這樣做。或者某些在開發環境中完美工作的程序在生產環境中卻無法實現。這就是以太坊的開發者體驗。

如果你將數據存儲在智能合約中,是沒有內置的方式可以在交易後顯示此數據當前狀態的。這意味著你需要構建自己的可視化工具來排除錯誤。

可用於跟蹤以太坊合約和交易的工具有:

etherscan.io:顯示有關合約,交易,區塊的數據

etherchain.org:區塊和乙太網信息

你還可以獲得有關網路和節點的匯總統計信息

例如,這是我們的合約在etherscan上的可視化界面:

每次交易(對合約方法的調用)以及合約執行的痕迹都會用機器語言記錄下來。除了用於確保你調用到了合約之外,這個工具不能用於調試的其他部分。

而且,這些工具只能監視公共以太坊網路。所以你不能用它們來調試本地的區塊鏈。

如果你曾經見過比特幣交易審計網站,千萬不要以為以太坊可以達到相同的複雜程度。此外,比特幣網路只有一種交易,因此比設計用於運行智能合約的網路更容易監控。

文檔

這還不是全部:以太坊文檔與代碼不同步(至少在Frontier版本中),所以大多數時候我們必須要通過查看這些庫的源代碼來了解如何寫代碼。由於有些出問題的庫使用的語言(Solidity)很少人用,所以我們在這裡只能祝福它們的工作方式不出問題了。還有,也不要指望Stack Overflow的幫助。像我們這樣敢於做一些認真的事情來為社區提供支持的人太少了。

不過這裡需要明確的是:我們不是在批評以太坊社區缺乏努力。以太坊背後的發展勢頭巨大,事情進展迅速。所有文檔貢獻者的工作都值得讚賞。但還是要承認在我們開發應用程序時,現有文檔狀態不足以讓新的以太坊開發人員啟動一個項目。

在網上搜索以太坊的教程很容易,但大多數時候,這些教程中複製粘貼的代碼根本無法使用。

如果你想自己動手開發智能合約,以下是一些值得一看的資源:

A 101 Noob Intro to Programming Smart Contracts on Ethereumhttps://medium.com/@ConsenSys/a-101-noob-intro-to-programming-smart-contracts-on-ethereum-695d15c1dab4

以太坊指南https://ethereum-homestead.readthedocs.org/en/latest/

以太坊Github wikihttps://github.com/ethereum/wiki

結論

經過兩位經驗豐富的開發人員4周的艱苦努力,我們的代碼終於可以在公有以太坊網路中工作了(心累)。在Frontier和Homestead版本之間的以太坊庫中的回歸和兼容性中斷也並沒有起到什麼作用。查看marmelab / ZeroDollarHomePage上的項目源代碼可以詳細了解其內部工作原理。因為確實是第一次開發,我們在這方面的經驗也實在有限,請原諒我們代碼中的潛在錯誤,以及本文中的不準確之處。請隨時在GitHub向我們提交更正或評論。

我們不喜歡這段經歷。通過糟糕的文檔和不成熟的軟體庫摸索編程的方式並不是很讓人開心。用半熟的語言來實現簡單的功能(如字元串操作)也不好玩。尤其是意識到自己儘管有著多年豐富的腳本語言編程經驗,但卻無法編寫簡單的可靠合約,這就更令人沮喪。最重要的是,以太坊生態系統的年輕人完全無法預測他們實現一個簡單的功能所需的時間。由於時間就是金錢,目前我們還無法確定開發去中心化應用到底需要多少代價。

在時間和資源方面,「零美元主頁」代表著超過20,000歐元的開發成本——即使它是一個非常簡單的系統。與我們在其他項目中使用的工具(Node.js,Koa,React.js,PostgreSQL等)相比,在區塊鏈上開發非常昂貴。對於開發團隊來說,這也是非常令人失望的。我們還可以從中發現一個很強烈的信號:這個生態系統還沒有準備好!

對區塊鏈的看法

在探索了區塊鏈的理論,並真正開發之後,我們已經對它的優缺點有了切身的體會。但令人驚訝的是,我們的大部分結論都和媒體上一直吹捧的不太一樣。或許這是因為我們並沒有迷信比特幣和其他人的瘋狂估值,也可能是因為區塊鏈確實面臨著不夠成熟的現狀。

區塊鏈確實是一個非常聰明的想法,具有巨大的潛在影響。但是目前的方案究竟能否為下一個十年顛覆性應用的誕生提供動力還尚未可知。

在技術方面,它的一些基本特徵根本不可行。區塊鏈效率不夠高,對開發人員不夠友好,而且它的技術特性很有可能被恐怖分子或地下黑市利用,用於非法毒品、武器、人口等的販賣,而難以監管。

在商業方面,區塊鏈變化速度過快,價格昂貴。費用可能會無緣無故地變化數十倍。在這樣一個不穩定的平台上開展業務是非常危險的。

我的意思是,我們必須等待。區塊鏈還沒有準備好。它需要更成熟些,需要成為另一個殺手級應用而不是成為一個投機引擎,需要更大的開發者社區,需要承擔更多的生態和經濟責任。這需要多長時間?也許一年或兩年?沒人能說出來。

說實話,最後得出這樣的看法也讓我感到很驚訝。關於區塊鏈的大部分出版物都表明了相反的情況。他們說「現在正是時候」,「不要錯過火車」,或者「下一個十年的巨型企業正在區塊鏈上建立」。也許他們是錯的,也許我們是錯的。我們正在試圖用更有力的證據來論證這一分析。如果您有不同的意見,歡迎和我們溝通交流。我們將密切關注不同區塊鏈項目的發展。

原文:

https://marmelab.com/blog/2016/05/20/blockchain-for-web-developers-in-practice.html


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 區塊鏈大本營 的精彩文章:

OKcoin創始人徐明星發飆了!怒斥神奇少女王凱馨:「典型的詐騙,請大家立即報警」
程序猿們,別著急入手區塊鏈,先給自己選好武林門派再練功不遲

TAG:區塊鏈大本營 |