GO 语言: 联盟链、私有链的搭建,设立接口、监听
Solidity语言: 智能合约开发,区块链上的逻辑实现
Node.js : 监听、通信、RPC
JavaScript : Dapp应用程序开发
现在市面上的区块链书很多、很杂,学习难度也相差很大。在走了一些弯路后,本人翻阅了这些书的主要内容、记录了每本书的主要侧重点与讲述较好的地方,并给出如下的阅读建议。并非要全部通读,尤其在有了一定基础之后可直接阅读书中的核心部分。
有了此相对平滑的学习曲线,相信你我在自学的路上一定能越走越远。祝你成功!
建议学习顺序(我目前还在5):
1. 《JavaScript编程精解》(掌握JS的基本编程)
2. 《Solidity编程》(掌握智能合约的基本编程)
3. 《区块链以太坊Dapp开发实战》 《GO语言实战》(参考) (学习GO的RPC接口链接以太坊)
4. 《深入理解以太坊》(此时学习以太坊内部的细节更易于理解)
5. 《第一行代码以太坊》(学习通过web3接口链接以太坊)
6. 《区块链Dapp》(Dapp完整实战)
7. 《区块链开发实战实用案例分析》(更复杂更多元的项目)《深入以太坊智能合约开发》(参考避免重复造轮子)
8. 《node.js区块链开发》(开放了更多功能,不再局限于只使用接口)
9. 《GO语言公链开发实战》《GO并发编程实战》(完全解开手脚,不再局限于别人的链,订制自己的联盟链、企业链)
电子身份、证据认证系统
供应链上下游智能合约
网上拍卖软件
众筹平台
融资、股份认证等
Ganache
使用Ganache图形界面端或ganache-cli快速搭建私有链,使用图形界面可快速监控链上的实时交易。
给出一个win下exe格式的下载链接:ganache图形界面下载地址
MetaMask
Mist钱包已经停用,使用轻度的MetaMask钱包管理主网、测试网、私有链中的账户
使用以太坊源码调用创建账户的函数,地址将保存在keystore下
再使用解码函数传入密码从中解出私钥(交易签名、导入其他钱包时使用)
//存储钱包文件的位置
keydir := "./keystores"
//调用源码
ks := keystore.NewKeyStore(keydir,keystore.StandardScryptN,keystore.StandardScryptP)
wallet,err := ks.NewAccount(password)
remix:
Truffle:
init
并导入到ganache的项目中build
编译当前工程内的所有sol文件migrate
部署智能合约进当前网络给出Truffle官方文档https://www.trufflesuite.com/docs/truffle/overview
remix
使用网页版remix编写智能合约
中文版remix网站 http://remix.hubwiz.com/
英文版remix网站 http://remix.ethereum.org
建议使用英文版,因为中文版可能存在以太坊测试网络连不上的问题
配合atom保存到本地
智能合约无法自动触发,只能通过外部调用。所以智能合约只能发布事件,但不能监听。
发布的事件数据储存在该交易(函数调用)的日志中,如下图所示为满足ERC20协议的Transfer交易触发的交易事件。
LOG由address、Topic、Data三部分组成
sha3('Transfer(address,uint256)')
同时,在solidity中定义事件时给出indexed关键字则参数将保存在topic中,而不存在data中,更有利于筛选事件:
event Transfer(address indexed from,address indexed to, uint256 value)
则Topic将出现3行数据,分别为函数头hash,传出参数1 from,参数2 to
用Keecak256算法给函数声明字符串进行hash,取结果的前四个字节:
bytes4(keccak256(Transfer(address,uint256)))
加密货币协议,可用于股权证明、众筹证明等一切权益共识
加密猫所用的协议,唯一个体币协议
开发环境 :GO
rpcClient,err := rpc.DialHTTP(erc.NodeUrl)
配置以太坊RPC接口接口协议
采用json结构传输数据,如获取交易信息 methodName := "eth_getTransactionByHash"
的输出结构:
type Transaction struct {
Hash string `json: "hash"`
Nonce string `json: "nonce"`
BlockHash string `json: "blockHash"`
BlockNumber string `json: "blockNumber"`
TransactionIndex string `json: "transactionIndex"`
From string `json: "from"`
To string `json: "to"`
Value string `json: "value"`
GasPrice string `json: "gasPrice"`
Gas string `json: "gas"`
Input string `json: "input"`
}
更多接口方法查询以太坊JSON RPC手册 http://cw.hubwiz.com/card/c/ethereum-json-rpc-api/1/3/19/
以太坊RPC接口nodeURL:
免费的节点调用网站:https://infura.io/
生成独立专属节点,比etherscan好用
eth_call函数只用于访问不修改内存变量(view、pure)类的函数,因为eth_call的内部修改不会被广播,也不消耗Gas
Call(&result,methodName,arg,"latest")
methodName 为“eth_call”
type CallArg struct {
From common.Address `json: "from"`
To common.Address `json: "to"`
Gas string `json: "gas"`
GasPrice string `json: "gasPrice"`
Value string `json: "value"`
Data string `json: "data"`
Nonce string `json: "nonce"`
}
to为智能合约的地址
若出现GAS用尽的报错,将Gas值改为“0x1000000“,但并不会真的消耗gas
data存储调用函数的信息和传入参数,分为以下2个部分:
ID生成方法: 根据函数的ABI编码(从remix中复制)生成ID,如ERC20协议的balanceOf函数的ABI编码
contractABI := `[{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "_value",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}]`
methodName := "balanceOf"
生成ID:
func MakeMethodID(methodName string,abistr string)(string,error){
abi := &abi2.ABI{
}
//生成该函数实例
err := abi.UnmarshalJSON([]byte(abistr))
if err != nil{
return "", err
}
method := abi.Methods[methodName]
methodIdBytes := method.ID
methodId := "0x"+common.Bytes2Hex(methodIdBytes)
return methodId,nil
}
当然也可以通过remix编译合约后生成的JSON来得到函数ID,如某合约的各个ID:
"methodIdentifiers": {
"allowed(address,address)": "5c658165",
"approve(address,uint256)": "095ea7b3",
"balance(address)": "e3d670d7",
"balanceAll()": "42729c14",
"balanceOf(address)": "70a08231",
"decimals()": "313ce567",
"gotCoin(address)": "3d492e56",
"name()": "06fdde03",
"symbol()": "95d89b41",
"totalSupply()": "18160ddd",
"transfer(address,address,uint256)": "beabacc8",
"transfer(address,uint256)": "a9059cbb"
}
字符串类型:
arg1 := common.HexToHash("aa").String()[2:]
地址类型:
common.HexToHash(userAddress).String()[2:]
整型:
//先传入确定真实值
valueStr := "100"
//真实值
realValue := myTools.GetRealDecimalValue(valueStr,0)
//转换为bigint类型
realValueBig,_ := new(big.Int).SetString(realValue,10)
//转换为以太坊所用的16进制hash类型
arg := common.BytesToHash(realValueBig.Bytes()).String()[2:]
Data: methodId + arg1 + arg2,
参数组装后需要通过私钥签名,签名过程
types.SignTx(tx, types.HomesteadSigner{
}, unlockedKey.PrivateKey)
签名后进行RLP序列化以向其他节点传入数据:
https://segmentfault.com/a/1190000011763339
eth_sendRawTransaction可以实现如下三种功能:
调用方法如下:
err = r.client.Getrpc().Call(&txHash,methodName,common.ToHex(txRlpData))
发送一个以太坊交易需要以下流程:
构建交易:
transaction := types.NewTransaction(nonce.Uint64(),to,amount,gasLimit_,gasPrice_, databytes)
以太坊源码:
func NewTransaction(nonce uint64, to common.Address, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction {
return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data)
}
func newTransaction(nonce uint64, to *common.Address, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction {
if len(data) > 0 {
data = common.CopyBytes(data)
}
d := txdata{
AccountNonce: nonce,
Recipient: to,
Payload: data,
Amount: new(big.Int),
GasLimit: new(big.Int),
Price: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
}
if amount != nil {
d.Amount.Set(amount)
}
if gasLimit != nil {
d.GasLimit.Set(gasLimit)
}
if gasPrice != nil {
d.Price.Set(gasPrice)
}
return &Transaction{
data: d}
}
交易结构体,确认数据类型:
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit *big.Int `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}
注意:交易ETH时Payload为nil,调用函数时amount为0,部署合约时to为空
获取nonce
func (r *ETHERRPCRequester) GetNonce(address string)(*big.Int,error){
methodName := "eth_getTransactionCount"
nonce := ""
err := r.client.Getrpc().Call(&nonce,methodName,address,"pending")
if err != nil{
return nil, err
}
//返回的是16进制 0x开头
n,_ := new(big.Int).SetString(nonce[2:],16)
return n,nil
}
序列化:
//rlp序列化
txRlpData,err := rlp.EncodeToBytes(signTx)
发送ETH或Token时需要进行换算:
//decimal为几,等于硬币最小单位到小数点后几位
func GetRealDecimalValue(value string,decimal int) string{
if strings.Contains(value,"."){
//存在小数
arr := strings.Split(value,".")
if len(arr) != 2{
return ""
}
num := len(arr[1])
left := decimal - num
return arr[0] + arr[1] + strings.Repeat("0",left)
}else {
return value + strings.Repeat("0",decimal)
}
}