bridge.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. package guardiand
  2. import (
  3. "context"
  4. "fmt"
  5. "go.uber.org/zap/zapcore"
  6. "net/http"
  7. _ "net/http/pprof"
  8. "os"
  9. "syscall"
  10. solana_types "github.com/gagliardetto/solana-go"
  11. "github.com/gorilla/mux"
  12. "github.com/prometheus/client_golang/prometheus/promhttp"
  13. eth_common "github.com/ethereum/go-ethereum/common"
  14. ethcrypto "github.com/ethereum/go-ethereum/crypto"
  15. "github.com/libp2p/go-libp2p-core/crypto"
  16. "github.com/libp2p/go-libp2p-core/peer"
  17. "github.com/spf13/cobra"
  18. "go.uber.org/zap"
  19. "golang.org/x/sys/unix"
  20. "github.com/certusone/wormhole/node/pkg/common"
  21. "github.com/certusone/wormhole/node/pkg/devnet"
  22. "github.com/certusone/wormhole/node/pkg/ethereum"
  23. "github.com/certusone/wormhole/node/pkg/p2p"
  24. "github.com/certusone/wormhole/node/pkg/processor"
  25. gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
  26. "github.com/certusone/wormhole/node/pkg/readiness"
  27. solana "github.com/certusone/wormhole/node/pkg/solana"
  28. "github.com/certusone/wormhole/node/pkg/supervisor"
  29. "github.com/certusone/wormhole/node/pkg/vaa"
  30. ipfslog "github.com/ipfs/go-log/v2"
  31. )
  32. var (
  33. p2pNetworkID *string
  34. p2pPort *uint
  35. p2pBootstrap *string
  36. nodeKeyPath *string
  37. adminSocketPath *string
  38. statusAddr *string
  39. bridgeKeyPath *string
  40. solanaBridgeAddress *string
  41. ethRPC *string
  42. ethContract *string
  43. ethConfirmations *uint64
  44. solanaWsRPC *string
  45. solanaRPC *string
  46. agentRPC *string
  47. logLevel *string
  48. unsafeDevMode *bool
  49. devNumGuardians *uint
  50. nodeName *string
  51. )
  52. func init() {
  53. p2pNetworkID = BridgeCmd.Flags().String("network", "/wormhole/dev", "P2P network identifier")
  54. p2pPort = BridgeCmd.Flags().Uint("port", 8999, "P2P UDP listener port")
  55. p2pBootstrap = BridgeCmd.Flags().String("bootstrap", "", "P2P bootstrap peers (comma-separated)")
  56. statusAddr = BridgeCmd.Flags().String("statusAddr", "[::1]:6060", "Listen address for status server (disabled if blank)")
  57. nodeKeyPath = BridgeCmd.Flags().String("nodeKey", "", "Path to node key (will be generated if it doesn't exist)")
  58. adminSocketPath = BridgeCmd.Flags().String("adminSocket", "", "Admin gRPC service UNIX domain socket path")
  59. bridgeKeyPath = BridgeCmd.Flags().String("bridgeKey", "", "Path to guardian key (required)")
  60. solanaBridgeAddress = BridgeCmd.Flags().String("solanaBridgeAddress", "", "Address of the Solana Bridge Program (required)")
  61. ethRPC = BridgeCmd.Flags().String("ethRPC", "", "Ethereum RPC URL")
  62. ethContract = BridgeCmd.Flags().String("ethContract", "", "Ethereum bridge contract address")
  63. ethConfirmations = BridgeCmd.Flags().Uint64("ethConfirmations", 15, "Ethereum confirmation count requirement")
  64. solanaWsRPC = BridgeCmd.Flags().String("solanaWS", "", "Solana Websocket URL (required")
  65. solanaRPC = BridgeCmd.Flags().String("solanaRPC", "", "Solana RPC URL (required")
  66. agentRPC = BridgeCmd.Flags().String("agentRPC", "", "Solana agent sidecar gRPC socket path")
  67. logLevel = BridgeCmd.Flags().String("logLevel", "info", "Logging level (debug, info, warn, error, dpanic, panic, fatal)")
  68. unsafeDevMode = BridgeCmd.Flags().Bool("unsafeDevMode", false, "Launch node in unsafe, deterministic devnet mode")
  69. devNumGuardians = BridgeCmd.Flags().Uint("devNumGuardians", 5, "Number of devnet guardians to include in guardian set")
  70. nodeName = BridgeCmd.Flags().String("nodeName", "", "Node name to announce in gossip heartbeats")
  71. }
  72. var (
  73. rootCtx context.Context
  74. rootCtxCancel context.CancelFunc
  75. )
  76. // "Why would anyone do this?" are famous last words.
  77. //
  78. // We already forcibly override RPC URLs and keys in dev mode to prevent security
  79. // risks from operator error, but an extra warning won't hurt.
  80. const devwarning = `
  81. +++++++++++++++++++++++++++++++++++++++++++++++++++
  82. | NODE IS RUNNING IN INSECURE DEVELOPMENT MODE |
  83. | |
  84. | Do not use -unsafeDevMode in prod. |
  85. +++++++++++++++++++++++++++++++++++++++++++++++++++
  86. `
  87. // lockMemory locks current and future pages in memory to protect secret keys from being swapped out to disk.
  88. // It's possible (and strongly recommended) to deploy Wormhole such that keys are only ever
  89. // stored in memory and never touch the disk. This is a privileged operation and requires CAP_IPC_LOCK.
  90. func lockMemory() {
  91. err := unix.Mlockall(syscall.MCL_CURRENT | syscall.MCL_FUTURE)
  92. if err != nil {
  93. fmt.Printf("Failed to lock memory: %v (CAP_IPC_LOCK missing?)\n", err)
  94. os.Exit(1)
  95. }
  96. }
  97. // setRestrictiveUmask masks the group and world bits. This ensures that key material
  98. // and sockets we create aren't accidentally group- or world-readable.
  99. func setRestrictiveUmask() {
  100. syscall.Umask(0077) // cannot fail
  101. }
  102. // BridgeCmd represents the bridge command
  103. var BridgeCmd = &cobra.Command{
  104. Use: "bridge",
  105. Short: "Run the bridge server",
  106. Run: runBridge,
  107. }
  108. func runBridge(cmd *cobra.Command, args []string) {
  109. if *unsafeDevMode {
  110. fmt.Print(devwarning)
  111. }
  112. lockMemory()
  113. setRestrictiveUmask()
  114. // Set up logging. The go-log zap wrapper that libp2p uses is compatible with our
  115. // usage of zap in supervisor, which is nice.
  116. lvl, err := ipfslog.LevelFromString(*logLevel)
  117. if err != nil {
  118. fmt.Println("Invalid log level")
  119. os.Exit(1)
  120. }
  121. logger := zap.New(zapcore.NewCore(
  122. consoleEncoder{zapcore.NewConsoleEncoder(
  123. zap.NewDevelopmentEncoderConfig())},
  124. zapcore.AddSync(zapcore.Lock(os.Stderr)),
  125. zap.NewAtomicLevelAt(zapcore.Level(lvl))))
  126. if *unsafeDevMode {
  127. // Use the hostname as nodeName. For production, we don't want to do this to
  128. // prevent accidentally leaking sensitive hostnames.
  129. hostname, err := os.Hostname()
  130. if err != nil {
  131. panic(err)
  132. }
  133. *nodeName = hostname
  134. // Put node name into the log for development.
  135. logger = logger.Named(*nodeName)
  136. }
  137. // Redirect ipfs logs to plain zap
  138. ipfslog.SetPrimaryCore(logger.Core())
  139. // Override the default go-log config, which uses a magic environment variable.
  140. ipfslog.SetAllLoggers(lvl)
  141. // Register components for readiness checks.
  142. readiness.RegisterComponent(common.ReadinessEthSyncing)
  143. readiness.RegisterComponent(common.ReadinessSolanaSyncing)
  144. if *statusAddr != "" {
  145. // Use a custom routing instead of using http.DefaultServeMux directly to avoid accidentally exposing packages
  146. // that register themselves with it by default (like pprof).
  147. router := mux.NewRouter()
  148. // pprof server. NOT necessarily safe to expose publicly - only enable it in dev mode to avoid exposing it by
  149. // accident. There's benefit to having pprof enabled on production nodes, but we would likely want to expose it
  150. // via a dedicated port listening on localhost, or via the admin UNIX socket.
  151. if *unsafeDevMode {
  152. // Pass requests to http.DefaultServeMux, which pprof automatically registers with as an import side-effect.
  153. router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)
  154. }
  155. // Simple endpoint exposing node readiness (safe to expose to untrusted clients)
  156. router.HandleFunc("/readyz", readiness.Handler)
  157. // Prometheus metrics (safe to expose to untrusted clients)
  158. router.Handle("/metrics", promhttp.Handler())
  159. go func() {
  160. logger.Info("status server listening on [::]:6060")
  161. logger.Error("status server crashed", zap.Error(http.ListenAndServe(*statusAddr, router)))
  162. }()
  163. }
  164. // In devnet mode, we automatically set a number of flags that rely on deterministic keys.
  165. if *unsafeDevMode {
  166. g0key, err := peer.IDFromPrivateKey(devnet.DeterministicP2PPrivKeyByIndex(0))
  167. if err != nil {
  168. panic(err)
  169. }
  170. // Use the first guardian node as bootstrap
  171. *p2pBootstrap = fmt.Sprintf("/dns4/guardian-0.guardian/udp/%d/quic/p2p/%s", *p2pPort, g0key.String())
  172. // Deterministic ganache ETH devnet address.
  173. *ethContract = devnet.GanacheBridgeContractAddress.Hex()
  174. // Use the hostname as nodeName. For production, we don't want to do this to
  175. // prevent accidentally leaking sensitive hostnames.
  176. hostname, err := os.Hostname()
  177. if err != nil {
  178. panic(err)
  179. }
  180. *nodeName = hostname
  181. }
  182. // Verify flags
  183. if *nodeKeyPath == "" && !*unsafeDevMode { // In devnet mode, keys are deterministically generated.
  184. logger.Fatal("Please specify --nodeKey")
  185. }
  186. if *bridgeKeyPath == "" {
  187. logger.Fatal("Please specify --bridgeKey")
  188. }
  189. if *adminSocketPath == "" {
  190. logger.Fatal("Please specify --adminSocket")
  191. }
  192. if *agentRPC == "" {
  193. logger.Fatal("Please specify --agentRPC")
  194. }
  195. if *ethRPC == "" {
  196. logger.Fatal("Please specify --ethRPC")
  197. }
  198. if *ethContract == "" {
  199. logger.Fatal("Please specify --ethContract")
  200. }
  201. if *nodeName == "" {
  202. logger.Fatal("Please specify --nodeName")
  203. }
  204. if *solanaBridgeAddress == "" {
  205. logger.Fatal("Please specify --solanaBridgeAddress")
  206. }
  207. if *solanaWsRPC == "" {
  208. logger.Fatal("Please specify --solanaWsUrl")
  209. }
  210. if *solanaRPC == "" {
  211. logger.Fatal("Please specify --solanaUrl")
  212. }
  213. ethContractAddr := eth_common.HexToAddress(*ethContract)
  214. solBridgeAddress, err := solana_types.PublicKeyFromBase58(*solanaBridgeAddress)
  215. if err != nil {
  216. logger.Fatal("invalid Solana bridge address", zap.Error(err))
  217. }
  218. // In devnet mode, we generate a deterministic guardian key and write it to disk.
  219. if *unsafeDevMode {
  220. gk, err := generateDevnetGuardianKey()
  221. if err != nil {
  222. logger.Fatal("failed to generate devnet guardian key", zap.Error(err))
  223. }
  224. err = writeGuardianKey(gk, "auto-generated deterministic devnet key", *bridgeKeyPath, true)
  225. if err != nil {
  226. logger.Fatal("failed to write devnet guardian key", zap.Error(err))
  227. }
  228. }
  229. // Guardian key
  230. gk, err := loadGuardianKey(*bridgeKeyPath)
  231. if err != nil {
  232. logger.Fatal("failed to load guardian key", zap.Error(err))
  233. }
  234. guardianAddr := ethcrypto.PubkeyToAddress(gk.PublicKey).String()
  235. logger.Info("Loaded guardian key", zap.String(
  236. "address", guardianAddr))
  237. p2p.DefaultRegistry.SetGuardianAddress(guardianAddr)
  238. // Node's main lifecycle context.
  239. rootCtx, rootCtxCancel = context.WithCancel(context.Background())
  240. defer rootCtxCancel()
  241. // Ethereum lock event channel
  242. lockC := make(chan *common.ChainLock)
  243. // Ethereum incoming guardian set updates
  244. setC := make(chan *common.GuardianSet)
  245. // Outbound gossip message queue
  246. sendC := make(chan []byte)
  247. // Inbound observations
  248. obsvC := make(chan *gossipv1.SignedObservation, 50)
  249. // Inbound observation requests from the p2p service (for all chains)
  250. obsvReqC := make(chan *gossipv1.ObservationRequest, 50)
  251. // Outbound observation requests
  252. obsvReqSendC := make(chan *gossipv1.ObservationRequest)
  253. // VAAs to submit to Solana
  254. solanaVaaC := make(chan *vaa.VAA)
  255. // Injected VAAs (manually generated rather than created via observation)
  256. injectC := make(chan *vaa.VAA)
  257. // Guardian set state managed by processor
  258. gst := common.NewGuardianSetState()
  259. // Load p2p private key
  260. var priv crypto.PrivKey
  261. if *unsafeDevMode {
  262. idx, err := devnet.GetDevnetIndex()
  263. if err != nil {
  264. logger.Fatal("Failed to parse hostname - are we running in devnet?")
  265. }
  266. priv = devnet.DeterministicP2PPrivKeyByIndex(int64(idx))
  267. } else {
  268. priv, err = getOrCreateNodeKey(logger, *nodeKeyPath)
  269. if err != nil {
  270. logger.Fatal("Failed to load node key", zap.Error(err))
  271. }
  272. }
  273. adminService, err := adminServiceRunnable(logger, *adminSocketPath, injectC, obsvReqSendC, gst)
  274. if err != nil {
  275. logger.Fatal("failed to create admin service socket", zap.Error(err))
  276. }
  277. // Run supervisor.
  278. supervisor.New(rootCtx, logger, func(ctx context.Context) error {
  279. if err := supervisor.Run(ctx, "p2p", p2p.Run(
  280. obsvC,
  281. sendC,
  282. obsvReqC,
  283. obsvReqSendC,
  284. priv,
  285. gk,
  286. gst,
  287. *p2pPort,
  288. *p2pNetworkID,
  289. *p2pBootstrap,
  290. *nodeName,
  291. rootCtxCancel)); err != nil {
  292. return err
  293. }
  294. if err := supervisor.Run(ctx, "ethwatch",
  295. ethereum.NewEthBridgeWatcher(*ethRPC, ethContractAddr, *ethConfirmations, lockC, setC, obsvReqC).Run); err != nil {
  296. return err
  297. }
  298. if err := supervisor.Run(ctx, "solvaa",
  299. solana.NewSolanaVAASubmitter(*agentRPC, solanaVaaC, false).Run); err != nil {
  300. return err
  301. }
  302. if err := supervisor.Run(ctx, "solwatch",
  303. solana.NewSolanaWatcher(*solanaWsRPC, *solanaRPC, solBridgeAddress, lockC).Run); err != nil {
  304. return err
  305. }
  306. // TODO: this thing has way too many arguments at this point - make it an options struct
  307. p := processor.NewProcessor(ctx,
  308. lockC,
  309. setC,
  310. sendC,
  311. obsvC,
  312. solanaVaaC,
  313. injectC,
  314. gk,
  315. gst,
  316. *unsafeDevMode,
  317. *devNumGuardians,
  318. *ethRPC,
  319. )
  320. if err := supervisor.Run(ctx, "processor", p.Run); err != nil {
  321. return err
  322. }
  323. if err := supervisor.Run(ctx, "admin", adminService); err != nil {
  324. return err
  325. }
  326. logger.Info("Started internal services")
  327. select {
  328. case <-ctx.Done():
  329. return nil
  330. }
  331. },
  332. // It's safer to crash and restart the process in case we encounter a panic,
  333. // rather than attempting to reschedule the runnable.
  334. supervisor.WithPropagatePanic)
  335. select {
  336. case <-rootCtx.Done():
  337. logger.Info("root context cancelled, exiting...")
  338. // TODO: wait for things to shut down gracefully
  339. }
  340. }