交互工具
以太坊提供了Geth客户端用于管理API,我们可以在终端输入geth help查看其具体使用方法:
ubuntu@ubuntu:~/geth-linux-amd64$ ./geth --helpNAME:geth - the go-ethereum command line interfaceCopyright 2013-2021 The go-ethereum AuthorsUSAGE:geth [options] [command] [command options] [arguments...]VERSION:1.10.2-stable-97d11b01COMMANDS:account Manage accountsattach Start an interactive JavaScript environment (connect to node)console Start an interactive JavaScript environmentdb Low level database operationsdump Dump a specific block from storagedumpconfig Show configuration valuesdumpgenesis Dumps genesis block JSON configuration to stdoutexport Export blockchain into fileexport-preimages Export the preimage database into an RLP streamimport Import a blockchain fileimport-preimages Import the preimage database from an RLP streaminit Bootstrap and initialize a new genesis blockjs Execute the specified JavaScript fileslicense Display license informationmakecache Generate ethash verification cache (for testing)makedag Generate ethash mining DAG (for testing)removedb Remove blockchain and state databasesshow-deprecated-flags Show flags that have been deprecatedsnapshot A set of commands based on the snapshotversion Print version numbersversion-check Checks (online) whether the current version suffers from any known security vulnerabilitieswallet Manage Ethereum presale walletshelp, h Shows a list of commands or help for one commandETHEREUM OPTIONS:--config value TOML configuration file--datadir value Data directory for the databases and keystore (default: \\\"/home/ubuntu/.ethereum\\\")--datadir.ancient value Data directory for ancient chain segments (default = inside chaindata)--datadir.minfreedisk value Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)--keystore value Directory for the keystore (default = inside the datadir)--usb Enable monitoring and management of USB hardware wallets--pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: \\\"/run/pcscd/pcscd.comm\\\")--networkid value Explicitly set network id (integer)(For testnets: use --ropsten, --rinkeby, --goerli instead) (default: 1)--mainnet Ethereum mainnet--goerli G?rli network: pre-configured proof-of-authority test network--rinkeby Rinkeby network: pre-configured proof-of-authority test network--yolov3 YOLOv3 network: pre-configured proof-of-authority shortlived test network.--ropsten Ropsten network: pre-configured proof-of-work test network--syncmode value Blockchain sync mode (\\\"fast\\\", \\\"full\\\", \\\"snap\\\" or \\\"light\\\") (default: fast)--exitwhensynced Exits after block synchronisation completes--gcmode value Blockchain garbage collection mode (\\\"full\\\", \\\"archive\\\") (default: \\\"full\\\")--txlookuplimit value Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain) (default: 2350000)--ethstats value Reporting URL of a ethstats service (nodename:secret@host:port)--identity value Custom node name--lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength--whitelist value Comma separated block number-to-hash mappings to enforce (<number>=<hash>)LIGHT CLIENT OPTIONS:--light.serve value Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100) (default: 0)--light.ingress value Incoming bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited) (default: 0)--light.egress value Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited) (default: 0)--light.maxpeers value Maximum number of light clients to serve, or light servers to attach to (default: 100)--ulc.servers value List of trusted ultra-light servers--ulc.fraction value Minimum % of trusted ultra-light servers required to announce a new head (default: 75)--ulc.onlyannounce Ultra light server sends announcements only--light.nopruning Disable ancient light chain data pruning--light.nosyncserve Enables serving light clients before syncingDEVELOPER CHAIN OPTIONS:--dev Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled--dev.period value Block period to use in developer mode (0 = mine only if transaction pending) (default: 0)ETHASH OPTIONS:--ethash.cachedir value Directory to store the ethash verification caches (default = inside the datadir)--ethash.cachesinmem value Number of recent ethash caches to keep in memory (16MB each) (default: 2)--ethash.cachesondisk value Number of recent ethash caches to keep on disk (16MB each) (default: 3)--ethash.cacheslockmmap Lock memory maps of recent ethash caches--ethash.dagdir value Directory to store the ethash mining DAGs (default: \\\"/home/ubuntu/.ethash\\\")--ethash.dagsinmem value Number of recent ethash mining DAGs to keep in memory (1+GB each) (default: 1)--ethash.dagsondisk value Number of recent ethash mining DAGs to keep on disk (1+GB each) (default: 2)--ethash.dagslockmmap Lock memory maps for recent ethash mining DAGsTRANSACTION POOL OPTIONS:--txpool.locals value Comma separated accounts to treat as locals (no flush, priority inclusion)--txpool.nolocals Disables price exemptions for locally submitted transactions--txpool.journal value Disk journal for local transaction to survive node restarts (default: \\\"transactions.rlp\\\")--txpool.rejournal value Time interval to regenerate the local transaction journal (default: 1h0m0s)--txpool.pricelimit value Minimum gas price limit to enforce for acceptance into the pool (default: 1)--txpool.pricebump value Price bump percentage to replace an already existing transaction (default: 10)--txpool.accountslots value Minimum number of executable transaction slots guaranteed per account (default: 16)--txpool.globalslots value Maximum number of executable transaction slots for all accounts (default: 4096)--txpool.accountqueue value Maximum number of non-executable transaction slots permitted per account (default: 64)--txpool.globalqueue value Maximum number of non-executable transaction slots for all accounts (default: 1024)--txpool.lifetime value Maximum amount of time non-executable transaction are queued (default: 3h0m0s)PERFORMANCE TUNING OPTIONS:--cache value Megabytes of memory allocated to internal caching (default = 4096 mainnet full node, 128 light mode) (default: 1024)--cache.database value Percentage of cache memory allowance to use for database io (default: 50)--cache.trie value Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode) (default: 15)--cache.trie.journal value Disk journal directory for trie cache to survive node restarts (default: \\\"triecache\\\")--cache.trie.rejournal value Time interval to regenerate the trie cache journal (default: 1h0m0s)--cache.gc value Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode) (default: 25)--cache.snapshot value Percentage of cache memory allowance to use for snapshot caching (default = 10% full mode, 20% archive mode) (default: 10)--cache.noprefetch Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)--cache.preimages Enable recording the SHA3/keccak preimages of trie keysACCOUNT OPTIONS:--unlock value Comma separated list of accounts to unlock--password value Password file to use for non-interactive password input--signer value External signer (url or path to ipc file)--allow-insecure-unlock Allow insecure account unlocking when account-related RPCs are exposed by httpAPI AND CONSOLE OPTIONS:--ipcdisable Disable the IPC-RPC server--ipcpath value Filename for IPC socket/pipe within the datadir (explicit paths escape it)--http Enable the HTTP-RPC server--http.addr value HTTP-RPC server listening interface (default: \\\"localhost\\\")--http.port value HTTP-RPC server listening port (default: 8545)--http.api value API\\\'s offered over the HTTP-RPC interface--http.rpcprefix value HTTP path path prefix on which JSON-RPC is served. Use \\\'/\\\' to serve on all paths.--http.corsdomain value Comma separated list of domains from which to accept cross origin requests (browser enforced)--http.vhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts \\\'*\\\' wildcard. (default: \\\"localhost\\\")--ws Enable the WS-RPC server--ws.addr value WS-RPC server listening interface (default: \\\"localhost\\\")--ws.port value WS-RPC server listening port (default: 8546)--ws.api value API\\\'s offered over the WS-RPC interface--ws.rpcprefix value HTTP path prefix on which JSON-RPC is served. Use \\\'/\\\' to serve on all paths.--ws.origins value Origins from which to accept websockets requests--graphql Enable GraphQL on the HTTP-RPC server. Note that GraphQL can only be started if an HTTP server is started as well.--graphql.corsdomain value Comma separated list of domains from which to accept cross origin requests (browser enforced)--graphql.vhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts \\\'*\\\' wildcard. (default: \\\"localhost\\\")--rpc.gascap value Sets a cap on gas that can be used in eth_call/estimateGas (0=infinite) (default: 25000000)--rpc.txfeecap value Sets a cap on transaction fee (in ether) that can be sent via the RPC APIs (0 = no cap) (default: 1)--rpc.allow-unprotected-txs Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC--jspath loadScript JavaScript root path for loadScript (default: \\\".\\\")--exec value Execute JavaScript statement--preload value Comma separated list of JavaScript files to preload into the consoleNETWORKING OPTIONS:--bootnodes value Comma separated enode URLs for P2P discovery bootstrap--discovery.dns value Sets DNS discovery entry points (use \\\"\\\" to disable DNS)--port value Network listening port (default: 30303)--maxpeers value Maximum number of network peers (network disabled if set to 0) (default: 50)--maxpendpeers value Maximum number of pending connection attempts (defaults used if set to 0) (default: 0)--nat value NAT port mapping mechanism (any|none|upnp|pmp|extip:<IP>) (default: \\\"any\\\")--nodiscover Disables the peer discovery mechanism (manual peer addition)--v5disc Enables the experimental RLPx V5 (Topic Discovery) mechanism--netrestrict value Restricts network communication to the given IP networks (CIDR masks)--nodekey value P2P node key file--nodekeyhex value P2P node key as hex (for testing)MINER OPTIONS:--mine Enable mining--miner.threads value Number of CPU threads to use for mining (default: 0)--miner.notify value Comma separated HTTP URL list to notify of new work packages--miner.notify.full Notify with pending block headers instead of work packages--miner.gasprice value Minimum gas price for mining a transaction (default: 1000000000)--miner.gastarget value Target gas floor for mined blocks (default: 8000000)--miner.gaslimit value Target gas ceiling for mined blocks (default: 8000000)--miner.etherbase value Public address for block mining rewards (default = first account) (default: \\\"0\\\")--miner.extradata value Block extra data set by the miner (default = client version)--miner.recommit value Time interval to recreate the block being mined (default: 3s)--miner.noverify Disable remote sealing verificationGAS PRICE ORACLE OPTIONS:--gpo.blocks value Number of recent blocks to check for gas prices (default: 20)--gpo.percentile value Suggested gas price is the given percentile of a set of recent transaction gas prices (default: 60)--gpo.maxprice value Maximum gas price will be recommended by gpo (default: 500000000000)VIRTUAL MACHINE OPTIONS:--vmdebug Record information useful for VM and contract debugging--vm.evm value External EVM configuration (default = built-in interpreter)--vm.ewasm value External ewasm configuration (default = built-in interpreter)LOGGING AND DEBUGGING OPTIONS:--fakepow Disables proof-of-work verification--nocompaction Disables db compaction after import--verbosity value Logging verbosity: 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 3)--vmodule value Per-module verbosity: comma-separated list of <pattern>=<level> (e.g. eth/*=5,p2p=4)--log.json Format logs with JSON--log.backtrace value Request a stack trace at a specific logging statement (e.g. \\\"block.go:271\\\")--log.debug Prepends log messages with call-site location (file and line number)--pprof Enable the pprof HTTP server--pprof.addr value pprof HTTP server listening interface (default: \\\"127.0.0.1\\\")--pprof.port value pprof HTTP server listening port (default: 6060)--pprof.memprofilerate value Turn on memory profiling with the given rate (default: 524288)--pprof.blockprofilerate value Turn on block profiling with the given rate (default: 0)--pprof.cpuprofile value Write CPU profile to the given file--trace value Write execution trace to the given fileMETRICS AND STATS OPTIONS:--metrics Enable metrics collection and reporting--metrics.expensive Enable expensive metrics collection and reporting--metrics.addr value Enable stand-alone metrics HTTP server listening interface (default: \\\"127.0.0.1\\\")--metrics.port value Metrics HTTP server listening port (default: 6060)--metrics.influxdb Enable metrics export/push to an external InfluxDB database--metrics.influxdb.endpoint value InfluxDB API endpoint to report metrics to (default: \\\"http://localhost:8086\\\")--metrics.influxdb.database value InfluxDB database name to push reported metrics to (default: \\\"geth\\\")--metrics.influxdb.username value Username to authorize access to the database (default: \\\"test\\\")--metrics.influxdb.password value Password to authorize access to the database (default: \\\"test\\\")--metrics.influxdb.tags value Comma-separated InfluxDB tags (key/values) attached to all measurements (default: \\\"host=localhost\\\")ALIASED (deprecated) OPTIONS:--nousb Disables monitoring for and managing USB hardware wallets (deprecated)--rpc Enable the HTTP-RPC server (deprecated and will be removed June 2021, use --http)--rpcaddr value HTTP-RPC server listening interface (deprecated and will be removed June 2021, use --http.addr) (default: \\\"localhost\\\")--rpcport value HTTP-RPC server listening port (deprecated and will be removed June 2021, use --http.port) (default: 8545)--rpccorsdomain value Comma separated list of domains from which to accept cross origin requests (browser enforced) (deprecated and will be removed June 2021, use --http.corsdomain)--rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts \\\'*\\\' wildcard. (deprecated and will be removed June 2021, use --http.vhosts) (default: \\\"localhost\\\")--rpcapi value API\\\'s offered over the HTTP-RPC interface (deprecated and will be removed June 2021, use --http.api)MISC OPTIONS:--snapshot Enables snapshot-database mode (default = enable)--bloomfilter.size value Megabytes of memory allocated to bloom-filter for pruning (default: 2048)--help, -h show help--override.berlin value Manually specify Berlin fork-block, overriding the bundled setting (default: 0)COPYRIGHT:Copyright 2013-2021 The go-ethereum Authorsubuntu@ubuntu:~/geth-linux-amd64$
源码分析
Geth相关的代码文件结构如下所示:
├─geth│ │ accountcmd.go 钱包账户相关│ │ chaincmd.go 链数据相关│ │ config.go 配置文件相关│ │ consolecmd.go console交互模式│ │ dbcmd.go 数据库操作相关│ │ main.go 节点启动主程序│ │ misccmd.go 杂项查询相关│ │ snapshot.go snapshot相关│ │ usage.go 使用模板│ │ version_check.go 安全版本检查
首先我们来看一下Geth的使用说明:
ubuntu@ubuntu:~/geth-linux-amd64$ ./geth --helpNAME:geth - the go-ethereum command line interfaceCopyright 2013-2021 The go-ethereum AuthorsUSAGE:geth [options] [command] [command options] [arguments...]VERSION:1.10.2-stable-97d11b01COMMANDS:account Manage accountsattach Start an interactive JavaScript environment (connect to node)console Start an interactive JavaScript environmentdb Low level database operationsdump Dump a specific block from storagedumpconfig Show configuration valuesdumpgenesis Dumps genesis block JSON configuration to stdoutexport Export blockchain into fileexport-preimages Export the preimage database into an RLP streamimport Import a blockchain fileimport-preimages Import the preimage database from an RLP streaminit Bootstrap and initialize a new genesis blockjs Execute the specified JavaScript fileslicense Display license informationmakecache Generate ethash verification cache (for testing)makedag Generate ethash mining DAG (for testing)removedb Remove blockchain and state databasesshow-deprecated-flags Show flags that have been deprecatedsnapshot A set of commands based on the snapshotversion Print version numbersversion-check Checks (online) whether the current version suffers from any known security vulnerabilitieswallet Manage Ethereum presale walletshelp, h Shows a list of commands or help for one commandETHEREUM OPTIONS:--config value TOML configuration file--datadir value Data directory for the databases and keystore (default: \\\"/home/ubuntu/.ethereum\\\").....COPYRIGHT:Copyright 2013-2021 The go-ethereum Authorsubuntu@ubuntu:~/geth-linux-amd64$
可以看到这里的基本格式为:
geth [options] [command] [command options] [arguments...]
下面来看一下go-ethereum-1.10.2\\\\cmd\\\\geth\\\\main.go文件,在该文件中可以看到有两个比较关键的函数:init函数、main函数,熟悉Go语言的都知道init函数与main函数都是Go语言保留的两个函数,当一个文件中出现init函数与main函数时,go语言会自动安装一定顺序先调用所有保留的init函数,之后再调用main函数,在当前文件中的init函数它是第三方包gopkg.in/urfave/cli.v1的实例,其用法是首先构造一个APP对象,然后通过代码配置app对象的行为,并提供必要的回调函数,在运行的时候直接在main函数里面运行app.Run(os.Args)就行
import (\\\"fmt\\\"\\\"os\\\"\\\"sort\\\"\\\"strconv\\\"\\\"strings\\\"\\\"time\\\"\\\"github.com/ethereum/go-ethereum/accounts\\\"......\\\"gopkg.in/urfave/cli.v1\\\")const (clientIdentifier = \\\"geth\\\" // Client identifier to advertise over the network)var (// Git SHA1 commit hash of the release (set via linker flags)gitCommit = \\\"\\\"gitDate = \\\"\\\"// The app that holds all commands and flags.app = flags.NewApp(gitCommit, gitDate, \\\"the go-ethereum command line interface\\\")// flags that configure the nodenodeFlags = []cli.Flag{utils.IdentityFlag,utils.UnlockedAccountFlag,utils.PasswordFileFlag,utils.BootnodesFlag,utils.DataDirFlag,utils.AncientFlag,utils.MinFreeDiskSpaceFlag,utils.KeyStoreDirFlag,......utils.MinerNotifyFullFlag,configFileFlag,}rpcFlags = []cli.Flag{utils.HTTPEnabledFlag,......utils.AllowUnprotectedTxs,}metricsFlags = []cli.Flag{utils.MetricsEnabledFlag,......utils.MetricsInfluxDBTagsFlag,})func init() {// Initialize the CLI app and start Gethapp.Action = gethapp.HideVersion = true // we have a command to print the versionapp.Copyright = \\\"Copyright 2013-2021 The go-ethereum Authors\\\"app.Commands = []cli.Command{// See chaincmd.go:initCommand,importCommand,exportCommand,importPreimagesCommand,exportPreimagesCommand,removedbCommand,dumpCommand,dumpGenesisCommand,// See accountcmd.go:accountCommand,walletCommand,// See consolecmd.go:consoleCommand,attachCommand,javascriptCommand,// See misccmd.go:makecacheCommand,makedagCommand,versionCommand,versionCheckCommand,licenseCommand,// See config.godumpConfigCommand,// see dbcmd.godbCommand,// See cmd/utils/flags_legacy.goutils.ShowDeprecated,// See snapshot.gosnapshotCommand,}sort.Sort(cli.CommandsByName(app.Commands))app.Flags = append(app.Flags, nodeFlags...)app.Flags = append(app.Flags, rpcFlags...)app.Flags = append(app.Flags, consoleFlags...)app.Flags = append(app.Flags, debug.Flags...)app.Flags = append(app.Flags, metricsFlags...)app.Before = func(ctx *cli.Context) error {return debug.Setup(ctx)}app.After = func(ctx *cli.Context) error {debug.Exit()prompt.Stdin.Close() // Resets terminal mode.return nil}}func main() {if err := app.Run(os.Args); err != nil {fmt.Fprintln(os.Stderr, err)os.Exit(1)}}// prepare manipulates memory cache allowance and setups metric system.// This function should be called before launching devp2p stack.func prepare(ctx *cli.Context) {// If we\\\'re running a known preset, log it for convenience.switch {case ctx.GlobalIsSet(utils.RopstenFlag.Name):log.Info(\\\"Starting Geth on Ropsten testnet...\\\")case ctx.GlobalIsSet(utils.RinkebyFlag.Name):log.Info(\\\"Starting Geth on Rinkeby testnet...\\\")case ctx.GlobalIsSet(utils.GoerliFlag.Name):log.Info(\\\"Starting Geth on G?rli testnet...\\\")case ctx.GlobalIsSet(utils.YoloV3Flag.Name):log.Info(\\\"Starting Geth on YOLOv3 testnet...\\\")case ctx.GlobalIsSet(utils.DeveloperFlag.Name):log.Info(\\\"Starting Geth in ephemeral dev mode...\\\")case !ctx.GlobalIsSet(utils.NetworkIdFlag.Name):log.Info(\\\"Starting Geth on Ethereum mainnet...\\\")}// If we\\\'re a full node on mainnet without --cache specified, bump default cache allowanceif ctx.GlobalString(utils.SyncModeFlag.Name) != \\\"light\\\" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) {// Make sure we\\\'re not on any supported preconfigured testnet eitherif !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) {// Nope, we\\\'re really on mainnet. Bump that cache up!log.Info(\\\"Bumping default cache on mainnet\\\", \\\"provided\\\", ctx.GlobalInt(utils.CacheFlag.Name), \\\"updated\\\", 4096)ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096))}}// If we\\\'re running a light client on any network, drop the cache to some meaningfully low amountif ctx.GlobalString(utils.SyncModeFlag.Name) == \\\"light\\\" && !ctx.GlobalIsSet(utils.CacheFlag.Name) {log.Info(\\\"Dropping default light client cache\\\", \\\"provided\\\", ctx.GlobalInt(utils.CacheFlag.Name), \\\"updated\\\", 128)ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(128))}// Start metrics export if enabledutils.SetupMetrics(ctx)// Start system runtime metrics collectiongo metrics.CollectProcessMetrics(3 * time.Second)}// geth is the main entry point into the system if no special subcommand is ran.// It creates a default node based on the command line arguments and runs it in// blocking mode, waiting for it to be shut down.func geth(ctx *cli.Context) error {if args := ctx.Args(); len(args) > 0 {return fmt.Errorf(\\\"invalid command: %q\\\", args[0])}prepare(ctx)stack, backend := makeFullNode(ctx)defer stack.Close()startNode(ctx, stack, backend)stack.Wait()return nil}
上述代码中的init函数内的app.Action表示如果用户没有输入其他的子命令的情况下,会调用这个字段指向的函数,也就是我们的geth函数,这也是我们上一篇文章中为什么直接说\\”geth\\”是启动节点时的入口,而非main函数的缘故,同时从上面的geth命令格式可以看出这里的主要交互式命令都是\\”command\\”参数,而启动节点的相关参数都是\\”options\\”参数,下面我们以创建账户为例进行源码分析:
Step 1:命令行执行命令如下
./geth account new
Step 2:进入到go-ethereum-1.10.2\\\\cmd\\\\geth\\\\main.go的init函数构造一个APP对象,然后通过代码配置app对象的行为,并提供必要的回调函数
func init() {// Initialize the CLI app and start Gethapp.Action = gethapp.HideVersion = true // we have a command to print the versionapp.Copyright = \\\"Copyright 2013-2021 The go-ethereum Authors\\\"app.Commands = []cli.Command{// See chaincmd.go:initCommand,importCommand,exportCommand,importPreimagesCommand,exportPreimagesCommand,removedbCommand,dumpCommand,dumpGenesisCommand,// See accountcmd.go:accountCommand,walletCommand,// See consolecmd.go:consoleCommand,attachCommand,javascriptCommand,// See misccmd.go:makecacheCommand,makedagCommand,versionCommand,versionCheckCommand,licenseCommand,// See config.godumpConfigCommand,// see dbcmd.godbCommand,// See cmd/utils/flags_legacy.goutils.ShowDeprecated,// See snapshot.gosnapshotCommand,}sort.Sort(cli.CommandsByName(app.Commands))app.Flags = append(app.Flags, nodeFlags...)app.Flags = append(app.Flags, rpcFlags...)app.Flags = append(app.Flags, consoleFlags...)app.Flags = append(app.Flags, debug.Flags...)app.Flags = append(app.Flags, metricsFlags...)app.Before = func(ctx *cli.Context) error {return debug.Setup(ctx)}app.After = func(ctx *cli.Context) error {debug.Exit()prompt.Stdin.Close() // Resets terminal mode.return nil}}
Step 3:之后在main函数里面运行app.Run(os.Args),这里的传入的参数中command为\\”account\\”,其对应的参数为\\”new\\”
func main() {if err := app.Run(os.Args); err != nil {fmt.Fprintln(os.Stderr, err)os.Exit(1)}}
Step 4:之后进入到accountcmd.go解析account命令及其二级子命令new
var (walletCommand = cli.Command{Name: \\\"wallet\\\",Usage: \\\"Manage Ethereum presale wallets\\\",ArgsUsage: \\\"\\\",Category: \\\"ACCOUNT COMMANDS\\\",Description: `geth wallet import /path/to/my/presale.walletwill prompt for your password and imports your ether presale account.It can be used non-interactively with the --password option taking apasswordfile as argument containing the wallet password in plaintext.`,Subcommands: []cli.Command{{Name: \\\"import\\\",Usage: \\\"Import Ethereum presale wallet\\\",ArgsUsage: \\\"<keyFile>\\\",Action: utils.MigrateFlags(importWallet),Category: \\\"ACCOUNT COMMANDS\\\",Flags: []cli.Flag{utils.DataDirFlag,utils.KeyStoreDirFlag,utils.PasswordFileFlag,utils.LightKDFFlag,},Description: `geth wallet [options] /path/to/my/presale.walletwill prompt for your password and imports your ether presale account.It can be used non-interactively with the --password option taking apasswordfile as argument containing the wallet password in plaintext.`,},},}accountCommand = cli.Command{Name: \\\"account\\\",Usage: \\\"Manage accounts\\\",Category: \\\"ACCOUNT COMMANDS\\\",Description: `Manage accounts, list all existing accounts, import a private key into a newaccount, create a new account or update an existing account.It supports interactive mode, when you are prompted for password as well asnon-interactive mode where passwords are supplied via a given password file.Non-interactive mode is only meant for scripted use on test networks or knownsafe environments.Make sure you remember the password you gave when creating a new account (witheither new or import). Without it you are not able to unlock your account.Note that exporting your key in unencrypted format is NOT supported.Keys are stored under <DATADIR>/keystore.It is safe to transfer the entire directory or the individual keys thereinbetween ethereum nodes by simply copying.Make sure you backup your keys regularly.`,Subcommands: []cli.Command{{Name: \\\"list\\\",Usage: \\\"Print summary of existing accounts\\\",Action: utils.MigrateFlags(accountList),Flags: []cli.Flag{utils.DataDirFlag,utils.KeyStoreDirFlag,},Description: `Print a short summary of all accounts`,},{Name: \\\"new\\\",Usage: \\\"Create a new account\\\",Action: utils.MigrateFlags(accountCreate),Flags: []cli.Flag{utils.DataDirFlag,utils.KeyStoreDirFlag,utils.PasswordFileFlag,utils.LightKDFFlag,},Description: `geth account newCreates a new account and prints the address.The account is saved in encrypted format, you are prompted for a password.You must remember this password to unlock your account in the future.For non-interactive use the password can be specified with the --password flag:Note, this is meant to be used for testing only, it is a bad idea to save yourpassword to file or expose in any other way.`,},{Name: \\\"update\\\",Usage: \\\"Update an existing account\\\",Action: utils.MigrateFlags(accountUpdate),ArgsUsage: \\\"<address>\\\",Flags: []cli.Flag{utils.DataDirFlag,utils.KeyStoreDirFlag,utils.LightKDFFlag,},Description: `geth account update <address>Update an existing account.The account is saved in the newest version in encrypted format, you are promptedfor a password to unlock the account and another to save the updated file.This same command can therefore be used to migrate an account of a deprecatedformat to the newest format or change the password for an account.For non-interactive use the password can be specified with the --password flag:geth account update [options] <address>Since only one password can be given, only format update can be performed,changing your password is only possible interactively.`,},{Name: \\\"import\\\",Usage: \\\"Import a private key into a new account\\\",Action: utils.MigrateFlags(accountImport),Flags: []cli.Flag{utils.DataDirFlag,utils.KeyStoreDirFlag,utils.PasswordFileFlag,utils.LightKDFFlag,},ArgsUsage: \\\"<keyFile>\\\",Description: `geth account import <keyfile>Imports an unencrypted private key from <keyfile> and creates a new account.Prints the address.The keyfile is assumed to contain an unencrypted private key in hexadecimal format.The account is saved in encrypted format, you are prompted for a password.You must remember this password to unlock your account in the future.For non-interactive use the password can be specified with the -password flag:geth account import [options] <keyfile>Note:As you can directly copy your encrypted accounts to another ethereum instance,this import mechanism is not needed when you transfer an account betweennodes.`,},},})
找到对应的处理函数accountCreate
// accountCreate creates a new account into the keystore defined by the CLI flags.func accountCreate(ctx *cli.Context) error {cfg := gethConfig{Node: defaultNodeConfig()}// Load config file.if file := ctx.GlobalString(configFileFlag.Name); file != \\\"\\\" {if err := loadConfig(file, &cfg); err != nil {utils.Fatalf(\\\"%v\\\", err)}}utils.SetNodeConfig(ctx, &cfg.Node)scryptN, scryptP, keydir, err := cfg.Node.AccountConfig()if err != nil {utils.Fatalf(\\\"Failed to read configuration: %v\\\", err)}password := utils.GetPassPhraseWithList(\\\"Your new account is locked with a password. Please give a password. Do not forget this password.\\\", true, 0, utils.MakePasswordList(ctx))account, err := keystore.StoreKey(keydir, password, scryptN, scryptP)if err != nil {utils.Fatalf(\\\"Failed to create account: %v\\\", err)}fmt.Printf(\\\"\\\\nYour new key was generated\\\\n\\\\n\\\")fmt.Printf(\\\"Public address of the key: %s\\\\n\\\", account.Address.Hex())fmt.Printf(\\\"Path of the secret key file: %s\\\\n\\\\n\\\", account.URL.Path)fmt.Printf(\\\"- You can share your public address with anyone. Others need it to interact with you.\\\\n\\\")fmt.Printf(\\\"- You must NEVER share the secret key with anyone! The key controls access to your funds!\\\\n\\\")fmt.Printf(\\\"- You must BACKUP your key file! Without the key, it\\\'s impossible to access account funds!\\\\n\\\")fmt.Printf(\\\"- You must REMEMBER your password! Without the password, it\\\'s impossible to decrypt the key!\\\\n\\\\n\\\")return nil}
在上面的代码中会首先去加载对应的节点配置信息,之后应用节点配置,然后要求用户输入新账户的密码,然后调用keystore.StoreKey来创建账户、验证并存储账户:
// StoreKey generates a key, encrypts with \\\'auth\\\' and stores in the given directoryfunc StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) {_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)return a, err}func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)if err != nil {return err}// Write into temporary filetmpName, err := writeTemporaryKeyFile(filename, keyjson)if err != nil {return err}if !ks.skipKeyFileVerification {// Verify that we can decrypt the file with the given password._, err = ks.GetKey(key.Address, tmpName, auth)if err != nil {msg := \\\"An error was encountered when saving and verifying the keystore file. \\\\n\\\" +\\\"This indicates that the keystore is corrupted. \\\\n\\\" +\\\"The corrupted file is stored at \\\\n%v\\\\n\\\" +\\\"Please file a ticket at:\\\\n\\\\n\\\" +\\\"https://github.com/ethereum/go-ethereum/issues.\\\" +\\\"The error was : %s\\\"//lint:ignore ST1005 This is a message for the userreturn fmt.Errorf(msg, tmpName, err)}}return os.Rename(tmpName, filename)}
在这里我们顺便来跟踪一下keystore(需要注意这里的keystore并不是私钥也不是助记词,而是将私钥通过钱包密码加密得来的,所以说如果我们得到了钱包密码,那么我们就得到了私钥)的生成方式,
// StoreKey generates a key, encrypts with \\\'auth\\\' and stores in the given directoryfunc StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) {_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)return a, err}
之后跟进storeNewKey
func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) {key, err := newKey(rand)if err != nil {return nil, accounts.Account{}, err}a := accounts.Account{Address: key.Address,URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))},}if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {zeroKey(key.PrivateKey)return nil, a, err}return key, a, err}
在这里会调用netKey并使用传入的随机参数通过椭圆曲线加密算法来生成一个私钥:
func newKey(rand io.Reader) (*Key, error) {privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)if err != nil {return nil, err}return newKeyFromECDSA(privateKeyECDSA), nil}
然后在调用newKeyFromECDSA根据私钥来生成公钥,然后根据公钥来生成地址参数并将其返回:
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {id, err := uuid.NewRandom()if err != nil {panic(fmt.Sprintf(\\\"Could not create random uuid: %v\\\", err))}key := &Key{Id: id,Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),PrivateKey: privateKeyECDSA,}return key}
之后调用StoreKey来存储账户信息,在这里会首先对生成的key(结构体类型,包含账户地址、私钥、ID序列)和密码进行一次加密操作,然后调用writeTemporaryKeyFile写文件进去
func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)if err != nil {return err}// Write into temporary filetmpName, err := writeTemporaryKeyFile(filename, keyjson)if err != nil {return err}if !ks.skipKeyFileVerification {// Verify that we can decrypt the file with the given password._, err = ks.GetKey(key.Address, tmpName, auth)if err != nil {msg := \\\"An error was encountered when saving and verifying the keystore file. \\\\n\\\" +\\\"This indicates that the keystore is corrupted. \\\\n\\\" +\\\"The corrupted file is stored at \\\\n%v\\\\n\\\" +\\\"Please file a ticket at:\\\\n\\\\n\\\" +\\\"https://github.com/ethereum/go-ethereum/issues.\\\" +\\\"The error was : %s\\\"//lint:ignore ST1005 This is a message for the userreturn fmt.Errorf(msg, tmpName, err)}}return os.Rename(tmpName, filename)}
加密流程如下,在这里会调用EncryptDataV3来进行关键的加密操作,然后将数据进行JSON格式化:
// EncryptKey encrypts a key using the specified scrypt parameters into a json// blob that can be decrypted later on.func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)if err != nil {return nil, err}encryptedKeyJSONV3 := encryptedKeyJSONV3{hex.EncodeToString(key.Address[:]),cryptoStruct,key.Id.String(),version,}return json.Marshal(encryptedKeyJSONV3)}
EncryptDataV3加密的具体实现代码如下所示,这里以我们输入的密码的作为盐值,然后经过一系列的处理后使用Keccak256进行加密处理:
// Encryptdata encrypts the data given as \\\'data\\\' with the password \\\'auth\\\'.func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {salt := make([]byte, 32)if _, err := io.ReadFull(rand.Reader, salt); err != nil {panic(\\\"reading from crypto/rand failed: \\\" + err.Error())}derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)if err != nil {return CryptoJSON{}, err}encryptKey := derivedKey[:16]iv := make([]byte, aes.BlockSize) // 16if _, err := io.ReadFull(rand.Reader, iv); err != nil {panic(\\\"reading from crypto/rand failed: \\\" + err.Error())}cipherText, err := aesCTRXOR(encryptKey, data, iv)if err != nil {return CryptoJSON{}, err}mac := crypto.Keccak256(derivedKey[16:32], cipherText)scryptParamsJSON := make(map[string]interface{}, 5)scryptParamsJSON[\\\"n\\\"] = scryptNscryptParamsJSON[\\\"r\\\"] = scryptRscryptParamsJSON[\\\"p\\\"] = scryptPscryptParamsJSON[\\\"dklen\\\"] = scryptDKLenscryptParamsJSON[\\\"salt\\\"] = hex.EncodeToString(salt)cipherParamsJSON := cipherparamsJSON{IV: hex.EncodeToString(iv),}cryptoStruct := CryptoJSON{Cipher: \\\"aes-128-ctr\\\",CipherText: hex.EncodeToString(cipherText),CipherParams: cipherParamsJSON,KDF: keyHeaderKDF,KDFParams: scryptParamsJSON,MAC: hex.EncodeToString(mac),}return cryptoStruct, nil}
之后调用writeTemporaryKeyFile来写文件,在这里会严格设置秘钥存储目录的权限:
func writeTemporaryKeyFile(file string, content []byte) (string, error) {// Create the keystore directory with appropriate permissions// in case it is not present yet.const dirPerm = 0700if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {return \\\"\\\", err}// Atomic write: create a temporary hidden file first// then move it into place. TempFile assigns mode 0600.f, err := ioutil.TempFile(filepath.Dir(file), \\\".\\\"+filepath.Base(file)+\\\".tmp\\\")if err != nil {return \\\"\\\", err}if _, err := f.Write(content); err != nil {f.Close()os.Remove(f.Name())return \\\"\\\", err}f.Close()return f.Name(), nil}
最后在StoreKey函数中检查使用最初设置的密码是否可以对秘钥文件(临时)进行解密,如果可以则将其进行更名存储:
if !ks.skipKeyFileVerification {// Verify that we can decrypt the file with the given password._, err = ks.GetKey(key.Address, tmpName, auth)if err != nil {msg := \\\"An error was encountered when saving and verifying the keystore file. \\\\n\\\" +\\\"This indicates that the keystore is corrupted. \\\\n\\\" +\\\"The corrupted file is stored at \\\\n%v\\\\n\\\" +\\\"Please file a ticket at:\\\\n\\\\n\\\" +\\\"https://github.com/ethereum/go-ethereum/issues.\\\" +\\\"The error was : %s\\\"//lint:ignore ST1005 This is a message for the userreturn fmt.Errorf(msg, tmpName, err)}}return os.Rename(tmpName, filename)
step 5:最后会返回账户地址信息、秘钥存储路径,其秘钥文件信息(AES-128加密且加盐处理)、秘钥文件的权限设置如下所示

PS:有安全经验的读者应该会发现这里少了一个关键点——\\”密码复杂度校验\\”,从而导致用户可以设置弱口令,例如:123456
Geth使用
下面我们简单介绍一下Geth的基本使用,跟多可以在控制台配合\\”–help\\”来使用~
账户操作
ubuntu@ubuntu:~/geth-linux-amd64$ ./geth account --helpNAME:geth account -Manage accounts, list all existing accounts, import a private key into a newaccount, create a new account or update an existing account.It supports interactive mode, when you are prompted for password as well asnon-interactive mode where passwords are supplied via a given password file.Non-interactive mode is only meant for scripted use on test networks or knownsafe environments.Make sure you remember the password you gave when creating a new account (witheither new or import). Without it you are not able to unlock your account.Note that exporting your key in unencrypted format is NOT supported.Keys are stored under <DATADIR>/keystore.It is safe to transfer the entire directory or the individual keys thereinbetween ethereum nodes by simply copying.Make sure you backup your keys regularly.USAGE:geth account command [command options] [arguments...]COMMANDS:list Print summary of existing accountsnew Create a new accountupdate Update an existing accountimport Import a private key into a new accountOPTIONS:--help, -h show help

交互模式
./geth console

查看节点信息:
>admin.nodeInfo

获取数据库目录信息:
>admin.datadir

获取远程节点列表:
admin.peers
跟多指令可以访问以下连接进行查看:
http://cw.hubwiz.com/card/c/geth-rpc-api/
数据库类
ubuntu@ubuntu:~/geth-linux-amd64$ ./geth db --helpNAME:geth db - Low level database operationsUSAGE:geth db command [command options] [arguments...]COMMANDS:inspect Inspect the storage size for each type of data in the databasestats Print leveldb statisticscompact Compact leveldb database. WARNING: May take a very long timeget Show the value of a database keydelete Delete a database key (WARNING: may corrupt your database)put Set the value of a database key (WARNING: may corrupt your database)dumptrie Show the storage key/values of a given storage trieOPTIONS:--help, -h show help
钱包操作
ubuntu@ubuntu:~/geth-linux-amd64$ ./geth wallet --helpNAME:geth wallet -geth wallet import /path/to/my/presale.walletwill prompt for your password and imports your ether presale account.It can be used non-interactively with the --password option taking apasswordfile as argument containing the wallet password in plaintext.USAGE:geth wallet command [command options] [arguments...]COMMANDS:ACCOUNT COMMANDS:import Import Ethereum presale walletOPTIONS:--help, -h show helpubuntu@ubuntu:~/geth-linux-amd64$
搭私有链
暂略~
文末小结
本篇文章以以太坊公链交互工具Geth为例介绍了公链交互工具命令参数的解析与执行流程,同时以Geth为例对其使用进行了简易演示,后面我们将对公链接口设计进行分析~
原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34155.html