consolecmd.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. // Copyright 2016 The go-ethereum Authors
  2. // This file is part of go-ethereum.
  3. //
  4. // go-ethereum is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // go-ethereum is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "context"
  19. "crypto/tls"
  20. "crypto/x509"
  21. "fmt"
  22. "io/ioutil"
  23. "net/http"
  24. "net/url"
  25. "os"
  26. "path/filepath"
  27. "strings"
  28. "github.com/ethereum/go-ethereum/cmd/utils"
  29. "github.com/ethereum/go-ethereum/console"
  30. "github.com/ethereum/go-ethereum/log"
  31. "github.com/ethereum/go-ethereum/node"
  32. "github.com/ethereum/go-ethereum/plugin/security"
  33. "github.com/ethereum/go-ethereum/rpc"
  34. "gopkg.in/urfave/cli.v1"
  35. )
  36. var (
  37. consoleFlags = []cli.Flag{utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag}
  38. rpcClientFlags = []cli.Flag{utils.RPCClientToken, utils.RPCClientTLSCert, utils.RPCClientTLSCaCert, utils.RPCClientTLSCipherSuites, utils.RPCClientTLSInsecureSkipVerify}
  39. consoleCommand = cli.Command{
  40. Action: utils.MigrateFlags(localConsole),
  41. Name: "console",
  42. Usage: "Start an interactive JavaScript environment",
  43. Flags: append(append(nodeFlags, rpcFlags...), consoleFlags...),
  44. Category: "CONSOLE COMMANDS",
  45. Description: `
  46. The Geth console is an interactive shell for the JavaScript runtime environment
  47. which exposes a node admin interface as well as the Ðapp JavaScript API.
  48. See https://geth.ethereum.org/docs/interface/javascript-console.`,
  49. }
  50. attachCommand = cli.Command{
  51. Action: utils.MigrateFlags(remoteConsole),
  52. Name: "attach",
  53. Usage: "Start an interactive JavaScript environment (connect to node)",
  54. ArgsUsage: "[endpoint]",
  55. Flags: append(append(consoleFlags, utils.DataDirFlag), rpcClientFlags...),
  56. Category: "CONSOLE COMMANDS",
  57. Description: `
  58. The Geth console is an interactive shell for the JavaScript runtime environment
  59. which exposes a node admin interface as well as the Ðapp JavaScript API.
  60. See https://geth.ethereum.org/docs/interface/javascript-console.
  61. This command allows to open a console on a running geth node.`,
  62. }
  63. javascriptCommand = cli.Command{
  64. Action: utils.MigrateFlags(ephemeralConsole),
  65. Name: "js",
  66. Usage: "Execute the specified JavaScript files",
  67. ArgsUsage: "<jsfile> [jsfile...]",
  68. Flags: append(nodeFlags, consoleFlags...),
  69. Category: "CONSOLE COMMANDS",
  70. Description: `
  71. The JavaScript VM exposes a node admin interface as well as the Ðapp
  72. JavaScript API. See https://geth.ethereum.org/docs/interface/javascript-console`,
  73. }
  74. )
  75. // Quorum
  76. //
  77. // read tls client configuration from command line arguments
  78. //
  79. // only for HTTPS or WSS
  80. func readTLSClientConfig(endpoint string, ctx *cli.Context) (*tls.Config, bool, error) {
  81. if !strings.HasPrefix(endpoint, "https://") && !strings.HasPrefix(endpoint, "wss://") {
  82. return nil, false, nil
  83. }
  84. hasCustomTls := false
  85. insecureSkipVerify := ctx.Bool(utils.RPCClientTLSInsecureSkipVerify.Name)
  86. tlsConfig := &tls.Config{
  87. InsecureSkipVerify: insecureSkipVerify,
  88. }
  89. var certFile, caFile string
  90. if !insecureSkipVerify {
  91. var certPem, caPem []byte
  92. certFile, caFile = ctx.String(utils.RPCClientTLSCert.Name), ctx.String(utils.RPCClientTLSCaCert.Name)
  93. var err error
  94. if certFile != "" {
  95. if certPem, err = ioutil.ReadFile(certFile); err != nil {
  96. return nil, true, err
  97. }
  98. }
  99. if caFile != "" {
  100. if caPem, err = ioutil.ReadFile(caFile); err != nil {
  101. return nil, true, err
  102. }
  103. }
  104. if len(certPem) != 0 || len(caPem) != 0 {
  105. certPool, err := x509.SystemCertPool()
  106. if err != nil {
  107. certPool = x509.NewCertPool()
  108. }
  109. if len(certPem) != 0 {
  110. certPool.AppendCertsFromPEM(certPem)
  111. }
  112. if len(caPem) != 0 {
  113. certPool.AppendCertsFromPEM(caPem)
  114. }
  115. tlsConfig.RootCAs = certPool
  116. hasCustomTls = true
  117. }
  118. } else {
  119. hasCustomTls = true
  120. }
  121. cipherSuitesInput := ctx.String(utils.RPCClientTLSCipherSuites.Name)
  122. cipherSuitesStrings := strings.FieldsFunc(cipherSuitesInput, func(r rune) bool {
  123. return r == ','
  124. })
  125. if len(cipherSuitesStrings) > 0 {
  126. cipherSuiteList := make(security.CipherSuiteList, len(cipherSuitesStrings))
  127. for i, s := range cipherSuitesStrings {
  128. cipherSuiteList[i] = security.CipherSuite(strings.TrimSpace(s))
  129. }
  130. cipherSuites, err := cipherSuiteList.ToUint16Array()
  131. if err != nil {
  132. return nil, true, err
  133. }
  134. tlsConfig.CipherSuites = cipherSuites
  135. hasCustomTls = true
  136. }
  137. if !hasCustomTls {
  138. return nil, false, nil
  139. }
  140. return tlsConfig, hasCustomTls, nil
  141. }
  142. // localConsole starts a new geth node, attaching a JavaScript console to it at the
  143. // same time.
  144. func localConsole(ctx *cli.Context) error {
  145. // Create and start the node based on the CLI flags
  146. prepare(ctx)
  147. stack, backend := makeFullNode(ctx)
  148. startNode(ctx, stack, backend)
  149. defer stack.Close()
  150. // Attach to the newly started node and start the JavaScript console
  151. client, err := stack.Attach()
  152. if err != nil {
  153. utils.Fatalf("Failed to attach to the inproc geth: %v", err)
  154. }
  155. config := console.Config{
  156. DataDir: utils.MakeDataDir(ctx),
  157. DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
  158. Client: client,
  159. Preload: utils.MakeConsolePreloads(ctx),
  160. }
  161. console, err := console.New(config)
  162. if err != nil {
  163. utils.Fatalf("Failed to start the JavaScript console: %v", err)
  164. }
  165. defer console.Stop(false)
  166. // If only a short execution was requested, evaluate and return
  167. if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
  168. console.Evaluate(script)
  169. return nil
  170. }
  171. // Otherwise print the welcome screen and enter interactive mode
  172. console.Welcome()
  173. console.Interactive()
  174. return nil
  175. }
  176. // remoteConsole will connect to a remote geth instance, attaching a JavaScript
  177. // console to it.
  178. func remoteConsole(ctx *cli.Context) error {
  179. // Attach to a remotely running geth instance and start the JavaScript console
  180. endpoint := ctx.Args().First()
  181. if endpoint == "" {
  182. path := node.DefaultDataDir()
  183. if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
  184. path = ctx.GlobalString(utils.DataDirFlag.Name)
  185. }
  186. if path != "" {
  187. if ctx.GlobalBool(utils.RopstenFlag.Name) {
  188. // Maintain compatibility with older Geth configurations storing the
  189. // Ropsten database in `testnet` instead of `ropsten`.
  190. legacyPath := filepath.Join(path, "testnet")
  191. if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
  192. path = legacyPath
  193. } else {
  194. path = filepath.Join(path, "ropsten")
  195. }
  196. } else if ctx.GlobalBool(utils.RinkebyFlag.Name) {
  197. path = filepath.Join(path, "rinkeby")
  198. } else if ctx.GlobalBool(utils.GoerliFlag.Name) {
  199. path = filepath.Join(path, "goerli")
  200. } else if ctx.GlobalBool(utils.YoloV3Flag.Name) {
  201. path = filepath.Join(path, "yolo-v3")
  202. }
  203. }
  204. endpoint = fmt.Sprintf("%s/geth.ipc", path)
  205. }
  206. client, err := dialRPC(endpoint, ctx)
  207. if err != nil {
  208. utils.Fatalf("Unable to attach to remote geth: %v", err)
  209. }
  210. config := console.Config{
  211. DataDir: utils.MakeDataDir(ctx),
  212. DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
  213. Client: client,
  214. Preload: utils.MakeConsolePreloads(ctx),
  215. }
  216. console, err := console.New(config)
  217. if err != nil {
  218. utils.Fatalf("Failed to start the JavaScript console: %v", err)
  219. }
  220. defer console.Stop(false)
  221. if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
  222. console.Evaluate(script)
  223. return nil
  224. }
  225. // Otherwise print the welcome screen and enter interactive mode
  226. console.Welcome()
  227. console.Interactive()
  228. return nil
  229. }
  230. // dialRPC returns a RPC client which connects to the given endpoint.
  231. // The check for empty endpoint implements the defaulting logic
  232. // for "geth attach" and "geth monitor" with no argument.
  233. //
  234. // Quorum: passing the cli context to build security-aware client:
  235. // 1. Custom TLS configuration
  236. // 2. Access Token awareness via rpc.HttpCredentialsProviderFunc
  237. // 3. PSI awareness from environment variable and endpoint query param
  238. func dialRPC(endpoint string, ctx *cli.Context) (*rpc.Client, error) {
  239. if endpoint == "" {
  240. endpoint = node.DefaultIPCEndpoint(clientIdentifier)
  241. } else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
  242. // Backwards compatibility with geth < 1.5 which required
  243. // these prefixes.
  244. endpoint = endpoint[4:]
  245. }
  246. var (
  247. client *rpc.Client
  248. err error
  249. dialCtx = context.Background()
  250. )
  251. tlsConfig, hasCustomTls, tlsReadErr := readTLSClientConfig(endpoint, ctx)
  252. if tlsReadErr != nil {
  253. return nil, tlsReadErr
  254. }
  255. if token := ctx.String(utils.RPCClientToken.Name); token != "" {
  256. var f rpc.HttpCredentialsProviderFunc = func(ctx context.Context) (string, error) {
  257. return token, nil
  258. }
  259. // it's important that f MUST BE OF TYPE rpc.HttpCredentialsProviderFunc
  260. dialCtx = rpc.WithCredentialsProvider(dialCtx, f)
  261. }
  262. if hasCustomTls {
  263. u, err := url.Parse(endpoint)
  264. if err != nil {
  265. return nil, err
  266. }
  267. switch u.Scheme {
  268. case "https":
  269. customHttpClient := &http.Client{
  270. Transport: http.DefaultTransport,
  271. }
  272. customHttpClient.Transport.(*http.Transport).TLSClientConfig = tlsConfig
  273. client, _ = rpc.DialHTTPWithClient(endpoint, customHttpClient)
  274. case "wss":
  275. client, _ = rpc.DialWebsocketWithCustomTLS(dialCtx, endpoint, "", tlsConfig)
  276. default:
  277. log.Warn("unsupported scheme for custom TLS which is only for HTTPS/WSS", "scheme", u.Scheme)
  278. client, _ = rpc.DialContext(dialCtx, endpoint)
  279. }
  280. } else {
  281. client, err = rpc.DialContext(dialCtx, endpoint)
  282. }
  283. if err != nil {
  284. return nil, err
  285. }
  286. // enrich clients with provider functions to populate HTTP request header
  287. if f := rpc.CredentialsProviderFromContext(dialCtx); f != nil {
  288. client = client.WithHTTPCredentials(f)
  289. }
  290. return client, nil
  291. }
  292. // ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
  293. // console to it, executes each of the files specified as arguments and tears
  294. // everything down.
  295. func ephemeralConsole(ctx *cli.Context) error {
  296. // Create and start the node based on the CLI flags
  297. stack, backend := makeFullNode(ctx)
  298. startNode(ctx, stack, backend)
  299. defer stack.Close()
  300. // Attach to the newly started node and start the JavaScript console
  301. client, err := stack.Attach()
  302. if err != nil {
  303. utils.Fatalf("Failed to attach to the inproc geth: %v", err)
  304. }
  305. config := console.Config{
  306. DataDir: utils.MakeDataDir(ctx),
  307. DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
  308. Client: client,
  309. Preload: utils.MakeConsolePreloads(ctx),
  310. }
  311. console, err := console.New(config)
  312. if err != nil {
  313. utils.Fatalf("Failed to start the JavaScript console: %v", err)
  314. }
  315. defer console.Stop(false)
  316. // Evaluate each of the specified JavaScript files
  317. for _, file := range ctx.Args() {
  318. if err = console.Execute(file); err != nil {
  319. utils.Fatalf("Failed to execute %s: %v", file, err)
  320. }
  321. }
  322. go func() {
  323. stack.Wait()
  324. console.Stop(false)
  325. }()
  326. console.Stop(true)
  327. return nil
  328. }