对我而言,Golang 中的 Context 一直是谜一样的存在,如果你还不了解它,建议阅读「快速掌握 Golang context 包,简单示例」,本文主要讨论一些我曾经的疑问。
如何优化Golang中重复的错误处理
Golang 错误处理最让人头疼的问题就是代码里充斥着「if err != nil」,它们破坏了代码的可读性,本文收集了几个例子,让大家明白如何优化此类问题。
To panic or not to panic
大家都知道 Golang 推荐的错误处理的方式是使用 error,这主要得益于 Golang 方法可以返回多个值,我们可以很自然的用最后一个值来表示是否有错误,这一点是其它很多编程语言所不具备的,不过这多少让那些习惯了 exception 的程序员无所适从,虽然 Golang 没有 exception,但是实际上可以通过 panic/recover 来模拟出类似的效果,于是很多 Gopher 在错误处理的时候开始倾向于直接 panic。
在Golang的HTTP请求中共享数据
首先,我们需要先明确一下问题的描述:本文所要讨论的共享数据可不是指的 cookie、session 之类的概念,它们描述的是在「请求间」共享数据,而我们关注的是在「请求中」共享数据,也就说是,在每个请求中的各个 middleware 和 handler 之间共享数据。
学习Golang的HTTP中间件机制
推导利滚利公式
以太坊地址区分大小写么
以太坊地址区分大小写么?要搞清楚这个问题,我们不妨先在私链上做个实验:
geth> eth.sendTransaction({ from: eth.accounts[0], to: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", value: web3.toWei(1, 'ether') }) geth> eth.sendTransaction({ from: eth.accounts[0], to: "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", value: web3.toWei(1, 'ether') }) geth> eth.getBalance("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") geth> eth.getBalance("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
从实验结果上来看,纯小写地址和纯大写地址实际上是同一个地址,那么是不是由此可以得出以太坊地址不区分大小写呢?我们再看下面这个 remix 上的实验:
pragma solidity ^0.5.0; contract Foo { address bar = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; }
代码运行结果如下图所示:
结果提示错误,并给了一个大小写混合的正确地址:
This looks like an address but has an invalid checksum. Correct checksummed address: “0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa”. If this is not used as an address, please prepend ’00’. For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals
怎么回事?实际上这是因为 EIP-55 规范引入了 checksum 机制。它有什么用处?设想一下,如果你在输入长长的以太坊地址的时候,不小心输错了一个字符,那么很难发现这样的错误,有了 checksum 机制,我们就能在业务程序里校验地址的准确性,从而降低用户输入错误地址的风险,给出一个 golang 代码的例子:
package main import ( "flag" "fmt" "github.com/ethereum/go-ethereum/common" ) var ( address = flag.String("address", "", "address") ) func init() { flag.Parse() } func main() { addressWithChecksum := common.HexToAddress(*address).Hex() if addressWithChecksum != *address { fmt.Println(" valid: " + addressWithChecksum) fmt.Println("invalid: " + *address) } else { fmt.Println("This address is valid.") } }
代码运行结果如下图所示:
如果你没有 Golang 的运行环境,实际上还有更简单的验证方法:通过 Etherscan 来获取带 checksum 的地址,留意地址栏中的地址和下面 Address 旁边的地址:
总结:以太坊地址本身不区分大小写,但是出于安全性的考虑,我们应该尽可能使用符合 EIP-55 规范的地址,当然了,业务程序要有相应的 checksum 校验逻辑才行。
关于以太坊随机数
在以太坊应用中,游戏一直都是热点中的热点,而在游戏中,随机数往往是一个不可或缺的功能,比如骰子游戏中,我们需要通过随机数来控制点数,如果一个游戏有一个好的随机数算法的话,那么既可以保证游戏庄家不被黑,也可以保证玩家不被宰。
如何解密keystore文件
如果你用 geth 创建过账号「geth –datadir /path/to/data account new」,那么多半知道 keystore 文件,它通过一个 password 加密保存着账号的私钥:
如果我想拿到加密前的私钥怎么办?最容易想到的办法是在 MetaMask 中导入账号的时候选择通过 JSON 文件导入的方式,然后再导出私钥。不过这个方法不方便,也无法实现自动化,下面看看如何通过 golang 解密 keystore 文件:
package main import ( "encoding/hex" "flag" "fmt" "io/ioutil" "os" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/crypto" ) var ( file = flag.String("file", "", "file") password = flag.String("password", "", "password") ) func init() { flag.Parse() } func main() { if _, err := os.Stat(*file); os.IsNotExist(err) { flag.Usage() os.Exit(1) } keyjson, err := ioutil.ReadFile(*file) if err != nil { panic(err) } key, err := keystore.DecryptKey(keyjson, *password) if err != nil { panic(err) } address := key.Address.Hex() privateKey := hex.EncodeToString(crypto.FromECDSA(key.PrivateKey)) fmt.Printf("Address:\t%s\nPrivateKey:\t%s\n", address, privateKey, ) }
更新:本文仅为演示,不推荐通过 flag 传递密码,否则可以在 history 中看到。