以太坊分析之 智能合约和DAPP

时间:2021-10-05 20:33:05

以太坊介绍

       以太坊(Ethereum)是一个建立在区块链技术之上,去中心化应用平台。它允许任何人在平台中建立和使用通过区块链技术运行的去中心化应用。

 

智能合约

      以太坊是一个运行着智能合约的分布式平台,智能合约本质就是脚本,这些脚本可以处理各种业务逻辑来利用以太坊区块链的能力,比如彩票、拍卖、物流跟踪等等。这也正是以太坊区别于比特币的最重要的属性,定位来说以太坊旨在成为一个平台,而比特币则是一个货币体系。智能合约赋能给以太坊无限想象力和更强大的生命力。

 以太坊分析之 智能合约和DAPP

一个简单的例子,就是A转账给B,在比特币和以太坊中大概都怎么实现的:

以太坊分析之 智能合约和DAPP

以太坊分析之 智能合约和DAPP

      以太坊智能合约实现的方式貌似能看懂,比较易读事实也是这样的,智能合约使得区块链的扩展性更强,且实现上更简洁,从而让以太坊发展成为目前最大的一个区块链开发平台。

随着智能合约的发展,智能合约构建的组织如同现实商业社会一样的运行,这样形成的去中心化组织网络会变得极其复杂和自治,会出现各种形态:DAPP(去中心化应用)DAO(去中心化自治组织)DAC(去中心化自治公司)DAS(去中心化自治社会).

 

DAPP

      DAPP是去中心化的APPDAPP和普通的App原理一样,除了他们是完全去中心化的,在以太坊网络本身自己的节点来运作的DAPP,不依赖于任何中心化的服务器,DAPP是去中心化的,可以完全自动地运行

      以太坊中一般会认为智能合约就是DAPP,当然更准确的可以认为智能合约相当于服务器后台,另外要实现用户体验,还需要UI交互界面,通过RPC与后台对接,那么DAPP就是包含完整的智能合约+用户UI交互界面。

 

智能合约开发

搭建开发环境

智能合约的开发环境需要:

·        Truffle:是以太坊的开发环境、测试框架和资产通道。换句话说,它可以帮助你开发、发布和测试智能合约等等。

·        Ganache:以前叫作 TestRPC,如果你读过几个月前的教程的话,有可能他们在使用TestRPC 的情境下配合使用了 Truffle,它在 TestRPC Truffle 的集成后被重新命名为 GanacheGanache 的工作很简单:创建一个虚拟的以太坊区块链,并生成一些我们将在开发过程中用到的虚拟账号。

·        MistMist 是一个分布式网络 apps 的浏览器,相当于是只针对 Dapps Chrome Firefox。目前来说,它仍然是不安全的,所以你还不能在不受信任的 Dapp 中使用它。

·        以太坊钱包:它是 Mist 的一个版本,但只启动一个 Dapp ——以太坊钱包。Mist 以太坊钱包只是 UI(用户界面)前端,我们还需要一个将我们连接到以太坊区块链的核心程序(它可以是一个真正的以太坊区块链,也可以是一个测试版的)。

·        GethGeth 是把你连接到区块链的核心应用程序,它也可以启动一个新的区块链(在我们这个示例中,我们将创建一个本地测试网区块链),创建合约,挖掘以太币等。

 

TruffleGanache都是基于NodeJS,所以首先得安装NodeNPM。本文是CentOS7环境,

Node版本如下:

$ node -v

v7.0.0

$ npm -v

3.10.8

 

安装Truffle

$ npm install -g truffle

 

安装成功查询:

$ truffle version

Truffle v4.1.11 (core: 4.1.11)

Solidity v0.4.24 (solc-js)

 

安装Ganache命令行工具:

$ npm install -g ganache-cli

创建Truffle工程

使用Truffle来创建一个工程:

$ mkdir my-dapp

$ cd my-dapp/

$ truffle init

Downloading...

Unpacking...

Setting up...

Unbox successful. Sweet!

 

Commands:

 

  Compile:        truffle compile

  Migrate:        truffle migrate

  Test contracts: truffle test

 

Truffle会初始化一个项目,项目的文件结构如下:

.

├── contracts

│   └── Migrations.sol

├── migrations

│   └── 1_initial_migration.js

├── test

├── truffle-config.js

└── truffle.js

 

 

第一步在工程contracts 目录下创建一个Solidity 脚本:

Wrestling.sol

pragma solidity ^0.4.18;
 
/**
* Example script for the Ethereum development walkthrough
*/
contract Wrestling {
    /**
    * Our wrestlers
    */
    address public wrestler1;
    address public wrestler2;
 
    bool public wrestler1Played;
    bool public wrestler2Played;
 
    uint private wrestler1Deposit;
    uint private wrestler2Deposit;
 
    bool public gameFinished;
    address public theWinner;
    uint gains;
 
    /**
    * The logs that will be emitted in every step of the contract's life cycle
    */
    event WrestlingStartsEvent(address wrestler1, address wrestler2);
    event EndOfRoundEvent(uint wrestler1Deposit, uint wrestler2Deposit);
    event EndOfWrestlingEvent(address winner, uint gains);
 
    /**
    * The contract constructor
    */
    constructor() public {
        wrestler1 = msg.sender;
    }
 
    /**
    * A second wrestler can register as an opponent
    */
    function registerAsAnOpponent() public {
        require(wrestler2 == address(0));
 
        wrestler2 = msg.sender;
 
        emit WrestlingStartsEvent(wrestler1, wrestler2);
    }
 
    /**
    * Every round a player can put a sum of ether, if one of the player put in twice or
    * more the money (in total) than the other did, the first wins
    */
    function wrestle() public payable {
        require(!gameFinished && (msg.sender == wrestler1 || msg.sender == wrestler2));
 
        if(msg.sender == wrestler1) {
            require(wrestler1Played == false);
            wrestler1Played = true;
            wrestler1Deposit = wrestler1Deposit + msg.value;
        } else {
            require(wrestler2Played == false);
            wrestler2Played = true;
            wrestler2Deposit = wrestler2Deposit + msg.value;
        }
        if(wrestler1Played && wrestler2Played) {
            if(wrestler1Deposit >= wrestler2Deposit * 2) {
                endOfGame(wrestler1);
            } else if (wrestler2Deposit >= wrestler1Deposit * 2) {
                endOfGame(wrestler2);
            } else {
                endOfRound();
            }
        }
    }
 
    function endOfRound() internal {
        wrestler1Played = false;
        wrestler2Played = false;
 
        emit EndOfRoundEvent(wrestler1Deposit, wrestler2Deposit);
    }
 
    function endOfGame(address winner) internal {
        gameFinished = true;
        theWinner = winner;
 
        gains = wrestler1Deposit + wrestler2Deposit;
        emit EndOfWrestlingEvent(winner, gains);
    }
 
    /**
    * The withdraw function, following the withdraw pattern shown and explained here:
    * http://solidity.readthedocs.io/en/develop/common-patterns.html#withdrawal-from-contracts
    */
    function withdraw() public {
        require(gameFinished && theWinner == msg.sender);
 
        uint amount = gains;
 
        gains = 0;
        msg.sender.transfer(amount);
    }
}

Wrestling实现的是一个角力的游戏,在这个游戏中,每一轮的比赛,参与者都可以投入一笔钱,如果一个人投入的钱大于等于另一个人的两倍(总计),那他就赢了。

关于代码的解读参考http://www.cocoachina.com/blockchain/20180312/22550.html

 

 

第二步在工程目录migrations创建一个部署脚本:

2_deploy_contracts.js

const Wrestling = artifacts.require("./Wrestling.sol")

 

module.exports = function(deployer) {

    deployer.deploy(Wrestling);

};

这个部署脚本的作用是导入Wrestling.sol然后部署到区块链中。

 

启动Ganache

Truffle工程创建完成后,现在通过Ganache创建一个虚拟的以太坊区块链。

新开一个窗口执行:

$ ganache-cli -p 7545

Ganache CLI v6.1.0 (ganache-core: 2.1.0)

 

Available Accounts

==================

(0) 0x67f0c56f2ed6b635e7a4d4285615ae01b4b6d834

(1) 0xdde20e330d5747253aa2041532b486fb425951a8

(2) 0xecc3a4c50bce3014d11a3840d0d1dd3011cf65e8

(3) 0x3db9dcb607a15b744affb1453ce1d36121f56a9a

(4) 0x0f1c36775edba34f9239f6fbe3e1d071a35a80bf

(5) 0x14156195c281babf28200aec756ce16e8794e724

(6) 0xdb89069d044cb1f8246fad2a805b97102b05c6a5

(7) 0xa3dfb369603aa5788626371ab022e42f98001607

(8) 0x455bffe5cce17a3a71bfb1ef1fb74c3640f77b97

(9) 0x82512876028923943d5e937441710d6675d573b0

 

Private Keys

==================

(0) 5d5d0d038bbd85c1ca25768a0da88b49805dd74bbffa628cd4b0968877eb84af

(1) ba616ed800cf1e0306ec1eaea7416d52bab5ad0dd7216a997194784461da7efe

(2) 0f80b88a4ad72850003a8aca809932e3e27f29e2a931d900ac0cb06fc22247bd

(3) c0cb6aecff075d9527eebb69c5389f6cfaea2a3e7120a2fb6fb4122e299dfdad

(4) 7ea0bd30edcbe05b89553a1c7e4eb52c4ebf87f1a664202ccad7eb26e589403c

(5) 5ff14162882f900a4c2241042b1e51c866adeb089c28e6643787ef26b43293c8

(6) 24da6f19bfaa3de81ab1fd9b62636064282604e4fa26d6697eacfc3c9d3d9de8

(7) 944c9ede3956b7dc55b2ece7f9506358b1d7c5e5f7275c6b11dacd5e38414c0f

(8) 43e8c352eaafd39e7116b43aed86006141daa18c1cf7c06806c6c19cbb7be29a

(9) bf43907fd082a64b987c00ceb9b7ae45eee08fcf96ae07d6b0260d4ec370f736

 

HD Wallet

==================

Mnemonic:      video march rail weapon lunch holiday organ vague food strategy gown artist

Base HD Path:  m/44'/60'/0'/0/{account_index}

 

Listening on localhost:7545

 

         Ganache启动成功后,会生成10测试账号,默认情况下,每个账户都有未锁定的100个以太币并,所以我们可以*地从那里发送以太币。最后Ganache服务监听在localhost:7545

 

编译和部署

         Ganache启动后,回到Truffle工程目录下修改配置文件truffle.jstruffle-config.js

module.exports = {

  // See   // for more about customizing your Truffle configuration!

  networks: {

    development: {

      host: "127.0.0.1",

      port: 7545,

      network_id: "*" // Match any network id

    }

  }

};

简单说就是配置Truffle工程的一个开发网络,地址是127.0.0.1:7545,也就是Ganache服务的地址。也就是说可以把工程部署到Ganache

 

然后开始编译Truffle工程:

$ cd /path/to/my-dapp

$ truffle compile

Compiling ./contracts/Migrations.sol...

Compiling ./contracts/Wrestling.sol...

Writing artifacts to ./build/contracts

 

编程成功后部署到Ganache

$ truffle migrate --network development

Using network 'development'.

 

Running migration: 1_initial_migration.js

  Deploying Migrations...

  ... 0xc278ec5575aa24a03c6c84e17c32d890ba6e748034e3d93a83c77f5082d856d7

  Migrations: 0xd4ec84fa50eef802535fce494a187f17beb2a642

Saving successful migration to network...

  ... 0xa1ec2490cdb988259b7762bf483184f7226a4af3a57d11f660b5d1848ef9aeb0

Saving artifacts...

Running migration: 2_initial_migration.js

  Deploying Wrestling...

  ... 0x802eaf5257108cdf3615b9c75903826a6223cb7ec9201d23c75f36085439d4f3

  Wrestling: 0xb386d031d04d3cb623d6f45872203a9cb99b3e4a

Saving successful migration to network...

  ... 0x57325073308f757f7788920795b7f3cff59c7483b71ff5319de251505b0d6b13

Saving artifacts...

可以看到Wrestling合约的地址0xb386d031d04d3cb623d6f45872203a9cb99b3e4a

 

另外在Ganache的日志可以看到:

 

  Transaction: 0x802eaf5257108cdf3615b9c75903826a6223cb7ec9201d23c75f36085439d4f3

  Contract created: 0xb386d031d04d3cb623d6f45872203a9cb99b3e4a

  Gas usage: 708985

  Block Number: 3

  Block Time: Fri May 25 2018 17:35:22 GMT+0800 (CST)

这里创建Wrestling合约花费了708985Gas

 

执行合约

启动 Truffle 控制台,这会帮助我们与ganache的区块链进行交互:

$ truffle console --network development

truffle(development)>

 

然后在控制台中进行编码来实现合约的执行

 

首先选择2个账户作为游戏的2个参与者

>  account0 = web3.eth.accounts[0]

'0xe2be5aa059b3891a519f7f1e04666d05e92b1365'

>  account1 = web3.eth.accounts[1]

'0x726f35aa2eabc41b6cb934fb5be7719263f4fe13'

 

 

接着初始化一个Wrestling合约实例WrestlingInstance

>  Wrestling.deployed().then(inst => { WrestlingInstance = inst })

 

可以查看WrestlingInstance的变量wrestler1也就是选手1的地址,因为我们没有指定,所以默认就是Ganache 生成的第一个账户,也就是account0

WrestlingInstance.wrestler1.call()

'0xe2be5aa059b3891a519f7f1e04666d05e92b1365'

 

然后我们把account1注册成Wrestling合约的第二个选手2

WrestlingInstance.registerAsAnOpponent({from: account1})

{ tx: '0x1144b68e8d19c4288ef0d20a558fd178066ec1cd38cc6610a9e16150e013d3ea',

  receipt:

   { transactionHash: '0x1144b68e8d19c4288ef0d20a558fd178066ec1cd38cc6610a9e16150e013d3ea',

     transactionIndex: 0,

     blockHash: '0x7b36144a86ff06c56fb702a376af0bbc300e077ee09451a154b7743fa2f6e052',

     blockNumber: 5,

     gasUsed: 43900,

     cumulativeGasUsed: 43900,

     contractAddress: null,

     logs: [ [Object] ],

     status: '0x01',

     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000' },

  logs:

   [ { logIndex: 0,

       transactionIndex: 0,

       transactionHash: '0x1144b68e8d19c4288ef0d20a558fd178066ec1cd38cc6610a9e16150e013d3ea',

       blockHash: '0x7b36144a86ff06c56fb702a376af0bbc300e077ee09451a154b7743fa2f6e052',

       blockNumber: 5,

       address: '0xb386d031d04d3cb623d6f45872203a9cb99b3e4a',

       type: 'mined',

       event: 'WrestlingStartsEvent',

       args: [Object] } ] }

从返回的结果可以看出该项交易使用了Gas,并且触发了“WrestlingStartsEvent” 事件。

 

现在2个选手准备就绪,就可以开始游戏: 每一轮的比赛,参与者都可以投入一笔钱,如果一个人投入的钱大于等于另一个人的两倍(总计),那他就赢了。

 

第一轮,选手1出价2 ether,选手2出价3 ether

>  WrestlingInstance.wrestle({from: account0, value: web3.toWei(2, "ether")})

>  WrestlingInstance.wrestle({from: account1, value: web3.toWei(3, "ether")})

 

第二轮,选手1出价5 ether,选手2出价20 ether

>  WrestlingInstance.wrestle({from: account0, value: web3.toWei(5, "ether")})

>  WrestlingInstance.wrestle({from: account1, value: web3.toWei(20, "ether")})

 

两轮下来,选手1出价7ether,选手2出价23ether,所以选手2获胜,这时候获胜者可以提现:

>  WrestlingInstance.withdraw({from: account1})

 

最后查询账户余额,因为在合约执行过程中要支付Gas,所以账户1的余额是小于107ether的。

>  web3.eth.getBalance(account1)

{ [String: '106974788700000000000'] s: 1, e: 20, c: [ 1069747, 88700000000000 ]


参考

  • https://blog.csdn.net/m0_37722557/article/details/79899847
  • http://xinwen.eastday.com/a/180314214918603.html?xx=1&recommendtype=d
  • http://www.cocoachina.com/blockchain/20180314/22592.html
  • http://www.cocoachina.com/blockchain/20180312/22550.html


作者简介

吴龙辉,《Kubernetes实战》作者,活跃于技术开源社区,贡献代码和撰写技术文档。 
邮箱: wlh6666@qq.com