1. 程式人生 > >區塊鏈(雜記)

區塊鏈(雜記)

區塊鏈

去中心化App

  1. 去中心化 github下載(Mist)
  2. Chrome外掛 MetaMask
  3. 以太坊的四種錢包:Ethereum Wallet、METAMASK、 Parity Ethereum、MyEtherWallet
  4. 編輯器:Atom、Webstorm、Sublime、Vscode

在區塊鏈上執行的程式,通常稱為智慧合約(Smart Contract)。所以通常會把寫區塊鏈程式改稱寫智慧合約。

簡單點來講,合約就是執行在區塊鏈上的一段程式。

Solidity合約結構(狀態變數、區域性變數、建構函式、解構函式、生命週期)

Demo 一個完整的合約

pragma solidity ^0.4.4;

contract Counter {
 
    uint count = 0;
    address owner;

    function Counter() {
       owner = msg.sender;
    } 

    function increment() public {
       uint step = 10;
       if (owner == msg.sender) {
          count = count + step;
       }
    }
 
    function getCount() constant returns (uint) {
       return count;
    }

    function kill() {
       if (owner == msg.sender) { 
          selfdestruct(owner);
       }
    }
}

合約部署

要想釋出我們的合約到區塊鏈,開啟Ethereum Wallet然後點選Contracts。

部署合約時,因為要往區塊鏈寫入資料,需要礦工進行驗證,所以需要花費一些gas獎勵給礦工,還有當我們每次呼叫increment方法時,也屬於寫入資料,同樣需要花費gas,但是呼叫getCount方法時只是從區塊鏈讀取資料,無需驗證,讀取資料無須花費gas。

Solidity值型別與引用型別

值型別(Value Type)

值型別包含:

  • 布林(Booleans)
  • 整型(Integer)
  • 地址(Address)
  • 定長位元組陣列(fixed byte arrays)
  • 有理數和整型(Rational and Integer Literals,String literals)
  • 列舉型別(Enums)
  • 函式(Function Types)

引用型別(Reference Types)

引用型別包含:

  • 不定長位元組陣列(bytes)
  • 字串(string)
  • 陣列(Array)
  • 結構體(Struts)

當函式引數為memory型別時,相當於值傳遞,而storage型別的函式引數將是指標傳遞。

案例:集資(CrowdFunding)智慧合約(Smart Contract)

結構體和字典綜合案例

下面的案例是一個集資合約的案例,裡面有兩個角色,一個是投資人Funder,也就是出資者。另一個角色是運動員Campaign,被贊助者。一個Funder可以給多個Campaign贊助,一個Campaign也可以被多個Funder贊助。

完整合約:

pragma solidity ^0.4.4;

contract CrowdFunding {
    
    // 定義一個`Funder`結構體型別,用於表示出資人,其中有出資人的錢包地址和他一共出資的總額度。
    struct Funder {
        address addr; // 出資人地址
        uint amount;  // 出資總額
    }


   // 定義一個表示儲存運動員相關資訊的結構體
    struct Campaign {
        address beneficiary; // 受益人錢包地址
        uint fundingGoal; // 需要贊助的總額度
        uint numFunders; // 有多少人贊助
        uint amount; // 已贊助的總金額
        mapping (uint => Funder) funders; // 按照索引儲存出資人資訊
    }

    uint numCampaigns; // 統計運動員(被贊助人)數量
    mapping (uint => Campaign) campaigns; // 以鍵值對的形式儲存被贊助人的資訊


    // 新增一個`Campaign`物件,需要傳入受益人的地址和需要籌資的總額
    function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // 計數+1
        // 建立一個`Campaign`物件,並存儲到`campaigns`裡面
        campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
    }

    // 通過campaignID給某個Campaign物件贊助
    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];// 通過campaignID獲取campaignID對應的Campaign物件
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); // 儲存投資者資訊
        c.amount += msg.value; // 計算收到的總款
        c.beneficiary.transfer(msg.value);
    }


    // 檢查某個campaignID編號的受益人集資是否達標,不達標返回false,否則返回true
    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        return true;
    }
}

總而言之,智慧合約實現上要達到的目標是:完備的業務功能、精悍的程式碼邏輯、良好的模組抽象、清晰的合約結構、合理的安全檢查、完備的升級方案。

從業務視角來看,智慧合約只需要做兩件事,其一是如何定義資料的結構和讀寫方式,其二是如何處理資料並對外提供服務介面。

IPFS + 區塊鏈

  1. IPFS簡介

IPFS(InterPlanetary File System)是一個點對點的分散式超媒體分發協議,它整合了過去幾年最好的分散式系統思路,為所有人提供全球統一的可定址空間,包括Git、自證明檔案系統SFS、BitTorrent和DHT,同時也被認為是最有可能取代HTTP的新一代網際網路協議。

IPFS用基於內容的定址替代傳統的基於域名的定址,使用者不需要關心伺服器的位置,不用考慮檔案儲存的名字和路徑。我們將一個檔案放到IPFS節點中,將會得到基於其內容計算出的唯一加密雜湊值。雜湊值直接反映檔案的內容,哪怕只修改1位元,雜湊值也會完全不同。當IPFS被請求一個檔案雜湊時,它會使用一個分散式雜湊表找到檔案所在的節點,取回檔案並驗證檔案資料。

IPFS是通用目的的基礎架構,基本沒有儲存上的限制。大檔案會被切分成小的分塊,下載的時候可以從多個伺服器同時獲取。IPFS的網路是不固定的、細粒度的、分散式的網路,可以很好的適應內容分發網路的要求。這樣的設計可以很好的共享各類資料,包括影象、視訊流、分散式資料庫、整個作業系統、模組鏈、8英寸軟盤的備份,還有靜態網站。

IPFS提供了一個友好的WEB訪問介面,使用者可通過http://ipfs.io/hash 獲取IPFS網路中的內容,也許在不久的將來,IPFS協議將會徹底替代傳統的HTTP協議。

基於以太坊Ethereum & IPFS的去中心化Ebay區塊鏈專案

  • 完整原始碼

    import React, {Component} from ‘react’; import ‘./App.css’;

    const ipfsAPI = require(‘ipfs-api’); const ipfs = ipfsAPI({host: ‘localhost’, port: ‘5001’, protocol: ‘http’});

    function Utf8ArrayToStr(array) { var out, i, len, c; var char2, char3;

    out = "";
    len = array.length;
    i = 0;
    while (i < len) {
      c = array[i++];
      switch (c >> 4) {
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
        case 7:
          // 0xxxxxxx
          out += String.fromCharCode(c);
          break;
        case 12:
        case 13:
          // 110x xxxx   10xx xxxx
          char2 = array[i++];
          out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
          break;
        case 14:
          // 1110 xxxx  10xx xxxx  10xx xxxx
          char2 = array[i++];
          char3 = array[i++];
          out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0));
          break;
        default:
          break;
      }
    }
    
    return out;
    

    }

    class App extends Component {

    constructor(props) {
      super(props);
      this.state = {
        strHash: null,
        strContent: null
      }
    }
    
    saveTextBlobOnIpfs = (blob) => {
      return new Promise(function(resolve, reject) {
        const descBuffer = Buffer.from(blob, 'utf-8');
        ipfs.add(descBuffer).then((response) => {
          console.log(response)
          resolve(response[0].hash);
        }).catch((err) => {
          console.error(err)
          reject(err);
        })
      })
    }
    
    render() {
      return (<div className="App">
        <input ref="ipfsContent" style=/>
        <button onClick={() => {
            let ipfsContent = this.refs.ipfsContent.value;
            console.log(ipfsContent);
            this.saveTextBlobOnIpfs(ipfsContent).then((hash) => {
              console.log(hash);
              this.setState({strHash: hash});
            });
          }}>提交到IPFS</button>
    
        <p>{this.state.strHash}</p>
    
        <button onClick={() => {
            console.log('從ipfs讀取資料。')
            ipfs.cat(this.state.strHash).then((stream) => {
              console.log(stream);
              let strContent = Utf8ArrayToStr(stream);
              console.log(strContent);
              this.setState({strContent: strContent});
            });
          }}>讀取資料</button>
        <h1>{this.state.strContent}</h1>
      </div>);
    }
    

    }

    export default App;

  1. 介紹

1.eBay簡介

eBay,(EBAY,中文電子灣、億貝、易貝)是一個管理可讓全球民眾上網買賣物品的線上拍賣及購物網站。ebay於1995年9月4日由Pierre Omidyar以Auctionweb的名稱創立於加利福尼亞州聖荷西。人們可以在ebay上通過網路出售商品。

因為我們會將所有的業務邏輯和資料儲存在以太坊區塊鏈上,所以這將是一個完全去中心化的應用程式。與此同時,如果我們將所有的圖片和大量文字都儲存在以太坊區塊鏈上,這將非常昂貴,甚至由於以太坊EVM的一些限制,我們根本不可能將大量的圖片和文字儲存到區塊鏈。為了解決這個問題,我們將在IPFS(Inter Planetary File System)上儲存大量的文字和影象。

  1. 為什麼要去中心化

在我們開始構建應用程式之前,花一分鐘的時間來了解在像Ethereum這樣的去中心化平臺上構建產品的動機。

eBay取得了巨大的成功,因為它使得買賣非常有效率。在網際網路成為主流之前,人們只能在當地社群購買和出售商品,當然也可以在一定的地理範圍內購物。隨著越來越多的人上網,像eBay這樣的公司完全可以體驗整個線下購物拍賣的場景,任何人都可以在網際網路上從世界任何地方買賣任何東西。 eBay對於消費者和商人都具有劃時代的意義。

儘管這對大家都有好處,並且總體上改善了貿易和經濟,但是它有一些缺點。

  • 參與的商人都在受到公司的相關限制:公司可以隨時決定是否阻止商家自行處理交易,這對商人來說可能是一個巨大的打擊。
  • 商家支付費用列出他們的產品,並支付銷售佣金。付費本身並不是那麼糟糕,因為eBay提供服務。然而,上市費有時太高,商家要麼保證金很高,要麼把這筆費用交給消費者。
  • 商家/消費者不擁有他們的任何資料。評論、購買歷史等都是這些公司所有。例如,如果商家想將自己的操作轉移到另一個提供商,那麼匯出她的評論或其他資料是幾乎不可能的。

在以太坊這樣的平臺上構建產品解決了這些問題。商戶的賬戶不能被封鎖,資料是公開的,所以它可以很容易匯出,交易費用比中心化的公司少很多很多。

  1. 專案詳情

現在,您已經瞭解了構建此應用程式的原因以及為什麼要構建這個應用程式,接下來我們來看一個高級別的,我們將在此專案中實現的所有功能。

  • 專案展示:一個網站應該允許商家列出他們的專案。我們將為任何人建立免費列出他們的專案的功能。我們會將這些專案都儲存在區塊鏈和非區塊鏈的資料庫中,以方便查詢。
  • 將檔案新增到IPFS:我們將新增將產品影象和產品描述(大文字)上傳到IPFS的功能。
  • 瀏覽產品:我們將新增根據類別、拍賣時間等過濾和瀏覽產品的功能。
  • 拍賣:就像eBay,我們將實現維克裡拍賣(Vickrey auction),即次價密封投標拍賣(Second-price sealed-bid auction)對物品進行投標。因為以太坊上的一切都是公開的,不像中心化應用程式,所以我們的實現將有所不同。我們的實現將非常類似於ENS的招標流程。
  • 託管合約:一旦投標結束,產品有贏家,我們將在買方,賣方和第三方仲裁人之間建立一個託管合同。
  • 2-of-3 數字簽名:我們將通過實施2-of-3 數字簽名解決方案來增加欺詐保護,其中3名參與者中的2名必須投票將資金釋放給賣方或將金額退還給買方。
  1. 技術需求

要成功完成本課程,您應該對以下語言/技術有基本的瞭解:

  • Solidity面向物件程式設計:在講解這個專案之前,我們會先給大家講解Solidity面向物件程式設計基礎,如何編寫簡單合約,部署合約,合約簡單互動。
  • HTML/CSS/React:您應該對構建前端的HTML / CSS有基本的瞭解。
  • Javascript:我們在這個過程中廣泛使用JavaScript。它在伺服器端用於將資料儲存到資料庫並查詢資料庫並將結果返回到前端。前端使用Web3.js與區塊鏈進行互動。我們盡力保持javascript程式碼儘可能簡單,以迎合不同背景的學生。
  • Database:我們將在本課程中使用MongoDB來儲存產品資訊。沒有必要特別瞭解MongoDB的知識,但是對這個過程需要對資料庫有基本的瞭解。
  1. 專案架構

在我們開始執行程式碼之前,讓我們來看看我們將在本課程中構建的Dapp的體系結構。

  • Web前端:Web前端是HTML,CSS和Javascript的組合(大量使用web3js)。使用者將通過這個前端應用程式與區塊鏈,IPFS和nodeJS伺服器互動。
  • 區塊鏈:這是所有程式碼和交易所在的應用程式的心臟。商店中的所有產品、使用者出價和託管都寫在區塊鏈上。
  • MongoDB:儘管產品儲存在區塊鏈中,但是查詢區塊鏈展示產品和應用各種過濾器(僅顯示特定類別的產品,顯示即將過期的產品等)效率並不高。我們將使用MongoDB資料庫來儲存產品資訊並查詢它以展示產品。
  • NodeJS伺服器:這是前端通過其與資料庫進行通訊的後端伺服器。我們將公開一些簡單的API來為前端查詢和從資料庫中檢索產品。
  • IPFS:當用戶在商店中列出商品時,前端會將產品檔案和描述上傳到IPFS,並將上傳檔案的雜湊HASH儲存到區塊鏈中。
  1. 實現步驟
  • 先通過truffle framework和Solidity實現合約程式碼,並將其部署到truffle develop自帶的測試網路中,並且在truffle console中可以自由互動。
  • 然後我們將學習IPFS,通過命令列安裝並與之互動。
  • 在後端實現完成後,我們將構建Web前端以與合約和IPFS進行互動。我們也會實現招標,揭示前端的拍賣功能。
  • 我們將安裝MongoDB並設計資料結構來儲存產品。
  • 資料庫啟動並執行後,我們將實現監聽合約事件的NodeJS伺服器端程式碼,並將請求記錄到控制檯。然後我們將執行程式碼將產品插入資料庫。
  • 我們將更新我們的前端,從資料庫而不是區塊鏈中查詢產品。
  • 我們將實現託管合同和相應的前端,參與者可以向買方/賣方發放或退款。