以太坊智慧合約部署與互動
啟動容器來執行geth命令
root@ubu-blockchain2:~# docker run -i blockchain101/ethereum-geth:1.6.5 geth attach http://45.32.252.88:8201
Welcome to the Geth JavaScript console!
instance: Geth/01/v1.6.5-stable/linux-amd64/go1.8
coinbase: 0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a
at block: 6064 (Wed, 02 Aug 2017 01:13:50 UTC)
datadir: /ethcluster/779977/data/01
modules: admin:1.0 eth:1.0 net:1.0 rpc:1.0 web3:1.0
檢視我們的賬戶和餘額
eth.getBalance(eth.accounts[0])
11000000000000000000
eth.getBalance(eth.accounts[1])
0
eth.accounts[0]
"0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a"
> eth.accounts[1]
"0xe82e2f0a5abd8774767b9751659976f9c4f59181"
發起一筆交易
eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(3,'ether' )})
"0x0075da712d26aea17d6647035107f509e13eaf3d113c1577db14d4cc4216caec"
檢視交易細節
> eth.getTransaction("0x0075da712d26aea17d6647035107f509e13eaf3d113c1577db14d4cc4216caec")
{
blockHash: "0x3115703894dc6015c96ef4de3e5615f416498ca1f985902b38cd70e27dab8871",
blockNumber: 1250,
from: "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a" ,
gas: 90000,
gasPrice: 18000000000,
hash: "0x0075da712d26aea17d6647035107f509e13eaf3d113c1577db14d4cc4216caec",
input: "0x",
nonce: 0,
r: "0x2aef2c1fa03a0fa4172d21e3383d8c0431d45ec855b9d16efdd5eb2de90c414c",
s: "0xc4d530fb7902bf509fe56bfbea4861bf6cc16791afc9c9103c1a18f77407d1f",
to: "0xe82e2f0a5abd8774767b9751659976f9c4f59181",
transactionIndex: 0,
v: "0x17cdb6",
value: 3000000000000000000
}
> eth.getBalance(eth.accounts[1])
3000000000000000000
驗證使用者0的餘額
> eth.getBalance(eth.accounts[0])
7999622000000000000
編寫一個簡單的合約
contract Sample {
uint public value;
function Sample(uint v){
value=v;
}
function set(uint v){
value=v;
}
function get() constant returns (uint){
return value;
}
}
在remix網頁編譯得到ABI介面和合約的二進位制程式碼、
abi=[{"constant":true,"inputs":[],"name":"value","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"v","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"v","type":"uint256"}],"payable":false,"type":"constructor"}]
[{
constant: true,
inputs: [],
name: "value",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{
name: "v",
type: "uint256"
}],
name: "set",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "get",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
inputs: [{
name: "v",
type: "uint256"
}],
payable: false,
type: "constructor"
}]
需要使用eth.contract來定義一個合約類
> sample=eth.contract(abi)
{
abi: [{
constant: true,
inputs: [],
name: "value",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
type: "function"
}, {
inputs: [{...}],
payable: false,
type: "constructor"
}],
eth: {
accounts: ["0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a", "0xe82e2f0a5abd8774767b9751659976f9c4f59181"],
blockNumber: 6225,
coinbase: "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a",
compile: {
lll: function(),
serpent: function(),
solidity: function()
},
defaultAccount: undefined,
defaultBlock: "latest",
gasPrice: 18000000000,
hashrate: 0,
mining: false,
pendingTransactions: [],
protocolVersion: "0x3f",
syncing: false,
call: function(),
contract: function(abi),
estimateGas: function(),
filter: function(fil, callback),
getAccounts: function(callback),
getBalance: function(),
getBlock: function(),
getBlockNumber: function(callback),
getBlockTransactionCount: function(),
getBlockUncleCount: function(),
getCode: function(),
getCoinbase: function(callback),
getCompilers: function(),
getGasPrice: function(callback),
getHashrate: function(callback),
getMining: function(callback),
getPendingTransactions: function(callback),
getProtocolVersion: function(callback),
getRawTransaction: function(),
getRawTransactionFromBlock: function(),
getStorageAt: function(),
getSyncing: function(callback),
getTransaction: function(),
getTransactionCount: function(),
getTransactionFromBlock: function(),
getTransactionReceipt: function(),
getUncle: function(),
getWork: function(),
iban: function(iban),
icapNamereg: function(),
isSyncing: function(callback),
namereg: function(),
resend: function(),
sendIBANTransaction: function(),
sendRawTransaction: function(),
sendTransaction: function(),
sign: function(),
signTransaction: function(),
submitTransaction: function(),
submitWork: function()
},
at: function(address, callback),
getData: function(),
new: function()
}
合約的二進位制程式碼賦值給SampleHEX方便使用
SampleHEX="0x6060604052341561000c57fe5b60405160208061013a833981016040528080519060200190919050505b806000819055505b505b60f9806100416000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633fa4f24514604e57806360fe47b11460715780636d4ce63c14608e575bfe5b3415605557fe5b605b60b1565b6040518082815260200191505060405180910390f35b3415607857fe5b608c600480803590602001909190505060b7565b005b3415609557fe5b609b60c2565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b600060005490505b905600a165627a7a72305820208c8101070c8ba5a9b32db2bf4b8062a9ba50bc2869c39ac2297938756540e80029"
把合約程式碼部署上鍊
> thesample=sample.new(1,{from:eth.accounts[0],data:SampleHEX,gas:3000000})
{
abi: [{
constant: true,
inputs: [],
name: "value",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
type: "function"
}, {
inputs: [{...}],
payable: false,
type: "constructor"
}],
address: undefined,
transactionHash: "0xee74bcb4461c9712ec9aca96a5a3a4c3c64be1213854d519fc8e5432b554f7a1"
}
檢視交易細節
> samplerecpt=eth.getTransactionReceipt("0xee74bcb4461c9712ec9aca96a5a3a4c3c64be1213854d519fc8e5432b554f7a1")
{
blockHash: "0xddba16545af882835fb9a69a0e5f3b9287c61664837d5ea0068b38575cb665c5",
blockNumber: 6246,
contractAddress: "0x7504fa9d64ab290844b82660d43b310f8fba0276",
cumulativeGasUsed: 141836,
from: "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a",
gasUsed: 141836,
logs: [],
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
root: "0xd1093ecaca9cc0d10e82a533a15feccedf7ff5c79fb3ebd9366ec0b35dbef478",
to: null,
transactionHash: "0xee74bcb4461c9712ec9aca96a5a3a4c3c64be1213854d519fc8e5432b554f7a1",
transactionIndex: 0
}
合約命名
> samplecontract=sample.at("0x7504fa9d64ab290844b82660d43b310f8fba0276")
{
abi: [{
constant: true,
inputs: [],
name: "value",
outputs: [{...}],
payable: false,
type: "function"
}, {
constant: false,
inputs: [{...}],
name: "set",
outputs: [],
payable: false,
type: "function"
}, {
constant: true,
inputs: [],
name: "get",
outputs: [{...}],
payable: false,
type: "function"
}, {
inputs: [{...}],
payable: false,
type: "constructor"
}],
address: "0x7504fa9d64ab290844b82660d43b310f8fba0276",
transactionHash: null,
allEvents: function(),
get: function(),
set: function(),
value: function()
}
合約檢視功能函式get(),然後呼叫set()函式,再get()檢視時已經改變了
samplecontract.get.call()
1
> samplecontract.set.sendTransaction(9, {from:eth.accounts[0], gas:3000000})
"0x822ee6fb4caceb7e844c533f7f3bc57806f7cb3676fb3066eb848cca46b2f38a"
> samplecontract.get.call()
9
我們再開啟一個終端,開啟cluster1的peer02的控制檯,直接at到上一個終端部署的智慧合約地址並進行set操作
root@ubu-blockchain2:~/ethereum-docker/ethereum-docker/ethereum-testnet-docker/dockercomposefiles# docker run -i blockchain101/ethereum-geth:1.6.5 geth attach http://45.32.252.88:9201
Welcome to the Geth JavaScript console!
> abi=[{"constant":true,"inputs":[],"name":"value","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"v","type":"uint256"}],"name":"set","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[{"name":"v","type":"uint256"}],"payable":false,"type":"constructor"}]
> sample=eth.contract(abi)
SampleHEX="0x6060604052341561000c57fe5b60405160208061013a833981016040528080519060200190919050505b806000819055505b505b60f9806100416000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633fa4f24514604e57806360fe47b11460715780636d4ce63c14608e575bfe5b3415605557fe5b605b60b1565b6040518082815260200191505060405180910390f35b3415607857fe5b608c600480803590602001909190505060b7565b005b3415609557fe5b609b60c2565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b600060005490505b905600a165627a7a72305820208c8101070c8ba5a9b32db2bf4b8062a9ba50bc2869c39ac2297938756540e80029"
直接把合約地址賦值並進行set操作
samplecontract=sample.at("0x7504fa9d64ab290844b82660d43b310f8fba0276")
> samplecontract.get.call()
9
這裡在網上找了個代幣的只能合約,可以進行充值、轉賬和查詢,issue 函式可以向充值以太到合約賬戶,transfer 函式可以向其他賬號傳送token,getBalance 函式可以獲取某個賬號的token餘額,程式碼如下:
pragma solidity ^0.4.2;
contract Token {
address issuer;
mapping (address => uint) balances;
event Issue(address account, uint amount);
event Transfer(address from, address to,uint amount);
function Token() {
issuer = msg.sender;
}
function issue(address account, uintamount) {
if (msg.sender != issuer) throw;
balances[account] += amount;
}
function transfer(address to, uint amount){
if (balances[msg.sender] < amount)throw;
balances[msg.sender] -= amount;
balances[to] += amount;
Transfer(msg.sender, to, amount);
}
function getBalance(address account)constant returns (uint) {
return balances[account];
}
}
修改編譯好的gas和物件名稱:
varbrowser_untitled_sol_tokenContract =web3.eth.contract([{"constant":false,"inputs":[{"name":"account","type":"address"},{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Transfer","type":"event"}]);
var token =browser_untitled_sol_tokenContract.new(
{
from: web3.eth.accounts[0],
data:'0x6060604052341561000f57600080fd5b5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5b6103d2806100616000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063867904b414610054578063a9059cbb14610096578063f8b2cb4f146100d8575b600080fd5b341561005f57600080fd5b610094600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610125565b005b34156100a157600080fd5b6100d6600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101d2565b005b34156100e357600080fd5b61010f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061035c565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561018057600080fd5b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505b5050565b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561021e57600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055507fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef338383604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a15b5050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b9190505600a165627a7a723058204afe007a03446d43d13ac892e6dba9d032f540a11ff427d26c22560727cbea2f0029',
gas: '4300000'
}, function (e, contract){
console.log(e, contract);
if (typeof contract.address !=='undefined') {
console.log('Contract mined! address:' + contract.address + ' transactionHash: ' + contract.transactionHash);
}
})
此時輸入合約部署的例項a_demotypes, 可以看到a_demotypes的詳情。
控制檯呼叫
> a_demotypes
{
abi: [{
constant: false,
inputs: [{...}],
name: "f",
outputs: [{...}],
payable: false,
type: "function"
}],
address: "0x54ed7a5f5a63ddada3bfe83b3e632adabaa5fc2f",
transactionHash: "0x69cde62bcd6458e14f40497f4840f422911d63f5dea2b3a9833e6810db64a1c9",
allEvents: function(),
f: function()
}
這裡重點就是address表示的是合約的地址,你會發現這個和賬號的資訊結果一樣,其實你也可以把這個合約地址看做是一個賬號地址,後面我們外部呼叫到的就是這個合約地址。
充值
token.issue.sendTransaction(eth.accounts[0],100, {from: eth.accounts[0]});
傳送 token
token.transfer(eth.accounts[1], 30, {from:eth.accounts[0]})
檢視餘額
token.getBalance()
控制檯呼叫就不多說,和Java物件呼叫一樣,直接呼叫即可
外部介面與智慧合約互動
以太坊對外提供的有很多介面JSON RPC介面,web3介面,這裡我們用JSON RPC介面。
相關API: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendrawtransaction
合約互動的原理
合約的互動都是一次交易,而我們要做的就是把要呼叫的方法和引數按照api規定的以引數的形式向區塊請求一次交易,ethereum接收到我們的請求後通過解析傳遞的引數來執行相關的合約程式碼。
RPC介面給我們提供了倆個方法:eth_sendTransaction和eth_call。
eth_sendTransaction
Createsnew message call transaction or a contract creation, if the data field containscode.
Parameters
Object - The transaction object
from: DATA, 20 Bytes - The address the transaction is send from.
to: DATA, 20 Bytes - (optional when creating new contract) The address the transaction is directed to.
gas: QUANTITY - (optional, default: 90000) Integer of the gas provided for the transaction execution. It will return unused gas.
gasPrice: QUANTITY - (optional, default: To-Be-Determined) Integer of the gasPrice used for each paid gas
value: QUANTITY - (optional) Integer of the value send with this transaction
data: DATA - The compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI
nonce: QUANTITY - (optional) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce.
可以看到,如果我們建立的為合約時,我們只需要from,to(文件上寫的是可選的,但是實際操作中沒有to為null的話合約不能正常執行,建議還是加上,這個值就是前面我們部署合約後生成的合約address),data。Data的屬性的值具體可以看Contract ABI。這裡大概說下:
Data的值相對來說不是固定的,具體怎麼生成與合約的引數型別,引數數量都有關聯。這裡我們以部署的token合約的三個方法為例:
充值issue (address account, uint amount)
這個方法有倆個引數,address充值賬號,uint充值數量。
根據Contract ABI,data值應該為方法名的sha3的前8個位元組+引數的64位元組,不夠前面補充為0。
這裡方法名並不是issue (address account, uint amount)而是issue(address,uint256)的sha3值。
web3.sha3("issue(address,uint256)")
"0x867904b44b606800f6f10498e11292d04ea19bfc7fe4bc0f1695aa516381f73d"
我們往第一個賬號充值10,這裡的資料不是以太幣,而是我們自己建立的代幣。
eth.accounts[0]
"0x0cc9684af605bae10de801218321c1336bb62946"
將10轉換為16進製為
000000000000000000000000000000000000000000000000000000000000000a
那麼data的資料為:
0x867904b4000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb000000000000000000000000000000000000000000000000000000000000000a
那麼最後我們呼叫eth_sendTransaction方法傳遞引數JSON物件為:
{
from:0xfdf57e81872562a6112656f961944ce821fdf7eb,
to:0x7fe133950fc010ce41322d88f64f1918b9abb3a3,
data: 0x867904b4000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb000000000000000000000000000000000000000000000000000000000000000a
}
返回:此交易的hash值,此時該交易還沒有執行,只是建立,還需要礦工通過挖礦才能完成。
呼叫介面方法:
JsonRpcHttpClient client = newJsonRpcHttpClient(new URL(“http://127.0.0.1:8545”));
Object result = client.invoke(methodName,params, Object.class);
通過控制檯檢視第一個賬號已有代幣:
token.getbalance(eth.accounts[0])
執行呼叫介面程式碼。返回交易hash值:
0x2013764d1c3fea680f9015353497aa5f9f8d61580a3bd0524b3613b34329c095
此時控制檯輸入:
交易和充值一樣,需要注意的是代幣轉出賬號為from屬性的值,代幣轉入賬號為data屬性裡的值,to對應的是合約地址。
eth_call
Executes anew message call immediately without creating a transaction on the block chain.
Parameters
Object - The transaction call object
from: DATA, 20 Bytes - (optional) The address the transaction is sent from.
to: DATA, 20 Bytes - The address the transaction is directed to.
gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas
value: QUANTITY - (optional) Integer of the value send with this transaction
data: DATA - (optional) Hash of the method signature and encoded parameters. For details seeEthereum Contract ABI
QUANTITY|TAG - integer block number, or the string "latest", "earliest" or "pending", see the default block parameter
這個方法返回一條資訊給我們,相當於資料庫的查詢功能,引數也是三個,from,to,data,資料格式都是一樣的。
查詢getBalance(address account)
查詢方法hash碼:
web3.sha3("getBalance(address)")
"0xf8b2cb4f3943230388faeee074f1503714bff212640051caba01411868d14ae3"
查詢我們上一步充值的賬號,那麼傳遞的引數data為:
0xf8b2cb4f000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb
eth_call方法最後引數為:
{
from: 0xfdf57e81872562a6112656f961944ce821fdf7eb,
to:0x7fe133950fc010ce41322d88f64f1918b9abb3a3,
data:0xf8b2cb4f000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb
}
注意,這個方法需要倆引數,處理一個JSONobject外,還有一個字串引數,這倆可以為“”或者”latest”, “earliest” or “pending”
呼叫介面返回一個16進位制字串:
0x0000000000000000000000000000000000000000000000000000000000000071就是該賬號的代幣數量,轉換為十進位制為:113,與控制查詢一致。