以太坊智能合約開發入門

本文作者最近在找工作,有意向致電 13113668890

Mr. Neo Chen (陳景峯), netkiller, BG7NYT


中國廣東省深圳市龍華新區民治街道溪山美地
518131
+86 13113668890


版權聲明

轉載請與作者聯繫,轉載時請務必標明文章原始出處和作者信息及本聲明。

http://www.netkiller.cn
http://netkiller.github.io
http://netkiller.sourceforge.net
微信訂閲號 netkiller-ebook (微信掃瞄二維碼)
QQ:13721218 請註明“讀者”
QQ群:128659835 請註明“讀者”

2018-02-06

摘要

本文采用碎片化寫作,原文會不定期更新,請儘量閲讀原文。

http://www.netkiller.cn/journal/ethereum.solidity.html


目錄

1. Truffle

Truffle 是 solidity 開發框架

1.1. 安裝 Truffle

			
# 安裝 Nodejs
curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
sudo apt-get install -y nodejs
			
# 安裝truffle
sudo npm install -g truffle

# 安裝testrpc
sudo npm install -g ethereumjs-testrpc
			
			

1.2. 創建項目

			
cd ~/ethereum
mkdir truffle-project
cd truffle-project 
truffle init			
			
			

操作演示

			
neo@netkiller ~/ethereum/truffle-project % truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test
  
neo@netkiller ~/ethereum/truffle-project % tree 
.
|-- contracts
|   `-- Migrations.sol
|-- migrations
|   `-- 1_initial_migration.js
|-- test
|-- truffle-config.js
`-- truffle.js

3 directories, 4 files			
			
			

1.3. 配置 Truffle

打開檔案 truffle.js

			
module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
};			
			
			

修改為

			
module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // to customize your Truffle configuration!
			
  networks: {
    development: {
      host: "localhost",
      port: 8545,
      network_id: "*" // Match any network id
    }
  }	
  		
};	
			
			

1.4. 編譯智能合約

			
neo@netkiller ~/ethereum/truffle-project % truffle compile
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts
			
			

truffle預設只會編譯最後一次修改過的合約檔案, 這是為了減少比重複編譯。"--all"選項,可以強制編譯所有檔案。

編譯結果

			
neo@netkiller ~/ethereum/truffle-project % find build 
build
build/contracts
build/contracts/Migrations.json			
			
			

1.5. migrate

			
neo@netkiller ~/ethereum/truffle-project % truffle migrate      
Using network 'development'.

Network up to date.			
			
			

1.6. 部署智能合約

			
neo@netkiller ~/ethereum/truffle-project % truffle deploy
Using network 'development'.

Network up to date.			
			
			

1.7. 開發環境

truffle 自帶一個開發環境

			
neo@netkiller ~/ethereum/truffle-project %  truffle develop
Truffle Develop started at http://localhost:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Private Keys:
(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
(1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
(2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
(3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
(4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
(5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
(6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
(7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
(8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
(9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

truffle(develop)> 
			
			
			

truffle develop 的作用於 testrpc 類似。

			
neo@netkiller ~/ethereum/truffle-project % testrpc 
EthereumJS TestRPC v6.0.3 (ganache-core: 2.0.2)

Available Accounts
==================
(0) 0xb5fd43ee8fa5ce1db9a30a25ba385ee3bfc72966
(1) 0xf5a732345734e1f0f49cbadb145a20d1e1a44b95
(2) 0x834fcd8c55fdf21fd14c82e9a1ef5d3636a2fed6
(3) 0x5aa4d047d85727309d3ca653c83c3bb0ecd18903
(4) 0xb4db2dede86f4539e56ac4438f6e36f09c307e46
(5) 0x8da382b1a10ab2f1dc149e19fda228a07c78935c
(6) 0xb290297e89b52713548ff93e5fc23bc3c4183dde
(7) 0x546183289bd4d9d33a3aee0ee663c0729926e583
(8) 0xca58321e442533b7f827e6e8976e1905acd15214
(9) 0xe2c0b336bbb03564204e15a2cb7744564a53efcc

Private Keys
==================
(0) ff32f7a06e2fb26b51a745c1e428c60df92c0f9bb3301b19a5b7e0cdfaae521a
(1) cdbfe40321b6ade8a246748df1c48a738b8a531aee4d1f60a45bfd7f941e0064
(2) 7092117c2d7832980945e18645a60a1ed0e59261d040749f8b5202c2fc653d74
(3) f329657c9ad808e9f794a7462a1a9c276266343d5ced263ab618b6a19d6857c1
(4) 1221766592618add3a57ab109f00efcc70867dd8a9b10a0f7ea75c2b619edfc3
(5) c27005d6c3581193124c84766cc0b1cc318cb201b7d00b1035f4a4c7767ba790
(6) 6b7f43dca1dcc00203b751191096bb0602e17a9a94dcee8b846329efa703cea9
(7) 41be5971d71935bc88c3cf8aefd78ed3188c8721b7134cda3b25d353faf05d4f
(8) bd4c9d512a4f2da2cdcd9e4f89c049e3e7ac81bf57a05369997c2f13e793bebb
(9) 04196d803f743cc1fd021e7d02d5a552f14ab9826ccd4d4b265ff96c45169d2c

HD Wallet
==================
Mnemonic:      confirm shift cable melody caught swing erode language spend victory conduct van
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545			
			
			

2. browser-solidity

在綫使用 browser-solidity

https://ethereum.github.io/browser-solidity/ https://remix.ethereum.org/

國內網絡有時不給力,建議將 Remix 安裝到本地目錄。

2.1. 將 Remix(browser-solidity) 安裝到本地

共享合約目錄

			
npm install -g remixd
remixd -S "/home/ethereum/codebase/blocks/contracts"		
			
			

安裝 browser-solidity

				
git clone https://github.com/ethereum/browser-solidity 
cd browser-solidity 
npm install 
npm run prepublish

sudo chown -R $USER:$(id -gn $USER) /home/neo/.config

npm start
				
			

啟動後瀏覽器中輸入 http://localhost:8080 可以看到 Remix 界面

Web3 Provider

Remix 提供三種運行環境,常用的有 JavaScript VM 和 Web3 Provider (連接到 --rpc --rpcaddr="0.0.0.0" --rpccorsdomain "*" --rpcport 8545)

Web3 Provider 方式需要解鎖賬號和啟動挖礦

					
> personal.unlockAccount(eth.accounts[0],"");
> miner.start(2); admin.sleepBlocks(1); miner.stop();			
					
				

3. 使用 solc 編譯 *.sol 代碼

			
neo@netkiller ~/ethereum/solidity % solc --bin --abi --optimize -o ./output helloworld.sol
neo@netkiller ~/ethereum/solidity % find output         
output
output/HelloWorld.bin
output/HelloWorld.abi			
			
		

4. 智能合約語言 Solidity

Solidity 是什麼?Solidity是以太坊智能合約的編程語言。

4.1. 智能合約入門演示

這裡我們先做一個 Helloword 演示,讓你初步對智能合約有一個大概的認識。

提示

需要注意的是,你在網上會看到很多例子,對照這例子一步一步操作,始終無法成功,這根Solidity的版本有很大關係。

將下面代碼粘貼到

			
pragma solidity ^0.4.19;

contract HelloWorld
{
    string tmp;

    function HelloWorld() public
    {

    }

    function get() public constant returns (string)
    {
        return tmp;
    }

    function set(string _tmp) public
    {
        tmp = _tmp;
    }

}
			
			

Compile - Details - WEB3DEPLOY

			
var helloworldContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"_tmp","type":"string"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
var helloworld = helloworldContract.new(
   {
     from: web3.eth.accounts[0], 
     data: '0x6060604052341561000f57600080fd5b6102e38061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634ed3885e146100515780636d4ce63c146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a72305820ea826c30d131f20a4d3a8e3fb059ffa95f4c222a5b099029750e4c1937b46e5b0029', 
     gas: '4700000'
   }, function (e, contract){
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })
			
			

部署智能合約需要消耗 gas 所以你要先解鎖賬號。

			
> personal.unlockAccount("0x83fda0ba7e6cfa8d7319d78fa0e6b753a2bcb5a6", "", 300)
true			
			
			
			
> var helloworldContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"_tmp","type":"string"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
undefined
> var helloworld = helloworldContract.new(
...    {
......      from: web3.eth.accounts[0], 
......      data: '0x6060604052341561000f57600080fd5b6102e38061001e6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634ed3885e146100515780636d4ce63c146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a72305820ea826c30d131f20a4d3a8e3fb059ffa95f4c222a5b099029750e4c1937b46e5b0029', 
......      gas: '4700000'
......    }, function (e, contract){
......     console.log(e, contract);
......     if (typeof contract.address !== 'undefined') {
.........          console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
.........     }
......  })
null [object Object]
undefined
			
			

helloworld 智能合約已經創建完畢

			
> helloworld
{
  abi: [{
      constant: false,
      inputs: [{...}],
      name: "set",
      outputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }, {
      constant: true,
      inputs: [],
      name: "get",
      outputs: [{...}],
      payable: false,
      stateMutability: "view",
      type: "function"
  }, {
      inputs: [],
      payable: false,
      stateMutability: "nonpayable",
      type: "constructor"
  }],
  address: undefined,
  transactionHash: "0x466c9ad9db8f37ed5b65bc261210da92f51364ebab1dcbd3759bfc3e16ad6502"
}
			
			
			

4.2. 修飾符

			
public : 函數可見性

payable :可支付的函數修飾符,沒有該修飾符無法接受轉賬操作。

msg.value :執行合約時,轉賬的eth數量,以wei為單位。

msg.sender : 執行合約的地址			
			
			

4.3. 數據類型



uint 無符號整形(256bits 無符號整數)
string 字元串類型

4.3.1. 數組

				
	//創建一個memory的數組
	uint[] memory a = new uint[](7);
	
	uint[] x = [uint(1), 3, 4];
				
				

4.3.2. 枚舉類型

				
pragma solidity ^0.4.0;

contract Purchase {
    enum State { Created, Locked, Inactive } // Enum
}				
				
				

4.3.3. 結構體

定義結構體

				
	struct Voter {
        uint weight; // weight is accumulated by delegation
        bool voted;  // if true, that person already voted
        address delegate; // person delegated to
        uint vote;   // index of the voted proposal
    }

    // This is a type for a single proposal.
    struct Proposal {
        bytes32 name;   // short name (up to 32 bytes)
        uint voteCount; // number of accumulated votes
    }			
				
				

4.3.4. address

				
address public minter;
				
				
4.3.4.1. payable
4.3.4.2. .value()
4.3.4.3. .gas()

4.4. 變數

			
address public minter;
string name;
int num;	

    uint constant x = 32**22 + 8;
    string constant text = "abc";
    bytes32 constant myHash = keccak256("abc");
    
    uint256 ticket = 1 ether;
			
			

變數賦值

			
pragma solidity ^0.4.19;

contract C {
    uint[] data;

    function f() public view returns (uint, bool, uint) {
        return (7, true, 2);
    }

    function g() public {
        // 聲明和分配變數。 明確指定類型是不可能的。
        var (x, b, y) = f();
        // 分配給一個預先存在的變數。
        (x, y) = (2, 7);
        // 互換值的常用技巧對於非價值存儲類型不起作用。
        (x, y) = (y, x);
        // 組件可以省略(也可以用於變數聲明)。
        // 如果元組以空組件結束,其餘的值將被丟棄。
        (data.length,) = f(); // 設置長度為 7
        // 在左邊也可以做同樣的事情。
        (,data[3]) = f(); // Sets data[3] to 2
        // 組件只能在作業的左側排除,但有一個例外:
        (x,) = (1,);
        // (1,) 是指定1元組元的唯一方法,因為(1)等於1。
    }
}			
			
			

4.5. 函數

4.5.1. 構造方法

構造方法的定義是 contract 與 function 相同

				
pragma solidity ^0.4.18;

contract MyContractByNetkiller {
    /* Constructor */

    function MyContractByNetkiller() public{

    }
}
				
				

4.5.2. 定義函數

沒有返回值

				
	function setName(string _name) public{
        name = _name;
    }			
				
				

4.5.3. 函數返回值

有返回值

				
	function getName() public view returns(string){
        return name;
    }
				
				

4.5.4. 參數傳遞

除了 f(2,3) 這樣傳遞參數,還可以使用類似字典或Map的方式 f({value: 2, key: 3});

				
pragma solidity ^0.4.0;

contract C {
    function f(uint key, uint value) {
        // ...
    }

    function g() {
        // named arguments
        f({value: 2, key: 3});
    }
}				
				
				

4.5.5. 函數的例子

完整的例子

				
pragma solidity ^0.4.18;

contract MyContractByNetkiller {
    /* Constructor */
    string name;
    int num;
    function MyContractByNetkiller() public{
        name = "default";
        num = 1;
    }
    function setName(string _name) public{
        name = _name;
    }
    function getName() public view returns(string){
        return name;
    }
    function setNum(int n) public{
        num = n;
    }
    function addNum(int m) public view returns(int res){
        res = m + num;
    }
}
				
				

4.5.6. Fallback function

				
				
				
				

4.5.7. modifier

modifier 可以理解為 function 的觸發器,或者理解為 hook。執行 function的時候會首先運行 modifier

_; 表示執行 modifier 完成所有命令後,繼續運行 function 內的邏輯。

				
pragma solidity ^0.4.11;  
  
contract owned {  
    function owned() { owner = msg.sender; }  
    address owner;    
    uint price;
    mapping (address => bool) registeredAddresses;
  
    modifier onlyOwner {  
        require(msg.sender == owner);  
        _;  
    } 
    function changePrice(uint _price) onlyOwner {  
        price = _price;  
    } 
    function close() onlyOwner {  
        selfdestruct(owner);  
    }  
}
				
				

4.6. 事件

			
	event Sent(address from, address to, uint amount);			
			
			

4.7. 面向兌獎編程

4.7.1. 繼承

例子 mortal 繼承 owned

				
pragma solidity ^0.4.11;  
  
contract owned {  
    function owned() { owner = msg.sender; }  
    address owner;  
  
    modifier onlyOwner {  
        require(msg.sender == owner);  
        _;  
    }  
}  
  
  
contract mortal is owned {  
    function close() onlyOwner {  
        selfdestruct(owner);  
    }  
}
				
				

5. Jave Client

官方網站 https://web3j.io

Java 客戶端與 Server 之間採用json-rpc協議連接。

5.1. 安裝命令行工具

web3j 命令用於將 sol 合約檔案轉換為 java 檔案。

5.1.1. Mac OS

				
brew tap web3j/web3j
brew install web3j

neo@MacBook-Pro ~ % web3j

              _      _____ _     _        
             | |    |____ (_)   (_)       
__      _____| |__      / /_     _   ___  
\ \ /\ / / _ \ '_ \     \ \ |   | | / _ \ 
 \ V  V /  __/ |_) |.___/ / | _ | || (_) |
  \_/\_/ \___|_.__/ \____/| |(_)|_| \___/ 
                         _/ |             
                        |__/              

Usage: web3j version|wallet|solidity ...	
				
				

5.1.2. 二進制包安裝

下載二進制檔案 https://github.com/web3j/web3j/releases

				
wget https://github.com/web3j/web3j/releases/download/v3.2.0/web3j-3.2.0.zip
unzip web3j-3.2.0.zip

$ ./web3j-3.2.0/bin/web3j

              _      _____ _     _
             | |    |____ (_)   (_)
__      _____| |__      / /_     _   ___
\ \ /\ / / _ \ '_ \     \ \ |   | | / _ \
 \ V  V /  __/ |_) |.___/ / | _ | || (_) |
  \_/\_/ \___|_.__/ \____/| |(_)|_| \___/
                         _/ |
                        |__/

Usage: web3j version|wallet|solidity ...
				
				

5.2. 啟動以太坊

首先啟動服務

		
neo@netkiller ~ % geth --networkid 123456 --rpc --rpcaddr="0.0.0.0" --rpccorsdomain "*" --nodiscover 
INFO [02-01|23:35:12] Starting peer-to-peer node               instance=Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9.1
INFO [02-01|23:35:12] Allocated cache and file handles         database=/home/neo/.ethereum/geth/chaindata cache=128 handles=1024
INFO [02-01|23:35:12] Initialised chain configuration          config="{ChainID: 15 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: <nil> EIP155: 0 EIP158: 0 Byzantium: <nil> Engine: unknown}"
INFO [02-01|23:35:12] Disk storage enabled for ethash caches   dir=/home/neo/.ethereum/geth/ethash count=3
INFO [02-01|23:35:12] Disk storage enabled for ethash DAGs     dir=/home/neo/.ethash               count=2
INFO [02-01|23:35:12] Initialising Ethereum protocol           versions="[63 62]" network=123456
INFO [02-01|23:35:12] Loaded most recent local header          number=719 hash=61330b…82786e td=108754979
INFO [02-01|23:35:12] Loaded most recent local full block      number=719 hash=61330b…82786e td=108754979
INFO [02-01|23:35:12] Loaded most recent local fast block      number=719 hash=61330b…82786e td=108754979
INFO [02-01|23:35:12] Loaded local transaction journal         transactions=0 dropped=0
INFO [02-01|23:35:12] Regenerated local transaction journal    transactions=0 accounts=0
WARN [02-01|23:35:12] Blockchain not empty, fast sync disabled 
INFO [02-01|23:35:12] Starting P2P networking 
INFO [02-01|23:35:12] RLPx listener up                         self="enode://9f6490ffb5236f2ddc5710ae73d47c740e0a3644bbd2d67029cf4a6c4693d2f470b642fd2cc3507f7e851df60aaeb730a1270b7a477f91ec5b6b17a8a4b40527@[::]:30303?discport=0"
INFO [02-01|23:35:12] IPC endpoint opened: /home/neo/.ethereum/geth.ipc 
INFO [02-01|23:35:12] HTTP endpoint opened: http://0.0.0.0:8545 
INFO [02-01|23:35:15] Mapped network port                      proto=tcp extport=30303 intport=30303 interface="UPNP IGDv1-IP1"		
		
			

Web3j 將使用這個地址連接 HTTP endpoint opened: http://your_ip_address:8545

5.3. Maven pom.xml 檔案

			
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>cn.netkiller</groupId>
	<artifactId>ethereum</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>ethereum</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.web3j</groupId>
			<artifactId>core</artifactId>
			<version>3.2.0</version>
		</dependency>
	</dependencies>
</project>
			
			
			

5.4. Java 與 Solidity 數據類型映射關係

			
boolean -> bool
BigInteger -> uint/int
byte[] -> bytes
String -> string and address types
List<> -> dynamic/static array			
			
			

5.5. 連接到伺服器獲取版本號

			
package cn.netkiller.ethereum;

import java.io.IOException;

import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.methods.response.Web3ClientVersion;
import org.web3j.protocol.http.HttpService;

public class Web3JClient {
	// TODO Auto-generated method stub

	public static void main(String[] args) {
		String url = "http://172.16.0.1:8545/";
		Web3j web3j = Web3j.build(new HttpService(url)); // defaults to http://localhost:8545/

		try {
			Web3ClientVersion web3ClientVersion = web3j.web3ClientVersion().send();
			String clientVersion = web3ClientVersion.getWeb3ClientVersion();
			System.out.println(clientVersion);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}
			
			

運行結果

			
Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9.1			
			
			

除了 TCP 方式連接,還支持 IPC 方式。這種方式比較少用,可以使用 localhost 替代。

			
// OS X/Linux/Unix:
Web3j web3 = Web3j.build(new UnixIpcService("/path/to/socketfile"));
...

// Windows
Web3j web3 = Web3j.build(new WindowsIpcService("/path/to/namedpipefile"));
...			
			
			

5.6. 賬號管理

5.6.1. 獲得賬號列表

				
	public List<String> getAccountlist() {

		try {
			return web3j.ethAccounts().send().getAccounts();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}				
				
				

5.6.2. 獲得賬號信息

				
	public String getAccount(int index) {
		String account = null;

		try {
			account = web3j.ethAccounts().send().getAccounts().get(index);

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return account;
	}				
				

				

5.6.3. 解鎖賬號

				
Admin web3j = Admin.build(new HttpService());  // defaults to http://localhost:8545/
PersonalUnlockAccount personalUnlockAccount = web3j.personalUnlockAccount("0x000...", "a password").sendAsync().get();
if (personalUnlockAccount.accountUnlocked()) {
    // send a transaction
}				
				
				

5.6.4. 例子

5.7. Credentials

			
package cn.netkiller.ethereum.credentials;

import java.io.IOException;
import java.math.BigInteger;

import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;

public class CredentialsTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		String url = "http://172.16.0.1:8545/";
		Web3j web3j = Web3j.build(new HttpService(url)); // defaults to http://localhost:8545/

		try {
			String account = web3j.ethAccounts().send().getAccounts().get(0);
			Credentials credentials = Credentials.create(account);
			ECKeyPair keyPair = credentials.getEcKeyPair();
			BigInteger privateKey = keyPair.getPrivateKey();
			BigInteger publicKey = keyPair.getPublicKey();

			System.out.println(privateKey);
			System.out.println("---");
			System.out.println(publicKey);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}
			
			
			

5.8. 交易

5.8.1. 獲取餘額

				
	public BigInteger getBalance(String account) throws IOException {

		EthGetBalance ethGetBalance = web3j.ethGetBalance(account, DefaultBlockParameterName.LATEST).send();
		BigInteger balance = ethGetBalance.getBalance();
		return balance;

	}				
				
				

5.8.2. 轉賬

				
	public void transfer(String account, float coin)
			throws InterruptedException, IOException, TransactionException, Exception {
		String password = "";
		String walletfile = "/Users/neo/netkiller/UTC--2018-01-20T04-04-06.786586541Z--83fda0ba7e6cfa8d7319d78fa0e6b753a2bcb5a6";
		Credentials credentials = WalletUtils.loadCredentials(password, walletfile);
		TransactionReceipt transactionReceipt = Transfer
				.sendFunds(web3j, credentials, account, BigDecimal.valueOf(coin), Unit.ETHER).send();
		System.out.println(transactionReceipt.getStatus());
	}
				
				

5.8.3. 交易結果查詢

				
EthTransaction transaction = web3.ethGetTransactionByHash("TRANSACTION_HASH")
                                   .sendAsync().get();
System.out.println(transaction.getResult());				
				
				

5.9. 錢包

5.9.1. 創建錢包

				
package cn.netkiller.ethereum.wallet;

import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

import org.web3j.crypto.CipherException;
import org.web3j.crypto.WalletUtils;

public class WalletMain {

	public void createWallet() throws NoSuchAlgorithmException, NoSuchProviderException,
			InvalidAlgorithmParameterException, CipherException, IOException {

		File file = new File("/tmp/ethereum/keystore");
		String password = "passw0rd";
		String fileName = WalletUtils.generateFullNewWalletFile(password, file);
		System.out.println(fileName);

	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		WalletMain wallet = new WalletMain();
		try {
			wallet.createWallet();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}
				
				
				

運行結果

				
neo@MacBook-Pro ~ % mkdir -p /tmp/ethereum/keystore
neo@MacBook-Pro ~ % ll /tmp/ethereum/keystore
total 8
-rw-r--r--  1 neo  wheel   491B Feb  4 18:30 UTC--2018-02-04T10-30-58.476000000Z--75d01e920d6e018445dae504058ce4d968fd2a58.json

neo@MacBook-Pro ~ % cat /tmp/ethereum/keystore/UTC--2018-02-04T10-30-58.476000000Z--75d01e920d6e018445dae504058ce4d968fd2a58.json 
{"address":"75d01e920d6e018445dae504058ce4d968fd2a58","id":"80700448-69bc-475a-aaf9-f2b836f17b13","version":3,"crypto":{"cipher":"aes-128-ctr","ciphertext":"fe86f5dbd61d15d092f9d6870e70bff7ed99a7925703ea71eef23669c8e3ec62","cipherparams":{"iv":"d058819ab660cd062080b405591ba143"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"f69c535137b08667dbac53b8001313f5b43f81fce67a5d0e94b518c97d212d14"},"mac":"c247e34760bc838c3a4c8b2da286ccc6acec244bbc13fc6cc9ce28e88a7319d5"}}
				
				

5.9.2. 從錢包取出賬號

				
package cn.netkiller.ethereum.wallet;

import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.WalletUtils;

public class WalletMain {

	public void walletAddress() throws IOException, CipherException {

		File file = new File(
				"/tmp/ethereum/keystore/UTC--2018-02-04T10-43-27.339000000Z--7cab470df532710d13078c5cdc0812a27f70cf51.json");
		String password = "passw0rd";
		Credentials credentials = WalletUtils.loadCredentials(password, file);
		System.out.println(credentials.getAddress());

	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		WalletMain wallet = new WalletMain();
		try {
			wallet.walletAddress();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}
				
				

運行結果

				
0x7cab470df532710d13078c5cdc0812a27f70cf51				
				
				

5.10. 智能合約

			
neo@netkiller ~/ethereum/solidity % cat netkiller.sol 
pragma solidity ^0.4.18;

contract Netkiller {
    string name;
    int num;
    function Netkiller() public{
        name = "default";
        num = 1;
    }
    function setName(string _name) public{
        name = _name;
    }
    function getName() public view returns(string){
        return name;
    }
    function setNum(int n) public{
        num = n;
    }
    function addNum(int m) public view returns(int res){
        res = m + num;
    }
}

			
			

編譯智能合約

			
$ solc /path/to/<smart-contract>.sol --bin --abi --optimize -o output/
$ web3j solidity generate /path/to/<smart-contract>.bin /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name			
			
			
			
$ solc netkiller.sol --bin --abi --optimize -o output/
$ web3j solidity generate output/Netkiller.bin output/Netkiller.abi -p cn.netkiller.ethereum.contract -o java

              _      _____ _     _        
             | |    |____ (_)   (_)       
__      _____| |__      / /_     _   ___  
\ \ /\ / / _ \ '_ \     \ \ |   | | / _ \ 
 \ V  V /  __/ |_) |.___/ / | _ | || (_) |
  \_/\_/ \___|_.__/ \____/| |(_)|_| \___/ 
                         _/ |             
                        |__/              

Generating cn.netkiller.ethereum.contract.Netkiller ... File written to java	

neo@netkiller ~/ethereum/solidity % ll java/cn/netkiller/ethereum/contract/Netkiller.java 
-rw-rw-r-- 1 neo neo 5.9K Feb  3 23:02 java/cn/netkiller/ethereum/contract/Netkiller.java
			
			

啟動以太坊,並開始挖礦。注意參數 --mine --minerthreads 1 ,你也可以啟動後在JavaScript 控制檯鐘啟動挖礦。

			
neo@netkiller ~ % geth --networkid 123456 --rpc --rpcaddr="0.0.0.0" --rpccorsdomain "*" --mine --minerthreads 1	
			
			
			
package cn.netkiller.ethereum;

import java.math.BigInteger;

import org.web3j.crypto.Credentials;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Contract;
import org.web3j.tx.ManagedTransaction;

import cn.netkiller.ethereum.contract.Netkiller;

public class ContractTest {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub

		String walletfile = "/Users/neo/Downloads/UTC--2018-01-20T04-04-06.786586541Z--83fda0ba7e6cfa8d7319d78fa0e6b753a2bcb5a6";

		Web3j web3j = Web3j.build(new HttpService("http://172.16.0.1:8545"));
		Credentials credentials = WalletUtils.loadCredentials("", walletfile);

		Netkiller contract = Netkiller.deploy(web3j, credentials, ManagedTransaction.GAS_PRICE, Contract.GAS_LIMIT).send();

		System.out.println(contract.isValid());
		if (contract.isValid()) {
			System.out.println("---");
			String contractAddress = contract.getContractAddress();
			System.out.println(contractAddress);
			System.out.println("---");
			String result = contract.getName().send();
			System.out.println(result);

			contract.setName("Netkiller").send();
			System.out.println(contract.getName().send());
			System.out.println("---");
			contract.setNum(BigInteger.valueOf(8)).send();
			System.out.println(contract.addNum(BigInteger.valueOf(8)).send());
			System.out.println("---");
		} else {
			System.out.println("Deploy ERROR !!!");
		}
	}

}
		
			
			

運行結果

			
true
---
0xef872f1b344a4b7c765c7d765a3cc82b741777a9
---
default
Netkiller
---
16
---
			
			

在程序運行是,去看 geth 打印的日誌,有如下記錄打印

			
INFO [02-04|00:04:43] Submitted transaction                    fullhash=0x9f70ccb600294d2dd6dda08d090362131b107d42a692f27dd4a3b7548dbaf22c recipient=0xEF872F1b344a4B7C765c7D765a3cC82b741777a9			
			
			

5.10.1. 載入合約

				
HelloWorld contract = HelloWorld.load(contractAddress,web3j,credentials, ManagedTransaction.GAS_PRICE, Contract.GAS_LIMIT);				
				
				
				
				

6. web3.py - A python interface for interacting with the Ethereum blockchain and ecosystem.

文檔地址 http://web3py.readthedocs.io/

7. 測試網絡

7.1. rinkeby

https://faucet.rinkeby.io

8. 總結

9. FAQ