
SmartRaiden 和 Lighting Network 进行去中心化跨链原子资产交换 前言 如果能够进行以太坊和比特币跨链原子资产交换,是不是一件很酷的事情? 目前链下的扩容方式有很多,最广为人知的就是比特币的闪电网络和以太坊的雷电网络,今天我就来告诉如何通过智能雷电和闪电网络来实现跨链原子资产交换。 场景 Alice 在某个信息发布网站上发布信息,希望用1个 BTC 置换100个 SMT Bob 看到以后,和 Alice 进行沟通,达成交换意见 那么Alice 和 Bob 如何不需要借助任何第三方实现原子资产置换呢? 智能雷电与闪电网络
如何为 smartRaiden 贡献代码 1.Fork 项目 登录 github 账号,并访问https://github.com/SmartMeshFoundation/SmartRaiden,然后点击右上角的 fork 按钮,等待几秒钟以后就可以在你自己的 github 账号下看到 smartraiden. 2. Clone 项目到本地 你应该 clone自己账号下的 SmartRaiden, 具体到我的,就应该是https://github.com/nkbai/SmartRaiden.git cd $GOPATH/src/github.com/SmartMeshFoundation git clone https://github.com/nkbai/SmartRaiden.git 注意代码是不能放到 github.com/nkbai/SmartRaiden 的,否则 go 会编译不过去 3. 提交 接下来你就可以修改代码,然后提交到 github, 这过程和维护你自己的 github 项目没有什么区别. 一旦 push 到 github, 这时候你可以创建 PR. 4. 代码同步 需要添加 remote, 才能保持SmartMeshFoundation/SmartRaiden和 nkbai/SmartRaiden 的同步. git remote add upstream https://github.com/SmartMeshFoundation/SmartRaiden.git git remote -v 需要同步代码时: git fetch upstream git merge upstream/master classDiagram Class01 <|-- AveryLongClass : Cool Class03 *-- Class04 Class05 o-- Class06 Class07 .. Class08 Class09 --> C2 : Where am i? Class09 --* C3 Class09 --|> Class07 Class07 : equals() Class07 : Object[] elementData Class01 : size() Class01 : int chimp Class01 : int gorilla Class08 <--> C2: Cool label
Golang WebAssembly 入门 Golang 在1.11版本中引入了 WebAssembly 支持,意味着以后可以用 go编写可以在浏览器中运行的程序,当然这个肯定也是要受浏览器沙盒环境约束的. 1. 浏览器中运行 Go 1.1 code package main func main() { println("Hello, WebAssembly!") } 1.2 编译 必须是 go1.11才行 GOARCH=wasm GOOS=js go build -o test.wasm main.go 1.3 运行 单独的 wasm 文件是无法直接运行的,必须载入浏览器中. mkdir test cp test.wasm test cp $GOROOT/misc/wasm/wasm_exec.{html,js} . 1.3.1 一个测试 http 服务器 chrome 是不支持本地文件中运行 wasm 的,所以必须有一个 http 服务器 //http.go package main import ( "flag" "log" "net/http" "strings" ) var ( listen = flag.String("listen", ":8080", "listen address") dir = flag.String("dir", ".", "directory to serve") ) func main() { flag.Parse() log.Printf("listening on %q...", *listen) log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { if strings.HasSuffix(req.URL.Path, ".wasm") { resp.Header().Set("content-type", "application/wasm") } http.FileServer(http.Dir(*dir)).ServeHTTP(resp, req) }))) } 1.3.2 http.go mv http.go test cd test go run http.go 1.4 效果 在浏览器中打开http://localhost:8080/wasm_exec.html,点击 run 按钮,可以在控制台看到 Hello, WebAssembly!字符串 2. node中运行 wasm 这个更直接 node wasm_exec.js test.wasm 就可以在控制台看到Hello, WebAssembly!字符串了. 3. 其他例子 在example中可以看到更多例子 3.1 bouncy 3.2 ranbow-mouse 会跟着鼠标画出彩虹图案,挺好看的 3.3 bumpy 可以画一些自定义的形状,形状不同,落地效果不同.球就会反弹,三角形就不会.不过都摔不坏,不会变形,这点不够真实
目录 使用 dlv 调试smartraiden 一 正常启动 smartraiden 二 dlv 调试 三 dlv attach 使用 dlv 调试smartraiden by 白振轩 使用 dlv 调试smartraiden 一 正常启动 smartraiden 二 dlv 调试 三 dlv attach goroutines -s 查看所有 goroutine 的栈 解决问题 对于程序运行过程中碰到的莫名其妙的问题,比如不明原因的阻塞,命令行 dlv 调试可能比 ide 调试效果更好 一 正常启动 smartraiden ./smartraiden --datadir=.smartraiden --api-address=0.0.0.0:5001 --listen-address=127.0.0.1:40001 --address="0x292650fee408320D888e06ed89D938294Ea42f99" --keystore-path ~/privnet3/keystore --registry-contract-address 0xf450955d87F23DF5DFc7297ed6DdDF4fb896Eff2 --password-file 123 --verbosity 5 --debug --conditionquit "{\"QuitEvent\":\"EventSendRevealSecretBeforex\"}" --debugcrash --eth-rpc-endpoint ws://127.0.0.1:8546 二 dlv 调试 出了问题,但是没有崩溃,直接 attach 即可 出了问题,但是崩溃了,可以提前设置断点 三 dlv attach attach 成功以后,程序会停止运行 goroutines -s 查看所有 goroutine 的栈 解决问题 如果觉得某个 goroutine 有问题goroutine 108 就可以切换到这个 goroutine, 进行调试 bt 查看堆栈 (dlv) goroutine 108 Switched from 0 to 108 (thread 4644478) (dlv) bt 0 0x000000000402f66a in runtime.gopark at /usr/local/go/src/runtime/proc.go:292 1 0x000000000403f150 in runtime.selectgo at /usr/local/go/src/runtime/select.go:392 2 0x0000000004608329 in github.com/SmartMeshFoundation/SmartRaiden/blockchain.(*Events).startListenEvent.func1 at /Volumes/dev/smdev2/src/github.com/SmartMeshFoundation/SmartRaiden/blockchain/events.go:275 3 0x000000000405c3d1 in runtime.goexit at /usr/local/go/src/runtime/asm_amd64.s:2361 在栈之间移动up/down up up 会显示当前可以控制的栈Frame 2: /Volumes/dev/smdev2/src/github.com/SmartMeshFoundation/SmartRaiden/blockchain/events.go:275 (PC: 4608329) 270: go func(name string) { 271: ch := be.LogChannelMap[name] 272: sub := be.Subscribes[name] 273: defer rpanic.PanicRecover(fmt.Sprintf("startListenEvent %s", name)) 274: for { => 275: select { 276: case l, ok := <-ch: 277: if !ok { 278: //channel closed 279: return 280: } (dlv) 查看局部变量locals 可以显示目前所有的局部变量,也可以通过p 来打印具体的局部变量和全局变量. (dlv) p ch chan github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log { qcount: 0, dataqsiz: 10, buf: *[10]struct github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log [ (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc420335500), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc4203355a8), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc420335650), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc4203356f8), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc4203357a0), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc420335848), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc4203358f0), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc420335998), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc420335a40), (*github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log)(0xc420335ae8), ], elemsize: 168, closed: 0, elemtype: *runtime._type { size: 168, ptrdata: 56, hash: 3390961113, tflag: tflagUncommon|tflagExtraStar|tflagNamed, align: 8, fieldalign: 8, kind: 25, alg: *(*runtime.typeAlg)(0x5218c40), gcdata: *72, str: 56993, ptrToThis: 1205856,}, sendx: 0, recvx: 0, recvq: waitq<github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log> { first: *(*sudog<github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log>)(0xc4304ae4e0), last: *(*sudog<github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log>)(0xc4304ae4e0),}, sendq: waitq<github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log> { first: *sudog<github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log> nil, last: *sudog<github.com/SmartMeshFoundation/SmartRaiden/vendor/github.com/ethereum/go-ethereum/core/types.Log> nil,}, lock: runtime.mutex {key: 0},} (dlv) 可以看到 ch 这个 chann
在 以太坊中合约间是可以相互调用,并且正常进行参数传递以及返回值处理. contract1.sol pragma solidity ^0.4.0; contract Test1 { uint256 public v=7; function vote(uint256 a) public returns (uint256) { v=a; return (a); } } contract2.sol pragma solidity ^0.4.0; import "./contract1.sol"; contract Test2 { Test1 public t; uint256 public v=7; constructor(address t1) public { t=Test1(t1); } function vote(uint256 a) public { v=t.vote(a); } } 如下调用Test2.vote(3)会将合约Test1的 v 和合约Test2的 v 都设置为3. 
比特币 解锁脚本signature script 包含了那些东西? 使用 UTXO 需要私钥签名,私钥到底都签了什么东西呢?一直比较好奇. 比特币的私钥签名总共有五中类型,具体见 btcd 代码,如下: // SigHashType represents hash type bits at the end of a signature. type SigHashType uint32 // Hash type bits from the end of a signature. const ( SigHashOld SigHashType = 0x0 SigHashAll SigHashType = 0x1 SigHashNone SigHashType = 0x2 SigHashSingle SigHashType = 0x3 SigHashAnyOneCanPay SigHashType = 0x80 // sigHashMask defines the number of bits of the hash type which is used // to identify which outputs are signed. sigHashMask = 0x1f ) SigHashOld 和 SigHashAll 从代码看,两者是一样的.具体签名内容见图. 主要内容: 所有的 TxIn,所有的 TxOut, 但是不包含签名本身(这个是不可能做到包含自身的). 这是目前主要的签名用法. SigHashNone 主要内容: 所有TxIn, 但是不包含 TxOut 我不知道这种签名用在什么地方, TxOut可以让别人随便改. SigHashSingle 对所有的 TxIn和某个 TxOut 进行签名 不清楚用途 SigHashAnyOneCanPay 对当前TxIn 和所有 TxOut 进行签名 这个可以保证输入输出的安全,但是因为没有包含TxIn 之间的顺序关系. 所有这个 Tx 的 ID 是可以被修改的.
比特币的地址类型 这部分内容主要来自于btcutil/address.go 一直困惑比特币是如何验证交易的,看了这个地质类型算是有点豁然开朗,实际上比特币的交易验证规则还是有点复杂的,它并不像以太坊那么简单明确. 个人理解,比特币对于交易的处理,首先是根据 pubkey script 判断是什么地址类型,然后进行不同的验证方法. 比如如果地质类型是AddressWitnessPubKeyHash,那么验证规则就明显和 P2PKH 不一样. 以下是address.go 中如何解析出地址: // DecodeAddress decodes the string encoding of an address and returns // the Address if addr is a valid encoding for a known address type. // // The bitcoin network the address is associated with is extracted if possible. // When the address does not encode the network, such as in the case of a raw // public key, the address will be associated with the passed defaultNet. func DecodeAddress(addr string, defaultNet *chaincfg.Params) (Address, error) { // Bech32 encoded segwit addresses start with a human-readable part // (hrp) followed by '1'. For Bitcoin mainnet the hrp is "bc", and for // testnet it is "tb". If the address string has a prefix that matches // one of the prefixes for the known networks, we try to decode it as // a segwit address. oneIndex := strings.LastIndexByte(addr, '1') if oneIndex > 1 { // The HRP is everything before the found '1'. hrp := strings.ToLower(addr[:oneIndex]) if hrp == defaultNet.Bech32HRPSegwit { witnessVer, witnessProg, err := decodeSegWitAddress(addr) if err != nil { return nil, err } // We currently only support P2WPKH and P2WSH, which is // witness version 0. if witnessVer != 0 { return nil, UnsupportedWitnessVerError(witnessVer) } switch len(witnessProg) { case 20: return newAddressWitnessPubKeyHash(hrp, witnessProg) case 32: return newAddressWitnessScriptHash(hrp, witnessProg) default: return nil, UnsupportedWitnessProgLenError(len(witnessProg)) } } } // Serialized public keys are either 65 bytes (130 hex chars) if // uncompressed/hybrid or 33 bytes (66 hex chars) if compressed. if len(addr) == 130 || len(addr) == 66 { serializedPubKey, err := hex.DecodeString(addr) if err != nil { return nil, err } return NewAddressPubKey(serializedPubKey, defaultNet) } // Switch on decoded length to determine the type. decoded, netID, err := base58.CheckDecode(addr) if err != nil { if err == base58.ErrChecksum { return nil, ErrChecksumMismatch } return nil, errors.New("decoded address is of unknown format") } switch len(decoded) { case ripemd160.Size: // P2PKH or P2SH isP2PKH := netID == defaultNet.PubKeyHashAddrID isP2SH := netID == defaultNet.ScriptHashAddrID switch hash160 := decoded; { case isP2PKH && isP2SH: return nil, ErrAddressCollision case isP2PKH: return newAddressPubKeyHash(hash160, netID) case isP2SH: return newAddressScriptHashFromHash(hash160, netID) default: return nil, ErrUnknownAddressType } default: return nil, errors.New("decoded address is of unknown size") } } 总共有四中地质类型: AddressPubKey // AddressPubKey is an Address for a pay-to-pubkey transaction. type AddressPubKey struct { pubKeyFormat PubKeyFormat pubKey *btcec.PublicKey pubKeyHashID byte } AddressPubKeyHash // AddressPubKeyHash is an Address for a pay-to-pubkey-hash (P2PKH) // transaction. type AddressPubKeyHash struct { hash [ripemd160.Size]byte netID byte } AddressScriptHash // AddressScriptHash is an Address for a pay-to-script-hash (P2SH) // transaction. type AddressScriptHash struct { hash [ripemd160.Size]byte netID byte } AddressScriptHash // AddressScriptHash is an Address for a pay-to-script-hash (P2SH) // transaction. type AddressScriptHash struct { hash [ripemd160.Size]byte netID byte } AddressWitnessPubKeyHash // AddressWitnessPubKeyHash is an Address for a pay-to-witness-pubkey-hash // (P2WPKH) output. See BIP 173 for further details regarding native segregated // witness address encoding: // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki type AddressWitnessPubKeyHash struct { hrp string witnessVersion byte witnessProgram [20]byte } AddressWitnessScriptHash // AddressWitnessScriptHash is an Address for a pay-to-witness-script-hash // (P2WSH) output. See BIP 173 for further details regarding native segregated // witness address encoding: // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki type AddressWitnessScriptHash struct { hrp string witnessVersion byte witnessProgram [32]byte }
P2PK P2PKH,MS,P2SH,OP_RETURN 等的区别 1.P2PK pay_to_public_key pubkey script: <pubkey> OP_CHECKSIG signature script: [sig] 2. P2PKH pay_to_public_key_hash pubkey script: OP_DUP OP_HASH160 hash(pubkey) OP_EQUALVERIFY signature script: [sig] <pubkey> 3.P2SH pay_to_script_hash pubkey script: OP_HASH160 hash(Redeem script) OP_EQUAL signature script: 此处和上面两个都不一样,解锁需要分成两部 第一步: 验证 redeem script hash 值是否正确,也就是将redeem script 作为数据放到栈上,然后执行 OP_HASH160,如果为真才会执行第二步,否则失败 第二步:执行redeem script, 将signature script+redeem script 一起执行,如果为真则成功,否则交易失败 4.P2WPKH pay_to_witness_public_key_hash pubkey script: 0 HASH160(public key) 解锁脚本 signature script(scriptSig): 空 还增加了一个 witness 字段,用于验证交易合法性 witness: 如果按照前面的验证规则,所有的隔离见证交易都是合法的,所以这是一个软分叉. 5. P2WSH pay_to_witness_script_hash pubkey script: 0 SHA256(redeem script) scriptSig: 空 witness: 矿工如何区分交易类型? 主要是根据 pubkey script 的模式进行匹配,不同的模式匹配不同的验证规则.尤其是隔离见证部分. scriptSig包含什么内容? 没看源码,纯属个人猜想. 一个 tx 的结构如下: type MsgTx struct { Version int32 TxIn []*TxIn TxOut []*TxOut LockTime uint32 } // TxIn defines a bitcoin transaction input. type TxIn struct { PreviousOutPoint OutPoint SignatureScript []byte Witness TxWitness Sequence uint32 } // TxOut defines a bitcoin transaction output. type TxOut struct { Value int64 PkScript []byte } 签名放在 TxIn 中,因此签名应该是对hash(Version,LockTime, current TxIn(exclude signature script),all TxOut)) 进行签名. 这样狂购可以验证交易,但是不能修改交易. 至于 TxID 为什么会变,是否因为 TxIn以及 TxOut 的顺序可以被矿工调整?
pay-to-pubkey-hash解析 本文主要译自比特币 wiki scriptPubKey: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG scriptSig: <sig> <pubKey> 例如如下的raw scriptPubKey: 76A91489ABCDEFABBAABBAABBAABBAABBAABBAABBAABBA88AC 解析如下: 76 A9 14 OP_DUP OP_HASH160 Bytes to push 89 AB CD EF AB BA AB BA AB BA AB BA AB BA AB BA AB BA AB BA 88 AC Data to push OP_EQUALVERIFY OP_CHECKSIG Stack Script Description Stack Script Description Empty. <sig> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG scriptSig and scriptPubKey are combined. <sig> <pubKey> OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG Constants are added to the stack. <sig> <pubKey> <pubKey> OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG Top stack item is duplicated. <sig> <pubKey> <pubHashA> <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG Top stack item is hashed. <sig> <pubKey> <pubHashA> <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG Constant added. <sig> <pubKey> OP_CHECKSIG Equality is checked between the top two stack items. true Empty. Signature is checked for top two stack items.
使用mermaid描述 raiden 通道 AB,正常状态 graph LR A-- 60,100,S_100 ---B 通道 AB closed graph LR A((A)) -. 60,100 .- B((B)) 通道 ABCD graph LR A((A)) A--60,100 ---B B-- 50,50 ---C B--20,30 ---D 交易 A-B-C sequenceDiagram participant A as Alice participant B as Bob participant C as Charle A->>B: MTR,expiration=3000,H(secret)=333 B->>C: MTR,expiration=3000 Note right of B: exiration should less than 3000 Note over A,C: secret should be the same C->>A: Secret Request A->>C: Reveal Secret C->>B: Reveal Secret B->>C: Unlock B->>A: Reveal Secret A->>B: Unlock mermaid示例以及帮助文档
Markdown 语法 转载自https://zh.mweb.im/markdown.html 首先应该了解的 每一个 Markdwon 使用者都应该了解的,是 Markdown 最基本的版本,也就是最官方的版本。它是如何创造出来的?它的设计哲学和语法是什么?如果你没看过,建议一定要看看这篇:Markdown 語法官方說明繁體中文版 。 MWeb 采用的语法是 Github Flavored Markdown 语法,简称 GFM 语法,GFM 是官网版本的扩展版,除了支持官方的语法外,还支持表格、TOC、LaTeX、代码块、任务列表、脚注等。 另外 MWeb 在兼容 GFM 语法的基础上,还扩展支持两种比较有用的语法:画图支持(mermaid, viz, echarts, plantuml, sequence, flow)和设置图片宽度,下面会仔细说明。 Markdown 的设计哲学 Markdown 的目標是實現「易讀易寫」。 不過最需要強調的便是它的可讀性。一份使用 Markdown 格式撰寫的文件應該可以直接以純文字發佈,並且看起來不會像是由許多標籤或是格式指令所構成。 Markdown 的語法有個主要的目的:用來作為一種網路內容的寫作用語言。 本文约定 如果有写 效果如下:, 在 MWeb 编辑状态下只有用 CMD + 4 或 CMD + R 预览才可以看效果。你可以直接下载本文然后在 MWeb 中打开以查看效果,下载网址为:下载本文 Markdown 标题 Markdown 语法: # 第一级标题 `<h1>` ## 第二级标题 `<h2>` ###### 第六级标题 `<h6>` 效果如下: 第一级标题 <h1> 第二级标题 <h2> 第六级标题 <h6> 强调 Markdown 语法: *这些文字会生成`<em>`* _这些文字会生成`<u>`_ **这些文字会生成`<strong>`** __这些文字会生成`<strong>`__ 在 MWeb 中的快捷键为: CMD + U、CMD + I、CMD + B 效果如下: 这些文字会生成<em> 这些文字会生成<u> 这些文字会生成<strong> 这些文字会生成<strong> 换行 四个及以上空格加回车。 如果不想打这么多空格,只要回车就为换行,请打开 偏好设置 - 主题&样式 勾选:把换行转为 <br /> 标签,这项默认是勾选状态的。 列表 无序列表 Markdown 语法: * 项目一 无序列表 `* + 空格键` * 项目二 * 项目二的子项目一 无序列表 `TAB + * + 空格键` * 项目二的子项目二 在 MWeb 中的快捷键为: Option + U 效果如下: 项目一 无序列表 * + 空格键 项目二 项目二的子项目一 无序列表 TAB + * + 空格键 项目二的子项目二 有序列表 Markdown 语法: 1. 项目一 有序列表 `数字 + . + 空格键` 2. 项目二 3. 项目三 1. 项目三的子项目一 有序列表 `TAB + 数字 + . + 空格键` 2. 项目三的子项目二 效果如下: 项目一 有序列表 数字 + . + 空格键 项目二 项目三 项目三的子项目一 有序列表 TAB + 数字 + . + 空格键 项目三的子项目二 任务列表(Task lists) Markdown 语法: - [ ] 任务一 未做任务 `- + 空格 + [ ]` - [x] 任务二 已做任务 `- + 空格 + [x]` 效果如下: 任务一 未做任务 - + 空格 + [ ] 任务二 已做任务 - + 空格 + [x] 图片 Markdown 语法:  格式:  Control + Shift + I 可插入Markdown语法。 如果是 MWeb 的文档库中和外部模式中引入的文件夹中的文档,还可以用截图并粘贴、复制并粘贴、拖拽等方式插入图片。 效果如下: MWeb 引入的特别的语法来设置图片宽度,方法是在图片描述后加 -w + 图片宽度 即可,比如说要设置上面的图片的宽度为 140,语法如下:  在 MWeb 中,你还可以设置图片的对齐,以上面的图片为例子,左对齐为 -l140,居中为 -c140,居右为 -r140。 链接 Markdown 语法: email <example@example.com> [GitHub](http://github.com) 自动生成连接 <http://www.github.com/> Control + Shift + L 可插入Markdown语法。 如果是 MWeb 的文档库中和外部模式中引入的文件夹中的文档,拖放或 CMD + Option + I 导入非图片时,会生成连接。 效果如下: Email 连接: example@example.com 连接标题Github网站 自动生成连接像: http://www.github.com/ 这样 区块引用 Markdown 语法: 某某说: > 第一行引用 > 第二行费用文字 CMD + Shift + B 可插入Markdown语法。 效果如下: 某某说: 第一行引用 第二行费用文字 行内代码 Markdown 语法: 像这样即可:`<addr>` `code` CMD + K 可插入Markdown语法。 效果如下: 像这样即可:<addr> code 多行或者一段代码 Markdown 语法: ```js function fancyAlert(arg) { if(arg) { $.facebox({div:'#foo'}) } } ``` CMD + Shift + K 可插入Markdown语法。 效果如下: function fancyAlert(arg) { if(arg) { $.facebox({div:'#foo'}) } } MWeb 画图 mermaid mermaid 是比较流行的画图库,它支持流程图、顺序图和甘特图,它的官网为:https://mermaidjs.github.io/ ,在 MWeb 中使用 mermaid 的语法就是声明代码块的语言为 mermaid,代码块中再写上 mermaid 的画图语法即可,你可以把下面的语法复制到 MWeb 中查看效果。 ```mermaid sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? loop Healthcheck John->John: Fight against hypochondria end Note right of John: Rational thoughts <br/>prevail... John-->Alice: Great! John->Bob: How about you? Bob-->John: Jolly good! ``` 效果如下: sequenceDiagram participant Alice participant Bob Alice->John: Hello John, how are you? loop Healthcheck John->John: Fight against hypochondria end Note right of John: Rational thoughts <br/>prevail... John-->Alice: Great! John->Bob: How about you? Bob-->John: Jolly good! Graphviz Graphviz 是开源的画图软件,它的官网为 http://www.graphviz.org/。MWeb 采用的是 Graphviz 的 js 版本的实现 http://viz-js.com/,可以解析 Graphviz 语法以生成图片。你可以将以下语法复制到 MWeb 中进行尝试。在尝试时可以把 dot 换成 circo, fdp, neato, osage, twopi 这几种来尝试效果。 ```dot # http://www.graphviz.org/content/cluster digraph G { subgraph cluster_0 { style=filled; color=lightgrey; node [style=filled,color=white]; a0 -> a1 -> a2 -> a3; label = "process #1"; } subgraph cluster_1 { node [style=filled]; b0 -> b1 -> b2 -> b3; label = "process #2"; color=blue } start -> a0; start -> b0; a1 -> b3; b2 -> a3; a3 -> a0; a3 -> end; b3 -> end; start [shape=Mdiamond]; end [shape=Msquare]; } ``` 效果如下: # http://www.graphviz.org/content/cluster digraph G { subgraph cluster_0 { style=filled; color=lightgrey; node [style=filled,color=white]; a0 -> a1 -> a2 -> a3; label = "process #1"; } subgraph cluster_1 { node [style=filled]; b0 -> b1 -> b2 -> b3; label = "process #2"; color=blue } start -> a0; start -> b0; a1 -> b3; b2 -> a3; a3 -> a0; a3 -> end; b3 -> end; start [shape=Mdiamond]; end [shape=Msquare]; } echarts echarts 是百度出口的 js 画图库,它的网址为:http://echarts.baidu.com/index.html,功能非常强大,MWeb 支持 echarts 的一些基本的用法,太高级的不支持。你可以将以下语法复制到 MWeb 中进行尝试。你也可以去 http://echarts.baidu.com/examples/index.html 这个网址查看一些例子,要注意的是 MWeb 只能解析 option = {} 这种简单的,不过应该是足够使用了。 ```echarts option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [{ data: [820, 932, 901, 934, 1290, 1330, 1320], type: 'line' }] }; ``` 效果如下: option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [{ data: [820, 932, 901, 934, 1290, 1330, 1320], type: 'line' }] }; plantuml plantuml 的网址为:http://www.plantuml.com/,直接上去看更能了解。MWeb 对 plantuml 的支持的方式是引用 plantuml 服务器生成的图片,你可以将以下语法复制到 MWeb 中进行尝试。 ```plantuml @startuml User -> (Start) User --> (Use the application) : A small label :Main Admin: ---> (Use the application) : This is\nyet another\nlabel @enduml ``` 效果如下: @startuml User -> (Start) User --> (Use the application) : A small label :Main Admin: ---> (Use the application) : This is\nyet another\nlabel @enduml 顺序图和流程图 顺序图和流程图是使用 http://bramp.github.io/js-sequence-diagrams/, http://adrai.github.io/flowchart.js/ 这两个画图库,以下是它在 MWeb 中的 Markdown 语法。 ```sequence 张三->李四: 嘿,小四儿, 写博客了没? Note right of 李四: 李四愣了一下,说: 李四-->张三: 忙得吐血,哪有时间写。 ``` ```flow st=>start: 开始 e=>end: 结束 op=>operation: 我的操作 cond=>condition: 确认? st->op->cond cond(yes)->e cond(no)->op ``` 效果如下: 张三->李四: 嘿,小四儿, 写博客了没? Note right of 李四: 李四愣了一下,说: 李四-->张三: 忙得吐血,哪有时间写。 st=>start: 开始 e=>end: 结束 op=>operation: 我的操作 cond=>condition: 确认? st->op->cond cond(yes)->e cond(no)->op 表格 Markdown 语法: 第一格表头 | 第二格表头 --------- | ------------- 内容单元格 第一列第一格 | 内容单元格第二列第一格 内容单元格 第一列第二格 多加文字 | 内容单元格第二列第二格 效果如下: 第一格表头 第二格表头 内容单元格 第一列第一格 内容单元格第二列第一格 内容单元格 第一列第二格 多加文字 内容单元格第二列第二格 删除线 Markdown 语法: 加删除线像这样用: ~~删除这些~~ 效果如下: 加删除线像这样用: 删除这些 分隔线 以下三种方式都可以生成分隔线: *** ***** - - - 效果如下: LaTeX (MathJax 渲染) Markdown 语法: 块级公式: $$ x = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$ \\[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\ldots} } } } \\] 行内公式: $\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$ 效果如下(如果没看到公式,请勾选 偏好设置 - 主题&样式 - 启用 LaTeX(MathJax) 即可): 块级公式: x = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a} \[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\ldots} } } } \] 行内公式: \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N 脚注(Footnote) Markdown 语法: 这是一个脚注:[^sample_footnote] 效果如下: 这是一个脚注:1 注释和阅读更多 Actions->Insert Read More Comment 或者 Command + . 注 阅读更多的功能只用在生成网站或博客时,插入时注意要后空一行。 TOC Markdown 语法: [TOC] 效果如下: TOC 这里是脚注信息↩
solidity 中 mapping 是如何存储的 为了探测 solidity mapping 如何实现,我构造了一个简单的合约. 先说结论,实际上 mapping的访问成本并不比直接访问storage变量多花费更多的 gas.两者几乎差不多. 构造合约 pragma solidity ^0.4.23; contract TestMap{ mapping(uint256 => uint256) public channels; function TestSet() external{ channels[0x39]=0x77; } } 合约非常简单,就是写一个 mapping. 汇编指令 最主要是这些指令里面就有一条昂贵的就是 sstore, 至少需要5000gas. /* "testmapping.sol":151:155 0x77 */ 0x77 /* "testmapping.sol":136:144 channels */ 0x0 /* "testmapping.sol":136:150 channels[0x39] */ dup1 /* "testmapping.sol":145:149 0x39 */ 0x39 /* "testmapping.sol":136:150 channels[0x39] */ dup2 mstore 0x20 add swap1 dup2 mstore 0x20 add 0x0 keccak256 /* "testmapping.sol":136:155 channels[0x39]=0x77 */ dup2 swap1 sstore pop 总结 由于指令的成本较低,写 storage 最少需要5000gas, 而 sha3只需要30+gas, 可以忽略不计,其他指令也很便宜. 当然如果是读的话,就稍微贵一点点,读 storage 是200gas, 那么 sha3加上这些指令,估计就有接近100了. 不过如果mapping 确实可以带来便利,那就用 mapping 吧.
solidity 合约注意事项 1. 栈深度 EVM 栈深度1024字(1024*256字节) 2. solidity 局部变量个数不能超过16 因为提供的指令集对于栈深度的访问不能超过16,也就是说最多把第16个字换到栈顶进行操作. 3.内存费用 占用 EVM 的内存越大,收费越高,收费是内存大小的平方,具体计算方式未知. 4.delete 关键字 delete 作用相当于将变量的值设置为默认值,但是会降低 gas 的费用. 4.abi.encodePacked 有什么价值? 以下面的registerSecret为例,如果参数个数为1个,那么有abi.encodePacked,将会多浪费350的 gas, 如果两个参数多浪费458的 gas, 如果是五个参数,将会多浪费749的 gas. 但是有没有 abi.encodePacked, 得到的 secrethash 都是一样的.也就是结果完全一样. 不明白为什么要多浪费这些 gas. pragma solidity ^0.4.23; contract SecretRegistry { /* * Data structures */ string constant public contract_version = "0.3._"; // secrethash => block number at which the secret was revealed mapping(bytes32 => uint256) public secrethash_to_block; /* * Events */ event SecretRevealed(bytes32 indexed secrethash); function registerSecret(uint256 u1,uint256 u2,uint256 u3,uint256 u4,uint256 u5) public returns (bool) { bytes32 secrethash = keccak256(abi.encodePacked(u1,u2,u3,u4,u5)); if (u1 == 0x0 || secrethash_to_block[secrethash] > 0) { return false; } secrethash_to_block[secrethash] = block.number; emit SecretRevealed(secrethash); return true; } function getSecretRevealBlockHeight(bytes32 secrethash) public view returns (uint256) { return secrethash_to_block[secrethash]; } } 5. sha256,ripemd160,ecrecover 这些功能并没有专门的指令集,而是有一个内置的合约来实现上述功能.(指令集明明有sha256,难道现在移除了?) 参考 solidity0.4.24文档 It might be that you run into Out-of-Gas for sha256, ripemd160 or ecrecover on a private blockchain. The reason for this is that those are implemented as so-called precompiled contracts and these contracts only really exist after they received the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution runs into an Out-of-Gas error. A workaround for this problem is to first send e.g. 1 Wei to each of the contracts before you use them in your actual contracts. This is not an issue on the official or test net. 6. 合约中使用 call 下面提供了两个例子, approve and call, 主要是尽量减少 transactions. 比如雷电网络在 openchannel 的时候,如果对应的 ERC20token 支持approve and call,那么 deposit 行为就没必要是两个 transaction, 只需一个即可. /* Approves and then calls the receiving contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this. //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData) //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead. if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; } return true; } /* Approves and then calls the contract code*/ function approveAndCallcode(address _spender, uint256 _value, bytes _extraData) returns (bool success) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); //Call the contract code if(!_spender.call(_extraData)) { throw; } return true; } 7.call,delegatecall,callcode 注意事项 如果被调合约地址不存在,也会返回成功. The low-level call, delegatecall and callcode will return success if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. 8.require 和 assert 的区别 require(对应指令0xfd) 用于验证用户输入是否有效, assert(对应指令0xfe) 用于未想到的内部错误. Internally, Solidity performs a revert operation (instruction 0xfd) for a require-style exception and executes an invalid operation (instruction 0xfe) to throw an assert-style exception. 9. 函数external,internal,public和 private 的区别 external 只能通过message call 的形式调用,就是本合约内部调用也要通过 this.f()这种形式,无法直接 f() internal 只能在合约内以及继承合约内调用 Public 既可以在合约内调用也可以在合约外调用,合约内调用是直接 jump, 合约外调用是 message call private 只能被合约自己调用,包括集成合约都不可以 10.函数的其他描述关键字 view view 保证不修改合约状态.以下均认为会改变合约状态 Writing to state variables. Emitting events. Creating other contracts. Using selfdestruct. Sending Ether via calls. Calling any function not marked view or pure. Using low-level calls. Using inline assembly that contains certain opcodes. 11. pure 关键字 pure 标注以后不仅不修改合约状态,甚至不会读合约状态, 读合约状态包括以下行为 Reading from state variables. Accessing this.balance or .balance. Accessing any of the members of block, tx, msg (with the exception of msg.sig and msg.data). Calling any function not marked pure. Using inline assembly that contains certain opcodes. 12. library用法 下面这个 Set 的insert,remove,contains 都是 delegatecall 调用, 但是 contains 是通过来jump 调用. delegatecall 也是一种 messagecall 但是将使用当前合约的上下文环境.也就是msg.sender,msg.value,this 都不变 pragma solidity ^0.4.22; library Set { // We define a new struct datatype that will be used to // hold its data in the calling contract. struct Data { mapping(uint => bool) flags; } // Note that the first parameter is of type "storage // reference" and thus only its storage address and not // its contents is passed as part of the call. This is a // special feature of library functions. It is idiomatic // to call the first parameter `self`, if the function can // be seen as a method of that object. function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; // already there self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; // not there self.flags[value] = false; return true; } function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; } } contract C { Set.Data knownValues; function register(uint value) public { // The library functions can be called without a // specific instance of the library, since the // "instance" will be the current contract. require(Set.insert(knownValues, value)); } // In this contract, we can also directly access knownValues.flags, if we want. } 合约优化123 存储优化 定义的 struct 就像 c 语言有 align 问题一样,否则会带来空间浪费. 除非是定义在 storage 区域的 state 变量,否则没必要使用小于一个字的类型,因为那样更浪费指令,并且不节省空间. When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. It is only beneficial to use reduced-size arguments if you are dealing with storage values because the compiler will pack multiple elements into one storage slot, and thus, combine multiple reads or writes into a single operation. When dealing with function arguments or memory values, there is no inherent benefit because the compiler does not pack these values. Finally, in order to allow the EVM to optimize for this, ensure that you try to order your storage variables and struct members such that they can be packed tightly. For example, declaring your storage variables in the order of uint128, uint128, uint256 instead of uint128, uint256, uint128, as the former will only take up two slots of storage whereas the latter will take up three. EVM指令集消耗 gas 统计 关键是看 gas 消耗最大的一些项目 create contract 32000 在 storage 区域写数据,第一次写20000,以后写5000 EXTCODESIZE 700,也就是判断一下某个地址是否有合约代码,需要 gas700 BALANCE 获取账户 ether 余额 400 几个 log 指令,375起步 SLOAD 读取 storage 区域,200 像 SHA3这些指令反而很便宜,不知道 ecrecover 有多贵,ecrecover 是一种合约调用指令,比较复杂. 7.1.3 The Ethereum Virtual Machine Overview The Ethereum Virtual Machine or EVM is the runtime environment for smart contracts in Ethereum. It is not only sandboxed but actually completely isolated, which means that code running inside the EVM has no access to network, filesystem or other processes. Smart contracts even have limited access to other smart contracts. Accounts There are two kinds of accounts in Ethereum which share the same address space: External accounts that are controlled by public-private key pairs (i.e. humans) and contract accounts which are controlled by the code stored together with the account. The address of an external account is determined from the public key while the address of a contract is determined at the time the contract is created (it is derived from the creator address and the number of transactions sent from that address, the so-called “nonce”). Regardless of whether or not the account stores code, the two types are treated equally by the EVM. Every account has a persistent key-value store mapping 256-bit words to 256-bit words called storage. Furthermore, every account has a balance in Ether (in “Wei” to be exact) which can be modified by sending transactions that include Ether. Transactions A transaction is a message that is sent from one account to another account (which might be the same or the special zero-account, see below). It can include binary data (its payload) and Ether. If the target account contains code, that code is executed and the payload is provided as input data. If the target account is the zero-account (the account with the address 0), the transaction creates a new contract. As already mentioned, the address of that contract is not the zero address but an address derived from the sender and its number of transactions sent (the “nonce”). The payload of such a contract creation transaction is taken to be EVM bytecode and executed. The output of this execution is permanently stored as the code of the contract. This means that in order to create a contract, you do not send the actual code of the contract, but in fact code that returns that code when executed. Note: While a contract is being created, its code is still empty. Because of that, you should not call back into the contract under construction until its constructor has finished executing. Gas Upon creation, each transaction is charged with a certain amount of gas, whose purpose is to limit the amount of work that is needed to execute the transaction and to pay for this execution. While the EVM executes the transaction, the gas is gradually depleted according to specific rules. The gas price is a value set by the creator of the transaction, who has to pay gas_price * gas up front from the sending account. If some gas is left after the execution, it is refunded in the same way. If the gas is used up at any point (i.e. it is negative), an out-of-gas exception is triggered, which reverts all modifications made to the state in the current call frame. Storage, Memory and the Stack Each account has a persistent memory area which is called storage. Storage is a key-value store that maps 256-bit words to 256-bit words. It is not possible to enumerate storage from within a contract and it is comparatively costly to read and even more so, to modify storage. A contract can neither read nor write to any storage apart from its own. The second memory area is called memory, of which a contract obtains a freshly cleared instance for each message call. Memory is linear and can be addressed at byte level, but reads are limited to a width of 256 bits, while writes can be either 8 bits or 256 bits wide. Memory is expanded by a word (256-bit), when accessing (either reading or writing) a previously untouched memory word (ie. any offset within a word). At the time of expansion, the cost in gas must be paid. Memory is more costly the larger it grows (it scales quadratically). The EVM is not a register machine but a stack machine, so all computations are performed on an area called the stack. It has a maximum size of 1024 elements and contains words of 256 bits. Access to the stack is limited to the top end in the following way: It is possible to copy one of the topmost 16 elements to the top of the stack or swap the topmost element with one of the 16 elements below it. All other operations take the topmost two (or one, or more, depending on the operation) elements from the stack and push the result onto the stack. Of course it is possible to move stack elements to storage or memory, but it is not possible to just access arbitrary elements deeper in the stack without first removing the top of the stack. Instruction Set The instruction set of the EVM is kept minimal in order to avoid incorrect implementations which could cause consensus problems. All instructions operate on the basic data type, 256-bit words. The usual arithmetic, bit, logical and comparison operations are present. Conditional and unconditional jumps are possible. Furthermore, contracts can access relevant properties of the current block like its number and timestamp. Message Calls Contracts can call other contracts or send Ether to non-contract accounts by the means of message calls. Message calls are similar to transactions, in that they have a source, a target, data payload, Ether, gas and return data. In fact, every transaction consists of a top-level message call which in turn can create further message calls. A contract can decide how much of its remaining gas should be sent with the inner message call and how much it wants to retain. If an out-of-gas exception happens in the inner call (or any other exception), this will be signalled by an error value put onto the stack. In this case, only the gas sent together with the call is used up. In Solidity, the calling contract causes a manual exception by default in such situations, so that exceptions “bubble up” the call stack. As already said, the called contract (which can be the same as the caller) will receive a freshly cleared instance of memory and has access to the call payload - which will be provided in a separate area called the calldata. After it has finished execution, it can return data which will be stored at a location in the caller’s memory preallocated by the caller. Calls are limited to a depth of 1024, which means that for more complex operations, loops should be preferred over recursive calls. Delegatecall / Callcode and Libraries There exists a special variant of a message call, named delegatecall which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values. This means that a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address. This makes it possible to implement the “library” feature in Solidity: Reusable library code that can be applied to a contract’s storage, e.g. in order to implement a complex data structure. Logs It is possible to store data in a specially indexed data structure that maps all the way up to the block level. This feature called logs is used by Solidity in order to implement events. Contracts cannot access log data after it has been created, but they can be efficiently accessed from outside the blockchain. Since some part of the log data is stored in bloom filters, it is possible to search for this data in an efficient and cryptographically secure way, so network peers that do not download the whole blockchain (“light clients”) can still find these logs. Create Contracts can even create other contracts using a special opcode (i.e. they do not simply call the zero address). The only difference between these create calls and normal message calls is that the payload data is executed and the result stored as code and the caller / creator receives the address of the new contract on the stack. Self-destruct The only possibility that code is removed from the blockchain is when a contract at that address performs the selfdestruct operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Warning: Even if a contract’s code does not contain a call to selfdestruct, it can still perform that operation using delegatecall or callcode. Note: The pruning of old contracts may or may not be implemented by Ethereum clients. Additionally, archive nodes could choose to keep the contract storage and code indefinitely. Note: Currently external accounts cannot be removed from the state
一条命令深度清理你的mac mac 用了一段时间后很快发现硬盘空间不够了,
将以太坊封装为 ERC20 TOKEN 很多 DAPP 都是在处理 ERC20接口的 token, 其实很容易将以太坊封装为 ERC20,这样就可以统一处理, 至少我目前在做的雷电网络就是这么处理的. 主要内容复制在网络https://programtheblockchain.com/posts/2018/05/26/wrapping-ether-in-an-erc20-token/ 直接上代码,核心部分是 pragma solidity ^0.4.24; import "baseerc20token.sol"; contract EtherToken is BaseERC20Token { constructor(string _name, string _symbol) BaseERC20Token(0, 18, _name, _symbol) public { } function buy() public payable { balanceOf[msg.sender] += msg.value; totalSupply += msg.value; emit Transfer(address(0), msg.sender, msg.value); } function sell(uint256 amount) public { require(balanceOf[msg.sender] >= amount, "Insufficient balance."); balanceOf[msg.sender] -= amount; totalSupply -= amount; msg.sender.transfer(amount); emit Transfer(msg.sender, address(0), amount); } } 它提供了以太坊和 ERC20互换的接口,换成 ERC20以后就很简单了. 实际上baseerc20token.sol只是一个非常标准的 ERC20实现 pragma solidity ^0.4.23; contract BaseERC20Token { mapping (address => uint256) public balanceOf; string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; event Transfer(address indexed from, address indexed to, uint256 value); constructor ( uint256 _totalSupply, uint8 _decimals, string _name, string _symbol ) public { name = _name; symbol = _symbol; decimals = _decimals; totalSupply = _totalSupply; balanceOf[msg.sender] = _totalSupply; emit Transfer(address(0), msg.sender, _totalSupply); } function transfer(address to, uint256 value) public returns (bool success) { require(balanceOf[msg.sender] >= value); balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); return true; } event Approval(address indexed owner, address indexed spender, uint256 value); mapping(address => mapping(address => uint256)) public allowance; function approve(address spender, uint256 value) public returns (bool success) { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } function transferFrom(address from, address to, uint256 value) public returns (bool success) { require(value <= balanceOf[from]); require(value <= allowance[from][msg.sender]); balanceOf[from] -= value; balanceOf[to] += value; allowance[from][msg.sender] -= value; emit Transfer(from, to, value); return true; } } 这样简单部署,就可以在雷电网络中将以太坊当做普通的 ERC20 TOKEN进行处理了.
golang Subprocess tests Sometimes you need to test the behavior of a process, not just a function. func Crasher() { fmt.Println("Going down in flames!") os.Exit(1) } To test this code, we invoke the test binary itself as a subprocess: func TestCrasher(t *testing.T) { if os.Getenv("BE_CRASHER") == "1" { Crasher() return } cmd := exec.Command(os.Args[0], "-test.run=TestCrasher") cmd.Env = append(os.Environ(), "BE_CRASHER=1") err := cmd.Run() if e, ok := err.(*exec.ExitError); ok && !e.Success() { return } t.Fatalf("process ran with err %v, want exit status 1", err) } 核心技巧在于os.args[0]可以获取到真实的可执行 test 程序,从而改变环境变量.
go 笔记 defer defer 的参数绑定是在 defer 时,而不是在执行时,和 go 是一样的。 for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } 会输出4 3 2 1 0 make && new make 只能应用于 slice,map,channel,返回的不是指针。 以 slice 为例,一个 slice 是引用 copy,但是每个 slice 结构体战三个字。 channel range 用法,以及如何idiomatic的使用 closure func handle(queue chan *Request) { for r := range queue { process(r) } } func Serve(queue chan *Request) { for req := range queue { req := req // Create new instance of req for the goroutine. sem <- 1 go func() { process(req) <-sem }() } } panic panic可以用作复杂的错误处理,但是如果是用作错误处理,不要把 panic 暴露到包外。 Useful though this pattern is, it should be used only within a package. Parse turns its internal panic calls into error values; it does not expose panics to its client. That is a good rule to follow. // error is a method of *Regexp that reports parsing errors by // panicking with an Error. func (regexp *Regexp) error(err string) { panic(Error(err)) } // Compile returns a parsed representation of the regular expression. func Compile(str string) (regexp *Regexp, err error) { regexp = new(Regexp) // doParse will panic if there is a parse error. defer func() { if e := recover(); e != nil { regexp = nil // Clear return value. err = e.(Error) // Will re-panic if not a parse error. } }() return regexp.doParse(str), nil }
如何使用 geth1.8来监听合约事件 新功能介绍 geth1.8版本带来了新的事件处理方式,使用 abigen 可以自动生成包含合约事件监听以及过滤相关代码. 这样就不用自己去写代码解析 log. 比如: abigen --sol token.sol --pkg token --out token.go 一个例子 通过监听谁给我转账,来说明如何使用新的接口 创建 Filter 只需指定合约地址即可. filter, err := token.NewTokenFilterer(tokenAddr, c) 监听将要发生的事件 这个应该放在过滤历史事件之前,因为有可能在处理历史事件过程中产生了新的事件.如果顺序错了,就会造成事件丢失. ch := make(chan *token.TokenTransfer, 10) sub, err := filter.WatchTransfer(nil, ch, nil, []common.Address{toAddr}) if err != nil { log.Fatalf("watch transfer err %s", err) } go func() { for { select { case <-sub.Err(): return case e := <-ch: log.Printf("new transfer event from %s to %s value=%s,at %d", e.From.String(), e.To.String(), e.Value, e.Raw.BlockNumber) } } }() 简单直观,不用去关心 log 的细节. 感兴趣的话,可以看一下 TokenTranser 结构 // TokenTransfer represents a Transfer event raised by the Token contract. type TokenTransfer struct { From common.Address To common.Address Value *big.Int Raw types.Log // Blockchain specific contextual infos 过滤历史事件 也很直观,把你感兴趣的事件范围传递进去,会返回一个 Iterator, 遍历就 ok 了. history, err := filter.FilterTransfer(&bind.FilterOpts{Start: 480000}, nil, []common.Address{toAddr}) for history.Next() { e := history.Event log.Printf("%s transfer to %s value=%s, at %d", e.From.String(), e.To.String(), e.Value, e.Raw.BlockNumber) } 结论 有了这些自动生成的代码以后,我们就不用费劲去理解过滤时候的 Topic 怎么设置,Log怎么解析. 直接关注我们想要的事件本身就可以了. 当然也不是没有问题,如果我关注的不是某个合约上发生了转账事件,而是所有的ERC20token, 那么该怎么写呢? 目前我是没想到怎么实现,要想这么做还是要回到老办法上.
如何使用 Channel 例子来自于Concurrency is not parallelism Google Search: A fake framework v1.0 var ( Web = fakeSearch("web") Image = fakeSearch("image") Video = fakeSearch("video") ) type Search func(query string) Result func fakeSearch(kind string) Search { return func(query string) Result { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) return Result(fmt.Sprintf("%s result for %q\n", kind, query)) } } func main() { rand.Seed(time.Now().UnixNano()) start := time.Now() results := Google("golang") elapsed := time.Since(start) fmt.Println(results) fmt.Println(elapsed) } 关键函数 func Google(query string) (results []Result) { results = append(results, Web(query)) results = append(results, Image(query)) results = append(results, Video(query)) return } Google 2.0 每个 search, 独立并发. No locks. No condition variables. No callbacks. func Google(query string) (results []Result) { c := make(chan Result) go func() { c <- Web(query) } () go func() { c <- Image(query) } () go func() { c <- Video(query) } () for i := 0; i < 3; i++ { result := <-c results = append(results, result) } return } Google 2.1 如果某个服务比较慢,怎么办? No locks. No condition variables. No callbacks. func Google(query string) (results []Result) { c := make(chan Result) go func() { c <- Web(query) } () go func() { c <- Image(query) } () go func() { c <- Video(query) } () timeout := time.After(80 * time.Millisecond) for i := 0; i < 3; i++ { select { case result := <-c: results = append(results, result) case <-timeout: fmt.Println("timed out") return } } return } Google 3.0 Avoid timeout No locks. No condition variables. No callbacks. func First(query string, replicas ...Search) Result { c := make(chan Result) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c } func Google(query string) (results []Result) { c := make(chan Result) go func() { c <- First(query, Web1, Web2) } () go func() { c <- First(query, Image1, Image2) } () go func() { c <- First(query, Video1, Video2) } () timeout := time.After(80 * time.Millisecond) for i := 0; i < 3; i++ { select { case result := <-c: results = append(results, result) case <-timeout: fmt.Println("timed out") return } } return } Google 3.1 上面的例子看起来挺完美,但是存在一个严重的内存泄漏,不知道你看出来没有. First 中的 searchReplica调用,除了第一个会成功返回以外,其他都不会返回.因为堵塞在 c 上面,从而导致了内存泄漏. 改进也很简单 func First(query string, replicas ...Search) Result { c := make(chan Result,len(replicas)) //看似多分配了资源,但是很快就会收回 searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c } 经过简单的替换,通过 Go 的并发模型,将一个慢的,顺序执行的,故障敏感的程序改造为了一个快速的,并发的,有冗余的,健壮的程序. 完整的 google 3.1 var ( Web1 = fakeSearch("web") Web2 = fakeSearch("web") Image1 = fakeSearch("image") Image2 = fakeSearch("image") Video1 = fakeSearch("video") Video2 = fakeSearch("video") ) type Search func(query string) Result func fakeSearch(kind string) Search { return func(query string) Result { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) return Result(fmt.Sprintf("%s result for %q\n", kind, query)) } } func main() { rand.Seed(time.Now().UnixNano()) start := time.Now() results := Google("golang") elapsed := time.Since(start) fmt.Println(results) fmt.Println(elapsed) } func First(query string, replicas ...Search) Result { c := make(chan Result,len(replicas)) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { go searchReplica(i) } return <-c } func Google(query string) (results []Result) { c := make(chan Result) go func() { c <- First(query, Web1, Web2) } () go func() { c <- First(query, Image1, Image2) } () go func() { c <- First(query, Video1, Video2) } () timeout := time.After(80 * time.Millisecond) for i := 0; i < 3; i++ { select { case result := <-c: results = append(results, result) case <-timeout: fmt.Println("timed out") return } } return }
修改vscode caipeiyu.writeCnblog ,简化博客发布 1. 安装caipeiyu.writeCnblog vscode的博客园文章发布插件WriteCnblog : https://marketplace.visualstudio.com/items?itemName=caipeiyu.writeCnblog 2. 修改扩展 2.1 修改 cd .vscode/extensions/caipeiyu.writecnblog-0.0.1/out/src code extension.js 找到getBlogConfig,并修改如下: function getBlogConfig(callBakc) { if (_blogConfig && callBakc) { callBakc(_blogConfig); return; } ; function blogNameInputThen() { var apiUrl = "http://rpc.cnblogs.com/metaweblog/<你的博客名字>"; var name = "<你的登录用户名>"; var password = "<你的密码>"; var metaweblog = new metaweblog_1.Metaweblog(apiUrl); metaweblog.getUsersBlogs("cnblogWriteVsCode", name, password, function getUsersBlogsCallBakc(err, method, backData) { if (backData.faultCode) { vscode.window.showErrorMessage(backData.faultString); return; } var blogConfig = {}; blogConfig["config"] = { apiUrl: apiUrl, blogid: backData[0].blogid, name: name, password: password }; blogConfig["metaweblog"] = metaweblog; _blogConfig = blogConfig; if (callBakc) callBakc(blogConfig); }); } blogNameInputThen() } 比如我的博客名字是:http://www.cnblogs.com/baizx/ 那么替换为baizx 2.2 重启 vscode
TheDao 简化版解释 the Dao 合约 contract f1{ function transfer() { if (acccount[m]>=100) { m.send(100) account[m]-=100 } } } send 会调用 m 的 fallback 函数 但是 m 不是一个普通的地址,而是下面的合约 contract m { //fallback function function ()payable{ f1.f() } } 结论 先扣钱再转转 contract f2{ function transfer() { if (acccount[m]>=100) { account[m]-=100 m.send(100) } } }
最近装了 firefox,电脑配置不太高,chrome 太吃内存了。 但是发现 SwitchyOmega的 pac 模式无法工作,这篇文章提到了两个思路, 其中network.dns.disableIPv6 to true经验证无效, 通过把pac 路径删掉,直接用内容可以工作,不知道为什么。 能解决问题就好。
ERC223对ERC220的改进 ERC223是以太坊上最新的代币(token)接口标准,主要是为了解决ERC220代币转账丢失问题,那么怎么解决的呢,一起来看看. 1. ERC220 存在问题 ERC220接口中存在诸多转账接口,以transfer为例./// @notice send `_value` token to `_to` from `msg.sender` /// @param _to The address of the recipient /// @param _value The amount of token to be transferred /// @return Whether the transfer was successful or not function transfer(address _to, uint256 _value) public returns (bool success); 这个接口意思就是我给一个指定地址转多少token,功能非常简单,但是当初设计的时候没有考虑到的一个问题就是如果接收者是一个智能合约,那么合约是没法感知自己收到了多少token的. 当然ERC220只是一个接口标准(类似于dll接口),具体实现者完全可以考虑_to是智能合约时,采取一些特殊动作. 2. ERC223 标准化_to是合约地址时如何响应 上文提到具体实现者可以自己采取特殊动作,但是这缺乏规范,无法推广. 2.1 合约规范化的响应动作 function transfer(address to, uint value) public returns (bool ok); function transfer(address to, uint value, bytes data) public returns (bool ok); 第一个接口兼容ERC220,第二个则是扩展ERC220,可以传递给合约一些数据(参数data). 具体实现就是,如果to是合约,那么会尝试调用该合约的tokenFallback函数,如果成功,则token转移成功,否则失败. 这个思路实际上就是以太坊上智能合约接收以太币时有一个fallback函数. 2.2 更灵活的响应机制 transfer(address _to, uint _value, bytes _data, string _custom_fallback) 如果接收方合约没有提供tokenFallback函数,则可以通过合约的fallback函数来传递信息,让合约知道有人给他转token了. 具体就是在这里 assert(_to.call.value(0)(bytes4(keccak256(_custom_fallback)), msg.sender, _value, _data)); 这里的_to.call.value(0)(...) 就是直接调用合约fallback函数,但是我没给你转以太币,只是给你捎了个信儿,可以包含更多的自定义信息.当然gas也不便宜啊..
都是gasLimit惹的祸 解决一个奇怪问题Error: Number can only safely store up to 53 bits 原来好好的node endpointtest.js ,结果在新的私链上怎么都不能运行了. 这个部署js文件就不上了,很简单. 只要 gas设置稍大一点就报标题的那个错误,但是gas设置小了,又满足不了要求. 经过排查发现时genesis.json指定的gasLimit太大了,导致web3.js在计算的时候出错. 私链的genesis.json { "config": { "chainId": 89120348582, "homesteadBlock": 1, "eip155Block": 1, "eip158Block": 1, "ByzantiumBlock":1 }, "difficulty": "2", "gasLimit": "0xff210000000000000", "alloc": { "0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa": { "balance": "30000000000000000000000" }, "0x33df901abc22dcb7f33c2a77ad43cc98fbfa0790": { "balance": "40000000000000000000000" } } } 后来修改gaslimit为990000000000000,总算解决问题.看来gaslimit不是随便设置一个就可以的. 太小会造成你的合约经常超出最大限制而无法运行,太大就会出现标题中的问题. 感谢开源软件,可以添加一些调试信息来发现错误.
以太坊系列之二十一 使用web3部署比较复杂的智能合约 搭建私链上的雷电网络 以太坊系列之二十一 使用web3部署比较复杂的智能合约 1 雷电网络智能合约简单介绍 2 remix 无法部署使用了library的contract 3 使用web3部署完整的雷电网络合约 3.1 部署NettingChannelLibrary.sol 3.2 部署ChannelManagerLibrary 3.3 部署EndpointRegistry.sol 前面博文中已经有一篇介绍如何使用web3部署智能合约了,但是针对比较复杂的智能合约那种方式就不行了. 实际上这篇文章的起因是因为打算研究雷电网络,就想在自己的私链上部署一个雷电网络的合约.结果发现居然很多问题. 1 雷电网络智能合约简单介绍 雷电网络要正常工作必须借助两个事先部署在区块链上的合约才能正常工作,具体来说就是registry和endpointregistry.如果去查看雷电网络的代码可以看到这两个合约地址是事先写好在代码中的. 这两个合约比较复杂,尤其是registry.sol,依赖复杂.我们主要来看几个主要文件之间的依赖关系: Registry.sol<---ChannelManagerContract.sol<-----ChannelManagerLibrary.sol<-------NettingChannelContract.sol<------NettingChannelLibrary.sol 中间名字里面有Library的,这些文件是library而不是contract,所以在solc编译的时候是不会编译进生成的二进制code中,必须先部署library,然后在对其进行link. 比如:Registry.sol<---ChannelManagerContract.sol<-----ChannelManagerLibrary.sol 那么在生成Registry.sol的code时,里面不会包含ChannelManagerLibrary.sol的代码,只会包含ChannelManagerContract.sol.的代码.这有点类似于前者是动态链接,而后者是静态链接. 附上remix生成的registry的bin code,里面包含了一些字符串__localhost/ChannelManagerLibrary.sol:__,这明显是错误的,这实际上是需要替换成ChannelManagerLibrary部署后的地址才能正常工作. 6060604052341561000f57600080fd5b6040516020806109ae8339810160405280805160018054600160a060020a03909216600160a060020a0319909216919091179055505061095a806100546000396000f30060606040526004361061005e5763ffffffff60e060020a6000350416630b74b620811461006e578063238bfba2146100d45780636785b5001461010f5780636cb30fee146101225780639d76ea5814610141578063f26c6aed14610154575b341561006957600080fd5b600080fd5b341561007957600080fd5b610081610176565b60405160208082528190810183818151815260200191508051906020019060200280838360005b838110156100c05780820151838201526020016100a8565b505050509050019250505060405180910390f35b34156100df57600080fd5b6100f3600160a060020a03600435166102c8565b604051600160a060020a03909116815260200160405180910390f35b341561011a57600080fd5b610081610350565b341561012d57600080fd5b610081600160a060020a03600435166103b9565b341561014c57600080fd5b6100f3610446565b341561015f57600080fd5b6100f3600160a060020a0360043516602435610455565b61017e6108d5565b6000806101896108d5565b60008060006002805490506002026040518059106101a45750595b9080825280602002602001820160405250935060009450600095505b6002548610156102bd5760028054879081106101d857fe5b6000918252602082200154600160a060020a031693508390636d2381b390604051608001526040518163ffffffff1660e060020a028152600401608060405180830381600087803b151561022b57600080fd5b6102c65a03f1151561023c57600080fd5b505050604051805190602001805190602001805190602001805190505092505091508184868151811061026b57fe5b600160a060020a03909216602092830290910190910152600194909401938084868151811061029657fe5b600160a060020a0390921660209283029091019091015260019586019594909401936101c0565b509195945050505050565b600073__localhost/ChannelManagerLibrary.sol:__638a1c00e28284816040516020015260405160e060020a63ffffffff85160281526004810192909252600160a060020a0316602482015260440160206040518083038186803b151561033057600080fd5b6102c65a03f4151561034157600080fd5b50505060405180519392505050565b6103586108d5565b60028054806020026020016040519081016040528092919081815260200182805480156103ae57602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610390575b505050505090505b90565b6103c16108d5565b6003600083600160a060020a0316600160a060020a0316815260200190815260200160002080548060200260200160405190810160405280929190818152602001828054801561043a57602002820191906000526020600020905b8154600160a060020a0316815260019091019060200180831161041c575b50505050509050919050565b600154600160a060020a031690565b60008060008060008060008061046a8a6102c8565b600160a060020a033381166000908152600360205260408082208e84168352912092995097509095508716156104f4576104a3876108b5565b156104ad57600080fd5b7fda8d2f351e0f7c8c368e631ce8ab15973e7582ece0c347d75a5cff49eb899eb7338b604051600160a060020a039283168152911660208201526040908101905180910390a15b73__localhost/ChannelManagerLibrary.sol:__63941583a560008c8c826040516020015260405160e060020a63ffffffff86160281526004810193909352600160a060020a039091166024830152604482015260640160206040518083038186803b151561056357600080fd5b6102c65a03f4151561057457600080fd5b5050506040518051945050600160a060020a038716156106a357505050600160a060020a038085166000908152600560209081526040808320543385168085526004808552838620968e168652958452828520549584528285209085529092529091205460028054929392859190859081106105ec57fe5b906000526020600020900160006101000a815481600160a060020a030219169083600160a060020a0316021790555083868381548110151561062a57fe5b906000526020600020900160006101000a815481600160a060020a030219169083600160a060020a0316021790555083858281548110151561066857fe5b6000918252602090912001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a039290921691909117905561084d565b60028054600181016106b583826108e7565b506000918252602090912001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03861617905585548690600181016106f983826108e7565b506000918252602090912001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038616179055845485906001810161073d83826108e7565b9160005260206000209001600086909190916101000a815481600160a060020a030219169083600160a060020a03160217905550506001600280549050036005600086600160a060020a0316600160a060020a031681526020019081526020016000208190555060018680549050036004600033600160a060020a0316600160a060020a0316815260200190815260200160002060008c600160a060020a0316600160a060020a03168152602001908152602001600020819055506001858054905003600460008c600160a060020a0316600160a060020a03168152602001908152602001600020600033600160a060020a0316600160a060020a03168152602001908152602001600020819055505b7f7bd269696a33040df6c111efd58439c9c77909fcbe90f7511065ac277e175dac84338c8c604051600160a060020a039485168152928416602084015292166040808301919091526060820192909252608001905180910390a1509198975050505050505050565b6000813b818111156108ca57600191506108cf565b600091505b50919050565b60206040519081016040526000815290565b81548183558181151161090b5760008381526020902061090b918101908301610910565b505050565b6103b691905b8082111561092a5760008155600101610916565b50905600a165627a7a7230582000e4924a646ac6cc6f9a6b01f9d4d7d6005bc46a391ce5cd70613d5ad04b015c0029 2 remix 无法部署使用了library的contract 网上也找了相关的文章看了看,总的来说就是因为如果一个contract使用了library(注意不是import 其他sol),那么编译生成的二进制code是不完整的,必须进行link,目前remix (solidity IDE)是不支持的,所以只能绕过. 比如在部署registry.sol时,具体报错: Error deploying required libraries: Library localhost/ChannelManagerLibrary.sol: not found 3 使用web3部署完整的雷电网络合约 3.1 部署NettingChannelLibrary.sol 这是依赖的最底层,必须先部署. 部署代码和以前的类似,直接给出完整代码: //参考文章:http://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#eth-contract var Web3 = require('web3'); console.log("Web3 Version:",Web3.version); //version 1.0 以下代码只在此版本测试,更早版本确定不行 var net = require('net'); var web3 = new Web3(new Web3.providers.IpcProvider("\\\\.\\pipe\\geth.ipc",net)); var eth=web3.eth; deployNettingChannelLibraryContract() function deployNettingChannelLibraryContract(){ var nettingchannellibraryContract =new web3.eth.Contract([{"constant":false,/*忽略*/}]); nettingchannellibraryContract.deploy({ data: '0x60606040523.....', }) .send({ from: "0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa", gas: 15000000, gasPrice: '30000000000000' }, function(error, transactionHash){ }) .on('error', function(error){ console.log('deploy error') }) .on('transactionHash', function(transactionHash){ }) .on('receipt', function(receipt){ //console.log(receipt.contractAddress) // contains the new contract address }) .on('confirmation', function(confirmationNumber, receipt){ }) .then(function(newContractInstance){ console.log("nettingchannellibraryContract",newContractInstance.options.address) // instance with the new contract address deployChannelManagerLibrary(newContractInstance.options.address) }); } 后面的deployChannelManagerLibrary调用就是说部署NettingChannelLibraryContract成功以后,再去部署上一层的ChannelManagerLibrary 3.2 部署ChannelManagerLibrary 部署ChannelManagerLibrary的关键是要知道NettingChannelLibraryContract在区块链上的地址 所以必须要先对其二进制code进行替换处理. var data='0x606060405234156105b...73__localhost/NettingChannelLibrary.sol:__63d...29' data=data.replace(/__localhost\/NettingChannelLibrary.sol:__/g,nettingChannelLibraryAddress.replace("0x","")) channelManagerLibraryContract.deploy({ data:data, }) 然后才能进行部署,否则会说code中的__localhost/NettingChannelLibrary.sol不是hex编码的字符串 完整的代码,为了凑字数...,由于部署registry的思路是一致的,就直接放到一起了. function deployChannelManagerLibrary(nettingChannelLibraryAddress) { var data='0x606060....29' data=data.replace(/__localhost\/NettingChannelLibrary.sol:__/g,nettingChannelLibraryAddress.replace("0x","")) //console.log("after replace data:",data) var channelManagerLibraryContract =new web3.eth.Contract([{"constant":false,/*忽略*/}]); channelManagerLibraryContract.deploy({ data:data, }) .send({ from: "0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa", gas: 15000000, gasPrice: '30000000000000' }, function(error, transactionHash){ }) .on('error', function(error){ console.log('deploy channelManagerLibraryContract error') }) .then(function(newContractInstance){ console.log("channelManagerLibraryContract",newContractInstance.options.address) // instance with the new contract address deployRegistry(newContractInstance.options.address) }); } function deployRegistry(channelManagerLibraryAddress) { var data='0x6060604....29' data=data.replace(/__localhost\/ChannelManagerLibrary.sol:__/g,channelManagerLibraryAddress.replace("0x","")) //console.log("after replace data:",data) var registryContract =new web3.eth.Contract([{"constant":true,/*忽略*/}]); registryContract.deploy({ data:data, }) .send({ from: "0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa", gas: 15000000, gasPrice: '30000000000000' }, function(error, transactionHash){ }) .on('error', function(error){ console.log('deploy registryContract error') }) .then(function(newContractInstance){ console.log("registryContract",newContractInstance.options.address) // instance with the new contract address }); } 3.3 部署EndpointRegistry.sol 直接部署,和3.1 部署NettingChannelLibrary.sol中介绍一致,不再赘述. 3.4 完整运行结果 node deployregistry.js Web3 Version: 1.0.0-beta.26 endPointRegistryContract: 0x7eA46670E6A75180e355F9b8fb7501d9618b6D7E nettingchannellibraryContract 0xCeFE92604e376C99568eb951106c8ADCE9D184eF channelManagerLibraryContract 0x15659CeC0602e76EC8e8d5325c2D7a0b4f63f86d registryContract 0x46c9D962c6D5CDc69cF2A7617432eDe2c99a5255 拿到地址,就可以将源码中相应地址进行替换,这样就是一个你自己的雷电网络了.
以太坊系列之二十 以太坊中的基础应用whisper 以太坊系列之二十 以太坊中的基础应用whisper 1 whisper介绍 2 whisper rpc模块 3 whisper中的消息 4 消息的加密 5 过滤器 以太坊作为一个区块链生态系统,为区块链dapp应用提供了丰富的环境,whisper就是其中一个基础性设施.它相当于是以太坊中的bitmessage,希望以后dapp中可以用上whisper.当然到目前为止,智能合约中还是无法访问whisper功能,智能合约指令集中没有访问这些功能的指令集,希望以后能够拓展. 1 whisper介绍 whisper是一个比较独立的模块,它的运行完全独立于geth也就是以太坊区块链.如果要感受whisper可以通过启动wnode来感受.wnode有丰富的参数,能够让大家加入whisper网络,来感受这个去中心化的聊天系统. 我前面写的[以太坊系列之十八: 百行go代码构建p2p聊天室](http://www.cnblogs.com/baizx/p/7505096.html)就是一个简版的wnode,只取了其中一小部分. whisper实际上就是以太坊p2p网络上的一个应用, 注意我强调以太坊网络是因为它的p2p协议直接取自前面介绍的[以太坊系列之六: p2p模块--以太坊源码学习](http://www.cnblogs.com/baizx/p/6963636.html),那里提到P2p模块是支持自定义Protocol的,实际上以太坊区块的同步,tx的发布都是其中的一个协议而已.同样whisper也是其中的一个协议. 我们来看看这个协议的定义. ```go // p2p whisper sub protocol handler whisper.protocol = p2p.Protocol{ Name: ProtocolName, //shh Version: uint(ProtocolVersion), //5 Length: NumberOfMessageCodes, //64 Run: whisper.HandlePeer, //数据处理 } ``` 在web3 api中暴露的shh模块主体就是在这里 2 whisper rpc模块 以太坊系列对外提供的api接口都是通过rpc调用来实现的. whisper同样也是,whisper对外提供的api主要是建立账户,发送消息,过滤消息等. 我们一般意义上的接收消息就是过滤消息,因为p2p网络中会收到大量各种消息,你只有定义了自己感兴趣的消息,也就是过滤消息才能得到想要的具体消息内容. 对外提供rpc api的接口一般都是如下形式: ```go []rpc.API{ { Namespace: ProtocolName, //shh Version: ProtocolVersionStr, //5.0 Service: NewPublicWhisperAPI(w), Public: true, }, } // NewPublicWhisperAPI create a new RPC whisper service. func NewPublicWhisperAPI(w *Whisper) *PublicWhisperAPI { return &PublicWhisperAPI{whisper: w} } ``` PublicWhisperAPI包含了所有可能被访问的api. 3 whisper中的消息 主要有三种消息,分别是messagesCode ,p2pCode,p2pRequestCode 我们一般用的主要就是messagesCode类型,p2pCode用于点对点直接通信,p2pRequestCode为智能合约使用. 主要说一下messageCode类型如何处理. 对于收到的消息,首先检验发送时间是否正确(防止错误垃圾消息),然后计算工作量是否到位(这个就是bitmessage的核心思想), 第三步检验该消息是否已经收到过(hash值存在),如果存在就忽略,否则进入第四步. 第四步将消息放入本地消息队列 节点会定期对消息队列中的消息进行处理. 第五步 检测是否满足某个过滤条件,如果是,就将消息放入过滤队列(channel)中. 另外,节点还会启动一个携程定期把本地收到的合法消息广播出去. 以上基本就是一个p2p聊天系统的核心了. 4 消息的加密 消息有两种加密形式,一种是对称的aes加密,另一种是非对称的ecdsaj加密.上面对于消息的处理流程中除了第五步检测是否满足设定的过滤条件,其他步骤都不涉及对消息内容的处理,也就无需解密. 也就是说真正对消息内容的处理,是在过滤器中进行的.先看看消息的格式,里面包含什么内容. 分成两个部分,一个是信封,一个是信体,信封没有加密,信体加密. ```go // Envelope represents a clear-text data packet to transmit through the Whisper // network. Its contents may or may not be encrypted and signed. type Envelope struct { Version []byte Expiry uint32 TTL uint32 Topic TopicType AESNonce []byte Data []byte EnvNonce uint64 pow float64 // Message-specific PoW as described in the Whisper specification. } ``` 信封里主要是时间相关信息以及验证工作量所需信息. 需要特别指出的是,whisper可以按照特定的topic过滤消息,topic是明文且没有加密的四字节数据. 信体格式比较复杂,关键是里面有需要解密消息的对称密钥或者公钥. ```go // ReceivedMessage represents a data packet to be received through the // Whisper protocol. type ReceivedMessage struct { Raw []byte Payload []byte Padding []byte Signature []byte PoW float64 // Proof of work as described in the Whisper spec Sent uint32 // Time when the message was posted into the network TTL uint32 // Maximum time to live allowed for the message Src *ecdsa.PublicKey // Message recipient (identity used to decode the message) Dst *ecdsa.PublicKey // Message recipient (identity used to decode the message) Topic TopicType SymKeyHash common.Hash // The Keccak256Hash of the key, associated with the Topic EnvelopeHash common.Hash // Message envelope hash to act as a unique id EnvelopeVersion uint64 } ``` 可以看到信体里也把信封的大部分内容都包含进去了. 5 过滤器 过滤器必须给出一个公钥或者aes密钥来解密消息, 也可以给出一个或多个可选的topic来进行过滤. 过滤器实际上是用户接入whisper消息系统的入口,也只有这种方式来接收消息. 另外过滤器还可以指定一个更高难度(相比默认难度)的工作量来过滤消息. 过滤器结构如下: type Filter struct { Src *ecdsa.PublicKey // Sender of the message KeyAsym *ecdsa.PrivateKey // Private Key of recipient KeySym []byte // Key associated with the Topic Topics [][]byte // Topics to filter messages with PoW float64 // Proof of work as described in the Whisper spec AllowP2P bool // Indicates whether this filter is interested in direct peer-to-peer messages SymKeyHash common.Hash // The Keccak256Hash of the symmetric key, needed for optimization msgChan chan *ReceivedMessage //matched message channel mutex sync.RWMutex } 主要包含指定的公钥,指定的对称密钥,topics,pow.
以太坊系列之十九 对p2p模块server的理解 type transport interface { // The two handshakes. doEncHandshake(prv *ecdsa.PrivateKey, dialDest *discover.Node) (discover.NodeID, error) doProtoHandshake(our *protoHandshake) (*protoHandshake, error) // The MsgReadWriter can only be used after the encryption // handshake has completed. The code uses conn.id to track this // by setting it to a non-nil value after the encryption handshake. MsgReadWriter // transports must provide Close because we use MsgPipe in some of // the tests. Closing the actual network connection doesn't do // anything in those tests because NsgPipe doesn't use it. close(err error) } type conn struct { fd net.Conn transport //指向真正的transport实现,rlpx flags connFlag cont chan error // The run loop uses cont to signal errors to setupConn. id discover.NodeID // valid after the encryption handshake caps []Cap // valid after the protocol handshake name string // valid after the protocol handshake } // Server manages all peer connections. type Server struct { // Config fields may not be modified while the server is running. Config // Hooks for testing. These are useful because we can inhibit // the whole protocol stack. newTransport func(net.Conn) transport newPeerHook func(*Peer) lock sync.Mutex // protects running running bool ntab discoverTable listener net.Listener ourHandshake *protoHandshake lastLookup time.Time DiscV5 *discv5.Network // These are for Peers, PeerCount (and nothing else). peerOp chan peerOpFunc peerOpDone chan struct{} quit chan struct{} addstatic chan *discover.Node removestatic chan *discover.Node posthandshake chan *conn addpeer chan *conn delpeer chan peerDrop loopWG sync.WaitGroup // loop, listenLoop } Server是暴露给上层使用的接口,配置完毕以后,直接调用Start就可以了,其中Start做了非常复杂的工作.主要是启动监听端口,等待连接.这里会创建多个goroutine,完毕以后进入run,run实际上就是一个汇聚层,其他的goroutine都回把得到的结果通过channel形式汇报给主goroutine,也就是run,由run集中处理,避免并发冲突. 另外run的另一个重要功能就是不断的调用dialstate,来创建连接,维护与结点的连接.也就是scheduleTasks. 创建Server,对于用户来说主要是指定一个newPeerHook,这样当有了新的结点以后,就可以和这些结点进行通信,通信的格式是Msg,这个应该是用户最关心的. newTransport 是和结点建立连接(tcp或者udp)以后进行协商密钥,协议握手的地方,实际上是newRLPX.也就是rlpx来负责. 这里先说说Config,这里的PrivateKey必须指定,否则会出错.
百行go代码构建p2p聊天室 百行go代码构建p2p聊天室 1. 上手使用 2. whisper 原理 3. 源码解读 3.1 参数说明 3.1 连接主节点 3.2 我的标识 3.2 配置我的节点 3.3 哪个聊天室 3.3 加入聊天室 3.4 群发消息 3.5 接收消息 4. 再次使用p2pmessage 只需百行代码,就可以构建一个完整的p2p聊天室,并且消息加密,无法被追踪;并且不需要服务器,永不停机,是不是很酷. 系统实际上基于以太坊的whisper,它本来是为以太坊上的DAPPS通信构建的,这里直接拿来做聊天室一点问题都没有. 1. 上手使用 先说用法,来感受一下完全匿名的P2p聊天系统. 在终端运行p2pmessage.exe,然后等待出现.Connected to peer,you can type message now.,这时候你就已经连接到whisper的p2p网络中了. 有可能你需要几分钟才能成功. 你可以收到来自别人的消息,也可以发送消息给别人. 你可以同时在不同的机器上启动程序来感受一下结果. 2017-09-11 11:31:18 <mine>: hello input ~Q to quit> 2017-09-11 11:33:10 [182cbbaac94313b3b96b25d9c9a0a1adea4519e9]: who am i 第一行是收到了我发送的消息,第二行是提示输入,第三行是收到了182cbbaac94313b3b96b25d9c9a0a1adea4519e9发来的消息. 其中182cbbaac94313b3b96b25d9c9a0a1adea4519e9是结点标识. 2. whisper 原理 接入whisper网络中的节点,在收到任何消息会首先验证一下工作量(可以参考bitmessage),如果没问题然后就转发. 同时也会看看是不是发送给我的,如果是就告诉用户. 至于怎么知道是不是发送给我的,有多种方式,这里只使用主题以及密码匹配. 也就是说,必须是我感兴趣的主题,同时加密的密码也是我指定的.那就可以愉快的聊天了. 当然,我没办法知道对方是谁,除非他告诉我. 3. 源码解读 可以到github下载完整的源码. 3.1 参数说明 总共有三个参数 -verbosity 用来打印调试信息 -topic 聊天室的主题(任意四个字节), 你必须事先知道主题才能加入,如果随便写一个,那就是你自己创建一个聊天室了. -password 聊天室的密码, 主题密码都一致,才能进入同一个聊天室. 3.1 连接主节点 虽然说p2p网络没有服务器,但是必须存在知名节点,否则无从启动网络. 首先就是连接以太坊的主节点. for _, node := range ethparams.MainnetBootnodes { peer := discover.MustParseNode(node) peers = append(peers, peer) } peer := discover.MustParseNode("enode://b89172e36cb79202dd0c0822d4238b7a7ddbefe8aa97489049c9afe68f71b10c5c9ce588ef9b5df58939f982c718c59243cc5add6cebf3321b88d752eac02626@182.254.155.208:33333") peers = append(peers, peer) 后面这个节点是我搭建的.方便国内的用户快速通信,因为基于主节点的通信可能会比较慢,延时比较长. 3.2 我的标识 每个节点都有自己的私钥,标识就是自己的公钥. 当然可以每次都使用相同的私钥,这里简单起见,每次都是自动生成了. asymKeyID, err = shh.NewKeyPair() if err != nil { utils.Fatalf("Failed to generate a new key pair: %s", err) } asymKey, err = shh.GetPrivateKey(asymKeyID) if err != nil { utils.Fatalf("Failed to retrieve a new key pair: %s", err) } 3.2 配置我的节点 一个节点就是不停的转发符合Pow的消息,如果是我这个聊天室的消息,就告诉用户.所以节点要和其他节点进行交互,交互的节点越多,消息传播的越快. 当然这些节点数量要有一个上限,这里是80. 其中peers变量就是3.1 连接主节点的主节点. maxPeers := 80 server = &p2p.Server{ Config: p2p.Config{ PrivateKey: asymKey, MaxPeers: maxPeers, Name: common.MakeName("p2p chat group", "5.0"), Protocols: shh.Protocols(), NAT: nat.Any(), BootstrapNodes: peers, StaticNodes: peers, TrustedNodes: peers, }, } 3.3 哪个聊天室 具有相同的主题和密码的就是同一个聊天室. symKey关联到指定的密码,topic保存四个字节的指定主题. func configureNode() { symKeyID, err := shh.AddSymKeyFromPassword(*argPass) if err != nil { utils.Fatalf("Failed to create symmetric key: %s", err) } symKey, err = shh.GetSymKey(symKeyID) if err != nil { utils.Fatalf("Failed to save symmetric key: %s", err) } copy(topic[:], common.FromHex(*argTopic)) fmt.Printf("Filter is configured for the topic: %x \n", topic) } 3.3 加入聊天室 我的节点可能会收到千百条各种消息,有些我能解密,有些我不能解密,但是其中只有极少一部分是我想看到的. 所以要告诉我的节点我只对这个聊天室感兴趣,如果有消息来就告诉我. SubscribeMessage订阅指定主题和密码的消息,注意filterID,它相当于向系统订阅特定消息的句柄,后面还会用到. func SubscribeMessage() { var err error filter := whisper.Filter{ KeySym: symKey, KeyAsym: asymKey, Topics: [][]byte{topic[:]}, AllowP2P: true, } filterID, err = shh.Subscribe(&filter) if err != nil { utils.Fatalf("Failed to install filter: %s", err) } } 3.4 群发消息 在p2p网络中群发消息反而是最简单的,如果要点对点发消息,限制反而要多. whisper提供有发送消息的api. 主要就是构造一个合法的消息结构,主要是指定topic以及加密的秘钥,还有就是消息体(payload)就可以了,asymKey主要是为了标识ID,不是用作非对称加密. 发送消息主要是按照指定的PoW要求(比特币,莱特币,以太币等等都是类似的思路),计算hash,然后把消息发送到网络上. func sendMsg(payload []byte) common.Hash { params := whisper.MessageParams{ Src: asymKey, KeySym: symKey, Payload: payload, Topic: topic, TTL: whisper.DefaultTTL, PoW: whisper.DefaultMinimumPoW, WorkTime: 5, } msg, err := whisper.NewSentMessage(&params) if err != nil { utils.Fatalf("failed to create new message: %s", err) } envelope, err := msg.Wrap(&params) if err != nil { fmt.Printf("failed to seal message: %v \n", err) return common.Hash{} } err = shh.Send(envelope) if err != nil { fmt.Printf("failed to send message: %v \n", err) return common.Hash{} } return envelope.Hash() } 3.5 接收消息 系统实际上一直在不停的接收消息并转发,这里说的接收消息实际上就是把我们感兴趣的消息提取出来,也就是我们这个聊天室的消息. 注意这里的filterID就是 3.3 哪个聊天室提到的,这里可以认作是聊天室的ID了. 可以看出messageLoop就是不停的轮询有没有相关聊天室的消息,目前whisper还没有实现消息推送功能. func messageLoop() { f := shh.GetFilter(filterID) if f == nil { utils.Fatalf("filter is not installed") } ticker := time.NewTicker(time.Millisecond * 50) for { select { case <-ticker.C: messages := f.Retrieve() for _, msg := range messages { printMessageInfo(msg) } case <-done: return } } } 4. 再次使用p2pmessage 在主机1和主机2同时上运行p2pmessage -topic ffff0000 -password 7859931 ,并等待Connected to peer,you can type message now. 可以看到如下截图: 主机1: 主机2: 5. 二进制程序下载地址 win64版本linux64版本
以太坊系列之十七: 使用web3进行智能合约的部署调用以及监听事件(Event) 上一篇介绍了使用golang进行智能合约的部署以及调用,但是使用go语言最大的一个问题是没法持续监听事件的发生. 比如我的后台程序需要监控谁给我转账了,如果使用go语言,目前就只能是轮询数据,而使用web3就简单许多,geth会把我关心的事件 主动通知给我. 以太坊系列之十七: 使用web3进行智能合约的部署调用以及监听事件(Event) token合约 编译token 部署合约 查询合约 调用合约函数 监听事件 token合约 token合约是官方提供的一个样例,这里给出我修改过的版本,方便演示. contract MyToken { /* Public variables of the token */ string public name; string public symbol; uint8 public decimals; /* This creates an array with all balances */ mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint)) public allowance; mapping (address => mapping (address => uint)) public spentAllowance; /* This generates a public event on the blockchain that will notify clients */ event Transfer(address indexed from, address indexed to, uint256 value); event ReceiveApproval(address _from, uint256 _value, address _token, bytes _extraData); /* Initializes contract with initial supply tokens to the creator of the contract */ function MyToken(uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol) { balanceOf[msg.sender] = initialSupply; // Give the creator all initial tokens name = tokenName; // Set the name for display purposes symbol = tokenSymbol; // Set the symbol for display purposes decimals = decimalUnits; // Amount of decimals for display purposes } /* Send coins */ function transfer(address _to, uint256 _value) { if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows balanceOf[msg.sender] -= _value; // Subtract from the sender balanceOf[_to] += _value; // Add the same to the recipient Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place } /* Allow another contract to spend some tokens in your behalf */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) { allowance[msg.sender][_spender] = _value; ReceiveApproval(msg.sender, _value, this, _extraData); } /* A contract attempts to get the coins */ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) { if (balanceOf[_from] < _value) throw; // Check if the sender has enough if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows if (spentAllowance[_from][msg.sender] + _value > allowance[_from][msg.sender]) throw; // Check allowance balanceOf[_from] -= _value; // Subtract from the sender balanceOf[_to] += _value; // Add the same to the recipient spentAllowance[_from][msg.sender] += _value; Transfer(msg.sender, _to, _value); } /* This unnamed function is called whenever someone tries to send ether to it */ function () { throw; // Prevents accidental sending of ether } } 编译token 通过solc编译得到的json abi以及hex编码的code,由于太长,太占地方就不放上来了. 这些会在部署以及调用的时候用到. 下面来通过web3进行合约的部署,读取数据,发起事务以及监听事件的发生. 部署合约 部署合约需要创建事务,话费gas,因此相关账户必须事先解锁,web3目前没有api可以解锁账户,需要自己在控制台事先解锁才行. 首先创建合约,需要制定abi. 然后就可以部署合约了,这时候需要合约的code, 部署合约的结果可以通过异步函数来获取,例子已经给出. var Web3 = require('web3'); var web3 = new Web3(new Web3.providers.IpcProvider("\\\\.\\pipe\\geth.ipc",net)); var eth=web3.eth; var tokenContract = new web3.eth.Contract(MyTokenABI, null, { from: '0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa' // 目前web3没有api来解锁账户,只能自己事先解锁 }); tokenContract.deploy({ data: MyTokenBin, arguments: [32222, 'token on web3',0,'web3'] }).send({ from: '0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa', gas: 1500000, gasPrice: '30000000000000' }, function(error, transactionHash){ console.log("deploy tx hash:"+transactionHash) }) .on('error', function(error){ console.error(error) }) .on('transactionHash', function(transactionHash){ console.log("hash:",transactionHash)}) .on('receipt', function(receipt){ console.log(receipt.contractAddress) // contains the new contract address }) .on('confirmation', function(confirmationNumber, receipt){console.log("receipt,",receipt)}) .then(function(newContractInstance){ console.log(newContractInstance.options.address) // instance with the new contract address }); 查询合约 合约部署成功以后,有了地址就可以根据地址来查询合约的状态. 查询合约状态并不需要发起事务,也不需要花费gas,因此比较简单. var tokenContract = new web3.eth.Contract(MyTokenABI, '0x6a0dF9E94a41fCd89d8236a8C03f9D678df5Acf9'); tokenContract.methods.name().call(null,function(error,result){ console.log("contract name "+result); }) 调用合约函数 合约的函数除了指明返回值是constant的以外,都需要发起事务,这时候就需要指定调用者,因为要花费该账户的gas. 这里调用一下transfer函数. tokenContract.methods.transfer("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401",387).send({from: '0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa'}) .on('transactionHash', function(hash){ }) .on('confirmation', function(confirmationNumber, receipt){ }) .on('receipt', function(receipt){ // receipt example console.log(receipt); //查询这里可以得到结果 }) .on('error', console.error); // If a out of gas error, the second parameter is the receipt. 监听事件 刚刚调用transfer的时候还会触发合约的事件Transfer,如果程序关注谁给谁进行了转账,那么就可以通过监听该事件. 通过指定fromBlock,toBlock可以限制事件发生的范围,除了这个还有一个filter参数可以进行更详细的限制, 如有兴趣可以查询文档web3文档 tokenContract.events.Transfer({ fromBlock: 0, toBlock:'latest' }, function(error, event){ /*console.log("result:\n"+JSON.stringify(event)); */}) .on('data', function(event){ console.log(event); // same results as the optional callback above }) .on('changed', function(event){ // remove event from local database }) .on('error', console.error); 需要说明的是,这个监听不仅传送历史事件,未来所有事件也会传送. 下面的结果是首先运行程序,会先打印出来刚刚进行的转账. 然后我在通过其他途径进行转账操作,这时候终端会把新的转账也打印出来. 历史转账: { address: '0x6a0dF9E94a41fCd89d8236a8C03f9D678df5Acf9', blockNumber: 10680, transactionHash: '0x27d5ab9277df504a436b1068697a444d30228584094632f10ab7ba5213a4eccc', transactionIndex: 0, blockHash: '0xcde734882b0d8cb7a5bf1f7e6d1ccfac5365308de2d7391ce286b45c5546f40b', logIndex: 0, removed: false, id: 'log_2588a961', returnValues: Result { '0': '0x1a9eC3b0b807464e6D3398a59d6b0a369Bf422fA', '1': '0x8c1b2E9e838e2Bf510eC7Ff49CC607b718Ce8401', '2': '387', from: '0x1a9eC3b0b807464e6D3398a59d6b0a369Bf422fA', to: '0x8c1b2E9e838e2Bf510eC7Ff49CC607b718Ce8401', value: '387' }, event: 'Transfer', signature: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', raw: { data: '0x0000000000000000000000000000000000000000000000000000000000000183', topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x0000000000000000000000001a9ec3b0b807464e6d3398a59d6b0a369bf422fa', '0x0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce8401' ] } } 实时监控到的转账 { address: '0x6a0dF9E94a41fCd89d8236a8C03f9D678df5Acf9', blockNumber: 10740, transactionHash: '0xb42651720e8b8b64283cbd245aebaa7ad7e3dda58b9887f645ad6957bd7771b8', transactionIndex: 0, blockHash: '0xcdca97ba5a277e402a93188df03a758c916c37eea0f7498365d227ebd7cb2ee2', logIndex: 0, removed: false, id: 'log_edc5dc68', returnValues: Result { '0': '0x1a9eC3b0b807464e6D3398a59d6b0a369Bf422fA', '1': '0x0000000000000000000000000000000000000001', '2': '32', from: '0x1a9eC3b0b807464e6D3398a59d6b0a369Bf422fA', to: '0x0000000000000000000000000000000000000001', value: '32' }, event: 'Transfer', signature: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', raw: { data: '0x0000000000000000000000000000000000000000000000000000000000000020', topics: [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', '0x0000000000000000000000001a9ec3b0b807464e6d3398a59d6b0a369bf422fa', '0x0000000000000000000000000000000000000000000000000000000000000001' ] } }
以太坊系列之十六: 使用golang与智能合约进行交互 以太坊系列之十六: 使用golang与智能合约进行交互 此例子的目录结构 token contract 智能合约的golang wrapper 部署合约 1.账户问题 2. 连接到geth 3. 部署合约 4. 测试部署结果 5. 等待成功部署到区块链上 golang 查询合约 调用合约 1. 直接构造合约 2. 创建账户 3.进行转账(函数调用) 4. 等待tx完成 5.通过remix来查询结果 官方提供的使用web3来进行智能合约的部署,调用等,实际上使用go也是可以的,这样更接近geth源码,更多的库可以使用. 此例子的目录结构 方便大家对照使用 我是在windows下进行的,在linux以及mac下都差不多,只需要更改里面的ipc地址即可 token contract 这是官方提供的一个智能合约的例子,比较简单,是一个典型的基于智能合约的代币.代码位于:token源码. 智能合约的golang wrapper go直接和智能合约交互,有很多琐碎的细节需要照顾到,比较麻烦.以太坊专门为我们提供了一个abigen的工具,他可以根据sol或者abi文件生成 特定语言的封装,方便进行交互,支持golang,objc,java三种语言.abigen --sol token.sol --pkg mytoken --out token.gotoken.go地址 可以看到里面把合约里面所有导出的函数都进行了封装,方便调用. 部署合约 使用go进行部署合约思路上和web3都差不多,首先需要启动geth,然后通过我们的程序通过ipc连接到geth进行操作. 直接上代码,然后解释 package main import ( "fmt" "log" "math/big" "strings" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/ethclient" "token-contract/mytoken" ) const key = ` { "address": "1a9ec3b0b807464e6d3398a59d6b0a369bf422fa", "crypto": { "cipher": "aes-128-ctr", "ciphertext": "a471054846fb03e3e271339204420806334d1f09d6da40605a1a152e0d8e35f3", "cipherparams": { "iv": "44c5095dc698392c55a65aae46e0b5d9" }, "kdf": "scrypt", "kdfparams": { "dklen": 32, "n": 262144, "p": 1, "r": 8, "salt": "e0a5fbaecaa3e75e20bccf61ee175141f3597d3b1bae6a28fe09f3507e63545e" }, "mac": "cb3f62975cf6e7dfb454c2973bdd4a59f87262956d5534cdc87fb35703364043" }, "id": "e08301fb-a263-4643-9c2b-d28959f66d6a", "version": 3 } ` func main() { // Create an IPC based RPC connection to a remote node and an authorized transactor conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc") if err != nil { log.Fatalf("Failed to connect to the Ethereum client: %v", err) } auth, err := bind.NewTransactor(strings.NewReader(key), "123") if err != nil { log.Fatalf("Failed to create authorized transactor: %v", err) } // Deploy a new awesome contract for the binding demo address, tx, token, err := mytoken.DeployMyToken(auth, conn, big.NewInt(9651), "Contracts in Go!!!", 0, "Go!") if err != nil { log.Fatalf("Failed to deploy new token contract: %v", err) } fmt.Printf("Contract pending deploy: 0x%x\n", address) fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash()) startTime := time.Now() fmt.Printf("TX start @:%s", time.Now()) ctx := context.Background() addressAfterMined, err := bind.WaitDeployed(ctx, conn, tx) if err != nil { log.Fatalf("failed to deploy contact when mining :%v", err) } fmt.Printf("tx mining take time:%s\n", time.Now().Sub(startTime)) if bytes.Compare(address.Bytes(), addressAfterMined.Bytes()) != 0 { log.Fatalf("mined address :%s,before mined address:%s", addressAfterMined, address) } name, err := token.Name(&bind.CallOpts{Pending: true}) if err != nil { log.Fatalf("Failed to retrieve pending name: %v", err) } fmt.Println("Pending name:", name) } 1.账户问题 部署合约是需要有以太坊账户的,账户一般位于/home/xxx/.eth/geth/keystore 目录里面,找到一个账户,然后把内容直接粘贴到key里面即可. 因为部署合约是要消耗以太币的,所以必须保证里面有以太币,并且在`bind.NewTransactor(strings.NewReader(key), "123")`时,还需提供密码. 2. 连接到geth `ethclient.Dial("\\\\.\\pipe\\geth.ipc")`就是连接到本地的geth,你可以通过http等通道,要是使用http通道,记得geth启动的时候要加上 `--rpcapi "eth,admin,web3,net,debug" `,否则很多rpc api是无法使用的 3. 部署合约 真正的部署合约反而是比较简单,因为有了token的golang封装,就像直接调用构造函数一样. 只不过多了两个参数,第一个是auth,也就是账户的封装; 第二个是ethclient的连接. 4. 测试部署结果 在testrpc或者其他模拟区块链上,因为合约部署不需要花时间,所以`name, err := token.Name(&bind.CallOpts{Pending: true})`是可以获取到name的, 但是在真实的区块链上有一个比较大的延时,所以运行结果会是: Contract pending deploy: 0xa9b61a3cc7cc1810e133174caa7ead7ef909d701 Transaction waiting to be mined: 0xf832802f6f262677f02eca761ffe65ae21bbe41e983ceeb6cf645166073f4eb5 TX start @:2017-09-04 11:13:57.217 +0800 CSTtx mining take time:34.009s Pending name: Contracts in Go!!! 5. 等待成功部署到区块链上 这里的成功可能不是真正的成功,大家都知道区块链稳定下来要等至少12个周期.不过通过`bind.WaitDeployed` 基本上可以确定该合约已经进入了区块链,并且可以在上面进行操作了. golang 查询合约 前一个例子中我们借助remix查询到已经到账了,实际上golang完全可以做到,并且做起来也很简单. 先看代码,再做解释. func main() { // Create an IPC based RPC connection to a remote node and instantiate a contract binding conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc") if err != nil { log.Fatalf("Failed to connect to the Ethereum client: %v", err) } token, err := mytoken.NewMyToken(common.HexToAddress("0x5e300171d7dc10e43f959877dba98a44df5d1466"), conn) if err != nil { log.Fatalf("Failed to instantiate a Token contract: %v", err) } contractName, err := token.Name(nil) if err != nil { log.Fatalf("query name err:%v", err) } fmt.Printf("MyToken Name is:%s\n", contractName) balance, err := token.BalanceOf(nil, common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401")) if err != nil { log.Fatalf("query balance error:%v", err) } fmt.Printf("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401's balance is %s\n", balance) } 运行结果: MyToken Name is:Contracts in Go!!! 0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401's balance is 387 token.Name,token.BalanceOf就是读取合约上的数据,因为这些操作并不会修改合约的状态,所以不会发起tx,也不需要auth. 读取合约的第一个参数是bind.CallOpts,定义如下: // CallOpts is the collection of options to fine tune a contract call request. type CallOpts struct { Pending bool // Whether to operate on the pending state or the last known one From common.Address // Optional the sender address, otherwise the first account is used Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } 大多数时候直接使用nil即可,我们不需要特殊指定什么. 调用合约 这里说调用实际上指的是要发生tx,这里举的例子就是token中进行转账操作,因为这个操作修改了合约的状态,所以它必须是一个tx(事务). 先看完整的例子 func main() { // Create an IPC based RPC connection to a remote node and instantiate a contract binding conn, err := ethclient.Dial("\\\\.\\pipe\\geth.ipc") if err != nil { log.Fatalf("Failed to connect to the Ethereum client: %v", err) } token, err := mytoken.NewMyToken(common.HexToAddress("0xa9b61a3cc7cc1810e133174caa7ead7ef909d701"), conn) if err != nil { log.Fatalf("Failed to instantiate a Token contract: %v", err) } toAddress := common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401") val, _ := token.BalanceOf(nil, toAddress) fmt.Printf("before transfer :%s\n", val) // Create an authorized transactor and spend 1 unicorn auth, err := bind.NewTransactor(strings.NewReader(key), "123") if err != nil { log.Fatalf("Failed to create authorized transactor: %v", err) } tx, err := token.Transfer(auth, toAddress, big.NewInt(387)) if err != nil { log.Fatalf("Failed to request token transfer: %v", err) } ctx := context.Background() receipt, err := bind.WaitMined(ctx, conn, tx) if err != nil { log.Fatalf("tx mining error:%v\n", err) } val, _ = token.BalanceOf(nil, toAddress) fmt.Printf("after transfere:%s\n", val) fmt.Printf("tx is :%s\n", tx) fmt.Printf("receipt is :%s\n", receipt) } 执行结果: before transfer :0 after transfere:387 tx is : TX(3028e06dbe037731f05c7c73c4694080df72460cf39a70bdb8df76e771800958) Contract: false From: 1a9ec3b0b807464e6d3398a59d6b0a369bf422fa To: a9b61a3cc7cc1810e133174caa7ead7ef909d701 Nonce: 29 GasPrice: 0x430e23400 GasLimit 0x8e73 Value: 0x0 Data: 0xa9059cbb0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce84010000000000000000000000000000000000000000000000000000000000000183 V: 0x1b R: 0xbb57f25039d5e33c1f74607f4d1733cd77ecf99dc6ffff5a7ac90404f6208ea2 S: 0x1e6685f69d51654d30cae14207f74f6858a9ffb9ccc5f1d3d9c852027d49f6c3 Hex: f8a91d850430e23400828e7394a9b61a3cc7cc1810e133174caa7ead7ef909d70180b844a9059cbb0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce840100000000000000000000000000000000000000000000000000000000000001831ba0bb57f25039d5e33c1f74607f4d1733cd77ecf99dc6ffff5a7ac90404f6208ea2a01e6685f69d51654d30cae14207f74f6858a9ffb9ccc5f1d3d9c852027d49f6c3 receipt is :receipt{med=5c0564d6b6568328a4407dfd86da58c1a8d26b38f93cbbd2b8c7cca13b3a792b cgas=36466 bloom=00000000000000000000000000000000001000000000000000000000000000000000000000010000000000000010000000000000000000000000000000200000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000008000000000000000000000000000000000020000000000000000000000000000000000400000000000000040000000000000 logs=[log: a9b61a3cc7cc1810e133174caa7ead7ef909d701 [ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef 0000000000000000000000001a9ec3b0b807464e6d3398a59d6b0a369bf422fa 0000000000000000000000008c1b2e9e838e2bf510ec7ff49cc607b718ce8401] 0000000000000000000000000000000000000000000000000000000000000183 3028e06dbe037731f05c7c73c4694080df72460cf39a70bdb8df76e771800958 0 ccb6f7f26ddcb2d1438f98f51046e3115b8eb27cfab9ffcbc3bd259b68e73d11 0]} 首先同样要连接到geth,然后才能进行后续操作. 1. 直接构造合约 因为合约已经部署到区块链上了,我们直接基于地址构造合约就可以了. `token, err := mytoken.NewMyToken(common.HexToAddress("0x5e300171d7dc10e43f959877dba98a44df5d1466"), conn)` 这次我们不需要auth,因为这个操作实际上是仅读取区块链上的内容. 2. 创建账户 进行转账修改了合约的状态,必须需要auth,和上次一样创建即可. 3.进行转账(函数调用) 转账操作`token.Transfer(auth, common.HexToAddress("0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401"), big.NewInt(387))`,是要给账户 0x8c1b2e9e838e2bf510ec7ff49cc607b718ce8401转387个代币,这实际上是调用了sol中的 /* Send coins */ function transfer(address _to, uint256 _value) { if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows balanceOf[msg.sender] -= _value; // Subtract from the sender balanceOf[_to] += _value; // Add the same to the recipient Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place } 可以看出,和在sol中是差不多的,只不过多了一个auth,他实际上起到的作用就是要对这个事务进行签名. 4. 等待tx完成 可以通过bind.WaitMined来等待事务真正被矿工处理完毕,这时候通过条用BalanceOf就可以查询到转账前后的数值变化. 5.通过remix来查询结果 转账是一个tx,必须等待矿工挖矿,提交到区块链中以后才能查询到结果,除了在程序中等待一段时间进行查询,也可以自己等待一会儿,然后直接在remix中进行查询了. #### (1) 打开http://ethereum.github.io/browser-solidity/#version=soljson-v0.4.16+commit.d7661dd9.js #### (2) 粘贴token.sol的内容 #### (3) 切换到Web3 Provider #### (4) 使用At Address创建合约 这是因为我们合约已经创建完毕了,通过指定地址就可以直接与我们的合约进行交互了 然后可以调用balanceof来查询已经到账了.截图如下:
以太坊数据库中都存了什么 以太坊使用的数据库是一个NOSQL数据库,是谷歌提供的开源数据leveldb. 这里尝试通过分析以太坊数据库存储了什么来分析以太坊可能为我们提供哪些关于区块链的API. 存储内容 NOSQL是一个key-value数据库,可以当做一个磁盘上的map数据结构.有以下key-value的映射. block number+block hash->block header block number+block hash->block difficulity block number->block hash block hash->block number block number+block hash-> block body (transactions 默克尔树) block number+block hash-> block receipts (交易执行结果) tx hash->tx receipts hash->receipts 可以进行的查询 看看web3提供的关于区块链的api就大致晓得了,可以进行哪些查询. 不可以进行的查询 比如我想在区块链上查询某个地址的所有交易,这是不行的,因为数据库中没有直接保存.想要获得,必须遍历整个数据库.
solidity中的特殊函数 括号里面有类型和名字的是参数,只有类型是返回值. block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks block.coinbase (address): current block miner’s address block.difficulty (uint): current block difficulty block.gaslimit (uint): current block gaslimit block.number (uint): current block number block.timestamp (uint): current block timestamp msg.data (bytes): complete calldata msg.gas (uint): remaining gas msg.sender (address): sender of the message (current call) msg.value (uint): number of wei sent with the message now (uint): current block timestamp (alias for block.timestamp) tx.gasprice (uint): gas price of the transaction 6.4. Solidity in Depth 99Solidity Documentation, 0.4.10 tx.origin (address): sender of the transaction (full call chain) revert(): abort execution and revert state changes keccak256(...) returns (bytes32): compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments sha3(...) returns (bytes32): an alias to keccak256() sha256(...) returns (bytes32): compute the SHA-256 hash of the (tightly packed) arguments ripemd160(...) returns (bytes20): compute the RIPEMD-160 hash of the (tightly packed) arguments ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address): recover address associated with the public key from elliptic curve signature, return zero on error addmod(uint x, uint y, uint k) returns (uint): compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256 mulmod(uint x, uint y, uint k) returns (uint): compute (x * y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2**256 this (current contract’s type): the current contract, explicitly convertible to address super: the contract one level higher in the inheritance hierarchy selfdestruct(address recipient): destroy the current contract, sending its funds to the given address .balance (uint256): balance of the Address in Wei .send(uint256 amount) returns (bool): send given amount of Wei to Address, returns false on failure .transfer(uint256 amount): send given amount of Wei to Address, throws on failure
evm指令集手册 Opcodes 结果列为"-"表示没有运算结果(不会在栈上产生值),为"*"是特殊情况,其他都表示运算产生唯一值,并放在栈顶. mem[a...b] 表示内存中a到b(不包含b)个字节 storage[p] 表示从p开始的32个字节 谨记evm虚拟机的word(字)是256位32字节 操作码 结果 注释 stop - stop execution, identical to return(0,0) add(x, y) x + y sub(x, y) x - y mul(x, y) x * y div(x, y) x / y sdiv(x, y) x / y, for signed numbers in two’s complement mod(x, y) x % y smod(x, y) x % y, for signed numbers in two’s complement exp(x, y) x to the power of y not(x) ~x, every bit of x is negated lt(x, y) 1 if x < y, 0 otherwise gt(x, y) 1 if x > y, 0 otherwise slt(x, y) 1 if x < y, 0 otherwise, for signed numbers in two’s complement sgt(x, y) 1 if x > y, 0 otherwise, for signed numbers in two’s complement eq(x, y) 1 if x == y, 0 otherwise iszero(x) 1 if x == 0, 0 otherwise and(x, y) bitwise and of x and y or(x, y) bitwise or of x and y xor(x, y) bitwise xor of x and y byte(n, x) nth byte of x, where the most significant byte is the 0th byte addmod(x, y, m) (x + y) % m with arbitrary precision arithmetics mulmod(x, y, m) (x * y) % m with arbitrary precision arithmetics signextend(i, x) sign extend from (i*8+7)th bit counting from least significant keccak256(p, n) keccak(mem[p...(p+n))) sha3(p, n) keccak(mem[p...(p+n))) jump(label) - jump to label / code position jumpi(label, cond) - jump to label if cond is nonzero pc current position in code pop(x) - remove the element pushed by x dup1 ... dup16 copy ith stack slot to the top (counting from top) swap1 ... swap16 * swap topmost and ith stack slot below it mload(p) mem[p..(p+32)) mstore(p, v) - mem[p..(p+32)) := v mstore8(p, v) - mem[p] := v & 0xff - only modifies a single byte sload(p) storage[p] sstore(p, v) - storage[p] := v msize size of memory, i.e. largest accessed memory index gas gas still available to execution address address of the current contract / execution context balance(a) wei balance at address a caller call sender (excluding delegatecall) callvalue wei sent together with the current call calldataload(p) call data starting from position p (32 bytes) calldatasize size of call data in bytes calldatacopy(t, f, s) - copy s bytes from calldata at position f to mem at position t codesize size of the code of the current contract / execution context codecopy(t, f, s) - copy s bytes from code at position f to mem at position t extcodesize(a) size of the code at address a extcodecopy(a, t, f, s) - like codecopy(t, f, s) but take code at address a returndatasize size of the last returndata returndatacopy(t, f, s) - copy s bytes from returndata at position f to mem at position t create(v, p, s) create new contract with code mem[p..(p+s)) and send v wei and return the new address create2(v, n, p, s) create new contract with code mem[p..(p+s)) at address keccak256( . n . keccak256(mem[p..(p+s))) and send v wei and return the new address call(g, a, v, in, insize, out, outsize) call contract at address a with input mem[in..(in+insize)) providing g gas and v wei and output area mem[out..(out+outsize)) returning 0 on error (eg. out of gas) and 1 on success callcode(g, a, v, in, insize, out, outsize) identical to call but only use the code from a and stay in the context of the current contract otherwise delegatecall(g, a, in, insize, out, outsize) identical to callcode but also keep caller and callvalue staticcall(g, a, in, insize, out, outsize) identical to call(g, a, 0, in, insize, out, outsize) but do not allow state modifications return(p, s) - end execution, return data mem[p..(p+s)) revert(p, s) - end execution, revert state changes, return data mem[p..(p+s)) selfdestruct(a) - end execution, destroy current contract and send funds to a invalid - end execution with invalid instruction log0(p, s) - log without topics and data mem[p..(p+s)) log1(p, s, t1) - log with topic t1 and data mem[p..(p+s)) log2(p, s, t1, t2) - log with topics t1, t2 and data mem[p..(p+s)) log3(p, s, t1, t2, t3) - log with topics t1, t2, t3 and data mem[p..(p+s)) log4(p, s, t1, t2, t3, t4) - log with topics t1, t2, t3, t4 and data mem[p..(p+s)) origin transaction sender gasprice gas price of the transaction blockhash(b) hash of block nr b - only for last 256 blocks excluding current coinbase current mining beneficiary timestamp timestamp of the current block in seconds since the epoch number current block number difficulty difficulty of the current block gaslimit block gas limit of the current block 其中call,callcode,delegatecall,staticcall非常重要,要搞清楚,才能理解evm的执行模型.
solidity中变量的存储 变量存储主要分为两个区域,一个是storage(对应指定是SLOAD,SSTORE),一个是Memory(MLOAD,MSTORE), 这和普通编程语言的内存模型是不一样的. storage就像硬盘是长期存储,memory调用返回就没了. 默认情况: 函数变量以及返回值都是存储在memory 其他变量(函数的局部变量)都是storage 强制情况(也就是不能通过在声明的时候指定memory或storage): * 外部函数调用时的参数真是calldata(和memory差不多) * 合约的成员变量(state variable) Forced data location: parameters (not return) of external functions: calldata state variables: storage Default data location: parameters (also return) of functions: memory all other local variables: storage 存储在memory和storage中的变量在操作上也是有区别的,比如memory中的数组不能改变大小,而storage中的就可以.比如: uint[] memory a = new uint[](7); a.length=8 //这是不允许的 uint[] storage sa; sa.push(32); //只有storage可以push,因为他的大小不固定,而memory中的一旦声明就固定下来了.
一步一步使用remix开发智能合约 最新版的remix(2017-8-3)只能使用在线开发了,已经没有离线版本了,并且好像在线版本要FQ才能访问(自行解决). 1.打开remix 注意地址如果是https开头的,要换成http,否则就没法访问本地的http rpc了.remix地址,打开以后会看到一个Ballot.sol文件,这是一个测试投票程序 2.打开本地的geth 本地的geth记得一定要开启rpc,下面是我自己的参数 ggeth.exe --datadir=d:/privnet --networkid 89120348581 --rpc --rpccorsdomain "*" --rpcapi "eth,admin,web3,net,debug" --rpc 表示启动http rpc通道,否则只有本地ipc通道 --rpccorsdomain "*" 是允许跨域访问,否则http://ethereum.github.io/在对http://localhost:8545进行 http rpc通信时会被浏览器阻塞 --rpcapi "eth,admin,web3,net,debug" 由于安全起见,geth启动http rpc的时候是禁止了admin,debug等模块的,而我们想要通过remix来调试solidity就必须给与这些权限. 3. 配置remix remix的Contract标签下的Environment不能是默认的Javascript VM,这样是没法运行智能合约的,必须选择Web3 Provider,另一个Injected Web3在我电脑上点了没有任何反应,不晓得什么原因. 具体如下图: 这时候浏览器会提示你链接http://localhost:8545 , 这时候确保你的geth已经启动,并且添加了--rpc参数,这里如果访问的是https://ethereum.github.io/browser-solidity, 还是会失败的,因为浏览器会禁止在https模式下去访问http地址. 如果成功,那么你就可以在Account下拉框中看到你自己的账户信息,如果没有看到那就是上面的某个步骤出错了. 4. 部署合约 点击Create按钮就可以创建合约了. 这时候最常见的错误有两个,一是账户没有解锁,那你就在geth console中调用personal.unlockAccount来解锁;二是余额不足. 当然还有一个就是如果你是在私链上,记得要启动miner,否则会一直不成功. 成功以后,就会看到下面的信息 会告诉你在花费了多少gas,合约的地址等信息. 还有就是下面会出现合约可以调用的函数,这时候你就可以切换上面的账户进行合约调用了.和普通的函数调用差不多. 就是要记得,这个是发生在区块链上的,每次调用都会花费以太币. 5.调试合约 调试也就是分步执行某个事务,这里的事务实际上就是一次合约调用,我这里使用的合约如下: pragma solidity ^0.4.0; contract Donation { address owner; event fundMoved(address _to, uint _amount); modifier onlyowner { if (msg.sender == owner) _; } address[] _giver; uint[] _values; function Donation() { owner = msg.sender; } function donate() payable { addGiver(msg.value); } function moveFund(address _to, uint _amount) onlyowner { if (_amount <= this.balance) { if (_to.send(this.balance)) { fundMoved(_to, _amount); } else { throw; } } else { throw; } } function addGiver(uint _amount) internal { _giver.push(msg.sender); _values.push(_amount); } } create 成功以后会出现两个调用按钮,donate和moveFund两个外部可调用函数. 其中donate是没有参数的,moveFund要有两个参数. 点击donate,会在区块链上执行该调用,结果如下: Result: { "blockHash": "0x05ff0a4b8680255fc2f8fd270ff29fc2658a4868bd4a834367e5292aa8c91235", "blockNumber": 4471, "contractAddress": null, "cumulativeGasUsed": 88115, "from": "0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa", "gasUsed": 88115, "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "root": "0xf50ef054ab10044cb0ae253121abd8a971abaeb0b69d29bbf5d3ef97652292d7", "to": "0xf77165817ad6e07cf32b16b2d6e64e3c85ca7ffc", "transactionHash": "0xdf4d91c207cf8d7acee3c64add6a994039cf77d27e07e85633e7953e35e542e6", "transactionIndex": 0 } 可以看到该事务发生在4471区块上,在该区块中的事务索引是0(因为是私链,肯定只会有这一个事务). 有了这些信息,我们就可以去调试这个调用 切换到Debugger模块: 如果按钮下面有一个The method debug_traceTransaction does not exist/is not available,那是因为geth启动的时候没有添加参数--rpcapi "eth,admin,web3,net,debug" 这时候我们就可以在代码中设置好断点,然后启动,程序就会停在断点处,我们可以在右侧的Solidity State中看到变量的变化.如果想要比较好的调试,还是需要懂一些evm的指令. 否则这样的调试感觉也意义不大,和js中按照语句调试不一样,这里主要是针对指令的调试,一句简单的_giver.push(msg.sender);需要十几条指令. 当然以后随着remix的发展,调试应该会做的越来越好.
从零一步一步搭建以太坊私有链 我会说明一步一步怎么做,同时说明可能的注意事项。 1.下载geth 这一步网上很多,就不细说了,windows的,linux的,mac都有。 2. 创建genesis.json 关于genesis文件的介绍,在此处:http://blog.csdn.net/sportshark/article/details/51855007有详细说明,自行查阅。 我的genesis.json如下{ "config": { "chainId": 89120348581, "homesteadBlock": 0, "eip155Block": 0, "eip158Block": 0 }, "difficulty": "20", "gasLimit": "2100000", "alloc": { "0x1a9ec3b0b807464e6d3398a59d6b0a369bf422fa": { "balance": "30000000000000000000000" }, "0x33df901abc22dcb7f33c2a77ad43cc98fbfa0790": { "balance": "40000000000000000000000" } } } 我这里预先给自己的测试账号分配了一些以太币,这样就不用挖矿了。 3.初始化区块链 这里需要注意的是,如果你要有多台电脑,那么每台电脑都要执行一遍下面的操作,也不能图省事,复制geth目录。geth --datadir ~/eth/privnet/ init genesis.json,我设定的私有链主目录是~/eth/privnet,你可以根据自己的情况修改 4.节点运行 节点之间能够联通,首先要保证网络是通的,测试网络是否畅通,可以通过telnet ip 30303来测试,有时候由于防火墙或者nat等原因,有些节点之间是没法直接通信的. 在各个节点上运行下面命令geth --datadir ~/eth/privnet --networkid 89120348581 这时候各个几点已经独立运行起来了,但是他们是没有连通的,因为他们的networkid,和创世块和以太坊网络上的都不一样,所以导致在p2p网络认证过程中会失败. 5.连接节点 在各自节点上运行geth attach来添加节点 各自节点信息可以通过admin.nodeInfo来获取,获取以后再其他节点通过admin.addPeer('enode://69e4026e61198088b9bdc353a7e0c924481b1835b35acbf298066634bf47406655516f251933992d3ae2d2752048c0df5656981d4113faa92bb0302243a62eea@xx.xx.xx.xx:30303') 来添加, 还有一种方式可以通过在运行参数中直接指定 如下:geth.exe --datadir=d:/privnet --networkid 89120348581 --bootnodes enode://69e4026e61198088b9bdc353a7e0c924481b1835b35acbf298066634bf47406655516f251933992d3ae2d2752048c0df5656981d4113faa92bb0302243a62eea@xx.xx.xx.xx:30303 如果有多个node,用逗号分隔即可. 6.注意事项 1) init genesis.json必须每个节点都运行一遍,否则添加节点的时候因为创世块不匹配,会失败,我就是在这里耽误了很久. 2) 节点之间单向网络通就可以,也就是在nat环境下A可以直接访问B,那就用A添加B 3) 后续转账等操作,必须启动挖矿,怎么挖矿,很简单,就是在启动的时候加上--mine参数就可以了. 7 加入我的私有链 1) 将上面的genesis.json内容保存为c:\privnet\genesis.json 2) 运行命令geth --datadir c:\privnet init genesis.json 3) 启动节点geth.exe --datadir=c:/privnet --networkid 89120348581 --bootnodes enode://69e4026e61198088b9bdc353a7e0c924481b1835b35acbf298066634bf47406655516f251933992d3ae2d2752048c0df5656981d4113faa92bb0302243a62eea@123.206.176.142:30303 4) 特别注意,genesis.json要一字不差,否则无法加入我的私有网络. 在私有链上挖矿很容易,方便自己测试开发.
版本信息: go:1.8.3 windows: win7/64 idea-go-plugin:171.4694.61 在windows下,使用dlv进行调试的时候,如果golang程序引入了c模块,比如常用的sqlite模块,那么在调试的时候一定会发生这个错误: · Could not determine version number: could not find symbol value for runtime.buildVersion· 现在这个问题已经解决了,解决方法就是go build 的时候加入-ldflags="-linkmode internal" ,这样就可以正常调试了。 如果用的是gogland ide,或者jetbrains其他ide,安装了golang插件,那么在Run/Debug Configurations时,可以在Go Tool arguments中加入 ·-ldflags="-linkmode internal"· 具体见下图:
区 块 421133 的 难度 位 数值 是 402990845, 转成 十六进制 是 0x180526FD。 比特 币 的 难度 目标 是以 十六进制 数 的 前 两位 做 指数, 其余 位数 做 系数, 由 下面 公式 计算 出来: target= coefficient × 2^( 8 ×( exponent- 3)) 区 块 421133 的 系数 是 0x0526FD, 指数 是 0x18, 根据 公式 计算 的 结果是: target= 0x0526FD × 2^( 8 ×( 24- 3))= 337661 × 2^ 168 = 0x00000000000000000526FD000000000000000000000000000000000000000000 也就是要求得到的hash值要小于0x00000000000000000526FD000000000000000000000000000000000000000000才符合要求
go内置的fmt.sprintf已经很强大了,但是和spew比起来还是相形见绌,这里来一个例子. import ( "fmt" "github.com/davecgh/go-spew/spew" ) func main() { scs := spew.ConfigState{Indent: "\t"} // Output using the ConfigState instance. v := map[string]int{"one": 1} fmt.Printf("v: %v\n", v) scs.Dump(v) } 对比一下,看看输出有多美: v: map[one:1] (map[string]int) (len=1) { (string) (len=3) "one": (int) 1 }
dial.go阅读手记 dial.go是负责和peer建立连接关系的地方,主要是实现 type dialer interface { /* peers已经有的结点 */ newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task taskDone(task, time.Time) addStatic(*discover.Node) removeStatic(*discover.Node) } // dialstate schedules dials and discovery lookups. // it get's a chance to compute new tasks on every iteration // of the main loop in Server.run. type dialstate struct { maxDynDials int ntab discoverTable netrestrict *netutil.Netlist lookupRunning bool dialing map[discover.NodeID]connFlag //正在创建的连接 lookupBuf []*discover.Node // current discovery lookup results randomNodes []*discover.Node // filled from Table static map[discover.NodeID]*dialTask hist *dialHistory start time.Time // time when the dialer was first used bootnodes []*discover.Node // default dials when there are no peers } 其中最复杂的是newTasks,是建立新的连接,从test代码中可以看出, 要在指定的最大连接数(peers)基之上去创建新的连接 lookupBuf // current discovery lookup results 主要是在结束taskdone的时候添加已经发现的? 在不超过maxDynDials的情况下,首先减去peers已有的连接,然后是static中的任务, 如果过还有富余,富余空间中最多一半(根据实现,可能为0)用ReadRandomNodes填充,剩下的 就用lookupBuf来填充,如果lookbuf中没有有效的任务,那么就创建一个discoverTask, 如果还有空间,就创建一个waitExpireTask 总之newTasks就是在尽可能的情况下,多创建任务.为Server.run scheduleTasks时服务, 保证其能够连接到尽可能多的节点.如果节点数量不够的情况下,还没有dialTask就创建discoverTask,否则就创建一个WaitExpireTask
p2p模块 p2p模块对外暴露了Server关键结构,帮助上层管理复杂的p2p网路,使其集中于Protocol的实现,只关注于数据的传输. Server使用discover模块,在指定的UDP端口管理网络中结点的发现以及维护,discover模块能够直接和临近结点交换各自已知结点信息,从而不断的更新结点网络. Server还是用nat模块来进行TCP端口映射,而nat主要是利用upnp和pmp两个协议,如果没有,那就只能认为指定的ip就是公网ip了. Server要同时在udp和tcp的30303端口和p2p网络进行数据交换,前者是用来维护P2p网络,后者则是各种应用协议真正交换数据的地方.他们使用的格式是Msg,具体见 p2p_server. 关于Server以及Protocol如何使用可以见 Peer-to-Peer 虽然不是很详细,但是已经基本上说明了Server以及Protocol如何使用,感兴趣可以基于这个p2p网络实现自己的协议,可以完全与以太坊应用没关系.
p2p的nat模块 该模块相对比较简单,因为nat的真正实现并不在此模块,主要是使用了第三方的nat-upnp和nat-pmp来实现真正的穿透(端口映射). 对外公布的接口 ```go // An implementation of nat.Interface can map local ports to ports // accessible from the Internet. type Interface interface { // These methods manage a mapping between a port on the local // machine to a port that can be connected to from the internet. // // protocol is "UDP" or "TCP". Some implementations allow setting // a display name for the mapping. The mapping may be removed by // the gateway when its lifetime ends. AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error DeleteMapping(protocol string, extport, intport int) error // This method should return the external (Internet-facing) // address of the gateway device. ExternalIP() (net.IP, error) // Should return name of the method. This is used for logging. String() string } ``` 主要有三个关键函数一个是添加映射,一个是删除映射,另一个是获取外部IP.使用起来非常直观,我们这里看一个例子,nat.go中的Map函数中, m.AddMapping(protocol, extport, intport, name, mapTimeout); 我看ethereum使用的时候,extport和intport都一样. Interface总共有四个实现分别是upnp,pmp,extIP和startautodisc,从名字可以看出这四个实现都没有暴露给调用者,也就是内部使用,其中extIP是在本机IP就是公网IP的情况下使用,也就是无需端口映射的时候使用.upnp和pmp是对两种端口映射协议的使用封装,而startautodisc则是对这两者的再次封装统一. 那么nat模块怎么使用呢,用起来其实很简单.下面是示例代码,不完整. //获取nat实例, var m nat.Interface=nat.Any() //然后就可以使用AddMapping等操作了, //nat还提供了更方便的函数Map来保持端口映射 //用法如下 来自server.go nat.Map(srv.NAT, srv.quit, "tcp", laddr.Port, laddr.Port, "ethereum p2p")
使用atomic来避免lock 在程序中为了互斥,难免要用锁,有些时候可以通过使用atomic来避免锁, 从而更高效. 下面给出一个以太坊中的例子,就是MsgPipeRW,从名字Pipe可以看出, 他实际上就是一个pipe,相比大家对pipe已经比较熟悉了,我就不多解释了. type MsgPipeRW struct { w chan<- Msg r <-chan Msg closing chan struct{} closed *int32 } //创建一个MsgPipeRw func MsgPipe() (*MsgPipeRW, *MsgPipeRW) { var ( c1, c2 = make(chan Msg), make(chan Msg) closing = make(chan struct{}) closed = new(int32) rw1 = &MsgPipeRW{c1, c2, closing, closed} rw2 = &MsgPipeRW{c2, c1, closing, closed} ) return rw1, rw2 } pipe就像水管一样,这里MsgPipe创建了两根水管,可以自由双向流动,rw1写,rw2就可以 读到,rw2写,rw1就可以读到.原理也很简单,因为rw1写和rw2操作的是同一个chan Msg,反之亦然. 关键是这里的closed,可以想想rw1,rw2很有可能在不同的goroutine发生读写关闭等操作, 这时候要同时访问closed这个变量,难免会发生冲突,我们看看如何避免. closed如果为0表示没有关闭,1表示已经关闭,就不应该再进行读写了. // 从pipe中读取一个msg func (p *MsgPipeRW) ReadMsg() (Msg, error) { //这里不能直接*p.closed==0,要使用atomic.LoadInt32来访问 if atomic.LoadInt32(p.closed) == 0 { ... } return Msg{}, ErrPipeClosed } // 写的时候也一样 func (p *MsgPipeRW) WriteMsg(msg Msg) error { if atomic.LoadInt32(p.closed) == 0 { ... } return ErrPipeClosed } 读写消息只是读取互斥变量,没有发生写入,下面来看看close的时候如何写入 func (p *MsgPipeRW) Close() error { if atomic.AddInt32(p.closed, 1) != 1 { //避免锁, // someone else is already closing atomic.StoreInt32(p.closed, 1) // avoid overflow return nil } close(p.closing) return nil } atomic.AddInt32能够避免我们一般这样的写法发生的并发访问. if *p.closed==0 { *p.closed+=1 } 感兴趣的可以修改代码试试,采用*p.closed==0这种方式,会不会造成崩溃,测试代码如下 func TestMsgPipeConcurrentClose(t *testing.T) { rw1, _ := MsgPipe() for i := 0; i < 10; i++ { go rw1.Close() } } atomic看似神奇的避免了锁,实际上这需要处理器的特殊指令支持,尤其是发生在多和处理器上时,atomic指令 会保证对特定地址的锁定. atomic相对于lock的最大优势就是他只是一条特殊指令,不用发生系统上下文切换,我们都知道系统上下文切换 代价要大得多.
转自: http://blog.csdn.net/u010053050/article/details/52388663 http://www.rehack.cn/techshare/devtools/842.html 首先你要升级到win10周年更新版,然后安装linux子系统。 win10的linux子系统给了我们一个将linux的强大的shell命令和windows的流畅界面结合的体验,让我们可以不用在windows上纠结虚拟机运行linux的卡顿,和在linux下缺少win下对应软件的尴尬。但是win10的cmd命令提示符和powershell都不能满足我对字体的一些要求,这让我去寻找更适合的bash客户端。 几经周折发现了一款不错的bash客户端软件 cmder,它有两个版本,一个是自带模拟一些bash命令的版本,一个是简单的mini版本,我们只是用它来连接bash,当然选择mini版的。 在cmder中复制和粘贴文字非常方便,选中文字按下回车键就是复制,鼠标右键就把文字粘贴上去。 cmder的设置复杂到爆。默认设置有些不合我口味的地方。 通过ssh连接服务器的时候,会出现中文字体相互重叠的情况,这时去设置>Main 取消选择 Compress long strings to fit space。 进入vim的时候会发现编辑模式左右箭头不好用了,会提示d,官方文档说要在启动时配置一下,进入 设置>Startup>Startup options 选择Command line,写上bash -cur_console:p, 这样问题解决,同时启动cmder时会直接进入bash。 默认cmder会在启动的时候检查更新并弹出烦人的提示框,我们可以去设置>update>update settings 取消startup前面的勾。 版权声明:
以太坊的crypto模块 该模块分为两个部分一个是实现sha3,一个是实现secp256k1(这也是比特币中使用的签名算法). 需要说明的是secp256k1有两种实现方式,一种是依赖libsecp256k1,需要cgo,另外一种是依赖github.com/btcsuite/btcd,这是一个使用go语言实现的比特币的客户端. sha3模块 这个模块实际上可以认为就是一个功能计算sha3-256,用法也很简单,就是调用crypto中的Keccak256,输出是一个32字节的hash结果 hash := crypto.Keccak256Hash([]byte("hello")) //hash值:4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 secp256k1模块 这个模块比较复杂,如果要细度源码,需要对密码学有比较深入的理解,但是使用起来其实比较简单. 主要就是签名,验证,以及公钥与以太坊地址转换 1.签名 secp256k1的私钥地址长度是32字节256位,公钥地址长度是65字节,而以太坊的地址长度是20字节, //签名 var testPrivHex = "289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032" //1.获取私钥 key, _ := crypto.HexToECDSA(testPrivHex) //2.对message进行hash msg := crypto.Keccak256([]byte("foo")) //3.对hash进行签名,注意签名对象只能是hash,并且长度真是32个字节的hash sig, err := crypto.Sign(msg, key) //sig:d155e94305af7e07dd8c32873e5c03cb95c9e05960ef85be9c07f671da58c73718c19adc397a211aa9e87e519e2038c5a3b658618db335f74f800b8e0cfeef4401 //签名结果长度和公钥长度相同 2.验证 验证签名是否正确,需要公钥,hash(对message进行hash的结果),以及签名. 这里面真正校验的是第三步,也就是公钥是否和我的相同,而不像普通工RSA签名验证一样.当然我们可以封装成和RSA签名验证一样形式的func VerifySignature(pubKey,msg,sig []byte) error //1.从签名中提取公钥 recoveredPub, err := crypto.Ecrecover(msg, sig) //2.将公钥转换为长度为65的字节序列 recoveredPubBytes:=crypto.FromECDSAPub(recoveredPub) //3.校验这个公钥是否和我的公钥一致 if recoveredPubBytes!=myPubKey { ...... } 3.公钥与地址的转换 以太坊中并没有直接拿公钥当做账户地址,而是进行了一个简单的转换,具体来说就是hash(公钥)的后20位,这里的hash算法是sha3-256,可以用一行代码来表示 crypto.Keccak256(pubKey)[12:] 详细的代码在crypto.go中, func PubkeyToAddress(p ecdsa.PublicKey) common.Address { pubBytes := FromECDSAPub(&p) //将pubkey转换为字节序列 return common.BytesToAddress(Keccak256(pubBytes[1:])[12:]) //对字节序列进行hash并去后二十个字节,BytesToAddress实际上就是[32]byte }
有三种解决方式,第一种方式最简单实用 安装新版本wslbridge 这个解决方法最简单,最实用,下载第三方wslbridge,安装即可使用. 这时再进入cmder,运行bash.exe,可以发现上下左右箭头已经可以工作了. 修改启动参数 Solution 1: Default task {bash} Don’t use Old ConEmu Builds! There is no sense to complain on >things changed months ago! Update your installation! ConEmu creates new task for ‘Bash on Windows’ automatically, you may check this by running ConEmu64.exe -basic -run {bash}. Also, you may call Add default tasks… from Tasks page on your existing config. So, just run {bash} task, arrow keys are expected to be working! 因为是cmder包装了一层,不知道怎么修改,放弃 状态栏终端模式修改 我的版本是161206版本,好像没有效果,感兴趣的自己可以试试 Solution 1: Default task {bash} Don’t use Old ConEmu Builds! There is no sense to complain on things changed months ago! Update your installation! ConEmu creates new task for ‘Bash on Windows’ automatically, you may check this by running ConEmu64.exe -basic -run {bash}. Also, you may call Add default tasks… from Tasks page on your existing config. So, just run {bash} task, arrow keys are expected to be working!
在程序中需要测量时间时最好使用monotime.Now()而不是time.Now(),相比之下前者更准确. 来个示例: func main() { var start, elapsed time.Duration start = monotime.Now() time.Sleep(time.Millisecond) elapsed = monotime.Since(start) fmt.Println(elapsed) // Prints: 1.062759ms } 可以看到,精确到毫秒级 ,以太坊中用来度量时间
RLP (递归长度前缀)提供了一种适用于任意二进制数据数组的编码,RLP已经成为以太坊中对对象进行序列化的主要编码方式。RLP的唯一目标就是解决结构体的编码问题;对原子数据类型(比如,字符串,整数型,浮点型)的编码则交给更高层的协议;以太坊中要求数字必须是一个大端字节序的、没有零占位的存储的格式(也就是说,一个整数0和一个空数组是等同的)。 如果想学习go语言中的反射用法,这个包里面倒是有比较完善的学习示例,感兴趣的可以看看. 下面是我写的一个使用示例,演示如何使用rlp这个包. /* rlp包用法 rlp目的是可以将常用的数据结构,uint,string,[]byte,struct,slice,array,big.int等序列化以及反序列化. 要注意的是rlp特别不支持有符号数的序列化 具体用法见下 */ //编码 type TestRlpStruct struct { A uint B string C []byte BigInt *big.Int } //rlp用法 func TestRlp(t *testing.T) { //1.将一个整数数组序列化 arrdata, err := rlp.EncodeToBytes([]uint{32, 28}) fmt.Printf("unuse err:%v\n", err) //fmt.Sprintf("data=%s,err=%v", hex.EncodeToString(arrdata), err) //2.将数组反序列化 var intarray []uint err = rlp.DecodeBytes(arrdata, &intarray) //intarray 应为{32,28} fmt.Printf("intarray=%v\n", intarray) //3.将一个布尔变量序列化到一个writer中 writer := new(bytes.Buffer) err = rlp.Encode(writer, true) //fmt.Sprintf("data=%s,err=%v",hex.EncodeToString(writer.Bytes()),err) //4.将一个布尔变量反序列化 var b bool err = rlp.DecodeBytes(writer.Bytes(), &b) //b:true fmt.Printf("b=%v\n", b) //5.将任意一个struct序列化 //将一个struct序列化到reader中 _, r, err := rlp.EncodeToReader(TestRlpStruct{3, "44", []byte{0x12, 0x32}, big.NewInt(32)}) var teststruct TestRlpStruct err = rlp.Decode(r, &teststruct) //{A:0x3, B:"44", C:[]uint8{0x12, 0x32}, BigInt:32} fmt.Printf("teststruct=%#v\n", teststruct) }
github上有两个package做编码转换,都是基于iconv,用到了cgo,在linux下没有问题,在windows下用,非常麻烦。采用mingw安装libiconv也不行,一直提示找不到libiconv方法。 最终找到一个官方实现(纯go实现):https://code.google.com/p/go/source/checkout?repo=text gbk转utf-8示例: func gbk2utf8(str byte[]) ([]byte, error) { return ioutil.ReadAll(transform.NewReader(bytes.NewReader(str), simplifiedchinese.GBK.NewDecoder())) } 经测试没有任何问题,大家可以方便引用,需要导入的包: "golang.org/x/text/transform""golang.org/x/text/encoding/simplifiedchinese" 如果golang.org下载不下来,可以去github上下载克隆版, go get github.com/zieckey/golang.org 然后将golang.org移动到src目录下即可。
我们日常写代码时,经常自己去做一些优化,其实绝大多数情况都是没必要的,因为编译器比你想的聪明太多! 下面这个例子就是明证, 这个是一个求竭诚的递归写法,很多老手都会告诉你这样写性能低,应该用循环。 int rfact(int x) { int rval; if(x<=1) return 1; rval=rfact(x-1); return rval*x; } 我想大家一般都不会有什么异议,那么来看看生成的代码是什么样子吧! 编译方法 gcc –O2 –S rfact.c 我只贴上关键的汇编代码,看看是不是毁三观! _rfact: movl 4(%esp), %edx movl $1, %eax cmpl $1, %edx jg L3 jmp L2 .p2align 4,,7 L9: movl %ecx, %edx L3: leal -1(%edx), %ecx imull %edx, %eax cmpl $1, %ecx jne L9 L2: rep ret 看到了吧,没有递归,没有递归,没有递归! 编译器帮你转换成循环了。 当然编译不能把所有的递归都转换成循环,但是可以看出大多数的优化真心没必要,只会带来错误, 比如使用右移来代替除法之类的,这些真心out了! 再给你看一个编译器优化的例子,比你想的聪明多了。 int choice3(int x) { return 15*x; } 你觉得这里编译器会用乘法?那你错了!你错了!你错了! _choice3: movl 4(%esp), %edx movl %edx, %eax sall $4, %eax subl %edx, %eax ret 看到了么?乘以16-1,聪明吧。