摘要:本文通過宏觀和微觀兩個層面窺探以太坊底層執行邏輯。開發等前端還是好,和就免了不太好用全局安裝初始化一個基于的項目項目里安裝依賴是的庫,通過方式與以太坊節點交互。
本文通過宏觀和微觀兩個層面窺探以太坊底層執行邏輯。
宏觀層面描述創建并運行一個小型帶錢包的發幣APP的過程,微觀層面是順藤摸瓜從http api深入go-ethereum源碼執行過程。
分析思路:自上而下,從APP深入EVM。
從應用入手,如果一頭扎進ethereum,收獲的可能是純理論的東西,要想有所理解還得結合以后的實踐才能恍然大悟。所以我始終堅持從應用入手、自上而下是一種正確、事半功倍的方法論。
我在講解以太坊基礎概念的那篇專題文章里,用的是從整體到局部的方法論,因為研究目標就是一個抽象的理論的東西,我對一個全然未知的東西的了解總是堅持從整體到局部的思路。
項目創建、部署合約到私鏈之前用truffle框架做項目開發,這個框架封裝了合約的創建、編譯、部署過程,為了研究清楚自上至下的架構,這里就不用truffle構建項目了。
項目前端基于vue,后端是geth節點,通過web3 http api通信。
開發vue、solidity等前端IDE還是webstorm好,Atom和goland就免了不太好用!
1、全局安裝vue-cli
npm i -g vue-cli
2、初始化一個基于webpack的vue項目
vue init webpack XXXProject
3、項目里安裝web3依賴
web3.js是ethereum的javascript api庫,通過rpc方式與以太坊節點交互。
npm install --save web3@1.0.0-beta.34
盡量用npm安裝,不要用cnpm,有時候是個坑玩意,會生成“_”開頭的很多垃圾還要求各種install。也可以寫好了package.json,刪除node_modules文件夾,再執行npm i。web3版本用1.0以上,和1.0以下語法有很大不同。
4、項目里創建全局web3對象
用vuex有點啰嗦,這里就寫個vue插件,提供全局的web3對象。
import Web3 from "web3" export default { install: function (Vue, options) { var web3 = window.web3 if (typeof web3 !== "undefined") { web3 = new Web3(web3.currentProvider) } else { web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) } Vue.prototype.$web3 = web3 } }
在main.js里啟用該插件,以后就可以這樣使用this.$web3這個全局對象了。
Vue.use(插件名)
5、寫一個ERC20合約
代碼省略
項目全部代碼地址:
https://github.com/m3shine/To...
6、編譯&部署合約
有必要說明一下編譯和部署方式的選擇,它嚴重關系到你實際項目的開發:
1)使用Remix,把自己寫好的合約拷貝到Remix里進行編譯和部署。這種方式最方便。
2)使用truffle這類的框架,這種方式是需要項目基于框架開發了,編譯和部署也是在truffle控制臺進行。
3)基于web3和solc依賴,寫編譯(solc)和部署(web3)程序,這些代碼就獨立(vue是前端,nodejs是后端,運行環境不同)于項目了,用node多帶帶運行。
4)本地安裝solidity進行編譯,部署的話也需要自己想辦法完成。
5)使用geth錢包、mist等編譯部署。
……
從geth1.6.0開始,剝離了solidity編譯函數,所以web3也不能調用編譯方法了。可以本地安裝solidity帶編譯器,也可以在項目里依賴solc進行編譯。
編譯部署的方式眼花繚亂,這里選擇方式3。
編譯部署參考代碼(web3的1.0及以上版本):token_deploy.js
const Web3 = require("web3") const fs = require("fs") const solc = require("solc") const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); const input = fs.readFileSync("../contracts/Token.sol"); const output = solc.compile(input.toString(), 1); fs.writeFile("../abi/Token.abi", output.contracts[":Token"].interface, err => { if (err) { console.log(err) } }) const bytecode = output.contracts[":Token"].bytecode; const abi = JSON.parse(output.contracts[":Token"].interface); const tokenContract = new web3.eth.Contract(abi); let log = { time: new Date(Date.now()), transactionHash: "", contractAddress: "" } // 部署合約 tokenContract.deploy({ data: "0x" + bytecode, arguments: ["200000000", "魔法幣", "MFC"] // Token.sol構造參數 }) .send({ from: "0x2d2afb7d0ef71f85dfbdc89d288cb3ce8e049e10", //寫你自己的礦工(發幣)地址 gas: 5000000, // 這個值很微妙 }, (err, transactionHash) => { if (err) { console.log(err); return; } log.transactionHash = transactionHash }) .on("error", error => { console.log(error); }) // 不是總能成功獲取newContractInstance, 包括監聽receipt也可能發生異常,原因是receipt獲取時機可能發生在交易未完成前。 .then(function (newContractInstance) { if(newContractInstance){ log.contractAddress = newContractInstance.options.address } fs.appendFile("Token_deploy.log",JSON.stringify(log) + " ", err => { if (err) { console.log(err) } }) }); ;
7、在執行部署腳本前,需要有一個賬戶并解鎖,在geth控制臺執行以下命令:
personal.newAccount("密碼") personal.unlockAccount(eth.coinbase,"密碼","20000")
8、發布合約是需要eth幣的,所以先挖礦弄點以太幣:
miner.start()
9、現在可以執行編譯部署腳本了:
node token_deploy.js
如果前面miner.stop()過,那么在執行部署的時候,要確保miner.start(),有礦工打包才能出塊。
這里還要知道,因為就是本礦工賬戶創建合約,所以交易費又回到了本賬戶,因此余額看起來總是沒有減少。
至此,我們已經在私鏈上部署了一個合約,產生了一筆交易(即創建合約本身這個交易)、一個礦工賬戶、一個合約賬戶。
常見錯誤Error: insufficient funds for gas * price + value
意思是賬戶里沒有足夠的eth幣,給創建合約的賬戶里弄些比特幣。
Error: intrinsic gas too low
調高以下發布合約時的gas值。
Error: Invalid number of parameters for "undefined". Got 0 expected 3! (類似這樣的)
沒有傳入合約構造函數參數
調用鏈上合約合約部署成功,就有了合約地址,根據合約地址構建合約實例。
let tokenContract = new this.$web3.eth.Contract(JSON.parse(abi),"合約地址")
tokenContract.methods.myMethod.
call()調用的都是abi里的constant方法,即合約里定義的狀態屬性,EVM里不會發送交易,不會改變合約狀態。
send()調用的是合約里定義的函數,是要發送交易到合約并執行合約方法的,會改變合約狀態。
以上就簡單說一下,不寫太多了。看官可以自行下載本項目源碼(上面第5步有github鏈接),自己運行起來看看界面和發幣/轉賬操作。
源碼交易過程分析當我們在項目中創建一個合約的時候,發生了什么?
geth節點默認開放了8545 RPC端口,web3通過連接這個rpc端口,以http的方式調用geth開放的rpc方法。從這一web3與以太坊節點交互基本原理入手,先分析web3源碼是怎樣調用rpc接口,對應的geth接口是否同名,再看geth源碼該接口又是怎么執行的。
new web3.eth.Contract(jsonInterface[, address][, options])這個函數,jsonInterface就是abi,不管傳不傳options,options.data屬性總是abi的編碼。
這個web3接口源碼中調用eth.sendTransaction,options.data編碼前面加了簽名,options.to賦值一個地址,最后返回這筆交易的hash。
再返回上面第6步看一下部署腳本,代碼截止到deploy都是在構造web3里的對象,首次與本地geth節點通信的方法是send,它是web3的一個接口方法。
deploy返回的是個web3定義的泛型TransactionObject
Contract對send接口方法的實現如下:
var sendTransaction = (new Method({ name: "sendTransaction", call: "eth_sendTransaction", params: 1, inputFormatter: [formatters.inputTransactionFormatter], requestManager: _this._parent._requestManager, accounts: Contract._ethAccounts, // is eth.accounts (necessary for wallet signing) defaultAccount: _this._parent.defaultAccount, defaultBlock: _this._parent.defaultBlock, extraFormatters: extraFormatters })).createFunction(); return sendTransaction(args.options, args.callback);
這個send最終由XMLHttpRequest2的request.send(JSON.stringify(payload))與節點通信。
var sendSignedTx = function(sign){ payload.method = "eth_sendRawTransaction"; payload.params = [sign.rawTransaction]; method.requestManager.send(payload, sendTxCallback); };
所以send方法對應的節點api是eth_sendRawTransaction。
go-ethereum/ethclient/ethclient.go
func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { data, err := rlp.EncodeToBytes(tx) if err != nil { return err } return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data)) }
找到該api執行入口
go-ethereum/internal/ethapi.SendTransaction
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) { // Look up the wallet containing the requested signer account := accounts.Account{Address: args.From} wallet, err := s.b.AccountManager().Find(account) if err != nil { return common.Hash{}, err } …… return submitTransaction(ctx, s.b, signed) }
我們在這個函數處打一個斷點!然后執行部署腳本(可以多次執行),運行到斷點處:
要調試geth需要對其重新編譯,去掉它原來編譯的優化,參見下面“調試源碼”一節。
(dlv) p args github.com/ethereum/go-ethereum/internal/ethapi.SendTxArgs { From: github.com/ethereum/go-ethereum/common.Address [45,42,251,125,14,247,31,133,223,189,200,157,40,140,179,206,142,4,158,16], To: *github.com/ethereum/go-ethereum/common.Address nil, Gas: *5000000, GasPrice: *github.com/ethereum/go-ethereum/common/hexutil.Big { neg: false, abs: math/big.nat len: 1, cap: 1, [18000000000],}, Value: *github.com/ethereum/go-ethereum/common/hexutil.Big nil, Nonce: *github.com/ethereum/go-ethereum/common/hexutil.Uint64 nil, Data: *github.com/ethereum/go-ethereum/common/hexutil.Bytes len: 2397, cap: 2397, [96,96,96,64,82,96,2,128,84,96,255,25,22,96,18,23,144,85,52,21,97,0,28,87,96,0,128,253,91,96,64,81,97,8,125,56,3,128,97,8,125,131,57,129,1,96,64,82,128,128,81,145,144,96,32,1,128,81,130,1,145,144,96,32,...+2333 more], Input: *github.com/ethereum/go-ethereum/common/hexutil.Bytes nil,}
(dlv) p wallet github.com/ethereum/go-ethereum/accounts.Wallet(*github.com/ethereum/go-ethereum/accounts/keystore.keystoreWallet) *{ account: github.com/ethereum/go-ethereum/accounts.Account { Address: github.com/ethereum/go-ethereum/common.Address [45,42,251,125,14,247,31,133,223,189,200,157,40,140,179,206,142,4,158,16], URL: (*github.com/ethereum/go-ethereum/accounts.URL)(0xc4200d9f18),}, keystore: *github.com/ethereum/go-ethereum/accounts/keystore.KeyStore { storage: github.com/ethereum/go-ethereum/accounts/keystore.keyStore(*github.com/ethereum/go-ethereum/accounts/keystore.keyStorePassphrase) ..., cache: *(*github.com/ethereum/go-ethereum/accounts/keystore.accountCache)(0xc4202fe360), changes: chan struct {} { qcount: 0, dataqsiz: 1, buf: *[1]struct struct {} [ {}, ], elemsize: 0, closed: 0, elemtype: *runtime._type { size: 0, ptrdata: 0, hash: 670477339, tflag: 2, align: 1, fieldalign: 1, kind: 153, alg: *(*runtime.typeAlg)(0x59e69d0), gcdata: *1, str: 67481, ptrToThis: 601472,}, sendx: 0, recvx: 0, recvq: waitq{ first: *(*sudog )(0xc42006ed20), last: *(*sudog )(0xc42006ed20),}, sendq: waitq { first: *sudog nil, last: *sudog nil,}, lock: runtime.mutex {key: 0},}, unlocked: map[github.com/ethereum/go-ethereum/common.Address]*github.com/ethereum/go-ethereum/accounts/keystore.unlocked [...], wallets: []github.com/ethereum/go-ethereum/accounts.Wallet len: 2, cap: 2, [ ..., ..., ], updateFeed: (*github.com/ethereum/go-ethereum/event.Feed)(0xc4202c4040), updateScope: (*github.com/ethereum/go-ethereum/event.SubscriptionScope)(0xc4202c40b0), updating: true, mu: (*sync.RWMutex)(0xc4202c40cc),},}
(dlv) p s.b github.com/ethereum/go-ethereum/internal/ethapi.Backend(*github.com/ethereum/go-ethereum/eth.EthApiBackend) *{ eth: *github.com/ethereum/go-ethereum/eth.Ethereum { config: *(*github.com/ethereum/go-ethereum/eth.Config)(0xc420153000), chainConfig: *(*github.com/ethereum/go-ethereum/params.ChainConfig)(0xc4201da540), shutdownChan: chan bool { qcount: 0, dataqsiz: 0, buf: *[0]bool [], elemsize: 1, closed: 0, elemtype: *runtime._type { size: 1, ptrdata: 0, hash: 335480517, tflag: 7, align: 1, fieldalign: 1, kind: 129, alg: *(*runtime.typeAlg)(0x59e69e0), gcdata: *1, str: 21072, ptrToThis: 452544,}, sendx: 0, recvx: 0, recvq: waitq{ first: *(*sudog )(0xc420230ba0), last: *(*sudog )(0xc420231440),}, sendq: waitq { first: *sudog nil, last: *sudog nil,}, lock: runtime.mutex {key: 0},}, stopDbUpgrade: nil, txPool: *(*github.com/ethereum/go-ethereum/core.TxPool)(0xc420012380), blockchain: *(*github.com/ethereum/go-ethereum/core.BlockChain)(0xc42029c000), protocolManager: *(*github.com/ethereum/go-ethereum/eth.ProtocolManager)(0xc420320270), lesServer: github.com/ethereum/go-ethereum/eth.LesServer nil, chainDb: github.com/ethereum/go-ethereum/ethdb.Database(*github.com/ethereum/go-ethereum/ethdb.LDBDatabase) ..., eventMux: *(*github.com/ethereum/go-ethereum/event.TypeMux)(0xc4201986c0), engine: github.com/ethereum/go-ethereum/consensus.Engine(*github.com/ethereum/go-ethereum/consensus/ethash.Ethash) ..., accountManager: *(*github.com/ethereum/go-ethereum/accounts.Manager)(0xc420089860), bloomRequests: chan chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval { qcount: 0, dataqsiz: 0, buf: *[0]chan *github.com/ethereum/go-ethereum/core/bloombits.Retrieval [], elemsize: 8, closed: 0, elemtype: *runtime._type { size: 8, ptrdata: 8, hash: 991379238, tflag: 2, align: 8, fieldalign: 8, kind: 50, alg: *(*runtime.typeAlg)(0x59e6a10), gcdata: *1, str: 283111, ptrToThis: 0,}, sendx: 0, recvx: 0, recvq: waitq { first: *(*sudog )(0xc420230c00), last: *(*sudog )(0xc4202314a0),}, sendq: waitq { first: *sudog nil, last: *sudog nil,}, lock: runtime.mutex {key: 0},}, bloomIndexer: unsafe.Pointer(0xc4201b23c0), ApiBackend: *(*github.com/ethereum/go-ethereum/eth.EthApiBackend)(0xc4202b8910), miner: *(*github.com/ethereum/go-ethereum/miner.Miner)(0xc420379540), gasPrice: *(*math/big.Int)(0xc420233c40), etherbase: github.com/ethereum/go-ethereum/common.Address [45,42,251,125,14,247,31,133,223,189,200,157,40,140,179,206,142,4,158,16], networkId: 13, netRPCService: *(*github.com/ethereum/go-ethereum/internal/ethapi.PublicNetAPI)(0xc42007feb0), lock: (*sync.RWMutex)(0xc4202ea528),}, gpo: *github.com/ethereum/go-ethereum/eth/gasprice.Oracle { backend: github.com/ethereum/go-ethereum/internal/ethapi.Backend(*github.com/ethereum/go-ethereum/eth.EthApiBackend) ..., lastHead: github.com/ethereum/go-ethereum/common.Hash [139,147,220,247,224,227,136,250,220,62,217,102,160,96,23,182,90,90,108,254,82,158,234,95,150,120,163,5,61,248,168,168], lastPrice: *(*math/big.Int)(0xc420233c40), cacheLock: (*sync.RWMutex)(0xc420010938), fetchLock: (*sync.Mutex)(0xc420010950), checkBlocks: 20, maxEmpty: 10, maxBlocks: 100, percentile: 60,},}
// submitTransaction is a helper function that submits tx to txPool and logs a message. func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) { if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } if tx.To() == nil { signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) from, err := types.Sender(signer, tx) if err != nil { return common.Hash{}, err } addr := crypto.CreateAddress(from, tx.Nonce()) log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex()) } else { log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To()) } return tx.Hash(), nil }
(dlv) p tx *github.com/ethereum/go-ethereum/core/types.Transaction { data: github.com/ethereum/go-ethereum/core/types.txdata { AccountNonce: 27, Price: *(*math/big.Int)(0xc4217f5640), GasLimit: 5000000, Recipient: *github.com/ethereum/go-ethereum/common.Address nil, Amount: *(*math/big.Int)(0xc4217f5620), Payload: []uint8 len: 2397, cap: 2397, [96,96,96,64,82,96,2,128,84,96,255,25,22,96,18,23,144,85,52,21,97,0,28,87,96,0,128,253,91,96,64,81,97,8,125,56,3,128,97,8,125,131,57,129,1,96,64,82,128,128,81,145,144,96,32,1,128,81,130,1,145,144,96,32,...+2333 more], V: *(*math/big.Int)(0xc4217e0a20), R: *(*math/big.Int)(0xc4217e09c0), S: *(*math/big.Int)(0xc4217e09e0), Hash: *github.com/ethereum/go-ethereum/common.Hash nil,}, hash: sync/atomic.Value { noCopy: sync/atomic.noCopy {}, v: interface {} nil,}, size: sync/atomic.Value { noCopy: sync/atomic.noCopy {}, v: interface {} nil,}, from: sync/atomic.Value { noCopy: sync/atomic.noCopy {}, v: interface {} nil,},}
(dlv) bt 0 0x00000000048d9248 in github.com/ethereum/go-ethereum/internal/ethapi.submitTransaction at ./go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1130 1 0x00000000048d9bd1 in github.com/ethereum/go-ethereum/internal/ethapi.(*PublicTransactionPoolAPI).SendTransaction at ./go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1176
(dlv) frame 0 l > github.com/ethereum/go-ethereum/internal/ethapi.submitTransaction() ./go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1130 (PC: 0x48d9248) Warning: debugging optimized function 1125: if err := b.SendTx(ctx, tx); err != nil { 1126: return common.Hash{}, err 1127: } 1128: if tx.To() == nil { 1129: signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number()) =>1130: from, err := types.Sender(signer, tx) 1131: if err != nil { 1132: return common.Hash{}, err 1133: } 1134: addr := crypto.CreateAddress(from, tx.Nonce()) 1135: log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex()) (dlv) frame 1 l Goroutine 3593 frame 1 at /Users/jiang/go/src/github.com/ethereum/go-ethereum/internal/ethapi/api.go:1176 (PC: 0x48d9bd1) 1171: } 1172: signed, err := wallet.SignTx(account, tx, chainID) 1173: if err != nil { 1174: return common.Hash{}, err 1175: } =>1176: return submitTransaction(ctx, s.b, signed) 1177: } 1178: 1179: // SendRawTransaction will add the signed transaction to the transaction pool. 1180: // The sender is responsible for signing the transaction and using the correct nonce. 1181: func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
先把調試結果展示出來,通過對一個交易的內部分析,可以了解EVM執行的大部分細節,此處需要另開篇幅詳述。請關注后續專題。
源碼調試1、重新強制編譯geth,去掉編譯內聯優化,方便跟蹤調試。
cd path/go-ethereum sudo go install -a -gcflags "-N -l" -v ./cmd/geth
編譯后的geth執行文件就在$gopath/bin下。
2、在datadir下啟動這個重新編譯后的geth
geth --datadir "./" --rpc --rpccorsdomain="*" --networkid 13 console 2>>00.glog
3、調試這個進程
dlv attach
4、給交易api入口函數設置斷點
b ethapi.(*PublicTransactionPoolAPI).SendTransaction
下面是一個區塊鏈小程序,供大家參考。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24036.html
摘要:使用和以太坊客戶端的容器鏡像,可以快速啟動解決方案,實現區塊鏈技術的本地開發。以太坊,主要是針對工程師使用進行區塊鏈以太坊開發的詳解。以太坊,主要講解如何使用開發基于的以太坊應用,包括賬戶管理狀態與交易智能合約開發與交互過濾器和事件等。 區塊鏈最近IT世界的流行語之一。這項有關數字加密貨幣的技術,并與比特幣一起構成了這個熱門的流行趨勢。它是去中心化的,不可變的分塊數據結構,這是可以安全...
摘要:使用和以太坊客戶端的容器鏡像,可以快速啟動解決方案,實現區塊鏈技術的本地開發。以太坊,主要是針對工程師使用進行區塊鏈以太坊開發的詳解。以太坊,主要講解如何使用開發基于的以太坊應用,包括賬戶管理狀態與交易智能合約開發與交互過濾器和事件等。 區塊鏈最近IT世界的流行語之一。這項有關數字加密貨幣的技術,并與比特幣一起構成了這個熱門的流行趨勢。它是去中心化的,不可變的分塊數據結構,這是可以安全...
本文首發于深入淺出區塊鏈社區原文鏈接:[使用 ethers.js 開發以太坊 Web 錢包 3 - 展示錢包信息及發起簽名交易)](https://learnblockchain.cn/20...,請讀者前往原文閱讀 以太坊去中心化網頁錢包開發系列,將從零開始開發出一個可以實際使用的錢包,本系列文章是理論與實戰相結合,一共有四篇:創建錢包賬號、賬號Keystore文件導入導出、展示錢包信息及發起簽...
本文首發于深入淺出區塊鏈社區原文鏈接:[使用 ethers.js 開發以太坊 Web 錢包 3 - 展示錢包信息及發起簽名交易)](https://learnblockchain.cn/20...,請讀者前往原文閱讀 以太坊去中心化網頁錢包開發系列,將從零開始開發出一個可以實際使用的錢包,本系列文章是理論與實戰相結合,一共有四篇:創建錢包賬號、賬號Keystore文件導入導出、展示錢包信息及發起簽...
閱讀 2600·2021-11-15 11:38
閱讀 2618·2021-11-04 16:13
閱讀 17979·2021-09-22 15:07
閱讀 1014·2019-08-30 15:55
閱讀 3260·2019-08-30 14:15
閱讀 1663·2019-08-29 13:59
閱讀 3206·2019-08-28 18:28
閱讀 1575·2019-08-23 18:29