github.com/yinchengtsinghua/golang-Eos-dpos-Ethereum@v0.0.0-20190121132951-92cc4225ed8e/cmd/clef/main.go (about)

     1  
     2  //此源码被清华学神尹成大魔王专业翻译分析并修改
     3  //尹成QQ77025077
     4  //尹成微信18510341407
     5  //尹成所在QQ群721929980
     6  //尹成邮箱 yinc13@mails.tsinghua.edu.cn
     7  //尹成毕业于清华大学,微软区块链领域全球最有价值专家
     8  //https://mvp.microsoft.com/zh-cn/PublicProfile/4033620
     9  //版权所有2018 Go Ethereum作者
    10  //此文件是Go以太坊的一部分。
    11  //
    12  //Go以太坊是免费软件:您可以重新发布和/或修改它
    13  //根据GNU通用公共许可证的条款
    14  //自由软件基金会,或者许可证的第3版,或者
    15  //(由您选择)任何更高版本。
    16  //
    17  //Go以太坊的分布希望它会有用,
    18  //但没有任何保证;甚至没有
    19  //适销性或特定用途的适用性。见
    20  //GNU通用公共许可证了解更多详细信息。
    21  //
    22  //你应该已经收到一份GNU通用公共许可证的副本
    23  //一起去以太坊吧。如果没有,请参见<http://www.gnu.org/licenses/>。
    24  
    25  //签名者是一个实用程序,可以用来对事务和
    26  //任意数据。
    27  package main
    28  
    29  import (
    30  	"bufio"
    31  	"context"
    32  	"crypto/rand"
    33  	"crypto/sha256"
    34  	"encoding/hex"
    35  	"encoding/json"
    36  	"fmt"
    37  	"io"
    38  	"io/ioutil"
    39  	"os"
    40  	"os/signal"
    41  	"os/user"
    42  	"path/filepath"
    43  	"runtime"
    44  	"strings"
    45  
    46  	"github.com/ethereum/go-ethereum/cmd/utils"
    47  	"github.com/ethereum/go-ethereum/common"
    48  	"github.com/ethereum/go-ethereum/crypto"
    49  	"github.com/ethereum/go-ethereum/log"
    50  	"github.com/ethereum/go-ethereum/node"
    51  	"github.com/ethereum/go-ethereum/rpc"
    52  	"github.com/ethereum/go-ethereum/signer/core"
    53  	"github.com/ethereum/go-ethereum/signer/rules"
    54  	"github.com/ethereum/go-ethereum/signer/storage"
    55  	"gopkg.in/urfave/cli.v1"
    56  )
    57  
    58  //ExternalApiVersion--请参阅extapi_changelog.md
    59  const ExternalAPIVersion = "2.0.0"
    60  
    61  //InternalApiVersion--请参阅intapi_changelog.md
    62  const InternalAPIVersion = "2.0.0"
    63  
    64  const legalWarning = `
    65  WARNING! 
    66  
    67  Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there
    68  are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software
    69  unless you agree to take full responsibility for doing so, and know what you are doing. 
    70  
    71  TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! 
    72  
    73  `
    74  
    75  var (
    76  	logLevelFlag = cli.IntFlag{
    77  		Name:  "loglevel",
    78  		Value: 4,
    79  		Usage: "log level to emit to the screen",
    80  	}
    81  	keystoreFlag = cli.StringFlag{
    82  		Name:  "keystore",
    83  		Value: filepath.Join(node.DefaultDataDir(), "keystore"),
    84  		Usage: "Directory for the keystore",
    85  	}
    86  	configdirFlag = cli.StringFlag{
    87  		Name:  "configdir",
    88  		Value: DefaultConfigDir(),
    89  		Usage: "Directory for Clef configuration",
    90  	}
    91  	rpcPortFlag = cli.IntFlag{
    92  		Name:  "rpcport",
    93  		Usage: "HTTP-RPC server listening port",
    94  		Value: node.DefaultHTTPPort + 5,
    95  	}
    96  	signerSecretFlag = cli.StringFlag{
    97  		Name:  "signersecret",
    98  		Usage: "A file containing the password used to encrypt Clef credentials, e.g. keystore credentials and ruleset hash",
    99  	}
   100  	dBFlag = cli.StringFlag{
   101  		Name:  "4bytedb",
   102  		Usage: "File containing 4byte-identifiers",
   103  		Value: "./4byte.json",
   104  	}
   105  	customDBFlag = cli.StringFlag{
   106  		Name:  "4bytedb-custom",
   107  		Usage: "File used for writing new 4byte-identifiers submitted via API",
   108  		Value: "./4byte-custom.json",
   109  	}
   110  	auditLogFlag = cli.StringFlag{
   111  		Name:  "auditlog",
   112  		Usage: "File used to emit audit logs. Set to \"\" to disable",
   113  		Value: "audit.log",
   114  	}
   115  	ruleFlag = cli.StringFlag{
   116  		Name:  "rules",
   117  		Usage: "Enable rule-engine",
   118  		Value: "rules.json",
   119  	}
   120  	stdiouiFlag = cli.BoolFlag{
   121  		Name: "stdio-ui",
   122  		Usage: "Use STDIN/STDOUT as a channel for an external UI. " +
   123  			"This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " +
   124  			"interface, and can be used when Clef is started by an external process.",
   125  	}
   126  	testFlag = cli.BoolFlag{
   127  		Name:  "stdio-ui-test",
   128  		Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.",
   129  	}
   130  	app         = cli.NewApp()
   131  	initCommand = cli.Command{
   132  		Action:    utils.MigrateFlags(initializeSecrets),
   133  		Name:      "init",
   134  		Usage:     "Initialize the signer, generate secret storage",
   135  		ArgsUsage: "",
   136  		Flags: []cli.Flag{
   137  			logLevelFlag,
   138  			configdirFlag,
   139  		},
   140  		Description: `
   141  The init command generates a master seed which Clef can use to store credentials and data needed for 
   142  the rule-engine to work.`,
   143  	}
   144  	attestCommand = cli.Command{
   145  		Action:    utils.MigrateFlags(attestFile),
   146  		Name:      "attest",
   147  		Usage:     "Attest that a js-file is to be used",
   148  		ArgsUsage: "<sha256sum>",
   149  		Flags: []cli.Flag{
   150  			logLevelFlag,
   151  			configdirFlag,
   152  			signerSecretFlag,
   153  		},
   154  		Description: `
   155  The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of 
   156  incoming requests. 
   157  
   158  Whenever you make an edit to the rule file, you need to use attestation to tell 
   159  Clef that the file is 'safe' to execute.`,
   160  	}
   161  
   162  	addCredentialCommand = cli.Command{
   163  		Action:    utils.MigrateFlags(addCredential),
   164  		Name:      "addpw",
   165  		Usage:     "Store a credential for a keystore file",
   166  		ArgsUsage: "<address> <password>",
   167  		Flags: []cli.Flag{
   168  			logLevelFlag,
   169  			configdirFlag,
   170  			signerSecretFlag,
   171  		},
   172  		Description: `
   173  The addpw command stores a password for a given address (keyfile). If you invoke it with only one parameter, it will 
   174  remove any stored credential for that address (keyfile)
   175  `,
   176  	}
   177  )
   178  
   179  func init() {
   180  	app.Name = "Clef"
   181  	app.Usage = "Manage Ethereum account operations"
   182  	app.Flags = []cli.Flag{
   183  		logLevelFlag,
   184  		keystoreFlag,
   185  		configdirFlag,
   186  		utils.NetworkIdFlag,
   187  		utils.LightKDFFlag,
   188  		utils.NoUSBFlag,
   189  		utils.RPCListenAddrFlag,
   190  		utils.RPCVirtualHostsFlag,
   191  		utils.IPCDisabledFlag,
   192  		utils.IPCPathFlag,
   193  		utils.RPCEnabledFlag,
   194  		rpcPortFlag,
   195  		signerSecretFlag,
   196  		dBFlag,
   197  		customDBFlag,
   198  		auditLogFlag,
   199  		ruleFlag,
   200  		stdiouiFlag,
   201  		testFlag,
   202  	}
   203  	app.Action = signer
   204  	app.Commands = []cli.Command{initCommand, attestCommand, addCredentialCommand}
   205  
   206  }
   207  func main() {
   208  	if err := app.Run(os.Args); err != nil {
   209  		fmt.Fprintln(os.Stderr, err)
   210  		os.Exit(1)
   211  	}
   212  }
   213  
   214  func initializeSecrets(c *cli.Context) error {
   215  	if err := initialize(c); err != nil {
   216  		return err
   217  	}
   218  	configDir := c.String(configdirFlag.Name)
   219  
   220  	masterSeed := make([]byte, 256)
   221  	n, err := io.ReadFull(rand.Reader, masterSeed)
   222  	if err != nil {
   223  		return err
   224  	}
   225  	if n != len(masterSeed) {
   226  		return fmt.Errorf("failed to read enough random")
   227  	}
   228  	err = os.Mkdir(configDir, 0700)
   229  	if err != nil && !os.IsExist(err) {
   230  		return err
   231  	}
   232  	location := filepath.Join(configDir, "secrets.dat")
   233  	if _, err := os.Stat(location); err == nil {
   234  		return fmt.Errorf("file %v already exists, will not overwrite", location)
   235  	}
   236  	err = ioutil.WriteFile(location, masterSeed, 0700)
   237  	if err != nil {
   238  		return err
   239  	}
   240  	fmt.Printf("A master seed has been generated into %s\n", location)
   241  	fmt.Printf(`
   242  This is required to be able to store credentials, such as : 
   243  * Passwords for keystores (used by rule engine)
   244  * Storage for javascript rules
   245  * Hash of rule-file
   246  
   247  You should treat that file with utmost secrecy, and make a backup of it. 
   248  NOTE: This file does not contain your accounts. Those need to be backed up separately!
   249  
   250  `)
   251  	return nil
   252  }
   253  func attestFile(ctx *cli.Context) error {
   254  	if len(ctx.Args()) < 1 {
   255  		utils.Fatalf("This command requires an argument.")
   256  	}
   257  	if err := initialize(ctx); err != nil {
   258  		return err
   259  	}
   260  
   261  	stretchedKey, err := readMasterKey(ctx)
   262  	if err != nil {
   263  		utils.Fatalf(err.Error())
   264  	}
   265  	configDir := ctx.String(configdirFlag.Name)
   266  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   267  	confKey := crypto.Keccak256([]byte("config"), stretchedKey)
   268  
   269  //初始化加密存储
   270  	configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey)
   271  	val := ctx.Args().First()
   272  	configStorage.Put("ruleset_sha256", val)
   273  	log.Info("Ruleset attestation updated", "sha256", val)
   274  	return nil
   275  }
   276  
   277  func addCredential(ctx *cli.Context) error {
   278  	if len(ctx.Args()) < 1 {
   279  		utils.Fatalf("This command requires at leaste one argument.")
   280  	}
   281  	if err := initialize(ctx); err != nil {
   282  		return err
   283  	}
   284  
   285  	stretchedKey, err := readMasterKey(ctx)
   286  	if err != nil {
   287  		utils.Fatalf(err.Error())
   288  	}
   289  	configDir := ctx.String(configdirFlag.Name)
   290  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   291  	pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   292  
   293  //初始化加密存储
   294  	pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   295  	key := ctx.Args().First()
   296  	value := ""
   297  	if len(ctx.Args()) > 1 {
   298  		value = ctx.Args().Get(1)
   299  	}
   300  	pwStorage.Put(key, value)
   301  	log.Info("Credential store updated", "key", key)
   302  	return nil
   303  }
   304  
   305  func initialize(c *cli.Context) error {
   306  //设置记录器以打印所有内容
   307  	logOutput := os.Stdout
   308  	if c.Bool(stdiouiFlag.Name) {
   309  		logOutput = os.Stderr
   310  //如果使用stdioui,则无法执行“确认”流
   311  		fmt.Fprintf(logOutput, legalWarning)
   312  	} else {
   313  		if !confirm(legalWarning) {
   314  			return fmt.Errorf("aborted by user")
   315  		}
   316  	}
   317  
   318  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true))))
   319  	return nil
   320  }
   321  
   322  func signer(c *cli.Context) error {
   323  	if err := initialize(c); err != nil {
   324  		return err
   325  	}
   326  	var (
   327  		ui core.SignerUI
   328  	)
   329  	if c.Bool(stdiouiFlag.Name) {
   330  		log.Info("Using stdin/stdout as UI-channel")
   331  		ui = core.NewStdIOUI()
   332  	} else {
   333  		log.Info("Using CLI as UI-channel")
   334  		ui = core.NewCommandlineUI()
   335  	}
   336  	db, err := core.NewAbiDBFromFiles(c.String(dBFlag.Name), c.String(customDBFlag.Name))
   337  	if err != nil {
   338  		utils.Fatalf(err.Error())
   339  	}
   340  	log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
   341  
   342  	var (
   343  		api core.ExternalAPI
   344  	)
   345  
   346  	configDir := c.String(configdirFlag.Name)
   347  	if stretchedKey, err := readMasterKey(c); err != nil {
   348  		log.Info("No master seed provided, rules disabled")
   349  	} else {
   350  
   351  		if err != nil {
   352  			utils.Fatalf(err.Error())
   353  		}
   354  		vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   355  
   356  //生成特定于域的密钥
   357  		pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   358  		jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey)
   359  		confkey := crypto.Keccak256([]byte("config"), stretchedKey)
   360  
   361  //初始化加密存储
   362  		pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   363  		jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
   364  		configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
   365  
   366  //我们有规则文件吗?
   367  		ruleJS, err := ioutil.ReadFile(c.String(ruleFlag.Name))
   368  		if err != nil {
   369  			log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
   370  		} else {
   371  			hasher := sha256.New()
   372  			hasher.Write(ruleJS)
   373  			shasum := hasher.Sum(nil)
   374  			storedShasum := configStorage.Get("ruleset_sha256")
   375  			if storedShasum != hex.EncodeToString(shasum) {
   376  				log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum)
   377  			} else {
   378  //初始化规则
   379  				ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage)
   380  				if err != nil {
   381  					utils.Fatalf(err.Error())
   382  				}
   383  				ruleEngine.Init(string(ruleJS))
   384  				ui = ruleEngine
   385  				log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
   386  			}
   387  		}
   388  	}
   389  
   390  	apiImpl := core.NewSignerAPI(
   391  		c.Int64(utils.NetworkIdFlag.Name),
   392  		c.String(keystoreFlag.Name),
   393  		c.Bool(utils.NoUSBFlag.Name),
   394  		ui, db,
   395  		c.Bool(utils.LightKDFFlag.Name))
   396  
   397  	api = apiImpl
   398  
   399  //审计日志
   400  	if logfile := c.String(auditLogFlag.Name); logfile != "" {
   401  		api, err = core.NewAuditLogger(logfile, api)
   402  		if err != nil {
   403  			utils.Fatalf(err.Error())
   404  		}
   405  		log.Info("Audit logs configured", "file", logfile)
   406  	}
   407  //向服务器注册签名者API
   408  	var (
   409  		extapiURL = "n/a"
   410  		ipcapiURL = "n/a"
   411  	)
   412  	rpcAPI := []rpc.API{
   413  		{
   414  			Namespace: "account",
   415  			Public:    true,
   416  			Service:   api,
   417  			Version:   "1.0"},
   418  	}
   419  	if c.Bool(utils.RPCEnabledFlag.Name) {
   420  
   421  		vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
   422  		cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
   423  
   424  //
   425  		httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
   426  		listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
   427  		if err != nil {
   428  			utils.Fatalf("Could not start RPC api: %v", err)
   429  		}
   430  extapiURL = fmt.Sprintf("http://%s“,httpendpoint)
   431  		log.Info("HTTP endpoint opened", "url", extapiURL)
   432  
   433  		defer func() {
   434  			listener.Close()
   435  			log.Info("HTTP endpoint closed", "url", httpEndpoint)
   436  		}()
   437  
   438  	}
   439  	if !c.Bool(utils.IPCDisabledFlag.Name) {
   440  		if c.IsSet(utils.IPCPathFlag.Name) {
   441  			ipcapiURL = c.String(utils.IPCPathFlag.Name)
   442  		} else {
   443  			ipcapiURL = filepath.Join(configDir, "clef.ipc")
   444  		}
   445  
   446  		listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
   447  		if err != nil {
   448  			utils.Fatalf("Could not start IPC api: %v", err)
   449  		}
   450  		log.Info("IPC endpoint opened", "url", ipcapiURL)
   451  		defer func() {
   452  			listener.Close()
   453  			log.Info("IPC endpoint closed", "url", ipcapiURL)
   454  		}()
   455  
   456  	}
   457  
   458  	if c.Bool(testFlag.Name) {
   459  		log.Info("Performing UI test")
   460  		go testExternalUI(apiImpl)
   461  	}
   462  	ui.OnSignerStartup(core.StartupInfo{
   463  		Info: map[string]interface{}{
   464  			"extapi_version": ExternalAPIVersion,
   465  			"intapi_version": InternalAPIVersion,
   466  			"extapi_http":    extapiURL,
   467  			"extapi_ipc":     ipcapiURL,
   468  		},
   469  	})
   470  
   471  	abortChan := make(chan os.Signal)
   472  	signal.Notify(abortChan, os.Interrupt)
   473  
   474  	sig := <-abortChan
   475  	log.Info("Exiting...", "signal", sig)
   476  
   477  	return nil
   478  }
   479  
   480  //splitandtrim拆分由逗号分隔的输入
   481  //并修剪子字符串中多余的空白。
   482  func splitAndTrim(input string) []string {
   483  	result := strings.Split(input, ",")
   484  	for i, r := range result {
   485  		result[i] = strings.TrimSpace(r)
   486  	}
   487  	return result
   488  }
   489  
   490  //
   491  //持久性要求。
   492  func DefaultConfigDir() string {
   493  //尝试将数据文件夹放在用户的home目录中
   494  	home := homeDir()
   495  	if home != "" {
   496  		if runtime.GOOS == "darwin" {
   497  			return filepath.Join(home, "Library", "Signer")
   498  		} else if runtime.GOOS == "windows" {
   499  			return filepath.Join(home, "AppData", "Roaming", "Signer")
   500  		} else {
   501  			return filepath.Join(home, ".clef")
   502  		}
   503  	}
   504  //因为我们无法猜测一个稳定的位置,所以返回空的,稍后再处理
   505  	return ""
   506  }
   507  
   508  func homeDir() string {
   509  	if home := os.Getenv("HOME"); home != "" {
   510  		return home
   511  	}
   512  	if usr, err := user.Current(); err == nil {
   513  		return usr.HomeDir
   514  	}
   515  	return ""
   516  }
   517  func readMasterKey(ctx *cli.Context) ([]byte, error) {
   518  	var (
   519  		file      string
   520  		configDir = ctx.String(configdirFlag.Name)
   521  	)
   522  	if ctx.IsSet(signerSecretFlag.Name) {
   523  		file = ctx.String(signerSecretFlag.Name)
   524  	} else {
   525  		file = filepath.Join(configDir, "secrets.dat")
   526  	}
   527  	if err := checkFile(file); err != nil {
   528  		return nil, err
   529  	}
   530  	masterKey, err := ioutil.ReadFile(file)
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  	if len(masterKey) < 256 {
   535  		return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey))
   536  	}
   537  //创建保管库位置
   538  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterKey)[:10]))
   539  	err = os.Mkdir(vaultLocation, 0700)
   540  	if err != nil && !os.IsExist(err) {
   541  		return nil, err
   542  	}
   543  //!todo,使用kdf拉伸主密钥
   544  //拉伸键:=拉伸键(主\键)
   545  
   546  	return masterKey, nil
   547  }
   548  
   549  //check file是检查文件
   550  //*存在
   551  //*模式0600
   552  func checkFile(filename string) error {
   553  	info, err := os.Stat(filename)
   554  	if err != nil {
   555  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   556  	}
   557  //检查Unix权限位
   558  	if info.Mode().Perm()&077 != 0 {
   559  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   560  	}
   561  	return nil
   562  }
   563  
   564  //确认显示文本并请求用户确认
   565  func confirm(text string) bool {
   566  	fmt.Printf(text)
   567  	fmt.Printf("\nEnter 'ok' to proceed:\n>")
   568  
   569  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   570  	if err != nil {
   571  		log.Crit("Failed to read user input", "err", err)
   572  	}
   573  
   574  	if text := strings.TrimSpace(text); text == "ok" {
   575  		return true
   576  	}
   577  	return false
   578  }
   579  
   580  func testExternalUI(api *core.SignerAPI) {
   581  
   582  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   583  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   584  	ctx = context.WithValue(ctx, "local", "main")
   585  
   586  	errs := make([]string, 0)
   587  
   588  	api.UI.ShowInfo("Testing 'ShowInfo'")
   589  	api.UI.ShowError("Testing 'ShowError'")
   590  
   591  	checkErr := func(method string, err error) {
   592  		if err != nil && err != core.ErrRequestDenied {
   593  			errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error()))
   594  		}
   595  	}
   596  	var err error
   597  
   598  	_, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil)
   599  	checkErr("SignTransaction", err)
   600  	_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
   601  	checkErr("Sign", err)
   602  	_, err = api.List(ctx)
   603  	checkErr("List", err)
   604  	_, err = api.New(ctx)
   605  	checkErr("New", err)
   606  	_, err = api.Export(ctx, common.Address{})
   607  	checkErr("Export", err)
   608  	_, err = api.Import(ctx, json.RawMessage{})
   609  	checkErr("Import", err)
   610  
   611  	api.UI.ShowInfo("Tests completed")
   612  
   613  	if len(errs) > 0 {
   614  		log.Error("Got errors")
   615  		for _, e := range errs {
   616  			log.Error(e)
   617  		}
   618  	} else {
   619  		log.Info("No errors")
   620  	}
   621  
   622  }
   623  
   624  /*
   625  
   626  
   627  curl-h“content-type:application/json”-x post--data'“jsonrpc”:“2.0”,“method”:“account_new”,“params”:[“test”],“id”:67“localhost:8550”
   628  
   629  //列出帐户
   630  
   631  curl-i-h“内容类型:application/json”-x post--data'“jsonrpc”:“2.0”,“method”:“account_list”,“params”:[“”],“id”:67”http://localhost:8550/
   632  
   633  
   634  //安全端(0x12)
   635  //4401A6E4000000000000000000000000000000000000000000000000000000000012
   636  
   637  /供给ABI
   638  
   639  
   640  
   641  curl-i-h“content-type:application/json”-x post--data'“jsonrpc”:“2.0”,“method”:“account-signtransaction”,“params”:[“from”:“0x82A2A876D39022B3019932D30CD9C97AD5616813”,“gas”:“0x333”,“gasprice”:“0x123”,“nonce”:“0x 0”,“to”:“0x07A565B7ED7D7A68680A4C162885BEDB695FE0”,“value”:“0x10”,“data”:“0x4401A6E400000000000000000000000000000000000000000”000000000000000000 12“”,“id”:67”http://localhost:8550/
   642  
   643  
   644  
   645  curl-i-h“内容类型:application/json”-x post--数据'“jsonrpc”:“2.0”,“方法”:“帐户符号”,“参数”:[“0x694267f14675d7e1b9494fd8d72fe1755710fa”,“bazonk gaz baz”],“id”:67“http://localhost:8550/
   646  
   647  
   648  */
   649